<?php

require_once dirname(__FILE__) . '/sql.php';

/**
 * The 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.
 *
 * Required parameters:
 * ====================
 *   'cyradmin'  --  The username of the cyrus administrator.
 *   'cyrpass'   --  The password for the cyrus administrator.
 *   'database'  --  The name of the database.
 *   'hostspec'  --  The hostname of the database server.
 *   'imap_dsn'  --  The full IMAP DSN
 *                   (i.e. {localhost:993/imap/ssl/novalidate-cert}).
 *   'password'  --  The password associated with 'username'.
 *   'phptype'   --  The database type (ie. 'pgsql', 'mysql, etc.).
 *   'protocol'  --  The communication protocol ('tcp', 'unix', etc.).
 *   'username'  --  The username with which to connect to the database.
 *
 * Optional parameters:
 * ====================
 *   'domain_field'    --  If set to anything other than 'none' this is used as
 *                         field name where domain is stored.
 *                         DEFAULT: NONE
 *   'encryption'      --  The encryption to use to store the password in the
 *                         table (e.g. plain, crypt, md5-hex, md5-base64, smd5,
 *                         sha, ssha).
 *                         DEFAULT: 'md5-hex'
 *   'folders'         --  An array of folders to create under username.
 *                         DEFAULT: NONE
 *   'password_field'  --  The name of the password field in the auth table.
 *                         DEFAULT: 'user_pass'
 *   'quota'           --  The quota (in kilobytes) to grant on the mailbox.
 *                         DEFAULT: NONE
 *   'table'           --  The name of the auth table in 'database'. 
 *                         DEFAULT: 'horde_users'
 *   'unixhier'        --  The value of imapd.conf's unixhierarchysep setting.
 *                         Set this to true if the value is true in imapd.conf.
 *   'username_field'  --  The name of the username field in the auth table.
 *                         DEFAULT: 'user_uid'
 *
 * Required by some database implementations:
 * ==========================================
 *   'options'  --  Additional options to pass to the database.
 *   'port'     --  The port on which to connect to the database.
 *   'tty'      --  The TTY on which to connect to the database.
 *
 *
 * The table structure for the auth system is as follows:
 *
 * CREATE TABLE horde_users (
 *     user_uid   VARCHAR(255) NOT NULL,
 *     user_pass  VARCHAR(255) NOT NULL,
 *     PRIMARY KEY (user_uid)
 * );
 *
 *
 * $Horde: horde/lib/Auth/cyrsql.php,v 1.17 2003/07/10 21:42:56 slusarz Exp $
 *
 * Copyright 2002-2003 Ilya <mail@krel.org>
 *
 * See the enclosed file COPYING for license information (LGPL). If you
 * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
 *
 * @author  Ilya <mail@krel.org>
 * @version $Revision: 1.17 $
 * @since   Horde 3.0
 * @package horde.auth
 */
class Auth_cyrsql extends Auth_sql {

    /**
     * Handle for the current IMAP connection.
     *
     * @var resource $_imapStream
     */
    var $_imapStream;

    /**
     * Hierarchy separator to use (e.g., is it user/mailbox or user.mailbox)
     *
     * @var string $_separator
     */
    var $_separator = '.';

