One more. I wasn't done.
[squirrelmail.git] / plugins / change_password / backend / ldap.php
index b4562c6e434355fbeeb21b39023ad6ffc24736cb..d325cb3cf84ff6c21d2c273f5a8e64959952a572 100644 (file)
@@ -1,41 +1,60 @@
 <?php
+
 /**
- * Change password ldap backend
+ * Change password LDAP backend
  *
+ * @copyright &copy; 2005-2007 The SquirrelMail Project Team
+ * @license http://opensource.org/licenses/gpl-license.php GNU Public License
  * @version $Id$
  * @package plugins
  * @subpackage change_password
  */
 
+/**
+ * do not allow to call this file directly
+ */
+if (isset($_SERVER['SCRIPT_FILENAME']) && $_SERVER['SCRIPT_FILENAME'] == __FILE__) {
+    header("Location: ../../../src/login.php");
+    die();
+}
+
+/** load required functions */
+
+/** sqimap_get_user_server() function */
+include_once(SM_PATH . 'functions/imap_general.php');
+
+/** get imap server and username globals */
+global $imapServerAddress, $username;
+
 /** Default plugin configuration.*/
 /**
- * Address of ldap server.
- * You can use any URL format that is supported by your ldap extension.
+ * 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.
+ *   <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';
+$cpw_ldap_server=$imapServerAddress;
 
 /**
- * Port of ldap server.
- * Used only when $cpw_ldap_server specifies ip address or dns name.
+ * 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.
+ * LDAP basedn that is used for binding to LDAP server.
  * this option must be set to correct value.
- * @global $cpw_ldap_basedn;
+ * @global string $cpw_ldap_basedn;
  */
 global $cpw_ldap_basedn;
 $cpw_ldap_basedn='';
@@ -49,8 +68,8 @@ 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 
+ * 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
  */
@@ -58,9 +77,9 @@ 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.
+ * 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;
@@ -68,15 +87,15 @@ $cpw_ldap_binddn='';
 
 /**
  * password used for $cpw_ldap_binddn
- * @global string $cpw_ldap_bindpw 
+ * @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 
+ * WARNING: sometimes 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
@@ -86,13 +105,13 @@ $cpw_ldap_admindn='';
 
 /**
  * password used for $cpw_ldap_admindn
- * @global string $cpw_ldap_adminpw 
+ * @global string $cpw_ldap_adminpw
  */
 global $cpw_ldap_adminpw;
 $cpw_ldap_adminpw='';
 
 /**
- * ldap attribute that stores username.
+ * LDAP attribute that stores username.
  * username entry should be unique for $cpw_ldap_basedn
  * @global string $cpw_ldap_userid_attr
  */
@@ -122,6 +141,9 @@ 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'];
 
+/** make sure that setting does not contain mapping */
+$cpw_ldap_server=sqimap_get_user_server($cpw_ldap_server,$username);
+
 /**
  * Adding plugin hooks
  */
@@ -132,50 +154,41 @@ $squirrelmail_plugin_hooks['change_password_init']['ldap'] =
         'cpw_ldap_init';
 
 /**
- * Backend specific functions
+ * Makes sure that required functions and configuration options are set.
  */
 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');
+    global $oTemplate, $cpw_ldap_basedn;
 
     // 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);
+        error_box(_("Current configuration requires LDAP support in PHP."));
         $cpw_ldap_initerr=true;
     }
 
     // chech required configuration settings.
     if ($cpw_ldap_basedn=='') {
-        error_box(_("Plugin is not configured correctly."),$color);
+        error_box(_("Plugin is not configured correctly."));
         $cpw_ldap_initerr=true;
     }
 
