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