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