[Web] Feature: Add password policy
parent
616226be8a
commit
5ea649b292
|
@ -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-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-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>
|
||||
</ul>
|
||||
</li>
|
||||
|
@ -77,7 +78,7 @@ if (!isset($_SESSION['gal']) && $license_cache = $redis->Get('LICENSE_STATUS_CAC
|
|||
foreach ($tfa_data['additional'] as $key_info) {
|
||||
?>
|
||||
<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'; ?>">
|
||||
<?=$key_info['key_id'];?>
|
||||
<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 style="min-width:240px;text-align: right">
|
||||
<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">
|
||||
<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>
|
||||
|
@ -381,7 +382,7 @@ if (!isset($_SESSION['gal']) && $license_cache = $redis->Get('LICENSE_STATUS_CAC
|
|||
</form>
|
||||
</div>
|
||||
<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>
|
||||
|
@ -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.');
|
||||
?>
|
||||
<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">
|
||||
<p><?=$lang['admin']['domain'];?>: <strong><?=htmlspecialchars($domain);?></strong>
|
||||
<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 {
|
||||
?>
|
||||
<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">
|
||||
<p><?=$lang['admin']['domain'];?>: <strong><?=htmlspecialchars($domain);?></strong><br><span class="label label-danger"><?=$lang['admin']['dkim_key_missing'];?></span></p>
|
||||
</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.');
|
||||
?>
|
||||
<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">
|
||||
<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>
|
||||
|
@ -560,7 +561,7 @@ if (!isset($_SESSION['gal']) && $license_cache = $redis->Get('LICENSE_STATUS_CAC
|
|||
else {
|
||||
?>
|
||||
<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">
|
||||
<p><small>↳ Alias-Domain: <strong><?=htmlspecialchars($alias_domain);?></strong><br></small><span class="label label-danger"><?=$lang['admin']['dkim_key_missing'];?></span></p>
|
||||
</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.');
|
||||
?>
|
||||
<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">
|
||||
<p><?=$lang['admin']['domain'];?>: <strong><?=htmlspecialchars($blind);?></strong>
|
||||
<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 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 class="panel panel-default">
|
||||
<div class="panel-heading"><?=$lang['admin']['sys_mails'];?></div>
|
||||
|
|
|
@ -48,24 +48,16 @@ function admin($_action, $_data = null) {
|
|||
return false;
|
||||
}
|
||||
}
|
||||
if (!empty($password) && !empty($password2)) {
|
||||
if (!preg_match('/' . $GLOBALS['PASSWD_REGEP'] . '/', $password)) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_data_log),
|
||||
'msg' => 'password_complexity'
|
||||
);
|
||||
if (password_check($password_new, $password_new2) !== true) {
|
||||
return false;
|
||||
}
|
||||
if ($password != $password2) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_data_log),
|
||||
'msg' => 'password_mismatch'
|
||||
);
|
||||
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);
|
||||
}
|
||||
$password_hashed = hash_password($password);
|
||||
$stmt = $pdo->prepare("INSERT INTO `admin` (`username`, `password`, `superadmin`, `active`)
|
||||
VALUES (:username, :password_hashed, '1', :active)");
|
||||
$stmt->execute(array(
|
||||
|
@ -73,15 +65,6 @@ function admin($_action, $_data = null) {
|
|||
':password_hashed' => $password_hashed,
|
||||
':active' => $active
|
||||
));
|
||||
}
|
||||
else {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_data_log),
|
||||
'msg' => 'password_empty'
|
||||
);
|
||||
return false;
|
||||
}
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'success',
|
||||
'log' => array(__FUNCTION__, $_action, $_data_log),
|
||||
|
@ -145,23 +128,16 @@ function admin($_action, $_data = null) {
|
|||
}
|
||||
}
|
||||
if (!empty($password) && !empty($password2)) {
|
||||
if (!preg_match('/' . $GLOBALS['PASSWD_REGEP'] . '/', $password)) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_data_log),
|
||||
'msg' => 'password_complexity'
|
||||
);
|
||||
continue;
|
||||
if (password_check($password, $password2) !== true) {
|
||||
return false;
|
||||
}
|
||||
if ($password != $password2) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_data_log),
|
||||
'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 `admin` SET `username` = :username_new, `active` = :active, `password` = :password_hashed WHERE `username` = :username");
|
||||
$stmt->execute(array(
|
||||
':password_hashed' => $password_hashed,
|
||||
|
|
|
@ -21,7 +21,7 @@ function dkim($_action, $_data = null, $privkey = false) {
|
|||
if (!is_valid_domain_name($domain) || !is_numeric($key_length)) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_data, $privkey),
|
||||
'log' => array(__FUNCTION__, $_action, $_data),
|
||||
'msg' => array('dkim_domain_or_sel_invalid', $domain)
|
||||
);
|
||||
continue;
|
||||
|
@ -29,7 +29,7 @@ function dkim($_action, $_data = null, $privkey = false) {
|
|||
if ($redis->hGet('DKIM_PUB_KEYS', $domain)) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_data, $privkey),
|
||||
'log' => array(__FUNCTION__, $_action, $_data),
|
||||
'msg' => array('dkim_domain_or_sel_invalid', $domain)
|
||||
);
|
||||
continue;
|
||||
|
@ -37,7 +37,7 @@ function dkim($_action, $_data = null, $privkey = false) {
|
|||
if (!ctype_alnum(str_replace(['-', '_'], '', $dkim_selector))) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_data, $privkey),
|
||||
'log' => array(__FUNCTION__, $_action, $_data),
|
||||
'msg' => array('dkim_domain_or_sel_invalid', $domain)
|
||||
);
|
||||
continue;
|
||||
|
@ -62,7 +62,7 @@ function dkim($_action, $_data = null, $privkey = false) {
|
|||
catch (RedisException $e) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_data, $privkey),
|
||||
'log' => array(__FUNCTION__, $_action, $_data),
|
||||
'msg' => array('redis_error', $e)
|
||||
);
|
||||
continue;
|
||||
|
@ -76,7 +76,7 @@ function dkim($_action, $_data = null, $privkey = false) {
|
|||
catch (RedisException $e) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_data, $privkey),
|
||||
'log' => array(__FUNCTION__, $_action, $_data),
|
||||
'msg' => array('redis_error', $e)
|
||||
);
|
||||
continue;
|
||||
|
@ -84,14 +84,14 @@ function dkim($_action, $_data = null, $privkey = false) {
|
|||
}
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'success',
|
||||
'log' => array(__FUNCTION__, $_action, $_data, $privkey),
|
||||
'log' => array(__FUNCTION__, $_action, $_data),
|
||||
'msg' => array('dkim_added', $domain)
|
||||
);
|
||||
}
|
||||
else {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_data, $privkey),
|
||||
'log' => array(__FUNCTION__, $_action, $_data),
|
||||
'msg' => array('dkim_domain_or_sel_invalid', $domain)
|
||||
);
|
||||
continue;
|
||||
|
@ -102,7 +102,7 @@ function dkim($_action, $_data = null, $privkey = false) {
|
|||
if ($_SESSION['mailcow_cc_role'] != "admin") {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_data, $privkey),
|
||||
'log' => array(__FUNCTION__, $_action, $_data),
|
||||
'msg' => 'access_denied'
|
||||
);
|
||||
return false;
|
||||
|
@ -112,7 +112,7 @@ function dkim($_action, $_data = null, $privkey = false) {
|
|||
if (empty($from_domain_dkim)) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_data, $privkey),
|
||||
'log' => array(__FUNCTION__, $_action, $_data),
|
||||
'msg' => array('dkim_domain_or_sel_invalid', $from_domain)
|
||||
);
|
||||
continue;
|
||||
|
@ -128,14 +128,14 @@ function dkim($_action, $_data = null, $privkey = false) {
|
|||
catch (RedisException $e) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_data, $privkey),
|
||||
'log' => array(__FUNCTION__, $_action, $_data),
|
||||
'msg' => array('redis_error', $e)
|
||||
);
|
||||
continue;
|
||||
}
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'success',
|
||||
'log' => array(__FUNCTION__, $_action, $_data, $privkey),
|
||||
'log' => array(__FUNCTION__, $_action, $_data),
|
||||
'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") {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_data, $privkey),
|
||||
'log' => array(__FUNCTION__, $_action, $_data),
|
||||
'msg' => 'access_denied'
|
||||
);
|
||||
return false;
|
||||
|
@ -156,7 +156,7 @@ function dkim($_action, $_data = null, $privkey = false) {
|
|||
if ($ssl_error = openssl_error_string()) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_data, $privkey),
|
||||
'log' => array(__FUNCTION__, $_action, $_data),
|
||||
'msg' => array('private_key_error', $ssl_error)
|
||||
);
|
||||
return false;
|
||||
|
@ -173,7 +173,7 @@ function dkim($_action, $_data = null, $privkey = false) {
|
|||
if (!is_valid_domain_name($domain)) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_data, $privkey),
|
||||
'log' => array(__FUNCTION__, $_action, $_data),
|
||||
'msg' => array('dkim_domain_or_sel_invalid', $domain)
|
||||
);
|
||||
return false;
|
||||
|
@ -182,7 +182,7 @@ function dkim($_action, $_data = null, $privkey = false) {
|
|||
if ($overwrite_existing == 0) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_data, $privkey),
|
||||
'log' => array(__FUNCTION__, $_action, $_data),
|
||||
'msg' => array('dkim_domain_or_sel_exists', $domain)
|
||||
);
|
||||
return false;
|
||||
|
@ -191,7 +191,7 @@ function dkim($_action, $_data = null, $privkey = false) {
|
|||
if (!ctype_alnum($dkim_selector)) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_data, $privkey),
|
||||
'log' => array(__FUNCTION__, $_action, $_data),
|
||||
'msg' => array('dkim_domain_or_sel_invalid', $domain)
|
||||
);
|
||||
return false;
|
||||
|
@ -205,7 +205,7 @@ function dkim($_action, $_data = null, $privkey = false) {
|
|||
catch (RedisException $e) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_data, $privkey),
|
||||
'log' => array(__FUNCTION__, $_action, $_data),
|
||||
'msg' => array('redis_error', $e)
|
||||
);
|
||||
return false;
|
||||
|
@ -218,14 +218,14 @@ function dkim($_action, $_data = null, $privkey = false) {
|
|||
catch (RedisException $e) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_data, $privkey),
|
||||
'log' => array(__FUNCTION__, $_action, $_data),
|
||||
'msg' => array('redis_error', $e)
|
||||
);
|
||||
return false;
|
||||
}
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'success',
|
||||
'log' => array(__FUNCTION__, $_action, $_data, $privkey),
|
||||
'log' => array(__FUNCTION__, $_action, $_data),
|
||||
'msg' => array('dkim_added', $domain)
|
||||
);
|
||||
return true;
|
||||
|
@ -270,7 +270,7 @@ function dkim($_action, $_data = null, $privkey = false) {
|
|||
if ($_SESSION['mailcow_cc_role'] != "admin") {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_data, $privkey),
|
||||
'log' => array(__FUNCTION__, $_action, $_data),
|
||||
'msg' => 'access_denied'
|
||||
);
|
||||
return false;
|
||||
|
@ -286,7 +286,7 @@ function dkim($_action, $_data = null, $privkey = false) {
|
|||
if ($_SESSION['mailcow_cc_role'] != "admin") {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_data, $privkey),
|
||||
'log' => array(__FUNCTION__, $_action, $_data),
|
||||
'msg' => 'access_denied'
|
||||
);
|
||||
return false;
|
||||
|
@ -295,7 +295,7 @@ function dkim($_action, $_data = null, $privkey = false) {
|
|||
if (!is_valid_domain_name($domain)) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_data, $privkey),
|
||||
'log' => array(__FUNCTION__, $_action, $_data),
|
||||
'msg' => array('dkim_domain_or_sel_invalid', $domain)
|
||||
);
|
||||
continue;
|
||||
|
@ -309,14 +309,14 @@ function dkim($_action, $_data = null, $privkey = false) {
|
|||
catch (RedisException $e) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_data, $privkey),
|
||||
'log' => array(__FUNCTION__, $_action, $_data),
|
||||
'msg' => array('redis_error', $e)
|
||||
);
|
||||
continue;
|
||||
}
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'success',
|
||||
'log' => array(__FUNCTION__, $_action, $_data, $privkey),
|
||||
'log' => array(__FUNCTION__, $_action, $_data),
|
||||
'msg' => array('dkim_removed', htmlspecialchars($domain))
|
||||
);
|
||||
}
|
||||
|
|
|
@ -65,24 +65,16 @@ function domain_admin($_action, $_data = null) {
|
|||
return false;
|
||||
}
|
||||
}
|
||||
if (!empty($password) && !empty($password2)) {
|
||||
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_check($password, $password2) !== true) {
|
||||
continue;
|
||||
}
|
||||
if ($password != $password2) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_data_log),
|
||||
'msg' => 'password_mismatch'
|
||||
);
|
||||
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;
|
||||
}
|
||||
else {
|
||||
$password_hashed = hash_password($password);
|
||||
}
|
||||
$valid_domains = 0;
|
||||
foreach ($domains as $domain) {
|
||||
if (!is_valid_domain_name($domain) || mailbox('get', 'domain_details', $domain) === false) {
|
||||
|
@ -112,15 +104,6 @@ function domain_admin($_action, $_data = null) {
|
|||
':active' => $active
|
||||
));
|
||||
}
|
||||
}
|
||||
else {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_data_log),
|
||||
'msg' => 'password_empty'
|
||||
);
|
||||
return false;
|
||||
}
|
||||
$stmt = $pdo->prepare("INSERT INTO `da_acl` (`username`) VALUES (:username)");
|
||||
$stmt->execute(array(
|
||||
':username' => $username
|
||||
|
@ -219,23 +202,16 @@ function domain_admin($_action, $_data = null) {
|
|||
}
|
||||
}
|
||||
if (!empty($password) && !empty($password2)) {
|
||||
if (!preg_match('/' . $GLOBALS['PASSWD_REGEP'] . '/', $password)) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_data_log),
|
||||
'msg' => 'password_complexity'
|
||||
);
|
||||
continue;
|
||||
if (password_check($password, $password2) !== true) {
|
||||
return false;
|
||||
}
|
||||
if ($password != $password2) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_data_log),
|
||||
'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 `admin` SET `username` = :username_new, `active` = :active, `password` = :password_hashed WHERE `username` = :username");
|
||||
$stmt->execute(array(
|
||||
':password_hashed' => $password_hashed,
|
||||
|
@ -296,21 +272,7 @@ function domain_admin($_action, $_data = null) {
|
|||
);
|
||||
return false;
|
||||
}
|
||||
if (!empty($password_new2) && !empty($password_new)) {
|
||||
if ($password_new2 != $password_new) {
|
||||
$_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'
|
||||
);
|
||||
if (password_check($password_new, $password_new2) !== true) {
|
||||
return false;
|
||||
}
|
||||
$password_hashed = hash_password($password_new);
|
||||
|
@ -319,7 +281,6 @@ function domain_admin($_action, $_data = null) {
|
|||
':password_hashed' => $password_hashed,
|
||||
':username' => $username
|
||||
));
|
||||
}
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'success',
|
||||
'log' => array(__FUNCTION__, $_action, $_data_log),
|
||||
|
|
|
@ -114,6 +114,132 @@ function hash_password($password) {
|
|||
}
|
||||
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) {
|
||||
global $pdo;
|
||||
$stmt = $pdo->prepare('SELECT `remote`, `time` FROM `logs`
|
||||
|
|
|
@ -1045,32 +1045,15 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
|||
);
|
||||
return false;
|
||||
}
|
||||
if (!empty($password) && !empty($password2)) {
|
||||
if (!preg_match('/' . $GLOBALS['PASSWD_REGEP'] . '/', $password)) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
|
||||
'msg' => 'password_complexity'
|
||||
);
|
||||
if (password_check($password, $password2) !== true) {
|
||||
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);
|
||||
// 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 {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
|
||||
'msg' => 'password_empty'
|
||||
);
|
||||
return false;
|
||||
$password_hashed = hash_password($password);
|
||||
}
|
||||
if ($MailboxData['count'] >= $DomainData['mailboxes']) {
|
||||
$_SESSION['return'][] = array(
|
||||
|
@ -2599,21 +2582,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
|||
));
|
||||
}
|
||||
}
|
||||
if (!empty($password) && !empty($password2)) {
|
||||
if (!preg_match('/' . $GLOBALS['PASSWD_REGEP'] . '/', $password)) {
|
||||
$_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'
|
||||
);
|
||||
if (password_check($password, $password2) !== true) {
|
||||
continue;
|
||||
}
|
||||
// support pre hashed passwords
|
||||
|
@ -2630,7 +2599,6 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
|||
':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)
|
||||
$stmt = $pdo->prepare("UPDATE `alias` SET
|
||||
`active` = :active
|
||||
|
|
|
@ -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.
|
||||
$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_PRIV_KEYS = false;
|
||||
|
||||
|
|
|
@ -95,7 +95,14 @@ $(document).ready(function() {
|
|||
// 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]').on('input', function() {
|
||||
out_field = $(this).next('.haveibeenpwned').next('.hibp-out').text('').attr('class', 'hibp-out');
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -446,6 +446,20 @@ if (isset($_GET['query'])) {
|
|||
}
|
||||
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":
|
||||
switch ($object) {
|
||||
case "all":
|
||||
|
@ -1561,6 +1575,9 @@ if (isset($_GET['query'])) {
|
|||
case "app_links":
|
||||
process_edit_return(customize('edit', 'app_links', $attr));
|
||||
break;
|
||||
case "passwordpolicy":
|
||||
process_edit_return(password_complexity('edit', $attr));
|
||||
break;
|
||||
case "relayhost":
|
||||
process_edit_return(relayhost('edit', array_merge(array('id' => $items), $attr)));
|
||||
break;
|
||||
|
|
|
@ -234,6 +234,13 @@
|
|||
"oauth2_revoke_tokens": "Alle Client Tokens entfernen",
|
||||
"optional": "Optional",
|
||||
"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",
|
||||
"priority": "Gewichtung",
|
||||
"private_key": "Private Key",
|
||||
|
@ -907,6 +914,7 @@
|
|||
"object_modified": "Änderungen an Objekt %s wurden gespeichert",
|
||||
"pushover_settings_edited": "Pushover Konfiguration gespeichert, bitte den Zugang im Anschluss verifizieren.",
|
||||
"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",
|
||||
"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",
|
||||
|
@ -1027,7 +1035,6 @@
|
|||
"messages": "Nachrichten",
|
||||
"never": "Niemals",
|
||||
"new_password": "Neues Passwort",
|
||||
"new_password_description": "Mindestanforderung: 6 Zeichen lang, Buchstaben und Zahlen.",
|
||||
"new_password_repeat": "Neues Passwort (Wiederholung)",
|
||||
"no_active_filter": "Kein aktiver Filter vorhanden",
|
||||
"no_last_login": "Keine letzte UI-Anmeldung gespeichert",
|
||||
|
|
|
@ -232,6 +232,13 @@
|
|||
"oauth2_revoke_tokens": "Revoke all client tokens",
|
||||
"optional": "optional",
|
||||
"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)",
|
||||
"priority": "Priority",
|
||||
"private_key": "Private key",
|
||||
|
@ -906,6 +913,7 @@
|
|||
"pushover_settings_edited": "Pushover settings successfully set, please verify credentials.",
|
||||
"qlearn_spam": "Message ID %s was learned as spam and deleted",
|
||||
"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_saved": "Recipient map entry \"%s\" has been saved",
|
||||
"relayhost_added": "Map entry %s has been added",
|
||||
|
@ -1025,7 +1033,6 @@
|
|||
"messages": "messages",
|
||||
"never": "Never",
|
||||
"new_password": "New password",
|
||||
"new_password_description": "Requirement: 6 characters long, letters and numbers.",
|
||||
"new_password_repeat": "Confirmation password (repeat)",
|
||||
"no_active_filter": "No active filter available",
|
||||
"no_last_login": "No last UI login information",
|
||||
|
|
Loading…
Reference in New Issue