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