1: <?php
2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17:
18: class Horde_Auth_Passwd extends Horde_Auth_Base
19: {
20: 21: 22: 23: 24: 25:
26: protected $_capabilities = array(
27: 'list' => true,
28: 'authenticate' => true,
29: );
30:
31: 32: 33: 34: 35:
36: protected $_users = null;
37:
38: 39: 40: 41: 42:
43: protected $_groups = array();
44:
45: 46: 47: 48: 49:
50: protected $_fplock;
51:
52: 53: 54: 55: 56:
57: protected $_locked;
58:
59: 60: 61: 62: 63: 64:
65: protected $_exclude = array(
66: 'root', 'daemon', 'bin', 'sys', 'sync', 'games', 'man', 'lp', 'mail',
67: 'news', 'uucp', 'proxy', 'postgres', 'www-data', 'backup', 'operator',
68: 'list', 'irc', 'gnats', 'nobody', 'identd', 'sshd', 'gdm', 'postfix',
69: 'mysql', 'cyrus', 'ftp',
70: );
71:
72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91: 92:
93: public function __construct(array $params = array())
94: {
95: if (!isset($params['filename'])) {
96: throw new InvalidArgumentException('Missing filename parameter.');
97: }
98:
99: $params = array_merge(array(
100: 'encryption' => 'crypt-des',
101: 'lock' => false,
102: 'show_encryption' => false
103: ), $params);
104:
105: parent::__construct($params);
106: }
107:
108: 109: 110: 111:
112: public function __destruct()
113: {
114: if ($this->_locked) {
115: foreach ($this->_users as $user => $pass) {
116: $data = $user . ':' . $pass;
117: if ($this->_users[$user]) {
118: $data .= ':' . $this->_users[$user];
119: }
120: fputs($this->_fplock, $data . "\n");
121: }
122: rename($this->_lockfile, $this->_params['filename']);
123: flock($this->_fplock, LOCK_UN);
124: $this->_locked = false;
125: fclose($this->_fplock);
126: }
127: }
128:
129: 130: 131: 132: 133: 134: 135: 136:
137: public function hasCapability($capability)
138: {
139: if ($this->_params['lock']) {
140: switch ($capability) {
141: case 'add':
142: case 'update':
143: case 'resetpassword':
144: case 'remove':
145: return true;
146: }
147: }
148:
149: return parent::hasCapability($capability);
150: }
151:
152: 153: 154: 155: 156:
157: protected function _read()
158: {
159: if (is_array($this->_users)) {
160: return;
161: }
162:
163: if (empty($this->_params['filename'])) {
164: throw new Horde_Auth_Exception('No password file set.');
165: }
166:
167: if ($this->_params['lock']) {
168: $this->_fplock = fopen(Horde_Util::getTempDir() . '/passwd.lock', 'w');
169: flock($this->_fplock, LOCK_EX);
170: $this->_locked = true;
171: }
172:
173: $fp = fopen($this->_params['filename'], 'r');
174: if (!$fp) {
175: throw new Horde_Auth_Exception("Couldn't open '" . $this->_params['filename'] . "'.");
176: }
177:
178: $this->_users = array();
179: while (!feof($fp)) {
180: $line = trim(fgets($fp, 256));
181: if (empty($line)) {
182: continue;
183: }
184:
185: $parts = explode(':', $line);
186: if (!count($parts)) {
187: continue;
188: }
189:
190: $user = $parts[0];
191: $userinfo = array();
192: if (strlen($user) && !in_array($user, $this->_exclude)) {
193: if (isset($parts[1])) {
194: $userinfo['password'] = $parts[1];
195: }
196: if (isset($parts[2])) {
197: $userinfo['uid'] = $parts[2];
198: }
199: if (isset($parts[3])) {
200: $userinfo['gid'] = $parts[3];
201: }
202: if (isset($parts[4])) {
203: $userinfo['info'] = $parts[4];
204: }
205: if (isset($parts[5])) {
206: $userinfo['home'] = $parts[5];
207: }
208: if (isset($parts[6])) {
209: $userinfo['shell'] = $parts[6];
210: }
211:
212: $this->_users[$user] = $userinfo;
213: }
214: }
215:
216: fclose($fp);
217:
218: if (!empty($this->_params['group_filename'])) {
219: $fp = fopen($this->_params['group_filename'], 'r');
220: if (!$fp) {
221: throw new Horde_Auth_Exception("Couldn't open '" . $this->_params['group_filename'] . "'.");
222: }
223:
224: $this->_groups = array();
225: while (!feof($fp)) {
226: $line = trim(fgets($fp));
227: if (empty($line)) {
228: continue;
229: }
230:
231: $parts = explode(':', $line);
232: $group = array_shift($parts);
233: $users = array_pop($parts);
234: $this->_groups[$group] = array_flip(preg_split('/\s*[,\s]\s*/', trim($users), -1, PREG_SPLIT_NO_EMPTY));
235: }
236:
237: fclose($fp);
238: }
239: }
240:
241: 242: 243: 244: 245: 246: 247: 248:
249: protected function _authenticate($userId, $credentials)
250: {
251: if (empty($credentials['password'])) {
252: throw new Horde_Auth_Exception('', Horde_Auth::REASON_BADLOGIN);
253: }
254:
255: try {
256: $this->_read();
257: } catch (Horde_Auth_Exception $e) {
258: if ($this->_logger) {
259: $this->_logger->log($e, 'ERR');
260: }
261: throw new Horde_Auth_Exception('', Horde_Auth::REASON_FAILED);
262: }
263:
264: if (!isset($this->_users[$userId]) ||
265: !$this->_comparePasswords($this->_users[$userId]['password'], $credentials['password'])) {
266: throw new Horde_Auth_Exception('', Horde_Auth::REASON_BADLOGIN);
267: }
268:
269: if (!empty($this->_params['required_groups'])) {
270: $allowed = false;
271: foreach ($this->_params['required_groups'] as $group) {
272: if (isset($this->_groups[$group][$userId])) {
273: $allowed = true;
274: break;
275: }
276: }
277:
278: if (!$allowed) {
279: throw new Horde_Auth_Exception('', Horde_Auth::REASON_BADLOGIN);
280: }
281: }
282: }
283:
284: 285: 286: 287: 288: 289:
290: public function listUsers($sort = false)
291: {
292: $this->_read();
293:
294: $users = array_keys($this->_users);
295: if (empty($this->_params['required_groups'])) {
296: return $this->_sort($users, $sort);
297: }
298:
299: $groupUsers = array();
300: foreach ($this->_params['required_groups'] as $group) {
301: $groupUsers = array_merge($groupUsers, array_intersect($users, array_keys($this->_groups[$group])));
302: }
303: return $this->_sort($groupUsers, $sort);
304: }
305:
306: 307: 308: 309: 310: 311: 312: 313:
314: public function addUser($userId, $credentials)
315: {
316: $this->_read();
317:
318: if (!$this->_locked) {
319: throw new Horde_Auth_Exception('Password file not locked');
320: }
321:
322: if (isset($this->_users[$userId])) {
323: throw new Horde_Auth_Exception("Couldn't add user '$user', because the user already exists.");
324: }
325:
326: $this->_users[$userId] = array(
327: 'password' => Horde_Auth::getCryptedPassword($credentials['password'],
328: '',
329: $this->_params['encryption'],
330: $this->_params['show_encryption']),
331:
332: );
333: }
334:
335: 336: 337: 338: 339: 340: 341: 342: 343:
344: public function updateUser($oldID, $newID, $credentials)
345: {
346: $this->_read();
347:
348: if (!$this->_locked) {
349: throw new Horde_Auth_Exception('Password file not locked');
350: }
351:
352: if (!isset($this->_users[$oldID])) {
353: throw new Horde_Auth_Exception("Couldn't modify user '$oldID', because the user doesn't exist.");
354: }
355:
356: $this->_users[$newID] = array(
357: 'password' => Horde_Auth::getCryptedPassword($credentials['password'],
358: '',
359: $this->_params['encryption'],
360: $this->_params['show_encryption']),
361: );
362: return true;
363: }
364:
365: 366: 367: 368: 369: 370: 371: 372: 373:
374: public function resetPassword($userId)
375: {
376:
377: $password = Horde_Auth::genRandomPassword();
378: $this->updateUser($userId, $userId, array('password' => $password));
379:
380: return $password;
381: }
382:
383: 384: 385: 386: 387: 388: 389:
390: public function removeUser($userId)
391: {
392: $this->_read();
393:
394: if (!$this->_locked) {
395: throw new Horde_Auth_Exception('Password file not locked');
396: }
397:
398: if (!isset($this->_users[$userId])) {
399: throw new Horde_Auth_Exception("Couldn't delete user '$oldID', because the user doesn't exist.");
400: }
401:
402: unset($this->_users[$userId]);
403: }
404:
405:
406: 407: 408: 409: 410: 411: 412: 413: 414:
415: protected function _comparePasswords($encrypted, $plaintext)
416: {
417: return $encrypted == Horde_Auth::getCryptedPassword($plaintext,
418: $encrypted,
419: $this->_params['encryption'],
420: $this->_params['show_encryption']);
421: }
422:
423: }
424: