\n"; echo "

".$messages["policy"]."

\n"; echo "\n"; echo "\n"; } # Check password strength # @return result code function check_password_strength( $password, $oldpassword, $pwd_policy_config, $login, $entry ) { extract( $pwd_policy_config ); $result = ""; $length = strlen(utf8_decode($password)); preg_match_all("/[a-z]/", $password, $lower_res); $lower = count( $lower_res[0] ); preg_match_all("/[A-Z]/", $password, $upper_res); $upper = count( $upper_res[0] ); preg_match_all("/[0-9]/", $password, $digit_res); $digit = count( $digit_res[0] ); $special = 0; $special_at_ends = false; if ( isset($pwd_special_chars) && !empty($pwd_special_chars) ) { preg_match_all("/[$pwd_special_chars]/", $password, $special_res); $special = count( $special_res[0] ); if ( $pwd_no_special_at_ends ) { $special_at_ends = preg_match("/(^[$pwd_special_chars]|[$pwd_special_chars]$)/", $password, $special_res); } } $forbidden = 0; if ( isset($pwd_forbidden_chars) && !empty($pwd_forbidden_chars) ) { preg_match_all("/[$pwd_forbidden_chars]/", $password, $forbidden_res); $forbidden = count( $forbidden_res[0] ); } # Complexity: checks for lower, upper, special, digits if ( $pwd_complexity ) { $complex = 0; if ( $special > 0 ) { $complex++; } if ( $digit > 0 ) { $complex++; } if ( $lower > 0 ) { $complex++; } if ( $upper > 0 ) { $complex++; } if ( $complex < $pwd_complexity ) { $result="notcomplex"; } } # Minimal lenght if ( $pwd_min_length and $length < $pwd_min_length ) { $result="tooshort"; } # Maximal lenght if ( $pwd_max_length and $length > $pwd_max_length ) { $result="toobig"; } # Minimal lower chars if ( $pwd_min_lower and $lower < $pwd_min_lower ) { $result="minlower"; } # Minimal upper chars if ( $pwd_min_upper and $upper < $pwd_min_upper ) { $result="minupper"; } # Minimal digit chars if ( $pwd_min_digit and $digit < $pwd_min_digit ) { $result="mindigit"; } # Minimal special chars if ( $pwd_min_special and $special < $pwd_min_special ) { $result="minspecial"; } # Forbidden chars if ( $forbidden > 0 ) { $result="forbiddenchars"; } # Special chars at beginning or end if ( $special_at_ends > 0 && $special == 1 ) { $result="specialatends"; } # Same as old password? if ( $pwd_no_reuse and $password === $oldpassword ) { $result="sameasold"; } # Same as login? if ( $pwd_diff_login and $password === $login ) { $result="sameaslogin"; } if ( $pwd_diff_last_min_chars > 0 and strlen($oldpassword) > 0 ) { $similarities = similar_text($oldpassword, $password); $check_len = strlen($oldpassword) < strlen($password) ? strlen($oldpassword) : strlen($password); $new_chars = $check_len - $similarities; if ($new_chars <= $pwd_diff_last_min_chars) { $result = "diffminchars"; } } # Contains forbidden words? if ( !empty($pwd_forbidden_words) ) { foreach( $pwd_forbidden_words as $disallowed ) { if( stripos($password, $disallowed) !== false ) { $result="forbiddenwords"; break; } } } # Contains values from forbidden ldap fields? if( !empty($pwd_forbidden_ldap_fields) ) { foreach( $pwd_forbidden_ldap_fields as $field ) { $values = $entry[$field]; if(!is_array($entry[$field])) { $values = array($entry[$field]); } foreach($values as $key => $value) { if($key === 'count') continue; if(stripos($password, $value) !== false) { $result = "forbiddenldapfields"; break 2; } } } } # pwned? if ($use_pwnedpasswords) { $pwned_passwords = new PwnedPasswords\PwnedPasswords; $insecure = $pwned_passwords->isInsecure($password); if($insecure) { $result="pwned"; } } return $result; } # Change password # @return result code function 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 ) { $result = ""; $error_code = ""; $error_msg = ""; $ppolicy_error_code = ""; $time = time(); # Set Samba password value if ( $samba_mode ) { $userdata["sambaNTPassword"] = make_md4_password($password); $userdata["sambaPwdLastSet"] = $time; if ( isset($samba_options['min_age']) && $samba_options['min_age'] > 0 ) { $userdata["sambaPwdCanChange"] = $time + ( $samba_options['min_age'] * 86400 ); } if ( isset($samba_options['max_age']) && $samba_options['max_age'] > 0 ) { $userdata["sambaPwdMustChange"] = $time + ( $samba_options['max_age'] * 86400 ); } if ( isset($samba_options['expire_days']) && $samba_options['expire_days'] > 0 ) { $userdata["sambaKickoffTime"] = $time + ( $samba_options['expire_days'] * 86400 ); } } # Get hash type if hash is set to auto if ( !$ad_mode && $hash == "auto" ) { $search_userpassword = ldap_read( $ldap, $dn, "(objectClass=*)", array("userPassword") ); if ( $search_userpassword ) { $userpassword = ldap_get_values($ldap, ldap_first_entry($ldap,$search_userpassword), "userPassword"); if ( isset($userpassword) ) { if ( preg_match( '/^\{(\w+)\}/', $userpassword[0], $matches ) ) { $hash = strtoupper($matches[1]); } } } } # Transform password value if ( $ad_mode ) { $password = make_ad_password($password); } elseif (!$use_exop_passwd) { # Hash password if needed if ( $hash == "SSHA" ) { $password = make_ssha_password($password); } if ( $hash == "SSHA256" ) { $password = make_ssha256_password($password); } if ( $hash == "SSHA384" ) { $password = make_ssha384_password($password); } if ( $hash == "SSHA512" ) { $password = make_ssha512_password($password); } if ( $hash == "SHA" ) { $password = make_sha_password($password); } if ( $hash == "SHA256" ) { $password = make_sha256_password($password); } if ( $hash == "SHA384" ) { $password = make_sha384_password($password); } if ( $hash == "SHA512" ) { $password = make_sha512_password($password); } if ( $hash == "SMD5" ) { $password = make_smd5_password($password); } if ( $hash == "MD5" ) { $password = make_md5_password($password); } if ( $hash == "CRYPT" ) { $password = make_crypt_password($password, $hash_options); } } # Set password value if ( $ad_mode ) { $userdata["unicodePwd"] = $password; if ( $ad_options['force_unlock'] ) { $userdata["lockoutTime"] = 0; } if ( $ad_options['force_pwd_change'] ) { $userdata["pwdLastSet"] = 0; } } # Shadow options if ( $shadow_options['update_shadowLastChange'] ) { $userdata["shadowLastChange"] = floor($time / 86400); } if ( $shadow_options['update_shadowExpire'] ) { if ( $shadow_options['shadow_expire_days'] > 0) { $userdata["shadowExpire"] = floor(($time / 86400) + $shadow_options['shadow_expire_days']); } else { $userdata["shadowExpire"] = $shadow_options['shadow_expire_days']; } } # Commit modification on directory # Special case: AD mode with password changed as user if ( $ad_mode and $who_change_password === "user" ) { # The AD password change procedure is modifying the attribute unicodePwd by # first deleting unicodePwd with the old password and them adding it with the # the new password $oldpassword = make_ad_password($oldpassword); $modifications = array( array( "attrib" => "unicodePwd", "modtype" => LDAP_MODIFY_BATCH_REMOVE, "values" => array($oldpassword), ), array( "attrib" => "unicodePwd", "modtype" => LDAP_MODIFY_BATCH_ADD, "values" => array($password), ), ); $bmod = ldap_modify_batch($ldap, $dn, $modifications); $error_code = ldap_errno($ldap); $error_msg = ldap_error($ldap); } elseif ($use_exop_passwd) { $exop_passwd = FALSE; if ( $use_ppolicy_control ) { $ctrls = array(); $exop_passwd = ldap_exop_passwd($ldap, $dn, $oldpassword, $password, $ctrls); $error_code = ldap_errno($ldap); $error_msg = ldap_error($ldap); if (!$exop_passwd) { if (isset($ctrls[LDAP_CONTROL_PASSWORDPOLICYRESPONSE])) { $value = $ctrls[LDAP_CONTROL_PASSWORDPOLICYRESPONSE]['value']; if (isset($value['error'])) { $ppolicy_error_code = $value['error']; error_log("LDAP - Ppolicy error code: $ppolicy_error_code"); } } } } else { $exop_passwd = ldap_exop_passwd($ldap, $dn, $oldpassword, $password); $error_code = ldap_errno($ldap); $error_msg = ldap_error($ldap); } if ($exop_passwd === TRUE) { # If password change works update other data if (!empty($userdata)) { ldap_mod_replace($ldap, $dn, $userdata); $error_code = ldap_errno($ldap); $error_msg = ldap_error($ldap); } } } else { # Else just replace with new password if (!$ad_mode) { $userdata["userPassword"] = $password; } if ( $use_ppolicy_control ) { $ppolicy_replace = ldap_mod_replace_ext($ldap, $dn, $userdata, [['oid' => LDAP_CONTROL_PASSWORDPOLICYREQUEST]]); if (ldap_parse_result($ldap, $ppolicy_replace, $error_code, $matcheddn, $error_msg, $referrals, $ctrls)) { if (isset($ctrls[LDAP_CONTROL_PASSWORDPOLICYRESPONSE])) { $value = $ctrls[LDAP_CONTROL_PASSWORDPOLICYRESPONSE]['value']; if (isset($value['error'])) { $ppolicy_error_code = $value['error']; error_log("LDAP - Ppolicy error code: $ppolicy_error_code"); } } } } else { ldap_mod_replace($ldap, $dn, $userdata); $error_code = ldap_errno($ldap); $error_msg = ldap_error($ldap); } } if ( !isset($error_code) ) { $result = "ldaperror"; } elseif ( $error_code > 0 ) { $result = "passworderror"; error_log("LDAP - Modify password error $error_code ($error_msg)"); if ( $ppolicy_error_code === 5 ) { $result = "badquality"; } if ( $ppolicy_error_code === 6 ) { $result = "tooshort"; } if ( $ppolicy_error_code === 7 ) { $result = "tooyoung"; } if ( $ppolicy_error_code === 8 ) { $result = "inhistory"; } } else { $result = "passwordchanged"; } return $result; } # Change sshPublicKey attribute # @return result code function change_sshkey( $ldap, $dn, $attribute, $sshkey ) { $result = ""; $userdata[$attribute] = $sshkey; # Commit modification on directory $replace = ldap_mod_replace($ldap, $dn, $userdata); $errno = ldap_errno($ldap); if ( $errno ) { $result = "sshkeyerror"; error_log("LDAP - Modify $attribute error $errno (".ldap_error($ldap).")"); } else { $result = "sshkeychanged"; } return $result; } /* @function encrypt(string $data) * Encrypt a data * @param string $data Data to encrypt * @param string $keyphrase Password for encryption * @return string Encrypted data, base64 encoded */ function encrypt($data, $keyphrase) { return base64_encode(\Defuse\Crypto\Crypto::encryptWithPassword($data, $keyphrase, true)); } /* @function decrypt(string $data) * Decrypt a data * @param string $data Encrypted data, base64 encoded * @param string $keyphrase Password for decryption * @return string Decrypted data */ function decrypt($data, $keyphrase) { try { return \Defuse\Crypto\Crypto::decryptWithPassword(base64_decode($data), $keyphrase, true); } catch (\Defuse\Crypto\Exception\CryptoException $e) { error_log("crypto: decryption error " . $e->getMessage()); return ''; } } /* @function string str_putcsv(array $fields[, string $delimiter = ','[, string $enclosure = '"'[, string $escape_char = '\\']]]) * Convert array to CSV line. Based on https://gist.github.com/johanmeiring/2894568 and https://bugs.php.net/bug.php?id=64183#1506521511 * Wrapped in `if(!function_exists(...` in case it gets added to PHP. * Also see https://www.php.net/manual/en/function.fgetcsv.php and related * @param string $fields An array of strings * @param string $delimiter field delimiter (one character only) * @param string $enclosure field enclosure (one character only) * @param string $escape_char escape character (at most one character) - empty string ("") disables escape mechanism * @return string fields in CSV format */ if(!function_exists('str_putcsv')) { function str_putcsv($fields, $delimiter = ',', $enclosure = '"', $escape_char = '\\') { $fp = fopen('php://temp', 'r+'); fputcsv($fp, $fields, $delimiter, $enclosure, $escape_char); rewind($fp); $data = stream_get_contents($fp); fclose($fp); return rtrim($data, "\n"); } } /* @function boolean send_mail(PHPMailer $mailer, string $mail, string $mail_from, string $subject, string $body, array $data) * Send a mail, replace strings in body * @param mailer PHPMailer object * @param mail Destination * @param mail_from Sender * @param subject Subject * @param body Body * @param data Data for string replacement * @return result */ function send_mail($mailer, $mail, $mail_from, $mail_from_name, $subject, $body, $data) { $result = false; if(!is_a($mailer, 'PHPMailer\PHPMailer\PHPMailer')) { error_log("send_mail: PHPMailer object required!"); return $result; } if (!$mail) { error_log("send_mail: no mail given, exiting..."); return $result; } /* Replace data in mail, subject and body */ foreach($data as $key => $value ) { $mail = str_replace('{'.$key.'}', $value, $mail); $mail_from = str_replace('{'.$key.'}', $value, $mail_from); $subject = str_replace('{'.$key.'}', $value, $subject); $body = str_replace('{'.$key.'}', $value, $body); } $mailer->setFrom($mail_from, $mail_from_name); $mailer->addReplyTo($mail_from, $mail_from_name); $mailer->addAddress($mail); $mailer->Subject = $subject; $mailer->Body = $body; $result = $mailer->send(); if (!$result) { error_log("send_mail: ".$mailer->ErrorInfo); } return $result; } /* @function string check_username_validity(string $username, string $login_forbidden_chars) * Check the user name against a regex or internal ctype_alnum() call to make sure the username doesn't contain * predetermined bad values, like an '*' can allow an attacker to 'test' to find valid usernames. * @param username the user name to test against * @param optional login_forbidden_chars invalid characters * @return $result */ function check_username_validity($username,$login_forbidden_chars) { $result = ""; if (!$login_forbidden_chars) { if (!ctype_alnum($username)) { $result = "badcredentials"; error_log("Non alphanumeric characters in username $username"); } } else { preg_match_all("/[$login_forbidden_chars]/", $username, $forbidden_res); if (count($forbidden_res[0])) { $result = "badcredentials"; error_log("Illegal characters in username $username (list of forbidden characters: $login_forbidden_chars)"); } } return $result; } /* @function string hook_command(string $hook, string $login, string $newpassword, null|string $oldpassword, null|boolean $hook_password_encodebase64) 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 characters to be modified by the escapeshellarg() function. @param $hook string script/command to execute for procesing hook data @param $login string username to change/set password for @param $newpassword string new passwword for given login @param $oldpassword string old password for given login @param hook_password_encodebase64 boolean set to true if passwords are to be converted to base64 encoded strings */ function hook_command($hook, $login, $newpassword, $oldpassword = null, $hook_password_encodebase64 = false) { $command = ''; if ( isset($hook_password_encodebase64) && $hook_password_encodebase64 ) { $command = escapeshellcmd($hook).' '.escapeshellarg($login).' '.base64_encode($newpassword); if ( ! is_null($oldpassword) ) { $command .= ' '.base64_encode($oldpassword); } } else { $command = escapeshellcmd($hook).' '.escapeshellarg($login).' '.escapeshellarg($newpassword); if ( ! is_null($oldpassword) ) { $command .= ' '.escapeshellarg($oldpassword); } } return $command; } /* function allowed_rate(string $login, string $ip_addr, array $rrl_config) * Check if this login / this IP reached the limit fixed * @return bool allowed */ function allowed_rate($login,$ip_addr,$rrl_config) { $now = time(); $fblock=1; if ($rrl_config["max_per_user"] > 0) { $rrludb = $rrl_config["dbdir"] . "/ssp_rrl_users.json"; if (!file_exists($rrludb)) { file_put_contents($rrludb,"{}"); } $dbfh = fopen($rrludb . ".lock","w"); if (!$dbfh) { throw new Exception('nowrite to '.$rrludb); } flock($dbfh,LOCK_EX,$fblock); $users = (array) json_decode(file_get_contents($rrludb)); $atts = [$now]; if (array_key_exists($login,$users)) { foreach ($users[$login] as $when) { if ( $when > ($now - $rrl_config['per_time']) ) { array_push($atts,$when); } } } $users[$login] = $atts; file_put_contents($rrludb,json_encode($users)); flock($dbfh,LOCK_UN); if (count($atts) > $rrl_config["max_per_user"]) { return false; } } if ($rrl_config["max_per_ip"] > 0) { $rrlidb = $rrl_config["dbdir"] . "/ssp_rrl_ips.json"; if (!file_exists($rrlidb)) { file_put_contents($rrlidb,"{}"); } $dbfh = fopen($rrlidb . ".lock","w"); if (!$dbfh) { throw new Exception('nowrite to '.$rrludb); } flock($dbfh,LOCK_EX,$fblock); $ips = (array) json_decode(file_get_contents($rrlidb)); $atts = [$now]; if (array_key_exists($ip_addr,$ips)) { foreach ($ips[$ip_addr] as $when) { if ( $when > ($now - $rrl_config['per_time']) ) { array_push($atts,$when); } } } $ips[$ip_addr] = $atts; file_put_contents($rrlidb,json_encode($ips)); flock($dbfh,LOCK_UN); if (count($atts) > $rrl_config["max_per_ip"]) { return false; } } return true; }