[Web] Feature: Add password policy

master
andryyy 2021-04-09 13:46:17 +02:00
parent 616226be8a
commit 5ea649b292
No known key found for this signature in database
GPG Key ID: 8EC34FF2794E25EF
12 changed files with 1042 additions and 921 deletions

View File

@ -23,6 +23,7 @@ if (!isset($_SESSION['gal']) && $license_cache = $redis->Get('LICENSE_STATUS_CAC
<li role="presentation"><a href="#tab-config-quarantine" aria-controls="tab-config-quarantine" role="tab" data-toggle="tab"><?=$lang['admin']['quarantine'];?></a></li> <li role="presentation"><a href="#tab-config-quarantine" aria-controls="tab-config-quarantine" role="tab" data-toggle="tab"><?=$lang['admin']['quarantine'];?></a></li>
<li role="presentation"><a href="#tab-config-quota" aria-controls="tab-config-quota" role="tab" data-toggle="tab"><?=$lang['admin']['quota_notifications'];?></a></li> <li role="presentation"><a href="#tab-config-quota" aria-controls="tab-config-quota" role="tab" data-toggle="tab"><?=$lang['admin']['quota_notifications'];?></a></li>
<li role="presentation"><a href="#tab-config-rsettings" aria-controls="tab-config-rsettings" role="tab" data-toggle="tab"><?=$lang['admin']['rspamd_settings_map'];?></a></li> <li role="presentation"><a href="#tab-config-rsettings" aria-controls="tab-config-rsettings" role="tab" data-toggle="tab"><?=$lang['admin']['rspamd_settings_map'];?></a></li>
<li role="presentation"><a href="#tab-config-password-policy" aria-controls="tab-config-password-policy" role="tab" data-toggle="tab"><?=$lang['admin']['password_policy'];?></a></li>
<li role="presentation"><a href="#tab-config-customize" aria-controls="tab-config-customize" role="tab" data-toggle="tab"><?=$lang['admin']['customize'];?></a></li> <li role="presentation"><a href="#tab-config-customize" aria-controls="tab-config-customize" role="tab" data-toggle="tab"><?=$lang['admin']['customize'];?></a></li>
</ul> </ul>
</li> </li>
@ -77,7 +78,7 @@ if (!isset($_SESSION['gal']) && $license_cache = $redis->Get('LICENSE_STATUS_CAC
foreach ($tfa_data['additional'] as $key_info) { foreach ($tfa_data['additional'] as $key_info) {
?> ?>
<form style="display:inline;" method="post"> <form style="display:inline;" method="post">
<input type="hidden" name="unset_tfa_key" value="<?=$key_info['id'];?>" /> <input type="hidden" name="unset_tfa_key" value="<?=$key_info['id'];?>">
<div style="padding:4px;margin:4px" class="label label-keys label-<?=($_SESSION['tfa_id'] == $key_info['id']) ? 'success' : 'default'; ?>"> <div style="padding:4px;margin:4px" class="label label-keys label-<?=($_SESSION['tfa_id'] == $key_info['id']) ? 'success' : 'default'; ?>">
<?=$key_info['key_id'];?> <?=$key_info['key_id'];?>
<a href="#" style="font-weight:bold;color:white" onClick="$(this).closest('form').submit()">[<?=$lang['admin']['remove'];?>]</a> <a href="#" style="font-weight:bold;color:white" onClick="$(this).closest('form').submit()">[<?=$lang['admin']['remove'];?>]</a>
@ -128,7 +129,7 @@ if (!isset($_SESSION['gal']) && $license_cache = $redis->Get('LICENSE_STATUS_CAC
</td> </td>
<td style="min-width:240px;text-align: right"> <td style="min-width:240px;text-align: right">
<form style="display:inline;" method="post"> <form style="display:inline;" method="post">
<input type="hidden" name="unset_fido2_key" value="<?=$key_info['cid'];?>" /> <input type="hidden" name="unset_fido2_key" value="<?=$key_info['cid'];?>">
<div class="btn-group"> <div class="btn-group">
<a href="#" class="btn btn-xs btn-default" data-cid="<?=$key_info['cid'];?>" data-subject="<?=base64_encode($key_info['subject']);?>" data-toggle="modal" data-target="#fido2ChangeFn"><span class="glyphicon glyphicon-pencil"></span> <?=$lang['fido2']['rename'];?></a> <a href="#" class="btn btn-xs btn-default" data-cid="<?=$key_info['cid'];?>" data-subject="<?=base64_encode($key_info['subject']);?>" data-toggle="modal" data-target="#fido2ChangeFn"><span class="glyphicon glyphicon-pencil"></span> <?=$lang['fido2']['rename'];?></a>
<a href="#" onClick='return confirm("<?=$lang['admin']['ays'];?>")?$(this).closest("form").submit():"";' class="btn btn-xs btn-danger"><span class="glyphicon glyphicon-trash"></span> <?=$lang['admin']['remove'];?></a> <a href="#" onClick='return confirm("<?=$lang['admin']['ays'];?>")?$(this).closest("form").submit():"";' class="btn btn-xs btn-danger"><span class="glyphicon glyphicon-trash"></span> <?=$lang['admin']['remove'];?></a>
@ -381,7 +382,7 @@ if (!isset($_SESSION['gal']) && $license_cache = $redis->Get('LICENSE_STATUS_CAC
</form> </form>
</div> </div>
<div class="col-sm-3"> <div class="col-sm-3">
<img class="img-responsive" src="/img/rspamd_logo.png" alt="Rspamd UI" /> <img class="img-responsive" src="/img/rspamd_logo.png" alt="Rspamd UI">
</div> </div>
</div> </div>
</div> </div>
@ -507,7 +508,7 @@ if (!isset($_SESSION['gal']) && $license_cache = $redis->Get('LICENSE_STATUS_CAC
($GLOBALS['SHOW_DKIM_PRIV_KEYS'] === true) ?: $dkim['privkey'] = base64_encode('Please set $SHOW_DKIM_PRIV_KEYS to true to show DKIM private keys.'); ($GLOBALS['SHOW_DKIM_PRIV_KEYS'] === true) ?: $dkim['privkey'] = base64_encode('Please set $SHOW_DKIM_PRIV_KEYS to true to show DKIM private keys.');
?> ?>
<div class="row collapse in dkim_key_valid"> <div class="row collapse in dkim_key_valid">
<div class="col-md-1"><input type="checkbox" data-id="dkim" name="multi_select" value="<?=$domain;?>" /></div> <div class="col-md-1"><input type="checkbox" data-id="dkim" name="multi_select" value="<?=$domain;?>"></div>
<div class="col-md-3"> <div class="col-md-3">
<p><?=$lang['admin']['domain'];?>: <strong><?=htmlspecialchars($domain);?></strong> <p><?=$lang['admin']['domain'];?>: <strong><?=htmlspecialchars($domain);?></strong>
<p class="dkim-label"><span class="label label-success"><?=$lang['admin']['dkim_key_valid'];?></span></p> <p class="dkim-label"><span class="label label-success"><?=$lang['admin']['dkim_key_valid'];?></span></p>
@ -526,7 +527,7 @@ if (!isset($_SESSION['gal']) && $license_cache = $redis->Get('LICENSE_STATUS_CAC
else { else {
?> ?>
<div class="row collapse in dkim_key_missing"> <div class="row collapse in dkim_key_missing">
<div class="col-md-1"><input class="dkim_missing" type="checkbox" data-id="dkim" name="multi_select" value="<?=$domain;?>" disabled /></div> <div class="col-md-1"><input class="dkim_missing" type="checkbox" data-id="dkim" name="multi_select" value="<?=$domain;?>" disabled></div>
<div class="col-md-3"> <div class="col-md-3">
<p><?=$lang['admin']['domain'];?>: <strong><?=htmlspecialchars($domain);?></strong><br><span class="label label-danger"><?=$lang['admin']['dkim_key_missing'];?></span></p> <p><?=$lang['admin']['domain'];?>: <strong><?=htmlspecialchars($domain);?></strong><br><span class="label label-danger"><?=$lang['admin']['dkim_key_missing'];?></span></p>
</div> </div>
@ -541,7 +542,7 @@ if (!isset($_SESSION['gal']) && $license_cache = $redis->Get('LICENSE_STATUS_CAC
($GLOBALS['SHOW_DKIM_PRIV_KEYS'] === true) ?: $dkim['privkey'] = base64_encode('Please set $SHOW_DKIM_PRIV_KEYS to true to show DKIM private keys.'); ($GLOBALS['SHOW_DKIM_PRIV_KEYS'] === true) ?: $dkim['privkey'] = base64_encode('Please set $SHOW_DKIM_PRIV_KEYS to true to show DKIM private keys.');
?> ?>
<div class="row collapse in dkim_key_valid"> <div class="row collapse in dkim_key_valid">
<div class="col-md-1"><input type="checkbox" data-id="dkim" name="multi_select" value="<?=$alias_domain;?>" /></div> <div class="col-md-1"><input type="checkbox" data-id="dkim" name="multi_select" value="<?=$alias_domain;?>"></div>
<div class="col-md-2 col-md-offset-1"> <div class="col-md-2 col-md-offset-1">
<p><small> Alias-Domain: <strong><?=htmlspecialchars($alias_domain);?></strong></small> <p><small> Alias-Domain: <strong><?=htmlspecialchars($alias_domain);?></strong></small>
<p class="dkim-label"><span class="label label-success"><?=$lang['admin']['dkim_key_valid'];?></span></p> <p class="dkim-label"><span class="label label-success"><?=$lang['admin']['dkim_key_valid'];?></span></p>
@ -560,7 +561,7 @@ if (!isset($_SESSION['gal']) && $license_cache = $redis->Get('LICENSE_STATUS_CAC
else { else {
?> ?>
<div class="row collapse in dkim_key_missing"> <div class="row collapse in dkim_key_missing">
<div class="col-md-1"><input class="dkim_missing" type="checkbox" data-id="dkim" name="multi_select" value="<?=$alias_domain;?>" disabled /></div> <div class="col-md-1"><input class="dkim_missing" type="checkbox" data-id="dkim" name="multi_select" value="<?=$alias_domain;?>" disabled></div>
<div class="col-md-2 col-md-offset-1"> <div class="col-md-2 col-md-offset-1">
<p><small> Alias-Domain: <strong><?=htmlspecialchars($alias_domain);?></strong><br></small><span class="label label-danger"><?=$lang['admin']['dkim_key_missing'];?></span></p> <p><small> Alias-Domain: <strong><?=htmlspecialchars($alias_domain);?></strong><br></small><span class="label label-danger"><?=$lang['admin']['dkim_key_missing'];?></span></p>
</div> </div>
@ -577,7 +578,7 @@ if (!isset($_SESSION['gal']) && $license_cache = $redis->Get('LICENSE_STATUS_CAC
($GLOBALS['SHOW_DKIM_PRIV_KEYS'] === true) ?: $dkim['privkey'] = base64_encode('Please set $SHOW_DKIM_PRIV_KEYS to true to show DKIM private keys.'); ($GLOBALS['SHOW_DKIM_PRIV_KEYS'] === true) ?: $dkim['privkey'] = base64_encode('Please set $SHOW_DKIM_PRIV_KEYS to true to show DKIM private keys.');
?> ?>
<div class="row collapse in dkim_key_unused"> <div class="row collapse in dkim_key_unused">
<div class="col-md-1"><input type="checkbox" data-id="dkim" name="multi_select" value="<?=$blind;?>" /></div> <div class="col-md-1"><input type="checkbox" data-id="dkim" name="multi_select" value="<?=$blind;?>"></div>
<div class="col-md-3"> <div class="col-md-3">
<p><?=$lang['admin']['domain'];?>: <strong><?=htmlspecialchars($blind);?></strong> <p><?=$lang['admin']['domain'];?>: <strong><?=htmlspecialchars($blind);?></strong>
<p class="dkim-label"><span class="label label-warning"><?=$lang['admin']['dkim_key_unused'];?></span></p> <p class="dkim-label"><span class="label label-warning"><?=$lang['admin']['dkim_key_unused'];?></span></p>
@ -1212,6 +1213,49 @@ if (!isset($_SESSION['gal']) && $license_cache = $redis->Get('LICENSE_STATUS_CAC
</div> </div>
</div> </div>
<div role="tabpanel" class="tab-pane" id="tab-config-password-policy">
<div class="panel panel-default">
<div class="panel-heading"><?=$lang['admin']['password_policy'];?></div>
<div class="panel-body">
<?php $password_complexity = password_complexity('get'); ?>
<form class="form-horizontal" data-id="passwordpolicy" role="form" method="post">
<?php
foreach ($password_complexity as $name => $value) {
if ($name == 'length') {
?>
<div class="form-group">
<label class="control-label col-sm-3" for="<?=$name;?>"><?=$lang['admin']['password_length'];?>:</label>
<div class="col-sm-2">
<input type="number" class="form-control" min="3" max="64" name="<?=$name;?>" id="<?=$name;?>" value="<?=$value;?>" required>
</div>
</div>
<?php
} else {
?>
<input type="hidden" name="<?=$name;?>" value="0">
<div class="form-group">
<div class="col-sm-offset-3 col-sm-9">
<label>
<input type="checkbox" name="<?=$name;?>" id="<?=$name;?>" value="1" <?=($value == 1) ? 'checked' : null;?>> <?=$lang['admin']["password_policy_$name"];?>
</label>
</div>
</div>
<?php
}
}
?>
<div class="form-group">
<div class="col-sm-offset-3 col-sm-9">
<div class="btn-group">
<button class="btn btn-sm btn-success" data-item="passwordpolicy" data-action="edit_selected" data-id="passwordpolicy" data-api-url='edit/passwordpolicy' data-api-attr='{}' href="#"><span class="glyphicon glyphicon-check"></span> <?=$lang['admin']['save'];?></button>
</div>
</div>
</div>
</form>
</div>
</div>
</div>
<div role="tabpanel" class="tab-pane" id="tab-sys-mails"> <div role="tabpanel" class="tab-pane" id="tab-sys-mails">
<div class="panel panel-default"> <div class="panel panel-default">
<div class="panel-heading"><?=$lang['admin']['sys_mails'];?></div> <div class="panel-heading"><?=$lang['admin']['sys_mails'];?></div>

View File

@ -48,40 +48,23 @@ function admin($_action, $_data = null) {
return false; return false;
} }
} }
if (!empty($password) && !empty($password2)) { if (password_check($password_new, $password_new2) !== true) {
if (!preg_match('/' . $GLOBALS['PASSWD_REGEP'] . '/', $password)) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_data_log),
'msg' => 'password_complexity'
);
return false;
}
if ($password != $password2) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_data_log),
'msg' => 'password_mismatch'
);
return false;
}
$password_hashed = hash_password($password);
$stmt = $pdo->prepare("INSERT INTO `admin` (`username`, `password`, `superadmin`, `active`)
VALUES (:username, :password_hashed, '1', :active)");
$stmt->execute(array(
':username' => $username,
':password_hashed' => $password_hashed,
':active' => $active
));
}
else {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_data_log),
'msg' => 'password_empty'
);
return false; return false;
} }
// support pre hashed passwords
if (preg_match('/^{(ARGON2I|ARGON2ID|BLF-CRYPT|CLEAR|CLEARTEXT|CRYPT|DES-CRYPT|LDAP-MD5|MD5|MD5-CRYPT|PBKDF2|PLAIN|PLAIN-MD4|PLAIN-MD5|PLAIN-TRUNC|PLAIN-TRUNC|SHA|SHA1|SHA256|SHA256-CRYPT|SHA512|SHA512-CRYPT|SMD5|SSHA|SSHA256|SSHA512)}/i', $password)) {
$password_hashed = $password_new;
}
else {
$password_hashed = hash_password($password_new);
}
$stmt = $pdo->prepare("INSERT INTO `admin` (`username`, `password`, `superadmin`, `active`)
VALUES (:username, :password_hashed, '1', :active)");
$stmt->execute(array(
':username' => $username,
':password_hashed' => $password_hashed,
':active' => $active
));
$_SESSION['return'][] = array( $_SESSION['return'][] = array(
'type' => 'success', 'type' => 'success',
'log' => array(__FUNCTION__, $_action, $_data_log), 'log' => array(__FUNCTION__, $_action, $_data_log),
@ -145,23 +128,16 @@ function admin($_action, $_data = null) {
} }
} }
if (!empty($password) && !empty($password2)) { if (!empty($password) && !empty($password2)) {
if (!preg_match('/' . $GLOBALS['PASSWD_REGEP'] . '/', $password)) { if (password_check($password, $password2) !== true) {
$_SESSION['return'][] = array( return false;
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_data_log),
'msg' => 'password_complexity'
);
continue;
} }
if ($password != $password2) { // support pre hashed passwords
$_SESSION['return'][] = array( if (preg_match('/^{(ARGON2I|ARGON2ID|BLF-CRYPT|CLEAR|CLEARTEXT|CRYPT|DES-CRYPT|LDAP-MD5|MD5|MD5-CRYPT|PBKDF2|PLAIN|PLAIN-MD4|PLAIN-MD5|PLAIN-TRUNC|PLAIN-TRUNC|SHA|SHA1|SHA256|SHA256-CRYPT|SHA512|SHA512-CRYPT|SMD5|SSHA|SSHA256|SSHA512)}/i', $password)) {
'type' => 'danger', $password_hashed = $password;
'log' => array(__FUNCTION__, $_action, $_data_log), }
'msg' => 'password_mismatch' else {
); $password_hashed = hash_password($password);
continue;
} }
$password_hashed = hash_password($password);
$stmt = $pdo->prepare("UPDATE `admin` SET `username` = :username_new, `active` = :active, `password` = :password_hashed WHERE `username` = :username"); $stmt = $pdo->prepare("UPDATE `admin` SET `username` = :username_new, `active` = :active, `password` = :password_hashed WHERE `username` = :username");
$stmt->execute(array( $stmt->execute(array(
':password_hashed' => $password_hashed, ':password_hashed' => $password_hashed,

View File

@ -21,7 +21,7 @@ function dkim($_action, $_data = null, $privkey = false) {
if (!is_valid_domain_name($domain) || !is_numeric($key_length)) { if (!is_valid_domain_name($domain) || !is_numeric($key_length)) {
$_SESSION['return'][] = array( $_SESSION['return'][] = array(
'type' => 'danger', 'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_data, $privkey), 'log' => array(__FUNCTION__, $_action, $_data),
'msg' => array('dkim_domain_or_sel_invalid', $domain) 'msg' => array('dkim_domain_or_sel_invalid', $domain)
); );
continue; continue;
@ -29,7 +29,7 @@ function dkim($_action, $_data = null, $privkey = false) {
if ($redis->hGet('DKIM_PUB_KEYS', $domain)) { if ($redis->hGet('DKIM_PUB_KEYS', $domain)) {
$_SESSION['return'][] = array( $_SESSION['return'][] = array(
'type' => 'danger', 'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_data, $privkey), 'log' => array(__FUNCTION__, $_action, $_data),
'msg' => array('dkim_domain_or_sel_invalid', $domain) 'msg' => array('dkim_domain_or_sel_invalid', $domain)
); );
continue; continue;
@ -37,7 +37,7 @@ function dkim($_action, $_data = null, $privkey = false) {
if (!ctype_alnum(str_replace(['-', '_'], '', $dkim_selector))) { if (!ctype_alnum(str_replace(['-', '_'], '', $dkim_selector))) {
$_SESSION['return'][] = array( $_SESSION['return'][] = array(
'type' => 'danger', 'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_data, $privkey), 'log' => array(__FUNCTION__, $_action, $_data),
'msg' => array('dkim_domain_or_sel_invalid', $domain) 'msg' => array('dkim_domain_or_sel_invalid', $domain)
); );
continue; continue;
@ -62,7 +62,7 @@ function dkim($_action, $_data = null, $privkey = false) {
catch (RedisException $e) { catch (RedisException $e) {
$_SESSION['return'][] = array( $_SESSION['return'][] = array(
'type' => 'danger', 'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_data, $privkey), 'log' => array(__FUNCTION__, $_action, $_data),
'msg' => array('redis_error', $e) 'msg' => array('redis_error', $e)
); );
continue; continue;
@ -76,7 +76,7 @@ function dkim($_action, $_data = null, $privkey = false) {
catch (RedisException $e) { catch (RedisException $e) {
$_SESSION['return'][] = array( $_SESSION['return'][] = array(
'type' => 'danger', 'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_data, $privkey), 'log' => array(__FUNCTION__, $_action, $_data),
'msg' => array('redis_error', $e) 'msg' => array('redis_error', $e)
); );
continue; continue;
@ -84,14 +84,14 @@ function dkim($_action, $_data = null, $privkey = false) {
} }
$_SESSION['return'][] = array( $_SESSION['return'][] = array(
'type' => 'success', 'type' => 'success',
'log' => array(__FUNCTION__, $_action, $_data, $privkey), 'log' => array(__FUNCTION__, $_action, $_data),
'msg' => array('dkim_added', $domain) 'msg' => array('dkim_added', $domain)
); );
} }
else { else {
$_SESSION['return'][] = array( $_SESSION['return'][] = array(
'type' => 'danger', 'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_data, $privkey), 'log' => array(__FUNCTION__, $_action, $_data),
'msg' => array('dkim_domain_or_sel_invalid', $domain) 'msg' => array('dkim_domain_or_sel_invalid', $domain)
); );
continue; continue;
@ -102,7 +102,7 @@ function dkim($_action, $_data = null, $privkey = false) {
if ($_SESSION['mailcow_cc_role'] != "admin") { if ($_SESSION['mailcow_cc_role'] != "admin") {
$_SESSION['return'][] = array( $_SESSION['return'][] = array(
'type' => 'danger', 'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_data, $privkey), 'log' => array(__FUNCTION__, $_action, $_data),
'msg' => 'access_denied' 'msg' => 'access_denied'
); );
return false; return false;
@ -112,7 +112,7 @@ function dkim($_action, $_data = null, $privkey = false) {
if (empty($from_domain_dkim)) { if (empty($from_domain_dkim)) {
$_SESSION['return'][] = array( $_SESSION['return'][] = array(
'type' => 'danger', 'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_data, $privkey), 'log' => array(__FUNCTION__, $_action, $_data),
'msg' => array('dkim_domain_or_sel_invalid', $from_domain) 'msg' => array('dkim_domain_or_sel_invalid', $from_domain)
); );
continue; continue;
@ -128,14 +128,14 @@ function dkim($_action, $_data = null, $privkey = false) {
catch (RedisException $e) { catch (RedisException $e) {
$_SESSION['return'][] = array( $_SESSION['return'][] = array(
'type' => 'danger', 'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_data, $privkey), 'log' => array(__FUNCTION__, $_action, $_data),
'msg' => array('redis_error', $e) 'msg' => array('redis_error', $e)
); );
continue; continue;
} }
$_SESSION['return'][] = array( $_SESSION['return'][] = array(
'type' => 'success', 'type' => 'success',
'log' => array(__FUNCTION__, $_action, $_data, $privkey), 'log' => array(__FUNCTION__, $_action, $_data),
'msg' => array('dkim_duplicated', $from_domain, $to_domain) 'msg' => array('dkim_duplicated', $from_domain, $to_domain)
); );
} }
@ -144,7 +144,7 @@ function dkim($_action, $_data = null, $privkey = false) {
if ($_SESSION['mailcow_cc_role'] != "admin") { if ($_SESSION['mailcow_cc_role'] != "admin") {
$_SESSION['return'][] = array( $_SESSION['return'][] = array(
'type' => 'danger', 'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_data, $privkey), 'log' => array(__FUNCTION__, $_action, $_data),
'msg' => 'access_denied' 'msg' => 'access_denied'
); );
return false; return false;
@ -156,7 +156,7 @@ function dkim($_action, $_data = null, $privkey = false) {
if ($ssl_error = openssl_error_string()) { if ($ssl_error = openssl_error_string()) {
$_SESSION['return'][] = array( $_SESSION['return'][] = array(
'type' => 'danger', 'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_data, $privkey), 'log' => array(__FUNCTION__, $_action, $_data),
'msg' => array('private_key_error', $ssl_error) 'msg' => array('private_key_error', $ssl_error)
); );
return false; return false;
@ -173,7 +173,7 @@ function dkim($_action, $_data = null, $privkey = false) {
if (!is_valid_domain_name($domain)) { if (!is_valid_domain_name($domain)) {
$_SESSION['return'][] = array( $_SESSION['return'][] = array(
'type' => 'danger', 'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_data, $privkey), 'log' => array(__FUNCTION__, $_action, $_data),
'msg' => array('dkim_domain_or_sel_invalid', $domain) 'msg' => array('dkim_domain_or_sel_invalid', $domain)
); );
return false; return false;
@ -182,7 +182,7 @@ function dkim($_action, $_data = null, $privkey = false) {
if ($overwrite_existing == 0) { if ($overwrite_existing == 0) {
$_SESSION['return'][] = array( $_SESSION['return'][] = array(
'type' => 'danger', 'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_data, $privkey), 'log' => array(__FUNCTION__, $_action, $_data),
'msg' => array('dkim_domain_or_sel_exists', $domain) 'msg' => array('dkim_domain_or_sel_exists', $domain)
); );
return false; return false;
@ -191,7 +191,7 @@ function dkim($_action, $_data = null, $privkey = false) {
if (!ctype_alnum($dkim_selector)) { if (!ctype_alnum($dkim_selector)) {
$_SESSION['return'][] = array( $_SESSION['return'][] = array(
'type' => 'danger', 'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_data, $privkey), 'log' => array(__FUNCTION__, $_action, $_data),
'msg' => array('dkim_domain_or_sel_invalid', $domain) 'msg' => array('dkim_domain_or_sel_invalid', $domain)
); );
return false; return false;
@ -205,7 +205,7 @@ function dkim($_action, $_data = null, $privkey = false) {
catch (RedisException $e) { catch (RedisException $e) {
$_SESSION['return'][] = array( $_SESSION['return'][] = array(
'type' => 'danger', 'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_data, $privkey), 'log' => array(__FUNCTION__, $_action, $_data),
'msg' => array('redis_error', $e) 'msg' => array('redis_error', $e)
); );
return false; return false;
@ -218,14 +218,14 @@ function dkim($_action, $_data = null, $privkey = false) {
catch (RedisException $e) { catch (RedisException $e) {
$_SESSION['return'][] = array( $_SESSION['return'][] = array(
'type' => 'danger', 'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_data, $privkey), 'log' => array(__FUNCTION__, $_action, $_data),
'msg' => array('redis_error', $e) 'msg' => array('redis_error', $e)
); );
return false; return false;
} }
$_SESSION['return'][] = array( $_SESSION['return'][] = array(
'type' => 'success', 'type' => 'success',
'log' => array(__FUNCTION__, $_action, $_data, $privkey), 'log' => array(__FUNCTION__, $_action, $_data),
'msg' => array('dkim_added', $domain) 'msg' => array('dkim_added', $domain)
); );
return true; return true;
@ -270,7 +270,7 @@ function dkim($_action, $_data = null, $privkey = false) {
if ($_SESSION['mailcow_cc_role'] != "admin") { if ($_SESSION['mailcow_cc_role'] != "admin") {
$_SESSION['return'][] = array( $_SESSION['return'][] = array(
'type' => 'danger', 'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_data, $privkey), 'log' => array(__FUNCTION__, $_action, $_data),
'msg' => 'access_denied' 'msg' => 'access_denied'
); );
return false; return false;
@ -286,7 +286,7 @@ function dkim($_action, $_data = null, $privkey = false) {
if ($_SESSION['mailcow_cc_role'] != "admin") { if ($_SESSION['mailcow_cc_role'] != "admin") {
$_SESSION['return'][] = array( $_SESSION['return'][] = array(
'type' => 'danger', 'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_data, $privkey), 'log' => array(__FUNCTION__, $_action, $_data),
'msg' => 'access_denied' 'msg' => 'access_denied'
); );
return false; return false;
@ -295,7 +295,7 @@ function dkim($_action, $_data = null, $privkey = false) {
if (!is_valid_domain_name($domain)) { if (!is_valid_domain_name($domain)) {
$_SESSION['return'][] = array( $_SESSION['return'][] = array(
'type' => 'danger', 'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_data, $privkey), 'log' => array(__FUNCTION__, $_action, $_data),
'msg' => array('dkim_domain_or_sel_invalid', $domain) 'msg' => array('dkim_domain_or_sel_invalid', $domain)
); );
continue; continue;
@ -309,14 +309,14 @@ function dkim($_action, $_data = null, $privkey = false) {
catch (RedisException $e) { catch (RedisException $e) {
$_SESSION['return'][] = array( $_SESSION['return'][] = array(
'type' => 'danger', 'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_data, $privkey), 'log' => array(__FUNCTION__, $_action, $_data),
'msg' => array('redis_error', $e) 'msg' => array('redis_error', $e)
); );
continue; continue;
} }
$_SESSION['return'][] = array( $_SESSION['return'][] = array(
'type' => 'success', 'type' => 'success',
'log' => array(__FUNCTION__, $_action, $_data, $privkey), 'log' => array(__FUNCTION__, $_action, $_data),
'msg' => array('dkim_removed', htmlspecialchars($domain)) 'msg' => array('dkim_removed', htmlspecialchars($domain))
); );
} }

View File

@ -65,61 +65,44 @@ function domain_admin($_action, $_data = null) {
return false; return false;
} }
} }
if (!empty($password) && !empty($password2)) { if (password_check($password, $password2) !== true) {
if (!preg_match('/' . $GLOBALS['PASSWD_REGEP'] . '/', $password)) { continue;
$_SESSION['return'][] = array( }
'type' => 'danger', // support pre hashed passwords
'log' => array(__FUNCTION__, $_action, $_data_log), if (preg_match('/^{(ARGON2I|ARGON2ID|BLF-CRYPT|CLEAR|CLEARTEXT|CRYPT|DES-CRYPT|LDAP-MD5|MD5|MD5-CRYPT|PBKDF2|PLAIN|PLAIN-MD4|PLAIN-MD5|PLAIN-TRUNC|PLAIN-TRUNC|SHA|SHA1|SHA256|SHA256-CRYPT|SHA512|SHA512-CRYPT|SMD5|SSHA|SSHA256|SSHA512)}/i', $password)) {
'msg' => 'password_complexity' $password_hashed = $password;
);
return false;
}
if ($password != $password2) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_data_log),
'msg' => 'password_mismatch'
);
return false;
}
$password_hashed = hash_password($password);
$valid_domains = 0;
foreach ($domains as $domain) {
if (!is_valid_domain_name($domain) || mailbox('get', 'domain_details', $domain) === false) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_data_log),
'msg' => array('domain_invalid', htmlspecialchars($domain))
);
continue;
}
$valid_domains++;
$stmt = $pdo->prepare("INSERT INTO `domain_admins` (`username`, `domain`, `created`, `active`)
VALUES (:username, :domain, :created, :active)");
$stmt->execute(array(
':username' => $username,
':domain' => $domain,
':created' => date('Y-m-d H:i:s'),
':active' => $active
));
}
if ($valid_domains != 0) {
$stmt = $pdo->prepare("INSERT INTO `admin` (`username`, `password`, `superadmin`, `active`)
VALUES (:username, :password_hashed, '0', :active)");
$stmt->execute(array(
':username' => $username,
':password_hashed' => $password_hashed,
':active' => $active
));
}
} }
else { else {
$_SESSION['return'][] = array( $password_hashed = hash_password($password);
'type' => 'danger', }
'log' => array(__FUNCTION__, $_action, $_data_log), $valid_domains = 0;
'msg' => 'password_empty' foreach ($domains as $domain) {
); if (!is_valid_domain_name($domain) || mailbox('get', 'domain_details', $domain) === false) {
return false; $_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_data_log),
'msg' => array('domain_invalid', htmlspecialchars($domain))
);
continue;
}
$valid_domains++;
$stmt = $pdo->prepare("INSERT INTO `domain_admins` (`username`, `domain`, `created`, `active`)
VALUES (:username, :domain, :created, :active)");
$stmt->execute(array(
':username' => $username,
':domain' => $domain,
':created' => date('Y-m-d H:i:s'),
':active' => $active
));
}
if ($valid_domains != 0) {
$stmt = $pdo->prepare("INSERT INTO `admin` (`username`, `password`, `superadmin`, `active`)
VALUES (:username, :password_hashed, '0', :active)");
$stmt->execute(array(
':username' => $username,
':password_hashed' => $password_hashed,
':active' => $active
));
} }
$stmt = $pdo->prepare("INSERT INTO `da_acl` (`username`) VALUES (:username)"); $stmt = $pdo->prepare("INSERT INTO `da_acl` (`username`) VALUES (:username)");
$stmt->execute(array( $stmt->execute(array(
@ -219,23 +202,16 @@ function domain_admin($_action, $_data = null) {
} }
} }
if (!empty($password) && !empty($password2)) { if (!empty($password) && !empty($password2)) {
if (!preg_match('/' . $GLOBALS['PASSWD_REGEP'] . '/', $password)) { if (password_check($password, $password2) !== true) {
$_SESSION['return'][] = array( return false;
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_data_log),
'msg' => 'password_complexity'
);
continue;
} }
if ($password != $password2) { // support pre hashed passwords
$_SESSION['return'][] = array( if (preg_match('/^{(ARGON2I|ARGON2ID|BLF-CRYPT|CLEAR|CLEARTEXT|CRYPT|DES-CRYPT|LDAP-MD5|MD5|MD5-CRYPT|PBKDF2|PLAIN|PLAIN-MD4|PLAIN-MD5|PLAIN-TRUNC|PLAIN-TRUNC|SHA|SHA1|SHA256|SHA256-CRYPT|SHA512|SHA512-CRYPT|SMD5|SSHA|SSHA256|SSHA512)}/i', $password)) {
'type' => 'danger', $password_hashed = $password;
'log' => array(__FUNCTION__, $_action, $_data_log), }
'msg' => 'password_mismatch' else {
); $password_hashed = hash_password($password);
continue;
} }
$password_hashed = hash_password($password);
$stmt = $pdo->prepare("UPDATE `admin` SET `username` = :username_new, `active` = :active, `password` = :password_hashed WHERE `username` = :username"); $stmt = $pdo->prepare("UPDATE `admin` SET `username` = :username_new, `active` = :active, `password` = :password_hashed WHERE `username` = :username");
$stmt->execute(array( $stmt->execute(array(
':password_hashed' => $password_hashed, ':password_hashed' => $password_hashed,
@ -296,30 +272,15 @@ function domain_admin($_action, $_data = null) {
); );
return false; return false;
} }
if (!empty($password_new2) && !empty($password_new)) { if (password_check($password_new, $password_new2) !== true) {
if ($password_new2 != $password_new) { return false;
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_data_log),
'msg' => 'password_mismatch'
);
return false;
}
if (!preg_match('/' . $GLOBALS['PASSWD_REGEP'] . '/', $password_new)) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_data_log),
'msg' => 'password_complexity'
);
return false;
}
$password_hashed = hash_password($password_new);
$stmt = $pdo->prepare("UPDATE `admin` SET `password` = :password_hashed WHERE `username` = :username");
$stmt->execute(array(
':password_hashed' => $password_hashed,
':username' => $username
));
} }
$password_hashed = hash_password($password_new);
$stmt = $pdo->prepare("UPDATE `admin` SET `password` = :password_hashed WHERE `username` = :username");
$stmt->execute(array(
':password_hashed' => $password_hashed,
':username' => $username
));
$_SESSION['return'][] = array( $_SESSION['return'][] = array(
'type' => 'success', 'type' => 'success',
'log' => array(__FUNCTION__, $_action, $_data_log), 'log' => array(__FUNCTION__, $_action, $_data_log),

View File

@ -114,6 +114,132 @@ function hash_password($password) {
} }
return $pw_hash; return $pw_hash;
} }
function password_complexity($_action, $_data = null) {
global $redis;
global $lang;
switch ($_action) {
case 'edit':
if ($_SESSION['mailcow_cc_role'] != "admin") {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_data),
'msg' => 'access_denied'
);
return false;
}
$is_now = password_complexity('get');
if (!empty($is_now)) {
$length = (isset($_data['length']) && intval($_data['length']) >= 3) ? intval($_data['length']) : $is_now['length'];
$chars = (isset($_data['chars'])) ? intval($_data['chars']) : $is_now['chars'];
$lowerupper = (isset($_data['lowerupper'])) ? intval($_data['lowerupper']) : $is_now['lowerupper'];
$special_chars = (isset($_data['special_chars'])) ? intval($_data['special_chars']) : $is_now['special_chars'];
$numbers = (isset($_data['numbers'])) ? intval($_data['numbers']) : $is_now['numbers'];
}
try {
$redis->hMSet('PASSWD_POLICY', [
'length' => $length,
'chars' => $chars,
'special_chars' => $special_chars,
'lowerupper' => $lowerupper,
'numbers' => $numbers
]);
}
catch (RedisException $e) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_data),
'msg' => array('redis_error', $e)
);
return false;
}
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $_action, $_data),
'msg' => print_r($_data, true)
);
break;
case 'get':
try {
$length = $redis->hGet('PASSWD_POLICY', 'length');
$chars = $redis->hGet('PASSWD_POLICY', 'chars');
$special_chars = $redis->hGet('PASSWD_POLICY', 'special_chars');
$lowerupper = $redis->hGet('PASSWD_POLICY', 'lowerupper');
$numbers = $redis->hGet('PASSWD_POLICY', 'numbers');
return array(
'length' => $length,
'chars' => $chars,
'special_chars' => $special_chars,
'lowerupper' => $lowerupper,
'numbers' => $numbers
);
}
catch (RedisException $e) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_data),
'msg' => array('redis_error', $e)
);
return false;
}
return false;
break;
case 'html':
$policies = password_complexity('get');
foreach ($policies as $name => $value) {
if ($value != 0) {
$policy_text[] = sprintf($lang['admin']["password_policy_$name"], $value);
}
}
return '<p class="help-block small">- ' . implode('<br>- ', $policy_text) . '</p>';
break;
}
}
function password_check($password1, $password2) {
$password_complexity = password_complexity('get');
if (empty($password1) || empty($password2)) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type),
'msg' => 'password_complexity'
);
return false;
}
if ($password1 != $password2) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type),
'msg' => 'password_mismatch'
);
return false;
}
$given_password['length'] = strlen($password1);
$given_password['special_chars'] = preg_match('/[^a-zA-Z\d]/', $password1);
$given_password['chars'] = preg_match('/[a-zA-Z]/',$password1);
$given_password['numbers'] = preg_match('/\d/', $password1);
$lower = strlen(preg_replace("/[^a-z]/", '', $password1));
$upper = strlen(preg_replace("/[^A-Z]/", '', $password1));
$given_password['lowerupper'] = ($lower > 0 && $upper > 0) ? true : false;
if (
($given_password['length'] < $password_complexity['length']) ||
($password_complexity['special_chars'] == 1 && (intval($given_password['special_chars']) != $password_complexity['special_chars'])) ||
($password_complexity['chars'] == 1 && (intval($given_password['chars']) != $password_complexity['chars'])) ||
($password_complexity['numbers'] == 1 && (intval($given_password['numbers']) != $password_complexity['numbers'])) ||
($password_complexity['lowerupper'] == 1 && (intval($given_password['lowerupper']) != $password_complexity['lowerupper']))
) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type),
'msg' => 'password_complexity'
);
return false;
}
return true;
}
function last_login($user) { function last_login($user) {
global $pdo; global $pdo;
$stmt = $pdo->prepare('SELECT `remote`, `time` FROM `logs` $stmt = $pdo->prepare('SELECT `remote`, `time` FROM `logs`

View File

@ -1045,32 +1045,15 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
); );
return false; return false;
} }
if (!empty($password) && !empty($password2)) { if (password_check($password, $password2) !== true) {
if (!preg_match('/' . $GLOBALS['PASSWD_REGEP'] . '/', $password)) { return false;
$_SESSION['return'][] = array( }
'type' => 'danger', // support pre hashed passwords
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), if (preg_match('/^{(ARGON2I|ARGON2ID|BLF-CRYPT|CLEAR|CLEARTEXT|CRYPT|DES-CRYPT|LDAP-MD5|MD5|MD5-CRYPT|PBKDF2|PLAIN|PLAIN-MD4|PLAIN-MD5|PLAIN-TRUNC|PLAIN-TRUNC|SHA|SHA1|SHA256|SHA256-CRYPT|SHA512|SHA512-CRYPT|SMD5|SSHA|SSHA256|SSHA512)}/i', $password)) {
'msg' => 'password_complexity' $password_hashed = $password;
);
return false;
}
if ($password != $password2) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'password_mismatch'
);
return false;
}
$password_hashed = hash_password($password);
} }
else { else {
$_SESSION['return'][] = array( $password_hashed = hash_password($password);
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'password_empty'
);
return false;
} }
if ($MailboxData['count'] >= $DomainData['mailboxes']) { if ($MailboxData['count'] >= $DomainData['mailboxes']) {
$_SESSION['return'][] = array( $_SESSION['return'][] = array(
@ -2599,38 +2582,23 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
)); ));
} }
} }
if (!empty($password) && !empty($password2)) { if (password_check($password, $password2) !== true) {
if (!preg_match('/' . $GLOBALS['PASSWD_REGEP'] . '/', $password)) { continue;
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'password_complexity'
);
continue;
}
if ($password != $password2) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'password_mismatch'
);
continue;
}
// support pre hashed passwords
if (preg_match('/^{(ARGON2I|ARGON2ID|BLF-CRYPT|CLEAR|CLEARTEXT|CRYPT|DES-CRYPT|LDAP-MD5|MD5|MD5-CRYPT|PBKDF2|PLAIN|PLAIN-MD4|PLAIN-MD5|PLAIN-TRUNC|PLAIN-TRUNC|SHA|SHA1|SHA256|SHA256-CRYPT|SHA512|SHA512-CRYPT|SMD5|SSHA|SSHA256|SSHA512)}/i', $password)) {
$password_hashed = $password;
}
else {
$password_hashed = hash_password($password);
}
$stmt = $pdo->prepare("UPDATE `mailbox` SET
`password` = :password_hashed
WHERE `username` = :username");
$stmt->execute(array(
':password_hashed' => $password_hashed,
':username' => $username
));
} }
// support pre hashed passwords
if (preg_match('/^{(ARGON2I|ARGON2ID|BLF-CRYPT|CLEAR|CLEARTEXT|CRYPT|DES-CRYPT|LDAP-MD5|MD5|MD5-CRYPT|PBKDF2|PLAIN|PLAIN-MD4|PLAIN-MD5|PLAIN-TRUNC|PLAIN-TRUNC|SHA|SHA1|SHA256|SHA256-CRYPT|SHA512|SHA512-CRYPT|SMD5|SSHA|SSHA256|SSHA512)}/i', $password)) {
$password_hashed = $password;
}
else {
$password_hashed = hash_password($password);
}
$stmt = $pdo->prepare("UPDATE `mailbox` SET
`password` = :password_hashed
WHERE `username` = :username");
$stmt->execute(array(
':password_hashed' => $password_hashed,
':username' => $username
));
// We could either set alias = 1 if alias = 2 or tune the Postfix alias table (that's what we did, TODO: to it the other way) // We could either set alias = 1 if alias = 2 or tune the Postfix alias table (that's what we did, TODO: to it the other way)
$stmt = $pdo->prepare("UPDATE `alias` SET $stmt = $pdo->prepare("UPDATE `alias` SET
`active` = :active `active` = :active

View File

@ -90,14 +90,6 @@ $AVAILABLE_LANGUAGES = array('ca', 'cs', 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu
// WARNING: Only lumen is loaded locally. Enabling any other theme, will download external sources. // WARNING: Only lumen is loaded locally. Enabling any other theme, will download external sources.
$DEFAULT_THEME = 'lumen'; $DEFAULT_THEME = 'lumen';
// Password complexity as regular expression
// Min. 6 characters
$PASSWD_REGEP = '.{6,}';
// Min. 6 characters, which must include at least one uppercase letter, one lowercase letter and one number
// $PASSWD_REGEP = '^(?=.*[A-Z])(?=.*[0-9])(?=.*[a-z]).{6,}$';
// Min. 6 characters, which must include at least one letter and one number
// $PASSWD_REGEP = '^(?=.*[0-9])(?=.*[A-Za-z]).{6,}$';
// Show DKIM private keys - false by default // Show DKIM private keys - false by default
$SHOW_DKIM_PRIV_KEYS = false; $SHOW_DKIM_PRIV_KEYS = false;

View File

@ -95,7 +95,14 @@ $(document).ready(function() {
// selectpicker // selectpicker
$('select').selectpicker(); $('select').selectpicker();
// haveibeenpwned? // haveibeenpwned and passwd policy
$.ajax({
url: '/api/v1/get/passwordpolicy/html',
type: 'GET',
success: function(res) {
$(".hibp-out").after(res);
}
});
$('[data-hibp]').after('<p class="small haveibeenpwned">↪ Check against haveibeenpwned.com</p><span class="hibp-out"></span>'); $('[data-hibp]').after('<p class="small haveibeenpwned">↪ Check against haveibeenpwned.com</p><span class="hibp-out"></span>');
$('[data-hibp]').on('input', function() { $('[data-hibp]').on('input', function() {
out_field = $(this).next('.haveibeenpwned').next('.hibp-out').text('').attr('class', 'hibp-out'); out_field = $(this).next('.haveibeenpwned').next('.hibp-out').text('').attr('class', 'hibp-out');

File diff suppressed because it is too large Load Diff

View File

@ -446,6 +446,20 @@ if (isset($_GET['query'])) {
} }
break; break;
case "passwordpolicy":
switch ($object) {
case "html":
$password_complexity_rules = password_complexity('html');
if ($password_complexity_rules !== false) {
process_get_return($password_complexity_rules);
}
else {
echo '{}';
}
break;
}
break;
case "app-passwd": case "app-passwd":
switch ($object) { switch ($object) {
case "all": case "all":
@ -1561,6 +1575,9 @@ if (isset($_GET['query'])) {
case "app_links": case "app_links":
process_edit_return(customize('edit', 'app_links', $attr)); process_edit_return(customize('edit', 'app_links', $attr));
break; break;
case "passwordpolicy":
process_edit_return(password_complexity('edit', $attr));
break;
case "relayhost": case "relayhost":
process_edit_return(relayhost('edit', array_merge(array('id' => $items), $attr))); process_edit_return(relayhost('edit', array_merge(array('id' => $items), $attr)));
break; break;

View File

@ -234,6 +234,13 @@
"oauth2_revoke_tokens": "Alle Client Tokens entfernen", "oauth2_revoke_tokens": "Alle Client Tokens entfernen",
"optional": "Optional", "optional": "Optional",
"password": "Passwort", "password": "Passwort",
"password_length": "Passwortlänge",
"password_policy": "Passwortrichtlinie",
"password_policy_chars": "Muss ein alphabetisches Zeichen enthalten",
"password_policy_length": "Mindestlänge des Passwortes ist %d Zeichen",
"password_policy_numbers": "Muss eine Ziffer enthalten",
"password_policy_lowerupper": "Muss Großbuchstaben und Kleinbuchstaben enthalten",
"password_policy_special_chars": "Muss Sonderzeichen enthalten",
"password_repeat": "Passwort wiederholen", "password_repeat": "Passwort wiederholen",
"priority": "Gewichtung", "priority": "Gewichtung",
"private_key": "Private Key", "private_key": "Private Key",
@ -907,6 +914,7 @@
"object_modified": "Änderungen an Objekt %s wurden gespeichert", "object_modified": "Änderungen an Objekt %s wurden gespeichert",
"pushover_settings_edited": "Pushover Konfiguration gespeichert, bitte den Zugang im Anschluss verifizieren.", "pushover_settings_edited": "Pushover Konfiguration gespeichert, bitte den Zugang im Anschluss verifizieren.",
"qlearn_spam": "Nachricht ID %s wurde als Spam gelernt und gelöscht", "qlearn_spam": "Nachricht ID %s wurde als Spam gelernt und gelöscht",
"password_policy_saved": "Passwortrichtlinie wurde erfolgreich gespeichert",
"queue_command_success": "Queue-Aufgabe erfolgreich ausgeführt", "queue_command_success": "Queue-Aufgabe erfolgreich ausgeführt",
"recipient_map_entry_deleted": "Empfängerumschreibung mit der ID %s wurde gelöscht", "recipient_map_entry_deleted": "Empfängerumschreibung mit der ID %s wurde gelöscht",
"recipient_map_entry_saved": "Empfängerumschreibung für Objekt \"%s\" wurde gespeichert", "recipient_map_entry_saved": "Empfängerumschreibung für Objekt \"%s\" wurde gespeichert",
@ -1027,7 +1035,6 @@
"messages": "Nachrichten", "messages": "Nachrichten",
"never": "Niemals", "never": "Niemals",
"new_password": "Neues Passwort", "new_password": "Neues Passwort",
"new_password_description": "Mindestanforderung: 6 Zeichen lang, Buchstaben und Zahlen.",
"new_password_repeat": "Neues Passwort (Wiederholung)", "new_password_repeat": "Neues Passwort (Wiederholung)",
"no_active_filter": "Kein aktiver Filter vorhanden", "no_active_filter": "Kein aktiver Filter vorhanden",
"no_last_login": "Keine letzte UI-Anmeldung gespeichert", "no_last_login": "Keine letzte UI-Anmeldung gespeichert",

View File

@ -232,6 +232,13 @@
"oauth2_revoke_tokens": "Revoke all client tokens", "oauth2_revoke_tokens": "Revoke all client tokens",
"optional": "optional", "optional": "optional",
"password": "Password", "password": "Password",
"password_length": "Password length",
"password_policy": "Password policy",
"password_policy_chars": "Must contain at least one alphabetic character",
"password_policy_length": "Minimum password length is %d",
"password_policy_numbers": "Must contain at least one number",
"password_policy_lowerupper": "Must contain lowercase and uppercase characters",
"password_policy_special_chars": "Must contain special chars",
"password_repeat": "Confirmation password (repeat)", "password_repeat": "Confirmation password (repeat)",
"priority": "Priority", "priority": "Priority",
"private_key": "Private key", "private_key": "Private key",
@ -906,6 +913,7 @@
"pushover_settings_edited": "Pushover settings successfully set, please verify credentials.", "pushover_settings_edited": "Pushover settings successfully set, please verify credentials.",
"qlearn_spam": "Message ID %s was learned as spam and deleted", "qlearn_spam": "Message ID %s was learned as spam and deleted",
"queue_command_success": "Queue command completed successfully", "queue_command_success": "Queue command completed successfully",
"password_policy_saved": "Password policy was saved successfully",
"recipient_map_entry_deleted": "Recipient map ID %s has been deleted", "recipient_map_entry_deleted": "Recipient map ID %s has been deleted",
"recipient_map_entry_saved": "Recipient map entry \"%s\" has been saved", "recipient_map_entry_saved": "Recipient map entry \"%s\" has been saved",
"relayhost_added": "Map entry %s has been added", "relayhost_added": "Map entry %s has been added",
@ -1025,7 +1033,6 @@
"messages": "messages", "messages": "messages",
"never": "Never", "never": "Never",
"new_password": "New password", "new_password": "New password",
"new_password_description": "Requirement: 6 characters long, letters and numbers.",
"new_password_repeat": "Confirmation password (repeat)", "new_password_repeat": "Confirmation password (repeat)",
"no_active_filter": "No active filter available", "no_active_filter": "No active filter available",
"no_last_login": "No last UI login information", "no_last_login": "No last UI login information",