Auth:: (CVS) -> Horde_Auth::
authorMichael M Slusarz <slusarz@curecanti.org>
Tue, 7 Jul 2009 01:08:53 +0000 (19:08 -0600)
committerMichael M Slusarz <slusarz@curecanti.org>
Wed, 8 Jul 2009 16:40:40 +0000 (10:40 -0600)
49 files changed:
framework/Auth/lib/Horde/Auth.php [new file with mode: 0644]
framework/Auth/lib/Horde/Auth/Application.php [new file with mode: 0644]
framework/Auth/lib/Horde/Auth/Auto.php [new file with mode: 0644]
framework/Auth/lib/Horde/Auth/Composite.php [new file with mode: 0644]
framework/Auth/lib/Horde/Auth/Customsql.php [new file with mode: 0644]
framework/Auth/lib/Horde/Auth/Cyrsql.php [new file with mode: 0644]
framework/Auth/lib/Horde/Auth/Cyrus.php [new file with mode: 0644]
framework/Auth/lib/Horde/Auth/Driver.php [new file with mode: 0644]
framework/Auth/lib/Horde/Auth/Ftp.php [new file with mode: 0644]
framework/Auth/lib/Horde/Auth/Http.php [new file with mode: 0644]
framework/Auth/lib/Horde/Auth/HttpRemote.php [new file with mode: 0644]
framework/Auth/lib/Horde/Auth/Imap.php [new file with mode: 0644]
framework/Auth/lib/Horde/Auth/Imsp.php [new file with mode: 0644]
framework/Auth/lib/Horde/Auth/Ipbasic.php [new file with mode: 0644]
framework/Auth/lib/Horde/Auth/Kolab.php [new file with mode: 0644]
framework/Auth/lib/Horde/Auth/Krb5.php [new file with mode: 0644]
framework/Auth/lib/Horde/Auth/Ldap.php [new file with mode: 0644]
framework/Auth/lib/Horde/Auth/Login.php [new file with mode: 0644]
framework/Auth/lib/Horde/Auth/Msad.php [new file with mode: 0644]
framework/Auth/lib/Horde/Auth/Pam.php [new file with mode: 0644]
framework/Auth/lib/Horde/Auth/Passwd.php [new file with mode: 0644]
framework/Auth/lib/Horde/Auth/Peclsasl.php [new file with mode: 0644]
framework/Auth/lib/Horde/Auth/Radius.php [new file with mode: 0644]
framework/Auth/lib/Horde/Auth/Shibboleth.php [new file with mode: 0644]
framework/Auth/lib/Horde/Auth/Signup.php [new file with mode: 0644]
framework/Auth/lib/Horde/Auth/Signup/Datatree.php [new file with mode: 0644]
framework/Auth/lib/Horde/Auth/Signup/Sql.php [new file with mode: 0644]
framework/Auth/lib/Horde/Auth/Smb.php [new file with mode: 0644]
framework/Auth/lib/Horde/Auth/Smbclient.php [new file with mode: 0644]
framework/Auth/lib/Horde/Auth/Sql.php [new file with mode: 0644]
framework/Auth/package.xml [new file with mode: 0644]
framework/Auth/test/Horde/Auth/AllTests.php [new file with mode: 0644]
framework/Auth/test/Horde/Auth/KolabScenarioTest.php [new file with mode: 0644]
framework/Auth/test/Horde/Auth/KolabTest.php [new file with mode: 0644]
framework/Auth/test/Horde/Auth/credentials.php [new file with mode: 0644]
framework/Auth/test/Horde/Auth/getCryptedPassword.phpt [new file with mode: 0644]
framework/Auth/test/Horde/Auth/getSalt.phpt [new file with mode: 0644]
framework/Auth/test/Horde/Auth/passwd.phpt [new file with mode: 0644]
framework/Auth/test/Horde/Auth/test.passwd [new file with mode: 0644]
framework/History/lib/Horde/History.php
framework/Kolab_Server/lib/Horde/Kolab/Test/Server.php
framework/Kolab_Session/lib/Horde/Kolab/Session.php
framework/LoginTasks/lib/Horde/LoginTasks.php
framework/Notification/lib/Horde/Notification.php
framework/Rpc/lib/Horde/Rpc.php
framework/Rpc/lib/Horde/Rpc/Phpgw.php
framework/Rpc/lib/Horde/Rpc/Soap.php
framework/Rpc/lib/Horde/Rpc/Webdav.php
framework/SessionHandler/lib/Horde/SessionHandler.php

