Source for file ldap.php
Documentation is available at ldap.php
* Change password LDAP backend
* @copyright © 2005-2006 The SquirrelMail Project Team
* @license http://opensource.org/licenses/gpl-license.php GNU Public License
* @version $Id: ldap.php,v 1.15 2006/07/15 12:01:09 tokul Exp $
* @subpackage change_password
* do not allow to call this file directly
if ((isset
($_SERVER['SCRIPT_FILENAME']) &&
$_SERVER['SCRIPT_FILENAME'] == __FILE__
) ||
(isset
($HTTP_SERVER_SERVER['SCRIPT_FILENAME']) &&
$HTTP_SERVER_SERVER['SCRIPT_FILENAME'] == __FILE__
) ) {
header("Location: ../../../src/login.php");
/** 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.
* <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.
* defaults to imap server address.
* @link http://www.php.net/ldap-connect
* @global string $cpw_ldap_server
$cpw_ldap_server=
$imapServerAddress;
* Used only when $cpw_ldap_server specifies IP address or DNS name.
* @global integer $cpw_ldap_port
* LDAP basedn that is used for binding to LDAP server.
* this option must be set to correct value.
* @global string $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;
* 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
* password used for $cpw_ldap_binddn
* @global string $cpw_ldap_bindpw
* BindDN that should be able to change 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.
* @global string $cpw_ldap_admindn
global $cpw_ldap_admindn;
* password used for $cpw_ldap_admindn
* @global string $cpw_ldap_adminpw
global $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'];
/** make sure that setting does not contain mapping */
global $squirrelmail_plugin_hooks;
$squirrelmail_plugin_hooks['change_password_dochange']['ldap'] =
$squirrelmail_plugin_hooks['change_password_init']['ldap'] =
* Makes sure that required functions and configuration options are set.
global $oTemplate, $cpw_ldap_basedn;
// set initial value for error tracker
// check for ldap support in php
error_box(_("Current configuration requires LDAP support in PHP."));
// chech required configuration settings.
if ($cpw_ldap_basedn==
'') {
error_box(_("Plugin is not configured correctly."));
// if error var is positive, close html and stop execution
$oTemplate->display('footer.tpl');
* Changes password. Main function attached to hook
* @param array $data The username/curpw/newpw data.
* @return array Array of error messages.
// unfortunately, we can only pass one parameter to a hook function,
// so we have to pass it as an array.
$username =
$data['username'];
// globalize current password.
* 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);
// set connection options
if (is_array($cpw_ldap_connect_opts) &&
$cpw_ldap_connect_opts!=
array()) {
// ldap_set_option() is available only with openldap 2.x and netscape directory sdk.
foreach ($cpw_ldap_connect_opts as $opt =>
$value) {
// Make sure that constant is defined defore using it.
// 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)) {
array_push($msgs,sprintf(_("Setting of LDAP connection option %s to value %s failed."),$opt,$value));
array_push($msgs,_("Current PHP LDAP extension does not allow use of ldap_set_option() function."));
// check for connection errors and stop execution if something is wrong
@ldap_unbind($cpw_ldap_con);
isset
($cpw_ldap_connect_opts['PROTOCOL_VERSION']) &&
$cpw_ldap_connect_opts['PROTOCOL_VERSION']>=
3 &&
// suppress ldap_start_tls errors and process error messages
if (! @ldap_start_tls($cpw_ldap_con)) {
sprintf(_("Error: %s"),ldap_error($cpw_ldap_con)));
} elseif ($cpw_ldap_use_tls) {
array_push($msgs,_("Unable to use LDAP TLS in current setup."));
// check for connection errors and stop execution if something is wrong
@ldap_unbind($cpw_ldap_con);
* 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!=
'') {
$cpw_ldap_binding=
@ldap_bind($cpw_ldap_con,$cpw_ldap_binddn,$cpw_ldap_bindpw);
$cpw_ldap_binding=
@ldap_bind($cpw_ldap_con);
// check ldap_bind errors
if (! $cpw_ldap_binding) {
_("Unable to bind to LDAP server."),
sprintf(_("Server replied: %s"),ldap_error($cpw_ldap_con)));
@ldap_unbind($cpw_ldap_con);
$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);
* 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
if (isset
($cpw_ldap_res2)) unset
($cpw_ldap_res2);
// rebind as userdn or admindn
if ($cpw_ldap_admindn!=
'') {
$cpw_ldap_binding=
@ldap_bind($cpw_ldap_con,$cpw_ldap_admindn,$cpw_ldap_adminpw);
// 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);
// errors are added to msgs by cpw_ldap_uid_search()
// we should check user password here.
// 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
if (! $cpw_ldap_cur_pass_array) {
array_push($msgs,_("Unable to find user's password attribute."));
@ldap_unbind($cpw_ldap_con);
// errors are added to $msgs by cpw_ldap_compare_pass()
$cpw_ldap_binding=
@ldap_bind($cpw_ldap_con,$cpw_ldap_userdn,$curpw);
if (! $cpw_ldap_binding) {
_("Unable to rebind to LDAP server."),
sprintf(_("Server replied: %s"),ldap_error($cpw_ldap_con)));
@ldap_unbind($cpw_ldap_con);
// 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);
// 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."));
// encrypt new password (old password is needed for plaintext encryption detection)
if (! $cpw_ldap_new_pass) {
@ldap_unbind($cpw_ldap_con);
// 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) {
@ldap_unbind($cpw_ldap_con);
array_push($msgs,_("Unable to connect to LDAP server."));
/** backend support functions **/
* Sanitizes LDAP query strings.
* original code - ldapquery plugin.
* @link http://www.faqs.org/rfcs/rfc2254.html
* @return string sanitized string
$sanitized=
array('\\' =>
'\5c',
* returns crypto algorithm used in password.
* @param string $pass encrypted/hashed password
* @return string lowercased crypto algorithm name
// {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)) {
} elseif (preg_match("/^\{crypt\}\\\$2+/i",$pass)) {
// maybe password is plaintext
if (! $ret &&
$curpass!=
'' &&
$pass==
$curpass) $ret=
'plaintext';
* 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.
$results=
ldap_search($ldap_con,$ldap_basedn,cpw_ldap_specialchars($cpw_ldap_userid_attr .
'=' .
$username));
_("Unable to find user's DN."),
sprintf(_("Error: %s"),ldap_error($ldap_con)));
} elseif ($onlyone &&
ldap_count_entries($ldap_con,$results)>
1) {
} elseif (! $userdn =
ldap_get_dn($ldap_con,ldap_first_entry($ldap_con,$results))) {
// ldap_get_dn() returned error
_("Unable to find user's DN."),
_("ldap_get_dn error."));
* 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
// which crypto should be used to encode/hash password
if ($cpw_ldap_default_crypto==
'') {
$ldap_crypto=
$cpw_ldap_default_crypto;
* @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.
// set default return code
// lowercase crypto just in case
// extra symbols used for random string in crypt salt
// squirrelmail GenerateRandomString() adds alphanumerics with third argument = 7.
// minimal requirement = php with mhash extension