diff --git a/framework/Auth/lib/Horde/Auth/Otp.php b/framework/Auth/lib/Horde/Auth/Otp.php
new file mode 100644
index 0000000..48c4340
--- /dev/null
+++ b/framework/Auth/lib/Horde/Auth/Otp.php
@@ -0,0 +1,272 @@
+<?php
+/**
+ * The Horde_Auth_Otp class provides a Hash-Chain implementation of the Horde
+ * authentication system.
+ *
+ * The table structure for the Auth system is in
+ * horde/scripts/sql/horde_users.sql.
+ *
+ * Copyright 1999-2012 Horde LLC (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you did
+ * not receive this file, http://www.horde.org/licenses/lgpl21
+ *
+ * @author  Alexandra Simon <alexandra.simon@hotmail.com>
+ * @author  Carl Denis <c.denis@mrduck.fr>
+ * @category Horde
+ * @license http://www.horde.org/licenses/lgpl21 LGPL-2.1
+ * @package  Auth
+ */
+class Horde_Auth_Otp extends Horde_Auth_Base
+{
+    /**
+     * An array of capabilities, so that the driver can report which
+     * operations it supports and which it doesn't.
+     *
+     * @var array
+     */
+    protected $_capabilities = array(
+        'authenticate'  => true,
+    );
+
+    /**
+     * Handle for the current database connection.
+     *
+     * @var Horde_Db_Adapter
+     */
+    protected $_db;
+
+    /**
+     * Constructor
+     *
+     * @param array $params  Parameters:
+     * 'db' - (Horde_Db_Adapter) [REQUIRED] Database object.
+     * <pre>
+     * 'encryption' - (string) The encryption to use to store the password in
+     *                the table (e.g. 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: 'hash_chain_pwd'
+     * 'index_field' - (string) The name of the field containing the count of
+     *                 left one time passwords in the current hash chain.
+     *                 DEFAULT: 'hash_chain_index'
+     * '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>
+     *
+     * @throws InvalidArgumentException
+     */
+    public function __construct(array $params = array())
+    {
+        if (!isset($params['db'])) {
+            throw new InvalidArgumentException('Missing db parameter.');
+        }
+
+        if (isset($params['encryption']) && $params['encryption'] == 'plain')
+        {
+            throw new InvalidArgumentException('Plain encryption cannot be used for hash-chain one time password.');
+        }
+
+        $this->_db = $params['db'];
+        unset($params['db']);
+
+        $params = array_merge(array(
+            'encryption' => 'md5-hex',
+            'password_field' => 'hash_chain_pwd',
+	        'index_field' => 'hash_chain_index',
+            'show_encryption' => false,
+            'table' => 'horde_users',
+            'username_field' => 'user_uid',
+            'soft_expiration_field' => null,
+            'soft_expiration_window' => null,
+            'hard_expiration_field' => null,
+            'hard_expiration_window' => null
+        ), $params);
+
+        parent::__construct($params);
+
+        /* If enabled, uses limits defined by the SQL Authentication driver,
+         * as it uses the same table in the database
+         */
+        if ((empty($params['soft_expiration_field'])) &&
+            ($params['soft_expiration_window'] > 0)) {
+            throw new InvalidArgumentException('You cannot set [soft_expiration_window] without [soft_expiration_field].');
+        }
+
+        if (($params['hard_expiration_field'] == '') &&
+            ($params['hard_expiration_window'] > 0)) {
+            throw new InvalidArgumentException('You cannot set [hard_expiration_window] without [hard_expiration_field].');
+        }
+
+    }
+
+    /**
+     * Find out if a set of login credentials are valid.
+     *
+     * @param string $userId      The userId to check.
+     * @param array $credentials  The credentials to use.
+     *
+     * @throws Horde_Auth_Exception
+     */
+    protected function _authenticate($userId, $credentials)
+    {
+        /* Build the SQL query. */
+        $query = sprintf('SELECT * FROM %s WHERE %s = ?',
+                         $this->_params['table'],
+                         $this->_params['username_field']);
+        $values = array($userId);
+
+        try {
+            $row = $this->_db->selectOne($query, $values);
+        } catch (Horde_Db_Exception $e) {
+            throw new Horde_Auth_Exception('', Horde_Auth::REASON_FAILED);
+        }
+
+        if ((!$row) ||
+            (!$this->_comparePasswords($row[$this->_params['password_field']], $credentials['password'])) ||
+            (!$row[$this->_params['index_field']] > 0) ) {
+            throw new Horde_Auth_Exception('', Horde_Auth::REASON_BADLOGIN);
+        }
+
+        $this->setNextPassword($userId, $credentials['password'], $row[$this->_params['index_field']]-1 );
+
+    	$now = time();
+        if (!empty($this->_params['hard_expiration_field']) &&
+            !empty($row[$this->_params['hard_expiration_field']]) &&
+            ($now > $row[$this->_params['hard_expiration_field']])) {
+            throw new Horde_Auth_Exception('', Horde_Auth::REASON_EXPIRED);
+        }
+
+        if (!empty($this->_params['soft_expiration_field']) &&
+            !empty($row[$this->_params['soft_expiration_field']]) &&
+            ($now > $row[$this->_params['soft_expiration_field']])) {
+            $this->setCredential('change', true);
+            $this->setCredential('expire', $date);
+        }
+    }
+
+
+    /**
+     * Set the next password in the hashchain.
+     * Reduce the counter of left passwords.
+     *
+     * @param string $userID      The user ID.
+     * @param array $credentials  The new password and next index value.
+     *
+     * @throws Horde_Auth_Exception
+     */
+    public function setNextPassword($userID, $password, $index)
+    {
+        $query = sprintf('UPDATE %s SET ', $this->_params['table']);
+        $values = array();
+
+        /* Build the SQL query. */
+        $query .= $this->_params['password_field'] . ' = ?';
+    	$values[] = $password;
+
+	    $query .= ', ' . $this->_params['index_field'] . ' = ?';
+	    $values[]= $index;
+
+        $query .= sprintf(' WHERE %s = ?', $this->_params['username_field']);
+        $values[] = $userID;
+        try {
+            $this->_db->update($query, $values);
+        } catch (Horde_Db_Exception $e) {
+            throw new Horde_Auth_Exception($e);
+        }
+    }
+
+    /**
+     * Checks if a userId exists in the system.
+     *
+     * @return boolean  Whether or not the userId already exists.
+     */
+    public function exists($userId)
+    {
+        /* Build the SQL query. */
+        $query = sprintf('SELECT 1 FROM %s WHERE %s = ?',
+                         $this->_params['table'],
+                         $this->_params['username_field']);
+        $values = array($userId);
+
+        try {
+            return (bool)$this->_db->selectValue($query, $values);
+        } catch (Horde_Db_Exception $e) {
+            return false;
+        }
+    }
+
+    /**
+     * 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 == $this->_reduce_password(Horde_Auth::getCryptedPassword($plaintext,
+                                                       $encrypted,
+                                                       $this->_params['encryption'],
+                                                       $this->_params['show_encryption']));
+    }
+
+    /**
+     * Calculate a timestamp and return it along with the field name
+     *
+     * @param string $type The timestamp parameter.
+     *
+     * @return integer 'timestamp' intended field value or null
+     */
+    private function _calc_expiration($type)
+    {
+        if (empty($this->_params[$type . '_expiration_window'])) {
+            return null;
+        } else {
+            $now = new Horde_Date(time());
+            return $now->add(array('mday' => $this->_params[$type.'_expiration_window']))->timestamp();
+        }
+    }
+
+    /**
+     * Transform a password hash in a Form, that can easily be entered
+     * by either reducing the length or using another encoding alphabet
+     *
+     * @param string $hash      The password to make userfriendly
+     * @param array  $outformat The format to set the password to
+     *                          DEFAULT: none
+     *
+     * @return string   The modified hash to fit a specific format
+     */
+    private function _reduce_password($hash)
+    {
+        switch ($this->_params['pwdformat']) {
+            // other cases need to be added to reduce length
+            // e.g. HEX to [a-zA-Z0-9] or just truncate
+        case 'truncate':
+            $len = isset($this->_params['pwdformat_length']) ? $this->_params['pwdformat_length'] : 8;
+            return substr($hash,0,$len);
+        default:
+            return $hash;
+        }
+    }
+}
diff --git a/framework/Core/lib/Horde/Core/Factory/Auth.php b/framework/Core/lib/Horde/Core/Factory/Auth.php
index 1d412fc..bef7100 100644
--- a/framework/Core/lib/Horde/Core/Factory/Auth.php
+++ b/framework/Core/lib/Horde/Core/Factory/Auth.php
@@ -183,6 +183,18 @@ class Horde_Core_Factory_Auth extends Horde_Core_Factory_Base
                     ->create('horde', is_null($orig_params) ? 'auth' : $orig_params);
             }
             break;
+	case 'otp':
+	    if (!empty($params['driverconfig']) &&
+                $params['driverconfig'] == 'horde') {
+                $params['db'] = $this->_injector
+                    ->getInstance('Horde_Db_Adapter');
+            } else {
+                $params['db'] = $this->_injector
+                    ->getInstance('Horde_Core_Factory_Db')
+                    ->create('horde', is_null($orig_params) ? 'auth' : $orig_params);
+            }
+            break;
+
         }
 
         $params['default_user'] = $GLOBALS['registry']->getAuth();