<?php
+
/**
* Change password LDAP backend
*
+ * @copyright 2005-2014 The SquirrelMail Project Team
+ * @license http://opensource.org/licenses/gpl-license.php GNU Public License
* @version $Id$
* @package plugins
* @subpackage change_password
*/
-/** get imap server global */
-global $imapServerAddress;
+/**
+ * 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.*/
/**
/**
* 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='';
/**
* BindDN that should be able to change password.
- * WARNING: usually user has enough privileges to change own password.
+ * 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.
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
*/
'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 ($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.
*/
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;
}
}
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) {
// 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;
}
// 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;
}
// 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;
}
}
}
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;
}
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);
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) {
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';
$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;
// 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' ) ) {
- sq_mt_seed( (double) microtime() * 1000000 );
+ // minimal requirement = mhash extension with md5 support and php 4.0.4.
+ if( function_exists( 'mhash' ) && function_exists( 'mhash_keygen_s2k' ) && defined('MHASH_MD5')) {
if ($forced_salt!='') {
$salt=$forced_salt;
} else {
}
$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' ) ) {
- sq_mt_seed( (double) microtime() * 1000000 );
+ if( function_exists( 'mhash' ) && function_exists( 'mhash_keygen_s2k' ) && defined('MHASH_SHA1')) {
if ($forced_salt!='') {
$salt=$forced_salt;
} else {
}
$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':
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':
break;
default:
array_push($msgs,sprintf(_("Unsupported crypto: %s"),
- (is_string($ldap_crypto) ? htmlspecialchars($ldap_crypto) : _("unknown"))));
+ (is_string($ldap_crypto) ? sm_encode_html_special_chars($ldap_crypto) : _("unknown"))));
}
return $ret;
}
* 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) {
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':
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.
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.
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 ...
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