<?php
/**
 * Copyright 1999-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   Chuck Hagenbuch <chuck@horde.org>
 * @author   Gaudenz Steinlin <gaudenz@soziologie.ch>
 * @author   Jan Schneider <jan@horde.org>
 * @category Horde
 * @license  http://www.horde.org/licenses/lgpl21 LGPL-2.1
 * @package  Auth
 */

/**
 * The Horde_Auth_Imap:: class provides an IMAP implementation of the Horde
 * authentication system.
 *
 * @author    Chuck Hagenbuch <chuck@horde.org>
 * @author    Gaudenz Steinlin <gaudenz@soziologie.ch>
 * @author    Jan Schneider <jan@horde.org>
 * @category  Horde
 * @copyright 1999-2017 Horde LLC
 * @license   http://www.horde.org/licenses/lgpl21 LGPL-2.1
 * @package   Auth
 */
class Horde_Auth_Imap extends Horde_Auth_Base
{
    /**
     * Imap client objects.
     *
     * @var array()
     */
    protected $_imap = array();

    /**
     * Constructor.
     *
     * @param array $params  Optional parameters:
     *   - admin_password: (string) The password of the administrator.
     *                     DEFAULT: null
     *   - admin_user: (string) The name of a user with admin privileges.
     *                 DEFAULT: null
     *   - hostspec: (string) The hostname or IP address of the server.
     *               DEFAULT: 'localhost'
     *   - port: (integer) The server port to which we will connect.
     *           IMAP is generally 143, while IMAP-SSL is generally 993.
     *           DEFAULT: Encryption port default
     *   - secure: (string) The encryption to use.  Either 'none', 'ssl', or
     *             'tls'.
     *             DEFAULT: 'none'
     *   - userhierarchy: (string) The hierarchy where user mailboxes are
     *                    stored (UTF-8).
     *                    DEFAULT: 'user.'
     */
    public function __construct(array $params = array())
    {
        $params = array_merge(array(
            'admin_password' => null,
            'admin_user' => null,
            'hostspec' => '',
            'port' => null,
            'secure' => 'none',
            'userhierarchy' => 'user.'
        ), $params);

        parent::__construct($params);

        if (!empty($this->_params['admin_user'])) {
            $this->_capabilities = array_merge($this->_capabilities, array(
                'add' => true,
                'list' => true,
                'remove' => true
            ));
        }
    }

    /**
     * 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. For IMAP,
     *                            this must contain a password entry.
     *
     * @throws Horde_Auth_Exception
     */
    protected function _authenticate($userId, $credentials)
    {
        try {
            $ob = $this->_getOb($userId, $credentials['password']);
            $ob->login();
        } catch (Horde_Imap_Client_Exception $e) {
            throw new Horde_Auth_Exception('', Horde_Auth::REASON_BADLOGIN);
        }
    }

    /**
     * Add a set of authentication credentials.
     *
     * @param string $userId       The userId to add.
     * @param array  $credentials  The credentials to use.
     *
     * @throws Horde_Auth_Exception
     */
    public function addUser($userId, $credentials)
    {
        if (!$this->hasCapability('add')) {
            throw new Horde_Auth_Exception('Adding of users is not supported.');
        }
        try {
            $ob = $this->_getOb($this->_params['admin_user'], $this->_params['admin_password']);

            $name = $this->_params['userhierarchy'] . $userId;
            $ob->createMailbox($name);
            $ob->setACL($name, $this->_params['admin_user'], array(
                'rights' => 'lrswipcda'
            ));
        } catch (Horde_Imap_Client_Exception $e) {
            throw new Horde_Auth_Exception($e);
        }
    }

    /**
     * Delete a set of authentication credentials.
     *
     * @param string $userId  The userId to delete.
     *
     * @throws Horde_Auth_Exception
     */
    public function removeUser($userId)
    {
        if (!$this->hasCapability('remove')) {
            throw new Horde_Auth_Exception('Removing of users is not supported.');
        }
        try {
            $ob = $this->_getOb($this->_params['admin_user'], $this->_params['admin_password']);

            $name = $this->_params['userhierarchy'] . $userId;
            $ob->setACL($name, $this->_params['admin_user'], array(
                'rights' => 'lrswipcda'
            ));
            $ob->deleteMailbox($name);
        } catch (Horde_Imap_Client_Exception $e) {
            throw new Horde_Auth_Exception($e);
        }
    }

    /**
     * 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)
    {
        if (!$this->hasCapability('list')) {
            throw new Horde_Auth_Exception('Listing of users is not supported.');
        }
        try {
            $ob = $this->_getOb($this->_params['admin_user'], $this->_params['admin_password']);
            $list = $ob->listMailboxes($this->_params['userhierarchy'] . '%', Horde_Imap_Client::MBOX_ALL, array('flat' => true));
        } catch (Horde_Imap_Client_Exception $e) {
            throw new Horde_Auth_Exception($e);
        }

        $users = empty($list)
            ? array()
            : preg_replace('/.*' . preg_quote($this->_params['userhierarchy'], '/') . '(.*)/', '\\1', $list);
        return $this->_sort($users, $sort);
    }

    /**
     * Get Horde_Imap_Client object.
     *
     * @param string $user  Username.
     * @param string $pass  Password.
     *
     * @return Horde_Imap_Client_Base  IMAP client object.
     * @throws Horde_Auth_Exception
     */
    protected function _getOb($user, $pass)
    {
        $sig = hash('md5', serialize(array($user, $pass)));

        if (!isset($this->_imap[$sig])) {
            $imap_config = array(
                'hostspec' => empty($this->_params['hostspec']) ? null : $this->_params['hostspec'],
                'password' => $pass,
                'port' => empty($this->_params['port']) ? null : $this->_params['port'],
                'secure' => ($this->_params['secure'] == 'none') ? null : $this->_params['secure'],
                'username' => $user
            );

            try {
                $this->_imap[$sig] = new Horde_Imap_Client_Socket($imap_config);
            } catch (InvalidArgumentException $e) {
                throw new Horde_Auth_Exception($e);
            }
        }

        return $this->_imap[$sig];
    }

}
