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

424 lines
15 KiB
PHP

<?php
/**
* 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 Ilya Krel <mail@krel.org>
* @author Jan Schneider <jan@horde.org>
* @category Horde
* @license http://www.horde.org/licenses/lgpl21 LGPL-2.1
* @package Auth
*/
/**
* The Horde_Auth_Cyrsql class provides a SQL implementation of the Horde
* authentication system for the Cyrus IMAP server. Most of the functionality
* is the same as for the SQL class; only what is different overrides the
* parent class implementations.
*
* The table structure for the auth system is as follows:
* <pre>
* CREATE TABLE accountuser (
* username VARCHAR(255) BINARY NOT NULL DEFAULT '',
* password VARCHAR(32) BINARY NOT NULL DEFAULT '',
* prefix VARCHAR(50) NOT NULL DEFAULT '',
* domain_name VARCHAR(255) NOT NULL DEFAULT '',
* UNIQUE KEY username (username)
* );
*
* CREATE TABLE adminuser (
* username VARCHAR(50) BINARY NOT NULL DEFAULT '',
* password VARCHAR(50) BINARY NOT NULL DEFAULT '',
* type INT(11) NOT NULL DEFAULT '0',
* SID VARCHAR(255) NOT NULL DEFAULT '',
* home VARCHAR(255) NOT NULL DEFAULT '',
* PRIMARY KEY (username)
* );
*
* CREATE TABLE alias (
* alias VARCHAR(255) NOT NULL DEFAULT '',
* dest LONGTEXT,
* username VARCHAR(50) NOT NULL DEFAULT '',
* status INT(11) NOT NULL DEFAULT '1',
* PRIMARY KEY (alias)
* );
*
* CREATE TABLE domain (
* domain_name VARCHAR(255) NOT NULL DEFAULT '',
* prefix VARCHAR(50) NOT NULL DEFAULT '',
* maxaccounts INT(11) NOT NULL DEFAULT '20',
* quota INT(10) NOT NULL DEFAULT '20000',
* transport VARCHAR(255) NOT NULL DEFAULT 'cyrus',
* freenames ENUM('YES','NO') NOT NULL DEFAULT 'NO',
* freeaddress ENUM('YES','NO') NOT NULL DEFAULT 'NO',
* PRIMARY KEY (domain_name),
* UNIQUE KEY prefix (prefix)
* );
*
* CREATE TABLE domainadmin (
* domain_name VARCHAR(255) NOT NULL DEFAULT '',
* adminuser VARCHAR(255) NOT NULL DEFAULT ''
* );
*
* CREATE TABLE search (
* search_id VARCHAR(255) NOT NULL DEFAULT '',
* search_sql TEXT NOT NULL,
* perpage INT(11) NOT NULL DEFAULT '0',
* timestamp TIMESTAMP(14) NOT NULL,
* PRIMARY KEY (search_id),
* KEY search_id (search_id)
* );
*
* CREATE TABLE virtual (
* alias VARCHAR(255) NOT NULL DEFAULT '',
* dest LONGTEXT,
* username VARCHAR(50) NOT NULL DEFAULT '',
* status INT(11) NOT NULL DEFAULT '1',
* KEY alias (alias)
* );
*
* CREATE TABLE log (
* id INT(11) NOT NULL AUTO_INCREMENT,
* msg TEXT NOT NULL,
* user VARCHAR(255) NOT NULL DEFAULT '',
* host VARCHAR(255) NOT NULL DEFAULT '',
* time DATETIME NOT NULL DEFAULT '2000-00-00 00:00:00',
* pid VARCHAR(255) NOT NULL DEFAULT '',
* PRIMARY KEY (id)
* );
* </pre>
*
* @author Ilya Krel <mail@krel.org>
* @author Jan Schneider <jan@horde.org>
* @category Horde
* @copyright 2002-2017 Horde LLC
* @license http://www.horde.org/licenses/lgpl21 LGPL-2.1
* @package Auth
*/
class Horde_Auth_Cyrsql extends Horde_Auth_Sql
{
/**
* An array of capabilities, so that the driver can report which
* operations it supports and which it doesn't.
*
* @var array
*/
protected $_capabilities = array(
'add' => true,
'list' => true,
'remove' => true,
'resetpassword' => false,
'update' => true,
'authenticate' => true,
);
/**
* Horde_Imap_Client object.
*
* @var Horde_Imap_Client_Base
*/
protected $_imap;
/**
* Constructor.
*
* @param array $params Parameters:
* - domain_field: (string) If set to anything other than 'none' this is
* used as field name where domain is stored.
* DEFAULT: 'domain_name'
* - folders: (array) An array of folders to create under username.
* DEFAULT: NONE
* - hidden_accounts: (array) An array of system accounts to hide from
* the user interface.
* DEFAULT: None.
* - imap: (Horde_Imap_Client_Base) [REQUIRED] An IMAP client object.
* - quota: (integer) The quota (in kilobytes) to grant on the mailbox.
* DEFAULT: NONE
* - userhierarchy: (string) The user hierarchy prefix (UTF-8).
* DEFAULT: 'user.'
*
* @throws InvalidArgumentException
*/
public function __construct(array $params = array())
{
if (!isset($params['imap']) ||
!($params['imap'] instanceof Horde_Imap_Client_Base)) {
throw new InvalidArgumentException('Missing imap parameter.');
}
$this->_imap = $params['imap'];
unset($params['imap']);
$params = array_merge(array(
'domain_field' => 'domain_name',
'folders' => array(),
'hidden_accounts' => array('cyrus'),
'quota' => null,
'userhierarchy' => 'user.'
), $params);
parent::__construct($params);
}
/**
* Find out if a set of login credentials are valid.
*
* @param string $userId The userId to check.
* @param array $credentials The credentials to use.
*
* @throws Horde_Auth_Exception
*/
protected function _authenticate($userId, $credentials)
{
if (!empty($this->_params['domain_field']) &&
($this->_params['domain_field'] != 'none')) {
/* Build the SQL query with domain. */
$query = sprintf('SELECT * FROM %s WHERE %s = ? AND %s = ?',
$this->_params['table'],
$this->_params['username_field'],
$this->_params['domain_field']);
$values = explode('@', $userId);
} else {
/* Build the SQL query without domain. */
$query = sprintf('SELECT * FROM %s WHERE %s = ?',
$this->_params['table'],
$this->_params['username_field']);
$values = array($userId);
}
try {
$row = $this->_db->selectOne($query, $values);
} catch (Horde_Db_Exception $e) {
throw new Horde_Auth_Exception('', Horde_Auth::REASON_FAILED);
}
if (!$row ||
!$this->_comparePasswords($row[$this->_params['password_field']], $credentials['password'])) {
throw new Horde_Auth_Exception('', Horde_Auth::REASON_BADLOGIN);
}
$now = time();
if (!empty($this->_params['hard_expiration_field']) &&
!empty($row[$this->_params['hard_expiration_field']]) &&
($now > $row[$this->_params['hard_expiration_field']])) {
throw new Horde_Auth_Exception('', Horde_Auth::REASON_EXPIRED);
}
if (!empty($this->_params['soft_expiration_field']) &&
!empty($row[$this->_params['soft_expiration_field']]) &&
($now > $row[$this->_params['soft_expiration_field']])) {
$this->setCredential('change', true);
}
}
/**
* Add a set of authentication credentials.
*
* @param string $userId The userId to add.
* @param array $credentials The credentials to add.
*
* @throw Horde_Auth_Exception
*/
public function addUser($userId, $credentials)
{
if (!empty($this->_params['domain_field']) &&
($this->_params['domain_field'] != 'none')) {
list($name, $domain) = explode('@', $userId);
$query = sprintf('INSERT INTO %s (%s, %s, %s) VALUES (?, ?, ?)',
$this->_params['table'],
$this->_params['username_field'],
$this->_params['domain_field'],
$this->_params['password_field']);
$values = array(
$name,
$domain,
Horde_Auth::getCryptedPassword($credentials['password'],
'',
$this->_params['encryption'],
$this->_params['show_encryption'])
);
$query2 = 'INSERT INTO virtual (alias, dest, username, status) VALUES (?, ?, ?, 1)';
$values2 = array($userId, $userId, $name);
try {
$this->_db->insert($query, $values);
$this->_db->insert($query2, $values2);
} catch (Horde_Db_Exception $e) {
throw new Horde_Auth_Exception($e);
}
} else {
parent::addUser($userId, $credentials);
}
$mailbox = $this->_params['userhierarchy'] . $userId;
try {
$this->_imap->createMailbox($mailbox);
$this->_imap->setACL($mailbox, $this->_params['cyradmin'], array('rights' => 'lrswipcda'));
if (isset($this->_params['quota']) &&
($this->_params['quota'] >= 0)) {
$this->_imap->setQuota($mailbox, array('storage' => $this->_params['quota']));
}
} catch (Horde_Imap_Client_Exception $e) {
throw new Horde_Auth_Exception($e);
}
foreach ($this->_params['folders'] as $val) {
try {
$this->_imap->createMailbox($val);
$this->_imap->setACL($val, $this->_params['cyradmin'], array('rights' => 'lrswipcda'));
} catch (Horde_Imap_Client_Exception $e) {}
}
}
/**
* Delete a set of authentication credentials.
*
* @param string $userId The userId to delete.
*
* @throws Horde_Auth_Exception
*/
public function removeUser($userId)
{
if (!empty($this->_params['domain_field']) &&
($this->_params['domain_field'] != 'none')) {
list($name, $domain) = explode('@', $userId);
/* Build the SQL query. */
$query = sprintf('DELETE FROM %s WHERE %s = ? and %s = ?',
$this->_params['table'],
$this->_params['username_field'],
$this->_params['domain_field']);
$values = array($name, $domain);
$query2 = 'DELETE FROM virtual WHERE dest = ?';
$values2 = array($userId);
try {
$this->_db->delete($query, $values);
$this->_db->delete($query2, $values2);
} catch (Horde_Db_Exception $e) {
throw new Horde_Auth_Exception($e);
}
} else {
parent::removeUser($userId);
}
/* Set ACL for mailbox deletion. */
list($admin) = explode('@', $this->_params['cyradmin']);
$mailbox = $this->_params['userhierarchy'] . $userId;
try {
$this->_imap->setACL($mailbox, $admin, array('rights' => 'lrswipcda'));
$this->_imap->deleteMailbox($mailbox);
} catch (Horde_Imap_Client_Exception $e) {
throw new Horde_Auth_Exception($e);
}
}
/**
* List all users in the system.
*
* @param boolean $sort Sort the users?
*
* @return mixed The array of userIds.
* @throws Horde_Auth_Exception
*/
public function listUsers($sort = false)
{
if (!empty($this->_params['domain_field']) &&
($this->_params['domain_field'] != 'none')) {
/* Build the SQL query with domain. */
$query = sprintf('SELECT %s, %s FROM %s',
$this->_params['username_field'],
$this->_params['domain_field'],
$this->_params['table']);
} else {
/* Build the SQL query without domain. */
$query = sprintf('SELECT %s FROM %s',
$this->_params['username_field'],
$this->_params['table']);
}
if ($sort) {
$query .= sprintf(" ORDER BY %s", $this->_params['username_field']);
}
try {
$result = $this->_db->select($query);
} catch (Horde_Db_Exception $e) {
throw new Horde_Auth_Exception($e);
}
/* Loop through and build return array. */
$users = array();
if (!empty($this->_params['domain_field']) &&
($this->_params['domain_field'] != 'none')) {
foreach ($result as $ar) {
if (!in_array($ar[$this->_params['username_field']], $this->_params['hidden_accounts'])) {
$users[] = $ar[$this->_params['username_field']] . '@' . $ar[$this->_params['domain_field']];
}
}
} else {
foreach ($result as $ar) {
if (!in_array($ar[$this->_params['username_field']], $this->_params['hidden_accounts'])) {
$users[] = $ar[$this->_params['username_field']];
}
}
}
return $users;
}
/**
* Update a set of authentication credentials.
*
* @param string $oldID The old userId.
* @param string $newID The new userId. [NOT SUPPORTED]
* @param array $credentials The new credentials
*
* @throws Horde_Auth_Exception
*/
public function updateUser($oldID, $newID, $credentials)
{
if (!empty($this->_params['domain_field']) &&
($this->_params['domain_field'] != 'none')) {
list($name, $domain) = explode('@', $oldID);
/* Build the SQL query with domain. */
$query = sprintf(
'UPDATE %s SET %s = ? WHERE %s = ? and %s = ?',
$this->_params['table'],
$this->_params['password_field'],
$this->_params['username_field'],
$this->_params['domain_field']
);
$values = array(
Horde_Auth::getCryptedPassword($credentials['password'], '', $this->_params['encryption'], $this->_params['show_encryption']),
$name,
$domain
);
} else {
/* Build the SQL query. */
$query = sprintf(
'UPDATE %s SET %s = ? WHERE %s = ?',
$this->_params['table'],
$this->_params['password_field'],
$this->_params['username_field']
);
$values = array(
Horde_Auth::getCryptedPassword($credentials['password'], '', $this->_params['encryption'], $this->_params['show_encryption']),
$oldID
);
}
try {
$this->_db->update($query, $values);
} catch (Horde_Db_Exception $e) {
throw new Horde_Auth_Exception($e);
}
}
}