adding ldap backend support to change_password plugin. will contact
authortokul <tokul@7612ce4b-ef26-0410-bec9-ea0150e637f0>
Sun, 27 Feb 2005 16:40:51 +0000 (16:40 +0000)
committertokul <tokul@7612ce4b-ef26-0410-bec9-ea0150e637f0>
Sun, 27 Feb 2005 16:40:51 +0000 (16:40 +0000)
change_ldappass plugin developer for review.

git-svn-id: https://svn.code.sf.net/p/squirrelmail/code/trunk/squirrelmail@8899 7612ce4b-ef26-0410-bec9-ea0150e637f0

plugins/change_password/README
plugins/change_password/backend/ldap.php [new file with mode: 0644]

index e1a683c..bbbe86b 100644 (file)
@@ -15,6 +15,16 @@ Probably, you need to set some config vars in the backend too
 (backend/<yourbackend>.php).
 
 BACKENDS
+- ldap
+
+  Default settings are supplied in backends/ldap.php.
+
+  You don't have to change any configuration vars in
+  backend/ldap.php - instead, create an $cpw_ldap array in 
+  config.php containing the variable you want to override.
+
+  See more information in "About ldap backend" chapter.
+
 - mysql
 
   Default settings are supplied in backends/mysql.php.
@@ -56,10 +66,115 @@ BACKENDS
 
   $vmailmgrd['vmail_inc_path'] setting is required.
 
+
 AUTHORS:
+ldap backend      - Tomas Kuliavas <tokul@users.sourceforge.net>
+                    used code from phpldapadmin and squirrelmail
+                    ldapquery plugin.
 merak backend     - Edwin van Elk <Edwin@eve-software.com>
 mysql backend     - Thijs Kinkhorst <kink@squirrelmail.org>
 poppassd backend  - Seth Randall <sethr@missoulafcu.org>
 vmailmgrd backend - Tomas Kuliavas <tokul@users.sourceforge.net>
 
+------------------
+ABOUT LDAP BACKEND
+------------------
+  List of supported overrides
+  * 'server'
+    overrides address of ldap server. use any syntax that is supported 
+    by your php ldap extension. Defaults to address of imap server.
+
+  * 'port'
+    overrides port of ldap server. Defaults to 389.
+
+  * 'basedn'
+    (required) ldap basedn used for binding to ldap server. Empty 
+    string blocks use of backend. Defaults to empty string.
+
+  * 'connect_opts'
+    override controls LDAP_OPT_* settings that are set with 
+    ldap_set_option() function. If you want to set specific ldap option 
+    that is not listed as LDAP_OPT_* constant, define own LDAP_OPT_* 
+    constant in config. LDAP_OPT_ prefix must be omitted in 
+    $cpw_ldap['connect_opts'] overrides. No connection options are 
+    enabled by default.
+
+  * 'use_tls'
+    enables or disables use of tls in ldap connection. Requires php 
+    4.2+, php ldap extension with ssl support and PROTOCOL_VERSION => 3 
+    setting in $cpw_ldap_connect_opts. Does not enable tls by default.
+
+  * 'binddn'
+    unprivileged binddn. should be able to search ldap directory and 
+    find DN used by user. Uses anonymous bind, if set to empty string.
+    You should not use DN with write access to ldap directory here.
+    Defaults to anonymous bind.
+
+  * 'bindpw'
+    password used for unprivileged bind
+
+  * 'admindn'
+    bind DN that should be able to change password.
+    WARNING: usually user has enough privileges to change own password.
+    If you leave default value, plugin will try to connect with dn that 
+    is detected in $cpw_ldap_username_attr=$username search and current
+    user password will be used for authentication.
+
+  * 'adminpw'
+    password for binding with 'admindn'
+
+  * 'userid_attr'
+    ldap attribute that stores username. Defaults to 'uid'
+
+  * 'default_crypto'
+    crypto that is used to encode new password. If set to empty string, 
+    system tries to keep same encoding/hashing algorithm. Currently 
+    backend supports:
+    - md5 - used name 'md5'. Implemented in standard php functions.
+    - smd5 - used name 'smd5'. Implemented in php mhash extension functions. 
+      Minimal php version 4.0.4.
+    - sha - used name 'sha'. Implemented in php mhash extension functions.
+    - ssha - used name 'ssha'. Implemeted in php mhash extension functions.
+      Minimal php version 4.0.4.
+    - md5 crypt - used name 'md5crypt'. Uses php crypt function. Depends on
+      md5 support in system crypt libraries. Should work on linux glibc2 systems
+      and openbsd.
+    - blowfish crypt - used name 'blowfish' Uses php crypt function. Depends on
+      blowfish support in system crypt libraries. Should work on openbsd. Is not
+      supported by glibc 2.3.3.
+    - extended des crypt - used name 'extcrypt'. Uses php crypt function. Depends on
+      extended des support in system crypt libraries. Should work on openbsd.
+      Is not supported by glibc 2.3.3,
+    - standard des crypt - used name 'crypt'. Uses php crypt function. Depends on
+      standard des support in system crypt libraries. Should work on libc systems
+      and openbsd.
+    - plain text passwords - used name 'plaintext'
+
+    If you use admindn, plugin should support all encryption/hashing 
+    algorithms used in your ldap server.
+
+    WARNINGS:
+    * don't enforce any crypto that is not supported by ldap server.
+    * don't enforce extcrypt, md5crypt or blowfish, if they are not supported 
+      by ldap server and web server crypt libraries.
+
+    Safest setting options:
+    * If web server and ldap server is on same OS, make sure that mhash 
+      extension is present in php.
+    * If web server and ldap server is on same OS and mhash extension is 
+      not present, enforce md5 passwords or any crypt password algorithm 
+      supported by your os. Remember that standard des crypt is limited
+      to eight symbols.  Don't use admindn override, if ldap server 
+      supports sha, ssha or smd5.
+    * If crypt libraries differ on web server and ldap server -
+      enforce md5 passwords or any crypt password algorithm supported by
+      web server and ldap server. Don't use admindn override, if ldap 
+      server supports sha, ssha or smd5 and mhash extension is not 
+      present.
+
+  Example:
+  $cpw_ldap['base_dn']='ou=users,dc=example,dc=com'; // sets base dn
+  $cpw_ldap['connect_opts']['PROTOCOL_VERSION']=3;   // forces v3 bind protocol
+
+
 $Id$
