adding ripe-md160 and md4 support for qmail-ldap
[squirrelmail.git] / plugins / change_password / backend / ldap.php
CommitLineData
02c81de4 1<?php
2/**
d04cab42 3 * Change password LDAP backend
02c81de4 4 *
5 * @version $Id$
6 * @package plugins
7 * @subpackage change_password
8 */
9
444db9b3 10/**
11 * If SM_PATH isn't defined, define it. Required to include files.
12 * @ignore
13 */
14if (!defined('SM_PATH')) define('SM_PATH','../../../');
15
16/** load required functions */
17
18/** error_box() function */
19include_once(SM_PATH . 'functions/display_messages.php');
20/** sqimap_get_user_server() function */
21include_once(SM_PATH . 'functions/imap_general.php');
22
23/** get imap server and username globals */
24global $imapServerAddress, $username;
f6d11dea 25
02c81de4 26/** Default plugin configuration.*/
27/**
d04cab42 28 * Address of LDAP server.
29 * You can use any URL format that is supported by your LDAP extension.
02c81de4 30 * Examples:
31 * <ul>
32 * <li>'ldap.example.com' - connect to server on ldap.example.com address
d04cab42 33 * <li>'ldaps://ldap.example.com' - connect to server on ldap.example.com address
34 * and use SSL encrypted connection to default LDAPs port.
02c81de4 35 * </ul>
36 * defaults to imap server address.
37 * @link http://www.php.net/ldap-connect
38 * @global string $cpw_ldap_server
39 */
40global $cpw_ldap_server;
f6d11dea 41$cpw_ldap_server=$imapServerAddress;
02c81de4 42
43/**
d04cab42 44 * Port of LDAP server.
45 * Used only when $cpw_ldap_server specifies IP address or DNS name.
02c81de4 46 * @global integer $cpw_ldap_port
47 */
48global $cpw_ldap_port;
49$cpw_ldap_port=389;
50
51/**
d04cab42 52 * LDAP basedn that is used for binding to LDAP server.
02c81de4 53 * this option must be set to correct value.
5de3158e 54 * @global string $cpw_ldap_basedn;
02c81de4 55 */
56global $cpw_ldap_basedn;
57$cpw_ldap_basedn='';
58
59/**
60 * LDAP connection options
61 * @link http://www.php.net/ldap-set-option
62 * @global array $cpw_ldap_connect_opts
63 */
64global $cpw_ldap_connect_opts;
65$cpw_ldap_connect_opts=array();
66
67/**
d04cab42 68 * Controls use of starttls on LDAP connection.
69 * Requires PHP 4.2+, PHP LDAP extension with SSL support and
02c81de4 70 * PROTOCOL_VERSION => 3 setting in $cpw_ldap_connect_opts
71 * @global boolean $cpw_ldap_use_tls
72 */
73global $cpw_ldap_use_tls;
74$cpw_ldap_use_tls=false;
75
76/**
d04cab42 77 * BindDN that should be able to search LDAP directory and find DN used by user.
78 * Uses anonymous bind if set to empty string. You should not use DN with write
79 * access to LDAP directory here. Write access is not required.
02c81de4 80 * @global string $cpw_ldap_binddn
81 */
82global $cpw_ldap_binddn;
83$cpw_ldap_binddn='';
84
85/**
86 * password used for $cpw_ldap_binddn
d04cab42 87 * @global string $cpw_ldap_bindpw
02c81de4 88 */
89global $cpw_ldap_bindpw;
90$cpw_ldap_bindpw='';
91
92/**
93 * BindDN that should be able to change password.
444db9b3 94 * WARNING: sometimes user has enough privileges to change own password.
d04cab42 95 * If you leave default value, plugin will try to connect with DN that
02c81de4 96 * is detected in $cpw_ldap_username_attr=$username search and current
97 * user password will be used for authentication.
98 * @global string $cpw_ldap_admindn
99 */
100global $cpw_ldap_admindn;
101$cpw_ldap_admindn='';
102
103/**
104 * password used for $cpw_ldap_admindn
d04cab42 105 * @global string $cpw_ldap_adminpw
02c81de4 106 */
107global $cpw_ldap_adminpw;
108$cpw_ldap_adminpw='';
109
110/**
d04cab42 111 * LDAP attribute that stores username.
02c81de4 112 * username entry should be unique for $cpw_ldap_basedn
113 * @global string $cpw_ldap_userid_attr
114 */
115global $cpw_ldap_userid_attr;
116$cpw_ldap_userid_attr='uid';
117
118/**
119 * crypto that is used to encode new password
120 * If set to empty string, system tries to keep same encoding/hashing algorithm
121 * @global string $cpw_ldap_default_crypto
122 */
123global $cpw_ldap_default_crypto;
124$cpw_ldap_default_crypto='';
125
126/** end of default config */
127
128/** configuration overrides from config file */
129if (isset($cpw_ldap['server'])) $cpw_ldap_server=$cpw_ldap['server'];
130if (isset($cpw_ldap['port'])) $cpw_ldap_port=$cpw_ldap['port'];
131if (isset($cpw_ldap['basedn'])) $cpw_ldap_basedn=$cpw_ldap['basedn'];
132if (isset($cpw_ldap['connect_opts'])) $cpw_ldap_connect_opts=$cpw_ldap['connect_opts'];
133if (isset($cpw_ldap['use_tls'])) $cpw_ldap_use_tls=$cpw_ldap['use_tls'];
134if (isset($cpw_ldap['binddn'])) $cpw_ldap_binddn=$cpw_ldap['binddn'];
135if (isset($cpw_ldap['bindpw'])) $cpw_ldap_bindpw=$cpw_ldap['bindpw'];
136if (isset($cpw_ldap['admindn'])) $cpw_ldap_admindn=$cpw_ldap['admindn'];
137if (isset($cpw_ldap['adminpw'])) $cpw_ldap_adminpw=$cpw_ldap['adminpw'];
138if (isset($cpw_ldap['userid_attr'])) $cpw_ldap_userid_attr=$cpw_ldap['userid_attr'];
139if (isset($cpw_ldap['default_crypto'])) $cpw_ldap_default_crypto=$cpw_ldap['default_crypto'];
140
444db9b3 141/** make sure that setting does not contain mapping */
142$cpw_ldap_server=sqimap_get_user_server($cpw_ldap_server,$username);
143
02c81de4 144/**
145 * Adding plugin hooks
146 */
147global $squirrelmail_plugin_hooks;
148$squirrelmail_plugin_hooks['change_password_dochange']['ldap'] =
149 'cpw_ldap_dochange';
150$squirrelmail_plugin_hooks['change_password_init']['ldap'] =
151 'cpw_ldap_init';
152
153/**
9664b344 154 * Makes sure that required functions and configuration options are set.
02c81de4 155 */
156function cpw_ldap_init() {
157 global $color;
158 global $cpw_ldap_basedn;
159
02c81de4 160 // set initial value for error tracker
161 $cpw_ldap_initerr=false;
162
163 // check for ldap support in php
164 if (! function_exists('ldap_connect')) {
d04cab42 165 error_box(_("Current configuration requires LDAP support in PHP."),$color);
02c81de4 166 $cpw_ldap_initerr=true;
167 }
168
169 // chech required configuration settings.
170 if ($cpw_ldap_basedn=='') {
171 error_box(_("Plugin is not configured correctly."),$color);
172 $cpw_ldap_initerr=true;
173 }
174
d04cab42 175 // if error var is positive, close html and stop execution
02c81de4 176 if ($cpw_ldap_initerr) {
177 echo '</body></html>';
178 exit;
179 }
180}
181
182
183/**
9664b344 184 * Changes password. Main function attached to hook
02c81de4 185 * @param array $data The username/curpw/newpw data.
186 * @return array Array of error messages.
187 */
188function cpw_ldap_dochange($data) {
d04cab42 189 global $cpw_ldap_server, $cpw_ldap_port, $cpw_ldap_basedn,
02c81de4 190 $cpw_ldap_connect_opts,$cpw_ldap_use_tls,
191 $cpw_ldap_binddn, $cpw_ldap_bindpw,
192 $cpw_ldap_admindn, $cpw_ldap_adminpw;
193
194 // unfortunately, we can only pass one parameter to a hook function,
195 // so we have to pass it as an array.
196 $username = $data['username'];
197 $curpw = $data['curpw'];
198 $newpw = $data['newpw'];
199
200 // globalize current password.
201
202 $msgs = array();
203
204 /**
d04cab42 205 * connect to LDAP server
02c81de4 206 * hide ldap_connect() function call errors, because they are processed in script.
d04cab42 207 * any script execution error is treated as critical, error messages are dumped
208 * to $msgs and LDAP connection is closed with ldap_unbind(). all ldap_unbind()
02c81de4 209 * errors are suppressed. Any other error suppression should be explained.
210 */
211 $cpw_ldap_con=@ldap_connect($cpw_ldap_server);
212
213 if ($cpw_ldap_con) {
214 $cpw_ldap_con_err=false;
9664b344 215
02c81de4 216 // set connection options
217 if (is_array($cpw_ldap_connect_opts) && $cpw_ldap_connect_opts!=array()) {
9664b344 218 // ldap_set_option() is available only with openldap 2.x and netscape directory sdk.
219 if (function_exists('ldap_set_option')) {
220 foreach ($cpw_ldap_connect_opts as $opt => $value) {
221 // Make sure that constant is defined defore using it.
222 if (defined('LDAP_OPT_' . $opt)) {
223 // ldap_set_option() should not produce E_NOTICE or E_ALL errors and does not modify ldap_error().
224 // leave it without @ in order to see any weird errors
225 if (! ldap_set_option($cpw_ldap_con,constant('LDAP_OPT_' . $opt),$value)) {
226 // set error message
227 array_push($msgs,sprintf(_("Setting of LDAP connection option %s to value %s failed."),$opt,$value));
228 $cpw_ldap_con_err=true;
229 }
230 } else {
231 array_push($msgs,sprintf(_("Incorrect LDAP connection option: %s"),$opt));
232 $cpw_ldap_con_err=true;
233 }
02c81de4 234 }
9664b344 235 } else {
236 array_push($msgs,_("Current PHP LDAP extension does not allow use of ldap_set_option() function."));
237 $cpw_ldap_con_err=true;
02c81de4 238 }
239 }
240
241 // check for connection errors and stop execution if something is wrong
242 if ($cpw_ldap_con_err) {
243 @ldap_unbind($cpw_ldap_con);
244 return $msgs;
245 }
246
9664b344 247 // enable ldap starttls
02c81de4 248 if ($cpw_ldap_use_tls &&
249 check_php_version(4,2,0) &&
250 isset($cpw_ldap_connect_opts['PROTOCOL_VERSION']) &&
9664b344 251 $cpw_ldap_connect_opts['PROTOCOL_VERSION']>=3 &&
252 function_exists('ldap_start_tls')) {
253 // suppress ldap_start_tls errors and process error messages
254 if (! @ldap_start_tls($cpw_ldap_con)) {
255 array_push($msgs,
256 _("Unable to use TLS."),
257 sprintf(_("Error: %s"),ldap_error($cpw_ldap_con)));
02c81de4 258 $cpw_ldap_con_err=true;
259 }
260 } elseif ($cpw_ldap_use_tls) {
261 array_push($msgs,_("Unable to use LDAP TLS in current setup."));
262 $cpw_ldap_con_err=true;
263 }
264
265 // check for connection errors and stop execution if something is wrong
266 if ($cpw_ldap_con_err) {
267 @ldap_unbind($cpw_ldap_con);
268 return $msgs;
269 }
270
271 /**
d04cab42 272 * Bind to LDAP (use anonymous bind or unprivileged DN) in order to get user's DN
02c81de4 273 * hide ldap_bind() function call errors, because errors are processed in script
274 */
275 if ($cpw_ldap_binddn!='') {
276 // authenticated bind
277 $cpw_ldap_binding=@ldap_bind($cpw_ldap_con,$cpw_ldap_binddn,$cpw_ldap_bindpw);
278 } else {
279 // anonymous bind
280 $cpw_ldap_binding=@ldap_bind($cpw_ldap_con);
281 }
282
283 // check ldap_bind errors
284 if (! $cpw_ldap_binding) {
9664b344 285 array_push($msgs,
286 _("Unable to bind to LDAP server."),
287 sprintf(_("Server replied: %s"),ldap_error($cpw_ldap_con)));
02c81de4 288 @ldap_unbind($cpw_ldap_con);
289 return $msgs;
290 }
291
292 // find userdn
293 $cpw_ldap_search_err=cpw_ldap_uid_search($cpw_ldap_con,$cpw_ldap_basedn,$msgs,$cpw_ldap_res,$cpw_ldap_userdn);
294
295 // check for search errors and stop execution if something is wrong
296 if (! $cpw_ldap_search_err) {
9664b344 297 @ldap_unbind($cpw_ldap_con);
02c81de4 298 return $msgs;
299 }
300
301 /**
302 * unset $cpw_ldap_res2 variable, if such var exists.
d04cab42 303 * $cpw_ldap_res2 object can be set in two places and second place checks,
304 * if object was created in first place. if variable name matches (somebody
305 * uses $cpw_ldap_res2 in code or globals), incorrect validation might
306 * cause script errors.
02c81de4 307 */
308 if (isset($cpw_ldap_res2)) unset($cpw_ldap_res2);
309
310 // rebind as userdn or admindn
311 if ($cpw_ldap_admindn!='') {
312 // admindn bind
313 $cpw_ldap_binding=@ldap_bind($cpw_ldap_con,$cpw_ldap_admindn,$cpw_ldap_adminpw);
314
315 if ($cpw_ldap_binding) {
316 // repeat search in order to get password info. Password info should be unavailable in unprivileged bind.
317 $cpw_ldap_search_err=cpw_ldap_uid_search($cpw_ldap_con,$cpw_ldap_basedn,$msgs,$cpw_ldap_res2,$cpw_ldap_userdn);
318
319 // check for connection errors and stop execution if something is wrong
320 if (! $cpw_ldap_search_err) {
321 @ldap_unbind($cpw_ldap_con);
9664b344 322 // errors are added to msgs by cpw_ldap_uid_search()
02c81de4 323 return $msgs;
324 }
325
326 // we should check user password here.
9664b344 327 // suppress errors and check value returned by function call
328 $cpw_ldap_cur_pass_array=@ldap_get_values($cpw_ldap_con,
02c81de4 329 ldap_first_entry($cpw_ldap_con,$cpw_ldap_res2),'userpassword');
9664b344 330
331 // check if ldap_get_values() have found userpassword field
332 if (! $cpw_ldap_cur_pass_array) {
333 array_push($msgs,_("Unable to find user's password attribute."));
334 return $msgs;
335 }
02c81de4 336
337 // compare passwords
338 if (! cpw_ldap_compare_pass($cpw_ldap_cur_pass_array[0],$curpw,$msgs)) {
339 @ldap_unbind($cpw_ldap_con);
9664b344 340 // errors are added to $msgs by cpw_ldap_compare_pass()
02c81de4 341 return $msgs;
342 }
343 }
344 } else {
345 $cpw_ldap_binding=@ldap_bind($cpw_ldap_con,$cpw_ldap_userdn,$curpw);
346 }
347
348 if (! $cpw_ldap_binding) {
9664b344 349 array_push($msgs,
350 _("Unable to rebind to LDAP server."),
351 sprintf(_("Server replied: %s"),ldap_error($cpw_ldap_con)));
02c81de4 352 @ldap_unbind($cpw_ldap_con);
353 return $msgs;
354 }
355
356 // repeat search in order to get password info
357 if (! isset($cpw_ldap_res2))
358 $cpw_ldap_search_err=cpw_ldap_uid_search($cpw_ldap_con,$cpw_ldap_basedn,$msgs,$cpw_ldap_res2,$cpw_ldap_userdn);
359
360 // check for connection errors and stop execution if something is wrong
361 if (! $cpw_ldap_search_err) {
362 @ldap_unbind($cpw_ldap_con);
363 return $msgs;
364 }
365
9664b344 366 // getpassword. suppress errors and check value returned by function call
367 $cpw_ldap_cur_pass_array=@ldap_get_values($cpw_ldap_con,ldap_first_entry($cpw_ldap_con,$cpw_ldap_res2),'userpassword');
368
369 // check if ldap_get_values() have found userpassword field.
370 // Error differs from previous one, because user managed to authenticate.
371 if (! $cpw_ldap_cur_pass_array) {
372 array_push($msgs,_("LDAP server uses different attribute to store user's password."));
373 return $msgs;
374 }
02c81de4 375
376 // encrypt new password (old password is needed for plaintext encryption detection)
377 $cpw_ldap_new_pass=cpw_ldap_encrypt_pass($newpw,$cpw_ldap_cur_pass_array[0],$msgs,$curpw);
378
379 if (! $cpw_ldap_new_pass) {
380 @ldap_unbind($cpw_ldap_con);
381 return $msgs;
382 }
383
d264e600 384 // set new password. suppress ldap_modify errors. script checks and displays ldap_modify errors.
385 $ldap_pass_change=@ldap_modify($cpw_ldap_con,$cpw_ldap_userdn,array('userpassword'=>$cpw_ldap_new_pass));
02c81de4 386
387 // check if ldap_modify was successful
388 if(! $ldap_pass_change) {
389 array_push($msgs,ldap_error($cpw_ldap_con));
390 }
391
392 // close connection
393 @ldap_unbind($cpw_ldap_con);
394 } else {
395 array_push($msgs,_("Unable to connect to LDAP server."));
396 }
397 return $msgs;
398}
399
400/** backend support functions **/
401
402/**
d04cab42 403 * Sanitizes LDAP query strings.
02c81de4 404 * original code - ldapquery plugin.
405 * See rfc2254
406 * @link http://www.faqs.org/rfcs/rfc2254.html
407 * @param string $string
408 * @return string sanitized string
409 */
410function cpw_ldap_specialchars($string) {
411 $sanitized=array('\\' => '\5c',
412 '*' => '\2a',
413 '(' => '\28',
414 ')' => '\29',
415 "\x00" => '\00');
416
417 return str_replace(array_keys($sanitized),array_values($sanitized),$string);
418}
419
420/**
421 * returns crypto algorithm used in password.
422 * @param string $pass encrypted/hashed password
423 * @return string lowercased crypto algorithm name
424 */
425function cpw_ldap_get_crypto($pass,$curpass='') {
426 $ret = false;
427
428 if (preg_match("/^\{(.+)\}+/",$pass,$crypto)) {
429 $ret=strtolower($crypto[1]);
430 }
431
432 if ($ret=='crypt') {
433 // {CRYPT} can be standard des crypt, extended des crypt, md5 crypt or blowfish
d264e600 434 // depends on first salt symbols (ext_des = '_', md5 = '$1$', blowfish = '$2')
02c81de4 435 // and length of salt (des = 2 chars, ext_des = 9, md5 = 12, blowfish = 16).
436 if (preg_match("/^\{crypt\}\\\$1\\\$+/i",$pass)) {
437 $ret='md5crypt';
d264e600 438 } elseif (preg_match("/^\{crypt\}\\\$2+/i",$pass)) {
02c81de4 439 $ret='blowfish';
440 } elseif (preg_match("/^\{crypt\}_+/i",$pass)) {
441 $ret='extcrypt';
442 }
443 }
444
445 // maybe password is plaintext
446 if (! $ret && $curpass!='' && $pass==$curpass) $ret='plaintext';
447
448 return $ret;
449}
450
451/**
d04cab42 452 * Search LDAP for user id.
02c81de4 453 * @param object $ldap_con ldap connection
454 * @param string $ldap_basedn ldap basedn
455 * @param array $msgs error messages
456 * @param object $results ldap search results
457 * @param string $userdn DN of found entry
458 * @param boolean $onlyone require unique search results
459 * @return boolean false if connection failed.
460 */
461function cpw_ldap_uid_search($ldap_con,$ldap_basedn,&$msgs,&$results,&$userdn,$onlyone=true) {
462 global $cpw_ldap_userid_attr,$username;
463
464 $ret=true;
465
466 $results=ldap_search($ldap_con,$ldap_basedn,cpw_ldap_specialchars($cpw_ldap_userid_attr . '=' . $username));
467
468 if (! $results) {
9664b344 469 array_push($msgs,
470 _("Unable to find user's DN."),
471 _("Search error."),
472 sprintf(_("Error: %s"),ldap_error($ldap_con)));
02c81de4 473 $ret=false;
474 } elseif ($onlyone && ldap_count_entries($ldap_con,$results)>1) {
475 array_push($msgs,_("Multiple userid matches found."));
476 $ret=false;
477 } elseif (! $userdn = ldap_get_dn($ldap_con,ldap_first_entry($ldap_con,$results))) {
478 // ldap_get_dn() returned error
9664b344 479 array_push($msgs,
480 _("Unable to find user's DN."),
481 _("ldap_get_dn error."));
02c81de4 482 $ret=false;
483 }
484 return $ret;
485}
486
487/**
d04cab42 488 * Encrypts LDAP password
02c81de4 489 *
d04cab42 490 * if $cpw_ldap_default_crypto is set to empty string or $same_crypto is set,
02c81de4 491 * uses same crypto as in old password.
492 * See phpldapadmin password_hash() function
493 * @link http://phpldapadmin.sf.net
494 * @param string $pass string that has to be encrypted/hashed
495 * @param string $cur_pass_hash old password hash
496 * @param array $msgs error message
497 * @param string $curpass current password. Used for plaintext password detection.
498 * @return string encrypted/hashed password or false
499 */
500function cpw_ldap_encrypt_pass($pass,$cur_pass_hash,&$msgs,$curpass='') {
501 global $cpw_ldap_default_crypto;
502
503 // which crypto should be used to encode/hash password
504 if ($cpw_ldap_default_crypto=='') {
505 $ldap_crypto=cpw_ldap_get_crypto($cur_pass_hash,$curpass);
506 } else {
507 $ldap_crypto=$cpw_ldap_default_crypto;
508 }
509 return cpw_ldap_password_hash($pass,$ldap_crypto,$msgs);
510}
511
512/**
513 * create hashed password
514 * @param string $pass plain text password
515 * @param string $crypto used crypto algorithm
516 * @param array $msgs array used for error messages
517 * @param string $forced_salt salt that should be used during hashing.
518 * Is used only when is not set to empty string. Salt should be formated
519 * according to $crypto requirements.
520 * @return hashed password or false.
521 */
522function cpw_ldap_password_hash($pass,$crypto,&$msgs,$forced_salt='') {
523 // set default return code
524 $ret=false;
525
526 // lowercase crypto just in case
527 $crypto=strtolower($crypto);
528
529 // extra symbols used for random string in crypt salt
530 // squirrelmail GenerateRandomString() adds alphanumerics with third argument = 7.
531 $extra_salt_chars='./';
532
533 // encrypt/hash password
534 switch ($crypto) {
444db9b3 535 case 'md4':
536 // minimal requirement = php with mhash extension
537 if ( function_exists( 'mhash' ) && defined('MHASH_MD4')) {
538 $ret = '{MD4}' . base64_encode( mhash( MHASH_MD4, $pass) );
539 } else {
540 array_push($msgs,
541 sprintf(_("Unsupported crypto: %s"),'md4'),
542 _("PHP mhash extension is missing or does not support selected crypto."));
543 }
544 break;
02c81de4 545 case 'md5':
546 $ret='{MD5}' . base64_encode(pack('H*',md5($pass)));
547 break;
548 case 'smd5':
444db9b3 549 // minimal requirement = mhash extension with md5 support and php 4.0.4.
550 if( function_exists( 'mhash' ) && function_exists( 'mhash_keygen_s2k' ) && defined('MHASH_MD5')) {
02c81de4 551 sq_mt_seed( (double) microtime() * 1000000 );
552 if ($forced_salt!='') {
553 $salt=$forced_salt;
554 } else {
555 $salt = mhash_keygen_s2k( MHASH_MD5, $pass, substr( pack( "h*", md5( mt_rand() ) ), 0, 8 ), 4 );
556 }
557 $ret = "{SMD5}".base64_encode( mhash( MHASH_MD5, $pass.$salt ).$salt );
558 } else {
d264e600 559 // use two array_push calls in order to display messages in different lines.
9664b344 560 array_push($msgs,
561 sprintf(_("Unsupported crypto: %s"),'smd5'),
444db9b3 562 _("PHP mhash extension is missing or does not support selected crypto."));
563 }
564 break;
565 case 'rmd160':
566 // minimal requirement = php with mhash extension
567 if ( function_exists( 'mhash' ) && defined('MHASH_RIPEMD160')) {
568 $ret = '{RMD160}' . base64_encode( mhash( MHASH_RIPEMD160, $pass) );
569 } else {
570 array_push($msgs,
571 sprintf(_("Unsupported crypto: %s"),'ripe-md160'),
572 _("PHP mhash extension is missing or does not support selected crypto."));
02c81de4 573 }
574 break;
575 case 'sha':
d264e600 576 // minimal requirement = php 4.3.0+ or php with mhash extension
444db9b3 577 if ( function_exists('sha1') && defined('MHASH_SHA1')) {
d264e600 578 // use php 4.3.0+ sha1 function, if it is available.
9664b344 579 $ret = '{SHA}' . base64_encode(pack('H*',sha1($pass)));
d264e600 580 } elseif( function_exists( 'mhash' ) ) {
02c81de4 581 $ret = '{SHA}' . base64_encode( mhash( MHASH_SHA1, $pass) );
582 } else {
9664b344 583 array_push($msgs,
584 sprintf(_("Unsupported crypto: %s"),'sha'),
444db9b3 585 _("PHP mhash extension is missing or does not support selected crypto."));
02c81de4 586 }
587 break;
588 case 'ssha':
589 // minimal requirement = mhash extension and php 4.0.4
444db9b3 590 if( function_exists( 'mhash' ) && function_exists( 'mhash_keygen_s2k' ) && defined('MHASH_SHA1')) {
02c81de4 591 sq_mt_seed( (double) microtime() * 1000000 );
592 if ($forced_salt!='') {
593 $salt=$forced_salt;
594 } else {
595 $salt = mhash_keygen_s2k( MHASH_SHA1, $pass, substr( pack( "h*", md5( mt_rand() ) ), 0, 8 ), 4 );
596 }
597 $ret = "{SSHA}".base64_encode( mhash( MHASH_SHA1, $pass.$salt ).$salt );
598 } else {
9664b344 599 array_push($msgs,
600 sprintf(_("Unsupported crypto: %s"),'ssha'),
444db9b3 601 _("PHP mhash extension is missing or does not support selected crypto."));
02c81de4 602 }
603 break;
604 case 'crypt':
605 if (defined('CRYPT_STD_DES') && CRYPT_STD_DES==1) {
606 $ret = '{CRYPT}' . crypt($pass,GenerateRandomString(2,$extra_salt_chars,7));
607 } else {
9664b344 608 array_push($msgs,
609 sprintf(_("Unsupported crypto: %s"),'crypt'),
610 _("System crypt library doesn't support standard DES crypt."));
02c81de4 611 }
612 break;
613 case 'md5crypt':
614 // check if crypt() supports md5
615 if (defined('CRYPT_MD5') && CRYPT_MD5==1) {
616 $ret = '{CRYPT}' . crypt($pass,'$1$' . GenerateRandomString(9,$extra_salt_chars,7));
617 } else {
9664b344 618 array_push($msgs,
619 sprintf(_("Unsupported crypto: %s"),'md5crypt'),
620 _("System crypt library doesn't have MD5 support."));
02c81de4 621 }
622 break;
623 case 'extcrypt':
624 // check if crypt() supports extended des
625 if (defined('CRYPT_EXT_DES') && CRYPT_EXT_DES==1) {
d04cab42 626 // FIXME: guinea pigs with extended des support needed.
02c81de4 627 $ret = '{CRYPT}' . crypt($pass,'_' . GenerateRandomString(8,$extra_salt_chars,7));
628 } else {
9664b344 629 array_push($msgs,
630 sprintf(_("Unsupported crypto: %s"),'ext_des'),
631 _("System crypt library doesn't support extended DES crypt."));
02c81de4 632 }
633 break;
634 case 'blowfish':
635 // check if crypt() supports blowfish
636 if (defined('CRYPT_BLOWFISH') && CRYPT_BLOWFISH==1) {
d04cab42 637 // FIXME: guinea pigs with blowfish support needed.
d264e600 638 $ret = '{CRYPT}' . crypt($pass,'$2a$12$' . GenerateRandomString(13,$extra_salt_chars,7));
02c81de4 639 } else {
9664b344 640 array_push($msgs,
641 sprintf(_("Unsupported crypto: %s"),'Blowfish'),
642 _("System crypt library doesn't have Blowfish support."));
02c81de4 643 }
644 break;
645 case 'plaintext':
646 // clear plain text password
647 $ret=$pass;
648 break;
649 default:
650 array_push($msgs,sprintf(_("Unsupported crypto: %s"),
651 (is_string($ldap_crypto) ? htmlspecialchars($ldap_crypto) : _("unknown"))));
652 }
653 return $ret;
654}
655
656/**
657 * compares two passwords
658 * Code reuse. See phpldapadmin password_compare() function.
659 * Some parts of code was rewritten to backend specifics.
660 * @link http://phpldapadmin.sf.net
444db9b3 661 * @param string $pass_hash hashed password string with password type indicators
662 * @param string $pass_clear plain text password
663 * @param array $msgs error messages
02c81de4 664 * @return boolean true, if passwords match
665 */
666function cpw_ldap_compare_pass($pass_hash,$pass_clear,&$msgs) {
667 $ret=false;
668
669 if( preg_match( "/{([^}]+)}(.*)/", $pass_hash, $cypher ) ) {
670 $pass_hash = $cypher[2];
671 $_cypher = strtolower($cypher[1]);
672 } else {
673 $_cypher = NULL;
674 }
675
676 switch( $_cypher ) {
677 case 'ssha':
678 // Salted SHA
679 // check for mhash support
444db9b3 680 if ( function_exists('mhash') && defined('MHASH_SHA1')) {
02c81de4 681 $hash = base64_decode($pass_hash);
682 $salt = substr($hash, -4);
683 $new_hash = base64_encode( mhash( MHASH_SHA1, $pass_clear.$salt).$salt );
684 if( strcmp( $pass_hash, $new_hash ) == 0 )
685 $ret=true;
686 } else {
9664b344 687 array_push($msgs,
688 _("Unable to validate user's password."),
444db9b3 689 _("PHP mhash extension is missing or does not support selected crypto."));
02c81de4 690 }
691 break;
692 case 'smd5':
693 // Salted MD5
694 // check for mhash support
444db9b3 695 if ( function_exists('mhash') && defined('MHASH_MD5')) {
02c81de4 696 $hash = base64_decode($pass_hash);
697 $salt = substr($hash, -4);
698 $new_hash = base64_encode( mhash( MHASH_MD5, $pass_clear.$salt).$salt );
699 if( strcmp( $pass_hash, $new_hash ) == 0)
700 $ret=true;
701 } else {
9664b344 702 array_push($msgs,
703 _("Unable to validate user's password."),
444db9b3 704 _("PHP mhash extension is missing or does not support selected crypto."));
02c81de4 705 }
706 break;
707 case 'sha':
708 // SHA crypted passwords
709 if( strcasecmp( cpw_ldap_password_hash($pass_clear,'sha',$msgs), "{SHA}".$pass_hash ) == 0)
710 $ret=true;
711 break;
444db9b3 712 case 'rmd160':
713 // RIPE-MD160 crypted passwords
714 if( strcasecmp( cpw_ldap_password_hash($pass_clear,'rmd160',$msgs), "{RMD160}".$pass_hash ) == 0 )
715 $ret=true;
716 break;
02c81de4 717 case 'md5':
718 // MD5 crypted passwords
d264e600 719 if( strcasecmp( cpw_ldap_password_hash($pass_clear,'md5',$msgs), "{MD5}".$pass_hash ) == 0 )
02c81de4 720 $ret=true;
721 break;
444db9b3 722 case 'md4':
723 // MD4 crypted passwords
724 if( strcasecmp( cpw_ldap_password_hash($pass_clear,'md4',$msgs), "{MD4}".$pass_hash ) == 0 )
725 $ret=true;
726 break;
02c81de4 727 case 'crypt':
728 // Crypt passwords
d264e600 729 if( preg_match( "/^\\\$2+/",$pass_hash ) ) { // Check if it's blowfish crypt
02c81de4 730 // check CRYPT_BLOWFISH here.
731 // ldap server might support it, but php can be on other OS
732 if (defined('CRYPT_BLOWFISH') && CRYPT_BLOWFISH==1) {
d264e600 733 if( crypt( $pass_clear, $pass_hash ) == $pass_hash )
02c81de4 734 $ret=true;
735 } else {
9664b344 736 array_push($msgs,
737 _("Unable to validate user's password."),
738 _("Blowfish is not supported by webserver's system crypt library."));
02c81de4 739 }
740 } elseif( strstr( $pass_hash, '$1$' ) ) { // Check if it's md5 crypt
741 // check CRYPT_MD5 here.
742 // ldap server might support it, but php might be on other OS
743 if (defined('CRYPT_MD5') && CRYPT_MD5==1) {
744 list(,$type,$salt,$hash) = explode('$',$pass_hash);
745 if( crypt( $pass_clear, '$1$' .$salt ) == $pass_hash )
746 $ret=true;
747 } else {
9664b344 748 array_push($msgs,
749 _("Unable to validate user's password."),
750 _("MD5 is not supported by webserver's system crypt library."));
02c81de4 751 }
752 } elseif( strstr( $pass_hash, '_' ) ) { // Check if it's extended des crypt
753 // check CRYPT_EXT_DES here.
754 // ldap server might support it, but php might be on other OS
755 if (defined('CRYPT_EXT_DES') && CRYPT_EXT_DES==1) {
756 if( crypt( $pass_clear, $pass_hash ) == $pass_hash )
757 $ret=true;
758 } else {
9664b344 759 array_push($msgs,
760 _("Unable to validate user's password."),
761 _("Extended DES crypt is not supported by webserver's system crypt library."));
02c81de4 762 }
763 } else {
764 // it is possible that this test is useless and any crypt library supports it, but ...
765 if (defined('CRYPT_STD_DES') && CRYPT_STD_DES==1) {
766 // plain crypt password
767 if( crypt($pass_clear, $pass_hash ) == $pass_hash )
768 $ret=true;
769 } else {
9664b344 770 array_push($msgs,
771 _("Unable to validate user's password."),
772 _("Standard DES crypt is not supported by webserver's system crypt library."));
02c81de4 773 }
774 }
775 break;
d264e600 776 // No crypt is given, assume plaintext passwords are used
02c81de4 777 default:
778 if( $pass_clear == $pass_hash )
779 $ret=true;
780 break;
781 }
d264e600 782 if (! $ret && empty($msgs)) {
02c81de4 783 array_push($msgs,CPW_CURRENT_NOMATCH);
784 }
785 return $ret;
786}
787?>