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