<?php
/**
 * $Horde: passwd/lib/Driver/ldap.php,v 1.22 2003/06/26 02:03:44 mdjukic Exp $
 *
 * The ldap class attempts to change a user's password stored in an ldap
 * directory service.
 *
 * @author   Mike Cochrane <mike@graftonhall.co.nz>
 * @author   Tjeerd van der Zee <admin@xar.nl>
 * @author   Mattias Webjörn Eriksson <mattias@webjorn.org>
 * @author   Eric Jon Rostetter <eric.rostetter@physics.utexas.edu>
 * @version $Revision: 1.22 $
 * @package passwd
 */
class Passwd_Driver_ldap extends Passwd_Driver {

    /** Pointer to the ldap connection. */
    var $_ds;

    /** Hash containing connection parameters. */
    var $_params;

    /**
     * Constructs a new ldap Passwd_Driver object.
     *
     * @param array  $params    A hash containing connection parameters.
     */
    function Passwd_Driver_ldap($params = array())
    {
        $this->_params['host']       = array_key_exists('host', $params)       ? $params['host'] : 'localhost';
        $this->_params['port']       = array_key_exists('port', $params)       ? $params['port'] : 389;
        $this->_params['encryption'] = array_key_exists('encryption', $params) ? $params['encryption'] : 'crypt';
        $this->_params['uid']        = array_key_exists('uid', $params)        ? $params['uid'] : 'uid';
        $this->_params['basedn']     = array_key_exists('basedn', $params)     ? $params['basedn'] : '';
        $this->_params['realm']      = array_key_exists('realm', $params)      ? $params['realm'] : '';
        $this->_params['tls']        = array_key_exists('tls', $params)        ? $params['tls'] : null;
    }

    /**
     * Do an ldap connect and bind as the guest user or as the optional userdn.
     *
     * @param    $userdn      The optional dn to use when binding non-anonymously.
     * @param    $oldpassword The optional password for $userdn.
     *
     * @return   boolean   True or False based on success of connect and bind.
     *
     */
    function _connect($userdn = null, $password = null) {
        $this->_ds = ldap_connect($this->_params['host'], $this->_params['port']);
        if (!$this->_ds) {
           return PEAR::raiseError(_("Could not connect to ldap server"));
        }

        if (ldap_set_option($this->_ds, LDAP_OPT_PROTOCOL_VERSION, 3) &&
            $this->_params['tls']) {
            if (!ldap_start_tls($this->_ds)) {
               PEAR::raiseError(_("ldap_start_tls failed"));
            }
        }

        if (!is_null($userdn)) {
            $result = @ldap_bind($this->_ds, $userdn, $password);
        } else {
            $result = @ldap_bind($this->_ds);
        }

        if (!$result) {
          return PEAR::raiseError(_("Could not bind to ldap server"));
        }

        return true;
    }

    /**
     * Close the ldap connection.
     */
    function _disconnect() {
        @ldap_close($this->_ds);
    }

    /**
     * Lookup and return the user's dn.
     *
     * @param  $user     The username of the user.
     * @param  $realm    The realm (domain) name of the user.
     * @param  $basedn   The ldap basedn.
     * @param  $uid      The ldap uid.
     *
     * @return string    The ldap dn for the user.
     */
    function _lookupdn($user) {
        // bind as guest
        $this->_connect();

        // construct search
        $search = $this->_params['uid'] . '=' . $user;
        if (!empty($this->_params['realm'])) {
            $search .= '@' . $this->_params['realm'];
        }

        // get userdn
        $result = ldap_search($this->_ds, $this->_params['basedn'], $search);
        $entry = ldap_first_entry($this->_ds, $result);
        if ($entry === false) {
            $this->_disconnect();
            return PEAR::raiseError(_("User not found."));
        }
        $userdn = ldap_get_dn($this->_ds, $entry);

        // disconnect from ldap server
        $this->_disconnect();

        return $userdn;
    }

    /**
     * Change the user's password.
     *
     * @param   $username      The user for which to change the password.
     * @param   $old_password  The old (current) user password.
     * @param   $new_password  The new user password to set.
     *
     * @return  boolean        True or false based on success of the change.
     */
    function changePassword($username, $old_password, $new_password) {
        // get the user's dn
        global $conf;

        if ($conf['hooks']['userdn']) {
            $userdn = Horde::callHook('_passwd_hook_userdn', array(Auth::getAuth()));
        } else {
            $userdn = $this->_lookupdn($username);
            if (is_a($userdn, 'PEAR_Error')) {
                return $userdn;
            }
        }

        // connect as the user
        $res = $this->_connect($userdn, $old_password);
        if (is_a($res, 'PEAR_Error')) {
            $this->_disconnect();
            if ($res->getMessage() == _("Could not bind to ldap server")) {
                return PEAR::raiseError(_("Incorrect Password"));
            }
            return $res;
        }

        // change the user's password
        $new_details['userPassword'] = $this->encryptPassword($new_password);
        $res = ldap_mod_replace($this->_ds, $userdn, $new_details);
        if (!$res) {
            $res = PEAR::raiseError(ldap_error($this->_ds));
        } else {
            $this->resetCredentials($username, $old_password, $new_password);
        }

        // disconnect from ldap server
        $this->_disconnect();

        return $res;
    }
    
}