-    // if error var is positive, close html and stop execution 
+    // if error var is positive, close html and stop execution
     if ($cpw_ldap_initerr) {
-        echo '</body></html>';
+        $oTemplate->display('footer.tpl');
         exit;
     }
 }
 
 
 /**
+ * Changes password. Main function attached to hook
  * @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, 
+    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;
@@ -191,26 +204,39 @@ function cpw_ldap_dochange($data) {
     $msgs = array();
 
     /**
-     * connect to ldap server
+     * 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()
+     * 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;
+            // ldap_set_option() is available only with openldap 2.x and netscape directory sdk.
+            if (function_exists('ldap_set_option')) {
+                foreach ($cpw_ldap_connect_opts as $opt => $value) {
+                    // Make sure that constant is defined defore using it.
+                    if (defined('LDAP_OPT_' . $opt)) {
+                        // ldap_set_option() should not produce E_NOTICE or E_ALL errors and does not modify ldap_error().
+                        // leave it without @ in order to see any weird errors
+                        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));
+                            $cpw_ldap_con_err=true;
+                        }
+                    } else {
+                        array_push($msgs,sprintf(_("Incorrect LDAP connection option: %s"),$opt));
+                        $cpw_ldap_con_err=true;
+                    }
                 }
+            } else {
+                array_push($msgs,_("Current PHP LDAP extension does not allow use of ldap_set_option() function."));
+                $cpw_ldap_con_err=true;
             }
         }
 
@@ -220,15 +246,17 @@ function cpw_ldap_dochange($data) {
             return $msgs;
         }
 
-        // enable tls
-        // FIXME: untested. use of undocumented ldap function
+        // enable ldap starttls
         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_connect_opts['PROTOCOL_VERSION']>=3 &&
+            function_exists('ldap_start_tls')) {
+            // suppress ldap_start_tls errors and process error messages
+            if (! @ldap_start_tls($cpw_ldap_con)) {
+                array_push($msgs,
+                           _("Unable to use TLS."),
+                           sprintf(_("Error: %s"),ldap_error($cpw_ldap_con)));
                 $cpw_ldap_con_err=true;
             }
         } elseif ($cpw_ldap_use_tls) {
@@ -243,7 +271,7 @@ function cpw_ldap_dochange($data) {
         }
 
         /**
-         * bind to ldap (use anonymous bind or unprivileged dn) in order to get user's dn
+         * 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!='') {
@@ -256,8 +284,9 @@ function cpw_ldap_dochange($data) {
 
         // 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)));
+            array_push($msgs,
+                       _("Unable to bind to LDAP server."),
+                       sprintf(_("Server replied: %s"),ldap_error($cpw_ldap_con)));
             @ldap_unbind($cpw_ldap_con);
             return $msgs;
         }
@@ -267,16 +296,16 @@ function cpw_ldap_dochange($data) {
 
         // check for search errors and stop execution if something is wrong
         if (! $cpw_ldap_search_err) {
-            ldap_unbind($cpw_ldap_con);
+            @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.  
+         * $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);
 
@@ -292,17 +321,25 @@ function cpw_ldap_dochange($data) {
                 // check for connection errors and stop execution if something is wrong
                 if (! $cpw_ldap_search_err) {
                     @ldap_unbind($cpw_ldap_con);
+                    // errors are added to msgs by cpw_ldap_uid_search()
                     return $msgs;
                 }
 
                 // we should check user password here.
-                $cpw_ldap_cur_pass_array=ldap_get_values($cpw_ldap_con,
+                // suppress errors and check value returned by function call
+                $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
+
+                // check if ldap_get_values() have found userpassword field
+                if (! $cpw_ldap_cur_pass_array) {
+                    array_push($msgs,_("Unable to find user's password attribute."));
+                    return $msgs;
+                }
 
                 // compare passwords
                 if (! cpw_ldap_compare_pass($cpw_ldap_cur_pass_array[0],$curpw,$msgs)) {
                     @ldap_unbind($cpw_ldap_con);
+                    // errors are added to $msgs by cpw_ldap_compare_pass()
                     return $msgs;
                 }
             }
@@ -311,8 +348,9 @@ function cpw_ldap_dochange($data) {
         }
 
         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)));
+            array_push($msgs,
+                       _("Unable to rebind to LDAP server."),
+                       sprintf(_("Server replied: %s"),ldap_error($cpw_ldap_con)));
             @ldap_unbind($cpw_ldap_con);
             return $msgs;
         }
@@ -327,9 +365,15 @@ function cpw_ldap_dochange($data) {
             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
+        // getpassword. suppress errors and check value returned by function call
+        $cpw_ldap_cur_pass_array=@ldap_get_values($cpw_ldap_con,ldap_first_entry($cpw_ldap_con,$cpw_ldap_res2),'userpassword');
+
+        // check if ldap_get_values() have found userpassword field.
+        // Error differs from previous one, because user managed to authenticate.
+        if (! $cpw_ldap_cur_pass_array) {
+            array_push($msgs,_("LDAP server uses different attribute to store user's password."));
+            return $msgs;
+        }
 
         // 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);
@@ -339,8 +383,8 @@ function cpw_ldap_dochange($data) {
             return $msgs;
         }
 
-        // set new password
-        $ldap_pass_change=ldap_modify($cpw_ldap_con,$cpw_ldap_userdn,array('userpassword'=>$cpw_ldap_new_pass));
+        // set new password. suppress ldap_modify errors. script checks and displays ldap_modify errors.
+        $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) {
@@ -358,7 +402,7 @@ function cpw_ldap_dochange($data) {
 /** backend support functions **/
 
 /**
- * Sanitizes ldap query strings.
+ * Sanitizes LDAP query strings.
  * original code - ldapquery plugin.
  * See rfc2254
  * @link http://www.faqs.org/rfcs/rfc2254.html
@@ -389,11 +433,11 @@ function cpw_ldap_get_crypto($pass,$curpass='') {
 
     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$') 
+        // 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)) {
+        } elseif (preg_match("/^\{crypt\}\\\$2+/i",$pass)) {
             $ret='blowfish';
         } elseif (preg_match("/^\{crypt\}_+/i",$pass)) {
             $ret='extcrypt';
@@ -407,7 +451,7 @@ function cpw_ldap_get_crypto($pass,$curpass='') {
 }
 
 /**
- * search ldap for user id.
+ * Search LDAP for user id.
  * @param object $ldap_con ldap connection
  * @param string $ldap_basedn ldap basedn
  * @param array $msgs error messages
@@ -424,24 +468,28 @@ function cpw_ldap_uid_search($ldap_con,$ldap_basedn,&$msgs,&$results,&$userdn,$o
     $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)));
+        array_push($msgs,
+                   _("Unable to find user's DN."),
+                   _("Search error."),
+                   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."));
+        array_push($msgs,
+                   _("Unable to find user's DN."),
+                   _("ldap_get_dn error."));
         $ret=false;
     }
     return $ret;
 }
 
 /**
- * encrypts ldap password
+ * Encrypts LDAP password
  *
- * if $cpw_ldap_default_crypto is set to empty string or $same_crypto is set, 
+ * 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
@@ -486,12 +534,22 @@ function cpw_ldap_password_hash($pass,$crypto,&$msgs,$forced_salt='') {
 
     // encrypt/hash password
     switch ($crypto) {
+    case 'md4':
+        // minimal requirement = php with mhash extension
+        if ( function_exists( 'mhash' ) && defined('MHASH_MD4')) {
+            $ret = '{MD4}' . base64_encode( mhash( MHASH_MD4, $pass) );
+        } else {
+            array_push($msgs,
+                       sprintf(_("Unsupported crypto: %s"),'md4'),
+                       _("PHP mhash extension is missing or does not support selected crypto."));
+        }
+        break;
     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' ) ) {
+        // minimal requirement = mhash extension with md5 support and php 4.0.4.
+        if( function_exists( 'mhash' ) && function_exists( 'mhash_keygen_s2k' ) && defined('MHASH_MD5')) {
             sq_mt_seed( (double) microtime() * 1000000 );
             if ($forced_salt!='') {
                 $salt=$forced_salt;
@@ -500,20 +558,38 @@ function cpw_ldap_password_hash($pass,$crypto,&$msgs,$forced_salt='') {
             }
             $ret = "{SMD5}".base64_encode( mhash( MHASH_MD5, $pass.$salt ).$salt );
         } else {
-            array_push($msgs,sprintf(_("Unsupported crypto: %s"),'smd5') . _("php mhash extension is missing."));
+            // use two array_push calls in order to display messages in different lines.
+            array_push($msgs,
+                       sprintf(_("Unsupported crypto: %s"),'smd5'),
+                       _("PHP mhash extension is missing or does not support selected crypto."));
+        }
+        break;
+    case 'rmd160':
+        // minimal requirement = php with mhash extension
+        if ( function_exists( 'mhash' ) && defined('MHASH_RIPEMD160')) {
+            $ret = '{RMD160}' . base64_encode( mhash( MHASH_RIPEMD160, $pass) );
+        } else {
+            array_push($msgs,
+                       sprintf(_("Unsupported crypto: %s"),'ripe-md160'),
+                       _("PHP mhash extension is missing or does not support selected crypto."));
         }
         break;
     case 'sha':
-        // minimal requirement = mhash extension
-        if( function_exists( 'mhash' ) ) {
+        // minimal requirement = php 4.3.0+ or php with mhash extension
+        if ( function_exists('sha1') && defined('MHASH_SHA1')) {
+            // use php 4.3.0+ sha1 function, if it is available.
+            $ret = '{SHA}' . base64_encode(pack('H*',sha1($pass)));
+        } elseif( 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."));
+            array_push($msgs,
+                       sprintf(_("Unsupported crypto: %s"),'sha'),
+                       _("PHP mhash extension is missing or does not support selected crypto."));
         }
         break;
     case 'ssha':
         // minimal requirement = mhash extension and php 4.0.4
-        if( function_exists( 'mhash' ) && function_exists( 'mhash_keygen_s2k' ) ) {
+        if( function_exists( 'mhash' ) && function_exists( 'mhash_keygen_s2k' ) && defined('MHASH_SHA1')) {
             sq_mt_seed( (double) microtime() * 1000000 );
             if ($forced_salt!='') {
                 $salt=$forced_salt;
@@ -522,16 +598,18 @@ function cpw_ldap_password_hash($pass,$crypto,&$msgs,$forced_salt='') {
             }
             $ret = "{SSHA}".base64_encode( mhash( MHASH_SHA1, $pass.$salt ).$salt );
         } else {
-            array_push($msgs,sprintf(_("Unsupported crypto: %s"),'ssha') 
-                       . _("php mhash extension is missing."));
+            array_push($msgs,
+                       sprintf(_("Unsupported crypto: %s"),'ssha'),
+                       _("PHP mhash extension is missing or does not support selected crypto."));
         }
         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."));
+            array_push($msgs,
+                       sprintf(_("Unsupported crypto: %s"),'crypt'),
+                       _("System crypt library doesn't support standard DES crypt."));
         }
         break;
     case 'md5crypt':
@@ -539,28 +617,29 @@ function cpw_ldap_password_hash($pass,$crypto,&$msgs,$forced_salt='') {
         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."));
+            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."));
+            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));
+            $ret = '{CRYPT}' . crypt($pass,'$2a$12$' . GenerateRandomString(13,$extra_salt_chars,7));
         } else {
-            array_push($msgs,sprintf(_("Unsupported crypto: %s"),'blowfish') 
-                       . _("System crypt library doesn't have blowfish support."));
+            array_push($msgs,
+                       sprintf(_("Unsupported crypto: %s"),'Blowfish'),
+                       _("System crypt library doesn't have Blowfish support."));
         }
         break;
     case 'plaintext':
@@ -579,9 +658,9 @@ function cpw_ldap_password_hash($pass,$crypto,&$msgs,$forced_salt='') {
  * 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
+ * @param string $pass_hash hashed password string with password type indicators
+ * @param string $pass_clear plain text password
+ * @param array $msgs error messages
  * @return boolean true, if passwords match
  */
 function cpw_ldap_compare_pass($pass_hash,$pass_clear,&$msgs) {
@@ -598,29 +677,31 @@ function cpw_ldap_compare_pass($pass_hash,$pass_clear,&$msgs) {
     case 'ssha':
         // Salted SHA
         // check for mhash support
-        if ( function_exists('mhash') ) {
+        if ( function_exists('mhash') && defined('MHASH_SHA1')) {
             $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."));
+            array_push($msgs,
+                       _("Unable to validate user's password."),
+                       _("PHP mhash extension is missing or does not support selected crypto."));
         }
         break;
     case 'smd5':
         // Salted MD5
         // check for mhash support
-        if ( function_exists('mhash') ) {
+        if ( function_exists('mhash') && defined('MHASH_MD5')) {
             $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."));
+            array_push($msgs,
+                       _("Unable to validate user's password."),
+                       _("PHP mhash extension is missing or does not support selected crypto."));
         }
         break;
     case 'sha':
@@ -628,23 +709,33 @@ function cpw_ldap_compare_pass($pass_hash,$pass_clear,&$msgs) {
         if( strcasecmp( cpw_ldap_password_hash($pass_clear,'sha',$msgs), "{SHA}".$pass_hash ) == 0)
             $ret=true;
         break;
+    case 'rmd160':
+        // RIPE-MD160 crypted passwords
+        if( strcasecmp( cpw_ldap_password_hash($pass_clear,'rmd160',$msgs), "{RMD160}".$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 )
+        if( strcasecmp( cpw_ldap_password_hash($pass_clear,'md5',$msgs), "{MD5}".$pass_hash ) == 0 )
+            $ret=true;
+        break;
+    case 'md4':
+        // MD4 crypted passwords
+        if( strcasecmp( cpw_ldap_password_hash($pass_clear,'md4',$msgs), "{MD4}".$pass_hash ) == 0 )
             $ret=true;
         break;
     case 'crypt':
         // Crypt passwords
-        if( strstr( $pass_hash, '$2$' ) ) { // Check if it's blowfish crypt
+        if(  preg_match( "/^\\\$2+/",$pass_hash ) ) { // 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 )
+                if( crypt( $pass_clear, $pass_hash ) == $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."));
+                array_push($msgs,
+                           _("Unable to validate user's password."),
+                           _("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.
@@ -654,8 +745,9 @@ function cpw_ldap_compare_pass($pass_hash,$pass_clear,&$msgs) {
                 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."));
+                array_push($msgs,
+                           _("Unable to validate user's password."),
+                           _("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.
@@ -664,8 +756,9 @@ function cpw_ldap_compare_pass($pass_hash,$pass_clear,&$msgs) {
                 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."));
+                array_push($msgs,
+                           _("Unable to validate user's password."),
+                           _("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 ...
@@ -674,20 +767,20 @@ function cpw_ldap_compare_pass($pass_hash,$pass_clear,&$msgs) {
                 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."));
+                array_push($msgs,
+                           _("Unable to validate user's password."),
+                           _("Standard DES crypt is not supported by webserver's system crypt library."));
             }
         }
         break;
-    // No crypt is given assume plaintext passwords are used
+    // No crypt is given, assume plaintext passwords are used
     default:
         if( $pass_clear == $pass_hash )
             $ret=true;
         break;
     }
-    if (! $ret) {
+    if (! $ret && empty($msgs)) {
         array_push($msgs,CPW_CURRENT_NOMATCH);
     }
     return $ret;
 }
-?>
\ No newline at end of file