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