1<?php
2/**
3 * Change LDAP Password plugin functions
4 * @copyright (c) 2000-2003 Simon Annetts <simon@ateb.co.uk>
5 * @copyright (c) 2006-2007 The SquirrelMail Project Team
6 * @copyright (c) 2007 The NaSMail Project
7 * @license http://opensource.org/licenses/gpl-license.php GNU Public License
8 * @version $Id: functions.php,v 1.12 2007/07/21 06:56:42 tokul Exp $
9 * @package plugins
10 * @subpackage change_ldappass
11 */
12
13/** load configuration */
14include_once(SM_PATH . 'plugins/change_ldappass/load_config.php');
15
16/**
17 * Checks user input
18 * @return array
19 */
20function change_ldappass_check() {
21    global $lcp_crack_dict;
22
23    $cp_oldpass = '';
24    $cp_newpass = '';
25    $cp_verify = '';
26    sqgetGlobalVar('cp_oldpass',$cp_oldpass,SQ_POST);
27    sqgetGlobalVar('cp_newpass',$cp_newpass,SQ_POST);
28    sqgetGlobalVar('cp_verify',$cp_verify,SQ_POST);
29
30    $Messages = array();
31    if (! extension_loaded('ldap')) {
32        array_push($Messages,_("PHP LDAP extension is not available."));
33    }
34    if ($cp_oldpass == '') {
35        array_push($Messages, _("You must type in your old password."));
36    }
37    if ($cp_newpass == '') {
38        array_push($Messages, _("You must type in a new password."));
39    }
40
41    if ($cp_verify == '') {
42        array_push($Messages,_("You must also type in your new password in the verify box."));
43    }
44
45    if ($cp_newpass != '' && $cp_verify != $cp_newpass) {
46        array_push($Messages, _("Your new password doesn't match the verify password."));
47    }
48
49    if (!preg_match("/^[A-Za-z0-9_%=:@#~,\\^\\*\\(\\)\\-\\+\\[\\]\\{\\}\\.\\?]+$/",$cp_newpass)) {
50        // i18n: comma separated list of acceptable characters is listed in next line
51        array_push($Messages,
52                   _("Passwords can only contain the following characters:"),
53                   "A-Z, a-z, 0-9, %^*()-_+=[]{}:@#~,.?");
54    }
55
56    // PHP crack support from NaSMail change_password plugin
57    if (! empty($lcp_crack_dict)) {
58        if (! function_exists('crack_opendict')) {
59            $Messages[] = _("PHP Crack extension is not available.");
60        } else {
61            // if crack_opendict command fails, it outputs E_WARNING,
62            // Suppress it and handle error internally. assume that it is dictionary open failure
63            if ($dict = @crack_opendict($lcp_crack_dict)) {
64                if (! crack_check($dict,$cp_newpass)) {
65                    $Messages[] = lcp_translate_crack_msg(crack_getlastmessage());
66                    $Messages[] = _("Please choose stronger password.");
67                }
68                crack_closedict($dict);
69            } else {
70                $Messages[] = sprintf(_("Could not open crack dictionary: %s"),$lcp_crack_dict);
71            }
72        }
73    }
74
75    if (count($Messages)) {
76        return $Messages;
77    }
78    return change_ldappass_go($cp_oldpass, $cp_newpass);
79}
80
81/**
82 * Changes password
83 * @param string $cp_oldpass
84 * @param string $cp_newpass
85 */
86function change_ldappass_go($cp_oldpass, $cp_newpass) {
87    global $username, $ldapsmb_object, $force_smb_sync, $ldap_server,
88        $ldap_protocol_version, $ldap_base_dn, $ldap_password_field,
89        $ldap_user_field, $query_dn, $query_pw, $ldap_filter,
90        $no_bind_as_user, $ldap_bind_as_manager, $ldap_manager_dn,
91        $ldap_manager_pw, $change_ldapsmb, $mkntpwd,
92        $ldapsmb_ntpassword, $ldapsmb_lmpassword, $change_smb,
93        $smb_host, $smb_passwd, $debug;
94
95    $Messages = array();
96    if ($debug) {
97        array_push($Messages, 'Connecting to LDAP Server');
98    }
99
100    $ds=ldap_connect($ldap_server);
101    if (! $ds) {
102        array_push($Messages, _("Can't connect to Directory Server, please try later!"));
103        return $Messages;
104    }
105
106    if (function_exists( 'ldap_set_option')) {
107        if (! @ldap_set_option($ds,LDAP_OPT_PROTOCOL_VERSION,$ldap_protocol_version)) {
108            array_push($Messages, _("Unable to set LDAP bind protocol version."));
109            if ($debug) {
110                array_push($Messages, htmlspecialchars("LDAP bind protocol version: $ldap_protocol_version"));
111            }
112            return $Messages;
113        } else {
114            if ($debug) {
115                array_push($Messages, htmlspecialchars("LDAP protocol version was set to $ldap_protocol_version"));
116            }
117        }
118    }
119
120
121    // first bind and try to determine the correct dn for the user with uid=$username
122    if (! @ldap_bind($ds, $query_dn, $query_pw)) {
123        array_push($Messages, _("LDAP bind failed."),
124                   sprintf(_("Error: %s"),htmlspecialchars(ldap_err2str(ldap_errno($ds)))));
125        if ($debug) {
126            array_push($Messages,
127                       htmlspecialchars("LDAP server: $ldap_server"),
128                       htmlspecialchars('BIND-DN: ' . (! empty($query_dn) ? $query_dn : 'anonymous')));
129        }
130        return $Messages;
131    } else {
132        if ($debug) {
133            array_push($Messages,
134                       'LDAP bind successful.',
135                       htmlspecialchars("LDAP server: $ldap_server"),
136                       htmlspecialchars('BIND-DN: ' . (! empty($query_dn) ? $query_dn : 'anonymous')));
137        }
138    }
139
140    if (!empty($ldap_filter)) {
141        if (!preg_match('/^\\(.+\\)$/',$ldap_filter)) {
142            $ldap_filter = '(' . $ldap_filter . ')';
143        }
144        $filter = "(&$ldap_filter($ldap_user_field=$username))";
145    } else {
146        $filter = "($ldap_user_field=$username)";
147    }
148
149    // Hide ldap_search notices
150    $sr=@ldap_search($ds,$ldap_base_dn,$filter,array('dn')); //search for uid
151    if (! $sr) {
152        array_push($Messages, _("LDAP search failed."),
153                   sprintf(_("Error: %s"),htmlspecialchars(ldap_err2str(ldap_errno($ds)))));
154        if ($debug) {
155            array_push($Messages,
156                       htmlspecialchars("BASE DN: $ldap_base_dn"),
157                       htmlspecialchars("Query: ($ldap_user_field=$username)"));
158        }
159        return $Messages;
160    }
161
162    if (ldap_count_entries($ds,$sr)>1) {
163        array_push($Messages, _("Duplicate login entries detected, cannot change password!"));
164        if ($debug) {
165            array_push($Messages,ldap_debug_print_array(ldap_get_entries($ds,$sr)));
166        }
167        return $Messages;
168    }
169
170    if (ldap_count_entries($ds,$sr)==0) {
171        array_push($Messages, _("Your login account was not found in the LDAP database, cannot change password!"));
172        return $Messages;
173    }
174
175    $info=ldap_get_entries($ds,$sr);
176    if ($debug) {
177        array_push($Messages,ldap_debug_print_array($info));
178    }
179    $dn=$info[0]["dn"]; //finally get the full users dn
180
181    // now rebind to the database as user to verify password.
182    if (! $no_bind_as_user) {
183        if (! @ldap_bind($ds,$dn,$cp_oldpass)) { //if we can't bind as the user then the old passwd must be wrong
184            array_push($Messages, _("Your old password is not correct."));
185            if ($debug) {
186                array_push($Messages,
187                           'LDAP bind failed.',
188                           htmlspecialchars("BIND-DN: $dn"));
189            }
190            return $Messages;
191        } else {
192            if ($debug) {
193                array_push($Messages,
194                           'LDAP bind successful.',
195                           htmlspecialchars("BIND-DN: $dn"));
196            }
197        }
198    } elseif ($ldap_bind_as_manager) {
199        // Now, if needed, we rebind as the manager so we can read passwords and make changes.
200        if (! @ldap_bind($ds,$ldap_manager_dn,$ldap_manager_pw)) {
201            array_push($Messages, _("LDAP bind failed."));
202            if ($debug){
203                array_push($Messages, htmlspecialchars("BIND-DN: $ldap_manager_dn"));
204            }
205            return $Messages;
206        } else {
207            if ($debug) {
208                array_push($Messages,
209                           'LDAP bind successful.',
210                           htmlspecialchars("BIND-DN: $ldap_manager_dn"));
211            }
212        }
213    }
214    //check the db again, this time so we get the password field returned
215    // (use ldap_read() in order to lookup only located dn entry)
216    $sr=@ldap_read($ds,$dn,"($ldap_user_field=$username)",array('dn',$ldap_password_field,$ldap_user_field,'objectclass'));
217    $info = ldap_get_entries($ds, $sr);
218    if ($debug) {
219        array_push($Messages,ldap_debug_print_array($info));
220    }
221
222    if (isset($info[0][$ldap_password_field][0])) {
223        $storedpass = $info[0][$ldap_password_field][0];
224    } else {
225        array_push($Messages, _("We could not retrieve your old password from the LDAP server."));
226        if ($debug) {
227            array_push($Messages,
228                       htmlspecialchars("Configured password field: $ldap_password_field"));
229        }
230        return $Messages;
231    }
232
233    //this next code tries to identify the correct password type
234    //*Note* I've not tested all types here as I cannot reproduce some setups
235    //Please let me know if the code does not work for you, or if you have code that will work
236    //for a particular type.
237
238    //password types:
239    //{crypt} salted passwords of type DES, MD5 and Blowfish
240    //{MD5} unsalted password in MD5 format
241    //{SHA} unsalted password in SHA format
242    //{SMD5} salted md5
243    //{SSHA} salted sha
244
245    //lets try to determine the encrytion method of the stored password
246    $p=split("}",$storedpass); //split the password
247    $ctype=strtolower($p[0]);  //into the {crypttype}
248    $lpass=$p[1];              //and the password
249    //if the stored password is {crypt} then its salted, but which sub-type?
250    switch ($ctype) {
251    case "{crypt":
252        $pl=strlen($lpass); // We'll look at the length and salt of what's already stored to determine the crypt sub-type
253        $stype="DES";       //sensible default if not detected
254        if ($pl>=34 and substr($lpass,0,3)=="$1$") $stype="MD5";
255        if ($pl>=34 and substr($lpass,0,3)=="$2$") $stype="BLOWFISH";
256        if ($debug) array_push($Messages, _("Password type is") . " {crypt}, sub-type $stype");
257        $cpass=ldap_crypt_passwd($cp_oldpass,$lpass,$stype); //crypt up our old password so we can check it again
258        break;
259    case "{md5":
260        if ($debug) array_push($Messages, _("Password type is") . " {MD5}" );
261        $cpass=ldap_md5_passwd($cp_oldpass);
262        break;
263    case "{sha":
264        if ($debug) array_push($Messages, _("Password type is") . " {SHA}");
265        if (!function_exists('sha1') && ! extension_loaded('mhash')) {
266            array_push($Messages,
267                       _("Unsupported password schema. Insufficient PHP version or PHP mhash extension is not available."));
268            return $Messages;
269        }
270        $cpass=ldap_sha_passwd($cp_oldpass);
271        break;
272    case "{smd5":
273        if ($debug) array_push($Messages, _("Password type is") . " {SMD5}");
274        $hash = base64_decode($lpass) ;
275        $salt = substr($hash, 16);
276        $cpass = base64_encode(pack("H*", md5($cp_oldpass . $salt)).$salt);
277        break;
278    case "{ssha":
279        if ($debug) array_push($Messages, _("Password type is") . " {SSHA}");
280        if (!function_exists('sha1') && ! extension_loaded('mhash')) {
281            array_push($Messages,
282                        _("Unsupported password schema. Insufficient PHP version or PHP mhash extension is not available."));
283            return $Messages;
284        }
285        $hash = base64_decode($lpass) ;
286        $salt = substr($hash, 20);
287        $cpass=ldap_ssha_passwd($cp_oldpass,$salt);
288        break;
289    default:                        // Use plain text password
290        $cpass=$cp_oldpass;
291        $lpass=$storedpass;     // Override $lpass as it is truncated from the original
292        break;
293    }
294    //now check again the stored password against the encrypted version of the supplied old password
295    if ($lpass != $cpass) {
296        array_push($Messages, _("Your old password is not correct."));
297        if ($debug) {
298            array_push($Messages,
299                       htmlspecialchars("Stored Password: $lpass"),
300                       htmlspecialchars("Old Password: $cpass"));
301        }
302        return $Messages;
303    }
304    //Make sure the new passwd generation uses the encryption method of the previous password
305    switch ($ctype) {
306    case "{crypt":
307        $newpass="{crypt}".ldap_crypt_passwd($cp_newpass,ldap_generate_salt($stype),$stype);
308        break;
309    case "{md5":
310        $newpass="{MD5}".ldap_md5_passwd($cp_newpass);
311        break;
312    case "{sha":
313        $newpass="{SHA}".ldap_sha_passwd($cp_newpass);
314        break;
315    case "{smd5":
316        $newpass="{SMD5}".ldap_smd5_passwd($cp_newpass);
317        break;
318    case "{ssha":
319        $newpass="{SSHA}".ldap_ssha_passwd($cp_newpass);
320        break;
321        // more password types should go here ;-) and the functions to drive them below.
322    default:                        // Use plain text password
323        $newpass=$cp_newpass;
324        break;
325    }
326    if ($debug) {
327        array_push($Messages, htmlspecialchars("New Password:  $newpass"));
328    }
329
330    $newinfo=array();
331    $newinfo[$ldap_password_field][0]=$newpass;
332
333    if (isset($info[0]['objectclass'])) {
334        $objects = $info[0]['objectclass'];
335        array_walk($objects,'lcp_arraytolower');
336    } else {
337        // failsafe for missing objectclass data
338        $objects = array();
339    }
340    if ($change_ldapsmb && in_array($ldapsmb_object,$objects) &&
341        ($force_smb_sync || sqgetGlobalVar('sync_smb_pass',$sync_smb_pass,SQ_POST))) {
342        $exe = "$mkntpwd " . escapeshellarg($cp_newpass) . " 2>&1" ;
343        if ($debug) array_push($Messages,$exe);
344        $retarray = array();
345        $retval = 1;
346        $ntString = exec($exe, $retarray, $retval);
347        if ($debug) $Messages = array_merge($Messages,$retarray);
348        if ( $retval == "0" && preg_match("/^[0-9A-F]+:[0-9A-F]+$/",$ntString )) {
349            list($lmPassword, $ntPassword) = explode (":", $ntString);
350            $newinfo[$ldapsmb_ntpassword] = $ntPassword;
351            $newinfo[$ldapsmb_lmpassword] = $lmPassword;
352        } else {  // could not generate ntlm passwords
353            array_push($Messages,_("Could not generate NTLM password hashes!"));
354            if (!empty($retarray)) {
355                $retmsg = implode("\n",$retarray);
356                array_push($Messages,sprintf(_("Error: %s"),htmlspecialchars($retmsg)));
357            }
358            return $Messages;
359        }
360    }
361
362    if (@ldap_modify($ds,$dn,$newinfo)) {
363        $smb=0;
364        if ($change_smb &&
365            ($force_smb_sync || sqgetGlobalVar('sync_smb_pass',$sync_smb_pass,SQ_POST))) {
366            // First we print three lines in stdin
367            $exe = 'echo -e ' . escapeshellarg($cp_oldpass . "\\n" . $cp_newpass . "\\n" . $cp_newpass);
368            // then pipe to smbpasswd
369            $exe.= " | $smb_passwd ";
370            // add remote machine name
371            if (!empty($smb_host)) {
372                $exe.= '-r ' . escapeshellarg($smb_host);
373            }
374            // add username, get passwords from stdin (-s), redirect output to stdout
375            $exe.= " -U " . escapeshellarg($username)
376                 . " -s 2>&1";
377
378            // Save used command for debugging
379            if ($debug) {
380                array_push($Messages,$exe);
381            }
382
383            // initial values
384            $retarr = array();
385            $retval = 1;
386            // exec returns last line, but we don't need it. Same line is stored in $retarr
387            exec($exe,$retarr,$retval);
388            // Check $retval. 0 = success, 1 = failure
389            if ($retval) {
390                // push $retarr to $Messages
391                foreach($retarr as $retline) {
392                    array_push($Messages, htmlspecialchars($retline));
393                }
394            } else {
395                $smb=1 ;
396                if ($debug) {
397                    foreach($retarr as $retline) {
398                        array_push($Messages, htmlspecialchars($retline));
399                    }
400                }
401            }
402        } else {
403            $smb=1;
404        }
405        if ($smb) {
406            // load sqm_baseuri() function for SM 1.4.0-1.4.5
407            include_once(SM_PATH . 'functions/display_messages.php');
408            $base_uri = sqm_baseuri();
409            // Write new cookies for the password
410            $onetimepad = OneTimePadCreate(strlen($cp_newpass));
411            $_SESSION['onetimepad']=$onetimepad; //do I need to do this now?
412            $key = OneTimePadEncrypt($cp_newpass, $onetimepad);
413            $_COOKIES['key']=$key;
414            setcookie("key", $key, 0, $base_uri);
415            // Give feedback if password change was successful.
416            if (! $debug) {
417                array_push($Messages, _("Password changed successfully"));
418                return $Messages ;
419            }
420            return $Messages;
421        } else { //smbpasswd change failed so we must re sync the ldap password back to its original
422            $newinfo[$ldap_password_field][0]=$storedpass;
423            if (@ldap_modify($ds,$dn,$newinfo)) {
424                array_push($Messages, _("SMB Password change was not successful, so LDAP not changed!"));
425            } else {
426                array_push($Messages, _("Due to numerous password modification errors your LDAP and SMB passwords are out of sync. Please contact your administrator."));
427            }
428            return $Messages;
429        }
430    } else {
431        array_push($Messages, _("LDAP Password change was not successful!"));
432        if ($debug) array_push($Messages, _("LDAP ERROR => " . ldap_error($ds))) ;
433        return $Messages;
434    }
435}
436
437// Generate an unsalted SHA1 pw. This should work withNetscape messaging / directory server 4+
438function ldap_sha_passwd($clear_pw) {
439    if(function_exists('sha1')) {
440        $hash = pack("H*",sha1($clear_pw));
441    } else if (function_exists('mHash')) {
442        $hash = mHash(MHASH_SHA1, $clear_pw);
443    } else {
444        echo "Error: You will need php >= 4.3.0 or php compiled with MHASH if you are going to use SHA or SSHA passwords.";
445        exit();
446    }
447    return base64_encode($hash);
448}
449
450/**
451 * Generate a salted SHA1 pw.
452 * @param string $clean_pw
453 * @param string $salt
454 * @return string
455 */
456function ldap_ssha_passwd($clear_pw,$salt=null) {
457    if (!isset($salt)){
458        // set seed for the random number generator
459        mt_srand((double)microtime()*1000000);
460        $salt = substr(md5(mt_rand()), 4, 8);
461    }
462    if(function_exists('sha1')) {
463        $hash = pack("H*",sha1($clear_pw . $salt));
464    } else if (function_exists('mHash')) {
465        $hash = mHash(MHASH_SHA1, $clear_pw . $salt);
466    } else {
467        echo "Error: You will need php >= 4.3.0 or php compiled with MHASH if you are going to use SHA or SSHA passwords.";
468        exit();
469    }
470    return base64_encode($hash . $salt);
471}
472
473// Generate an unsalted MD5 pw. This works fine with OpenLDAP.
474function ldap_md5_passwd($clear_pw) {
475    return base64_encode(pack("H*", md5($clear_pw)));
476}
477
478function ldap_smd5_passwd($clear_pw) {
479    $salt = myhash_keyge_s2k($clear_pw, 4);
480    $new_password = base64_encode(pack("H*", md5($clear_pw . $salt)) . $salt);
481    return $new_password ;
482}
483
484/**
485 * Generates salted crypt passwords
486 *
487 * @param string $password
488 * @param string $salt
489 * @param string $stype Password type. MD5, BLOWFISH, DES. If other string is
490 * used, since 2.2 function defaults to DES.
491 * @return string
492 */
493function ldap_crypt_passwd($password,$salt,$stype) {
494    if ($stype=="MD5")      return crypt($password,substr($salt,0,12)); //MD5 uses 12 chr salt
495    if ($stype=="BLOWFISH") return crypt($password,substr($salt,0,16)); //BLOWFISH uses 16 chr salt
496    // DES or something else
497    return crypt($password,substr($salt,0,2));      //crypt uses 2 chr salt
498}
499
500/**
501 * Generates salt
502 * @param string $stype Salt type. MD5, BLOWFISH or DES. If other string is
503 * used, since 2.2 function defaults to DES.
504 * @return string Salt string
505 */
506function ldap_generate_salt($stype) {
507    $salt=""; //generate a salt using characters [A-Z][a-z][0-9]./
508    $chars="./0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
509    for ($i=0;$i<16;$i++) {
510        $salt.=$chars[mt_rand(0,strlen($chars))];
511    }
512    //return salts longer than we need but these will be trimmed in the ldap_crypt_passwd function
513    if ($stype=="MD5")      return "$1$".$salt;
514    if ($stype=="BLOWFISH") return "$2$".$salt;
515    // DES or something else
516    return $salt;
517}
518
519/**
520 * This and md5 implimentation of the Salted S2K algorithm as
521 * specified in the OpenPGP document (RFC 2440).
522 * basically a non mhash dependant version of mhash_keygen_s2k
523 * @param string $pass
524 * @param integer $bytes
525 * @return string
526 */
527function myhash_keyge_s2k($pass, $bytes ){
528    $salt=substr(pack("h*", md5(mt_rand())), 0, 8);
529    return substr(pack("H*", md5($salt . $pass)), 0, $bytes);
530}
531
532/**
533 * Debug function used to print ldap search results
534 * @param array $array
535 */
536function ldap_debug_print_array($array) {
537    $out="<br>--------------------------------------------------------<br>\n";
538    $out.=ldap_debug_print_array1($array,"");
539    $out.= "<br>--------------------------------------------------------<br>\n";
540    return $out;
541}
542
543/**
544 * Prints array recursively
545 * @param mixed $array
546 * @param string $out
547 * @return string
548 */
549function ldap_debug_print_array1($array,$out) {
550    if(gettype($array)=="array") {
551        $out.="<ul>";
552        while (list($index, $subarray) = each($array) ) {
553            $out.='<li>' . htmlspecialchars($index) . " <code>=&gt;</code>";
554            $out=ldap_debug_print_array1($subarray,$out);
555            $out.="</li>";
556        }
557        $out.="</ul>";
558    } else {
559        $out.=htmlspecialchars($array);
560    }
561    return $out;
562}
563
564/**
565 * Translates PHP Crack extension messages
566 *
567 * cpw_translate_crack_msg() function from NaSMail change_password plugin.
568 * @param string $err error from crack_getlastmessage()
569 * @return string translated error message or original error
570 * @since 2.2
571 */
572function lcp_translate_crack_msg($err) {
573    /**
574     * PHP crack 0.4 extension errors
575     * from cracklib_fascist_look_ex()
576     * - it's WAY too short
577     * - it is too short
578     * - it is too simplistic/systematic
579     * - it looks like a National Insurance number.
580     * - it is all whitespace
581     * - it does not contain enough DIFFERENT characters
582     * - it is based on a dictionary word
583     * - it is based on a (reversed) dictionary word
584     * from cracklib_fascist_gecos() (checks are not executed on Win32)
585     * - you are not registered in the password file
586     * - it is based on your username
587     * - it's derivable from your password entry
588     * - it is derivable from your password entry
589     * - it is derived from your password entry
590     * - it's derived from your password entry
591     * - it is based upon your password entry
592     */
593
594    // can't use strcasecmp(). No locale insensitive string comparison functions in PHP.
595    // internal implementation will slow things down.
596    switch($err) {
597    case 'it\'s WAY too short':
598    case 'it is too short':
599        $err = _("Password is too short.");
600        break;
601    case 'it is too simplistic/systematic':
602        $err = _("New password is too simplistic/systematic.");
603        break;
604    case 'it looks like a National Insurance number.':
605        // password looks like personal identification number used
606        // in UK's social security system (aa000000a).
607        $err = _("New password looks like a National Insurance number.");
608        break;
609    case 'it is all whitespace':
610        $err = _("New password contains only whitespace.");
611        break;
612    case 'it does not contain enough DIFFERENT characters':
613        $err = _("New password does not contain enough DIFFERENT characters.");
614        break;
615    case 'it is based on a dictionary word':
616        $err = _("New password is based on a dictionary word.");
617        break;
618    case 'it is based on a (reversed) dictionary word':
619        $err = _("New password is based on a (reversed) dictionary word.");
620        break;
621    case 'you are not registered in the password file':
622        $err = _("You are not registered in the password file.");
623        break;
624    case 'it is based on your username':
625        $err = _("New password is based on your username.");
626        break;
627    case 'it\'s derivable from your password entry':
628    case 'it is derivable from your password entry':
629    case 'it is derived from your password entry':
630    case 'it\'s derived from your password entry':
631    case 'it is based upon your password entry':
632        // combines five error messages.
633        // password is derivable/derived/based upon your password entry
634        $err = _("New password is similar to your current password entry.");
635        break;
636    default:
637        // leave $err untranslated
638    }
639    // 'No obscure checks in this session' - error comes from PHP crack extension
640    // should never pop out because we always call crack_check() before crack_getlastmessage()
641    return $err;
642}
643
644/**
645 * Callback function to lowercase array keys and values
646 *
647 * See ldq_arraytolower in ldapquery plugin.
648 * @param string Array value
649 * @param string Array key
650 * @since 2.2
651 */
652function lcp_arraytolower(&$value,&$key) {
653    $value=strtolower($value);
654    $key=strtolower($key);
655}
656