From 02c81de496ba189cf1dca6d9588ea434f93ee3da Mon Sep 17 00:00:00 2001 From: tokul Date: Sun, 27 Feb 2005 16:40:51 +0000 Subject: [PATCH] adding ldap backend support to change_password plugin. will contact 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 | 115 ++++ plugins/change_password/backend/ldap.php | 693 +++++++++++++++++++++++ 2 files changed, 808 insertions(+) create mode 100644 plugins/change_password/backend/ldap.php diff --git a/plugins/change_password/README b/plugins/change_password/README index e1a683ce..bbbe86b9 100644 --- a/plugins/change_password/README +++ b/plugins/change_password/README @@ -15,6 +15,16 @@ Probably, you need to set some config vars in the backend too (backend/.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 + used code from phpldapadmin and squirrelmail + ldapquery plugin. merak backend - Edwin van Elk mysql backend - Thijs Kinkhorst poppassd backend - Seth Randall vmailmgrd backend - Tomas Kuliavas +------------------ +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 index 00000000..b4562c6e --- /dev/null +++ b/plugins/change_password/backend/ldap.php @@ -0,0 +1,693 @@ + + *
  • 'ldap.example.com' - connect to server on ldap.example.com address + *
  • 'ldaps://ldap.example.com' - connect to server on ldap.example.com address + * and use SSL encrypted connection to default ldaps port. + * + * 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 ''; + 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 -- 2.25.1