1<?php 2#============================================================================== 3# LTB Self Service Password 4# 5# Copyright (C) 2009 Clement OUDOT 6# Copyright (C) 2009 LTB-project.org 7# 8# This program is free software; you can redistribute it and/or 9# modify it under the terms of the GNU General Public License 10# as published by the Free Software Foundation; either version 2 11# of the License, or (at your option) any later version. 12# 13# This program is distributed in the hope that it will be useful, 14# but WITHOUT ANY WARRANTY; without even the implied warranty of 15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16# GNU General Public License for more details. 17# 18# GPL License: http://www.gnu.org/licenses/gpl.txt 19# 20#============================================================================== 21 22# Create SSHA password 23function make_ssha_password($password) { 24 $salt = random_bytes(4); 25 $hash = "{SSHA}" . base64_encode(pack("H*", sha1($password . $salt)) . $salt); 26 return $hash; 27} 28 29# Create SSHA256 password 30function make_ssha256_password($password) { 31 $salt = random_bytes(4); 32 $hash = "{SSHA256}" . base64_encode(pack("H*", hash('sha256', $password . $salt)) . $salt); 33 return $hash; 34} 35 36# Create SSHA384 password 37function make_ssha384_password($password) { 38 $salt = random_bytes(4); 39 $hash = "{SSHA384}" . base64_encode(pack("H*", hash('sha384', $password . $salt)) . $salt); 40 return $hash; 41} 42 43# Create SSHA512 password 44function make_ssha512_password($password) { 45 $salt = random_bytes(4); 46 $hash = "{SSHA512}" . base64_encode(pack("H*", hash('sha512', $password . $salt)) . $salt); 47 return $hash; 48} 49 50# Create SHA password 51function make_sha_password($password) { 52 $hash = "{SHA}" . base64_encode(pack("H*", sha1($password))); 53 return $hash; 54} 55 56# Create SHA256 password 57function make_sha256_password($password) { 58 $hash = "{SHA256}" . base64_encode(pack("H*", hash('sha256', $password))); 59 return $hash; 60} 61 62# Create SHA384 password 63function make_sha384_password($password) { 64 $hash = "{SHA384}" . base64_encode(pack("H*", hash('sha384', $password))); 65 return $hash; 66} 67 68# Create SHA512 password 69function make_sha512_password($password) { 70 $hash = "{SHA512}" . base64_encode(pack("H*", hash('sha512', $password))); 71 return $hash; 72} 73 74# Create SMD5 password 75function make_smd5_password($password) { 76 $salt = random_bytes(4); 77 $hash = "{SMD5}" . base64_encode(pack("H*", md5($password . $salt)) . $salt); 78 return $hash; 79} 80 81# Create MD5 password 82function make_md5_password($password) { 83 $hash = "{MD5}" . base64_encode(pack("H*", md5($password))); 84 return $hash; 85} 86 87# Create CRYPT password 88function make_crypt_password($password, $hash_options) { 89 90 $salt_length = 2; 91 if ( isset($hash_options['crypt_salt_length']) ) { 92 $salt_length = $hash_options['crypt_salt_length']; 93 } 94 95 // Generate salt 96 $possible = '0123456789'. 97 'abcdefghijklmnopqrstuvwxyz'. 98 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'. 99 './'; 100 $salt = ""; 101 102 while( strlen( $salt ) < $salt_length ) { 103 $salt .= substr( $possible, random_int( 0, strlen( $possible ) - 1 ), 1 ); 104 } 105 106 if ( isset($hash_options['crypt_salt_prefix']) ) { 107 $salt = $hash_options['crypt_salt_prefix'] . $salt; 108 } 109 110 $hash = '{CRYPT}' . crypt( $password, $salt); 111 return $hash; 112} 113 114# Create MD4 password (Microsoft NT password format) 115function make_md4_password($password) { 116 if (function_exists('hash')) { 117 $hash = strtoupper( hash( "md4", iconv( "UTF-8", "UTF-16LE", $password ) ) ); 118 } else { 119 $hash = strtoupper( bin2hex( mhash( MHASH_MD4, iconv( "UTF-8", "UTF-16LE", $password ) ) ) ); 120 } 121 return $hash; 122} 123 124# Create AD password (Microsoft Active Directory password format) 125function make_ad_password($password) { 126 $password = "\"" . $password . "\""; 127 $adpassword = mb_convert_encoding($password, "UTF-16LE", "UTF-8"); 128 return $adpassword; 129} 130 131# Generate SMS token 132function generate_sms_token( $sms_token_length ) { 133 $Range=explode(',','48-57'); 134 $NumRanges=count($Range); 135 $smstoken=''; 136 for ($i = 1; $i <= $sms_token_length; $i++){ 137 $r=random_int(0,$NumRanges-1); 138 list($min,$max)=explode('-',$Range[$r]); 139 $smstoken.=chr(random_int($min,$max)); 140 } 141 return $smstoken; 142} 143 144# Get message criticity 145function get_criticity( $msg ) { 146 147 if ( preg_match( "/nophpldap|phpupgraderequired|nophpmhash|nokeyphrase|ldaperror|nomatch|badcredentials|passworderror|tooshort|toobig|minlower|minupper|mindigit|minspecial|forbiddenchars|sameasold|answermoderror|answernomatch|mailnomatch|tokennotsent|tokennotvalid|notcomplex|smsnonumber|smscrypttokensrequired|nophpmbstring|nophpxml|smsnotsent|sameaslogin|pwned|sshkeyerror|specialatends|forbiddenwords|forbiddenldapfields|diffminchars|badquality|tooyoung|inhistory|throttle/" , $msg ) ) { 148 return "danger"; 149 } 150 151 if ( preg_match( "/(login|oldpassword|newpassword|confirmpassword|answer|question|password|mail|token|sshkey|captcha)required|badcaptcha|tokenattempts|checkdatabeforesubmit/" , $msg ) ) { 152 return "warning"; 153 } 154 155 return "success"; 156} 157 158# Get FontAwesome class icon 159function get_fa_class( $msg) { 160 161 $criticity = get_criticity( $msg ); 162 163 if ( $criticity === "danger" ) { return "fa-exclamation-circle"; } 164 if ( $criticity === "warning" ) { return "fa-exclamation-triangle"; } 165 if ( $criticity === "success" ) { return "fa-check-square"; } 166 167} 168 169# Display policy bloc 170# @return HTML code 171function show_policy( $messages, $pwd_policy_config, $result ) { 172 extract( $pwd_policy_config ); 173 174 # Should we display it? 175 if ( !$pwd_show_policy or $pwd_show_policy === "never" ) { return; } 176 if ( $pwd_show_policy === "onerror" ) { 177 if ( !preg_match( "/tooshort|toobig|minlower|minupper|mindigit|minspecial|forbiddenchars|sameasold|notcomplex|sameaslogin|pwned||specialatendsforbiddenwords|forbiddenldapfields/" , $result) ) { return; } 178 } 179 180 # Display bloc 181 echo "<div class=\"help alert alert-warning\">\n"; 182 echo "<p>".$messages["policy"]."</p>\n"; 183 echo "<ul>\n"; 184 if ( $pwd_min_length ) { echo "<li>".$messages["policyminlength"] ." $pwd_min_length</li>\n"; } 185 if ( $pwd_max_length ) { echo "<li>".$messages["policymaxlength"] ." $pwd_max_length</li>\n"; } 186 if ( $pwd_min_lower ) { echo "<li>".$messages["policyminlower"] ." $pwd_min_lower</li>\n"; } 187 if ( $pwd_min_upper ) { echo "<li>".$messages["policyminupper"] ." $pwd_min_upper</li>\n"; } 188 if ( $pwd_min_digit ) { echo "<li>".$messages["policymindigit"] ." $pwd_min_digit</li>\n"; } 189 if ( $pwd_min_special ) { echo "<li>".$messages["policyminspecial"] ." $pwd_min_special</li>\n"; } 190 if ( $pwd_complexity ) { echo "<li>".$messages["policycomplex"] ." $pwd_complexity</li>\n"; } 191 if ( $pwd_forbidden_chars ) { echo "<li>".$messages["policyforbiddenchars"] ." $pwd_forbidden_chars</li>\n"; } 192 if ( $pwd_no_reuse ) { echo "<li>".$messages["policynoreuse"] ."</li>\n"; } 193 if ( $pwd_diff_last_min_chars ) { echo "<li>".$messages['policydiffminchars']." $pwd_diff_last_min_chars</li>\n"; } 194 if ( $pwd_diff_login ) { echo "<li>".$messages["policydifflogin"] ."</li>\n"; } 195 if ( $use_pwnedpasswords ) { echo "<li>".$messages["policypwned"] ."</li>\n"; } 196 if ( $pwd_no_special_at_ends ) { echo "<li>".$messages["policyspecialatends"] ."</li>\n"; } 197 if ( !empty($pwd_forbidden_words)) { echo "<li>".$messages["policyforbiddenwords"] ." " . implode(', ', $pwd_forbidden_words) ."</li>\n"; } 198 if ( !empty($pwd_forbidden_ldap_fields)) { 199 $pwd_forbidden_ldap_fields = array_map( 200 function($field) use ($messages) { 201 if(empty($messages['ldap_' . $field])) return $field; 202 return $messages['ldap_' . $field]; 203 }, $pwd_forbidden_ldap_fields); 204 echo "<li>".$messages["policyforbiddenldapfields"] ." " . implode(', ', $pwd_forbidden_ldap_fields) ."</li>\n"; 205 } 206 echo "</ul>\n"; 207 echo "</div>\n"; 208} 209 210# Check password strength 211# @return result code 212function check_password_strength( $password, $oldpassword, $pwd_policy_config, $login, $entry ) { 213 extract( $pwd_policy_config ); 214 215 $result = ""; 216 217 $length = strlen(utf8_decode($password)); 218 preg_match_all("/[a-z]/", $password, $lower_res); 219 $lower = count( $lower_res[0] ); 220 preg_match_all("/[A-Z]/", $password, $upper_res); 221 $upper = count( $upper_res[0] ); 222 preg_match_all("/[0-9]/", $password, $digit_res); 223 $digit = count( $digit_res[0] ); 224 225 $special = 0; 226 $special_at_ends = false; 227 if ( isset($pwd_special_chars) && !empty($pwd_special_chars) ) { 228 preg_match_all("/[$pwd_special_chars]/", $password, $special_res); 229 $special = count( $special_res[0] ); 230 if ( $pwd_no_special_at_ends ) { 231 $special_at_ends = preg_match("/(^[$pwd_special_chars]|[$pwd_special_chars]$)/", $password, $special_res); 232 } 233 } 234 235 $forbidden = 0; 236 if ( isset($pwd_forbidden_chars) && !empty($pwd_forbidden_chars) ) { 237 preg_match_all("/[$pwd_forbidden_chars]/", $password, $forbidden_res); 238 $forbidden = count( $forbidden_res[0] ); 239 } 240 241 # Complexity: checks for lower, upper, special, digits 242 if ( $pwd_complexity ) { 243 $complex = 0; 244 if ( $special > 0 ) { $complex++; } 245 if ( $digit > 0 ) { $complex++; } 246 if ( $lower > 0 ) { $complex++; } 247 if ( $upper > 0 ) { $complex++; } 248 if ( $complex < $pwd_complexity ) { $result="notcomplex"; } 249 } 250 251 # Minimal lenght 252 if ( $pwd_min_length and $length < $pwd_min_length ) { $result="tooshort"; } 253 254 # Maximal lenght 255 if ( $pwd_max_length and $length > $pwd_max_length ) { $result="toobig"; } 256 257 # Minimal lower chars 258 if ( $pwd_min_lower and $lower < $pwd_min_lower ) { $result="minlower"; } 259 260 # Minimal upper chars 261 if ( $pwd_min_upper and $upper < $pwd_min_upper ) { $result="minupper"; } 262 263 # Minimal digit chars 264 if ( $pwd_min_digit and $digit < $pwd_min_digit ) { $result="mindigit"; } 265 266 # Minimal special chars 267 if ( $pwd_min_special and $special < $pwd_min_special ) { $result="minspecial"; } 268 269 # Forbidden chars 270 if ( $forbidden > 0 ) { $result="forbiddenchars"; } 271 272 # Special chars at beginning or end 273 if ( $special_at_ends > 0 && $special == 1 ) { $result="specialatends"; } 274 275 # Same as old password? 276 if ( $pwd_no_reuse and $password === $oldpassword ) { $result="sameasold"; } 277 278 # Same as login? 279 if ( $pwd_diff_login and $password === $login ) { $result="sameaslogin"; } 280 281 if ( $pwd_diff_last_min_chars > 0 ) { 282 $similarities = similar_text($oldpassword, $password); 283 $check_len = strlen($oldpassword) < strlen($password) ? strlen($oldpassword) : strlen($password); 284 $new_chars = $check_len - $similarities; 285 if ($new_chars <= $pwd_diff_last_min_chars) { $result = "diffminchars"; } 286 } 287 288 # Contains forbidden words? 289 if ( !empty($pwd_forbidden_words) ) { 290 foreach( $pwd_forbidden_words as $disallowed ) { 291 if( stripos($password, $disallowed) !== false ) { 292 $result="forbiddenwords"; 293 break; 294 } 295 } 296 } 297 298 # Contains values from forbidden ldap fields? 299 if( !empty($pwd_forbidden_ldap_fields) ) { 300 foreach( $pwd_forbidden_ldap_fields as $field ) { 301 $values = $entry[$field]; 302 if(!is_array($entry[$field])) { 303 $values = array($entry[$field]); 304 } 305 foreach($values as $key => $value) { 306 if($key === 'count') continue; 307 if(stripos($password, $value) !== false) { 308 $result = "forbiddenldapfields"; 309 break 2; 310 } 311 } 312 } 313 } 314 315 # pwned? 316 if ($use_pwnedpasswords) { 317 $pwned_passwords = new PwnedPasswords\PwnedPasswords; 318 $insecure = $pwned_passwords->isInsecure($password); 319 if($insecure) { $result="pwned"; } 320 } 321 322 return $result; 323} 324 325# Change password 326# @return result code 327function change_password( $ldap, $dn, $password, $ad_mode, $ad_options, $samba_mode, $samba_options, $shadow_options, $hash, $hash_options, $who_change_password, $oldpassword, $use_exop_passwd, $use_ppolicy_control ) { 328 329 $result = ""; 330 $error_code = ""; 331 $error_msg = ""; 332 $ppolicy_error_code = ""; 333 334 $time = time(); 335 336 # Set Samba password value 337 if ( $samba_mode ) { 338 $userdata["sambaNTPassword"] = make_md4_password($password); 339 $userdata["sambaPwdLastSet"] = $time; 340 if ( isset($samba_options['min_age']) && $samba_options['min_age'] > 0 ) { 341 $userdata["sambaPwdCanChange"] = $time + ( $samba_options['min_age'] * 86400 ); 342 } 343 if ( isset($samba_options['max_age']) && $samba_options['max_age'] > 0 ) { 344 $userdata["sambaPwdMustChange"] = $time + ( $samba_options['max_age'] * 86400 ); 345 } 346 if ( isset($samba_options['expire_days']) && $samba_options['expire_days'] > 0 ) { 347 $userdata["sambaKickoffTime"] = $time + ( $samba_options['expire_days'] * 86400 ); 348 } 349 } 350 351 # Get hash type if hash is set to auto 352 if ( !$ad_mode && $hash == "auto" ) { 353 $search_userpassword = ldap_read( $ldap, $dn, "(objectClass=*)", array("userPassword") ); 354 if ( $search_userpassword ) { 355 $userpassword = ldap_get_values($ldap, ldap_first_entry($ldap,$search_userpassword), "userPassword"); 356 if ( isset($userpassword) ) { 357 if ( preg_match( '/^\{(\w+)\}/', $userpassword[0], $matches ) ) { 358 $hash = strtoupper($matches[1]); 359 } 360 } 361 } 362 } 363 364 # Transform password value 365 if ( $ad_mode ) { 366 $password = make_ad_password($password); 367 } elseif (!$use_exop_passwd) { 368 # Hash password if needed 369 if ( $hash == "SSHA" ) { 370 $password = make_ssha_password($password); 371 } 372 if ( $hash == "SSHA256" ) { 373 $password = make_ssha256_password($password); 374 } 375 if ( $hash == "SSHA384" ) { 376 $password = make_ssha384_password($password); 377 } 378 if ( $hash == "SSHA512" ) { 379 $password = make_ssha512_password($password); 380 } 381 if ( $hash == "SHA" ) { 382 $password = make_sha_password($password); 383 } 384 if ( $hash == "SHA256" ) { 385 $password = make_sha256_password($password); 386 } 387 if ( $hash == "SHA384" ) { 388 $password = make_sha384_password($password); 389 } 390 if ( $hash == "SHA512" ) { 391 $password = make_sha512_password($password); 392 } 393 if ( $hash == "SMD5" ) { 394 $password = make_smd5_password($password); 395 } 396 if ( $hash == "MD5" ) { 397 $password = make_md5_password($password); 398 } 399 if ( $hash == "CRYPT" ) { 400 $password = make_crypt_password($password, $hash_options); 401 } 402 } 403 404 # Set password value 405 if ( $ad_mode ) { 406 $userdata["unicodePwd"] = $password; 407 if ( $ad_options['force_unlock'] ) { 408 $userdata["lockoutTime"] = 0; 409 } 410 if ( $ad_options['force_pwd_change'] ) { 411 $userdata["pwdLastSet"] = 0; 412 } 413 } 414 415 # Shadow options 416 if ( $shadow_options['update_shadowLastChange'] ) { 417 $userdata["shadowLastChange"] = floor($time / 86400); 418 } 419 420 if ( $shadow_options['update_shadowExpire'] ) { 421 if ( $shadow_options['shadow_expire_days'] > 0) { 422 $userdata["shadowExpire"] = floor(($time / 86400) + $shadow_options['shadow_expire_days']); 423 } else { 424 $userdata["shadowExpire"] = $shadow_options['shadow_expire_days']; 425 } 426 } 427 428 # Commit modification on directory 429 430 # Special case: AD mode with password changed as user 431 if ( $ad_mode and $who_change_password === "user" ) { 432 # The AD password change procedure is modifying the attribute unicodePwd by 433 # first deleting unicodePwd with the old password and them adding it with the 434 # the new password 435 $oldpassword = make_ad_password($oldpassword); 436 437 $modifications = array( 438 array( 439 "attrib" => "unicodePwd", 440 "modtype" => LDAP_MODIFY_BATCH_REMOVE, 441 "values" => array($oldpassword), 442 ), 443 array( 444 "attrib" => "unicodePwd", 445 "modtype" => LDAP_MODIFY_BATCH_ADD, 446 "values" => array($password), 447 ), 448 ); 449 450 $bmod = ldap_modify_batch($ldap, $dn, $modifications); 451 $error_code = ldap_errno($ldap); 452 $error_msg = ldap_error($ldap); 453 } elseif ($use_exop_passwd) { 454 $exop_passwd = FALSE; 455 if ( $use_ppolicy_control ) { 456 $ctrls = array(); 457 $exop_passwd = ldap_exop_passwd($ldap, $dn, $oldpassword, $password, $ctrls); 458 $error_code = ldap_errno($ldap); 459 $error_msg = ldap_error($ldap); 460 if (!$exop_passwd) { 461 if (isset($ctrls[LDAP_CONTROL_PASSWORDPOLICYRESPONSE])) { 462 $value = $ctrls[LDAP_CONTROL_PASSWORDPOLICYRESPONSE]['value']; 463 if (isset($value['error'])) { 464 $ppolicy_error_code = $value['error']; 465 error_log("LDAP - Ppolicy error code: $ppolicy_error_code"); 466 } 467 } 468 } 469 } else { 470 $exop_passwd = ldap_exop_passwd($ldap, $dn, $oldpassword, $password); 471 $error_code = ldap_errno($ldap); 472 $error_msg = ldap_error($ldap); 473 } 474 if ($exop_passwd === TRUE) { 475 # If password change works update other data 476 if (!empty($userdata)) { 477 ldap_mod_replace($ldap, $dn, $userdata); 478 $error_code = ldap_errno($ldap); 479 $error_msg = ldap_error($ldap); 480 } 481 } 482 } else { 483 # Else just replace with new password 484 if (!$ad_mode) { 485 $userdata["userPassword"] = $password; 486 } 487 if ( $use_ppolicy_control ) { 488 $ppolicy_replace = ldap_mod_replace_ext($ldap, $dn, $userdata, [['oid' => LDAP_CONTROL_PASSWORDPOLICYREQUEST]]); 489 if (ldap_parse_result($ldap, $ppolicy_replace, $error_code, $matcheddn, $error_msg, $referrals, $ctrls)) { 490 if (isset($ctrls[LDAP_CONTROL_PASSWORDPOLICYRESPONSE])) { 491 $value = $ctrls[LDAP_CONTROL_PASSWORDPOLICYRESPONSE]['value']; 492 if (isset($value['error'])) { 493 $ppolicy_error_code = $value['error']; 494 error_log("LDAP - Ppolicy error code: $ppolicy_error_code"); 495 } 496 } 497 } 498 } else { 499 ldap_mod_replace($ldap, $dn, $userdata); 500 $error_code = ldap_errno($ldap); 501 $error_msg = ldap_error($ldap); 502 } 503 } 504 505 if ( !isset($error_code) ) { 506 $result = "ldaperror"; 507 } elseif ( $error_code > 0 ) { 508 $result = "passworderror"; 509 error_log("LDAP - Modify password error $error_code ($error_msg)"); 510 if ( $ppolicy_error_code === 5 ) { $result = "badquality"; } 511 if ( $ppolicy_error_code === 6 ) { $result = "tooshort"; } 512 if ( $ppolicy_error_code === 7 ) { $result = "tooyoung"; } 513 if ( $ppolicy_error_code === 8 ) { $result = "inhistory"; } 514 } else { 515 $result = "passwordchanged"; 516 } 517 518 return $result; 519} 520 521 522# Change sshPublicKey attribute 523# @return result code 524function change_sshkey( $ldap, $dn, $attribute, $sshkey ) { 525 526 $result = ""; 527 528 $userdata[$attribute] = $sshkey; 529 530 # Commit modification on directory 531 $replace = ldap_mod_replace($ldap, $dn, $userdata); 532 533 $errno = ldap_errno($ldap); 534 535 if ( $errno ) { 536 $result = "sshkeyerror"; 537 error_log("LDAP - Modify $attribute error $errno (".ldap_error($ldap).")"); 538 } else { 539 $result = "sshkeychanged"; 540 } 541 542 return $result; 543} 544 545 546/* @function encrypt(string $data) 547 * Encrypt a data 548 * @param string $data Data to encrypt 549 * @param string $keyphrase Password for encryption 550 * @return string Encrypted data, base64 encoded 551 */ 552function encrypt($data, $keyphrase) { 553 return base64_encode(\Defuse\Crypto\Crypto::encryptWithPassword($data, $keyphrase, true)); 554} 555 556/* @function decrypt(string $data) 557 * Decrypt a data 558 * @param string $data Encrypted data, base64 encoded 559 * @param string $keyphrase Password for decryption 560 * @return string Decrypted data 561 */ 562function decrypt($data, $keyphrase) { 563 try { 564 return \Defuse\Crypto\Crypto::decryptWithPassword(base64_decode($data), $keyphrase, true); 565 } catch (\Defuse\Crypto\Exception\CryptoException $e) { 566 error_log("crypto: decryption error " . $e->getMessage()); 567 return ''; 568 } 569} 570 571 572/* @function string str_putcsv(array $fields[, string $delimiter = ','[, string $enclosure = '"'[, string $escape_char = '\\']]]) 573 * Convert array to CSV line. Based on https://gist.github.com/johanmeiring/2894568 and https://bugs.php.net/bug.php?id=64183#1506521511 574 * Wrapped in `if(!function_exists(...` in case it gets added to PHP. 575 * Also see https://www.php.net/manual/en/function.fgetcsv.php and related 576 * @param string $fields An array of strings 577 * @param string $delimiter field delimiter (one character only) 578 * @param string $enclosure field enclosure (one character only) 579 * @param string $escape_char escape character (at most one character) - empty string ("") disables escape mechanism 580 * @return string fields in CSV format 581 */ 582if(!function_exists('str_putcsv')) 583{ 584 function str_putcsv($fields, $delimiter = ',', $enclosure = '"', $escape_char = '\\') 585 { 586 $fp = fopen('php://temp', 'r+'); 587 fputcsv($fp, $fields, $delimiter, $enclosure, $escape_char); 588 rewind($fp); 589 $data = stream_get_contents($fp); 590 fclose($fp); 591 return rtrim($data, "\n"); 592 } 593} 594 595 596/* @function boolean send_mail(PHPMailer $mailer, string $mail, string $mail_from, string $subject, string $body, array $data) 597 * Send a mail, replace strings in body 598 * @param mailer PHPMailer object 599 * @param mail Destination 600 * @param mail_from Sender 601 * @param subject Subject 602 * @param body Body 603 * @param data Data for string replacement 604 * @return result 605 */ 606function send_mail($mailer, $mail, $mail_from, $mail_from_name, $subject, $body, $data) { 607 608 $result = false; 609 610 if(!is_a($mailer, 'PHPMailer\PHPMailer\PHPMailer')) { 611 error_log("send_mail: PHPMailer object required!"); 612 return $result; 613 } 614 615 if (!$mail) { 616 error_log("send_mail: no mail given, exiting..."); 617 return $result; 618 } 619 620 /* Replace data in mail, subject and body */ 621 foreach($data as $key => $value ) { 622 $mail = str_replace('{'.$key.'}', $value, $mail); 623 $mail_from = str_replace('{'.$key.'}', $value, $mail_from); 624 $subject = str_replace('{'.$key.'}', $value, $subject); 625 $body = str_replace('{'.$key.'}', $value, $body); 626 } 627 628 $mailer->setFrom($mail_from, $mail_from_name); 629 $mailer->addReplyTo($mail_from, $mail_from_name); 630 $mailer->addAddress($mail); 631 $mailer->Subject = $subject; 632 $mailer->Body = $body; 633 634 $result = $mailer->send(); 635 636 if (!$result) { 637 error_log("send_mail: ".$mailer->ErrorInfo); 638 } 639 640 return $result; 641 642} 643 644/* @function string check_username_validity(string $username, string $login_forbidden_chars) 645 * Check the user name against a regex or internal ctype_alnum() call to make sure the username doesn't contain 646 * predetermined bad values, like an '*' can allow an attacker to 'test' to find valid usernames. 647 * @param username the user name to test against 648 * @param optional login_forbidden_chars invalid characters 649 * @return $result 650 */ 651function check_username_validity($username,$login_forbidden_chars) { 652 $result = ""; 653 654 if (!$login_forbidden_chars) { 655 if (!ctype_alnum($username)) { 656 $result = "badcredentials"; 657 error_log("Non alphanumeric characters in username $username"); 658 } 659 } 660 else { 661 preg_match_all("/[$login_forbidden_chars]/", $username, $forbidden_res); 662 if (count($forbidden_res[0])) { 663 $result = "badcredentials"; 664 error_log("Illegal characters in username $username (list of forbidden characters: $login_forbidden_chars)"); 665 } 666 } 667 668 return $result; 669} 670 671/* @function string hook_command(string $hook, string $login, string $newpassword, null|string $oldpassword, null|boolean $hook_password_encodebase64) 672 Creates the command line to execute for the prehook/posthook process. Passwords will be base64 encoded if configured. Base64 encoding will prevent passwords with special 673 characters to be modified by the escapeshellarg() function. 674 @param $hook string script/command to execute for procesing hook data 675 @param $login string username to change/set password for 676 @param $newpassword string new passwword for given login 677 @param $oldpassword string old password for given login 678 @param hook_password_encodebase64 boolean set to true if passwords are to be converted to base64 encoded strings 679*/ 680function hook_command($hook, $login, $newpassword, $oldpassword = null, $hook_password_encodebase64 = false) { 681 682 $command = ''; 683 if ( isset($hook_password_encodebase64) && $hook_password_encodebase64 ) { 684 $command = escapeshellcmd($hook).' '.escapeshellarg($login).' '.base64_encode($newpassword); 685 686 if ( ! is_null($oldpassword) ) { 687 $command .= ' '.base64_encode($oldpassword); 688 } 689 690 } else { 691 $command = escapeshellcmd($hook).' '.escapeshellarg($login).' '.escapeshellarg($newpassword); 692 693 if ( ! is_null($oldpassword) ) { 694 $command .= ' '.escapeshellarg($oldpassword); 695 } 696 } 697 return $command; 698} 699 700/* function allowed_rate(string $login, string $ip_addr, array $rrl_config) 701 * Check if this login / this IP reached the limit fixed 702 * @return bool allowed 703 */ 704function allowed_rate($login,$ip_addr,$rrl_config) { 705 $now = time(); 706 $fblock=1; 707 if ($rrl_config["max_per_user"] > 0) { 708 $rrludb = $rrl_config["dbdir"] . "/ssp_rrl_users.json"; 709 if (!file_exists($rrludb)) { 710 file_put_contents($rrludb,"{}"); 711 } 712 $dbfh = fopen($rrludb . ".lock","w"); 713 if (!$dbfh) { throw new Exception('nowrite to '.$rrludb); } 714 flock($dbfh,LOCK_EX,$fblock); 715 $users = (array) json_decode(file_get_contents($rrludb)); 716 $atts = [$now]; 717 if (array_key_exists($login,$users)) { 718 foreach ($users[$login] as $when) { 719 if ( $when > ($now - $rrl_config['per_time']) ) { 720 array_push($atts,$when); 721 } 722 } 723 } 724 $users[$login] = $atts; 725 file_put_contents($rrludb,json_encode($users)); 726 flock($dbfh,LOCK_UN); 727 if (count($atts) > $rrl_config["max_per_user"]) { 728 return false; 729 } 730 } 731 if ($rrl_config["max_per_ip"] > 0) { 732 $rrlidb = $rrl_config["dbdir"] . "/ssp_rrl_ips.json"; 733 if (!file_exists($rrlidb)) { 734 file_put_contents($rrlidb,"{}"); 735 } 736 $dbfh = fopen($rrlidb . ".lock","w"); 737 if (!$dbfh) { throw new Exception('nowrite to '.$rrludb); } 738 flock($dbfh,LOCK_EX,$fblock); 739 $ips = (array) json_decode(file_get_contents($rrlidb)); 740 $atts = [$now]; 741 if (array_key_exists($ip_addr,$ips)) { 742 foreach ($ips[$ip_addr] as $when) { 743 if ( $when > ($now - $rrl_config['per_time']) ) { 744 array_push($atts,$when); 745 } 746 } 747 } 748 $ips[$ip_addr] = $atts; 749 file_put_contents($rrlidb,json_encode($ips)); 750 flock($dbfh,LOCK_UN); 751 if (count($atts) > $rrl_config["max_per_ip"]) { 752 return false; 753 } 754 } 755 return true; 756} 757 758 759