Files
server/usr/share/psa-pear/pear/php/Horde/Auth/Passwd.php
2026-01-07 20:52:11 +01:00

436 lines
13 KiB
PHP

<?php
/**
* Copyright 1997-2007 Rasmus Lerdorf <rasmus@php.net>
* Copyright 2002-2017 Horde LLC (http://www.horde.org/)
*
* See the enclosed file COPYING for license information (LGPL). If you did
* not receive this file, see http://www.horde.org/licenses/lgpl21.
*
* @author Rasmus Lerdorf <rasmus@php.net>
* @author Chuck Hagenbuch <chuck@horde.org>
* @category Horde
* @license http://www.horde.org/licenses/lgpl21 LGPL-2.1
* @package Auth
*/
/**
* The Horde_Auth_Passwd class provides a passwd-file implementation of the
* Horde authentication system.
*
* @author Rasmus Lerdorf <rasmus@php.net>
* @author Chuck Hagenbuch <chuck@horde.org>
* @category Horde
* @copyright 1997-2007 Rasmus Lerdorf <rasmus@php.net>
* @copyright 2002-2017 Horde LLC
* @license http://www.horde.org/licenses/lgpl21 LGPL-2.1
* @package Auth
*/
class Horde_Auth_Passwd extends Horde_Auth_Base
{
/**
* An array of capabilities, so that the driver can report which
* operations it supports and which it doesn't.
*
* @var array
*/
protected $_capabilities = array(
'list' => true,
'authenticate' => true,
);
/**
* Hash list of users.
*
* @var array
*/
protected $_users = null;
/**
* Array of groups and members.
*
* @var array
*/
protected $_groups = array();
/**
* Filehandle for lockfile.
*
* @var resource
*/
protected $_fplock;
/**
* Locking state.
*
* @var boolean
*/
protected $_locked;
/**
* List of users that should be excluded from being listed/handled
* in any way by this driver.
*
* @var array
*/
protected $_exclude = array(
'root', 'daemon', 'bin', 'sys', 'sync', 'games', 'man', 'lp', 'mail',
'news', 'uucp', 'proxy', 'postgres', 'www-data', 'backup', 'operator',
'list', 'irc', 'gnats', 'nobody', 'identd', 'sshd', 'gdm', 'postfix',
'mysql', 'cyrus', 'ftp',
);
/**
* Constructor.
*
* @param array $params Connection parameters:
* <pre>
* 'encryption' - (string) The encryption to use to store the password in
* the table (e.g. plain, crypt, md5-hex, md5-base64, smd5,
* sha, ssha, aprmd5).
* DEFAULT: 'crypt-des'
* 'filename' - (string) [REQUIRED] The passwd file to use.
* 'lock' - (boolean) Should we lock the passwd file? The password file
* cannot be changed (add, edit, or delete users) unless this is
* true.
* DEFAULT: false
* 'show_encryption' - (boolean) Whether or not to prepend the encryption
* in the password field.
* DEFAULT: false
* </pre>
*
* @throws InvalidArgumentException
*/
public function __construct(array $params = array())
{
if (!isset($params['filename'])) {
throw new InvalidArgumentException('Missing filename parameter.');
}
$params = array_merge(array(
'encryption' => 'crypt-des',
'lock' => false,
'show_encryption' => false
), $params);
parent::__construct($params);
}
/**
* Writes changes to passwd file and unlocks it. Takes no arguments and
* has no return value. Called on script shutdown.
*/
public function __destruct()
{
if ($this->_locked) {
foreach ($this->_users as $user => $pass) {
$data = $user . ':' . $pass;
if ($this->_users[$user]) {
$data .= ':' . $this->_users[$user];
}
fputs($this->_fplock, $data . "\n");
}
rename($this->_lockfile, $this->_params['filename']);
flock($this->_fplock, LOCK_UN);
$this->_locked = false;
fclose($this->_fplock);
}
}
/**
* Queries the current Auth object to find out if it supports the given
* capability.
*
* @param string $capability The capability to test for.
*
* @return boolean Whether or not the capability is supported.
*/
public function hasCapability($capability)
{
if ($this->_params['lock']) {
switch ($capability) {
case 'add':
case 'update':
case 'resetpassword':
case 'remove':
return true;
}
}
return parent::hasCapability($capability);
}
/**
* Read and, if requested, lock the password file.
*
* @throws Horde_Auth_Exception
*/
protected function _read()
{
if (is_array($this->_users)) {
return;
}
if (empty($this->_params['filename'])) {
throw new Horde_Auth_Exception('No password file set.');
}
if ($this->_params['lock']) {
$this->_fplock = fopen(sys_get_temp_dir() . '/passwd.lock', 'w');
flock($this->_fplock, LOCK_EX);
$this->_locked = true;
}
$fp = fopen($this->_params['filename'], 'r');
if (!$fp) {
throw new Horde_Auth_Exception("Couldn't open '" . $this->_params['filename'] . "'.");
}
$this->_users = array();
while (!feof($fp)) {
$line = trim(fgets($fp, 256));
if (empty($line)) {
continue;
}
$parts = explode(':', $line);
if (!count($parts)) {
continue;
}
$user = $parts[0];
$userinfo = array();
if (strlen($user) && !in_array($user, $this->_exclude)) {
if (isset($parts[1])) {
$userinfo['password'] = $parts[1];
}
if (isset($parts[2])) {
$userinfo['uid'] = $parts[2];
}
if (isset($parts[3])) {
$userinfo['gid'] = $parts[3];
}
if (isset($parts[4])) {
$userinfo['info'] = $parts[4];
}
if (isset($parts[5])) {
$userinfo['home'] = $parts[5];
}
if (isset($parts[6])) {
$userinfo['shell'] = $parts[6];
}
$this->_users[$user] = $userinfo;
}
}
fclose($fp);
if (!empty($this->_params['group_filename'])) {
$fp = fopen($this->_params['group_filename'], 'r');
if (!$fp) {
throw new Horde_Auth_Exception("Couldn't open '" . $this->_params['group_filename'] . "'.");
}
$this->_groups = array();
while (!feof($fp)) {
$line = trim(fgets($fp));
if (empty($line)) {
continue;
}
$parts = explode(':', $line);
$group = array_shift($parts);
$users = array_pop($parts);
$this->_groups[$group] = array_flip(preg_split('/\s*[,\s]\s*/', trim($users), -1, PREG_SPLIT_NO_EMPTY));
}
fclose($fp);
}
}
/**
* Find out if a set of login credentials are valid.
*
* @param string $userId The userId to check.
* @param array $credentials An array of login credentials.
*
* @throws Horde_Auth_Exception
*/
protected function _authenticate($userId, $credentials)
{
if (empty($credentials['password'])) {
throw new Horde_Auth_Exception('', Horde_Auth::REASON_BADLOGIN);
}
try {
$this->_read();
} catch (Horde_Auth_Exception $e) {
if ($this->_logger) {
$this->_logger->log($e, 'ERR');
}
throw new Horde_Auth_Exception('', Horde_Auth::REASON_FAILED);
}
if (!isset($this->_users[$userId]) ||
!$this->_comparePasswords($this->_users[$userId]['password'], $credentials['password'])) {
throw new Horde_Auth_Exception('', Horde_Auth::REASON_BADLOGIN);
}
if (!empty($this->_params['required_groups'])) {
$allowed = false;
foreach ($this->_params['required_groups'] as $group) {
if (isset($this->_groups[$group][$userId])) {
$allowed = true;
break;
}
}
if (!$allowed) {
throw new Horde_Auth_Exception('', Horde_Auth::REASON_BADLOGIN);
}
}
}
/**
* Lists all users in the system.
*
* @param boolean $sort Sort the users?
*
* @return array The array of userIds.
* @throws Horde_Auth_Exception
*/
public function listUsers($sort = false)
{
$this->_read();
$users = array_keys($this->_users);
if (empty($this->_params['required_groups'])) {
return $this->_sort($users, $sort);
}
$groupUsers = array();
foreach ($this->_params['required_groups'] as $group) {
$groupUsers = array_merge($groupUsers, array_intersect($users, array_keys($this->_groups[$group])));
}
return $this->_sort($groupUsers, $sort);
}
/**
* Add a set of authentication credentials.
*
* @param string $userId The userId to add.
* @param array $credentials The credentials to add.
*
* @throws Horde_Auth_Exception
*/
public function addUser($userId, $credentials)
{
$this->_read();
if (!$this->_locked) {
throw new Horde_Auth_Exception('Password file not locked');
}
if (isset($this->_users[$userId])) {
throw new Horde_Auth_Exception("Couldn't add user '$userId', because the user already exists.");
}
$this->_users[$userId] = array(
'password' => Horde_Auth::getCryptedPassword($credentials['password'],
'',
$this->_params['encryption'],
$this->_params['show_encryption']),
);
}
/**
* Update a set of authentication credentials.
*
* @param string $oldID The old userId.
* @param string $newID The new userId.
* @param array $credentials The new credentials
*
* @throws Horde_Auth_Exception
*/
public function updateUser($oldID, $newID, $credentials)
{
$this->_read();
if (!$this->_locked) {
throw new Horde_Auth_Exception('Password file not locked');
}
if (!isset($this->_users[$oldID])) {
throw new Horde_Auth_Exception("Couldn't modify user '$oldID', because the user doesn't exist.");
}
$this->_users[$newID] = array(
'password' => Horde_Auth::getCryptedPassword($credentials['password'],
'',
$this->_params['encryption'],
$this->_params['show_encryption']),
);
return true;
}
/**
* Reset a user's password. Used for example when the user does not
* remember the existing password.
*
* @param string $userId The user id for which to reset the password.
*
* @return string The new password.
* @throws Horde_Auth_Exception
*/
public function resetPassword($userId)
{
/* Get a new random password. */
$password = Horde_Auth::genRandomPassword();
$this->updateUser($userId, $userId, array('password' => $password));
return $password;
}
/**
* Delete a set of authentication credentials.
*
* @param string $userId The userId to delete.
*
* @throws Horde_Auth_Exception
*/
public function removeUser($userId)
{
$this->_read();
if (!$this->_locked) {
throw new Horde_Auth_Exception('Password file not locked');
}
if (!isset($this->_users[$userId])) {
throw new Horde_Auth_Exception("Couldn't delete user '$userId', because the user doesn't exist.");
}
unset($this->_users[$userId]);
}
/**
* Compare an encrypted password to a plaintext string to see if
* they match.
*
* @param string $encrypted The crypted password to compare against.
* @param string $plaintext The plaintext password to verify.
*
* @return boolean True if matched, false otherwise.
*/
protected function _comparePasswords($encrypted, $plaintext)
{
return $encrypted == Horde_Auth::getCryptedPassword($plaintext,
$encrypted,
$this->_params['encryption'],
$this->_params['show_encryption']);
}
}