diff --git a/framework/Auth/lib/Horde/Auth.php b/framework/Auth/lib/Horde/Auth.php
new file mode 100644 (file)
index 0000000..4e4edce
--- /dev/null
@@ -0,0 +1,1001 @@
+<?php
+/**
+ * The Horde_Auth:: class provides a common abstracted interface into the
+ * various backends for the Horde authentication system.
+ *
+ * Copyright 1999-2009 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you did
+ * not receive this file, see http://opensource.org/licenses/lgpl-2.1.php
+ *
+ * @author  Chuck Hagenbuch <chuck@horde.org>
+ * @package Horde_Auth
+ */
+class Horde_Auth
+{
+    /**
+     * The parameter name for the logout reason.
+     */
+    const REASON_PARAM = 'logout_reason';
+
+    /**
+     * The parameter name for the logout message used with type
+     * REASON_MESSAGE.
+    */
+    const REASON_MSG_PARAM = 'logout_msg';
+
+    /**
+     * The 'badlogin' reason.
+     *
+     * The following 'reasons' for the logout screen are recognized:
+     * <pre>
+     * REASON_BADLOGIN - Bad username and/or password
+     * REASON_BROWSER - A browser change was detected
+     * REASON_FAILED - Login failed
+     * REASON_EXPIRED - Password has expired
+     * REASON_LOGOUT - Logout due to user request
+     * REASON_MESSAGE - Logout with custom message in REASON_MSG_PARAM
+     * REASON_SESSION - Logout due to session expiration
+     * REASON_SESSIONIP - Logout due to change of IP address during session
+     * </pre>
+     */
+    const REASON_BADLOGIN = 1;
+    const REASON_BROWSER = 2;
+    const REASON_FAILED = 3;
+    const REASON_EXPIRED = 4;
+    const REASON_LOGOUT = 5;
+    const REASON_MESSAGE = 6;
+    const REASON_SESSION = 7;
+    const REASON_SESSIONIP = 8;
+
+    /**
+     * 64 characters that are valid for APRMD5 passwords.
+     */
+    const APRMD5_VALID = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
+
+    /**
+     * Characters used when generating a password.
+     */
+    const VOWELS = 'aeiouy';
+    const CONSONANTS = 'bcdfghjklmnpqrstvwxz';
+    const NUMBERS = '0123456789';
+
+    /**
+     * Singleton instances.
+     *
+     * @var array
+     */
+    static protected $_instances = array();
+
+    /**
+     * Attempts to return a concrete Horde_Auth_Driver instance based on
+     * $driver.
+     *
+     * @param mixed $driver  The type of concrete Horde_Auth_Driver subclass
+     *                       to return. If $driver is an array, then look
+     *                       in $driver[0]/lib/Auth/ for the subclass
+     *                       implementation named $driver[1].php.
+     * @param array $params  A hash containing any additional configuration or
+     *                       parameters a subclass might need.
+     *
+     * @return Horde_Auth_Driver  The newly created concrete instance.
+     * @throws Horde_Exception
+     */
+    static public function factory($driver, $params = null)
+    {
+        if (is_array($driver)) {
+            list($app, $driv_name) = $driver;
+            $driver = basename($driv_name);
+        } else {
+            $driver = basename($driver);
+        }
+
+        /* Return a base Horde_Auth_Driver object if no driver is
+         * specified. */
+        if (empty($driver) || (strcasecmp($driver, 'none') == 0)) {
+            return new Horde_Auth_Driver();
+        }
+
+        if (empty($params)) {
+            $params = Horde::getDriverConfig('auth', $driver);
+        }
+
+        $class = (empty($app) ? 'Horde' : $app) . '_Auth_' . ucfirst($driver);
+
+        if (class_exists($class)) {
+            return new $class($params);
+        }
+
+        throw new Horde_Exception('Class definition of ' . $class . ' not found.');
+    }
+
+    /**
+     * Attempts to return a reference to a concrete instance based on $driver.
+     * It will only create a new instance if no instance with the same
+     * parameters currently exists.
+     *
+     * This method must be invoked as: $var = Horde_Auth::singleton()
+     *
+     * @param mixed $driver  The type of concrete Horde_Auth_Driver subclass
+     *                       to return. If $driver is an array, then look
+     *                       in $driver[0]/lib/Auth/ for the subclass
+     *                       implementation named $driver[1].php.
+     * @param array $params  A hash containing any additional configuration or
+     *                       connection parameters a subclass might need.
+     *
+     * @return Horde_Auth_Driver  The concrete reference.
+     * @throws Horde_Exception
+     */
+    static public function singleton($driver, $params = array())
+    {
+        ksort($params);
+        $signature = hash('md5', serialize(array($driver, $params)));
+
+        if (!isset(self::$_instances[$signature])) {
+            self::$_instances[$signature] = Horde_Auth::factory($driver, $params);
+        }
+
+        return self::$_instances[$signature];
+    }
+
+    /**
+     * Formats a password using the current encryption.
+     *
+     * @param string $plaintext      The plaintext password to encrypt.
+     * @param string $salt           The salt to use to encrypt the password.
+     *                               If not present, a new salt will be
+     *                               generated.
+     * @param string $encryption     The kind of pasword encryption to use.
+     *                               Defaults to md5-hex.
+     * @param boolean $show_encrypt  Some password systems prepend the kind of
+     *                               encryption to the crypted password ({SHA},
+     *                               etc). Defaults to false.
+     *
+     * @return string  The encrypted password.
+     */
+    static public function getCryptedPassword($plaintext, $salt = '',
+                                              $encryption = 'md5-hex',
+                                              $show_encrypt = false)
+    {
+        /* Get the salt to use. */
+        $salt = self::getSalt($encryption, $salt, $plaintext);
+
+        /* Encrypt the password. */
+        switch ($encryption) {
+        case 'plain':
+            return $plaintext;
+
+        case 'msad':
+            return Horde_String::convertCharset('"' . $plaintext . '"', 'ISO-8859-1', 'UTF-16LE');
+
+        case 'sha':
+            $encrypted = base64_encode(pack('H*', hash('sha1', $plaintext)));
+            return $show_encrypt ? '{SHA}' . $encrypted : $encrypted;
+
+        case 'crypt':
+        case 'crypt-des':
+        case 'crypt-md5':
+        case 'crypt-blowfish':
+            return ($show_encrypt ? '{crypt}' : '') . crypt($plaintext, $salt);
+
+        case 'md5-base64':
+            $encrypted = base64_encode(pack('H*', hash('md5', $plaintext)));
+            return $show_encrypt ? '{MD5}' . $encrypted : $encrypted;
+
+        case 'ssha':
+            $encrypted = base64_encode(pack('H*', hash('sha1', $plaintext . $salt)) . $salt);
+            return $show_encrypt ? '{SSHA}' . $encrypted : $encrypted;
+
+        case 'smd5':
+            $encrypted = base64_encode(pack('H*', hash('md5', $plaintext . $salt)) . $salt);
+            return $show_encrypt ? '{SMD5}' . $encrypted : $encrypted;
+
+        case 'aprmd5':
+            $length = strlen($plaintext);
+            $context = $plaintext . '$apr1$' . $salt;
+            $binary = pack('H*', hash('md5', $plaintext . $salt . $plaintext));
+
+            for ($i = $length; $i > 0; $i -= 16) {
+                $context .= substr($binary, 0, ($i > 16 ? 16 : $i));
+            }
+            for ($i = $length; $i > 0; $i >>= 1) {
+                $context .= ($i & 1) ? chr(0) : $plaintext[0];
+            }
+
+            $binary = pack('H*', hash('md5', $context));
+
+            for ($i = 0; $i < 1000; ++$i) {
+                $new = ($i & 1) ? $plaintext : substr($binary, 0, 16);
+                if ($i % 3) {
+                    $new .= $salt;
+                }
+                if ($i % 7) {
+                    $new .= $plaintext;
+                }
+                $new .= ($i & 1) ? substr($binary, 0, 16) : $plaintext;
+                $binary = pack('H*', hash('md5', $new));
+            }
+
+            $p = array();
+            for ($i = 0; $i < 5; $i++) {
+                $k = $i + 6;
+                $j = $i + 12;
+                if ($j == 16) {
+                    $j = 5;
+                }
+                $p[] = self::_toAPRMD5((ord($binary[$i]) << 16) |
+                                       (ord($binary[$k]) << 8) |
+                                       (ord($binary[$j])),
+                                       5);
+            }
+
+            return '$apr1$' . $salt . '$' . implode('', $p) . self::_toAPRMD5(ord($binary[11]), 3);
+
+        case 'md5-hex':
+        default:
+            return ($show_encrypt) ? '{MD5}' . hash('md5', $plaintext) : hash('md5', $plaintext);
+        }
+    }
+
+    /**
+     * Returns a salt for the appropriate kind of password encryption.
+     * Optionally takes a seed and a plaintext password, to extract the seed
+     * of an existing password, or for encryption types that use the plaintext
+     * in the generation of the salt.
+     *
+     * @param string $encryption  The kind of pasword encryption to use.
+     *                            Defaults to md5-hex.
+     * @param string $seed        The seed to get the salt from (probably a
+     *                            previously generated password). Defaults to
+     *                            generating a new seed.
+     * @param string $plaintext   The plaintext password that we're generating
+     *                            a salt for. Defaults to none.
+     *
+     * @return string  The generated or extracted salt.
+     */
+    static public function getSalt($encryption = 'md5-hex', $seed = '',
+                                   $plaintext = '')
+    {
+        switch ($encryption) {
+        case 'crypt':
+        case 'crypt-des':
+            return $seed
+                ? substr(preg_replace('|^{crypt}|i', '', $seed), 0, 2)
+                : substr(hash('md5', mt_rand()), 0, 2);
+
+        case 'crypt-md5':
+            return $seed
+                ? substr(preg_replace('|^{crypt}|i', '', $seed), 0, 12)
+                : '$1$' . substr(hash('md5', mt_rand()), 0, 8) . '$';
+
+        case 'crypt-blowfish':
+            return $seed
+                ? substr(preg_replace('|^{crypt}|i', '', $seed), 0, 16)
+                : '$2$' . substr(hash('md5', mt_rand()), 0, 12) . '$';
+
+        case 'ssha':
+            return $seed
+                ? substr(base64_decode(preg_replace('|^{SSHA}|i', '', $seed)), 20)
+                : substr(pack('H*', sha1(substr(pack('h*', hash('md5', mt_rand())), 0, 8) . $plaintext)), 0, 4);
+
+        case 'smd5':
+            return $seed
+                ? substr(base64_decode(preg_replace('|^{SMD5}|i', '', $seed)), 16)
+                : substr(pack('H*', hash('md5', substr(pack('h*', hash('md5', mt_rand())), 0, 8) . $plaintext)), 0, 4);
+
+        case 'aprmd5':
+            if ($seed) {
+                return substr(preg_replace('/^\$apr1\$(.{8}).*/', '\\1', $seed), 0, 8);
+            } else {
+                $salt = '';
+                $valid = self::APRMD5_VALID;
+                for ($i = 0; $i < 8; ++$i) {
+                    $salt .= $valid[mt_rand(0, 63)];
+                }
+                return $salt;
+            }
+
+        default:
+            return '';
+        }
+    }
+
+    /**
+     * Generates a random, hopefully pronounceable, password. This can be used
+     * when resetting automatically a user's password.
+     *
+     * @return string A random password
+     */
+    static public function genRandomPassword()
+    {
+        /* Alternate consonant and vowel random chars with two random numbers
+         * at the end. This should produce a fairly pronounceable password. */
+        return substr(self::CONSONANTS, mt_rand(0, strlen(self::CONSONANTS) - 1), 1) .
+            substr(self::VOWELS, mt_rand(0, strlen(self::VOWELS) - 1), 1) .
+            substr(self::CONSONANTS, mt_rand(0, strlen(self::CONSONANTS) - 1), 1) .
+            substr(self::VOWELS, mt_rand(0, strlen(self::VOWELS) - 1), 1) .
+            substr(self::CONSONANTS, mt_rand(0, strlen(self::CONSONANTS) - 1), 1) .
+            substr(self::NUMBERS, mt_rand(0, strlen(self::NUMBERS) - 1), 1) .
+            substr(self::NUMBERS, mt_rand(0, strlen(self::NUMBERS) - 1), 1);
+    }
+
+    /**
+     * Calls all applications' removeUser API methods.
+     *
+     * @param string $userId  The userId to delete.
+     *
+     * @throws Horde_Exception
+     */
+    static public function removeUserData($userId)
+    {
+        $errApps = array();
+
+        foreach ($GLOBALS['registry']->listApps(array('notoolbar', 'hidden', 'active', 'admin')) as $app) {
+            if ($GLOBALS['registry']->hasMethod('removeUserData', $app) &&
+                is_a($result = $GLOBALS['registry']->callByPackage($app, 'removeUserData', array($userId)), 'PEAR_Error')) {
+                Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR);
+                $errApps[] = $app;
+            }
+        }
+
+        if (count($errApps)) {
+            throw new Horde_Exception(sprintf(_("The following applications encountered errors removing user data: %s"), implode(', ', $errApps)));
+        }
+    }
+
+    /**
+     * Checks if there is a session with valid auth information. for the
+     * specified user. If there isn't, but the configured Auth driver supports
+     * transparent authentication, then we try that.
+     *
+     * @param string $realm  The authentication realm to check.
+     *
+     * @return boolean  Whether or not the user is authenticated.
+     */
+    static public function isAuthenticated($realm = null)
+    {
+        if (isset($_SESSION['horde_auth']) &&
+            !empty($_SESSION['horde_auth']['authenticated']) &&
+            !empty($_SESSION['horde_auth']['userId']) &&
+            ($_SESSION['horde_auth']['realm'] == $realm)) {
+            if (!self::checkSessionIP()) {
+                self::setAuthError(self::REASON_SESSIONIP);
+                return false;
+            } elseif (!self::checkBrowserString()) {
+                self::setAuthError(self::REASON_BROWSER);
+                return false;
+            }
+            return true;
+        }
+
+        // Try transparent authentication now.
+        $auth = self::singleton($GLOBALS['conf']['auth']['driver']);
+        if ($auth->hasCapability('transparent') && $auth->transparent()) {
+            return self::isAuthenticated($realm);
+        }
+
+        return false;
+    }
+
+    /**
+     * Returns the currently logged in user, if there is one.
+     *
+     * @return mixed  The userId of the current user, or false if no user is
+     *                logged in.
+     */
+    static public function getAuth()
+    {
+        if (isset($_SESSION['horde_auth'])) {
+            if (!empty($_SESSION['horde_auth']['authenticated']) &&
+                !empty($_SESSION['horde_auth']['userId'])) {
+                return $_SESSION['horde_auth']['userId'];
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * Return whether the authentication backend requested a password change.
+     *
+     * @return boolean Whether the backend requested a password change.
+     */
+    static public function isPasswordChangeRequested()
+    {
+        return (isset($_SESSION['horde_auth']) &&
+                !empty($_SESSION['horde_auth']['authenticated']) &&
+                !empty($_SESSION['horde_auth']['changeRequested']));
+    }
+
+    /**
+     * Returns the curently logged-in user without any domain information
+     * (e.g., bob@example.com would be returned as 'bob').
+     *
+     * @return mixed  The user ID of the current user, or false if no user
+     *                is logged in.
+     */
+    static public function getBareAuth()
+    {
+        $user = self::getAuth();
+        if ($user) {
+            $pos = strpos($user, '@');
+            if ($pos !== false) {
+                $user = substr($user, 0, $pos);
+            }
+        }
+
+        return $user;
+    }
+
+    /**
+     * Returns the domain of currently logged-in user (e.g., bob@example.com
+     * would be returned as 'example.com').
+     *
+     * @return mixed  The domain suffix of the current user, or false.
+     */
+    static public function getAuthDomain()
+    {
+        if ($user = self::getAuth()) {
+            $pos = strpos($user, '@');
+            if ($pos !== false) {
+                return substr($user, $pos + 1);
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * Returns the requested credential for the currently logged in user, if
+     * present.
+     *
+     * @param string $credential  The credential to retrieve.
+     *
+     * @return mixed  The requested credential, or false if no user is
+     *                logged in.
+     */
+    static public function getCredential($credential)
+    {
+        if (empty($_SESSION['horde_auth']) ||
+            empty($_SESSION['horde_auth']['authenticated'])) {
+            return false;
+        }
+
+        $credentials = Horde_Secret::read(Horde_Secret::getKey('auth'), $_SESSION['horde_auth']['credentials']);
+        $credentials = @unserialize($credentials);
+
+        if (is_array($credentials) &&
+            isset($credentials[$credential])) {
+            return $credentials[$credential];
+        }
+
+        return false;
+    }
+
+    /**
+     * Sets the requested credential for the currently logged in user.
+     *
+     * @param string $credential  The credential to set.
+     * @param string $value       The value to set the credential to.
+     */
+    static public function setCredential($credential, $value)
+    {
+        if (!empty($_SESSION['horde_auth']) &&
+            !empty($_SESSION['horde_auth']['authenticated'])) {
+            $credentials = @unserialize(Horde_Secret::read(Horde_Secret::getKey('auth'), $_SESSION['horde_auth']['credentials']));
+            if (is_array($credentials)) {
+                $credentials[$credential] = $value;
+            } else {
+                $credentials = array($credential => $value);
+            }
+            $_SESSION['horde_auth']['credentials'] = Horde_Secret::write(Horde_Secret::getKey('auth'), serialize($credentials));
+        }
+    }
+
+    /**
+     * Sets a variable in the session saying that authorization has succeeded,
+     * note which userId was authorized, and note when the login took place.
+     *
+     * If a user name hook was defined in the configuration, it gets applied
+     * to the $userId at this point.
+     *
+     * @param string $userId      The userId who has been authorized.
+     * @param array $credentials  The credentials of the user.
+     * @param string $realm       The authentication realm to use.
+     * @param boolean $change     Whether to request that the user change
+     *                            their password.
+     */
+    static public function setAuth($userId, $credentials, $realm = null,
+                                   $change = false)
+    {
+        $userId = self::addHook(trim($userId));
+
+        if (!empty($GLOBALS['conf']['hooks']['postauthenticate']) &&
+            !Horde::callHook('_horde_hook_postauthenticate', array($userId, $credentials, $realm), 'horde', false)) {
+            if (self::getAuthError() != self::REASON_MESSAGE) {
+                self::setAuthError(self::REASON_FAILED);
+            }
+            return false;
+        }
+
+        /* If we're already set with this userId, don't continue. */
+        if (isset($_SESSION['horde_auth']['userId']) &&
+            ($_SESSION['horde_auth']['userId'] == $userId)) {
+            return true;
+        }
+
+        /* Clear any existing info. */
+        self::clearAuth($realm);
+
+        $credentials = Horde_Secret::write(Horde_Secret::getKey('auth'), serialize($credentials));
+
+        if (!empty($realm)) {
+            $userId .= '@' . $realm;
+        }
+
+        $browser = Horde_Browser::singleton();
+
+        $_SESSION['horde_auth'] = array(
+            'authenticated' => true,
+            'browser' => $browser->getAgentString(),
+            'changeRequested' => $change,
+            'credentials' => $credentials,
+            'realm' => $realm,
+            'remote_addr' => isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : null,
+            'timestamp' => time(),
+            'userId' => $userId
+        );
+
+        /* Reload preferences for the new user. */
+        $GLOBALS['registry']->loadPrefs();
+        NLS::setLang($GLOBALS['prefs']->getValue('language'));
+
+        /* Fetch the user's last login time. */
+        $old_login = @unserialize($GLOBALS['prefs']->getValue('last_login'));
+
+        /* Display it, if we have a notification object and the
+         * show_last_login preference is active. */
+        if (isset($GLOBALS['notification']) &&
+            $GLOBALS['prefs']->getValue('show_last_login')) {
+            if (empty($old_login['time'])) {
+                $GLOBALS['notification']->push(_("Last login: Never"), 'horde.message');
+            } else {
+                if (empty($old_login['host'])) {
+                    $GLOBALS['notification']->push(sprintf(_("Last login: %s"), strftime('%c', $old_login['time'])), 'horde.message');
+                } else {
+                    $GLOBALS['notification']->push(sprintf(_("Last login: %s from %s"), strftime('%c', $old_login['time']), $old_login['host']), 'horde.message');
+                }
+            }
+        }
+
+        /* Set the user's last_login information. */
+        $host = empty($_SERVER['HTTP_X_FORWARDED_FOR'])
+            ? $_SERVER['REMOTE_ADDR']
+            : $_SERVER['HTTP_X_FORWARDED_FOR'];
+
+        if (class_exists('Net_DNS')) {
+           $resolver = new Net_DNS_Resolver();
+           $resolver->retry = isset($GLOBALS['conf']['dns']['retry']) ? $GLOBALS['conf']['dns']['retry'] : 1;
+           $resolver->retrans = isset($GLOBALS['conf']['dns']['retrans']) ? $GLOBALS['conf']['dns']['retrans'] : 1;
+           $response = $resolver->query($host, 'PTR');
+           $ptrdname = $response ? $response->answer[0]->ptrdname : $host;
+       } else {
+           $ptrdname = @gethostbyaddr($host);
+       }
+
+        $last_login = array('time' => time(), 'host' => $ptrdname);
+        $GLOBALS['prefs']->setValue('last_login', serialize($last_login));
+
+        if ($change) {
+            $GLOBALS['notification']->push(_("Your password has expired."),
+                                           'horde.message');
+
+            $auth = self::singleton($GLOBALS['conf']['auth']['driver']);
+            if ($auth->hasCapability('update')) {
+                /* A bit of a kludge.  URL is set from the login screen, but
+                 * we aren't completely certain we got here from the login
+                 * screen.  So any screen which calls setAuth() which has a
+                 * url will end up going there.  Should be OK. */
+                $url_param = Horde_Util::getFormData('url');
+
+                if ($url_param) {
+                    $url = Horde::url(Horde_Util::removeParameter($url_param, session_name()), true);
+                    $return_to = $GLOBALS['registry']->get('webroot', 'horde') .  '/index.php';
+                    $return_to = Horde_Util::addParameter($return_to, 'url', $url);
+                } else {
+                    $return_to = Horde::url($GLOBALS['registry']->get('webroot', 'horde') . '/index.php');
+                }
+
+                $url = Horde::applicationUrl('services/changepassword.php');
+                $url = Horde_Util::addParameter($url, array('return_to' => $return_to), null, false);
+
+                header('Location: ' . $url);
+                exit;
+            }
+        }
+
+        return true;
+    }
+
+    /**
+     * Clears any authentication tokens in the current session.
+     *
+     * @param string $realm  The authentication realm to clear.
+     */
+    static public function clearAuth($realm = null)
+    {
+        if (!empty($realm) && isset($_SESSION['horde_auth'][$realm])) {
+            $_SESSION['horde_auth'][$realm] = array('authenticated' => false);
+        } elseif (isset($_SESSION['horde_auth'])) {
+            $_SESSION['horde_auth'] = array('authenticated' => false);
+        }
+
+        /* Remove the user's cached preferences if they are present. */
+        if (isset($GLOBALS['registry'])) {
+            $GLOBALS['registry']->unloadPrefs();
+        }
+    }
+
+    /**
+     * Is the current user an administrator?
+     *
+     * @param string $permission  Allow users with this permission admin access
+     *                            in the current context.
+     * @param integer $permlevel  The level of permissions to check for
+     *                            (PERMS_EDIT, PERMS_DELETE, etc). Defaults
+     *                            to PERMS_EDIT.
+     * @param string $user        The user to check. Defaults to
+     *                            self::getAuth().
+     *
+     * @return boolean  Whether or not this is an admin user.
+     */
+    static public function isAdmin($permission = null, $permlevel = null,
+                                   $user = null)
+    {
+        if (is_null($user)) {
+            $user = self::getAuth();
+        }
+
+        if ($user &&
+            @is_array($GLOBALS['conf']['auth']['admins']) &&
+            in_array($user, $GLOBALS['conf']['auth']['admins'])) {
+            return true;
+        }
+
+        if (!is_null($permission)) {
+            if (is_null($permlevel)) {
+                $permlevel = PERMS_EDIT;
+            }
+            return $GLOBALS['perms']->hasPermission($permission, $user, $permlevel);
+        }
+
+        return false;
+    }
+
+    /**
+     * Applies a hook defined by the function _username_hook_frombackend() to
+     * the given user name if this function exists and user hooks are enabled.
+     *
+     * This method should be called if a authentication backend's user name
+     * needs to be converted to a (unique) Horde user name. The backend's user
+     * name is what the user sees and uses, but internally we use the Horde
+     * user name.
+     *
+     * @param string $userId  The authentication backend's user name.
+     *
+     * @return string  The internal Horde user name.
+     */
+    static public function addHook($userId)
+    {
+        if (!empty($GLOBALS['conf']['hooks']['username'])) {
+            $newId = Horde::callHook('_username_hook_frombackend', array($userId));
+            if (!is_a($newId, 'PEAR_Error')) {
+                return $newId;
+            }
+        }
+
+        return $userId;
+    }
+
+    /**
+     * Applies a hook defined by the function _username_hook_tobackend() to
+     * the given user name if this function exists and user hooks are enabled.
+     *
+     * This method should be called if a Horde user name needs to be converted
+     * to an authentication backend's user name or displayed to the user. The
+     * backend's user name is what the user sees and uses, but internally we
+     * use the Horde user name.
+     *
+     * @param string $userId  The internal Horde user name.
+     *
+     * @return string  The authentication backend's user name.
+     */
+    static public function removeHook($userId)
+    {
+        if (!empty($GLOBALS['conf']['hooks']['username'])) {
+            $newId = Horde::callHook('_username_hook_tobackend', array($userId));
+            if (!is_a($newId, 'PEAR_Error')) {
+                return $newId;
+            }
+        }
+
+        return $userId;
+    }
+
+    /**
+     * Returns the name of the authentication provider.
+     *
+     * @param string $driver  Used by recursive calls when untangling composite
+     *                        auth.
+     * @param array $params   Used by recursive calls when untangling composite
+     *                        auth.
+     *
+     * @return string  The name of the driver currently providing
+     *                 authentication.
+     */
+    static public function getProvider($driver = null, $params = null)
+    {
+        if (is_null($driver)) {
+            $driver = $GLOBALS['conf']['auth']['driver'];
+        }
+
+        if (is_null($params)) {
+            $params = Horde::getDriverConfig('auth', is_array($driver) ? $driver[1] : $driver);
+        }
+
+        if ($driver == 'application') {
+            return isset($params['app']) ? $params['app'] : 'application';
+        } elseif ($driver == 'composite') {
+            if (($login_driver = self::getDriverByParam('loginscreen_switch', $params)) &&
+                !empty($params['drivers'][$login_driver])) {
+                return self::getProvider($params['drivers'][$login_driver]['driver'],
+                                         isset($params['drivers'][$login_driver]['params']) ? $params['drivers'][$login_driver]['params'] : null);
+            }
+            return 'composite';
+        } else {
+            return $driver;
+        }
+    }
+
+    /**
+     * Returns the logout reason.
+     *
+     * @return string One of the logout reasons (see the AUTH_LOGOUT_*
+     *                constants for the valid reasons).  Returns null if there
+     *                is no logout reason present.
+     */
+    static public function getLogoutReason()
+    {
+        return isset($GLOBALS['horde_auth']['logout']['type'])
+            ? $GLOBALS['horde_auth']['logout']['type']
+            : Horde_Util::getFormData(self::REASON_PARAM);
+    }
+
+    /**
+     * Returns the status string to use for logout messages.
+     *
+     * @return string  The logout reason string.
+     */
+    static public function getLogoutReasonString()
+    {
+        switch (self::getLogoutReason()) {
+        case self::REASON_SESSION:
+            return sprintf(_("Your %s session has expired. Please login again."), $GLOBALS['registry']->get('name'));
+
+        case self::REASON_SESSIONIP:
+            return sprintf(_("Your Internet Address has changed since the beginning of your %s session. To protect your security, you must login again."), $GLOBALS['registry']->get('name'));
+
+        case self::REASON_BROWSER:
+            return sprintf(_("Your browser appears to have changed since the beginning of your %s session. To protect your security, you must login again."), $GLOBALS['registry']->get('name'));
+
+        case self::REASON_LOGOUT:
+            return _("You have been logged out.");
+
+        case self::REASON_FAILED:
+            return _("Login failed.");
+            break;
+
+        case self::REASON_BADLOGIN:
+            return _("Login failed because your username or password was entered incorrectly.");
+            break;
+
+        case self::REASON_EXPIRED:
+            return _("Your login has expired.");
+            break;
+
+        case self::REASON_MESSAGE:
+            return isset($GLOBALS['horde_auth']['logout']['msg'])
+                ? $GLOBALS['horde_auth']['logout']['msg']
+                : Horde_Util::getFormData(self::REASON_MSG_PARAM);
+
+        default:
+            return '';
+        }
+    }
+
+    /**
+     * Generates the correct parameters to pass to the given logout URL.
+     *
+     * If no reason/msg is passed in, use the current global authentication
+     * error message.
+     *
+     * @param string $url     The URL to redirect to.
+     * @param string $reason  The reason for logout.
+     * @param string $msg     If reason is self::REASON_MESSAGE, the message to
+     *                        display to the user.
+     *
+     * @return string The formatted URL
+     */
+    static public function addLogoutParameters($url, $reason = null,
+                                               $msg = null)
+    {
+        $params = array('horde_logout_token' => Horde::getRequestToken('horde.logout'));
+
+        if (isset($GLOBALS['registry'])) {
+            $params['app'] = $GLOBALS['registry']->getApp();
+        }
+
+        if (is_null($reason)) {
+            $reason = self::getLogoutReason();
+        }
+
+        if ($reason) {
+            $params[self::REASON_PARAM] = $reason;
+            if ($reason == self::REASON_MESSAGE) {
+                if (is_null($msg)) {
+                    $msg = self::getLogoutReasonString();
+                }
+                $params[self::REASON_MSG_PARAM] = $msg;
+            }
+        }
+
+        return Horde_Util::addParameter($url, $params, null, false);
+    }
+
+    /**
+     * Reads session data to determine if it contains Horde authentication
+     * credentials.
+     *
+     * @param string $session_data  The session data.
+     * @param boolean $info         Return session information.  The following
+     *                              information is returned: userid, realm,
+     *                              timestamp, remote_addr, browser.
+     *
+     * @return array  An array of the user's sesion information if
+     *                authenticated or false.  The following information is
+     *                returned: userid, realm, timestamp, remote_addr, browser.
+     */
+    static public function readSessionData($session_data)
+    {
+        if (empty($session_data)) {
+            return false;
+        }
+
+        $pos = strpos($session_data, 'horde_auth|');
+        if ($pos === false) {
+            return false;
+        }
+
+        $endpos = $pos + 7;
+        $old_error = error_reporting(0);
+
+        while ($endpos !== false) {
+            $endpos = strpos($session_data, '|', $endpos);
+            $data = unserialize(substr($session_data, $pos + 7, $endpos));
+            if (is_array($data)) {
+                error_reporting($old_error);
+                if (empty($data['authenticated'])) {
+                    return false;
+                }
+                return array(
+                    'browser' => $data['browser'],
+                    'realm' => $data['realm'],
+                    'remote_addr' => $data['remote_addr'],
+                    'timestamp' => $data['timestamp'],
+                    'userid' => $data['userId']
+                );
+            }
+            ++$endpos;
+        }
+
+        return false;
+    }
+
+    /**
+     * Sets the error message for an invalid authentication.
+     *
+     * @param string $type  The type of error (self::REASON_* constant).
+     * @param string $msg   The error message/reason for invalid
+     *                      authentication.
+     */
+    public function setAuthError($type, $msg = null)
+    {
+        $GLOBALS['horde_auth']['logout'] = array(
+            'msg' => $msg,
+            'type' => $type
+        );
+    }
+
+    /**
+     * Returns the error type for an invalid authentication or false on error.
+     *
+     * @return mixed  Error type or false on error.
+     */
+    public function getAuthError()
+    {
+        return isset($GLOBALS['horde_auth']['logout']['type'])
+            ? $GLOBALS['horde_auth']['logout']['type']
+            : false;
+    }
+
+    /**
+     * Returns the appropriate authentication driver, if any, selecting by the
+     * specified parameter.
+     *
+     * @param string $name          The parameter name.
+     * @param array $params         The parameter list.
+     * @param string $driverparams  A list of parameters to pass to the driver.
+     *
+     * @return mixed Return value or called user func or null if unavailable
+     */
+    public function getDriverByParam($name, $params,
+                                     $driverparams = array())
+    {
+        if (isset($params[$name]) &&
+            function_exists($params[$name])) {
+            return call_user_func_array($params[$name], $driverparams);
+        }
+
+        return null;
+    }
+
+    /**
+     * Performs check on session to see if IP Address has changed since the
+     * last access.
+     *
+     * @return boolean  True if IP Address is the same (or the check is
+     *                  disabled), false if the address has changed.
+     */
+    static public function checkSessionIP()
+    {
+        return (empty($GLOBALS['conf']['auth']['checkip']) ||
+                (isset($_SESSION['horde_auth']['remote_addr']) &&
+                 ($_SESSION['horde_auth']['remote_addr'] == $_SERVER['REMOTE_ADDR'])));
+    }
+
+    /**
+     * Performs check on session to see if browser string has changed since
+     * the last access.
+     *
+     * @return boolean  True if browser string is the same, false if the
+     *                  string has changed.
+     */
+    static public function checkBrowserString()
+    {
+        return (empty($GLOBALS['conf']['auth']['checkbrowser']) ||
+                ($_SESSION['horde_auth']['browser'] == $GLOBALS['browser']->getAgentString()));
+    }
+
+    /**
+     * Converts to allowed 64 characters for APRMD5 passwords.
+     *
+     * @param string $value   TODO
+     * @param integer $count  TODO
+     *
+     * @return string  $value converted to the 64 MD5 characters.
+     */
+    static protected function _toAPRMD5($value, $count)
+    {
+        $aprmd5 = '';
+        $count = abs($count);
+        $valid = self::APRMD5_VALID;
+
+        while (--$count) {
+            $aprmd5 .= $valid[$value & 0x3f];
+            $value >>= 6;
+        }
+
+        return $aprmd5;
+    }
+
+}
diff --git a/framework/Auth/lib/Horde/Auth/Application.php b/framework/Auth/lib/Horde/Auth/Application.php
new file mode 100644 (file)
index 0000000..3924262
--- /dev/null
@@ -0,0 +1,199 @@
+<?php
+/**
+ * The Horde_Auth_Application class provides a wrapper around
+ * application-provided Horde authentication which fits inside the
+ * Horde Horde_Auth:: API.
+ *
+ * Required parameters:
+ * <pre>
+ * 'app' - (string) The application which is providing authentication.
+ * </pre>
+ *
+ * Copyright 2002-2009 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you did
+ * not receive this file, see http://opensource.org/licenses/lgpl-2.1.php
+ *
+ * @author  Chuck Hagenbuch <chuck@horde.org>
+ * @package Horde_Auth
+ */
+class Horde_Auth_Application extends Horde_Auth_Driver
+{
+    /**
+     * Cache for hasCapability().
+     *
+     * @var array
+     */
+    protected $_loaded = array();
+
+    /**
+     * Constructor.
+     *
+     * @param array $params  A hash containing connection parameters.
+     */
+    public function __construct($params = array())
+    {
+        Horde::assertDriverConfig($params, 'auth', array('app'), 'authentication application');
+
+        parent::__construct($params);
+    }
+
+    /**
+     * 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)
+    {
+        $capability = strtolower($capability);
+
+        $methods = array(
+            'add' => 'addUser',
+            'exists' => 'userExists',
+            'list' => 'userList',
+            'remove' => 'removeUser',
+            'update' => 'updateUser'
+        );
+
+        if (!in_array($capability, $this->_loaded) &&
+            isset($methods[$capability])) {
+            $this->_capabilities[$capability] = $GLOBALS['registry']->hasMethod($methods[$capability], $this->_params['app']);
+            $this->_loaded[] = $capability;
+        }
+
+        return parent::hasCapability($capability);
+    }
+
+    /**
+     * 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_Exception
+     */
+    protected function _authenticate($userId, $credentials)
+    {
+        if (!$GLOBALS['registry']->hasMethod('authenticate', $this->_params['app'])) {
+            throw new Horde_Exception($this->_params['app'] . ' does not provide an authenticate() method.');
+        }
+
+        if (!$GLOBALS['registry']->callByPackage($this->_params['app'], 'authenticate', array('userId' => $userId, 'credentials' => $credentials, 'params' => $this->_params))) {
+            throw new Horde_Exception('', Horde_Auth::REASON_BADLOGIN);
+        }
+
+        // Horrific hack.  Avert your eyes.  Since an application may already
+        // set the authentication information necessary, we don't want to
+        // overwrite that info.  Coming into this function, we know that
+        // the authentication has not yet been set in this session.  So after
+        // calling the app-specific auth handler, if authentication
+        // information has suddenly appeared, it follows that the information
+        // has been stored already in the session and we shouldn't overwrite.
+        // So grab the authentication ID set and stick it in $_authCredentials
+        // this will eventually cause setAuth() in authenticate() to exit
+        // before re-setting the auth info values.
+        if ($ret && ($authid = Horde_Auth::getAuth())) {
+            $this->_authCredentials['userId'] = $authid;
+        }
+    }
+
+    /**
+     * Return the URI of the login screen for this authentication method.
+     *
+     * @param string $app  The application to use.
+     * @param string $url  The URL to redirect to after login.
+     *
+     * @return string  The login screen URI.
+     */
+    public function getLoginScreen($app = 'horde', $url = '')
+    {
+        return parent::getLoginScreen($this->_params['app'], $url);
+    }
+
+    /**
+     * List all users in the system.
+     *
+     * @return array  The array of userIds.
+     * @throws Horde_Exception
+     */
+    public function listUsers()
+    {
+        return $this->hasCapability('list')
+            ? $GLOBALS['registry']->callByPackage($this->_params['app'], 'userList')
+            : parent::listUsers();
+    }
+
+    /**
+     * Checks if $userId exists in the system.
+     *
+     * @param string $userId  User ID to check.
+     *
+     * @return boolean  Whether or not $userId already exists.
+     */
+    public function exists($userId)
+    {
+        return $this->hasCapability('exists')
+            ? $GLOBALS['registry']->callByPackage($this->_params['app'], 'userExists', array($userId))
+            : parent::exists($userId);
+    }
+
+    /**
+     * Add a set of authentication credentials.
+     *
+     * @param string $userId      The userId to add.
+     * @param array $credentials  The credentials to use.
+     *
+     * @throws Horde_Exception
+     */
+    public function addUser($userId, $credentials)
+    {
+        if ($this->hasCapability('add')) {
+            $GLOBALS['registry']->callByPackage($this->_params['app'], 'addUser', array($userId, $credentials));
+        } else {
+            parent::addUser($userId, $credentials);
+        }
+    }
+
+    /**
+     * 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_Exception
+     */
+    public function updateUser($oldID, $newID, $credentials)
+    {
+        if ($this->hasCapability('update')) {
+            $GLOBALS['registry']->callByPackage($this->_params['app'], 'updateUser', array($oldID, $newID, $credentials));
+        } else {
+            parent::addUser($userId, $credentials);
+        }
+    }
+
+    /**
+     * Delete a set of authentication credentials.
+     *
+     * @param string $userId  The userId to delete.
+     *
+     * @throws Horde_Exception
+     */
+    public function removeUser($userId)
+    {
+        if ($this->hasCapability('remove')) {
+            $res = $GLOBALS['registry']->callByPackage($this->_params['app'], 'removeUser', array($userId));
+            if (is_a($res, 'PEAR_Error')) {
+                throw new Horde_Exception($result);
+            }
+
+            Horde_Auth::removeUserData($userId);
+        } else {
+            parent::removeUser($userId);
+        }
+    }
+
+}
diff --git a/framework/Auth/lib/Horde/Auth/Auto.php b/framework/Auth/lib/Horde/Auth/Auto.php
new file mode 100644 (file)
index 0000000..3fac9b3
--- /dev/null
@@ -0,0 +1,70 @@
+<?php
+/**
+ * The Horde_Auth_Auto class transparently logs users in to Horde using ONE
+ * username, either defined in the config or defaulting to 'horde_user'.
+ * This is only for use in testing or behind a firewall; it should NOT be
+ * used on a public, production machine.
+ *
+ * Optional parameters:
+ * <pre>
+ * 'password' - (string) The password to record in the user's credentials.
+ *              DEFAULT: none
+ * 'requestuser' - (boolean) If true, allow username to be passed by GET, POST
+ *                 or cookie.
+ *                DEFAULT: No
+ * 'username' - (string) The username to authenticate everyone as.
+ *              DEFAULT: 'horde_user'
+ * </pre>
+ *
+ * Copyright 1999-2009 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you did
+ * not receive this file, see http://opensource.org/licenses/lgpl-2.1.php
+ *
+ * @author  Chuck Hagenbuch <chuck@horde.org>
+ * @package Horde_Auth
+ */
+class Horde_Auth_Auto extends Horde_Auth_Driver
+{
+    /**
+     * An array of capabilities, so that the driver can report which
+     * operations it supports and which it doesn't.
+     *
+     * @var array
+     */
+    protected $_capabilities = array(
+        'transparent' => true
+    );
+
+    /**
+     * Constructor.
+     *
+     * @param array $params  A hash containing parameters.
+     */
+    public function __construct($params = array())
+    {
+        parent::__construct($params);
+
+        if (!isset($this->_params['username'])) {
+            $this->_params['username'] = 'horde_user';
+        }
+    }
+
+    /**
+     * Automatic authentication: Set the user allowed IP block.
+     *
+     * @return boolean  Whether or not the client is allowed.
+     */
+    protected function _transparent()
+    {
+        $username = (!empty($this->_params['requestuser']) && isset($_REQUEST['username']))
+            ? $_REQUEST['username']
+            : $this->_params['username'];
+
+        return Horde_Auth::setAuth($username, array(
+            'transparent' => 1,
+            'password' => isset($this->_params['password']) ? $this->_params['password'] : null
+        ));
+    }
+
+}
diff --git a/framework/Auth/lib/Horde/Auth/Composite.php b/framework/Auth/lib/Horde/Auth/Composite.php
new file mode 100644 (file)
index 0000000..5dd17ab
--- /dev/null
@@ -0,0 +1,254 @@
+<?php
+/**
+ * The Auth_composite class provides a wrapper around
+ * application-provided Horde authentication which fits inside the
+ * Horde Horde_Auth:: API.
+ *
+ * Copyright 2002-2009 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you did
+ * not receive this file, see http://opensource.org/licenses/lgpl-2.1.php
+ *
+ * @author  Chuck Hagenbuch <chuck@horde.org>
+ * @package Horde_Auth
+ */
+class Horde_Auth_Composite extends Horde_Auth_Driver
+{
+    /**
+     * Hash containing any instantiated drivers.
+     *
+     * @var array
+     */
+    protected $_drivers = array();
+
+    /**
+     * Return the named parameter for the current auth driver.
+     *
+     * @param string $param  The parameter to fetch.
+     *
+     * @return string  The parameter's value.
+     */
+    public function getParam($param)
+    {
+        if (($login_driver = Horde_Auth::getDriverByParam('loginscreen_switch', $this->_params)) &&
+            $this->_loadDriver($login_driver)) {
+            return $this->_drivers[$login_driver]->getParam($param);
+        }
+
+        return null;
+    }
+
+    /**
+     * 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_Exception
+     */
+    protected function _authenticate($userId, $credentials)
+    {
+        if (($auth_driver = Horde_Auth::getDriverByParam('loginscreen_switch', $this->_params)) &&
+            $this->_loadDriver($auth_driver)) {
+            $this->_drivers[$auth_driver]->authenticate($userId, $credentials);
+            return;
+        }
+
+        if (($auth_driver = Horde_Auth::getDriverByParam('username_switch', $this->_params, array($userId))) &&
+            $this->_loadDriver($auth_driver)) {
+            $this->_drivers[$auth_driver]->hasCapability('transparent');
+            return;
+        }
+
+        throw new Horde_Exception('', Horde_Auth::REASON_FAILED);
+    }
+
+    /**
+     * Query 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)
+    {
+        switch ($capability) {
+        case 'add':
+        case 'update':
+        case 'remove':
+        case 'list':
+            if (!empty($this->_params['admin_driver']) &&
+                $this->_loadDriver($this->_params['admin_driver'])) {
+                return $this->_drivers[$this->_params['admin_driver']]->hasCapability($capability);
+            } else {
+                return false;
+            }
+            break;
+
+        case 'transparent':
+            if (($login_driver = Horde_Auth::getDriverByParam('loginscreen_switch', $this->_params)) &&
+                $this->_loadDriver($login_driver)) {
+                return $this->_drivers[$login_driver]->hasCapability('transparent');
+            }
+            return false;
+            break;
+
+        default:
+            return false;
+        }
+    }
+
+    /**
+     * Automatic authentication: Find out if the client matches an allowed IP
+     * block.
+     *
+     * @return boolean  Whether or not the client is allowed.
+     */
+    protected function _transparent()
+    {
+        if (($login_driver = Horde_Auth::getDriverByParam('loginscreen_switch', $this->_params)) &&
+            $this->_loadDriver($login_driver)) {
+            return $this->_drivers[$login_driver]->transparent();
+        }
+
+        return false;
+    }
+
+    /**
+     * Return the URI of the login screen for this authentication object.
+     *
+     * @param string $app  The application to use.
+     * @param string $url  The URL to redirect to after login.
+     *
+     * @return string  The login screen URI.
+     */
+    public function getLoginScreen($app = 'horde', $url = '')
+    {
+        if (($login_driver = Horde_Auth::getDriverByParam('loginscreen_switch', $this->_params)) &&
+            $this->_loadDriver($login_driver)) {
+            return $this->_drivers[$login_driver]->getLoginScreen($app, $url);
+        }
+
+        return parent::getLoginScreen($app, $url);
+    }
+
+    /**
+     * Add a set of authentication credentials.
+     *
+     * @param string $userId       The userId to add.
+     * @param array  $credentials  The credentials to use.
+     *
+     * @throws Horde_Exception
+     */
+    public function addUser($userId, $credentials)
+    {
+        if (!empty($this->_params['admin_driver']) &&
+            $this->_loadDriver($this->_params['admin_driver'])) {
+            $this->_drivers[$this->_params['admin_driver']]->addUser($userId, $credentials);
+        } else {
+            parent::addUser($userId, $credentials);
+        }
+    }
+
+    /**
+     * 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_Exception
+     */
+    public function updateUser($oldID, $newID, $credentials)
+    {
+        if (!empty($this->_params['admin_driver']) &&
+            $this->_loadDriver($this->_params['admin_driver'])) {
+            $this->_drivers[$this->_params['admin_driver']]->updateUser($oldID, $newID, $credentials);
+        } else {
+            parent::updateUser($oldID, $newID, $credentials);
+        }
+    }
+
+    /**
+     * Delete a set of authentication credentials.
+     *
+     * @param string $userId  The userId to delete.
+     *
+     * @throws Horde_Exception
+     */
+    public function removeUser($userId)
+    {
+        if (!empty($this->_params['admin_driver']) &&
+            $this->_loadDriver($this->_params['admin_driver'])) {
+            $this->_drivers[$this->_params['admin_driver']]->removeUser($userId);
+        } else {
+            parent::removeUser($userId);
+        }
+    }
+
+    /**
+     * List all users in the system.
+     *
+     * @return array  The array of userIds.
+     * @throws Horde_Exception
+     */
+    public function listUsers()
+    {
+        if (!empty($this->_params['admin_driver']) &&
+            $this->_loadDriver($this->_params['admin_driver'])) {
+            return $this->_drivers[$this->_params['admin_driver']]->listUsers();
+        }
+
+        return parent::listUsers();
+    }
+
+    /**
+     * Checks if a userId exists in the system.
+     *
+     * @param string $userId  User ID to check
+     *
+     * @return boolean  Whether or not the userId already exists.
+     */
+    public function exists($userId)
+    {
+        if (!empty($this->_params['admin_driver']) &&
+            $this->_loadDriver($this->_params['admin_driver'])) {
+            return $this->_drivers[$this->_params['admin_driver']]->exists($userId);
+        }
+
+        return parent::exists($userId);
+    }
+
+    /**
+     * Loads one of the drivers in our configuration array, if it isn't already
+     * loaded.
+     *
+     * @param string $driver  The name of the driver to load.
+     *
+     * @return boolean  True if driver successfully initializes.
+     */
+    protected function _loadDriver($driver)
+    {
+        if (empty($this->_drivers[$driver])) {
+            // This is a bit specialized for Horde::getDriverConfig(),
+            // so localize it here:
+            global $conf;
+            if (!empty($this->_params['drivers'][$driver]['params'])) {
+                $params = $this->_params['drivers'][$driver]['params'];
+                if (isset($conf[$this->_params['drivers'][$driver]['driver']])) {
+                    $params = array_merge($conf[$this->_params['drivers'][$driver]['driver']], $params);
+                }
+            } elseif (!empty($conf[$driver])) {
+                $params = $conf[$driver];
+            } else {
+                $params = null;
+            }
+
+            $this->_drivers[$driver] = Horde_Auth::singleton($this->_params['drivers'][$driver]['driver'], $params);
+        }
+
+        return isset($this->_drivers[$driver]);
+    }
+
+}
diff --git a/framework/Auth/lib/Horde/Auth/Customsql.php b/framework/Auth/lib/Horde/Auth/Customsql.php
new file mode 100644 (file)
index 0000000..243e92e
--- /dev/null
@@ -0,0 +1,297 @@
+<?php
+/**
+ * The Horde_Auth_Customsql class provides a sql implementation of the Horde
+ * authentication system with the possibility to set custom-made queries.
+ *
+ * Required parameters: See Horde_Auth_Sql driver.
+ * <pre>
+ * Some special tokens can be used in the sql query. They are replaced
+ * at the query stage:
+ *
+ *   - '\L' will be replaced by the user's login
+ *   - '\P' will be replaced by the user's password.
+ *   - '\O' will be replaced by the old user's login (required for update)
+ *
+ *   Eg: "SELECT * FROM users WHERE uid = \L
+ *                            AND passwd = \P
+ *                            AND billing = 'paid'
+ *
+ *   'query_auth'    Authenticate the user.       '\L' & '\P'
+ *   'query_add'     Add user.                    '\L' & '\P'
+ *   'query_getpw'   Get one user's password.     '\L'
+ *   'query_update'  Update user.                 '\O', '\L' & '\P'
+ *   'query_resetpassword'  Reset password.       '\L', & '\P'
+ *   'query_remove'  Remove user.                 '\L'
+ *   'query_list'    List user.
+ *   'query_exists'  Check for existance of user. '\L'
+ * </pre>
+ *
+ * Optional parameters: See Horde_Auth_Sql driver.
+ *
+ * Copyright 2002 Ronnie Garcia <ronnie@mk2.net>
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you did
+ * not receive this file, see http://opensource.org/licenses/lgpl-2.1.php
+ *
+ * @author  Ronnie Garcia <ronnie@mk2.net>
+ * @author  Chuck Hagenbuch <chuck@horde.org>
+ * @author  Joel Vandal <joel@scopserv.com>
+ * @package Horde_Auth
+ */
+class Horde_Auth_Customsql 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' => true,
+        'update' => true
+    );
+
+    /**
+     * Constructor.
+     *
+     * @param array $params  A hash containing connection parameters.
+     */
+    public function __construct($params = array())
+    {
+        Horde::assertDriverConfig($params, 'auth',
+            array('query_auth'),
+            'authentication custom SQL');
+
+        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_Exception
+     */
+    protected function _authenticate($userId, $credentials)
+    {
+        try {
+            $this->_connect();
+        } catch (Horde_Exception $e) {
+            throw new Horde_Exception('', Horde_Auth::REASON_FAILED);
+        }
+
+        /* Build a custom query, based on the config file. */
+        $query = $this->_params['query_auth'];
+        $query = str_replace('\L', $this->_db->quote($userId), $query);
+        $query = str_replace('\P', $this->_db->quote(Horde_Auth::getCryptedPassword(
+                                                         $credentials['password'],
+                                                         $this->_getPassword($userId),
+                                                         $this->_params['encryption'],
+                                                         $this->_params['show_encryption'])), $query);
+
+        $result = $this->_db->query($query);
+        if ($result instanceof PEAR_Error) {
+            throw new Horde_Exception('', Horde_Auth::REASON_FAILED);
+        }
+
+        $row = $result->fetchRow(DB_GETMODE_ASSOC);
+
+        /* If we have at least one returned row, then the user is valid. */
+        if (is_array($row)) {
+            $result->free();
+            return;
+        }
+
+        $result->free();
+        throw new Horde_Exception('', Horde_Auth::REASON_BADLOGIN);
+    }
+
+    /**
+     * Add a set of authentication credentials.
+     *
+     * @param string $userId      The userId to add.
+     * @param array $credentials  The credentials to add.
+     *
+     * @throws Horde_Exception
+     */
+    public function addUser($userId, $credentials)
+    {
+        $this->_connect();
+
+        /* Build a custom query, based on the config file. */
+        $query = $this->_params['query_add'];
+        $query = str_replace('\L', $this->_db->quote($userId), $query);
+        $query = str_replace('\P', $this->_db->quote(Horde_Auth::getCryptedPassword(
+                                                         $credentials['password'], '',
+                                                         $this->_params['encryption'],
+                                                         $this->_params['show_encryption'])), $query);
+
+        $result = $this->_db->query($query);
+        if ($result instanceof PEAR_Error) {
+            throw new Horde_Exception($result);
+        }
+    }
+
+    /**
+     * 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_Exception
+     */
+    function updateUser($oldId, $newId, $credentials)
+    {
+        $this->_connect();
+
+        /* Build a custom query, based on the config file. */
+        $query = $this->_params['query_update'];
+        $query = str_replace('\O', $this->_db->quote($oldId), $query);
+        $query = str_replace('\L', $this->_db->quote($newId), $query);
+        $query = str_replace('\P', $this->_db->quote(Horde_Auth::getCryptedPassword(
+                                                         $credentials['password'],
+                                                         $this->_getPassword($oldId),
+                                                         $this->_params['encryption'],
+                                                         $this->_params['show_encryption'])), $query);
+
+        $result = $this->_db->query($query);
+        if ($result instanceof PEAR_Error) {
+            throw new Horde_Exception($result);
+        }
+    }
+
+    /**
+     * Resets 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 on success.
+     * @throws Horde_Exception
+     */
+    public function resetPassword($userId)
+    {
+        $this->_connect();
+
+        /* Get a new random password. */
+        $password = Horde_Auth::genRandomPassword();
+
+        /* Build the SQL query. */
+        $query = $this->_params['query_resetpassword'];
+        $query = str_replace('\L', $this->_db->quote($userId), $query);
+        $query = str_replace('\P', $this->_db->quote(Horde_Auth::getCryptedPassword($password,
+                                                                               '',
+                                                                               $this->_params['encryption'],
+                                                                               $this->_params['show_encryption'])), $query);
+
+        $result = $this->_db->query($query);
+        if ($result instanceof PEAR_Error) {
+            throw new Horde_Exception($result);
+        }
+
+        return $password;
+    }
+
+    /**
+     * Delete a set of authentication credentials.
+     *
+     * @param string $userId  The userId to delete.
+     *
+     * @throws Horde_Exception
+     */
+    public function removeUser($userId)
+    {
+        $this->_connect();
+
+        /* Build a custom query, based on the config file. */
+        $query = $this->_params['query_remove'];
+        $query = str_replace('\L', $this->_db->quote($userId), $query);
+
+        $result = $this->_db->query($query);
+        if ($result instanceof PEAR_Error) {
+            throw new Horde_Exception($result);
+        }
+
+        $this->removeUserData($userId);
+    }
+
+    /**
+     * List all users in the system.
+     *
+     * @return array  The array of userIds.
+     * @throws Horde_Exception
+     */
+    public function listUsers()
+    {
+        $this->_connect();
+
+        /* Build a custom query, based on the config file. */
+        $query = $this->_params['query_list'];
+        $query = str_replace('\L', $this->_db->quote(Horde_Auth::getAuth()), $query);
+
+        $result = $this->_db->getAll($query, null, DB_FETCHMODE_ORDERED);
+        if ($result instanceof PEAR_Error) {
+            throw new Horde_Exception($result);
+        }
+
+        /* Loop through and build return array. */
+        $users = array();
+        foreach ($result as $ar) {
+            $users[] = $ar[0];
+        }
+
+        return $users;
+    }
+
+    /**
+     * Checks if a userId exists in the system.
+     *
+     * @return boolean  Whether or not the userId already exists.
+     */
+    public function exists($userId)
+    {
+        try {
+            $this->_connect();
+        } catch (Horde_Exception $e) {
+            return false;
+        }
+
+        /* Build a custom query, based on the config file. */
+        $query = $this->_params['query_exists'];
+        $query = str_replace('\L', $this->_db->quote($userId), $query);
+
+        $result = $this->_db->getOne($query);
+
+        return ($result instanceof PEAR_Error)
+            ? false
+            : (bool)$result;
+    }
+
+    /**
+     * Fetch $userId's current password - needed for the salt with some
+     * encryption schemes when doing authentication or updates.
+     *
+     * @param string $userId  TODO
+     *
+     * @return string  $userId's current password.
+     */
+    protected function _getPassword($userId)
+    {
+        /* Retrieve the old password in case we need the salt. */
+        $query = $this->_params['query_getpw'];
+        $query = str_replace('\L', $this->_db->quote($userId), $query);
+        $pw = $this->_db->getOne($query);
+        if ($pw instanceof PEAR_Error) {
+            Horde::logMessage($pw, __FILE__, __LINE__, PEAR_LOG_ERR);
+            return '';
+        }
+
+        return $pw;
+    }
+
+}
diff --git a/framework/Auth/lib/Horde/Auth/Cyrsql.php b/framework/Auth/lib/Horde/Auth/Cyrsql.php
new file mode 100644 (file)
index 0000000..a44d7f0
--- /dev/null
@@ -0,0 +1,497 @@
+<?php
+/**
+ * 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.
+ *
+ * Required parameters: See Horde_Auth_Sql driver.
+ * <pre>
+ * 'cyradmin'  The username of the cyrus administrator.
+ * 'cyrpass'   The password for the cyrus administrator.
+ * 'hostspec'        The hostname or IP address of the server.
+ *                   DEFAULT: 'localhost'
+ * 'port'            The server port to which we will connect.
+ *                   IMAP is generally 143, while IMAP-SSL is generally 993.
+ *                   DEFAULT: Encryption port default
+ * 'secure'          The encryption to use.  Either 'none', 'ssl', or 'tls'.
+ *                   DEFAULT: 'none'
+ * </pre>
+ *
+ * Optional parameters: See Horde_Auth_Sql driver.
+ * <pre>
+ * 'domain_field'    If set to anything other than 'none' this is used as
+ *                   field name where domain is stored.
+ *                   DEFAULT: 'domain_name'
+ * 'hidden_accounts' An array of system accounts to hide from the user
+ *                   interface.
+ * 'folders'         An array of folders to create under username.
+ *                   DEFAULT: NONE
+ * 'quota'           The quota (in kilobytes) to grant on the mailbox.
+ *                   DEFAULT: NONE
+ * 'unixhier'        The value of imapd.conf's unixhierarchysep setting.
+ *                   Set this to true if the value is true in imapd.conf.
+ * </pre>
+ *
+ * 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>
+ *
+ * Copyright 2002-2009 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you did
+ * not receive this file, see http://opensource.org/licenses/lgpl-2.1.php
+ *
+ * @author  Ilya Krel <mail@krel.org>
+ * @author  Jan Schneider <jan@horde.org>
+ * @package Horde_Auth
+ */
+class Horde_Auth_Cyrsql extends Horde_Auth_Sql
+{
+    /**
+     * Horde_Imap_Client object.
+     *
+     * @var Horde_Imap_Client_Base
+     */
+    protected $_ob;
+
+    /**
+     * Hierarchy separator to use (e.g., is it user/mailbox or user.mailbox)
+     *
+     * @var string
+     */
+    protected $_separator = '.';
+
+    /**
+     * Constructor.
+     *
+     * @param array $params  A hash containing connection parameters.
+     */
+    public function __construct($params = array())
+    {
+        parent::__construct($params);
+
+        $admin_params = array(
+            'admin_user' => $params['cyradmin'],
+            'admin_password' => $params['cyrpass'],
+            'dsn' => $params['imap_dsn']
+        );
+
+        if (!empty($this->_params['unixhier'])) {
+            $admin_params['userhierarchy'] = 'user/';
+        }
+
+        if (!empty($this->_params['unixhier'])) {
+            $this->_separator = '/';
+        }
+    }
+
+    /**
+     * 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_Exception
+     */
+    protected function _authenticate($userId, $credentials)
+    {
+        try {
+            $this->_connect();
+        } catch (Horde_Exception $e) {
+            Horde::logMessage($e, __FILE__, __LINE__, PEAR_LOG_ERR);
+            throw new Horde_Exception('', Horde_Auth::REASON_FAILED);
+        }
+
+        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);
+        }
+
+        Horde::logMessage('SQL Query by Horde_Auth_Cyrsql::_authenticate(): ' . $query, __FILE__, __LINE__, PEAR_LOG_DEBUG);
+
+        $result = $this->_db->query($query, $values);
+        if ($result instanceof PEAR_Error) {
+            Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR);
+            throw new Horde_Exception('', Horde_Auth::REASON_FAILED);
+        }
+
+        $row = $result->fetchRow(DB_GETMODE_ASSOC);
+        if (is_array($row)) {
+            $result->free();
+        } else {
+            throw new Horde_Exception('', Horde_Auth::REASON_BADLOGIN);
+        }
+
+        if (!$this->_comparePasswords($row[$this->_params['password_field']],
+                                      $credentials['password'])) {
+            throw new Horde_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_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->_authCredentials['changeRequested'] = true;
+        }
+    }
+
+    /**
+     * Add a set of authentication credentials.
+     *
+     * @param string $userId       The userId to add.
+     * @param array  $credentials  The credentials to add.
+     *
+     * @throw Horde_Exception
+     */
+    public function addUser($userId, $credentials)
+    {
+        $this->_connect();
+
+        if (!empty($this->_params['domain_field']) &&
+            ($this->_params['domain_field'] != 'none')) {
+            list($name, $domain) = explode('@', $userId);
+            /* Build the SQL query. */
+            $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']));
+
+            Horde::logMessage('SQL Query by Horde_Auth_Cyrsql::addUser(): ' . $query, __FILE__, __LINE__, PEAR_LOG_DEBUG);
+
+            $dbresult = $this->_db->query($query, $values);
+            $query = 'INSERT INTO virtual (alias, dest, username, status) VALUES (?, ?, ?, 1)';
+            $values = array($userId, $userId, $name);
+
+            Horde::logMessage('SQL Query by Horde_Auth_Cyrsql::addUser(): ' . $query, __FILE__, __LINE__, PEAR_LOG_DEBUG);
+
+            $dbresult2 = $this->_db->query($query, $values);
+            if ($dbresult2 instanceof PEAR_Error) {
+                throw new Horde_Exception($dbresult2);
+            }
+        } else {
+            parent::addUser($userId, $credentials);
+        }
+
+        try {
+            $mailbox = Horde_String::convertCharset($this->_params['userhierarchy'] . $userId, NLS::getCharset(), 'utf7-imap');
+            $ob->createMailbox($mailbox);
+            $ob->setACL($mailbox, $this->_params['cyradm'], 'lrswipcda');
+        } catch (Horde_Imap_Client_Exception $e) {
+            throw new Horde_Exception($e);
+        }
+
+        foreach ($this->_params['folders'] as $folders) {
+            if (!empty($this->_params['domain_field']) &&
+                ($this->_params['domain_field'] != 'none')) {
+                list($userName, $domain) = explode('@', $userName);
+                $tmp = $userName . $this->_separator . $value . '@' . $domain;
+Horde_String::convertCharset($userName . $this->_separator . $value . '@' . $domain, NLS::getCharset(), 'utf7-imap');
+            } else {
+                $tmp = $userName . $this->_separator . $value;
+            }
+
+            $tmp = Horde_String::convertCharset($tmp, NLS::getCharset(), 'utf7-imap');
+            $ob->createMailbox($tmp);
+            $ob->setACL($tmp, $this->_params['cyradm'], 'lrswipcda');
+        }
+
+        if (isset($this->_params['quota']) &&
+            ($this->_params['quota'] >= 0)) {
+            try {
+                $this->_ob->setQuota($mailbox, array('storage' => $this->_params['quota']));
+            } catch (Horde_Imap_Client_Exception $e) {
+                throw new Horde_Exception($e);
+            }
+        }
+
+        if (isset($this->_params['quota']) &&
+            ($this->_params['quota'] >= 0) &&
+            !@imap_set_quota($this->_imapStream, 'user' . $this->_separator . $userId, $this->_params['quota'])) {
+            throw new Horde_Exception(sprintf(_("IMAP mailbox quota creation failed: %s"), imap_last_error()));
+        }
+    }
+
+    /**
+     * Delete a set of authentication credentials.
+     *
+     * @param string $userId  The userId to delete.
+     *
+     * @throws Horde_Exception
+     */
+    function removeUser($userId)
+    {
+        $this->_connect();
+
+        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);
+
+            Horde::logMessage('SQL Query by Horde_Auth_Cyrsql::removeUser(): ' . $query, __FILE__, __LINE__, PEAR_LOG_DEBUG);
+
+            $dbresult = $this->_db->query($query, $values);
+            $query = 'DELETE FROM virtual WHERE dest = ?';
+            $values = array($userId);
+
+            Horde::logMessage('SQL Query by Horde_Auth_Cyrsql::removeUser(): ' . $query, __FILE__, __LINE__, PEAR_LOG_DEBUG);
+
+            $dbresult2 = $this->_db->query($query, $values);
+            if ($dbresult2 instanceof PEAR_Error) {
+                return $dbresult2;
+            }
+        } else {
+            parent::removeUser($userId);
+        }
+
+        /* Set ACL for mailbox deletion. */
+        list($admin) = explode('@', $this->_params['cyradmin']);
+
+        try {
+            $this->_ob->setACL($mailbox, $admin, array('rights' => 'lrswipcda'));
+            $this->_ob->deleteMailbox($mailbox);
+        } catch (Horde_Imap_Client_Exception $e) {
+            throw new Horde_Exception($e);
+        }
+
+        Horde_Auth::removeUserData($userId);
+    }
+
+    /**
+     * List all users in the system.
+     *
+     * @return mixed  The array of userIds.
+     * @throws Horde_Exception
+     */
+    public function listUsers()
+    {
+        $this->_connect();
+
+        if (!empty($this->_params['domain_field']) &&
+            ($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']);
+        }
+
+        Horde::logMessage('SQL Query by Horde_Auth_Cyrsql::listUsers(): ' . $query, __FILE__, __LINE__, PEAR_LOG_DEBUG);
+
+        $result = $this->_db->getAll($query, null, DB_FETCHMODE_ORDERED);
+        if ($result instanceof PEAR_Error) {
+            throw new Horde_Exception($result);
+        }
+
+        /* 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[0], $this->_params['hidden_accounts'])) {
+                    $users[] = $ar[0] . '@' . $ar[1];
+                }
+            }
+        } else {
+            foreach ($result as $ar) {
+                if (!in_array($ar[0], $this->_params['hidden_accounts'])) {
+                    $users[] = $ar[0];
+                }
+            }
+        }
+
+        return $users;
+    }
+
+    /**
+     * 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_Exception
+     */
+    public function updateUser($oldID, $newID, $credentials)
+    {
+        $this->_connect();
+
+        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);
+        }
+
+        Horde::logMessage('SQL Query by Horde_Auth_Cyrsql::updateUser(): ' . $query, __FILE__, __LINE__, PEAR_LOG_DEBUG);
+
+        $res = $this->_db->query($query, $values);
+        if ($res instanceof PEAR_Error) {
+            throw new Horde_Exception($res);
+        }
+    }
+
+    /**
+     * Attempts to open connections to the SQL and IMAP servers.
+     *
+     * @throws Horde_Exception
+     */
+    protected function _connect()
+    {
+        if ($this->_connected) {
+            return;
+        }
+
+        parent::_connect();
+
+        if (!isset($this->_params['hidden_accounts'])) {
+            $this->_params['hidden_accounts'] = array('cyrus');
+        }
+
+        // Reset the $_connected flag; we haven't yet successfully
+        // opened everything.
+        $this->_connected = false;
+
+        $imap_config = array(
+            'hostspec' => empty($this->_params['hostspec']) ? null : $this->_params['hostspec'],
+            'password' => $this->_params['cyrpass'],
+            'port' => empty($this->_params['port']) ? null : $this->_params['port'],
+            'secure' => ($this->_params['secure'] == 'none') ? null : $this->_params['secure'],
+            'username' => $this->_params['cyradmin']
+        );
+
+        try {
+            $this->_ob = Horde_Imap_Client::getInstance('Socket', $imap_config);
+            $this->_ob->login();
+        } catch (Horde_Imap_Client_Exception $e) {
+            throw new Horde_Exception($e);
+        }
+
+        $this->_connected = true;
+    }
+
+}
diff --git a/framework/Auth/lib/Horde/Auth/Cyrus.php b/framework/Auth/lib/Horde/Auth/Cyrus.php
new file mode 100644 (file)
index 0000000..ad6e767
--- /dev/null
@@ -0,0 +1,302 @@
+<?php
+/**
+ * The Horde_Auth_Cyrus class provides Horde with the ability of
+ * administrating a Cyrus mail server authentications against another backend
+ * that Horde can update (eg SQL or LDAP).
+ *
+ * Required parameters:
+ * <pre>
+ * 'backend'    The complete hash for the Auth_* driver that cyrus
+ *              authenticates against (eg SQL, LDAP).
+ * 'cyradmin'   The username of the cyrus administrator
+ * 'cyrpass'    The password for the cyrus administrator
+ * 'hostspec'        The hostname or IP address of the server.
+ *                   DEFAULT: 'localhost'
+ * 'port'            The server port to which we will connect.
+ *                   IMAP is generally 143, while IMAP-SSL is generally 993.
+ *                   DEFAULT: Encryption port default
+ * 'secure'          The encryption to use.  Either 'none', 'ssl', or 'tls'.
+ *                   DEFAULT: 'none'
+ * </pre>
+ *
+ * Optional values:
+ * <pre>
+ * 'folders'    An array of folders to create under username.
+ *              Doesn't create subfolders by default.
+ * 'quota'      The quota (in kilobytes) to grant on the mailbox.
+ *              Does not establish quota by default.
+ * 'separator'  Hierarchy separator to use (e.g., is it user/mailbox or
+ *              user.mailbox)
+ * 'unixhier'   The value of imapd.conf's unixhierarchysep setting.
+ *              Set this to 'true' if the value is true in imapd.conf
+ * </pre>
+ *
+ * Example Usage:
+ * <pre>
+ * $conf['auth']['driver'] = 'composite';
+ * $conf['auth']['params']['loginscreen_switch'] = '_horde_select_loginscreen';
+ * $conf['auth']['params']['admin_driver'] = 'cyrus';
+ * $conf['auth']['params']['drivers']['imp'] = array(
+ *     'driver' => 'application',
+ *     'params' => array('app' => 'imp')
+ * );
+ * $conf['auth']['params']['drivers']['cyrus'] = array(
+ *    'driver' => 'cyrus',
+ *    'params' => array(
+ *        'cyradmin' => 'cyrus',
+ *        'cyrpass' => 'password',
+ *        'hostspec' => 'imap.example.com',
+ *        'secure' => 'none'
+ *        'separator' => '.'
+ *    )
+ * );
+ * $conf['auth']['params']['drivers']['cyrus']['params']['backend'] = array(
+ *     'driver' => 'sql',
+ *     'params' => array(
+ *         'phptype' => 'mysql',
+ *         'hostspec' => 'database.example.com',
+ *         'protocol' => 'tcp',
+ *         'username' => 'username',
+ *         'password' => 'password',
+ *         'database' => 'mail',
+ *         'table' => 'accountuser',
+ *         'encryption' => 'md5-hex',
+ *         'username_field' => 'username',
+ *         'password_field' => 'password'
+ *     )
+ * );
+ *
+ * if (!function_exists('_horde_select_loginscreen')) {
+ *     function _horde_select_loginscreen()
+ *     {
+ *         return 'imp';
+ *     }
+ * }
+ * </pre>
+ *
+ * Copyright 2002-2009 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you did
+ * not receive this file, see http://opensource.org/licenses/lgpl-2.1.php
+ *
+ * @author  Ilya Krel <mail@krel.org>
+ * @author  Mike Cochrane <mike@graftonhall.co.nz>
+ * @package Horde_Auth
+ */
+class Horde_Auth_Cyrus extends Horde_Auth_Driver
+{
+    /**
+     * Horde_Imap_Client object.
+     *
+     * @var Horde_Imap_Client_Base
+     */
+    protected $_ob;
+
+    /**
+     * Pointer to another backend that Cyrus authenticates against.
+     *
+     * @var Horde_Auth_Driver
+     */
+     protected $_backend;
+
+    /**
+     * 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,
+        'remove' => true,
+        'update' => true
+    );
+
+    /**
+     * Constructor.
+     *
+     * @param array $params  A hash containing connection parameters.
+     */
+    public function __construct($params = array())
+    {
+        parent::__construct($params);
+
+        if (!isset($this->_params['separator'])) {
+            $this->_params['separator'] = '.';
+        }
+
+        if (isset($this->_params['unixhier']) &&
+            $this->_params['unixhier'] == true) {
+            $this->_params['separator'] = '/';
+        }
+
+        // Create backend instance.
+        $this->_backend = Horde_Auth::singleton($this->_params['backend']['driver'], $this->_params['backend']['params']);
+
+        // Check the capabilities of the backend.
+        if (!$this->_backend->hasCapability('add') ||
+            !$this->_backend->hasCapability('update') ||
+            !$this->_backend->hasCapability('remove')) {
+            Horde::fatal('Horde_Auth_Cyrus: Backend does not have required capabilites.', __FILE__, __LINE__);
+        }
+
+        $this->_capabilities['list'] = $this->_backend->hasCapability('list');
+        $this->_capabilities['groups'] = $this->_backend->hasCapability('groups');
+        $this->_capabilities['transparent'] = $this->_backend->hasCapability('transparent');
+    }
+
+    /**
+     * Add a set of authentication credentials.
+     *
+     * @param string $userId      The userId to add.
+     * @param array $credentials  The credentials to add.
+     *
+     * @throws Horde_Exception
+     */
+    public function addUser($userId, $credentials)
+    {
+        $this->_connect();
+
+        $this->_backend->addUser($userId, $credentials);
+
+        $mailbox = Horde_String::convertCharset('user' . $this->_params['separator'] . $userId, NLS::getCharset(), 'utf7-imap');
+
+        try {
+            $this->_ob->createMailbox($mailbox);
+        } catch (Horde_Imap_Client_Exception $e) {
+            throw new Horde_Exception($e);
+        }
+
+        if (isset($this->_params['folders']) &&
+            is_array($this->_params['folders'])) {
+            foreach ($this->_params['folders'] as $folder) {
+                try {
+                    $this->_ob->createMailbox($mailbox . Horde_String::convertCharset($this->_params['separator'] . $folder, NLS::getCharset(), 'utf7-imap'));
+                } catch (Horde_Imap_Client_Exception $e) {}
+            }
+        }
+
+        if (isset($this->_params['quota']) &&
+            ($this->_params['quota'] >= 0)) {
+            try {
+                $this->_ob->setQuota($mailbox, array('storage' => $this->_params['quota']));
+            } catch (Horde_Imap_Client_Exception $e) {
+                throw new Horde_Exception($e);
+            }
+        }
+    }
+
+    /**
+     * Delete a set of authentication credentials.
+     *
+     * @param string $userId  The userId to delete.
+     *
+     * @throws Horde_Exception
+     */
+    public function removeUser($userId)
+    {
+        $this->_connect();
+
+        $this->_backend->removeUser($userId);
+
+        $mailbox = Horde_String::convertCharset('user' . $this->_params['separator'] . $userId, NLS::getCharset(), 'utf7-imap');
+
+        /* Set ACL for mailbox deletion. */
+        list($admin) = explode('@', $this->_params['cyradmin']);
+
+        try {
+            $this->_ob->setACL($mailbox, $admin, array('rights' => 'lrswipcda'));
+            $this->_ob->deleteMailbox($mailbox);
+        } catch (Horde_Imap_Client_Exception $e) {
+            throw new Horde_Exception($e);
+        }
+
+        Horde_Auth::removeUserData($userId);
+    }
+
+    /**
+     * Attempts to open connections to the IMAP servers.
+     *
+     * @throws Horde_Exception
+     */
+    protected function _connect()
+    {
+        if ($this->_ob) {
+            return;
+        }
+
+        $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->_ob = Horde_Imap_Client::getInstance('Socket', $imap_config);
+            $this->_ob->login();
+        } catch (Horde_Imap_Client_Exception $e) {
+            throw new Horde_Exception($e);
+        }
+    }
+
+    /**
+     * List all users in the system.
+     *
+     * @return array  The array of userIds.
+     * @throws Horde_Exception
+     */
+    public function listUsers()
+    {
+        return $this->_backend->listUsers();
+    }
+
+    /**
+     * 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_Exception
+     */
+    public function updateUser($oldID, $newID, $credentials)
+    {
+        $this->_backend->updateUser($oldID, $newID, $credentials);
+    }
+
+    /**
+     * Return the URI of the login screen for this authentication method.
+     *
+     * @param string $app  The application to use.
+     * @param string $url  The URL to redirect to after login.
+     *
+     * @return string  The login screen URI.
+     */
+    public function getLoginScreen($app = 'horde', $url = '')
+    {
+        return $this->_backend->getLoginScreen($app, $url);
+    }
+
+    /**
+     * Checks if a userId exists in the system.
+     *
+     * @return boolean  Whether or not the userId already exists.
+     */
+    public function exists($userId)
+    {
+        return $this->_backend->exists($userId);
+    }
+
+    /**
+     * Automatic authentication: Find out if the client matches an allowed IP
+     * block.
+     *
+     * @return boolean  Whether or not the client is allowed.
+     */
+    protected function _transparent()
+    {
+        return $this->_backend->transparent();
+    }
+
+}
diff --git a/framework/Auth/lib/Horde/Auth/Driver.php b/framework/Auth/lib/Horde/Auth/Driver.php
new file mode 100644 (file)
index 0000000..0fa3edc
--- /dev/null
@@ -0,0 +1,304 @@
+<?php
+/**
+ * The Horde_Auth_Driver:: class provides a common abstracted interface to
+ * creating various authentication backends.
+ *
+ * Copyright 1999-2009 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you did
+ * not receive this file, see http://opensource.org/licenses/lgpl-2.1.php
+ *
+ * @author  Chuck Hagenbuch <chuck@horde.org>
+ * @author  Michael Slusarz <slusarz@curecanti.org>
+ * @package Horde_Auth
+ */
+class Horde_Auth_Driver
+{
+    /**
+     * 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'           => false,
+        'groups'        => false,
+        'list'          => false,
+        'resetpassword' => false,
+        'remove'        => false,
+        'transparent'   => false,
+        'update'        => false
+    );
+
+    /**
+     * Hash containing parameters needed for the drivers.
+     *
+     * @var array
+     */
+    protected $_params = array();
+
+    /**
+     * The credentials currently being authenticated.
+     *
+     * @var array
+     */
+    protected $_authCredentials = array();
+
+    /**
+     * Constructor.
+     *
+     * @param array $params  A hash containing parameters.
+     */
+    public function __construct($params = array())
+    {
+        $this->_params = $params;
+    }
+
+    /**
+     * Finds out if a set of login credentials are valid, and if requested,
+     * mark the user as logged in in the current session.
+     *
+     * @param string $userId      The userId to check.
+     * @param array $credentials  The credentials to check.
+     * @param boolean $login      Whether to log the user in. If false, we'll
+     *                            only test the credentials and won't modify
+     *                            the current session. Defaults to true.
+     * @param string $realm       The authentication realm to check.
+     *
+     * @return boolean  Whether or not the credentials are valid.
+     */
+    public function authenticate($userId, $credentials, $login = true,
+                                 $realm = null)
+    {
+        $auth = false;
+        $userId = trim($userId);
+
+        if (!empty($GLOBALS['conf']['hooks']['preauthenticate'])) {
+            if (!Horde::callHook('_horde_hook_preauthenticate', array($userId, $credentials, $realm), 'horde', false)) {
+                if (Horde_Auth::getAuthError() != Horde_Auth::REASON_MESSAGE) {
+                    Horde_Auth::setAuthError(Horde_Auth::REASON_FAILED);
+                }
+                return false;
+            }
+        }
+
+        /* Store the credentials being checked so that subclasses can modify
+         * them if necessary (like transparent auth does). */
+        $this->_authCredentials = array(
+            'changeRequested' => false,
+            'credentials' => $credentials,
+            'realm' => $realm,
+            'userId' => $userId
+        );
+
+        try {
+            $this->_authenticate($userId, $credentials);
+
+            if ($login) {
+                $auth = Horde_Auth::setAuth(
+                    $this->_authCredentials['userId'],
+                    $this->_authCredentials['credentials'],
+                    $this->_authCredentials['realm'],
+                    $this->_authCredentials['changeRequested']
+                );
+            } else {
+                if (!Horde_Auth::checkSessionIP()) {
+                    Horde_Auth::setAuthError(self::REASON_SESSIONIP);
+                } elseif (!Horde_Auth::checkBrowserString()) {
+                    Horde_Auth::setAuthError(self::REASON_BROWSER);
+                } else {
+                    $auth = true;
+                }
+            }
+        } catch (Horde_Exception $e) {
+            Horde_Auth::setAuthError($e->getCode() || Horde_Auth::REASON_MESSAGE, $e->getMessage());
+        }
+
+        return $auth;
+    }
+
+    /**
+     * Authentication stub.
+     *
+     * Horde_Exception should pass a message string (if any) in the message
+     * field, and the REASON_* constant in the code field (defaults to
+     * REASON_MESSAGE).
+     *
+     * @throws Horde_Exception
+     */
+    protected function _authenticate()
+    {
+    }
+
+    /**
+     * Adds a set of authentication credentials.
+     *
+     * @param string $userId      The userId to add.
+     * @param array $credentials  The credentials to use.
+     *
+     * @throws Horde_Exception
+     */
+    public function addUser($userId, $credentials)
+    {
+        throw new Horde_Exception('unsupported');
+    }
+
+    /**
+     * Updates 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_Exception
+     */
+    public function updateUser($oldID, $newID, $credentials)
+    {
+        throw new Horde_Exception('unsupported');
+    }
+
+    /**
+     * Deletes a set of authentication credentials.
+     *
+     * @param string $userId  The userId to delete.
+     *
+     * @throws Horde_Exception
+     */
+    public function removeUser($userId)
+    {
+        throw new Horde_Exception('unsupported');
+    }
+
+    /**
+     * Lists all users in the system.
+     *
+     * @return mixed  The array of userIds.
+     * @throws Horde_Exception
+     */
+    public function listUsers()
+    {
+        throw new Horde_Exception('unsupported');
+    }
+
+    /**
+     * Checks if $userId exists in the system.
+     *
+     * @param string $userId  User ID for which to check
+     *
+     * @return boolean  Whether or not $userId already exists.
+     */
+    public function exists($userId)
+    {
+        try {
+            $users = $this->listUsers();
+            return in_array($userId, $users);
+        } catch (Horde_Exception $e) {
+            return false;
+        }
+    }
+
+    /**
+     * Automatic authentication: Finds out if the client matches an allowed IP
+     * block.
+     *
+     * @return boolean  Whether or not the client is allowed.
+     */
+    public function transparent()
+    {
+        try {
+            return $this->_transparent();
+        } catch (Horde_Exception $e) {
+            Horde_Auth::setAuthError($e->getCode() || Horde_Auth::REASON_MESSAGE, $e->getMessage());
+            return false;
+        }
+    }
+
+    /**
+     * Transparent authentication stub.
+     *
+     * If the auth error message is desired to be set, Horde_Exception should
+     * thrown instead of returning false.
+     * The Horde_Exception object should have a message string (if any) in the
+     * message field, and the REASON_* constant in the code field (defaults to
+     * REASON_MESSAGE).
+     *
+     * @return boolean  Whether transparent login is supported.
+     * @throws Horde_Exception
+     */
+    protected function _transparent()
+    {
+        return false;
+    }
+
+    /**
+     * 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 on success.
+     * @throws Horde_Exception
+     */
+    public function resetPassword($userId)
+    {
+        throw new Horde_Exception('unsupported');
+    }
+
+    /**
+     * Queries the current driver 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)
+    {
+        return !empty($this->_capabilities[$capability]);
+    }
+
+    /**
+     * Returns the URI of the login screen for the current authentication
+     * method.
+     *
+     * @param string $app  The application to use.
+     * @param string $url  The URL to redirect to after login.
+     *
+     * @return string  The login screen URI.
+     */
+    public function getLoginScreen($app = 'horde', $url = '')
+    {
+        $login = Horde::url($GLOBALS['registry']->get('webroot', $app) . '/login.php', true);
+        if (!empty($url)) {
+            $login = Horde_Util::addParameter($login, 'url', $url);
+        }
+        return $login;
+    }
+
+    /**
+     * Returns the named parameter for the current auth driver.
+     *
+     * @param string $param  The parameter to fetch.
+     *
+     * @return string  The parameter's value, or null if it doesn't exist.
+     */
+    public function getParam($param)
+    {
+        return isset($this->_params[$param])
+            ? $this->_params[$param]
+            : null;
+    }
+
+    /**
+     * Driver-level admin check stub.
+     *
+     * @todo
+     *
+     * @return boolean  False.
+     */
+    public function isAdmin($permission = null, $permlevel = null, $user = null)
+    {
+        return false;
+    }
+
+}
diff --git a/framework/Auth/lib/Horde/Auth/Ftp.php b/framework/Auth/lib/Horde/Auth/Ftp.php
new file mode 100644 (file)
index 0000000..3003c0c
--- /dev/null
@@ -0,0 +1,68 @@
+<?php
+/**
+ * The Horde_Auth_Ftp class provides an FTP implementation of the Horde
+ * authentication system.
+ *
+ * Optional parameters:
+ * <pre>
+ * 'hostspec' - (string) The hostname or IP address of the FTP server.
+ *              DEFAULT: 'localhost'
+ * 'port' - (integer) The server port to connect to.
+ *          DEFAULT: 21
+ * </pre>
+ *
+ * Copyright 1999-2009 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you did
+ * not receive this file, see http://opensource.org/licenses/lgpl-2.1.php
+ *
+ * @author  Chuck Hagenbuch <chuck@horde.org>
+ * @author  Max Kalika <max@horde.org>
+ * @package Horde_Auth
+ */
+class Horde_Auth_Ftp extends Horde_Auth_Driver
+{
+    /**
+     * Constructor.
+     *
+     * @param array $params  A hash containing connection parameters.
+     *
+     * @throws Horde_Exception
+     */
+    public function __construct($params = array())
+    {
+        if (!Horde_Util::extensionExists('ftp')) {
+            throw new Horde_Exception('Horde_Auth_Ftp: Required FTP extension not found. Compile PHP with the --enable-ftp switch.');
+            return false;
+        }
+
+        $params = array_merge(array(
+            'hostspec' => 'localhost',
+            'port' => 21
+        ), $params);
+
+        parent::__construct($params);
+    }
+
+    /**
+     * 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 FTP,
+     *                            this must contain a password entry.
+     *
+     * @throws Horde_Exception
+     */
+    protected function _authenticate($userId, $credentials)
+    {
+        $ftp = @ftp_connect($this->_params['hostspec'], $this->_params['port']);
+
+        $res = $ftp && @ftp_login($ftp, $userId, $credentials['password']);
+        @ftp_quit($ftp);
+
+        if ($res) {
+            throw new Horde_Exception('', Horde_Auth::REASON_BADLOGIN);
+        }
+    }
+
+}
diff --git a/framework/Auth/lib/Horde/Auth/Http.php b/framework/Auth/lib/Horde/Auth/Http.php
new file mode 100644 (file)
index 0000000..99693c5
--- /dev/null
@@ -0,0 +1,140 @@
+<?php
+/**
+ * The Horde_Auth_Http class transparently logs users in to Horde using
+ * already present HTTP authentication headers.
+ *
+ * The 'encryption' parameter specifies what kind of passwords are in
+ * the .htpasswd file. The supported options are 'crypt-des' (standard
+ * crypted htpasswd entries) and 'aprmd5'. This information is used if
+ * you want to directly authenticate users with this driver, instead
+ * of relying on transparent auth.
+ *
+ * Copyright 1999-2009 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you did
+ * not receive this file, see http://opensource.org/licenses/lgpl-2.1.php
+ *
+ * @author  Chuck Hagenbuch <chuck@horde.org>
+ * @package Horde_Auth
+ */
+class Horde_Auth_Http extends Horde_Auth_Driver
+{
+    /**
+     * An array of capabilities, so that the driver can report which
+     * operations it supports and which it doesn't.
+     *
+     * @var array
+     */
+    protected $_capabilities = array(
+        'transparent' => true
+    );
+
+    /**
+     * Array of usernames and hashed passwords.
+     *
+     * @var array
+     */
+    protected $_users = array();
+
+    /**
+     * Constructor.
+     *
+     * @param array $params  A hash containing parameters.
+     */
+    public function __construct($params = array())
+    {
+        parent::__construct($params);
+
+        // Default to DES passwords.
+        if (empty($this->_params['encryption'])) {
+            $this->_params['encryption'] = 'crypt-des';
+        }
+
+        if (!empty($this->_params['htpasswd_file'])) {
+            $users = file($this->_params['htpasswd_file']);
+            if (is_array($users)) {
+                // Enable the list users capability.
+                $this->_capabilities['list'] = true;
+
+                // Put users into alphabetical order.
+                sort($users);
+
+                foreach ($users as $line) {
+                    list($user, $pass) = explode(':', $line, 2);
+                    $this->_users[trim($user)] = trim($pass);
+                }
+            }
+        }
+    }
+
+    /**
+     * Find out if a set of login credentials are valid. Only supports
+     * htpasswd files with DES passwords right now.
+     *
+     * @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_Exception
+     */
+    protected function _authenticate($userId, $credentials)
+    {
+        if (empty($credentials['password']) ||
+            empty($this->_users[$userId])) {
+            throw new Horde_Exception('', Horde_Auth::REASON_BADLOGIN);
+        }
+
+        $hash = Horde_Auth::getCryptedPassword($credentials['password'], $this->_users[$userId], $this->_params['encryption'], !empty($this->_params['show_encryption']));
+
+        if ($hash != $this->_users[$userId]) {
+            throw new Horde_Exception('', Horde_Auth::REASON_BADLOGIN);
+        }
+    }
+
+    /**
+     * Return the URI of the login screen for this authentication object.
+     *
+     * @param string $app  The application to use.
+     * @param string $url  The URL to redirect to after login.
+     *
+     * @return string  The login screen URI.
+     */
+    public function getLoginScreen($app = 'horde', $url = '')
+    {
+        if (!empty($this->_params['loginScreen'])) {
+            return $url
+                ? Horde_Util::addParameter($this->_params['loginScreen'], 'url', $url)
+                : $this->_params['loginScreen'];
+        }
+
+        return parent::getLoginScreen($app, $url);
+    }
+
+    /**
+     * List all users in the system.
+     *
+     * @return array  The array of userIds.
+     */
+    public function listUsers()
+    {
+        return array_keys($this->_users);
+    }
+
+    /**
+     * Automatic authentication: Find out if the client has HTTP
+     * authentication info present.
+     *
+     * @return boolean  Whether or not the client is allowed.
+     * @throws Horde_Exception
+     */
+    protected function _transparent()
+    {
+        if (!empty($_SERVER['PHP_AUTH_USER']) &&
+            !empty($_SERVER['PHP_AUTH_PW'])) {
+            return Horde_Auth::setAuth(Horde_Util::dispelMagicQuotes($_SERVER['PHP_AUTH_USER']), array('password' => Horde_Util::dispelMagicQuotes($_SERVER['PHP_AUTH_PW']), 'transparent' => 1));
+        }
+
+        throw new Horde_Exception(_("HTTP Authentication not found."));
+    }
+
+}
diff --git a/framework/Auth/lib/Horde/Auth/HttpRemote.php b/framework/Auth/lib/Horde/Auth/HttpRemote.php
new file mode 100644 (file)
index 0000000..7b8aa31
--- /dev/null
@@ -0,0 +1,46 @@
+<?php
+/**
+ * The Auth_Http_Remote class authenticates users against a remote
+ * HTTP-Auth endpoint.
+ *
+ * Copyright 2007-2009 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you did
+ * not receive this file, see http://opensource.org/licenses/lgpl-2.1.php
+ *
+ * @author  Duck <duck@obala.net>
+ * @package Horde_Auth
+ */
+class Horde_Auth_HttpRemote extends Horde_Auth_Driver
+{
+    /**
+     * 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_Exception
+     */
+    protected function _authenticate($userId, $credentials)
+    {
+        $options = array(
+            'allowRedirects' => true,
+            'method' => 'GET',
+            'timeout' => 5
+        );
+
+        if (!empty($GLOBALS['conf']['http']['proxy']['proxy_host'])) {
+            $options = array_merge($options, $GLOBALS['conf']['http']['proxy']);
+        }
+
+        $request = new HTTP_Request($this->_params['url'], $options);
+        $request->setBasicAuth($userId, $credentials['password']);
+
+        $request->sendRequest();
+
+        if ($request->getResponseCode() != 200) {
+            throw new Horde_Exception('', Horde_Auth::REASON_BADLOGIN);
+        }
+    }
+
+}
diff --git a/framework/Auth/lib/Horde/Auth/Imap.php b/framework/Auth/lib/Horde/Auth/Imap.php
new file mode 100644 (file)
index 0000000..e8e7d9f
--- /dev/null
@@ -0,0 +1,165 @@
+<?php
+/**
+ * The Horde_Auth_Imap:: class provides an IMAP implementation of the Horde
+ * authentication system.
+ *
+ * Optional parameters:
+ * <pre>
+ * 'admin_password'  The password of the adminstrator.
+ *                   DEFAULT: null
+ * 'admin_user'      The name of a user with admin privileges.
+ *                   DEFAULT: null
+ * 'hostspec'        The hostname or IP address of the server.
+ *                   DEFAULT: 'localhost'
+ * 'port'            The server port to which we will connect.
+ *                   IMAP is generally 143, while IMAP-SSL is generally 993.
+ *                   DEFAULT: Encryption port default
+ * 'secure'          The encryption to use.  Either 'none', 'ssl', or 'tls'.
+ *                   DEFAULT: 'none'
+ * 'userhierarchy'   The hierarchy where user mailboxes are stored.
+ *                   DEFAULT: 'user.'
+ * </pre>
+ *
+ * If setting up as Horde auth handler in conf.php, this is a sample entry:
+ * <pre>
+ * $conf['auth']['params']['hostspec'] = 'imap.example.com';
+ * $conf['auth']['params']['port'] = 143;
+ * $conf['auth']['params']['secure'] = 'none';
+ * </pre>
+ *
+ * Copyright 1999-2009 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you did
+ * not receive this file, see http://opensource.org/licenses/lgpl-2.1.php
+ *
+ * @author  Chuck Hagenbuch <chuck@horde.org>
+ * @author  Gaudenz Steinlin <gaudenz@soziologie.ch>
+ * @author  Jan Schneider <jan@horde.org>
+ * @package Horde_Auth
+ */
+class Horde_Auth_Imap extends Horde_Auth_Driver
+{
+    /**
+     * Constructor.
+     *
+     * @param array $params  A hash containing connection parameters.
+     */
+    public function __construct($params = array())
+    {
+        $params = array_merge(array(
+            'admin_password' => null,
+            'admin_user' => null,
+            'hostspec' => '',
+            'port' => '',
+            'secure' => 'none',
+            'userhierarchy' => 'user.'
+        ), $params);
+
+        parent::__construct(array_merge($default_params, $params));
+
+        if (!empty($this->_params['admin_user'])) {
+            $this->_capabilities['add'] = true;
+            $this->_capabilities['remove'] = true;
+            $this->_capabilities['list'] = 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_Exception
+     */
+    protected function _authenticate($userId, $credentials)
+    {
+        try {
+            $ob->_getOb($userId, $credentials['password']);
+            $ob->login();
+            $ob->logout();
+        } catch (Horde_Imap_Client_Exception $e) {
+            throw new Horde_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_Exception
+     */
+    public function addUser($userId, $credentials)
+    {
+        try {
+            $ob->_getOb($this->_params['admin_user'], $this->_params['admin_password']);
+            $mailbox = Horde_String::convertCharset($this->_params['userhierarchy'] . $userId, NLS::getCharset(), 'utf7-imap');
+            $ob->createMailbox($mailbox);
+            $ob->setACL($mailbox, $this->_params['admin_user'], 'lrswipcda');
+        } catch (Horde_Imap_Client_Exception $e) {
+            throw new Horde_Exception($e);
+        }
+    }
+
+    /**
+     * Delete a set of authentication credentials.
+     *
+     * @param string $userId  The userId to delete.
+     *
+     * @throws Horde_Exception
+     */
+    public function removeUser($userId)
+    {
+        try {
+            $ob->_getOb($this->_params['admin_user'], $this->_params['admin_password']);
+            $ob->setACL($mailbox, $this->_params['admin_user'], 'lrswipcda');
+            $ob->deleteMailbox(Horde_String::convertCharset($this->_params['userhierarchy'] . $userId, NLS::getCharset(), 'utf7-imap'));
+        } catch (Horde_Imap_Client_Exception $e) {
+            throw new Horde_Exception($e);
+        }
+
+        Horde_Auth::removeUserData($userId);
+    }
+
+    /**
+     * List all users in the system.
+     *
+     * @return array  The array of userIds.
+     * @throws Horde_Exception
+     */
+    public function listUsers()
+    {
+        try {
+            $ob->_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_Exception($e);
+        }
+
+        return empty($list)
+            ? array()
+            : preg_replace('/.*' . preg_quote($this->_params['userhierarchy'], '/') . '(.*)/', '\\1', $list);
+    }
+
+    /**
+     * Get Horde_Imap_Client object.
+     *
+     * @return Horde_Imap_Client_Base  IMAP client object.
+     */
+    protected function _getOb($user, $pass)
+    {
+        $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
+        );
+
+        return Horde_Imap_Client::getInstance('Socket', $imap_config);
+    }
+
+}
diff --git a/framework/Auth/lib/Horde/Auth/Imsp.php b/framework/Auth/lib/Horde/Auth/Imsp.php
new file mode 100644 (file)
index 0000000..6004ac4
--- /dev/null
@@ -0,0 +1,42 @@
+<?php
+/**
+ * The Horde_Auth_Imsp class provides basic authentication against an IMSP
+ * server.
+ * This will be most benificial if already using an IMSP based preference
+ * system or IMSP based addressbook system
+ *
+ * Copyright 2004-2009 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you did
+ * not receive this file, see http://opensource.org/licenses/lgpl-2.1.php
+ *
+ * @author  Michael Rubinsky <mrubinsk@horde.org>
+ * @package Horde_Auth
+ */
+class Horde_Auth_imsp extends Horde_Auth_Driver
+{
+    /**
+     * Private authentication function.
+     *
+     * @param string $userID      Username for IMSP server.
+     * @param array $credentials  Hash containing 'password' element.
+     *
+     * @return boolean  True on success / False on failure.
+     */
+    protected function _authenticate($userID, $credentials)
+    {
+        $this->_params['username'] = $userID;
+        $this->_params['password'] = $credentials['password'];
+
+        $imsp = Net_IMSP_Auth::singleton($this->_params['auth_method']);
+        if ($imsp instanceof PEAR_Error) {
+            throw new Horde_Exception($imsp->getMessage());
+        }
+
+        $result = $imsp->authenticate($this->_params, false);
+        if ($result instanceof PEAR_Error) {
+            throw new Horde_Exception('', Horde_Auth::REASON_BADLOGIN);
+        }
+    }
+
+}
diff --git a/framework/Auth/lib/Horde/Auth/Ipbasic.php b/framework/Auth/lib/Horde/Auth/Ipbasic.php
new file mode 100644 (file)
index 0000000..066adfb
--- /dev/null
@@ -0,0 +1,90 @@
+<?php
+/**
+ * The Horde_Auth_Ipbasic class provides access control based on CIDR masks
+ * (client IP addresses). It is not meant for user-based systems, but
+ * for times when you want a block of IPs to be able to access a site,
+ * and that access is simply on/off - no preferences, etc.
+ *
+ * Optional Parameters:
+ * <pre>
+ * 'blocks' - (array) CIDR masks which are allowed access.
+ * </pre>
+ *
+ * Copyright 1999-2009 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you did
+ * not receive this file, see http://opensource.org/licenses/lgpl-2.1.php
+ *
+ * @author  Chuck Hagenbuch <chuck@horde.org>
+ * @package Horde_Auth
+ */
+class Horde_Auth_Ipbasic extends Horde_Auth_Driver
+{
+    /**
+     * An array of capabilities, so that the driver can report which
+     * operations it supports and which it doesn't.
+     *
+     * @var array
+     */
+    protected $_capabilities = array(
+        'transparent' => true
+    );
+
+    /**
+     * Constructor.
+     *
+     * @param array $params  A hash containing parameters.
+     */
+    public function __construct($params = array())
+    {
+        if (empty($params['blocks'])) {
+            $params['blocks'] = array();
+        } elseif (!is_array($params['blocks'])) {
+            $params['blocks'] = array($params['blocks']);
+        }
+
+        parent::__construct($params);
+    }
+
+    /**
+     * Automatic authentication: Find out if the client matches an allowed IP
+     * block.
+     *
+     * @return boolean  Whether or not the client is allowed.
+     * @throws Horde_Exception
+     */
+    protected function _transparent()
+    {
+        if (!isset($_SERVER['REMOTE_ADDR'])) {
+            throw new Horde_Exception(_("IP Address not avaialble."));
+        }
+
+        $client = $_SERVER['REMOTE_ADDR'];
+        foreach ($this->_params['blocks'] as $cidr) {
+            if ($this->_addressWithinCIDR($client, $cidr)) {
+                return Horde_Auth::setAuth($cidr, array('transparent' => 1));
+            }
+        }
+
+        throw new Horde_Exception(_("IP Address not within allowed CIDR block."));
+    }
+
+    /**
+     * Determine if an IP address is within a CIDR block.
+     *
+     * @param string $address  The IP address to check.
+     * @param string $cidr     The block (e.g. 192.168.0.0/16) to test against.
+     *
+     * @return boolean  Whether or not the address matches the mask.
+     */
+    protected function _addressWithinCIDR($address, $cidr)
+    {
+        $address = ip2long($address);
+        list($quad, $bits) = explode('/', $cidr);
+        $bits = intval($bits);
+        $quad = ip2long($quad);
+
+        return (($address >> (32 - $bits)) == ($quad >> (32 - $bits)));
+    }
+
+}
diff --git a/framework/Auth/lib/Horde/Auth/Kolab.php b/framework/Auth/lib/Horde/Auth/Kolab.php
new file mode 100644 (file)
index 0000000..d511467
--- /dev/null
@@ -0,0 +1,208 @@
+<?php
+/**
+ * The Horde_Auth_Kolab implementation of the Horde authentication system.
+ * Derives from the Horde_Auth_Imap authentication object, and provides
+ * parameters to it based on the global Kolab configuration.
+ *
+ * Copyright 2004-2007 Stuart Binge <s.binge@codefusion.co.za>
+ * Copyright 2008-2009 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you did
+ * not receive this file, see http://opensource.org/licenses/lgpl-2.1.php
+ *
+ * @author  Stuart Binge <s.binge@codefusion.co.za>
+ * @author  Gunnar Wrobel <wrobel@pardus.de>
+ * @package Horde_Auth
+ */
+class Horde_Auth_Kolab extends Horde_Auth_Driver
+{
+    /**
+     * 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
+    );
+
+    /**
+     * Find out if a set of login credentials are valid.
+     *
+     * For Kolab this requires to identify the IMAP server the user should
+     * be authenticated against before the credentials can be checked using
+     * this server. The Kolab_Server module handles identification of the
+     * correct IMAP server.
+     *
+     * @param string $userId      The userId to check.
+     * @param array $credentials  An array of login credentials. For Kolab,
+     *                            this must contain a "password" entry.
+     *
+     * @throws Horde_Exception
+     */
+    protected function _authenticate($userId, $credentials)
+    {
+        global $conf;
+
+        $params = array();
+
+        if (class_exists('Horde_Kolab_Session')) {
+            try {
+                $session = Horde_Kolab_Session::singleton($userId, $credentials, true);
+            } catch (Horde_Kolab_Server_MissingObjectException $e) {
+                throw new Horde_Exception('', Horde_Auth::REASON_BADLOGIN);
+            } catch (Exception $e) {
+                Horde::logMessage($e, __FILE__, __LINE__, PEAR_LOG_ERR);
+                throw new Horde_Exception('', Horde_Auth::REASON_FAILED);
+            }
+        } else {
+            throw new Horde_Exception('The class Horde_Kolab_Session is required for the Kolab auth driver but it is missing!', Horde_Auth::REASON_MESSAGE);
+        }
+
+        if (!isset($conf['auth']['params']) ||
+            $conf['auth']['params']['login_block'] != 1) {
+            // Return if feature is disabled.
+            return $session->auth;
+        }
+
+        if ($session->auth !== true &&
+            class_exists('Horde_History')) {
+            $history = Horde_History::singleton();
+
+            $history_identifier = "$userId@logins.kolab";
+            $history_log = $history->getHistory($history_identifier);
+            $history_list = array();
+
+            // Extract history list from log.
+            if ($history_log && !($history_log instanceof PEAR_Error)) {
+                $data = $history_log->getData();
+                if (!empty($data)) {
+                    $entry = array_shift($data);
+                    $history_list = $entry['history_list'];
+                }
+            }
+
+            // Calculate the time range.
+            $start_time = (time() - $conf['auth']['params']['login_block_time'] * 60);
+
+            $new_history_list = array();
+            $count = 0;
+
+            // Copy and count all relevant timestamps.
+            foreach ($history_list as $entry) {
+                $timestamp = $entry[ 'timestamp' ];
+
+                if ($timestamp > $start_time) {
+                    $new_history_list[] = $entry;
+                    $count++;
+                }
+            }
+
+            $max_count = $conf['auth']['params']['login_block_count'];
+
+            if ($count > $max_count) {
+                // Add entry for current failed login.
+                $entry = array();
+                $entry[ 'timestamp' ] = time();
+                $new_history_list[] = $entry;
+
+                // Write back history.
+                $history->log($history_identifier,
+                              array('action' => 'add', 'who' => $userId,
+                                    'history_list' => $new_history_list), true);
+
+                if ($count > $max_count) {
+                    throw new Horde_Exception(_("Too many invalid logins during the last minutes."));
+                }
+
+                throw new Horde_Exception('', Horde_Auth::REASON_BADLOGIN);
+            }
+        }
+
+        return ($session->auth === true);
+    }
+
+    /**
+     * Sets a variable in the session saying that authorization has succeeded,
+     * note which userId was authorized, and note when the login took place.
+     *
+     * The kolab driver rewrites UIDs into the correct mail addresses that
+     * need to be used to log into the IMAP server.
+     *
+     * @param string $userId            The userId who has been authorized.
+     * @param array $credentials        The credentials of the user.
+     * @param string $realm             The authentication realm to use.
+     * @param boolean $changeRequested  Whether to request that the user change
+     *                                  their password.
+     */
+    function setAuth($userId, $credentials, $realm = null, $changeRequested = false)
+    {
+        // TODO - setAuth doesn't exist in Horde_Auth_Driver
+        //        This should probably use _username_hook_frombackend.
+
+        if (class_exists('Horde_Kolab_Session')) {
+            $session = Horde_Kolab_Session::singleton($userId);
+            $userId = $session->user_mail;
+        }
+
+        return parent::setAuth($userId, $credentials, $realm, $changeRequested);
+    }
+
+    /**
+     * List Users
+     *
+     * @return array  List of Users
+     * @throws Horde_Exception
+     */
+    public function listUsers()
+    {
+        if (!class_exists('Horde_Kolab_Session')) {
+            throw new Horde_Exception('The Horde_Kolab_Session class is not available.');
+        }
+
+        $session = Horde_Kolab_Session::singleton();
+        $server = $session->getServer();
+        if ($server instanceof PEAR_Error) {
+            return $server;
+        }
+        $users = $server->listObjects(KOLAB_OBJECT_USER);
+        $mails = array();
+        foreach ($users as $user) {
+            $mails[] = $user->get(KOLAB_ATTR_MAIL);
+        }
+        return $mails;
+    }
+
+    /**
+     * Add a set of authentication credentials.
+     *
+     * @param string $userId      The userId to add.
+     * @param array $credentials  The credentials to be set.
+     *
+     * @throws Horde_Exception
+     */
+    public function addUser($userId, $credentials)
+    {
+        if (!class_exists('Horde_Kolab_Session')) {
+            throw new Horde_Exception('The Horde_Kolab_Session class is not available.');
+        }
+
+        $session = Horde_Kolab_Session::singleton();
+        $server = $session->getServer();
+        if ($server instanceof PEAR_Error) {
+            return $server;
+        }
+
+        $result = $server->store(KOLAB_OBJECT_USER, $userId, $credentials);
+
+        if (is_a($result, KOLAB_OBJECT_USER)) {
+            return true;
+        } else if ($result instanceof PEAR_Error) {
+            return $result;
+        } else {
+            throw new Horde_Exception(sprintf('The new Kolab object is a %s rather than a ' . KOLAB_OBJECT_USER, get_class($result)));
+        }
+    }
+
+}
diff --git a/framework/Auth/lib/Horde/Auth/Krb5.php b/framework/Auth/lib/Horde/Auth/Krb5.php
new file mode 100644 (file)
index 0000000..8a24844
--- /dev/null
@@ -0,0 +1,72 @@
+<?php
+/**
+ * The Horde_Auth_Krb5 class provides an kerberos implementation of the Horde
+ * authentication system.
+ *
+ * This driver requires the 'krb5' PHP extension to be loaded.
+ * The module can be downloaded here:
+ *   http://www.horde.org/download/php/phpkrb5.tar.gz
+ *
+ * Kerberos must be correctly configured on your system (e.g. /etc/krb5.conf)
+ * for this class to work correctly.
+ *
+ * Copyright 2002-2009 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you did
+ * not receive this file, see http://opensource.org/licenses/lgpl-2.1.php
+ *
+ * @author  Michael Slusarz <slusarz@horde.org>
+ * @package Horde_Auth
+ */
+class Horde_Auth_Krb5 extends Horde_Auth_Driver
+{
+    /**
+     * Constructor.
+     *
+     * @param array $params  Not used.
+     *
+     * @throws Horde_Exception
+     */
+    public function __construct($params = array())
+    {
+        if (!Horde_Util::extensionExists('krb5')) {
+            throw new Horde_Exception(_("Horde_Auth_Krb5: Required krb5 extension not found."));
+        }
+
+        parent::__construct($params);
+    }
+
+    /**
+     * 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 kerberos, this must contain a password
+     *                            entry.
+     *
+     * @throws Horde_Exception
+     */
+    protected function _authenticate($userId, $credentials)
+    {
+        if (empty($credentials['password'])) {
+            throw new Horde_Exception('', Horde_Auth::REASON_BADLOGIN);
+        }
+
+        $result = krb5_login($userId, $credentials['password']);
+
+        switch ($result) {
+        case KRB5_OK:
+            return;
+
+        case KRB5_BAD_PASSWORD:
+            throw new Horde_Exception(_("Bad kerberos password."));
+
+        case KRB5_BAD_USER:
+            throw new Horde_Exception(_("Bad kerberos username."));
+
+        default:
+            throw new Horde_Exception(_("Kerberos server rejected authentication."));
+        }
+    }
+
+}
diff --git a/framework/Auth/lib/Horde/Auth/Ldap.php b/framework/Auth/lib/Horde/Auth/Ldap.php
new file mode 100644 (file)
index 0000000..e2dd723
--- /dev/null
@@ -0,0 +1,566 @@
+<?php
+/**
+ * The Horde_Auth_Ldap class provides an LDAP implementation of the Horde
+ * authentication system.
+ *
+ * Required parameters:
+ * <pre>
+ * 'basedn'       The base DN for the LDAP server.
+ * 'hostspec'     The hostname of the LDAP server.
+ * 'uid'          The username search key.
+ * 'filter'       The LDAP formatted search filter to search for users. This
+ *                setting overrides the 'objectclass' method below.
+ * 'objectclass'  The objectclass filter used to search for users. Can be a
+ *                single objectclass or an array.
+ * </pre>
+ *
+ * Optional parameters:
+ * <pre>
+ * 'binddn'       The DN used to bind to the LDAP server
+ * 'password'     The password used to bind to the LDAP server
+ * 'version'      The version of the LDAP protocol to use.
+ *                DEFAULT: NONE (system default will be used)
+ * </pre>
+ *
+ * Copyright 1999-2009 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you did
+ * not receive this file, see http://opensource.org/licenses/lgpl-2.1.php
+ *
+ * @author  Jon Parise <jon@horde.org>
+ * @package Horde_Auth
+ */
+class Horde_Auth_Ldap extends Horde_Auth_Driver
+{
+    /**
+     * 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,
+        'update' => true,
+        'remove' => true,
+        'list' => true
+    );
+
+    /**
+     * LDAP connection handle.
+     *
+     * @var resource
+     */
+    protected $_ds;
+
+    /**
+     * Construct.
+     *
+     * @param array $params  A hash containing connection parameters.
+     *
+     * @throws Horde_Exception
+     */
+    public function __construct($params = array())
+    {
+        if (!Horde_Util::extensionExists('ldap')) {
+            throw new Horde_Exception(_("Horde_Auth_Ldap: Required LDAP extension not found."));
+        }
+
+        /* Ensure we've been provided with all of the necessary parameters. */
+        Horde::assertDriverConfig($params, 'auth',
+                                  array('hostspec', 'basedn', 'uid'),
+                                  'authentication LDAP');
+
+        parent::__construct($params);
+    }
+
+    /**
+     * Does an ldap connect and binds as the guest user or as the optional dn.
+     *
+     * @throws Horde_Exception
+     */
+    protected function _connect()
+    {
+        /* Connect to the LDAP server. */
+        $this->_ds = @ldap_connect($this->_params['hostspec']);
+        if (!$this->_ds) {
+            throw new Horde_Exception(_("Failed to connect to LDAP server."));
+        }
+
+        if (isset($this->_params['version'])) {
+            if (!ldap_set_option($this->_ds, LDAP_OPT_PROTOCOL_VERSION,
+                                 $this->_params['version'])) {
+                Horde::logMessage(
+                    sprintf('Set LDAP protocol version to %d failed: [%d] %s',
+                            $this->_params['version'],
+                            @ldap_errno($this->_ds),
+                            @ldap_error($this->_ds)),
+                    __FILE__, __LINE__, PEAR_LOG_ERR);
+            }
+        }
+
+        /* Start TLS if we're using it. */
+        if (!empty($this->_params['tls'])) {
+            if (!@ldap_start_tls($this->_ds)) {
+                Horde::logMessage(
+                    sprintf('STARTTLS failed: [%d] %s',
+                            @ldap_errno($this->_ds),
+                            @ldap_error($this->_ds)),
+                    __FILE__, __LINE__, PEAR_LOG_ERR);
+            }
+        }
+
+        /* Work around Active Directory quirk. */
+        if (!empty($this->_params['ad'])) {
+            if (!ldap_set_option($this->_ds, LDAP_OPT_REFERRALS, false)) {
+                Horde::logMessage(
+                    sprintf('Unable to disable directory referrals on this connection to Active Directory: [%d] %s',
+                            @ldap_errno($this->_ds),
+                            @ldap_error($this->_ds)),
+                    __FILE__, __LINE__, PEAR_LOG_ERR);
+            }
+        }
+
+        $bind = isset($this->_params['binddn'])
+            ? @ldap_bind($this->_ds, $this->_params['binddn'], $this->_params['password'])
+            : @ldap_bind($this->_ds);
+
+        if (!$bind) {
+            throw new Horde_Exception(_("Could not bind to LDAP server."));
+        }
+    }
+
+    /**
+     * Find the user dn
+     *
+     * @param string $userId  The userId to find.
+     *
+     * @return string  The users full DN
+     * @throws Horde_Exception
+     */
+    protected function _findDN($userId)
+    {
+        /* Search for the user's full DN. */
+        $filter = $this->_getParamFilter();
+        $filter = '(&(' . $this->_params['uid'] . '=' . $userId . ')' .
+                  $filter . ')';
+
+        $func = ($this->_params['scope'] == 'one')
+            ? 'ldap_list'
+            : 'ldap_search';
+
+        $search = @$func($this->_ds, $this->_params['basedn'], $filter,
+                         array($this->_params['uid']));
+        if (!$search) {
+            Horde::logMessage(ldap_error($this->_ds), __FILE__, __LINE__, PEAR_LOG_ERR);
+            throw new Horde_Exception(_("Could not search the LDAP server."));
+        }
+
+        $result = @ldap_get_entries($this->_ds, $search);
+        if (is_array($result) && (count($result) > 1)) {
+            $dn = $result[0]['dn'];
+        } else {
+            throw new Horde_Exception(_("Empty result."));
+        }
+
+        return $dn;
+    }
+
+    /**
+     * Checks for shadowLastChange and shadowMin/Max support and returns their
+     * values.  We will also check for pwdLastSet if Active Directory is
+     * support is requested.  For this check to succeed we need to be bound
+     * to the directory.
+     *
+     * @param string $dn  The dn of the user.
+     *
+     * @return array  Array with keys being "shadowlastchange", "shadowmin"
+     *                "shadowmax", "shadowwarning" and containing their
+     *                respective values or false for no support.
+     */
+    protected function _lookupShadow($dn)
+    {
+        /* Init the return array. */
+        $lookupshadow = array(
+            'shadowlastchange' => false,
+            'shadowmin' => false,
+            'shadowmax' => false,
+            'shadowwarning' => false
+        );
+
+        /* According to LDAP standard, to read operational attributes, you
+         * must request them explicitly. Attributes involved in password
+         * expiration policy:
+         *    pwdlastset: Active Directory
+         *    shadow*: shadowUser schema
+         *    passwordexpirationtime: Sun and Fedora Directory Server */
+        $result = @ldap_read($this->_ds, $dn, '(objectClass=*)',
+                             array('pwdlastset', 'shadowmax', 'shadowmin',
+                                   'shadowlastchange', 'shadowwarning',
+                                   'passwordexpirationtime'));
+        if ($result) {
+            $information = @ldap_get_entries($this->_ds, $result);
+
+            if ($this->_params['ad']) {
+                if (isset($information[0]['pwdlastset'][0])) {
+                    /* Active Directory handles timestamps a bit differently.
+                     * Convert the timestamp to a UNIX timestamp. */
+                    $lookupshadow['shadowlastchange'] = floor((($information[0]['pwdlastset'][0] / 10000000) - 11644406783) / 86400) - 1;
+
+                    /* Password expiry attributes are in a policy. We cannot
+                     * read them so use the Horde config. */
+                    $lookupshadow['shadowwarning'] = $this->_params['warnage'];
+                    $lookupshadow['shadowmin'] = $this->_params['minage'];
+                    $lookupshadow['shadowmax'] = $this->_params['maxage'];
+                }
+            } elseif (isset($information[0]['passwordexpirationtime'][0])) {
+                /* Sun/Fedora Directory Server uses a special attribute
+                 * passwordexpirationtime.  It has precedence over shadow*
+                 * because it actually locks the expired password at the LDAP
+                 * server level.  The correct way to check expiration should
+                 * be using LDAP controls, unfortunately PHP doesn't support
+                 * controls on bind() responses. */
+                $ldaptimepattern = "/([0-9]{4})([0-9]{2})([0-9]{2})([0-9]{2})([0-9]{2})([0-9]{2})Z/";
+                if (preg_match($ldaptimepattern, $information[0]['passwordexpirationtime'][0], $regs)) {
+                    /* Sun/Fedora Directory Server return expiration time, not
+                     * last change time. We emulate the behaviour taking it
+                     * back to maxage. */
+                    $lookupshadow['shadowlastchange'] = floor(mktime($regs[4], $regs[5], $regs[6], $regs[2], $regs[3], $regs[1]) / 86400) - $this->_params['maxage'];
+
+                    /* Password expiry attributes are in not accessible policy
+                     * entry. */
+                    $lookupshadow['shadowwarning'] = $this->_params['warnage'];
+                    $lookupshadow['shadowmin']     = $this->_params['minage'];
+                    $lookupshadow['shadowmax']     = $this->_params['maxage'];
+                } else {
+                    Horde::logMessage('Wrong time format: ' . $information[0]['passwordexpirationtime'][0], __FILE__, __LINE__, PEAR_LOG_ERR);
+                }
+            } else {
+                if (isset($information[0]['shadowmax'][0])) {
+                    $lookupshadow['shadowmax'] =
+                        $information[0]['shadowmax'][0];
+                }
+                if (isset($information[0]['shadowmin'][0])) {
+                    $lookupshadow['shadowmin'] =
+                        $information[0]['shadowmin'][0];
+                }
+                if (isset($information[0]['shadowlastchange'][0])) {
+                    $lookupshadow['shadowlastchange'] =
+                        $information[0]['shadowlastchange'][0];
+                }
+                if (isset($information[0]['shadowwarning'][0])) {
+                    $lookupshadow['shadowwarning'] =
+                        $information[0]['shadowwarning'][0];
+                }
+            }
+        }
+
+        return $lookupshadow;
+    }
+
+    /**
+     * Find out if the given set of login credentials are valid.
+     *
+     * @param string $userId       The userId to check.
+     * @param array  $credentials  An array of login credentials.
+     *
+     * @throws Horde_Exception
+     */
+    protected function _authenticate($userId, $credentials)
+    {
+        /* Connect to the LDAP server. */
+        $this->_connect();
+
+        /* Search for the user's full DN. */
+        $dn = $this->_findDN($userId);
+
+        /* Attempt to bind to the LDAP server as the user. */
+        $bind = @ldap_bind($this->_ds, $dn, $credentials['password']);
+        if ($bind == false) {
+            @ldap_close($this->_ds);
+            throw new Horde_Exception('', Horde_Auth::REASON_FAILED);
+        }
+
+        if ($this->_params['password_expiration'] == 'yes') {
+            $shadow = $this->_lookupShadow($dn);
+            if ($shadow['shadowmax'] && $shadow['shadowlastchange'] &&
+                $shadow['shadowwarning']) {
+                $today = floor(time() / 86400);
+                $warnday = $shadow['shadowlastchange'] +
+                           $shadow['shadowmax'] - $shadow['shadowwarning'];
+                $toexpire = $shadow['shadowlastchange'] +
+                            $shadow['shadowmax'] - $today;
+
+                if ($today >= $warnday) {
+                    $GLOBALS['notification']->push(sprintf(ngettext("%d day until your password expires.", "%d days until your password expires.", $toexpire), $toexpire), 'horde.warning');
+                }
+
+                if ($toexpire == 0) {
+                    $this->_authCredentials['changeRequested'] = true;
+                } elseif ($toexpire < 0) {
+                    throw new Horde_Exception('', Horde_Auth::REASON_EXPIRED);
+                }
+            }
+        }
+
+        @ldap_close($this->_ds);
+    }
+
+    /**
+     * Add a set of authentication credentials.
+     *
+     * @param string $userId      The userId to add.
+     * @param array $credentials  The credentials to be set.
+     *
+     * @throws Horde_Exception
+     */
+    public function addUser($userId, $credentials)
+    {
+        if ($this->_params['ad']) {
+            throw new Horde_Exception(_("Horde_Auth_Ldap: Adding users is not supported for Active Directory"));
+        }
+
+        /* Connect to the LDAP server. */
+        $this->_connect();
+
+        global $conf;
+        if (!empty($conf['hooks']['authldap'])) {
+            $entry = Horde::callHook('_horde_hook_authldap', array($userId, $credentials));
+            if ($entry instanceof PEAR_Error) {
+                throw new Horde_Exception($entry);
+            }
+            $dn = $entry['dn'];
+            /* Remove the dn entry from the array. */
+            unset($entry['dn']);
+        } else {
+            /* Try this simple default and hope it works. */
+            $dn = $this->_params['uid'] . '=' . $userId . ','
+                . $this->_params['basedn'];
+            $entry['cn'] = $userId;
+            $entry['sn'] = $userId;
+            $entry[$this->_params['uid']] = $userId;
+            $entry['objectclass'] = array_merge(
+                array('top'),
+                $this->_params['newuser_objectclass']);
+            $entry['userPassword'] = Horde_Auth::getCryptedPassword(
+                $credentials['password'], '',
+                $this->_params['encryption'],
+                'true');
+
+            if ($this->_params['password_expiration'] == 'yes') {
+                $entry['shadowMin'] = $this->_params['minage'];
+                $entry['shadowMax'] = $this->_params['maxage'];
+                $entry['shadowWarning'] = $this->_params['warnage'];
+                $entry['shadowLastChange'] = floor(time() / 86400);
+            }
+        }
+        $result = @ldap_add($this->_ds, $dn, $entry);
+
+        if (!$result) {
+           throw new Horde_Exception(sprintf(_("Horde_Auth_Ldap: Unable to add user \"%s\". This is what the server said: "), $userId) . @ldap_error($this->_ds));
+        }
+
+        @ldap_close($this->_ds);
+    }
+
+    /**
+     * Remove a set of authentication credentials.
+     *
+     * @param string $userId  The userId to add.
+     *
+     * @throws Horde_Exception
+     */
+    public function removeUser($userId)
+    {
+        if ($this->_params['ad']) {
+           throw new Horde_Exception(_("Horde_Auth_Ldap: Removing users is not supported for Active Directory"));
+        }
+
+        /* Connect to the LDAP server. */
+        $this->_connect();
+
+        if (!empty($GLOBALS['conf']['hooks']['authldap'])) {
+            $entry = Horde::callHook('_horde_hook_authldap', array($userId));
+            if ($entry instanceof PEAR_Error) {
+                throw new Horde_Exception($entry);
+            }
+            $dn = $entry['dn'];
+        } else {
+            /* Search for the user's full DN. */
+            $dn = $this->_findDN($userId);
+        }
+
+        $result = @ldap_delete($this->_ds, $dn);
+        if (!$result) {
+           throw new Horde_Exception(sprintf(_("Auth_ldap: Unable to remove user \"%s\""), $userId));
+        }
+
+        @ldap_close($this->_ds);
+
+        Horde_Auth::removeUserData($userId);
+    }
+
+    /**
+     * 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_Exception
+     */
+    public function updateUser($oldID, $newID, $credentials)
+    {
+        if ($this->_params['ad']) {
+           throw new Horde_Exception(_("Horde_Auth_Ldap: Updating users is not supported for Active Directory."));
+        }
+
+        /* Connect to the LDAP server. */
+        $this->_connect();
+
+        if (!empty($GLOBLS['conf']['hooks']['authldap'])) {
+            $entry = Horde::callHook('_horde_hook_authldap', array($oldID, $credentials));
+            if ($entry instanceof PEAR_Error) {
+                throw new Horde_Exception($entry);
+            }
+            $olddn = $entry['dn'];
+            $entry = Horde::callHook('_horde_hook_authldap', array($newID, $credentials));
+            $newdn = $entry['dn'];
+            unset($entry['dn']);
+        } else {
+            /* Search for the user's full DN. */
+            $dn = $this->_findDN($oldID);
+
+            $olddn = $dn;
+            $newdn = preg_replace('/uid=.*?,/', 'uid=' . $newID . ',', $dn, 1);
+            $shadow = $this->_lookupShadow($dn);
+
+            /* If shadowmin hasn't yet expired only change when we are
+               administrator */
+            if ($shadow['shadowlastchange'] &&
+                $shadow['shadowmin'] &&
+                ($shadow['shadowlastchange'] + $shadow['shadowmin'] > (time() / 86400))) {
+                throw new Horde_Exception(_("Minimum password age has not yet expired"));
+            }
+
+            /* Set the lastchange field */
+            if ($shadow['shadowlastchange']) {
+                $entry['shadowlastchange'] =  floor(time() / 86400);
+            }
+
+            /* Encrypt the new password */
+            $entry['userpassword'] = Horde_Auth::getCryptedPassword(
+                $credentials['password'], '',
+                $this->_params['encryption'],
+                'true');
+        }
+
+        if ($oldID != $newID) {
+            if (LDAP_OPT_PROTOCOL_VERSION == 3) {
+                ldap_rename($this->_ds, $olddn, $newdn,
+                            $this->_params['basedn'], true);
+
+                $result = ldap_modify($this->_ds, $newdn, $entry);
+            } else {
+                /* Get the complete old record first */
+                $result = @ldap_read($this->_ds, $olddn, 'objectClass=*');
+
+                if ($result) {
+                    $information = @ldap_get_entries($this->_ds, $result);
+
+                    /* Remove the count elements from the array */
+                    $counter = 0;
+                    $newrecord = array();
+                    while (isset($information[0][$counter])) {
+                        if ($information[0][$information[0][$counter]]['count'] == 1) {
+                            $newrecord[$information[0][$counter]] = $information[0][$information[0][$counter]][0];
+                        } else {
+                            $newrecord[$information[0][$counter]] = $information[0][$information[0][$counter]];
+                            unset($newrecord[$information[0][$counter]]['count']);
+                        }
+                        $counter++;
+                    }
+
+                    /* Adjust the changed parameters */
+                    unset($newrecord['dn']);
+                    $newrecord[$this->_params['uid']] = $newID;
+                    $newrecord['userpassword'] = $entry['userpassword'];
+                    if (isset($entry['shadowlastchange'])) {
+                        $newrecord['shadowlastchange'] = $entry['shadowlastchange'];
+                    }
+
+                    $result = ldap_add($this->_ds, $newdn, $newrecord);
+                    if ($result) {
+                        $result = @ldap_delete($this->_ds, $olddn);
+                    }
+                }
+            }
+        } else {
+            $result = @ldap_modify($this->_ds, $olddn, $entry);
+        }
+
+        if (!$result) {
+            throw new Horde_Exception(sprintf(_("Horde_Auth_Ldap: Unable to update user \"%s\""), $newID));
+        }
+
+        @ldap_close($this->_ds);
+    }
+
+    /**
+     * List Users
+     *
+     * @return array  List of Users
+     * @throws Horde_Exception
+     */
+    public function listUsers()
+    {
+        /* Connect to the LDAP server. */
+        $this->_connect();
+
+        $filter = $this->_getParamFilter();
+
+        $func = ($this->_params['scope'] == 'one')
+            ? 'ldap_list'
+            : 'ldap_search';
+
+        /* Add a sizelimit, if specified. Default is 0, which means no limit.
+         * Note: You cannot override a server-side limit with this. */
+        $sizelimit = isset($this->_params['sizelimit']) ? $this->_params['sizelimit'] : 0;
+        $search = @$func($this->_ds, $this->_params['basedn'], $filter,
+                         array($this->_params['uid']), 0, $sizelimit);
+
+        $entries = @ldap_get_entries($this->_ds, $search);
+        $userlist = array();
+        $uid = Horde_String::lower($this->_params['uid']);
+        for ($i = 0; $i < $entries['count']; $i++) {
+            $userlist[$i] = $entries[$i][$uid][0];
+        }
+
+        return $userlist;
+    }
+
+    /**
+     * Return a formatted LDAP filter as configured within the parameters.
+     *
+     * @return string  LDAP search filter
+     */
+    protected function _getParamFilter()
+    {
+        if (!empty($this->_params['filter'])) {
+            $filter = $this->_params['filter'];
+        } elseif (!is_array($this->_params['objectclass'])) {
+            $filter = 'objectclass=' . $this->_params['objectclass'];
+        } else {
+            $filter = '';
+            if (count($this->_params['objectclass']) > 1) {
+                $filter = '(&' . $filter;
+                foreach ($this->_params['objectclass'] as $objectclass) {
+                    $filter .= '(objectclass=' . $objectclass . ')';
+                }
+                $filter .= ')';
+            } elseif (count($this->_params['objectclass']) == 1) {
+                $filter = '(objectClass=' . $this->_params['objectclass'][0] . ')';
+            }
+        }
+        return $filter;
+    }
+}
diff --git a/framework/Auth/lib/Horde/Auth/Login.php b/framework/Auth/lib/Horde/Auth/Login.php
new file mode 100644 (file)
index 0000000..9f229be
--- /dev/null
@@ -0,0 +1,76 @@
+<?php
+/**
+ * The Horde_Auth_login:: class provides a system login implementation of
+ * the Horde authentication system.
+ * This Auth driver is useful if you have a shadow password system
+ * where the Horde_Auth_Passwd driver doesn't work.
+ *
+ * Optional parameters:
+ * <pre>
+ * 'location' - (string) Location of the su binary.
+ *              DEFAULT: /bin/su
+ * </pre>
+ *
+ * Copyright 2004-2009 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you did
+ * not receive this file, see http://opensource.org/licenses/lgpl-2.1.php
+ *
+ * @author  Jan Schneider <jan@horde.org>
+ * @package Horde_Auth
+ */
+class Horde_Auth_Login extends Horde_Auth_Driver
+{
+    /**
+     * 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'
+    );
+
+    /**
+     * Constructs a new Login authentication object.
+     *
+     * @param array $params  A hash containing connection parameters.
+     */
+    public function __construct($params = array())
+    {
+        if (empty($params['location'])) {
+            $params['location'] = '/bin/su';
+        }
+
+        parent::__construct($params);
+    }
+
+    /**
+     * 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.
+     *
+     * @return boolean  Whether or not the credentials are valid.
+     */
+    protected function _authenticate($userId, $credentials)
+    {
+        if (empty($credentials['password'])) {
+            throw new Horde_Exception('', Horde_Auth::REASON_BADLOGIN);
+        }
+
+        $proc = @popen($this->_location . ' -c /bin/true ' . $userId, 'w');
+        if (!is_resource($proc)) {
+            throw new Horde_Exception('', Horde_Auth::REASON_FAILED);
+        }
+
+        fwrite($proc, $credentials['password']);
+        if (@pclose($proc) !== 0) {
+            throw new Horde_Exception('', Horde_Auth::REASON_FAILED);
+        }
+    }
+
+}
diff --git a/framework/Auth/lib/Horde/Auth/Msad.php b/framework/Auth/lib/Horde/Auth/Msad.php
new file mode 100644 (file)
index 0000000..5dc168d
--- /dev/null
@@ -0,0 +1,304 @@
+<?php
+/**
+ * The Horde_Auth_Msad class provides an experimental MSAD extension of the
+ * LDAP implementation of the Horde authentication system.
+ *
+ * Required parameters: See Horde_Auth_Ldap.
+ *
+ * Optional parameters: See Horde_Auth_Ldap.
+ *
+ * Copyright 2007-2009 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you did
+ * not receive this file, see http://opensource.org/licenses/lgpl-2.1.php
+ *
+ * @author  Francois Helly <fhelly@bebop-design.net>
+ * @package Horde_Auth
+ */
+class Horde_Auth_Msad extends Horde_Auth_Ldap
+{
+    /**
+     * Constructor.
+     *
+     * @param array $params  A hash containing connection parameters.
+     *
+     * @throws Horde_Exception
+     */
+    public function __construct($params = array())
+    {
+        if (!Horde_Util::extensionExists('ldap')) {
+            throw new Horde_Exception(_("Horde_Auth_Ldap: Required LDAP extension not found."));
+        }
+
+        $params = array_merge(array(
+            'adduser' => true,
+            'authId' => 'initials',
+            'encryption' => 'msad',
+            'newuser_objectclass' => 'user',
+            'password_expiration' => 'no',
+            'port' => 389,
+            'ssl' => false,
+            'uid' => array('samaccountname')
+        ), $params);
+
+        if (!is_array($params['uid'])) {
+            $params['uid'] = array($params['uid']);
+        }
+
+        /* Ensure we've been provided with all of the necessary parameters. */
+        Horde::assertDriverConfig($params, 'auth',
+            array('hostspec', 'basedn'), 'authentication MSAD');
+
+        $this->_params = $params;
+        /* Adjust capabilities: depending on if SSL encryption is
+         * enabled or not */
+        $this->_capabilities = array(
+            'add'           => ($params['ssl'] || $params['adduser']),
+            'list'          => true,
+            'remove'        => true,
+            'resetpassword' => $params['ssl'],
+            'update'        => $params['ssl']
+        );
+
+        parent::__construct($params);
+    }
+
+    /**
+     * Add a set of authentication credentials.
+     *
+     * @param string $accountName  The user sAMAccountName to find.
+     * @param array $credentials   The credentials to be set.
+     *
+     * @throws Horde_Exception
+     */
+    public function addUser($accountName, $credentials)
+    {
+        /* Connect to the MSAD server. */
+        $this->_connect();
+
+        $entry = Horde::callHook('_horde_hook_authmsad', array($accountName, $credentials), 'horde', null);
+        if (!is_null($entry)) {
+            $dn = $entry['dn'];
+            unset($entry['dn']);
+        } else {
+            $basedn = (isset($credentials['basedn'])) ?
+                $credentials['basedn'] : $this->_params['basedn'];
+
+            /* Set a default CN */
+            $dn = 'cn=' . $accountName . ',' . $basedn;
+
+            $entry['cn'] = $accountName;
+            $entry['samaccountname'] = $accountName;
+
+            $entry['objectclass'][0] = "top";
+            $entry['objectclass'][1] = "person";
+            $entry['objectclass'][2] = "organizationalPerson";
+            $entry['objectclass'][3] = "user";
+
+            $entry['description'] = (isset($credentials['description'])) ?
+                $credentials['description'] : 'New horde user';
+
+            if ($this->_params['ssl']) {
+                $entry["AccountDisabled"] = false;
+            }
+            $entry['userPassword'] = Horde_Auth::getCryptedPassword($credentials['password'],'',
+                                                               $this->_params['encryption'],
+                                                               false);
+
+            if (isset($this->_params['binddn'])) {
+                $entry['manager'] = $this->_params['binddn'];
+            }
+
+        }
+
+        $success = @ldap_add($this->_ds, $dn, $entry);
+
+        if (!$success) {
+           throw new Horde_Exception(sprintf(_("Auth_msad: Unable to add user \"%s\". This is what the server said: "), $accountName) . ldap_error($this->_ds));
+        }
+
+        @ldap_close($this->_ds);
+    }
+
+    /**
+     * Remove a set of authentication credentials.
+     *
+     * @param string $accountName  The user sAMAccountName to remove.
+     *
+     * @throws Horde_Exception
+     */
+    public function removeUser($accountName)
+    {
+        /* Connect to the MSAD server. */
+        $this->_connect();
+
+        $entry = Horde::callHook('_horde_hook_authmsad', array($accountName), 'horde', null);
+        if (!is_null($entry)) {
+            $dn = $entry['dn'];
+        } else {
+            /* Search for the user's full DN. */
+            $dn = $this->_findDN($accountName);
+        }
+
+        if (!@ldap_delete($this->_ds, $dn)) {
+            throw new Horde_Exception(sprintf(_("Horde_Auth_Msad: Unable to remove user \"%s\""), $accountName));
+        }
+        @ldap_close($this->_ds);
+
+        /* Remove user data */
+        Horde_Auth::removeUserData($accountName);
+    }
+
+    /**
+     * 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_Exception
+     */
+    public function updateUser($oldId, $newId, $credentials)
+    {
+        /* Connect to the MSAD server. */
+        $this->_connect();
+
+        $entry = Horde::callHook('_horde_hook_authmsad', array($oldId, $credentials), 'horde', null);
+        if (!is_null($entry)) {
+            $olddn = $entry['dn'];
+            unset($entry['dn']);
+        } else {
+            /* Search for the user's full DN. */
+            $dn = $this->_findDN($oldId);
+
+            /* Encrypt the new password */
+            if (isset($credentials['password'])) {
+                $entry['userpassword'] = Horde_Auth::getCryptedPassword($credentials['password'],'',
+                                                                   $this->_params['encryption'],
+                                                                   true);
+            }
+        }
+
+        if ($oldID != $newID) {
+            $newdn = str_replace($oldId, $newID, $dn);
+            ldap_rename($this->_ds, $olddn, $newdn, $this->_params['basedn'], true);
+            $success = @ldap_modify($this->_ds, $newdn, $entry);
+        } else {
+            $success = @ldap_modify($this->_ds, $olddn, $entry);
+        }
+
+        if (!$success) {
+            throw new Horde_Exception(sprintf(_("Horde_Auth_Msad: Unable to update user \"%s\""), $newID), __FILE__, __LINE__);
+        }
+
+        @ldap_close($this->_ds);
+    }
+
+    /**
+     * Reset a user's password. Used for example when the user does not
+     * remember the existing password.
+     *
+     * @param string $user_id  The user id for which to reset the password.
+     *
+     * @return string  The new password on success.
+     * @throws Horde_Exception
+     */
+    public function resetPassword($user_id)
+    {
+        /* Get a new random password. */
+        $password = Horde_Auth::genRandomPassword() . '/';
+        $this->updateUser($user_id, $user_id, array('userPassword' => $password));
+        return $password;
+    }
+
+    /**
+     * Does an ldap connect and binds as the guest user.
+     *
+     * @throws Horde_Exception
+     */
+    protected function _connect()
+    {
+        /* Connect to the MSAD server. */
+        $ssl = ($this->_params['ssl']) ? 'ldaps://' : '';
+        $this->_ds = ldap_connect($ssl . $this->_params['hostspec'], $this->_params['port']);
+        if (!$this->_ds) {
+            return PEAR::raiseError(_("Failed to connect to MSAD server."));
+        }
+
+        if (!ldap_set_option($this->_ds, LDAP_OPT_PROTOCOL_VERSION, 3)) {
+            Horde::logMessage(
+            sprintf('Set MSAD protocol version to %d failed: [%d] %s',
+            3,
+            ldap_errno($conn),
+            ldap_error($conn),
+            __FILE__, __LINE__));
+        }
+        if (!ldap_set_option($this->_ds, LDAP_OPT_REFERRALS, 0)) {
+            Horde::logMessage(
+            sprintf('Set MSAD referrals option to %d failed: [%d] %s',
+            0,
+            ldap_errno($conn),
+            ldap_error($conn),
+            __FILE__, __LINE__));
+        }
+
+        if (isset($this->_params['binddn'])) {
+            $bind = ldap_bind($this->_ds,
+                              $this->_params['binddn'],
+                              $this->_params['password']);
+        } else {
+            $bind = ldap_bind($this->_ds);
+        }
+
+        if (!$bind) {
+            return PEAR::raiseError(_("Could not bind to MSAD server."));
+        }
+
+        return true;
+    }
+
+    /**
+     * Find the user dn
+     *
+     * @access private
+     *
+     * @param string $userId  The user UID to find.
+     *
+     * @return string  The user's full DN
+     */
+    function _findDN($userId)
+    {
+        /* Search for the user's full DN. */
+        foreach ($this->_params['uid'] as $uid) {
+            $entries = array($uid);
+            if ($uid != $this->_params['authId']) {
+                array_push($entries, $this->_params['authId']);
+            }
+            $search = @ldap_search($this->_ds, $this->_params['basedn'],
+                               $uid . '=' . $userId,
+                               $entries
+                               );
+            /* Searching the tree is not successful */
+            if (!$search) {
+                return PEAR::raiseError(_("Could not search the MSAD server."));
+            }
+
+            /* Fetch the search result */
+            $result = @ldap_get_entries($this->_ds, $search);
+            /* The result isn't empty: the DN was found */
+            if (is_array($result) && (count($result) > 1)) {
+                break;
+            }
+        }
+
+        if (is_array($result) && (count($result) > 1)) {
+            $dn = $result[0]['dn'];
+        } else {
+            return PEAR::raiseError(_("Empty result."));
+        }
+        /* Be sure the horde userId is the configured one */
+        $this->_authCredentials['userId'] = $result[0][$this->_params['authId']][0];
+        return $dn;
+    }
+
+}
diff --git a/framework/Auth/lib/Horde/Auth/Pam.php b/framework/Auth/lib/Horde/Auth/Pam.php
new file mode 100644 (file)
index 0000000..282a075
--- /dev/null
@@ -0,0 +1,71 @@
+<?php
+/**
+ * The Horde_Auth_Pam:: class provides a PAM-based implementation of the Horde
+ * authentication system.
+ *
+ * PAM (Pluggable Authentication Modules) is a flexible mechanism for
+ * authenticating users. It has become the standard authentication system for
+ * Linux, Solaris and FreeBSD.
+ *
+ * This driver relies on the PECL PAM package:
+ *
+ *      http://pecl.php.net/package/PAM
+ *
+ * Optional parameters:
+ * <pre>
+ * 'service' - (string) The name of the PAM service to use when
+ *             authenticating.
+ *             DEFAULT: php
+ * </pre>
+ *
+ * Copyright 2004-2009 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you did
+ * not receive this file, see http://opensource.org/licenses/lgpl-2.1.php
+ *
+ * @author  Jon Parise <jon@horde.org>
+ * @package Horde_Auth
+ */
+class Horde_Auth_Pam extends Horde_Auth_Driver
+{
+    /**
+     * Constructor.
+     *
+     * @param array $params  A hash containing connection parameters.
+     *
+     * @throws Horde_Exception
+     */
+    public function __construct($params = array())
+    {
+        if (!Horde_Util::extensionExists('pam')) {
+            throw new Horde_Exception(_("PAM authentication is not available."));
+        }
+
+        if (!empty($params['service'])) {
+            ini_set('pam.servicename', $params['service']);
+        }
+
+        parent::__construct($params);
+    }
+
+    /**
+     * 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_Exception
+     */
+    protected function _authenticate($userId, $credentials)
+    {
+        if (empty($credentials['password'])) {
+            throw new Horde_Exception('', Horde_Auth::REASON_BADLOGIN);
+        }
+
+        $error = null;
+        if (!pam_auth($userId, $credentials['password'], $error)) {
+            throw new Horde_Exception($error);
+        }
+    }
+
+}
diff --git a/framework/Auth/lib/Horde/Auth/Passwd.php b/framework/Auth/lib/Horde/Auth/Passwd.php
new file mode 100644 (file)
index 0000000..e1a6040
--- /dev/null
@@ -0,0 +1,421 @@
+<?php
+/**
+ * The Horde_Auth_Passwd:: class provides a passwd-file implementation of
+ * the Horde authentication system.
+ *
+ * Required parameters:
+ * <pre>
+ * 'filename' - (string) The passwd file to use.
+ * </pre>
+ *
+ * Optional parameters:
+ * <pre>
+ * 'encryption'       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'
+ * 'lock'             Should we lock the passwd file? (boolean) The password
+ *                    file cannot be changed (add, edit, or delete users)
+ *                    unless this is true.
+ *                    DEFAULT: false<
+ * 'show_encryption'  Whether or not to prepend the encryption in the
+ *                    password field.
+ *                    DEFAULT: 'false'
+ * </pre>
+ *
+ * Copyright 1997-2007 Rasmus Lerdorf <rasmus@php.net>
+ * Copyright 2002-2009 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you did
+ * not receive this file, see http://opensource.org/licenses/lgpl-2.1.php
+ *
+ * @author  Rasmus Lerdorf <rasmus@php.net>
+ * @author  Chuck Hagenbuch <chuck@horde.org>
+ * @package Horde_Auth
+ */
+class Horde_Auth_Passwd extends Horde_Auth_Driver
+{
+    /**
+     * 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
+    );
+
+    /**
+     * Hash list of users.
+     *
+     * @var array
+     */
+    protected $_users = null;
+
+    /**
+     * Array of groups and members.
+     *
+     * @var array
+     */
+    protected $_groups = array();
+
+    /**
+     * Filehandle for lockfile.
+     *
+     * @var integer
+     */
+    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  A hash containing connection parameters.
+     */
+    public function __construct($params = array())
+    {
+        $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) {
+                if ($this->_users[$user]) {
+                    fputs($this->_fplock, "$user:$pass:" . $this->_users[$user] . "\n");
+                } else {
+                    fputs($this->_fplock, "$user:$pass\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_Exception
+     */
+    protected function _read()
+    {
+        if (is_array($this->_users)) {
+            return;
+        }
+
+        if (empty($this->_params['filename'])) {
+            throw new Horde_Exception('No password file set.');
+        }
+
+        if ($this->_params['lock']) {
+            $this->_fplock = fopen(Horde::getTempDir() . '/passwd.lock', 'w');
+            flock($this->_fplock, LOCK_EX);
+            $this->_locked = true;
+        }
+
+        $fp = fopen($this->_params['filename'], 'r');
+        if (!$fp) {
+            throw new Horde_Exception("Couldn't open '" . $this->_params['filename'] . "'.");
+        }
+
+        $this->_users = array();
+        while (!feof($fp)) {
+            $line = trim(fgets($fp, 128));
+            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_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. For MCAL,
+     *                            this must contain a password entry.
+     *
+     * @throws Horde_Exception
+     */
+    protected function _authenticate($userId, $credentials)
+    {
+        if (empty($credentials['password'])) {
+            throw new Horde_Exception('', Horde_Auth::REASON_BADLOGIN);
+        }
+
+        try {
+            $this->_read();
+        } catch (Horde_Exception $e) {
+            Horde::logMessage($e, __FILE__, __LINE__, PEAR_LOG_ERR);
+            throw new Horde_Exception('', Horde_Auth::REASON_FAILED);
+        }
+
+        if (!isset($this->_users[$userId]) ||
+            !$this->_comparePasswords($this->_users[$userId]['password'], $credentials['password'])) {
+            throw new Horde_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_Exception('', Horde_Auth::REASON_BADLOGIN);
+            }
+        }
+    }
+
+    /**
+     * List all users in the system.
+     *
+     * @return array  The array of userIds.
+     * @throws Horde_Exception
+     */
+    public function listUsers()
+    {
+        $this->_read();
+
+        $users = array_keys($this->_users);
+        if (empty($this->_params['required_groups'])) {
+            return $users;
+        }
+
+        $groupUsers = array();
+        foreach ($this->_params['required_groups'] as $group) {
+            $groupUsers = array_merge($groupUsers, array_intersect($users, array_keys($this->_groups[$group])));
+        }
+
+        return $groupUsers;
+    }
+
+    /**
+     * Add a set of authentication credentials.
+     *
+     * @param string $userId      The userId to add.
+     * @param array $credentials  The credentials to add.
+     *
+     * @throws Horde_Exception
+     */
+    public function addUser($userId, $credentials)
+    {
+        $this->_read();
+
+        if (!$this->_locked) {
+            throw new Horde_Exception('Password file not locked');
+        }
+
+        if (isset($this->_users[$userId])) {
+            throw new Horde_Exception("Couldn't add user '$user', 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_Exception
+     */
+    public function updateUser($oldID, $newID, $credentials)
+    {
+        $this->_read();
+
+        if (!$this->_locked) {
+            throw new Horde_Exception('Password file not locked');
+        }
+
+        if (!isset($this->_users[$userId])) {
+            throw new Horde_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_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_Exception
+     */
+    public function removeUser($userId)
+    {
+        $this->_read();
+
+        if (!$this->_locked) {
+            throw new Horde_Exception('Password file not locked');
+        }
+
+        if (!isset($this->_users[$userId])) {
+            throw new Horde_Exception("Couldn't delete user '$oldID', because the user doesn't exist.");
+        }
+
+        unset($this->_users[$userId]);
+
+        Horde_Auth::removeUserData($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']);
+    }
+
+}
diff --git a/framework/Auth/lib/Horde/Auth/Peclsasl.php b/framework/Auth/lib/Horde/Auth/Peclsasl.php
new file mode 100644 (file)
index 0000000..600756e
--- /dev/null
@@ -0,0 +1,79 @@
+<?php
+/**
+ * The Horde_Auth_Peclsasl:: class provides a SASL-based implementation of the
+ * Horde authentication system.
+ *
+ * SASL is the Simple Authentication and Security Layer (as defined by RFC
+ * 2222). It provides a system for adding plugable authenticating support to
+ * connection-based protocols.
+ *
+ * This driver relies on the PECL sasl package:
+ *
+ *      http://pecl.php.net/package/sasl
+ *
+ * Optional parameters:
+ * <pre>
+ * 'app'      The name of the authenticating application.
+ *            DEFAULT: horde
+ * 'service'  The name of the SASL service to use when authenticating.
+ *            DEFAULT: php
+ * </pre>
+ *
+ * Copyright 2004-2009 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you did
+ * not receive this file, see http://opensource.org/licenses/lgpl-2.1.php
+ *
+ * @author  Jon Parise <jon@horde.org>
+ * @package Horde_Auth
+ */
+class Horde_Auth_Peclsasl extends Horde_Auth_Driver
+{
+    /**
+     * Constructor.
+     *
+     * @param array $params  A hash containing connection parameters.
+     *
+     * @throws Horde_Exception
+     */
+    public function __construct($params = array())
+    {
+        if (!Horde_Util::extensionExists('sasl')) {
+            throw new Horde_Exception('Horde_Auth_Peclsasl:: requires the sasl PECL extension to be loaded.');
+        }
+
+        $params = array_merge(array(
+            'app' => 'horde',
+            'service' => 'php'
+        ), $params);
+
+        parent::__construct($params);
+
+        sasl_server_init($this->_params['app']);
+    }
+
+    /**
+     * 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_Exception
+     */
+    protected function _authenticate($userId, $credentials)
+    {
+        if (empty($credentials['password'])) {
+            throw new Horde_Exception('', Horde_Auth::REASON_BADLOGIN);
+        }
+
+        $conn = sasl_server_new($this->_params['service']);
+        if (!is_resource($conn)) {
+            throw new Horde_Exception(_("Failed to create new SASL connection."));
+        }
+
+        if (!sasl_checkpass($conn, $userId, $credentials['password'])) {
+            throw new Horde_Exception(sasl_errdetail($conn));
+        }
+    }
+
+}
diff --git a/framework/Auth/lib/Horde/Auth/Radius.php b/framework/Auth/lib/Horde/Auth/Radius.php
new file mode 100644 (file)
index 0000000..804c391
--- /dev/null
@@ -0,0 +1,163 @@
+<?php
+/**
+ * The Horde_Auth_Radius class provides a RADIUS implementation of the Horde
+ * authentication system.
+ *
+ * This class requires the 'radius' PECL extension:
+ *   http://pecl.php.net/package/radius
+ *
+ * On *nix-y machines, this extension can be installed as follows:
+ * <pre>
+ * pecl install radius
+ * </pre>
+ *
+ * Then, edit your php.ini file and make sure the following line is present:
+ * <pre>
+ *   For Windows machines:  extension=php_radius.dll
+ *   For all others:        extension=radius.so
+ * </pre>
+ *
+ * Required parameters:
+ * <pre>
+ * 'host' - (string) The RADIUS host to use (IP address or fully qualified
+ *          hostname).
+ * 'method' - (string) The RADIUS method to use for validating the request.
+ *            Either: 'PAP', 'CHAP_MD5', 'MSCHAPv1', or 'MSCHAPv2'.
+ *            ** CURRENTLY, only 'PAP' is supported. **
+ * 'secret' - (string) The RADIUS shared secret string for the host. The
+ *            RADIUS protocol ignores all but the leading 128 bytes
+ *            of the shared secret.
+ * </pre>
+ *
+ * Optional parameters:
+ * <pre>
+ * 'nas' - (string) The RADIUS NAS identifier to use.
+ *         DEFAULT: The value of $_SERVER['HTTP_HOST'] or, if not
+ *                  defined, then 'localhost'.
+ * 'port' - (integer) The port to use on the RADIUS server.
+ *          DEFAULT: Whatever the local system identifies as the
+ *                   'radius' UDP port
+ * 'retries' - (integer) The maximum number of repeated requests to make
+ *             before giving up.
+ *             DEFAULT: 3
+ * 'suffix' - (string) The domain name to add to unqualified user names.
+ *             DEFAULT: NONE
+ * 'timeout' - (integer) The timeout for receiving replies from the server (in
+ *             seconds).
+ *             DEFAULT: 3
+ * </pre>
+ *
+ * Copyright 2002-2009 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you did
+ * not receive this file, see http://opensource.org/licenses/lgpl-2.1.php
+ *
+ * @author  Michael Slusarz <slusarz@horde.org>
+ * @package Horde_Auth
+ */
+class Horde_Auth_Radius extends Horde_Auth_Driver
+{
+    /**
+     * Constructor.
+     *
+     * @param array $params  A hash containing connection parameters.
+     *
+     * @throws Horde_Exception
+     */
+    public function __construct($params = array())
+    {
+        parent::__construct($params);
+
+        if (!Horde_Util::extensionExists('radius')) {
+            throw new Horde_Exception('Horde_Auth_Radius:: requires the radius PECL extension to be loaded.');
+        }
+
+        /* A RADIUS host is required. */
+        if (empty($this->_params['host'])) {
+            throw new Horde_Exception('Horde_Auth_Radius:: requires a RADIUS host to connect to.');
+        }
+
+        /* A RADIUS secret string is required. */
+        if (empty($this->_params['secret'])) {
+            throw new Horde_Exception('Horde_Auth_Radius:: requires a RADIUS secret string.');
+        }
+
+        /* A RADIUS authentication method is required. */
+        if (empty($this->_params['method'])) {
+            throw new Horde_Exception('Horde_Auth_Radius:: requires a RADIUS authentication method.');
+        }
+
+        /* RADIUS NAS Identifier. */
+        if (empty($this->_params['nas'])) {
+            $this->_params['nas'] = isset($_SERVER['HTTP_HOST'])
+                ? $_SERVER['HTTP_HOST']
+                : 'localhost';
+        }
+
+        /* Suffix to add to unqualified user names. */
+        if (empty($this->_params['suffix'])) {
+            $this->_params['suffix'] = '';
+        }
+
+        /* The RADIUS port to use. */
+        if (empty($this->_params['port'])) {
+            $this->_params['port'] = 0;
+        }
+
+        /* Maximum number of retries. */
+        if (empty($this->_params['retries'])) {
+            $this->_params['retries'] = 3;
+        }
+
+        /* RADIUS timeout. */
+        if (empty($this->_params['timeout'])) {
+            $this->_params['timeout'] = 3;
+        }
+    }
+
+    /**
+     * Find out if a set of login credentials are valid.
+     *
+     * @param string $username    The userId to check.
+     * @param array $credentials  An array of login credentials.
+     *                            For radius, this must contain a password
+     *                            entry.
+     *
+     * @throws Horde_Exception
+     */
+    protected function _authenticate($username, $credentials)
+    {
+        /* Password is required. */
+        if (!isset($credentials['password'])) {
+            throw new Horde_Exception(_("Password required for RADIUS authentication."));
+        }
+
+        $res = radius_auth_open();
+        radius_add_server($res, $this->_params['host'], $this->_params['port'], $this->_params['secret'], $this->_params['timeout'], $this->_params['retries']);
+        radius_create_request($res, RADIUS_ACCESS_REQUEST);
+        radius_put_attr($res, RADIUS_NAS_IDENTIFIER, $this->_params['nas']);
+        radius_put_attr($res, RADIUS_NAS_PORT_TYPE, RADIUS_VIRTUAL);
+        radius_put_attr($res, RADIUS_SERVICE_TYPE, RADIUS_FRAMED);
+        radius_put_attr($res, RADIUS_FRAMED_PROTOCOL, RADIUS_PPP);
+        radius_put_attr($res, RADIUS_CALLING_STATION_ID, isset($_SERVER['REMOTE_HOST']) ? $_SERVER['REMOTE_HOST'] : '127.0.0.1');
+
+        /* Insert username/password into request. */
+        radius_put_attr($res, RADIUS_USER_NAME, $username);
+        radius_put_attr($res, RADIUS_USER_PASSWORD, $credentials['password']);
+
+        /* Send request. */
+        $success = radius_send_request($res);
+
+        switch ($success) {
+        case RADIUS_ACCESS_ACCEPT:
+            break;
+
+        case RADIUS_ACCESS_REJECT:
+            throw new Horde_Exception(_("Authentication rejected by RADIUS server."));
+
+        default:
+            throw new Horde_Exception(radius_strerror($res));
+        }
+    }
+
+}
diff --git a/framework/Auth/lib/Horde/Auth/Shibboleth.php b/framework/Auth/lib/Horde/Auth/Shibboleth.php
new file mode 100644 (file)
index 0000000..810147e
--- /dev/null
@@ -0,0 +1,96 @@
+<?php
+/**
+ * The Horde_Auth_Shibboleth class only provides transparent authentication
+ * based on the headers set by a Shibboleth SP.  Note that this class does
+ * not provide any actual SP functionality, it just takes the username
+ * from the HTTP headers that should be set by the Shibboleth SP.
+ *
+ * Required Parameters:
+ * <pre>
+ * 'username_header' - (string) Name of the header holding the username of the
+ *                     logged in user.
+ * </pre>
+ *
+ * Optional Parameters:
+ * <pre>
+ * 'password_header' - (string) Name of the header holding the password of the
+ *                     logged in user.
+ * 'password_holder' - (string) Where the hordeauth password is stored.
+ * 'password_preference' - (string) Name of the Horde preference holding the
+ *                         password of the logged in user.
+ * </pre>
+ *
+ * Copyright 9Star Research, Inc. 2006 http://www.protectnetwork.org/
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you did
+ * not receive this file, see http://opensource.org/licenses/lgpl-2.1.php
+ *
+ * @author  Cassio Nishiguchi <cassio@protectnetwork.org>
+ * @package Horde_Auth
+ */
+class Horde_Auth_Shibboleth extends Horde_Auth_Driver
+{
+    /**
+     * An array of capabilities, so that the driver can report which
+     * operations it supports and which it doesn't.
+     *
+     * @var array
+     */
+    protected $_capabilities = array(
+        'transparent' => true
+    );
+
+    /**
+     * Constructor.
+     *
+     * @param array $params  A hash containing parameters.
+     */
+    public function __construct($params = array())
+    {
+        Horde::assertDriverConfig($params, 'auth', array('username_header'), 'Authentication Shib');
+
+        $params = array_merge(array(
+            'password_header' => '',
+            'password_holder' => '',
+            'password_preference' => ''
+        ), $params);
+
+        parent::__construct($params);
+    }
+
+    /**
+     * Automatic authentication: Check if the username is set in the
+     * configured header.
+     *
+     * @return boolean  Whether or not the client is allowed.
+     * @throws Horde_Exception
+     */
+    protected function _transparent()
+    {
+        if (empty($_SERVER[$this->_params['username_header']])) {
+            throw new Horde_Exception(_("Shibboleth authentication not available."));
+        }
+
+        $username = $_SERVER[$this->_params['username_header']];
+
+        // Remove scope from username, if present.
+        $pos = strrpos($username, '@');
+        if ($pos !== false) {
+            $username = substr($username, 0, $pos);
+        }
+
+        if (!Horde_Auth::setAuth($username, array('transparent' => 1))) {
+            return false;
+        }
+
+        // Set password for hordeauth login.
+        if ($this->_params['password_holder'] == 'header') {
+            Horde_Auth::setCredential('password', $_SERVER[$this->_params['password_header']]);
+        } elseif ($this->_params['password_holder'] == 'preferences') {
+            Horde_Auth::setCredential('password', $GLOBALS['prefs']->getValue($this->_params['password_preference']));
+        }
+
+        return true;
+    }
+
+}
diff --git a/framework/Auth/lib/Horde/Auth/Signup.php b/framework/Auth/lib/Horde/Auth/Signup.php
new file mode 100644 (file)
index 0000000..2abdf62
--- /dev/null
@@ -0,0 +1,305 @@
+<?php
+/**
+ * Horde_Auth_Signup:: This class provides an interface to sign up or have
+ * new users sign themselves up into the horde installation, depending
+ * on how the admin has configured Horde.
+ *
+ * Copyright 2002-2009 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you did
+ * not receive this file, see http://opensource.org/licenses/lgpl-2.1.php
+ *
+ * @author  Marko Djukic <marko@oblo.com>
+ * @package Horde_Auth
+ */
+class Horde_Auth_Signup
+{
+    /**
+     * Attempts to return a concrete Auth_Signup instance based on $driver.
+     *
+     * @param string $driver  The type of the concrete Auth_Signup subclass
+     *                        to return.  The class name is based on the
+     *                        storage driver ($driver).  The code is
+     *                        dynamically included.
+     * @param array $params   A hash containing any additional configuration or
+     *                        connection parameters a subclass might need.
+     *
+     * @return Auth_Signup  The newly created concrete Auth_Signup instance,
+     *                      or false on an error.
+     */
+    static public function factory($driver = null, $params = null)
+    {
+        if ($driver === null) {
+            if (!empty($GLOBALS['conf']['signup']['driver'])) {
+                $driver = $GLOBALS['conf']['signup']['driver'];
+            } else {
+                $driver = 'datatree';
+            }
+        } else {
+            $driver = basename($driver);
+        }
+
+        if ($params === null) {
+            $params = Horde::getDriverConfig('signup', $driver);
+        }
+
+        $class = 'Auth_Signup_' . $driver;
+        if (!class_exists($class)) {
+            include dirname(__FILE__) . '/Signup/' . $driver . '.php';
+        }
+        if (class_exists($class)) {
+            return new $class($params);
+        } else {
+            return PEAR::raiseError(_("You must configure a backend to use Signups."));
+        }
+    }
+
+    /**
+     * Adds a new user to the system and handles any extra fields that may have
+     * been compiled, relying on the hooks.php file.
+     *
+     * @params mixed $info  Reference to array of parameteres to be passed
+     *                      to hook
+     *
+     * @return mixed  PEAR_Error if any errors, otherwise true.
+     */
+    public function addSignup(&$info)
+    {
+        global $auth, $conf;
+
+        // Perform any preprocessing if requested.
+        if ($conf['signup']['preprocess']) {
+            $info = Horde::callHook('_horde_hook_signup_preprocess', array($info));
+            if (is_a($info, 'PEAR_Error')) {
+                return $info;
+            }
+        }
+
+        // Check to see if the username already exists.
+        if ($auth->exists($info['user_name']) ||
+            $this->exists($info['user_name'])) {
+            return PEAR::raiseError(sprintf(_("Username \"%s\" already exists."), $info['user_name']));
+        }
+
+        // Attempt to add the user to the system.
+        $result = $auth->addUser($info['user_name'], array('password' => $info['password']));
+        if (is_a($result, 'PEAR_Error')) {
+            return $result;
+        }
+
+        $result = true;
+        // Attempt to add/update any extra data handed in.
+        if (!empty($info['extra'])) {
+            $result = false;
+            $result = Horde::callHook('_horde_hook_signup_addextra',
+                                     array($info['user_name'], $info['extra']));
+            if (is_a($result, 'PEAR_Error')) {
+                Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_EMERG);
+                return $result;
+            }
+        }
+
+        return $result;
+    }
+
+    /**
+     * Queues the user's submitted registration info for later admin approval.
+     *
+     * @params mixed $info  Reference to array of parameteres to be passed
+     *                      to hook
+     *
+     * @return mixed  PEAR_Error if any errors, otherwise true.
+     *
+     * @throws Horde_Mime_Exception
+     */
+    public function queueSignup(&$info)
+    {
+        global $auth, $conf;
+
+        // Perform any preprocessing if requested.
+        if ($conf['signup']['preprocess']) {
+            $info = Horde::callHook('_horde_hook_signup_preprocess',
+                                    array($info));
+            if (is_a($info, 'PEAR_Error')) {
+                return $info;
+            }
+        }
+
+        // Check to see if the username already exists.
+        if ($auth->exists($info['user_name']) ||
+            $this->exists($info['user_name'])) {
+            return PEAR::raiseError(sprintf(_("Username \"%s\" already exists."), $info['user_name']));
+        }
+
+        // If it's a unique username, go ahead and queue the request.
+        $signup = $this->newSignup($info['user_name']);
+        if (!empty($info['extra'])) {
+            $signup->data = array_merge($info['extra'],
+                                        array('password' => $info['password'],
+                                              'dateReceived' => time()));
+        } else {
+            $signup->data = array('password' => $info['password'],
+                                  'dateReceived' => time());
+        }
+
+        $result = $this->_queueSignup($signup);
+        if (is_a($result, 'PEAR_Error')) {
+            return $result;
+        }
+
+        if ($conf['signup']['queue']) {
+            $result = Horde::callHook('_horde_hook_signup_queued',
+                                      array($info['user_name'], $info));
+        }
+
+        if (!empty($conf['signup']['email'])) {
+            $link = Horde_Util::addParameter(Horde::url($GLOBALS['registry']->get('webroot', 'horde') . '/admin/signup_confirm.php', true, -1),
+                                             array('u' => $signup->name,
+                                                   'h' => hash_hmac('sha1', $signup->name, $conf['secret_key'])),
+                                             null, false);
+            $message = sprintf(_("A new account for the user \"%s\" has been requested through the signup form."), $signup->name)
+                . "\n\n"
+                . _("Approve the account:")
+                . "\n" . Horde_Util::addParameter($link, 'a', 'approve') . "\n"
+                . _("Deny the account:")
+                . "\n" . Horde_Util::addParameter($link, 'a', 'deny');
+            $mail = new Horde_Mime_Mail(
+                sprintf(_("Account signup request for \"%s\""), $signup->name),
+                $message,
+                $conf['signup']['email'],
+                $conf['signup']['email'],
+                NLS::getCharset());
+            $mail->send($conf['mailer']['type'], $conf['mailer']['params']);
+        }
+    }
+
+    /**
+     * Queues the user's submitted registration info for later admin approval.
+     *
+     * @params mixed $info  Reference to array of parameteres to be passed
+     *                      to hook
+     *
+     * @return mixed  PEAR_Error if any errors, otherwise true.
+     */
+    protected function &_queueSignup(&$info)
+    {
+        return PEAR::raiseError('Not implemented');
+    }
+
+    /**
+     * Get a user's queued signup information.
+     *
+     * @param string $username  The username to retrieve the queued info for.
+     *
+     * @return object  The bject for the requested signup.
+     */
+    public function getQueuedSignup($username)
+    {
+        return PEAR::raiseError('Not implemented');
+    }
+
+    /**
+     * Get the queued information for all pending signups.
+     *
+     * @return array  An array of objects, one for each signup in the queue.
+     */
+    public function getQueuedSignups()
+    {
+        return PEAR::raiseError('Not implemented');
+    }
+
+    /**
+     * Remove a queued signup.
+     *
+     * @param string $username  The user to remove from the signup queue.
+     */
+    public function removeQueuedSignup($username)
+    {
+        return PEAR::raiseError('Not implemented');
+    }
+
+    /**
+     * Return a new signup object.
+     *
+     * @param string $name  The signups's name.
+     *
+     * @return object  A new signup object.
+     */
+    public function newSignup($name)
+    {
+        return PEAR::raiseError('Not implemented');
+    }
+
+}
+
+/**
+ * Horde Signup Form.
+ *
+ * Copyright 2003-2009 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you did
+ * not receive this file, see http://opensource.org/licenses/lgpl-2.1.php
+ *
+ * @author  Marko Djukic <marko@oblo.com>
+ * @package Horde_Auth
+ */
+class HordeSignupForm extends Horde_Form {
+
+    var $_useFormToken = true;
+
+    function HordeSignupForm(&$vars)
+    {
+        global $registry;
+
+        parent::Horde_Form($vars, sprintf(_("%s Sign Up"), $registry->get('name')));
+
+        $this->setButtons(_("Sign up"), true);
+
+        $this->addHidden('', 'url', 'text', false);
+
+        /* Use hooks get any extra fields required in signing up. */
+        $extra = Horde::callHook('_horde_hook_signup_getextra');
+        if (!is_a($extra, 'PEAR_Error') && !empty($extra)) {
+            if (!isset($extra['user_name'])) {
+                $this->addVariable(_("Choose a username"), 'user_name', 'text', true);
+            }
+            if (!isset($extra['password'])) {
+                $this->addVariable(_("Choose a password"), 'password', 'passwordconfirm', true, false, _("type the password twice to confirm"));
+            }
+            foreach ($extra as $field_name => $field) {
+                $readonly = isset($field['readonly']) ? $field['readonly'] : null;
+                $desc = isset($field['desc']) ? $field['desc'] : null;
+                $required = isset($field['required']) ? $field['required'] : false;
+                $field_params = isset($field['params']) ? $field['params'] : array();
+
+                $this->addVariable($field['label'], 'extra[' . $field_name . ']',
+                                   $field['type'], $required, $readonly,
+                                   $desc, $field_params);
+            }
+        } else {
+            $this->addVariable(_("Choose a username"), 'user_name', 'text', true);
+            $this->addVariable(_("Choose a password"), 'password', 'passwordconfirm', true, false, _("type the password twice to confirm"));
+        }
+    }
+
+    /**
+     * Fetch the field values of the submitted form.
+     *
+     * @param Variables $vars  A Variables instance, optional since Horde 3.2.
+     * @param array $info      Array to be filled with the submitted field
+     *                         values.
+     */
+    function getInfo($vars, &$info)
+    {
+        parent::getInfo($vars, $info);
+
+        if (!isset($info['user_name']) && isset($info['extra']['user_name'])) {
+            $info['user_name'] = $info['extra']['user_name'];
+        }
+
+        if (!isset($info['password']) && isset($info['extra']['password'])) {
+            $info['password'] = $info['extra']['password'];
+        }
+    }
+
+}
diff --git a/framework/Auth/lib/Horde/Auth/Signup/Datatree.php b/framework/Auth/lib/Horde/Auth/Signup/Datatree.php
new file mode 100644 (file)
index 0000000..a3e19f4
--- /dev/null
@@ -0,0 +1,141 @@
+<?php
+
+require_once 'Horde/DataTree.php';
+
+/**
+ * Horde_Auth_Signup:: This class provides an interface to sign up or have
+ * new users sign themselves up into the horde installation, depending
+ * on how the admin has configured Horde.
+ *
+ * Copyright 2002-2009 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you did
+ * not receive this file, see http://opensource.org/licenses/lgpl-2.1.php
+ *
+ * @author  Marko Djukic <marko@oblo.com>
+ * @package Horde_Auth
+ */
+class Horde_Auth_Signup_Datatree extends Horde_Auth_Signup {
+
+    /**
+     * Pointer to a DataTree instance to manage/store signups
+     *
+     * @var DataTree
+     */
+    var $_datatree;
+
+    public function _-construct()
+    {
+        global $conf;
+
+        if (empty($conf['datatree']['driver'])) {
+            Horde::fatal(_("You must configure a DataTree backend to use Signups."), __FILE__, __LINE__);
+        }
+        $driver = $conf['datatree']['driver'];
+        $this->_datatree = DataTree::singleton($driver,
+                                                array_merge(Horde::getDriverConfig('datatree', $driver),
+                                                            array('group' => 'horde.signup')));
+    }
+
+    /**
+     * Stores the signup data in the backend.
+     *
+     * @params DataTreeObject_Signup $signup  Signup data.
+     */
+    protected function _queueSignup($signup)
+    {
+        return $this->_datatree->add($signup);
+    }
+
+    /**
+     * Get a user's queued signup information.
+     *
+     * @param string $username  The username to retrieve the queued info for.
+     *
+     * @return DataTreeObject_Signup  The DataTreeObject for the requested
+     *                                signup.
+     */
+    public function getQueuedSignup($username)
+    {
+        return $this->_datatree->getObject($username, 'DataTreeObject_Signup');
+    }
+
+    /**
+     * Get the queued information for all pending signups.
+     *
+     * @return array  An array of DataTreeObject_Signup objects, one for
+     *                each signup in the queue.
+     */
+    public function getQueuedSignups()
+    {
+        $signups = array();
+        foreach ($this->_datatree->get(DATATREE_FORMAT_FLAT, DATATREE_ROOT, true) as $username) {
+            if ($username != DATATREE_ROOT) {
+                $signups[] = $this->_datatree->getObject($username);
+            }
+        }
+        return $signups;
+    }
+
+    /**
+     * Remove a queued signup.
+     *
+     * @param string $username  The user to remove from the signup queue.
+     */
+    public function removeQueuedSignup($username)
+    {
+        $this->_datatree->remove($username);
+    }
+
+    /**
+     * Return a new signup object.
+     *
+     * @param string $name  The signups's name.
+     *
+     * @return DataTreeObject_Signup  A new signup object.
+     */
+    public function newSignup($name)
+    {
+        if (empty($name)) {
+            return PEAR::raiseError('Signup names must be non-empty');
+        }
+        return new DataTreeObject_Signup($name);
+    }
+
+}
+
+/**
+ * Extension of the DataTreeObject class for storing Signup
+ * information in the DataTree driver. If you want to store
+ * specialized Signup information, you should extend this class
+ * instead of extending DataTreeObject directly.
+ *
+ * @author  Marko Djukic <marko@oblo.com>
+ * @package Horde_Auth
+ */
+class DataTreeObject_Signup extends DataTreeObject {
+
+    /**
+     * We want to see queued signups in descending order of receipt.
+     * Insert new signups at position 0 and push the rest down.
+     *
+     * @var integer
+     */
+    var $order = 0;
+
+    /**
+     * The DataTreeObject_Signup constructor. Just makes sure to call
+     * the parent constructor so that the signup's is is set
+     * properly.
+     *
+     * @param string $id  The id of the signup.
+     */
+    function DataTreeObject_Signup($id)
+    {
+        parent::DataTreeObject($id);
+        if (is_null($this->data)) {
+            $this->data = array();
+        }
+    }
+
+}
diff --git a/framework/Auth/lib/Horde/Auth/Signup/Sql.php b/framework/Auth/lib/Horde/Auth/Signup/Sql.php
new file mode 100644 (file)
index 0000000..fc745de
--- /dev/null
@@ -0,0 +1,358 @@
+<?php
+/**
+ * Auth_Signup:: This class provides an interface to sign up or have
+ * new users sign themselves up into the horde installation, depending
+ * on how the admin has configured Horde.
+ *
+ * Copyright 2008-2009 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you did
+ * not receive this file, see http://opensource.org/licenses/lgpl-2.1.php
+ *
+ * @author  Duck <duck@obala.net>
+ * @package Horde_Auth
+ */
+class Horde_Auth_Signup_Sql extends Horde_Auth_Signup
+{
+    /**
+     * Handle for the current database connection.
+     *
+     * @var DB
+     */
+    protected $_db;
+
+    /**
+     * Handle for the current database connection, used for writing. Defaults
+     * to the same handle as $db if a separate write database is not required.
+     *
+     * @var DB
+     */
+    protected $_write_db;
+
+    /**
+     * SQL connection parameters
+     */
+    protected $_params = array();
+
+    /**
+     * Connect to DB.
+     */
+    public function __construct($params)
+    {
+        $this->_params = $params;
+        $this->_connect();
+    }
+
+    /**
+     * Stores the signup data in the backend.
+     *
+     * @params SQLObject_Signup $signup  Signup data.
+     */
+    protected function _queueSignup($signup)
+    {
+        $query = 'INSERT INTO ' . $this->_params['table']
+            . ' (user_name, signup_date, signup_host, signup_data) VALUES (?, ?, ?, ?) ';
+        $values = array($signup->name,
+                        time(),
+                        $_SERVER['REMOTE_ADDR'],
+                        serialize($signup->data));
+        Horde::logMessage('SQL query by Auth_Signup_sql::_queueSignup(): ' . $query,
+                          __FILE__, __LINE__, PEAR_LOG_DEBUG);
+        $stmt = $this->_write_db->prepare($query, null, MDB2_PREPARE_MANIP);
+        if (is_a($stmt, 'PEAR_Error')) {
+            Horde::logMessage($stmt, __FILE__, __LINE__, PEAR_LOG_ERR);
+            return $stmt;
+        }
+        $result = $stmt->execute($values);
+        if (is_a($result, 'PEAR_Error')) {
+            Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR);
+            return $result;
+        }
+        $stmt->free();
+    }
+
+    /**
+     * Checks if a user exists in the system.
+     *
+     * @param string $user  The user to check.
+     *
+     * @return boolean  True if the user exists.
+     */
+    public function exists($user)
+    {
+        $stmt = $this->_db->prepare('SELECT 1 FROM ' . $this->_params['table']
+                                    . ' WHERE user_name = ?');
+
+        if (is_a($stmt, 'PEAR_Error')) {
+            Horde::logMessage($stmt, __FILE__, __LINE__, PEAR_LOG_ERR);
+            return $stmt;
+        }
+        $result = $stmt->execute(array($user));
+        if (is_a($result, 'PEAR_Error')) {
+            Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR);
+            return $result;
+        }
+
+        $exists = (bool)$result->fetchOne();
+        $stmt->free();
+        $result->free();
+
+        return $exists;
+    }
+
+    /**
+     * Get a user's queued signup information.
+     *
+     * @param string $username  The username to retrieve the queued info for.
+     *
+     * @return SQLObject_Signup  The SQLObject for the requested
+     *                                signup.
+     */
+    public function getQueuedSignup($username)
+    {
+        $stmt = $this->_db->prepare('SELECT * FROM ' . $this->_params['table'] . ' WHERE user_name = ?');
+        if (is_a($stmt, 'PEAR_Error')) {
+            Horde::logMessage($stmt, __FILE__, __LINE__, PEAR_LOG_ERR);
+            return $stmt;
+        }
+        $results = $stmt->execute(array($username));
+        if (is_a($results, 'PEAR_Error')) {
+            Horde::logMessage($results, __FILE__, __LINE__, PEAR_LOG_ERR);
+            return $results;
+        }
+        $data = $results->fetchRow(MDB2_FETCHMODE_ASSOC);
+        if (is_a($data, 'PEAR_Error')) {
+            Horde::logMessage($data, __FILE__, __LINE__, PEAR_LOG_ERR);
+            return $data;
+        } elseif (empty($data)) {
+            return PEAR::RaiseError(sprintf(_("User \"%s\" does not exist."), $name));
+        }
+        $stmt->free();
+        $results->free();
+
+        $object = new SQLObject_Signup($data['user_name']);
+        $object->setData($data);
+
+        return $object;
+    }
+
+    /**
+     * Get the queued information for all pending signups.
+     *
+     * @return array  An array of SQLObject_Signup objects, one for
+     *                each signup in the queue.
+     */
+    public function getQueuedSignups()
+    {
+        $query = 'SELECT * FROM ' . $this->_params['table'] . '  ORDER BY signup_date';
+        $result = $this->_db->query($query);
+        if (is_a($result, 'PEAR_Error')) {
+            Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR);
+            return $result;
+        } elseif (empty($result)) {
+            return array();
+        }
+
+        $signups = array();
+        while ($signup = $result->fetchRow(MDB2_FETCHMODE_ASSOC)) {
+            $object = new SQLObject_Signup($signup['user_name']);
+            $object->setData($signup);
+            $signups[] = $object;
+        }
+
+        $result->free();
+
+        return $signups;
+    }
+
+    /**
+     * Remove a queued signup.
+     *
+     * @param string $username  The user to remove from the signup queue.
+     */
+    public function removeQueuedSignup($username)
+    {
+        $stmt = $this->_write_db->prepare('DELETE FROM ' . $this->_params['table'] . ' WHERE user_name = ?', null, MDB2_PREPARE_MANIP);
+        if (is_a($stmt, 'PEAR_Error')) {
+            Horde::logMessage($stmt, __FILE__, __LINE__, PEAR_LOG_ERR);
+            return $stmt;
+        }
+        $result = $stmt->execute(array($username));
+        if (is_a($result, 'PEAR_Error')) {
+            Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR);
+            return $result;
+        }
+        $stmt->free();
+
+        return true;
+    }
+
+    /**
+     * Return a new signup object.
+     *
+     * @param string $name  The signups's name.
+     *
+     * @return SQLObject_Signup  A new signup object.
+     */
+    public function newSignup($name)
+    {
+        if (empty($name)) {
+            return PEAR::raiseError('Signup names must be non-empty');
+        }
+        return new SQLObject_Signup($name);
+    }
+
+    /**
+     * Attempts to open a connection to the sql server.
+     *
+     * @return boolean  True on success; exits (Horde::fatal()) on error.
+     */
+    protected function _connect()
+    {
+        if (!isset($this->_params['database'])) {
+            $this->_params['database'] = '';
+        }
+        if (!isset($this->_params['username'])) {
+            $this->_params['username'] = '';
+        }
+        if (!isset($this->_params['hostspec'])) {
+            $this->_params['hostspec'] = '';
+        }
+        if (!isset($this->_params['table'])) {
+            $this->_params['table'] = 'horde_signups';
+        }
+
+        /* Connect to the sql server using the supplied parameters. */
+        $params = $this->_params;
+        unset($params['charset']);
+        $this->_write_db = MDB2::factory($params);
+        if (is_a($this->_write_db, 'PEAR_Error')) {
+            Horde::fatal($this->_write_db, __FILE__, __LINE__);
+        }
+
+        /* Set DB portability options. */
+        switch ($this->_write_db->phptype) {
+        case 'mssql':
+            $this->_write_db->setOption('field_case', CASE_LOWER);
+            $this->_write_db->setOption('portability', MDB2_PORTABILITY_FIX_CASE | MDB2_PORTABILITY_ERRORS | MDB2_PORTABILITY_RTRIM | MDB2_PORTABILITY_FIX_ASSOC_FIELD_NAMES);
+            break;
+        default:
+            $this->_write_db->setOption('field_case', CASE_LOWER);
+            $this->_write_db->setOption('portability', MDB2_PORTABILITY_FIX_CASE | MDB2_PORTABILITY_ERRORS | MDB2_PORTABILITY_FIX_ASSOC_FIELD_NAMES);
+        }
+
+        /* Check if we need to set up the read DB connection seperately. */
+        if (!empty($this->_params['splitread'])) {
+            $params = array_merge($params, $this->_params['read']);
+            $this->_db = MDB2::factory($params);
+            if (is_a($this->_db, 'PEAR_Error')) {
+                Horde::fatal($this->_db, __FILE__, __LINE__);
+            }
+
+            /* Set DB portability options. */
+            switch ($this->_db->phptype) {
+            case 'mssql':
+                $this->_db->setOption('field_case', CASE_LOWER);
+                $this->_db->setOption('portability', MDB2_PORTABILITY_FIX_CASE | MDB2_PORTABILITY_ERRORS | MDB2_PORTABILITY_RTRIM | MDB2_PORTABILITY_FIX_ASSOC_FIELD_NAMES);
+                break;
+            default:
+                $this->_db->setOption('portability', MDB2_PORTABILITY_FIX_CASE | MDB2_PORTABILITY_ERRORS | MDB2_PORTABILITY_FIX_ASSOC_FIELD_NAMES);
+            }
+        } else {
+            /* Default to the same DB handle as the writer for reading too */
+            $this->_db = $this->_write_db;
+        }
+
+        return true;
+    }
+
+}
+
+/**
+ * Extension of the SQLObject class for storing Signup
+ * information in the SQL driver. If you want to store
+ * specialized Signup information, you should extend this class
+ * instead of extending SQLObject directly.
+ *
+ * @author  Duck <duck@obala.net>
+ * @package Horde_Auth
+ */
+class SQLObject_Signup {
+
+    /**
+     * Key-value hash that will be serialized.
+     *
+     * @see getData()
+     * @var array
+     */
+    var $data = array();
+
+    /**
+     * The unique name of this object.
+     * These names have the same requirements as other object names - they must
+     * be unique, etc.
+     *
+     * @var string
+     */
+    var $name;
+
+    /**
+     * The SQLObject_Signup constructor. Just makes sure to call
+     * the parent constructor so that the signup's is is set
+     * properly.
+     *
+     * @param string $id  The id of the signup.
+     */
+    function SQLObject_Signup($id)
+    {
+        if (is_null($this->data)) {
+            $this->data = array();
+        }
+
+        $this->name = $id;
+    }
+
+    /**
+     * Gets the data array.
+     *
+     * @return array  The internal data array.
+     */
+    function getData()
+    {
+        return $this->data;
+    }
+
+    /**
+     * Sets the data array.
+     *
+     * @param array  The data array to store internally.
+     */
+    function setData($data)
+    {
+        $part = unserialize($data['signup_data']);
+        if (!empty($part) && is_array($part)) {
+            if (!empty($part['extra'])) {
+                $extra = $part['extra'];
+                unset($part['extra']);
+                $part = array_merge($part, $extra);
+            }
+            $this->data = array_merge($data, $part);
+        } else {
+            $this->data = $data;
+        }
+
+        unset($this->data['signup_data']);
+        $this->data['dateReceived'] = $data['signup_date'];
+    }
+
+    /**
+     * Gets the name of this object.
+     *
+     * @return string The object name.
+     */
+    function getName()
+    {
+        return $this->name;
+    }
+
+}
diff --git a/framework/Auth/lib/Horde/Auth/Smb.php b/framework/Auth/lib/Horde/Auth/Smb.php
new file mode 100644 (file)
index 0000000..0a02c9a
--- /dev/null
@@ -0,0 +1,87 @@
+<?php
+/**
+ * The Horde_Auth_Smb class provides a SMB implementation of the Horde
+ * authentication system.
+ *
+ * This module requires the smbauth extension for PHP:
+ *   http://tekrat.com/wp/smbauth/
+ *
+ * At the time of this writing, the extension, and thus this module, only
+ * supported authentication against a domain, and pdc and bdc must be non-null
+ * and not equal to each other. In other words, to use this module you must
+ * have a domain with at least one PDC and one BDC.
+ *
+ * Required parameters:
+ * <pre>
+ * 'hostspec' - IP, DNS Name, or NetBios Name of the SMB server to
+ *              authenticate with.
+ * 'domain' - The domain name to authenticate with.
+ * </pre>
+ *
+ * Optional parameters:
+ * <pre>
+ * 'group' - Group name that the user must be a member of. Will be
+ *           ignored if the value passed is a zero length string.
+ * </pre>
+ *
+ * Copyright 1999-2009 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you did
+ * not receive this file, see http://opensource.org/licenses/lgpl-2.1.php
+ *
+ * @author  Jon Parise <jon@horde.org>
+ * @author  Marcus I. Ryan <marcus@riboflavin.net>
+ * @package Horde_Auth
+ */
+class Horde_Auth_Smb extends Horde_Auth_Driver
+{
+    /**
+     * Constructor.
+     *
+     * @param array $params  A hash containing connection parameters.
+     *
+     * @throws Horde_Exception
+     */
+    public function __construct($params = array())
+    {
+        if (!Util::extensionExists('smbauth')) {
+            throw new Horde_Exception(_("Horde_Auth_Smb: Required smbauth extension not found."));
+        }
+
+        /* Ensure we've been provided with all of the necessary parameters. */
+        Horde::assertDriverConfig($params, 'auth',
+            array('hostspec', 'domain'),
+            'authentication Samba');
+
+        parent::__construct($params);
+    }
+
+    /**
+     * Find out if the given set of login credentials are valid.
+     *
+     * @param string $userId      The userId to check.
+     * @param array $credentials  An array of login credentials.
+     *
+     * @throws Horde_Exception
+     */
+    public function _authenticate($userId, $credentials)
+    {
+        if (empty($credentials['password'])) {
+            throw new Horde_Exception('', Horde_Auth::REASON_BADLOGIN);
+        }
+
+        /* Authenticate. */
+        $rval = validate($this->_params['hostspec'],
+                         $this->_params['domain'],
+                         empty($this->_params['group']) ? '' : $this->_params['group'],
+                         $userId,
+                         $credentials['password']);
+
+        if ($rval === 1) {
+            throw new Horde_Exception(_("Failed to connect to SMB server."));
+        } elseif ($rval !== 0) {
+            throw new Horde_Exception(err2str());
+        }
+    }
+
+}
diff --git a/framework/Auth/lib/Horde/Auth/Smbclient.php b/framework/Auth/lib/Horde/Auth/Smbclient.php
new file mode 100644 (file)
index 0000000..121745e
--- /dev/null
@@ -0,0 +1,82 @@
+<?php
+/**
+ * The Horde_Auth_Smbclient class provides an smbclient implementation of
+ * the Horde authentication system.
+ *
+ * Required parameters:
+ * <pre>
+ * 'domain'          The domain name to authenticate with.
+ * 'hostspec'        IP, DNS Name, or NetBios Name of the SMB server to
+ *                   authenticate with.
+ * 'smbclient_path'  The location of the smbclient(1) utility.
+ * </pre>
+ *
+ * Optional parameters:
+ * <pre>
+ * 'group' - Group name that the user must be a member of. Will be
+ *           ignored if the value passed is a zero length string.
+ * </pre>
+ *
+ * Copyright 1999-2009 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you did
+ * not receive this file, see http://opensource.org/licenses/lgpl-2.1.php
+ *
+ * @author  Jon Parise <jon@horde.org>
+ * @author  Marcus I. Ryan <marcus@riboflavin.net>
+ * @package Horde_Auth
+ */
+class Horde_Auth_Smbclient extends Horde_Auth_Driver
+{
+    /**
+     * Constructor.
+     *
+     * @param array $params  A hash containing connection parameters.
+     */
+    public function __construct($params = array())
+    {
+        /* Ensure we've been provided with all of the necessary parameters. */
+        Horde::assertDriverConfig($params, 'auth',
+            array('hostspec', 'domain', 'smbclient_path'),
+            'authentication smbclient');
+
+        parent::__construct($params);
+    }
+
+    /**
+     * Find out if the given set of login credentials are valid.
+     *
+     * @param string $userId      The userId to check.
+     * @param array $credentials  An array of login credentials.
+     *
+     * @throws Horde_Exception
+     */
+    protected function _authenticate($userId, $credentials)
+    {
+        if (empty($credentials['password'])) {
+            throw new Horde_Exception('', Horde_Auth::REASON_BADLOGIN);
+        }
+
+        /* Authenticate. */
+        $cmdline = implode(' ', array($this->_params['smbclient_path'],
+                                      '-L',
+                                      $this->_params['hostspec'],
+                                      '-W',
+                                      $this->_params['domain'],
+                                      '-U',
+                                      $userId));
+
+        $sc = popen($cmdline, 'w');
+        if ($sc === false) {
+            throw new Horde_Exception(_("Unable to execute smbclient."));
+        }
+
+        fwrite($sc, $credentials['password']);
+        $rc = pclose($sc);
+
+        if ((int)($rc & 0xff) != 0) {
+            throw new Horde_Exception('', Horde_Auth::REASON_BADLOGIN);
+        }
+    }
+
+}
diff --git a/framework/Auth/lib/Horde/Auth/Sql.php b/framework/Auth/lib/Horde/Auth/Sql.php
new file mode 100644 (file)
index 0000000..8200d42
--- /dev/null
@@ -0,0 +1,484 @@
+<?php
+/**
+ * The Horde_Auth_Sql class provides a SQL implementation of the Horde
+ * authentication system.
+ *
+ * Required parameters:
+ * <pre>
+ * 'phptype' - (string) The database type (ie. 'pgsql', 'mysql', etc.).
+ * </pre>
+ *
+ * Optional 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: 'md5-hex'
+ * 'hard_expiration_field' - (string) The name of the field containing a date
+ *                           after which the account is no longer valid and
+ *                           the user will not be able to log in at all.
+ *                           DEFAULT: none
+ * 'password_field' - (string) The name of the password field in the auth
+ *                    table.
+ *                    DEFAULT: 'user_pass'
+ * 'show_encryption' - (boolean) Whether or not to prepend the encryption in
+ *                     the password field.
+ *                     DEFAULT: false
+ * 'soft_expiration_field' - (string) The name of the field containing a date
+ *                           after which the system will request the user
+ *                           change his or her password.
+ *                           DEFAULT: none
+ * 'table' - (string) The name of the SQL table to use in 'database'.
+ *           DEFAULT: 'horde_users'
+ * 'username_field' - (string) The name of the username field in the auth
+ *                    table.
+ *                    DEFAULT: 'user_uid'
+ * </pre>
+ *
+ * Required by some database implementations:
+ * <pre>
+ * 'hostspec'     The hostname of the database server.
+ * 'protocol'     The communication protocol ('tcp', 'unix', etc.).
+ * 'database'     The name of the database.
+ * 'username'     The username with which to connect to the database.
+ * 'password'     The password associated with 'username'.
+ * '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.
+ * </pre>
+ *
+ * Optional values when using separate read and write servers, for example
+ * in replication settings:
+ * <pre>
+ * 'splitread'   Boolean, whether to implement the separation or not.
+ * 'read'        Array containing the parameters which are different for
+ *               the read database connection, currently supported
+ *               only 'hostspec' and 'port' parameters.
+ * </pre>
+ *
+ * The table structure for the Auth system is in
+ * horde/scripts/sql/horde_users.sql.
+ *
+ * Copyright 1999-2009 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you did
+ * not receive this file, see http://opensource.org/licenses/lgpl-2.1.php
+ *
+ * @author  Chuck Hagenbuch <chuck@horde.org>
+ * @package Horde_Auth
+ */
+class Horde_Auth_Sql extends Horde_Auth_Driver
+{
+    /**
+     * 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' => true,
+        'update'        => true
+    );
+
+    /**
+     * Handle for the current database connection.
+     *
+     * @var DB
+     */
+    protected $_db;
+
+    /**
+     * Handle for the current database connection, used for writing. Defaults
+     * to the same handle as $_db if a separate write database is not required.
+     *
+     * @var DB
+     */
+    protected $_write_db;
+
+    /**
+     * Boolean indicating whether or not we're connected to the SQL server.
+     *
+     * @var boolean
+     */
+    protected $_connected = false;
+
+    /**
+     * 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_Exception
+     */
+    protected function _authenticate($userId, $credentials)
+    {
+        try {
+            $this->_connect();
+        } catch (Horde_Exception $e) {
+            Horde::logMessage($e, __FILE__, __LINE__, PEAR_LOG_ERR);
+            throw new Horde_Exception('', Horde_Auth::REASON_FAILED);
+        }
+
+        /* Build the SQL query. */
+        $query = sprintf('SELECT * FROM %s WHERE %s = ?',
+                         $this->_params['table'],
+                         $this->_params['username_field']);
+        $values = array($userId);
+
+        Horde::logMessage('SQL Query by Auth_sql::_authenticate(): ' . $query, __FILE__, __LINE__, PEAR_LOG_DEBUG);
+
+        $result = $this->_db->query($query, $values);
+        if ($result instanceof PEAR_Error) {
+            Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR);
+            throw new Horde_Exception('', Horde_Auth::REASON_FAILED);
+        }
+
+        $row = $result->fetchRow(DB_GETMODE_ASSOC);
+        if (is_array($row)) {
+            $result->free();
+        } else {
+            throw new Horde_Exception('', Horde_Auth::REASON_BADLOGIN);
+        }
+
+        if (!$this->_comparePasswords($row[$this->_params['password_field']],
+                                      $credentials['password'])) {
+            throw new Horde_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_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->_authCredentials['changeRequested'] = true;
+        }
+    }
+
+    /**
+     * Add a set of authentication credentials.
+     *
+     * @param string $userId      The userId to add.
+     * @param array $credentials  The credentials to add.
+     *
+     * @throws Horde_Exception
+     */
+    public function addUser($userId, $credentials)
+    {
+        $this->_connect();
+
+        /* Build the SQL query. */
+        $query = sprintf('INSERT INTO %s (%s, %s) VALUES (?, ?)',
+                         $this->_params['table'],
+                         $this->_params['username_field'],
+                         $this->_params['password_field']);
+        $values = array($userId,
+                        Horde_Auth::getCryptedPassword($credentials['password'],
+                                                  '',
+                                                  $this->_params['encryption'],
+                                                  $this->_params['show_encryption']));
+
+        Horde::logMessage('SQL Query by Auth_sql::addUser(): ' . $query, __FILE__, __LINE__, PEAR_LOG_DEBUG);
+
+        $result = $this->_write_db->query($query, $values);
+        if ($result instanceof PEAR_Error) {
+            throw new Horde_Exception($result);
+        }
+    }
+
+    /**
+     * 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_Exception
+     */
+    public function updateUser($oldID, $newID, $credentials)
+    {
+        $this->_connect();
+
+        /* Build the SQL query. */
+        $tuple = array();
+        $tuple[$this->_params['username_field']] = $newID;
+        $tuple[$this->_params['password_field']] =
+            Horde_Auth::getCryptedPassword($credentials['password'],
+                                      '',
+                                      $this->_params['encryption'],
+                                      $this->_params['show_encryption']);
+
+        if (empty($this->_params['soft_expiration_window'])) {
+            if (!empty($this->_params['soft_expiration_field'])) {
+                $tuple[$this->_params['soft_expiration_field']] = null;
+            }
+        } else {
+            $date = time();
+            $datea = localtime($date, true);
+            $date = mktime($datea['tm_hour'], $datea['tm_min'],
+                           $datea['tm_sec'], $datea['tm_mon'] + 1,
+                           $datea['tm_mday'] + $this->_params['soft_expiration_window'],
+                           $datea['tm_year']);
+
+            $tuple[$this->_params['soft_expiration_field']] = $date;
+
+            global $notification;
+            if (!empty($notification)) {
+                $notification->push(strftime(_("New password will expire on %s."), $date), 'horde.message');
+            }
+
+            if (empty($this->_params['hard_expiration_window'])) {
+                $tuple[$this->_params['hard_expiration_field']] = null;
+            } else {
+                $datea = localtime($date, true);
+                $date = mktime($datea['tm_hour'], $datea['tm_min'],
+                               $datea['tm_sec'], $datea['tm_mon'] + 1,
+                               $datea['tm_mday'] + $this->_params['soft_expiration_window'],
+                               $datea['tm_year']);
+
+                $tuple[$this->_params['hard_expiration_field']] = $date;
+            }
+        }
+
+        $query = sprintf('UPDATE %s SET %s WHERE %s = ?',
+                         $this->_params['table'],
+                         Horde_SQL::updateValues($this->_write_db, $tuple),
+                         $this->_params['username_field']);
+        $values = array($oldID);
+
+        Horde::logMessage('SQL Query by Auth_sql:updateUser(): ' . $query, __FILE__, __LINE__, PEAR_LOG_DEBUG);
+
+        $result = $this->_write_db->query($query, $values);
+        if ($result instanceof PEAR_Error) {
+            Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR);
+            throw new Horde_Exception($result);
+        }
+    }
+
+    /**
+     * 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 on success.
+     * @throws Horde_Exception
+     */
+    public function resetPassword($userId)
+    {
+        $this->_connect();
+
+        /* Get a new random password. */
+        $password = Horde_Auth::genRandomPassword();
+
+        /* 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($password,
+                                                  '',
+                                                  $this->_params['encryption'],
+                                                  $this->_params['show_encryption']),
+                        $userId);
+
+        Horde::logMessage('SQL Query by Auth_sql::resetPassword(): ' . $query, __FILE__, __LINE__, PEAR_LOG_DEBUG);
+
+        $result = $this->_write_db->query($query, $values);
+        if ($result instanceof PEAR_Error) {
+            throw new Horde_Exception($result);
+        }
+
+        return $password;
+    }
+
+    /**
+     * Delete a set of authentication credentials.
+     *
+     * @param string $userId  The userId to delete.
+     *
+     * @throws Horde_Exception
+     */
+    public function removeUser($userId)
+    {
+        $this->_connect();
+
+        /* Build the SQL query. */
+        $query = sprintf('DELETE FROM %s WHERE %s = ?',
+                         $this->_params['table'],
+                         $this->_params['username_field']);
+        $values = array($userId);
+
+        Horde::logMessage('SQL Query by Auth_sql::removeUser(): ' . $query, __FILE__, __LINE__, PEAR_LOG_DEBUG);
+
+        $result = $this->_write_db->query($query, $values);
+        if ($result instanceof PEAR_Error) {
+            throw new Horde_Exception($result);
+        }
+
+        Horde_Auth::removeUserData($userId);
+    }
+
+    /**
+     * List all users in the system.
+     *
+     * @return array  The array of userIds.
+     * @throws Horde_Exception
+     */
+    public function listUsers()
+    {
+        $this->_connect();
+
+        /* Build the SQL query. */
+        $query = sprintf('SELECT %s FROM %s',
+                         $this->_params['username_field'],
+                         $this->_params['table']);
+
+        Horde::logMessage('SQL Query by Auth_sql::listUsers(): ' . $query, __FILE__, __LINE__, PEAR_LOG_DEBUG);
+
+        $res = $this->_db->getCol($query);
+        if ($res instanceof PEAR_Error) {
+            throw new Horde_Exception($res);
+        }
+
+        return $res;
+    }
+
+    /**
+     * Checks if a userId exists in the system.
+     *
+     * @return boolean  Whether or not the userId already exists.
+     */
+    public function exists($userId)
+    {
+        $this->_connect();
+
+        /* Build the SQL query. */
+        $query = sprintf('SELECT 1 FROM %s WHERE %s = ?',
+                         $this->_params['table'],
+                         $this->_params['username_field']);
+        $values = array($userId);
+
+        Horde::logMessage('SQL Query by Auth_sql::exists(): ' . $query, __FILE__, __LINE__, PEAR_LOG_DEBUG);
+
+        $res = $this->_db->getOne($query, $values);
+        return ($res instanceof PEAR_Error)
+            ? false
+            : $res;
+    }
+
+    /**
+     * 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']);
+    }
+
+    /**
+     * Attempts to open a connection to the SQL server.
+     *
+     * @throws Horde_Exception
+     */
+    function _connect()
+    {
+        if ($this->_connected) {
+            return;
+        }
+
+        Horde::assertDriverConfig($this->_params, 'auth', array('phptype'),
+                                  'authentication SQL');
+
+        if (!isset($this->_params['database'])) {
+            $this->_params['database'] = '';
+        }
+        if (!isset($this->_params['username'])) {
+            $this->_params['username'] = '';
+        }
+        if (!isset($this->_params['password'])) {
+            $this->_params['password'] = '';
+        }
+        if (!isset($this->_params['hostspec'])) {
+            $this->_params['hostspec'] = '';
+        }
+        if (empty($this->_params['encryption'])) {
+            $this->_params['encryption'] = 'md5-hex';
+        }
+        if (!isset($this->_params['show_encryption'])) {
+            $this->_params['show_encryption'] = false;
+        }
+        if (empty($this->_params['table'])) {
+            $this->_params['table'] = 'horde_users';
+        }
+        if (empty($this->_params['username_field'])) {
+            $this->_params['username_field'] = 'user_uid';
+        } else {
+            $this->_params['username_field'] = Horde_String::lower($this->_params['username_field']);
+        }
+        if (empty($this->_params['password_field'])) {
+            $this->_params['password_field'] = 'user_pass';
+        } else {
+            $this->_params['password_field'] = Horde_String::lower($this->_params['password_field']);
+        }
+
+        /* Connect to the SQL server using the supplied parameters. */
+        $this->_write_db = &DB::connect($this->_params,
+                                        array('persistent' => !empty($this->_params['persistent']),
+                                              'ssl' => !empty($this->_params['ssl'])));
+        if ($this->_write_db instanceof PEAR_Error) {
+            throw new Horde_Exception($this->_write_db);
+        }
+
+        // Set DB portability options.
+        switch ($this->_write_db->phptype) {
+        case 'mssql':
+            $this->_write_db->setOption('portability', DB_PORTABILITY_LOWERCASE | DB_PORTABILITY_ERRORS | DB_PORTABILITY_RTRIM);
+            break;
+        default:
+            $this->_write_db->setOption('portability', DB_PORTABILITY_LOWERCASE | DB_PORTABILITY_ERRORS);
+        }
+
+        /* Check if we need to set up the read DB connection
+         * seperately. */
+        if (!empty($this->_params['splitread'])) {
+            $params = array_merge($this->_params, $this->_params['read']);
+            $this->_db = &DB::connect($params,
+                                      array('persistent' => !empty($params['persistent']),
+                                            'ssl' => !empty($params['ssl'])));
+            if ($this->_db instanceof PEAR_Error) {
+                throw new Horde_Exception($this->_db);
+            }
+
+            switch ($this->_db->phptype) {
+            case 'mssql':
+                $this->_db->setOption('portability', DB_PORTABILITY_LOWERCASE | DB_PORTABILITY_ERRORS | DB_PORTABILITY_RTRIM);
+                break;
+            default:
+                $this->_db->setOption('portability', DB_PORTABILITY_LOWERCASE | DB_PORTABILITY_ERRORS);
+            }
+
+        } else {
+            /* Default to the same DB handle for reads. */
+            $this->_db =& $this->_write_db;
+        }
+
+        $this->_connected = true;
+    }
+
+}
diff --git a/framework/Auth/package.xml b/framework/Auth/package.xml
new file mode 100644 (file)
index 0000000..71c1255
--- /dev/null
@@ -0,0 +1,238 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<package packagerversion="1.4.9" version="2.0" xmlns="http://pear.php.net/dtd/package-2.0" xmlns:tasks="http://pear.php.net/dtd/tasks-1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://pear.php.net/dtd/tasks-1.0
+http://pear.php.net/dtd/tasks-1.0.xsd
+http://pear.php.net/dtd/package-2.0
+http://pear.php.net/dtd/package-2.0.xsd">
+ <name>Auth</name>
+ <channel>pear.horde.org</channel>
+ <summary>Horde Authentication API</summary>
+ <description>The Horde_Auth:: package provides a common abstracted interface into the various backends for the Horde authentication system.
+</description>
+ <lead>
+  <name>Chuck Hagenbuch</name>
+  <user>chuck</user>
+  <email>chuck@horde.org</email>
+  <active>yes</active>
+ </lead>
+ <lead>
+  <name>Jan Schneider</name>
+  <user>jan</user>
+  <email>jan@horde.org</email>
+  <active>yes</active>
+ </lead>
+ <date>2009-07-06</date>
+ <version>
+  <release>0.2.0</release>
+  <api>0.2.0</api>
+ </version>
+ <stability>
+  <release>beta</release>
+  <api>beta</api>
+ </stability>
+ <license uri="http://opensource.org/licenses/lgpl-2.1.php">LGPL</license>
+ <notes>* Split Horde_Auth:: into Horde_Auth:: and Horde_Auth_Driver:: components.
+ * Initial Horde 4 package.
+ </notes>
+ <contents>
+  <dir name="/">
+   <dir name="lib">
+    <dir name="Horde">
+     <dir name="Auth">
+      <dir name="Signup">
+       <file name="Datatree.php" role="php" />
+       <file name="Sql.php" role="php" />
+      </dir> <!-- /lib/Horde/Auth/Signup -->
+      <file name="Application.php" role="php" />
+      <file name="Auto.php" role="php" />
+      <file name="Composite.php" role="php" />
+      <file name="Customsql.php" role="php" />
+      <file name="Cyrsql.php" role="php" />
+      <file name="Cyrus.php" role="php" />
+      <file name="Driver.php" role="php" />
+      <file name="Ftp.php" role="php" />
+      <file name="Http.php" role="php" />
+      <file name="HttpRemote.php" role="php" />
+      <file name="Imap.php" role="php" />
+      <file name="Imsp.php" role="php" />
+      <file name="Ipbasic.php" role="php" />
+      <file name="Kolab.php" role="php" />
+      <file name="Krb5.php" role="php" />
+      <file name="Ldap.php" role="php" />
+      <file name="Login.php" role="php" />
+      <file name="Msad.php" role="php" />
+      <file name="Pam.php" role="php" />
+      <file name="Passwd.php" role="php" />
+      <file name="Peclsasl.php" role="php" />
+      <file name="Radius.php" role="php" />
+      <file name="Shibboleth.php" role="php" />
+      <file name="Signup.php" role="php" />
+      <file name="Smb.php" role="php" />
+      <file name="Smbclient.php" role="php" />
+      <file name="Sql.php" role="php" />
+     </dir> <!-- /lib/Horde/Auth -->
+     <file name="Auth.php" role="php" />
+    </dir> <!-- /lib/Horde -->
+   </dir> <!-- /lib -->
+   <dir name="test">
+    <dir name="Horde">
+     <dir name="Auth">
+      <file name="AllTests.php" role="test" />
+      <file name="KolabScenarioTest.php" role="test" />
+      <file name="KolabTest.php" role="test" />
+      <file name="credentials.php" role="test" />
+      <file name="getCryptedPassword.phpt" role="test" />
+      <file name="getSalt.phpt" role="test" />
+      <file name="passwd.phpt" role="test" />
+      <file name="test.passwd" role="test" />
+     </dir> <!-- /test/Horde/Auth -->
+    </dir> <!-- /test/Horde -->
+   </dir> <!-- /test -->
+  </dir> <!-- / -->
+ </contents>
+ <dependencies>
+  <required>
+   <php>
+    <min>5.2.0</min>
+   </php>
+   <pearinstaller>
+    <min>1.5.4</min>
+   </pearinstaller>
+   <package>
+    <name>Horde_Framework</name>
+    <channel>pear.horde.org</channel>
+   </package>
+   <package>
+    <name>Secret</name>
+    <channel>pear.horde.org</channel>
+   </package>
+   <package>
+    <name>Util</name>
+    <channel>pear.horde.org</channel>
+   </package>
+  </required>
+  <optional>
+   <package>
+    <name>DataTree</name>
+    <channel>pear.horde.org</channel>
+   </package>
+   <package>
+    <name>Form</name>
+    <channel>pear.horde.org</channel>
+   </package>
+   <package>
+    <name>History</name>
+    <channel>pear.horde.org</channel>
+   </package>
+   <package>
+    <name>Imap_Client</name>
+    <channel>pear.horde.org</channel>
+   </package>
+   <package>
+    <name>Kolab_Server</name>
+    <channel>pear.horde.org</channel>
+    <min>0.2.0</min>
+   </package>
+   <extension>
+    <name>gettext</name>
+   </extension>
+   <extension>
+    <name>pam_auth</name>
+   </extension>
+   <extension>
+    <name>sasl</name>
+   </extension>
+  </optional>
+ </dependencies>
+ <phprelease>
+  <filelist>
+   <install name="lib/Horde/Auth/Signup/Datatree.php" as="Horde/Auth/Signup/Datatree.php" />
+   <install name="lib/Horde/Auth/Signup/Sql.php" as="Horde/Auth/Signup/Sql.php" />
+   <install name="lib/Horde/Auth/Application.php" as="Horde/Auth/Application.php" />
+   <install name="lib/Horde/Auth/Auto.php" as="Horde/Auth/Auto.php" />
+   <install name="lib/Horde/Auth/Composite.php" as="Horde/Auth/Composite.php" />
+   <install name="lib/Horde/Auth/Customsql.php" as="Horde/Auth/Customsql.php" />
+   <install name="lib/Horde/Auth/Cyrsql.php" as="Horde/Auth/Cyrsql.php" />
+   <install name="lib/Horde/Auth/Cyrus.php" as="Horde/Auth/Cyrus.php" />
+   <install name="lib/Horde/Auth/Driver.php" as="Horde/Auth/Driver.php" />
+   <install name="lib/Horde/Auth/Ftp.php" as="Horde/Auth/Ftp.php" />
+   <install name="lib/Horde/Auth/Http.php" as="Horde/Auth/Http.php" />
+   <install name="lib/Horde/Auth/HttpRemote.php" as="Horde/Auth/HttpRemote.php" />
+   <install name="lib/Horde/Auth/Imap.php" as="Horde/Auth/Imap.php" />
+   <install name="lib/Horde/Auth/Imsp.php" as="Horde/Auth/Imsp.php" />
+   <install name="lib/Horde/Auth/Ipbasic.php" as="Horde/Auth/Ipbasic.php" />
+   <install name="lib/Horde/Auth/Kolab.php" as="Horde/Auth/Kolab.php" />
+   <install name="lib/Horde/Auth/Krb5.php" as="Horde/Auth/Krb5.php" />
+   <install name="lib/Horde/Auth/Ldap.php" as="Horde/Auth/Ldap.php" />
+   <install name="lib/Horde/Auth/Login.php" as="Horde/Auth/Login.php" />
+   <install name="lib/Horde/Auth/Msad.php" as="Horde/Auth/Msad.php" />
+   <install name="lib/Horde/Auth/Pam.php" as="Horde/Auth/Pam.php" />
+   <install name="lib/Horde/Auth/Passwd.php" as="Horde/Auth/Passwd.php" />
+   <install name="lib/Horde/Auth/Peclsasl.php" as="Horde/Auth/Peclsasl.php" />
+   <install name="lib/Horde/Auth/Radius.php" as="Horde/Auth/Radius.php" />
+   <install name="lib/Horde/Auth/Shibboleth.php" as="Horde/Auth/Shibboleth.php" />
+   <install name="lib/Horde/Auth/Signup.php" as="Horde/Auth/Signup.php" />
+   <install name="lib/Horde/Auth/Smb.php" as="Horde/Auth/Smb.php" />
+   <install name="lib/Horde/Auth/Smbclient.php" as="Horde/Auth/Smbclient.php" />
+   <install name="lib/Horde/Auth/Sql.php" as="Horde/Auth/Sql.php" />
+   <install name="lib/Horde/Auth.php" as="Horde/Auth.php" />
+  </filelist>
+ </phprelease>
+ <changelog>
+  <release>
+   <date>2008-10-29</date>
+   <version>
+    <release>0.1.1</release>
+    <api>0.1.0</api>
+   </version>
+   <stability>
+    <release>beta</release>
+    <api>beta</api>
+   </stability>
+   <license uri="http://opensource.org/licenses/lgpl-license.php">LGPL</license>
+   <notes>* Imap driver now uses Horde_Imap_Client library.
+  * Add signup drivers to package.xml (Bug #7345).
+  * Fix the "overwriting realm info from application auth drivers" (Bug #6749)
+  * Switched Kolab auth handling from IMAP to LDAP by using Kolab_Server.
+  * Added "add user" capability to the Kolab driver.
+  * Adapted the Kolab auth driver to the changes in Kolab_Server.
+   </notes>
+  </release>
+  <release>
+   <version>
+    <release>0.1.0</release>
+    <api>0.1.0</api>
+   </version>
+   <stability>
+    <release>beta</release>
+    <api>beta</api>
+   </stability>
+   <license uri="http://opensource.org/licenses/lgpl-license.php">LGPL</license>
+   <notes>* Fixed error handling when removing user data.
+* Retrieve password where necessary for salt calculations in the custom SQL driver (Bug #3739).
+* Added a query for checking existance of a user to the custom SQL driver (Request #5151).
+* Completed Cyrus virtual domain support in the cyrsql driver.
+* Fixed handling of UID logins in the Kolab driver  (Bugs #1317, #4662).
+* Fixed case handling in the LDAP driver (Bug #2435).
+* Rewrote PAM driver to use PAM extension from PECL.
+* Fixed the PAM driver authentication so that it works with both pam and pam_auth extensions (Bug #6860).
+* Ensured that the LDAP driver always uses the configured filter.
+* Renamed Auth_sasl to Auth_peclsasl (Bug #4547).
+* Allow signup hooks to override the user_name and password fields (Request #2904).
+* Created an SQL driver for signups and allowing backends other than DataTree (Request #7161).
+* Added smbclient driver as an alternative to the smb driver which requires a PHP extension.
+* Fix handling of SSHA and SMD5 salts (ulrich-horde@topfen.net, Bug #2863).
+* Added readSessionData() function to get Horde authentication data from session strings.
+* Allow drivers to include their own isAdmin() implementations (Request #5521).
+* Add Active Directory extension of LDAP driver (Request #3769).
+* Hide the cyrus account in cyrsql driver (vilius@lnk.lt, Request #5626).
+* Bring the passwd driver into compliance with the Auth API.
+* Remove dependency on the mhash extension for some encryption types.
+* Add authentication against a remote HTTP Authentication endpoint (duck@obala.net).
+* CSRF token protections for logout links.
+* Call the postauthenticate hook in Horde_Auth::setAuth() instead of authenticate().
+* Modified the Kolab driver to use the new Kolab_Server package.
+* Improved handling of multiple IMAP server setups in the Kolab driver.
+   </notes>
+  </release>
+ </changelog>
+</package>
diff --git a/framework/Auth/test/Horde/Auth/AllTests.php b/framework/Auth/test/Horde/Auth/AllTests.php
new file mode 100644 (file)
index 0000000..6c5e091
--- /dev/null
@@ -0,0 +1,88 @@
+<?php
+/**
+ * All tests for the Horde_Auth:: package.
+ *
+ * $Horde: framework/Auth/tests/Horde/Auth/AllTests.php,v 1.2 2009/01/06 17:49:08 jan Exp $
+ *
+ * PHP version 5
+ *
+ * @category   Horde
+ * @package    Auth
+ * @subpackage UnitTests
+ * @author     Gunnar Wrobel <wrobel@pardus.de>
+ * @license    http://www.fsf.org/copyleft/lgpl.html LGPL
+ * @link       http://pear.horde.org/index.php?package=Auth
+ */
+
+/**
+ * Define the main method 
+ */
+if (!defined('PHPUnit_MAIN_METHOD')) {
+    define('PHPUnit_MAIN_METHOD', 'Horde_Auth_AllTests::main');
+}
+
+require_once 'PHPUnit/Framework/TestSuite.php';
+require_once 'PHPUnit/TextUI/TestRunner.php';
+
+/**
+ * Combine the tests for this package.
+ *
+ * $Horde: framework/Auth/tests/Horde/Auth/AllTests.php,v 1.2 2009/01/06 17:49:08 jan Exp $
+ *
+ * Copyright 2007-2009 The Horde Project (http://www.horde.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.
+ *
+ * @category   Horde
+ * @package    Auth
+ * @subpackage UnitTests
+ * @author     Gunnar Wrobel <wrobel@pardus.de>
+ * @license    http://www.fsf.org/copyleft/lgpl.html LGPL
+ * @link       http://pear.horde.org/index.php?package=Auth
+ */
+class Horde_Auth_AllTests
+{
+
+    /**
+     * Main entry point for running the suite.
+     *
+     * @return NULL
+     */
+    public static function main()
+    {
+        PHPUnit_TextUI_TestRunner::run(self::suite());
+    }
+
+    /**
+     * Collect the unit tests of this directory into a new suite.
+     *
+     * @return PHPUnit_Framework_TestSuite The test suite.
+     */
+    public static function suite()
+    {
+        $suite = new PHPUnit_Framework_TestSuite('Horde Framework - Auth');
+
+        $basedir    = dirname(__FILE__);
+        $baseregexp = preg_quote($basedir . DIRECTORY_SEPARATOR, '/');
+
+        foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($basedir)) as $file) {
+            if ($file->isFile() && preg_match('/Test.php$/', $file->getFilename())) {
+                $pathname = $file->getPathname();
+                require $pathname;
+
+                $class = str_replace(DIRECTORY_SEPARATOR, '_',
+                                     preg_replace("/^$baseregexp(.*)\.php/",
+                                                  '\\1', $pathname));
+                $suite->addTestSuite('Horde_Auth_' . $class);
+            }
+        }
+
+        return $suite;
+    }
+
+}
+
+if (PHPUnit_MAIN_METHOD == 'Horde_Auth_AllTests::main') {
+    Horde_Auth_AllTests::main();
+}
diff --git a/framework/Auth/test/Horde/Auth/KolabScenarioTest.php b/framework/Auth/test/Horde/Auth/KolabScenarioTest.php
new file mode 100644 (file)
index 0000000..7edae2c
--- /dev/null
@@ -0,0 +1,57 @@
+<?php
+/**
+ * Kolab authentication scenarios.
+ *
+ * $Horde: framework/Auth/tests/Horde/Auth/KolabScenarioTest.php,v 1.3 2009/03/20 23:38:13 wrobel Exp $
+ *
+ * PHP version 5
+ *
+ * @category   Horde
+ * @package    Auth
+ * @subpackage UnitTests
+ * @author     Gunnar Wrobel <wrobel@pardus.de>
+ * @license    http://www.fsf.org/copyleft/lgpl.html LGPL
+ * @link       http://pear.horde.org/index.php?package=Auth
+ */
+
+/**
+ *  We need the base class
+ */
+require_once 'Horde/Kolab/Test/Storage.php';
+
+/**
+ * Kolab authentication scenarios.
+ *
+ * $Horde: framework/Auth/tests/Horde/Auth/KolabScenarioTest.php,v 1.3 2009/03/20 23:38:13 wrobel Exp $
+ *
+ * Copyright 2008-2009 The Horde Project (http://www.horde.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.
+ *
+ * @category Kolab
+ * @package  Kolab_Server
+ * @author   Gunnar Wrobel <wrobel@pardus.de>
+ * @license  http://www.fsf.org/copyleft/lgpl.html LGPL
+ * @link     http://pear.horde.org/index.php?package=Kolab_Server
+ */
+class Horde_Auth_KolabScenarioTest extends Horde_Kolab_Test_Storage
+{
+    /**
+     * Test loggin in after a user has been added.
+     *
+     * @scenario
+     *
+     * @return NULL
+     */
+    public function login()
+    {
+        $test_user = $this->provideBasicUserOne();
+
+        $this->given('a populated Kolab setup')
+            ->and('the Kolab auth driver has been selected')
+            ->when('logging in as a user with a password', $test_user['mail'],
+                  $test_user['userPassword'])
+            ->then('the login was successful');
+    }
+}
\ No newline at end of file
diff --git a/framework/Auth/test/Horde/Auth/KolabTest.php b/framework/Auth/test/Horde/Auth/KolabTest.php
new file mode 100644 (file)
index 0000000..596ec34
--- /dev/null
@@ -0,0 +1,92 @@
+<?php
+/**
+ * Kolab authentication tests.
+ *
+ * $Horde: framework/Auth/tests/Horde/Auth/KolabTest.php,v 1.4 2009/04/01 07:59:47 wrobel Exp $
+ *
+ * PHP version 5
+ *
+ * @category   Horde
+ * @package    Auth
+ * @subpackage UnitTests
+ * @author     Gunnar Wrobel <wrobel@pardus.de>
+ * @license    http://www.fsf.org/copyleft/lgpl.html LGPL
+ * @link       http://pear.horde.org/index.php?package=Auth
+ */
+
+/**
+ *  We need the base class
+ */
+require_once 'Horde/Kolab/Test/Storage.php';
+
+/**
+ * Kolab authentication tests.
+ *
+ * $Horde: framework/Auth/tests/Horde/Auth/KolabTest.php,v 1.4 2009/04/01 07:59:47 wrobel Exp $
+ *
+ * Copyright 2008-2009 The Horde Project (http://www.horde.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.
+ *
+ * @category Kolab
+ * @package  Kolab_Server
+ * @author   Gunnar Wrobel <wrobel@pardus.de>
+ * @license  http://www.fsf.org/copyleft/lgpl.html LGPL
+ * @link     http://pear.horde.org/index.php?package=Kolab_Server
+ */
+class Horde_Auth_KolabTest extends Horde_Kolab_Test_Storage
+{
+    /**
+     * Test loggin in after a user has been added.
+     *
+     * @return NULL
+     */
+    public function testLogin()
+    {
+        /** Create the test base */
+        $world = &$this->prepareBasicSetup();
+        $server = $world['server'];
+        $auth = $world['auth'];
+
+        /** Ensure we always use the test server */
+        $GLOBALS['conf']['kolab']['server']['driver'] = 'test';
+
+        $uid = $server->uidForIdOrMail('wrobel@example.org');
+        $this->assertEquals('cn=Gunnar Wrobel,dc=example,dc=org', $uid);
+
+        $result = $auth->authenticate('wrobel@example.org',
+                                      array('password' => 'none'));
+        $this->assertNoError($result);
+        $this->assertTrue($result);
+
+        $session = Horde_Kolab_Session::singleton();
+        $this->assertNoError($session->user_mail);
+        $this->assertEquals('wrobel@example.org', $session->user_mail);
+
+        $result = $auth->authenticate('wrobel@example.org',
+                                      array('password' => 'invalid'));
+        $this->assertFalse($result);
+
+        /** Ensure we don't use a connection from older tests */
+        $server->unbind();
+
+        $result = $auth->authenticate('wrobel',
+                                      array('password' => 'invalid'));
+        $this->assertNoError($result);
+        $this->assertFalse($result);
+
+        /** Ensure we don't use a connection from older tests */
+        $server->unbind();
+        $result = $auth->authenticate('wrobel',
+                                      array('password' => 'none'));
+        $this->assertNoError($result);
+        $this->assertTrue($result);
+
+        $session = Horde_Kolab_Session::singleton();
+        $this->assertNoError($session->user_mail);
+        $this->assertEquals('wrobel@example.org', $session->user_mail);
+
+        $this->assertEquals('wrobel@example.org', Horde_Auth::getAuth());
+    }
+}
\ No newline at end of file
diff --git a/framework/Auth/test/Horde/Auth/credentials.php b/framework/Auth/test/Horde/Auth/credentials.php
new file mode 100644 (file)
index 0000000..a044190
Binary files /dev/null and b/framework/Auth/test/Horde/Auth/credentials.php differ
diff --git a/framework/Auth/test/Horde/Auth/getCryptedPassword.phpt b/framework/Auth/test/Horde/Auth/getCryptedPassword.phpt
new file mode 100644 (file)
index 0000000..2aa5924
Binary files /dev/null and b/framework/Auth/test/Horde/Auth/getCryptedPassword.phpt differ
diff --git a/framework/Auth/test/Horde/Auth/getSalt.phpt b/framework/Auth/test/Horde/Auth/getSalt.phpt
new file mode 100644 (file)
index 0000000..4bc6e27
Binary files /dev/null and b/framework/Auth/test/Horde/Auth/getSalt.phpt differ
diff --git a/framework/Auth/test/Horde/Auth/passwd.phpt b/framework/Auth/test/Horde/Auth/passwd.phpt
new file mode 100644 (file)
index 0000000..92c882b
--- /dev/null
@@ -0,0 +1,24 @@
+--TEST--
+Horde_Auth_Passwd:: test
+--FILE--
+<?php
+
+require_once dirname(__FILE__) . '/../../../lib/Horde/Auth.php';
+require_once dirname(__FILE__) . '/../../../lib/Horde/Auth/Driver.php';
+require_once dirname(__FILE__) . '/../../../lib/Horde/Auth/Passwd.php';
+
+$auth = Horde_Auth::factory('passwd', array('filename' => dirname(__FILE__) . '/test.passwd'));
+
+// List users
+var_dump($auth->listUsers());
+
+// Authenticate
+var_dump($auth->authenticate('user', array('password' => 'password'), false));
+
+?>
+--EXPECT--
+array(1) {
+  [0]=>
+  string(4) "user"
+}
+bool(true)
diff --git a/framework/Auth/test/Horde/Auth/test.passwd b/framework/Auth/test/Horde/Auth/test.passwd
new file mode 100644 (file)
index 0000000..0ff58ae
--- /dev/null
@@ -0,0 +1 @@
+user:3U6GxZSGmKPGA
index 90ed3ef..87c858b 100644 (file)
@@ -127,7 +127,7 @@ class Horde_History
         $history = &$this->getHistory($guid);
 
         if (!isset($attributes['who'])) {
-            $attributes['who'] = Auth::getAuth();
+            $attributes['who'] = Horde_Auth::getAuth();
         }
         if (!isset($attributes['ts'])) {
             $attributes['ts'] = time();
index 2bd1724..358c2c3 100644 (file)
@@ -395,7 +395,7 @@ class Horde_Kolab_Test_Server extends PHPUnit_Extensions_Story_TestCase
     {
         include_once 'Horde/Auth.php';
 
-        $auth = Auth::singleton('kolab');
+        $auth = Horde_Auth::singleton('kolab');
         return $auth;
     }
 
index 719b583..181ccbc 100644 (file)
@@ -110,7 +110,7 @@ class Horde_Kolab_Session
         global $conf;
 
         if (empty($user)) {
-            $user = Auth::getAuth();
+            $user = Horde_Auth::getAuth();
             if (empty($user)) {
                 $user = 'anonymous';
             } else if (!strpos($user, '@')) {
@@ -240,13 +240,13 @@ class Horde_Kolab_Session
         $params = array();
         if ($this->user_uid) {
             $params['uid']  = $this->user_uid;
-            $params['pass'] = Auth::getCredential('password');
+            $params['pass'] = Horde_Auth::getCredential('password');
         } else if (isset($user)) {
             $params['user'] = $user;
             if (isset($credentials['password'])) {
                 $params['pass'] = $credentials['password'];
             } else {
-                $params['pass'] = Auth::getCredential('password');
+                $params['pass'] = Horde_Auth::getCredential('password');
             }
         }
         return Horde_Kolab_Server::singleton($params);
@@ -285,8 +285,8 @@ class Horde_Kolab_Session
                 return $imap;
             }
 
-            $result = $imap->connect(Auth::getAuth(),
-                                     Auth::getCredential('password'));
+            $result = $imap->connect(Horde_Auth::getAuth(),
+                                     Horde_Auth::getCredential('password'));
             if (is_a($result, 'PEAR_Error')) {
                 return $result;
             }
@@ -331,7 +331,7 @@ class Horde_Kolab_Session
         }
 
         if (empty($user)) {
-            $user = Auth::getAuth();
+            $user = Horde_Auth::getAuth();
         }
 
         if ($destruct || empty($session)
index 46fb237..4d084b8 100644 (file)
@@ -130,7 +130,7 @@ class Horde_LoginTasks
         /* If this application handles Horde auth, need to add Horde tasks
          * here. */
         $app_list = array($this->_app);
-        if (strnatcasecmp($this->_app, Auth::getProvider()) === 0) {
+        if (strnatcasecmp($this->_app, Horde_Auth::getProvider()) === 0) {
             array_unshift($app_list, 'horde');
         }
 
@@ -236,7 +236,7 @@ class Horde_LoginTasks
         if (empty($need_display)) {
             $lasttasks = unserialize($GLOBALS['prefs']->getValue('last_logintasks'));
             $lasttasks[$this->_app] = time();
-            if (strnatcasecmp($this->_app, Auth::getProvider()) === 0) {
+            if (strnatcasecmp($this->_app, Horde_Auth::getProvider()) === 0) {
                 $lasttasks['horde'] = time();
             }
             $GLOBALS['prefs']->setValue('last_logintasks', serialize($lasttasks));
index d97ccd6..a45e50c 100644 (file)
@@ -205,7 +205,7 @@ class Horde_Notification
         $options['listeners'] = array_map('strtolower', $options['listeners']);
 
         if ($this->_alarm && in_array('status', $options['listeners'])) {
-            $this->_alarm->notify(Auth::getAuth());
+            $this->_alarm->notify(Horde_Auth::getAuth());
         }
 
         foreach ($options['listeners'] as $listener) {
index fe2ddda..587f517 100644 (file)
@@ -13,8 +13,8 @@
  *                                'contacts.search',
  *                                array(array('jan'), array('localsql'),
  *                                      array('name', 'email')),
- *                                array('user' => Auth::getAuth(),
- *                                      'pass' => Auth::getCredential('password')));
+ *                                array('user' => Horde_Auth::getAuth(),
+ *                                      'pass' => Horde_Auth::getCredential('password')));
  * </code>
  *
  * Copyright 2002-2009 The Horde Project (http://www.horde.org/)
@@ -86,7 +86,7 @@ class Horde_Rpc
             return true;
         }
 
-        $auth = &Auth::singleton($GLOBALS['conf']['auth']['driver']);
+        $auth = Horde_Auth::singleton($GLOBALS['conf']['auth']['driver']);
 
         if (isset($_SERVER['PHP_AUTH_USER'])) {
             $user = $_SERVER['PHP_AUTH_USER'];
index 666e1fc..56a6c4c 100644 (file)
@@ -100,8 +100,7 @@ class Horde_Rpc_Phpgw extends Horde_Rpc
         }
 
         // Be authenticated or call system.login.
-        $auth = &Auth::singleton($GLOBALS['conf']['auth']['driver']);
-        $authenticated = $auth->isAuthenticated() || $method== "phpgw/system/login";
+        $authenticated = Horde_Auth::isAuthenticated() || $method== "phpgw/system/login";
 
         if ($authenticated) {
             Horde::logMessage("rpc call $method allowed", __FILE__, __LINE__, PEAR_LOG_NOTICE);
index e9278d6..1d8733a 100644 (file)
@@ -92,7 +92,7 @@ class Horde_Rpc_Soap extends Horde_Rpc
                     $GLOBALS['__horde_rpc_PhpSoap']['lastMethodCalled'],
                     implode(', ', array_map(create_function('$a', 'return is_array($a) ? "Array" : $a;'),
                                             $GLOBALS['__horde_rpc_PhpSoap']['lastMethodParams'])),
-                    Auth::getAuth(),
+                    Horde_Auth::getAuth(),
                     time() - $beginTime,
                     ob_get_length()),
             __FILE__, __LINE__, PEAR_LOG_INFO
index 36f7862..7b5dff5 100644 (file)
@@ -738,7 +738,7 @@ Horde::logMessage(print_r($list, true), __FILE__, __LINE__, PEAR_LOG_ERR);
             $locktype = HORDE_LOCK_TYPE_EXCLUSIVE;
         }
 
-        $lockid = $locks->setLock(Auth::getAuth(), 'webdav', $params['path'],
+        $lockid = $locks->setLock(Horde_Auth::getAuth(), 'webdav', $params['path'],
                                   $timeout, $locktype);
 
         if (is_a($lockid, 'PEAR_Error')) {
@@ -750,7 +750,7 @@ Horde::logMessage(print_r($list, true), __FILE__, __LINE__, PEAR_LOG_ERR);
         }
 
         $params['locktoken'] = $lockid;
-        $params['owner'] = Auth::getAuth();
+        $params['owner'] = Horde_Auth::getAuth();
         $params['timeout'] = $timeout;
 
         return "200";
@@ -849,7 +849,7 @@ Horde::logMessage(print_r($list, true), __FILE__, __LINE__, PEAR_LOG_ERR);
      */
     function check_auth($type, $username, $password)
     {
-        $auth = &Auth::singleton($GLOBALS['conf']['auth']['driver']);
+        $auth = Horde_Auth::singleton($GLOBALS['conf']['auth']['driver']);
         return $auth->authenticate($username, array('password' => $password));
     }
 
index 4856748..4a4c6ee 100644 (file)
@@ -332,7 +332,7 @@ class Horde_SessionHandler
      * Returns a list of authenticated users and data about their session.
      *
      * @return array  For authenticated users, the sessionid as a key and the
-     *                information returned from Auth::readSessionData() as
+     *                information returned from Horde_Auth::readSessionData() as
      *                values.
      * @throws Horde_Exception
      */
@@ -348,7 +348,7 @@ class Horde_SessionHandler
             } catch (Horde_Exception $e) {
                 continue;
             }
-            $data = Auth::readSessionData($data, true);
+            $data = Horde_Auth::readSessionData($data, true);
             if ($data !== false) {
                 $info[$id] = $data;
             }