Overview

Packages

  • Auth

Classes

  • Horde_Auth
  • Horde_Auth_Auto
  • Horde_Auth_Base
  • Horde_Auth_Composite
  • Horde_Auth_Customsql
  • Horde_Auth_Cyrsql
  • Horde_Auth_Exception
  • Horde_Auth_Ftp
  • Horde_Auth_Http
  • Horde_Auth_Http_Remote
  • Horde_Auth_Imap
  • Horde_Auth_Ipbasic
  • Horde_Auth_Kolab
  • Horde_Auth_Ldap
  • Horde_Auth_Login
  • Horde_Auth_Msad
  • Horde_Auth_Pam
  • Horde_Auth_Passwd
  • Horde_Auth_Peclsasl
  • Horde_Auth_Radius
  • Horde_Auth_Shibboleth
  • Horde_Auth_Smb
  • Horde_Auth_Smbclient
  • Horde_Auth_Sql
  • Horde_Auth_Translation
  • Overview
  • Package
  • Class
  • Tree
  1: <?php
  2: /**
  3:  * The Horde_Auth_Passwd:: class provides a passwd-file implementation of
  4:  * the Horde authentication system.
  5:  *
  6:  * Copyright 1997-2007 Rasmus Lerdorf <rasmus@php.net>
  7:  * Copyright 2002-2012 Horde LLC (http://www.horde.org/)
  8:  *
  9:  * See the enclosed file COPYING for license information (LGPL). If you did
 10:  * not receive this file, http://www.horde.org/licenses/lgpl21
 11:  *
 12:  * @author   Rasmus Lerdorf <rasmus@php.net>
 13:  * @author   Chuck Hagenbuch <chuck@horde.org>
 14:  * @category Horde
 15:  * @license http://www.horde.org/licenses/lgpl21 LGPL-2.1
 16:  * @package  Auth
 17:  */
 18: class Horde_Auth_Passwd extends Horde_Auth_Base
 19: {
 20:     /**
 21:      * An array of capabilities, so that the driver can report which
 22:      * operations it supports and which it doesn't.
 23:      *
 24:      * @var array
 25:      */
 26:     protected $_capabilities = array(
 27:         'list' => true,
 28:         'authenticate' => true,
 29:     );
 30: 
 31:     /**
 32:      * Hash list of users.
 33:      *
 34:      * @var array
 35:      */
 36:     protected $_users = null;
 37: 
 38:     /**
 39:      * Array of groups and members.
 40:      *
 41:      * @var array
 42:      */
 43:     protected $_groups = array();
 44: 
 45:     /**
 46:      * Filehandle for lockfile.
 47:      *
 48:      * @var resource
 49:      */
 50:     protected $_fplock;
 51: 
 52:     /**
 53:      * Locking state.
 54:      *
 55:      * @var boolean
 56:      */
 57:     protected $_locked;
 58: 
 59:     /**
 60:      * List of users that should be excluded from being listed/handled
 61:      * in any way by this driver.
 62:      *
 63:      * @var array
 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:      * Constructor.
 74:      *
 75:      * @param array $params  Connection parameters:
 76:      * <pre>
 77:      * 'encryption' - (string) The encryption to use to store the password in
 78:      *                the table (e.g. plain, crypt, md5-hex, md5-base64, smd5,
 79:      *                sha, ssha, aprmd5).
 80:      *                DEFAULT: 'crypt-des'
 81:      * 'filename' - (string) [REQUIRED] The passwd file to use.
 82:      * 'lock' - (boolean) Should we lock the passwd file? The password file
 83:      *          cannot be changed (add, edit, or delete users) unless this is
 84:      *          true.
 85:      *          DEFAULT: false
 86:      * 'show_encryption' - (boolean) Whether or not to prepend the encryption
 87:      *                     in the password field.
 88:      *                     DEFAULT: false
 89:      * </pre>
 90:      *
 91:      * @throws InvalidArgumentException
 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:      * Writes changes to passwd file and unlocks it.  Takes no arguments and
110:      * has no return value. Called on script shutdown.
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:      * Queries the current Auth object to find out if it supports the given
131:      * capability.
132:      *
133:      * @param string $capability  The capability to test for.
134:      *
135:      * @return boolean  Whether or not the capability is supported.
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:      * Read and, if requested, lock the password file.
154:      *
155:      * @throws Horde_Auth_Exception
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:      * Find out if a set of login credentials are valid.
243:      *
244:      * @param string $userId      The userId to check.
245:      * @param array $credentials  An array of login credentials.
246:      *
247:      * @throws Horde_Auth_Exception
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:      * List all users in the system.
286:      *
287:      * @return array  The array of userIds.
288:      * @throws Horde_Auth_Exception
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:      * Add a set of authentication credentials.
308:      *
309:      * @param string $userId      The userId to add.
310:      * @param array $credentials  The credentials to add.
311:      *
312:      * @throws Horde_Auth_Exception
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:      * Update a set of authentication credentials.
337:      *
338:      * @param string $oldID        The old userId.
339:      * @param string $newID        The new userId.
340:      * @param array  $credentials  The new credentials
341:      *
342:      * @throws Horde_Auth_Exception
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:      * Reset a user's password. Used for example when the user does not
367:      * remember the existing password.
368:      *
369:      * @param string $userId  The user id for which to reset the password.
370:      *
371:      * @return string  The new password.
372:      * @throws Horde_Auth_Exception
373:      */
374:     public function resetPassword($userId)
375:     {
376:         /* Get a new random password. */
377:         $password = Horde_Auth::genRandomPassword();
378:         $this->updateUser($userId, $userId, array('password' => $password));
379: 
380:         return $password;
381:     }
382: 
383:     /**
384:      * Delete a set of authentication credentials.
385:      *
386:      * @param string $userId  The userId to delete.
387:      *
388:      * @throws Horde_Auth_Exception
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:      * Compare an encrypted password to a plaintext string to see if
408:      * they match.
409:      *
410:      * @param string $encrypted  The crypted password to compare against.
411:      * @param string $plaintext  The plaintext password to verify.
412:      *
413:      * @return boolean  True if matched, false otherwise.
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: 
API documentation generated by ApiGen