    /**
     * Constructor.
     *
     * @access public
     *
     * @param optional array $params  A hash containing connection parameters.
    function Auth_cyrsql($params = array())
    {
        if (!Horde::extensionExists('imap')) {
            Horde::fatal(PEAR::raiseError(_("Auth_cyrsql: Required imap extension not found."), __FILE__, __LINE__));
        }
    }

    /**
     * Add a set of authentication credentials.
     *
     * @access public
     *
     * @param string $userID       The userID to add.
     * @param array  $credentials  The credentials to add.
     *
     * @return mixed  True on success or a PEAR_Error object on failure.
     */
    function addUser($userID, $credentials)
    {
        $this->_connect();

        if (array_key_exists('domain_field', $this->_params) &&
                            ($this->_params['domain_field'] != 'none')){
            list($name,$domain)=explode('@',$userID);
            /* Build the SQL query. */
            $query = sprintf('INSERT INTO %s (%s, %s, %s) VALUES (%s, %s, %s)',
                            $this->_params['table'],
                            $this->_params['username_field'],
                            $this->_params['domain_field'],
                            $this->_params['password_field'],
                            $this->_db->quote($name),
                            $this->_db->quote($domain),
                            $this->_db->quote($this->_encryptPassword($credentials['password'])));
            $dbresult = $this->_db->query($query);
        } else {
            $dbresult = parent::addUser($userID, $credentials);
        }
        if (DB::isError($dbresult)) {
            return $dbresult;
        }

        $name = imap_utf7_encode($userID);
        if (@imap_createmailbox($this->_imapStream, 
                                imap_utf7_encode($this->_params['imap_dsn'] .
                                'user' . $this->_separator . $name))) {
            @array_walk($this->_params['folders'], 
                        array($this, '_createSubFolders'), $name);
        } else {
            Horde::logMessage('IMAP mailbox creation for ' . $name . ' failed ',
                              __FILE__, __LINE__, PEAR_LOG_ERR);
            return PEAR::raiseError(sprintf(_("IMAP mailbox creation failed: %s"), imap_last_error()));
        }

        if (array_key_exists('quota', $this->_params) && $this->_params['quota'] >= 0) {
            if (!@imap_set_quota($this->_imapStream,
                                'user' . $this->_separator . $name,
                                $this->_params['quota'])) {
                return PEAR::raiseError(sprintf(_("IMAP mailbox quota creation failed: %s"), imap_last_error()));
            }
        }
                
            
        return true;
    }

    /**
     * Delete a set of authentication credentials.
     *
     * @access public
     *
     * @param string $userID  The userID to delete.
     *
     * @return boolean        Success or failure.
     */
    function removeUser($userID)
    {
        $this->_connect();

        if (array_key_exists('domain_field', $this->_params) &&
                            ($this->_params['domain_field'] != 'none')){
            list($name,$domain)=explode('@',$userID);
            /* Build the SQL query. */
            $query = sprintf('DELETE FROM %s WHERE %s = %s and %s = %s',
                         $this->_params['table'],
                         $this->_params['username_field'],
                         $this->_db->quote($name),
                         $this->_params['domain_field'],
                         $this->_db->quote($domain));
            $dbresult = $this->_db->query($query);
        } else {
            $dbresult = parent::removeUser($userID);
        }

        if (DB::isError($dbresult)) {
            return $dbresult;
        }

        /* Set ACL for mailbox deletion. */
        list($admin)=explode('@',$this->_params['cyradmin']);
        @imap_setacl($this->_imapStream, 
                     'user' . $this->_separator . $userID,
                     $admin, 'lrswipcda');

        /* Delete IMAP mailbox. */
        $imapresult = @imap_deletemailbox($this->_imapStream, 
                                          $this->_params['imap_dsn'] . 
                                          'user' . $this->_separator . $userID);

        if (!$imapresult) {
            return PEAR::raiseError(sprintf(_("IMAP mailbox deletion failed: %s"), imap_last_error()));
        }

        return true;
    }

