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