diff --git a/lam/help/help.inc b/lam/help/help.inc index 82e8a417..9cd82dd9 100644 --- a/lam/help/help.inc +++ b/lam/help/help.inc @@ -171,6 +171,8 @@ $helpArray = array ( "Text" => _('Specifies if the password must not contain the user name.')), "248" => array ("Headline" => _('Password must not contain part of user/first/last name'), "Text" => _('Specifies if the password must not contain 3 or more characters of the user/first/last name.')), + "249" => array ("Headline" => _('External password check'), + "Text" => _('Please specify the URL (e.g. "https://api.pwnedpasswords.com/range/{SHA1PREFIX}") of your external password check and the severity if the check fails.')), "250" => array ("Headline" => _("Filter"), "Text" => _("Here you can input simple filter expressions (e.g. 'value' or 'v*'). The filter is case-sensitive.")), "260" => array ("Headline" => _("Additional LDAP filter"), diff --git a/lam/lib/config.inc b/lam/lib/config.inc index eae0c1bd..af7b8697 100644 --- a/lam/lib/config.inc +++ b/lam/lib/config.inc @@ -1,10 +1,9 @@ checkedRulesCount); if (!in_array("passwordMustNotContain3Chars", $saved)) array_push($file_array, "\n\n# Password: must not contain part of user name\n" . "passwordMustNotContain3Chars: " . $this->passwordMustNotContain3Chars); if (!in_array("passwordMustNotContainUser", $saved)) array_push($file_array, "\n\n# Password: must not contain user name\n" . "passwordMustNotContainUser: " . $this->passwordMustNotContainUser); + if (!in_array("externalPwdCheckUrl", $saved)) array_push($file_array, "\n\n" . "externalPwdCheckUrl: " . $this->externalPwdCheckUrl); if (!in_array("mailEOL", $saved)) array_push($file_array, "\n\n# Email format (default/unix)\n" . "mailEOL: " . $this->mailEOL); if (!in_array("errorReporting", $saved)) array_push($file_array, "\n\n# PHP error reporting (default/system)\n" . "errorReporting: " . $this->errorReporting); if (!in_array("license", $saved)) array_push($file_array, "\n\n# License\n" . "license: " . $this->license); diff --git a/lam/lib/security.inc b/lam/lib/security.inc index b014cd07..bea11c4d 100644 --- a/lam/lib/security.inc +++ b/lam/lib/security.inc @@ -491,6 +491,49 @@ function checkPasswordStrength($password, $userName, $otherUserAttrs) { } } } + // check external password service + if (!checkPwdWithExternalPasswordService($cfg, $password)) { + return _('Your selected password is known to be insecure.'); + } + return true; +} + +/** + * Checks the password against the external password service. + * + * @param LAMCfgMain $cfg main configuration + * @param string $password password + * @return boolean password accepted as secure + */ +function checkPwdWithExternalPasswordService($cfg, $password) { + if (!function_exists('curl_init') || empty($cfg->externalPwdCheckUrl)) { + return true; + } + $sha1 = sha1($password); + $sha1Prefix = substr($sha1, 0, 5); + $sha1Suffix = substr($sha1, 5); + $curl = curl_init(); + curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); + $url = $cfg->externalPwdCheckUrl; + $url = str_replace('{SHA1PREFIX}', $sha1Prefix, $url); + curl_setopt($curl, CURLOPT_URL, $url); + $results = curl_exec($curl); + $code = curl_errno($curl); + if ($code) { + logNewMessage(LOG_ERR, 'Error calling the external password service at ' . $url + . '. ' . curl_error($curl)); + return true; + } + curl_close($curl); + if (empty($results)) { + return true; + } + $results = explode("\n", $results); + foreach ($results as $result) { + if (stripos($result, $sha1Suffix . ':') !== false) { + return false; + } + } return true; } diff --git a/lam/templates/config/mainmanage.php b/lam/templates/config/mainmanage.php index cd8d9a42..450f8202 100644 --- a/lam/templates/config/mainmanage.php +++ b/lam/templates/config/mainmanage.php @@ -179,6 +179,12 @@ if (isset($_POST['submitFormData'])) { $cfg->checkedRulesCount = $_POST['passwordRulesCount']; $cfg->passwordMustNotContain3Chars = isset($_POST['passwordMustNotContain3Chars']) && ($_POST['passwordMustNotContain3Chars'] == 'on') ? 'true' : 'false'; $cfg->passwordMustNotContainUser = isset($_POST['passwordMustNotContainUser']) && ($_POST['passwordMustNotContainUser'] == 'on') ? 'true' : 'false'; + if (function_exists('curl_init')) { + $cfg->externalPwdCheckUrl = $_POST['externalPwdCheckUrl']; + if (!empty($cfg->externalPwdCheckUrl) && (strpos($cfg->externalPwdCheckUrl, '{SHA1PREFIX}') === false)) { + $errors[] = _('The URL for the external password check is invalid.'); + } + } if (isset($_POST['sslCaCertUpload'])) { if (!isset($_FILES['sslCaCert']) || ($_FILES['sslCaCert']['size'] == 0)) { $errors[] = _('No file selected.'); @@ -378,6 +384,10 @@ $passwordMustNotContainUser = ($cfg->passwordMustNotContainUser === 'true') ? tr $row->add(new htmlResponsiveInputCheckbox('passwordMustNotContainUser',$passwordMustNotContainUser , _('Password must not contain user name'), '247'), 12); $passwordMustNotContain3Chars = ($cfg->passwordMustNotContain3Chars === 'true') ? true : false; $row->add(new htmlResponsiveInputCheckbox('passwordMustNotContain3Chars', $passwordMustNotContain3Chars, _('Password must not contain part of user/first/last name'), '248'), 12); +if (function_exists('curl_init')) { + $row->addVerticalSpacer('1rem'); + $row->add(new htmlResponsiveInputField(_('External password check'), 'externalPwdCheckUrl', $cfg->externalPwdCheckUrl, '249'), 12); +} // logging $row->add(new htmlSubTitle(_("Logging")), 12);