    /**
     * Attempts to open connections to the SQL and IMAP servers.
     *
     * @access private
     *
     * @return mixed  True on success or a PEAR_Error object on failure.
     */
    function _connect()
    {
        if (!$this->_connected) {
            parent::_connect();

            // Reset the $_connected flag; we haven't yet successfully
            // opened everything.
            $this->_connected = false;

            $this->_imapStream = @imap_open($this->_params['imap_dsn'], $this->_params['cyradmin'], $this->_params['cyrpass'], OP_HALFOPEN);
            if (!$this->_imapStream) {
                Horde::fatal(PEAR::raiseError(sprintf(_("Can't connect to IMAP server: %s"), imap_last_error())), __FILE__, __LINE__);
            }

            if (!empty($this->_params['unixhier']) {
                $this->_separator = '/';
            }
            $this->_connected = true;
        }

        return true;
    }

    /**
     * Disconnect from the SQL and IMAP servers and clean up the
     * connections.
     *
     * @access private
     *
     * @return boolean  True on success, false on failure.
     */
    function _disconnect()
    {
        if ($this->_connected) {
            parent::_disconnect();
            @imap_close($this->_imapStream);
        }

        return true;
    }

    /**
     * Creates all mailboxes supllied in configuration
     *
     * @access private
     */
    function _createSubFolders($value, $key, $userName)
    {
        if (array_key_exists('domain_field', $this->_params) &&
                            ($this->_params['domain_field'] != 'none')){
             list($name,$domain)=explode('@',$userName);
             @imap_createmailbox($this->_imapStream, 
                           	    imap_utf7_encode($this->_params['imap_dsn'] .
                                'user' . $this->_separator . $name . 
                                         $this->_separator . $value . '@' . $domain));
        } else {
             @imap_createmailbox($this->_imapStream,
                                imap_utf7_encode($this->_params['imap_dsn'] .
                                'user' . $this->_separator . $userName .
                                         $this->_separator . $value));
        }
    }

    /**
     * List all users in the system.
     *
     * @access public
     *
     * @return mixed  The array of userIDs, or false on failure/unsupported.
     */
    function listUsers()
    {
        /* _connect() will die with Horde::fatal() upon failure. */
        $this->_connect();

        if (array_key_exists('domain_field', $this->_params) && 
                            ($this->_params['domain_field'] != 'none')){
            /* Build the SQL query with domain. */
            $query = sprintf('SELECT %s , %s FROM %s ORDER BY %s',
                         $this->_params['username_field'],
                         $this->_params['domain_field'],
                         $this->_params['table'],
                         $this->_params['username_field']);
        } else {
            /* Build the SQL query without domain. */
            $query = sprintf('SELECT %s FROM %s ORDER BY %s',
                         $this->_params['username_field'],
                         $this->_params['table'],
                         $this->_params['username_field']);
       }

        $result = $this->_db->getAll($query, null, DB_FETCHMODE_ORDERED);
        if (PEAR::isError($result)) {
            return $result;
        }

        /* Loop through and build return array. */
        $users = array();
        if (array_key_exists('domain_field', $this->_params) &&
                            ($this->_params['domain_field'] != 'none')){
            foreach ($result as $ar) {
           	   $users[] = $ar[0] . '@' . $ar[1];
            }
        } else {
            foreach ($result as $ar) {
               $users[] = $ar[0];
            }
        }

        return $users;
    }
    
    /**
     * Update a set of authentication credentials.
     *
     * @access public
     *
     * @param string $oldID       The old userID.
     * @param string $newID       The new userID.
     * @param array $credentials  The new credentials
     *
     * @return mixed  True on success or a PEAR_Error object on failure.
     */

    function updateUser($oldID, $newID, $credentials)
    {
        /* _connect() will die with Horde::fatal() upon failure. */
        $this->_connect();

        if (array_key_exists('domain_field', $this->_params) &&
                            ($this->_params['domain_field'] != 'none')){
            list($name,$domain)=explode('@',$oldID);
            /* Build the SQL query with domain. */
            $query = sprintf('UPDATE %s SET %s = %s WHERE %s = %s and %s = %s',
                         $this->_params['table'],
                         $this->_params['password_field'],
                         $this->_db->quote($this->_encryptPassword($credentials['password'])),
                         $this->_params['username_field'],
                         $this->_db->quote($name),
                         $this->_params['domain_field'],
                         $this->_db->quote($domain));
        } else {
           /* Build the SQL query. */
            $query = sprintf('UPDATE %s SET %s = %s WHERE %s = %s',
                         $this->_params['table'],
                         $this->_params['password_field'],
                         $this->_db->quote($this->_encryptPassword($credentials['password'])),
                         $this->_params['username_field'],
                         $this->_db->quote($oldID)); 
        }

        $dbresult = $this->_db->query($query);
        if (DB::isError($dbresult)) {
            return $dbresult;
        }

        return true;
    }
}