diff --git a/plugins/change_password/backend/ldap.php b/plugins/change_password/backend/ldap.php
new file mode 100644 (file)
index 0000000..b4562c6
--- /dev/null
@@ -0,0 +1,693 @@
+<?php
+/**
+ * Change password ldap backend
+ *
+ * @version $Id$
+ * @package plugins
+ * @subpackage change_password
+ */
+
+/** Default plugin configuration.*/
+/**
+ * Address of ldap server.
+ * You can use any URL format that is supported by your ldap extension.
+ * Examples:
+ * <ul>
+ *   <li>'ldap.example.com' - connect to server on ldap.example.com address
+ *   <li>'ldaps://ldap.example.com' - connect to server on ldap.example.com address 
+ *   and use SSL encrypted connection to default ldaps port.
+ * </ul>
+ * defaults to imap server address.
+ * @link http://www.php.net/ldap-connect
+ * @global string $cpw_ldap_server
+ */
+global $cpw_ldap_server;
+$cpw_ldap_server='localhost';
+
+/**
+ * Port of ldap server.
+ * Used only when $cpw_ldap_server specifies ip address or dns name.
+ * @global integer $cpw_ldap_port
+ */
+global $cpw_ldap_port;
+$cpw_ldap_port=389;
+
+/**
+ * ldap basedn that is used for binding to ldap server.
+ * this option must be set to correct value.
+ * @global $cpw_ldap_basedn;
+ */
+global $cpw_ldap_basedn;
+$cpw_ldap_basedn='';
+
+/**
+ * LDAP connection options
+ * @link http://www.php.net/ldap-set-option
+ * @global array $cpw_ldap_connect_opts
+ */
+global $cpw_ldap_connect_opts;
+$cpw_ldap_connect_opts=array();
+
+/**
+ * Controls use of starttls on ldap connection.
+ * Requires php 4.2+, php ldap extension with ssl support and 
+ * PROTOCOL_VERSION => 3 setting in $cpw_ldap_connect_opts
+ * @global boolean $cpw_ldap_use_tls
+ */
+global $cpw_ldap_use_tls;
+$cpw_ldap_use_tls=false;
+
+/**
+ * BindDN that should be able to search ldap directory and find DN used by user.
+ * Uses anonymous bind if set to empty string. You should not use DN with write 
+ * access to ldap directory here. Write access is not required.
+ * @global string $cpw_ldap_binddn
+ */
+global $cpw_ldap_binddn;
+$cpw_ldap_binddn='';
+
+/**
+ * password used for $cpw_ldap_binddn
+ * @global string $cpw_ldap_bindpw 
+ */
+global $cpw_ldap_bindpw;
+$cpw_ldap_bindpw='';
+
+/**
+ * BindDN that should be able to change password.
+ * WARNING: usually user has enough privileges to change own password.
+ * If you leave default value, plugin will try to connect with dn that 
+ * is detected in $cpw_ldap_username_attr=$username search and current
+ * user password will be used for authentication.
+ * @global string $cpw_ldap_admindn
+ */
+global $cpw_ldap_admindn;
+$cpw_ldap_admindn='';
+
+/**
+ * password used for $cpw_ldap_admindn
+ * @global string $cpw_ldap_adminpw 
+ */
+global $cpw_ldap_adminpw;
+$cpw_ldap_adminpw='';
+
+/**
+ * ldap attribute that stores username.
+ * username entry should be unique for $cpw_ldap_basedn
+ * @global string $cpw_ldap_userid_attr
+ */
+global $cpw_ldap_userid_attr;
+$cpw_ldap_userid_attr='uid';
+
+/**
+ * crypto that is used to encode new password
+ * If set to empty string, system tries to keep same encoding/hashing algorithm
+ * @global string $cpw_ldap_default_crypto
+ */
+global $cpw_ldap_default_crypto;
+$cpw_ldap_default_crypto='';
+
+/** end of default config */
+
+/** configuration overrides from config file */
+if (isset($cpw_ldap['server'])) $cpw_ldap_server=$cpw_ldap['server'];
+if (isset($cpw_ldap['port'])) $cpw_ldap_port=$cpw_ldap['port'];
+if (isset($cpw_ldap['basedn'])) $cpw_ldap_basedn=$cpw_ldap['basedn'];
+if (isset($cpw_ldap['connect_opts'])) $cpw_ldap_connect_opts=$cpw_ldap['connect_opts'];
+if (isset($cpw_ldap['use_tls'])) $cpw_ldap_use_tls=$cpw_ldap['use_tls'];
+if (isset($cpw_ldap['binddn'])) $cpw_ldap_binddn=$cpw_ldap['binddn'];
+if (isset($cpw_ldap['bindpw'])) $cpw_ldap_bindpw=$cpw_ldap['bindpw'];
+if (isset($cpw_ldap['admindn'])) $cpw_ldap_admindn=$cpw_ldap['admindn'];
+if (isset($cpw_ldap['adminpw'])) $cpw_ldap_adminpw=$cpw_ldap['adminpw'];
+if (isset($cpw_ldap['userid_attr'])) $cpw_ldap_userid_attr=$cpw_ldap['userid_attr'];
+if (isset($cpw_ldap['default_crypto'])) $cpw_ldap_default_crypto=$cpw_ldap['default_crypto'];
+
+/**
+ * Adding plugin hooks
+ */
+global $squirrelmail_plugin_hooks;
+$squirrelmail_plugin_hooks['change_password_dochange']['ldap'] =
+        'cpw_ldap_dochange';
+$squirrelmail_plugin_hooks['change_password_init']['ldap'] =
+        'cpw_ldap_init';
+
+/**
+ * Backend specific functions
+ */
+function cpw_ldap_init() {
+    global $color;
+    global $cpw_ldap_basedn;
+
+    /**
+     * If SM_PATH isn't defined, define it.  Required to include files.
+     * @ignore
+     */
+    if (!defined('SM_PATH')) define('SM_PATH','../../../');
+
+    // load error_box() function
+    include_once(SM_PATH . 'functions/display_messages.php');
+
+    // set initial value for error tracker
+    $cpw_ldap_initerr=false;
+
+    // check for ldap support in php
+    if (! function_exists('ldap_connect')) {
+        error_box(_("Current configuration requires ldap support in php."),$color);
+        $cpw_ldap_initerr=true;
+    }
+
+    // chech required configuration settings.
+    if ($cpw_ldap_basedn=='') {
+        error_box(_("Plugin is not configured correctly."),$color);
+        $cpw_ldap_initerr=true;
+    }
+
+    // if error var is positive, close html and stop execution 
+    if ($cpw_ldap_initerr) {
+        echo '</body></html>';
+        exit;
+    }
+}
+
+
+/**
+ * @param array $data The username/curpw/newpw data.
+ * @return array Array of error messages.
+ */
+function cpw_ldap_dochange($data) {
+    global $cpw_ldap_server, $cpw_ldap_port, $cpw_ldap_basedn, 
+        $cpw_ldap_connect_opts,$cpw_ldap_use_tls,
+        $cpw_ldap_binddn, $cpw_ldap_bindpw,
+        $cpw_ldap_admindn, $cpw_ldap_adminpw;
+
+    // unfortunately, we can only pass one parameter to a hook function,
+    // so we have to pass it as an array.
+    $username = $data['username'];
+    $curpw = $data['curpw'];
+    $newpw = $data['newpw'];
+
+    // globalize current password.
+
+    $msgs = array();
+
+    /**
+     * connect to ldap server
+     * hide ldap_connect() function call errors, because they are processed in script.
+     * any script execution error is treated as critical, error messages are dumped 
+     * to $msgs and ldap connection is closed with ldap_unbind(). all ldap_unbind()
+     * errors are suppressed. Any other error suppression should be explained.
+     */
+    $cpw_ldap_con=@ldap_connect($cpw_ldap_server);
+
+    if ($cpw_ldap_con) {
+        $cpw_ldap_con_err=false;
+        // set connection options
+        if (is_array($cpw_ldap_connect_opts) && $cpw_ldap_connect_opts!=array()) {
+            foreach ($cpw_ldap_connect_opts as $opt => $value) {
+                if (! ldap_set_option($cpw_ldap_con,constant('LDAP_OPT_' . $opt),$value)) {
+                    // set error message
+                    array_push($msgs,sprintf(_("Setting of ldap connection option %s to value %s failed."),$opt,$value));
+                    // FIXME: check if ldap_set_option modifies ldap_error.
+                    array_push($msgs,sprintf(_("Error: %s"),ldap_error($cpw_ldap_con)));
+                    $cpw_ldap_con_err=true;
+                }
+            }
+        }
+
+        // check for connection errors and stop execution if something is wrong
+        if ($cpw_ldap_con_err) {
+            @ldap_unbind($cpw_ldap_con);
+            return $msgs;
+        }
+
+        // enable tls
+        // FIXME: untested. use of undocumented ldap function
+        if ($cpw_ldap_use_tls &&
+            check_php_version(4,2,0) &&
+            isset($cpw_ldap_connect_opts['PROTOCOL_VERSION']) &&
+            $cpw_ldap_connect_opts['PROTOCOL_VERSION']>=3) {
+            if (! ldap_use_tls($cpw_ldap_con)) {
+                array_push($msgs,_("Unable to use TLS."));
+                array_push($msgs,sprintf(_("Error: %s"),ldap_error($cpw_ldap_con)));
+                $cpw_ldap_con_err=true;
+            }
+        } elseif ($cpw_ldap_use_tls) {
+            array_push($msgs,_("Unable to use LDAP TLS in current setup."));
+            $cpw_ldap_con_err=true;
+        }
+
+        // check for connection errors and stop execution if something is wrong
+        if ($cpw_ldap_con_err) {
+            @ldap_unbind($cpw_ldap_con);
+            return $msgs;
+        }
+
+        /**
+         * bind to ldap (use anonymous bind or unprivileged dn) in order to get user's dn
+         * hide ldap_bind() function call errors, because errors are processed in script
+         */
+        if ($cpw_ldap_binddn!='') {
+            // authenticated bind
+            $cpw_ldap_binding=@ldap_bind($cpw_ldap_con,$cpw_ldap_binddn,$cpw_ldap_bindpw);
+        } else {
+            // anonymous bind
+            $cpw_ldap_binding=@ldap_bind($cpw_ldap_con);
+        }
+
+        // check ldap_bind errors
+        if (! $cpw_ldap_binding) {
+            array_push($msgs,_("Unable to bind to ldap server"));
+            array_push($msgs,sprintf(_("Server replied: %s"),ldap_error($cpw_ldap_con)));
+            @ldap_unbind($cpw_ldap_con);
+            return $msgs;
+        }
+
+        // find userdn
+        $cpw_ldap_search_err=cpw_ldap_uid_search($cpw_ldap_con,$cpw_ldap_basedn,$msgs,$cpw_ldap_res,$cpw_ldap_userdn);
+
+        // check for search errors and stop execution if something is wrong
+        if (! $cpw_ldap_search_err) {
+            ldap_unbind($cpw_ldap_con);
+            return $msgs;
+        }
+
+        /**
+         * unset $cpw_ldap_res2 variable, if such var exists.
+         * $cpw_ldap_res2 object can be set in two places and second place checks, 
+         * if object was created in first place. if variable name matches (somebody 
+         * uses $cpw_ldap_res2 in code or globals), incorrect validation might 
+         * cause script errors.  
+         */
+        if (isset($cpw_ldap_res2)) unset($cpw_ldap_res2);
+
+        // rebind as userdn or admindn
+        if ($cpw_ldap_admindn!='') {
+            // admindn bind
+            $cpw_ldap_binding=@ldap_bind($cpw_ldap_con,$cpw_ldap_admindn,$cpw_ldap_adminpw);
+
+            if ($cpw_ldap_binding) {
+                // repeat search in order to get password info. Password info should be unavailable in unprivileged bind.
+                $cpw_ldap_search_err=cpw_ldap_uid_search($cpw_ldap_con,$cpw_ldap_basedn,$msgs,$cpw_ldap_res2,$cpw_ldap_userdn);
+
+                // check for connection errors and stop execution if something is wrong
+                if (! $cpw_ldap_search_err) {
+                    @ldap_unbind($cpw_ldap_con);
+                    return $msgs;
+                }
+
+                // we should check user password here.
+                $cpw_ldap_cur_pass_array=ldap_get_values($cpw_ldap_con,
+                                                         ldap_first_entry($cpw_ldap_con,$cpw_ldap_res2),'userpassword');
+                // FIXME: check if ldap_get_values() found userpassword field. Currently it might cause php errors
+
+                // compare passwords
+                if (! cpw_ldap_compare_pass($cpw_ldap_cur_pass_array[0],$curpw,$msgs)) {
+                    @ldap_unbind($cpw_ldap_con);
+                    return $msgs;
+                }
+            }
+        } else {
+            $cpw_ldap_binding=@ldap_bind($cpw_ldap_con,$cpw_ldap_userdn,$curpw);
+        }
+
+        if (! $cpw_ldap_binding) {
+            array_push($msgs,_("Unable to rebind to ldap server"));
+            array_push($msgs,sprintf(_("Server replied: %s"),ldap_error($cpw_ldap_con)));
+            @ldap_unbind($cpw_ldap_con);
+            return $msgs;
+        }
+
+        // repeat search in order to get password info
+        if (! isset($cpw_ldap_res2))
+            $cpw_ldap_search_err=cpw_ldap_uid_search($cpw_ldap_con,$cpw_ldap_basedn,$msgs,$cpw_ldap_res2,$cpw_ldap_userdn);
+
+        // check for connection errors and stop execution if something is wrong
+        if (! $cpw_ldap_search_err) {
+            @ldap_unbind($cpw_ldap_con);
+            return $msgs;
+        }
+
+        // getpassword
+        $cpw_ldap_cur_pass_array=ldap_get_values($cpw_ldap_con,ldap_first_entry($cpw_ldap_con,$cpw_ldap_res2),'userpassword');
+        // FIXME: check if ldap_get_values() found userpassword field
+
+        // encrypt new password (old password is needed for plaintext encryption detection)
+        $cpw_ldap_new_pass=cpw_ldap_encrypt_pass($newpw,$cpw_ldap_cur_pass_array[0],$msgs,$curpw);
+
+        if (! $cpw_ldap_new_pass) {
+            @ldap_unbind($cpw_ldap_con);
+            return $msgs;
+        }
+
+        // set new password
+        $ldap_pass_change=ldap_modify($cpw_ldap_con,$cpw_ldap_userdn,array('userpassword'=>$cpw_ldap_new_pass));
+
+        // check if ldap_modify was successful
+        if(! $ldap_pass_change) {
+            array_push($msgs,ldap_error($cpw_ldap_con));
+        }
+
+        // close connection
+        @ldap_unbind($cpw_ldap_con);
+    } else {
+        array_push($msgs,_("Unable to connect to LDAP server."));
+    }
+    return $msgs;
+}
+
+/** backend support functions **/
+
+/**
+ * Sanitizes ldap query strings.
+ * original code - ldapquery plugin.
+ * See rfc2254
+ * @link http://www.faqs.org/rfcs/rfc2254.html
+ * @param string $string
+ * @return string sanitized string
+ */
+function cpw_ldap_specialchars($string) {
+    $sanitized=array('\\' => '\5c',
+                     '*' => '\2a',
+                     '(' => '\28',
+                     ')' => '\29',
+                     "\x00" => '\00');
+
+    return str_replace(array_keys($sanitized),array_values($sanitized),$string);
+}
+
+/**
+ * returns crypto algorithm used in password.
+ * @param string $pass encrypted/hashed password
+ * @return string lowercased crypto algorithm name
+ */
+function cpw_ldap_get_crypto($pass,$curpass='') {
+    $ret = false;
+
+    if (preg_match("/^\{(.+)\}+/",$pass,$crypto)) {
+        $ret=strtolower($crypto[1]);
+    }
+
+    if ($ret=='crypt') {
+        // {CRYPT} can be standard des crypt, extended des crypt, md5 crypt or blowfish
+        // depends on first salt symbols (ext_des = '_', md5 = '$1$', blowfish = '$2$') 
+        // and length of salt (des = 2 chars, ext_des = 9, md5 = 12, blowfish = 16).
+        if (preg_match("/^\{crypt\}\\\$1\\\$+/i",$pass)) {
+            $ret='md5crypt';
+        } elseif (preg_match("/^\{crypt\}\\\$2\\\$+/i",$pass)) {
+            $ret='blowfish';
+        } elseif (preg_match("/^\{crypt\}_+/i",$pass)) {
+            $ret='extcrypt';
+        }
+    }
+
+    // maybe password is plaintext
+    if (! $ret && $curpass!='' && $pass==$curpass) $ret='plaintext';
+
+    return $ret;
+}
+
+/**
+ * search ldap for user id.
+ * @param object $ldap_con ldap connection
+ * @param string $ldap_basedn ldap basedn
+ * @param array $msgs error messages
+ * @param object $results ldap search results
+ * @param string $userdn DN of found entry
+ * @param boolean $onlyone require unique search results
+ * @return boolean false if connection failed.
+ */
+function cpw_ldap_uid_search($ldap_con,$ldap_basedn,&$msgs,&$results,&$userdn,$onlyone=true) {
+    global $cpw_ldap_userid_attr,$username;
+
+    $ret=true;
+
+    $results=ldap_search($ldap_con,$ldap_basedn,cpw_ldap_specialchars($cpw_ldap_userid_attr . '=' . $username));
+
+    if (! $results) {
+        array_push($msgs,_("Unable to find user's dn.") . _("Search error."));
+        array_push($msgs,sprintf(_("Error: %s"),ldap_error($ldap_con)));
+        $ret=false;
+    } elseif ($onlyone && ldap_count_entries($ldap_con,$results)>1) {
+        array_push($msgs,_("Multiple userid matches found."));
+        $ret=false;
+    } elseif (! $userdn = ldap_get_dn($ldap_con,ldap_first_entry($ldap_con,$results))) {
+        // ldap_get_dn() returned error
+        array_push($msgs,_("Unable to find user's dn.") . _("ldap_get_dn error."));
+        $ret=false;
+    }
+    return $ret;
+}
+
+/**
+ * encrypts ldap password
+ *
+ * if $cpw_ldap_default_crypto is set to empty string or $same_crypto is set, 
+ * uses same crypto as in old password.
+ * See phpldapadmin password_hash() function
+ * @link http://phpldapadmin.sf.net
+ * @param string $pass string that has to be encrypted/hashed
+ * @param string $cur_pass_hash old password hash
+ * @param array $msgs error message
+ * @param string $curpass current password. Used for plaintext password detection.
+ * @return string encrypted/hashed password or false
+ */
+function cpw_ldap_encrypt_pass($pass,$cur_pass_hash,&$msgs,$curpass='') {
+    global $cpw_ldap_default_crypto;
+
+    // which crypto should be used to encode/hash password
+    if ($cpw_ldap_default_crypto=='') {
+        $ldap_crypto=cpw_ldap_get_crypto($cur_pass_hash,$curpass);
+    } else {
+        $ldap_crypto=$cpw_ldap_default_crypto;
+    }
+    return cpw_ldap_password_hash($pass,$ldap_crypto,$msgs);
+}
+
+/**
+ * create hashed password
+ * @param string $pass plain text password
+ * @param string $crypto used crypto algorithm
+ * @param array $msgs array used for error messages
+ * @param string $forced_salt salt that should be used during hashing.
+ * Is used only when is not set to empty string. Salt should be formated
+ * according to $crypto requirements.
+ * @return hashed password or false.
+ */
+function cpw_ldap_password_hash($pass,$crypto,&$msgs,$forced_salt='') {
+    // set default return code
+    $ret=false;
+
+    // lowercase crypto just in case
+    $crypto=strtolower($crypto);
+
+    // extra symbols used for random string in crypt salt
+    // squirrelmail GenerateRandomString() adds alphanumerics with third argument = 7.
+    $extra_salt_chars='./';
+
+    // encrypt/hash password
+    switch ($crypto) {
+    case 'md5':
+        $ret='{MD5}' . base64_encode(pack('H*',md5($pass)));
+        break;
+    case 'smd5':
+        // minimal requirement mhash extension and php 4.0.4.
+        if( function_exists( 'mhash' ) && function_exists( 'mhash_keygen_s2k' ) ) {
+            sq_mt_seed( (double) microtime() * 1000000 );
+            if ($forced_salt!='') {
+                $salt=$forced_salt;
+            } else {
+                $salt = mhash_keygen_s2k( MHASH_MD5, $pass, substr( pack( "h*", md5( mt_rand() ) ), 0, 8 ), 4 );
+            }
+            $ret = "{SMD5}".base64_encode( mhash( MHASH_MD5, $pass.$salt ).$salt );
+        } else {
+            array_push($msgs,sprintf(_("Unsupported crypto: %s"),'smd5') . _("php mhash extension is missing."));
+        }
+        break;
+    case 'sha':
+        // minimal requirement = mhash extension
+        if( function_exists( 'mhash' ) ) {
+            $ret = '{SHA}' . base64_encode( mhash( MHASH_SHA1, $pass) );
+        } else {
+            array_push($msgs,sprintf(_("Unsupported crypto: %s"),'sha') . _("php mhash extension is missing."));
+        }
+        break;
+    case 'ssha':
+        // minimal requirement = mhash extension and php 4.0.4
+        if( function_exists( 'mhash' ) && function_exists( 'mhash_keygen_s2k' ) ) {
+            sq_mt_seed( (double) microtime() * 1000000 );
+            if ($forced_salt!='') {
+                $salt=$forced_salt;
+            } else {
+                $salt = mhash_keygen_s2k( MHASH_SHA1, $pass, substr( pack( "h*", md5( mt_rand() ) ), 0, 8 ), 4 );
+            }
+            $ret = "{SSHA}".base64_encode( mhash( MHASH_SHA1, $pass.$salt ).$salt );
+        } else {
+            array_push($msgs,sprintf(_("Unsupported crypto: %s"),'ssha') 
+                       . _("php mhash extension is missing."));
+        }
+        break;
+    case 'crypt':
+        if (defined('CRYPT_STD_DES') && CRYPT_STD_DES==1) {
+            $ret = '{CRYPT}' . crypt($pass,GenerateRandomString(2,$extra_salt_chars,7));
+        } else {
+            array_push($msgs,sprintf(_("Unsupported crypto: %s"),'crypt') 
+                       . _("System crypt library doesn't support standard des crypt."));
+        }
+        break;
+    case 'md5crypt':
+        // check if crypt() supports md5
+        if (defined('CRYPT_MD5') && CRYPT_MD5==1) {
+            $ret = '{CRYPT}' . crypt($pass,'$1$' . GenerateRandomString(9,$extra_salt_chars,7));
+        } else {
+            array_push($msgs,sprintf(_("Unsupported crypto: %s"),'md5crypt') 
+                       . _("System crypt library doesn't have md5 support."));
+        }
+        break;
+    case 'extcrypt':
+        // check if crypt() supports extended des
+        if (defined('CRYPT_EXT_DES') && CRYPT_EXT_DES==1) {
+            // FIXME: guinea pigs with extended des support needed. 
+            $ret = '{CRYPT}' . crypt($pass,'_' . GenerateRandomString(8,$extra_salt_chars,7));
+        } else {
+            array_push($msgs,sprintf(_("Unsupported crypto: %s"),'ext_des') 
+                       . _("System crypt library doesn't support extended des crypt."));
+        }
+        break;
+    case 'blowfish':
+        // check if crypt() supports blowfish
+        if (defined('CRYPT_BLOWFISH') && CRYPT_BLOWFISH==1) {
+            // FIXME: guinea pigs with blowfish support needed. 
+            $ret = '{CRYPT}' . crypt($pass,'$2$' . GenerateRandomString(13,$extra_salt_chars,7));
+        } else {
+            array_push($msgs,sprintf(_("Unsupported crypto: %s"),'blowfish') 
+                       . _("System crypt library doesn't have blowfish support."));
+        }
+        break;
+    case 'plaintext':
+        // clear plain text password
+        $ret=$pass;
+        break;
+    default:
+        array_push($msgs,sprintf(_("Unsupported crypto: %s"),
+                                 (is_string($ldap_crypto) ? htmlspecialchars($ldap_crypto) : _("unknown"))));
+    }
+    return $ret;
+}
+
+/**
+ * compares two passwords
+ * Code reuse. See phpldapadmin password_compare() function.
+ * Some parts of code was rewritten to backend specifics.
+ * @link http://phpldapadmin.sf.net
+ * @param string $pass_hash
+ * @param string $pass_clear
+ * @param array $msgs
+ * @return boolean true, if passwords match
+ */
+function cpw_ldap_compare_pass($pass_hash,$pass_clear,&$msgs) {
+    $ret=false;
+
+    if( preg_match( "/{([^}]+)}(.*)/", $pass_hash, $cypher ) ) {
+        $pass_hash = $cypher[2];
+        $_cypher = strtolower($cypher[1]);
+    } else  {
+        $_cypher = NULL;
+    }
+
+    switch( $_cypher ) {
+    case 'ssha':
+        // Salted SHA
+        // check for mhash support
+        if ( function_exists('mhash') ) {
+            $hash = base64_decode($pass_hash);
+            $salt = substr($hash, -4);
+            $new_hash = base64_encode( mhash( MHASH_SHA1, $pass_clear.$salt).$salt );
+            if( strcmp( $pass_hash, $new_hash ) == 0 )
+                $ret=true;
+        } else {
+            array_push($msgs,_("Unable to validate user's password."));
+            array_push($msgs, _("php mhash extension is missing."));
+        }
+        break;
+    case 'smd5':
+        // Salted MD5
+        // check for mhash support
+        if ( function_exists('mhash') ) {
+            $hash = base64_decode($pass_hash);
+            $salt = substr($hash, -4);
+            $new_hash = base64_encode( mhash( MHASH_MD5, $pass_clear.$salt).$salt );
+            if( strcmp( $pass_hash, $new_hash ) == 0)
+                $ret=true;
+        } else {
+            array_push($msgs,_("Unable to validate user's password."));
+            array_push($msgs, _("php mhash extension is missing."));
+        }
+        break;
+    case 'sha':
+        // SHA crypted passwords
+        if( strcasecmp( cpw_ldap_password_hash($pass_clear,'sha',$msgs), "{SHA}".$pass_hash ) == 0)
+            $ret=true;
+        break;
+    case 'md5':
+        // MD5 crypted passwords
+        if( strcasecmp( cpw_ldap_password_hash( $pass_clear,'md5',$msgs), "{MD5}".$pass_hash ) == 0 )
+            $ret=true;
+        break;
+    case 'crypt':
+        // Crypt passwords
+        if( strstr( $pass_hash, '$2$' ) ) { // Check if it's blowfish crypt
+            // check CRYPT_BLOWFISH here.
+            // ldap server might support it, but php can be on other OS
+            if (defined('CRYPT_BLOWFISH') && CRYPT_BLOWFISH==1) {
+                list(,$type,$salt,$hash) = explode('$',$pass_hash);
+                if( crypt( $pass_clear, '$2$' .$salt ) == $pass_hash )
+                    $ret=true;
+            } else {
+                array_push($msgs,_("Unable to validate user's password."));
+                array_push($msgs,_("Blowfish is not supported by webserver's system crypt library."));
+            }
+        } elseif( strstr( $pass_hash, '$1$' ) ) { // Check if it's md5 crypt
+            // check CRYPT_MD5 here.
+            // ldap server might support it, but php might be on other OS
+            if (defined('CRYPT_MD5') && CRYPT_MD5==1) {
+                list(,$type,$salt,$hash) = explode('$',$pass_hash);
+                if( crypt( $pass_clear, '$1$' .$salt ) == $pass_hash )
+                    $ret=true;
+            } else {
+                array_push($msgs,_("Unable to validate user's password."));
+                array_push($msgs,_("MD5 is not supported by webserver's system crypt library."));
+            }
+        } elseif( strstr( $pass_hash, '_' ) ) { // Check if it's extended des crypt
+            // check CRYPT_EXT_DES here.
+            // ldap server might support it, but php might be on other OS
+            if (defined('CRYPT_EXT_DES') && CRYPT_EXT_DES==1) {
+                if( crypt( $pass_clear, $pass_hash ) == $pass_hash )
+                    $ret=true;
+            } else {
+                array_push($msgs,_("Unable to validate user's password."));
+                array_push($msgs,_("Extended DES crypt is not supported by webserver's system crypt library."));
+            }
+        } else {
+            // it is possible that this test is useless and any crypt library supports it, but ...
+            if (defined('CRYPT_STD_DES') && CRYPT_STD_DES==1) {
+                // plain crypt password
+                if( crypt($pass_clear, $pass_hash ) == $pass_hash )
+                    $ret=true;
+            } else {
+                array_push($msgs,_("Unable to validate user's password."));
+                array_push($msgs,_("Standard DES crypt is not supported by webserver's system crypt library."));
+            }
+        }
+        break;
+    // No crypt is given assume plaintext passwords are used
+    default:
+        if( $pass_clear == $pass_hash )
+            $ret=true;
+        break;
+    }
+    if (! $ret) {
+        array_push($msgs,CPW_CURRENT_NOMATCH);
+    }
+    return $ret;
+}
+?>
\ No newline at end of file