From d64a89473ea53fb49ad9dfd13af8ffd89cc417f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9?= Date: Tue, 21 Aug 2018 17:41:04 +0200 Subject: [PATCH] [Web] Add multiple DKIM at once (+ button to auto-fill missing keys) [Web] Duplicate DKIM keys from a single domain to a single or multiple domains [Web] WIP: Started Ratelimit rework [Web] Show RL in overview of mailbox and domains [Web] Move RL function out of mailbox functions file [Web] Some language fixes/changes --- data/web/admin.php | 80 ++++++++-- data/web/css/admin.css | 3 + data/web/css/mailcow.css | 1 + data/web/edit.php | 12 +- data/web/img/cow_mailcow.svg | 31 ++-- data/web/inc/functions.dkim.inc.php | 170 ++++++++++++-------- data/web/inc/functions.mailbox.inc.php | 131 +--------------- data/web/inc/functions.ratelimit.inc.php | 188 +++++++++++++++++++++++ data/web/inc/prerequisites.inc.php | 1 + data/web/js/admin.js | 12 ++ data/web/js/debug.js | 2 +- data/web/js/mailbox.js | 16 ++ data/web/json_api.php | 72 ++++++++- data/web/lang/lang.de.php | 9 ++ data/web/lang/lang.en.php | 11 +- 15 files changed, 508 insertions(+), 231 deletions(-) create mode 100644 data/web/inc/functions.ratelimit.inc.php diff --git a/data/web/admin.php b/data/web/admin.php index d97bc141..d2734431 100644 --- a/data/web/admin.php +++ b/data/web/admin.php @@ -208,6 +208,8 @@ $tfa_data = get_tfa();
@@ -229,7 +231,7 @@ $tfa_data = get_tfa(); else { ?>
-
+

Domain:

@@ -240,6 +242,8 @@ $tfa_data = get_tfa(); } foreach(mailbox('get', 'alias_domains', $domain) as $alias_domain) { if (!empty($dkim = dkim('details', $alias_domain))) { + $dkim_domains[] = $alias_domain; + ($GLOBALS['SHOW_DKIM_PRIV_KEYS'] === true) ?: $dkim['privkey'] = base64_encode('Please set $SHOW_DKIM_PRIV_KEYS to true to show DKIM private keys.'); ?>
@@ -261,7 +265,7 @@ $tfa_data = get_tfa(); else { ?>
-
+

↳ Alias-Domain:

@@ -274,6 +278,8 @@ $tfa_data = get_tfa(); } foreach(dkim('blind') as $blind) { if (!empty($dkim = dkim('details', $blind))) { + $dkim_domains[] = $blind; + ($GLOBALS['SHOW_DKIM_PRIV_KEYS'] === true) ?: $dkim['privkey'] = base64_encode('Please set $SHOW_DKIM_PRIV_KEYS to true to show DKIM private keys.'); ?>
@@ -298,40 +304,90 @@ $tfa_data = get_tfa();
- - + + +
- +
-
- +
- + + +
- +
- +
- +
- +
+ + + + +
+
+
+ +
+ +
+
+
+ +
+ +
+
+ +
+
+
diff --git a/data/web/css/admin.css b/data/web/css/admin.css index bd6ce1a5..a53d721c 100644 --- a/data/web/css/admin.css +++ b/data/web/css/admin.css @@ -71,3 +71,6 @@ body.modal-open { .table-condensed .input-sm { width: 100%!important; } +.full-width-select { + width: 100%!important; +} diff --git a/data/web/css/mailcow.css b/data/web/css/mailcow.css index 56d129dd..374688a1 100644 --- a/data/web/css/mailcow.css +++ b/data/web/css/mailcow.css @@ -101,6 +101,7 @@ legend { -ms-user-select: none -o-user-select: none; user-select: none; + font-size: 12pt; } .navbar .navbar-brand { padding-top: 5px; diff --git a/data/web/edit.php b/data/web/edit.php index 1efbbed3..c4903c28 100644 --- a/data/web/edit.php +++ b/data/web/edit.php @@ -153,7 +153,7 @@ if (isset($_SESSION['mailcow_cc_role'])) { !empty($_GET["domain"])) { $domain = $_GET["domain"]; $result = mailbox('get', 'domain_details', $domain); - $rl = mailbox('get', 'ratelimit', $domain); + $rl = ratelimit('get', 'domain', $domain); $rlyhosts = relayhost('get'); if (!empty($result)) { ?> @@ -266,7 +266,7 @@ if (isset($_SESSION['mailcow_cc_role'])) {
- +

@@ -329,7 +329,7 @@ if (isset($_SESSION['mailcow_cc_role'])) { !empty($_GET["aliasdomain"])) { $alias_domain = html_entity_decode(rawurldecode($_GET["aliasdomain"])); $result = mailbox('get', 'alias_domain_details', $alias_domain); - $rl = mailbox('get', 'ratelimit', $alias_domain); + $rl = ratelimit('get', 'domain', $alias_domain); if (!empty($result)) { ?>

@@ -368,7 +368,7 @@ if (isset($_SESSION['mailcow_cc_role'])) {
- +

@@ -522,7 +522,7 @@ if (isset($_SESSION['mailcow_cc_role'])) {
- +
diff --git a/data/web/img/cow_mailcow.svg b/data/web/img/cow_mailcow.svg index bea3f826..d4577821 100644 --- a/data/web/img/cow_mailcow.svg +++ b/data/web/img/cow_mailcow.svg @@ -13,9 +13,9 @@ id="Layer_1" x="0px" y="0px" - width="446.22601" - height="396.02499" - viewBox="0 0 446.226 396.02499" + width="374.82101" + height="356.871" + viewBox="0 0 374.821 356.871" enable-background="new 0 0 1600 1200" xml:space="preserve" inkscape:version="0.91 r13725" @@ -36,19 +36,19 @@ inkscape:window-height="1138" id="namedview140" showgrid="false" - inkscape:zoom="1.1125147" - inkscape:cx="261.00704" - inkscape:cy="233.97883" + inkscape:zoom="1.5733334" + inkscape:cx="264.82839" + inkscape:cy="340.43592" inkscape:window-x="814" inkscape:window-y="0" inkscape:window-maximized="0" - inkscape:current-layer="Layer_1" + inkscape:current-layer="g3" fit-margin-top="0" fit-margin-left="0" fit-margin-right="0" fit-margin-bottom="0" /> 'danger', - 'log' => array(__FUNCTION__, $_action, $_data), - 'msg' => 'dkim_domain_or_sel_invalid' - ); - return false; - } - if ($redis->hGet('DKIM_PUB_KEYS', $domain)) { + $domains = array_map('trim', preg_split( "/( |,|;|\n)/", $_data['domains'])); + $domains = array_filter($domains); + foreach ($domains as $domain) { + if (!is_valid_domain_name($domain) || !is_numeric($key_length)) { $_SESSION['return'][] = array( 'type' => 'danger', 'log' => array(__FUNCTION__, $_action, $_data), - 'msg' => 'dkim_domain_or_sel_invalid' + 'msg' => array('dkim_domain_or_sel_invalid', $domain) ); - return false; - } - if (!ctype_alnum($dkim_selector)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data), - 'msg' => 'dkim_domain_or_sel_invalid' - ); - return false; - } - $config = array( - "digest_alg" => "sha256", - "private_key_bits" => $key_length, - "private_key_type" => OPENSSL_KEYTYPE_RSA, - ); - if ($keypair_ressource = openssl_pkey_new($config)) { - $key_details = openssl_pkey_get_details($keypair_ressource); - $pubKey = implode(array_slice( - array_filter( - explode(PHP_EOL, $key_details['key']) - ), 1, -1) - ); - // Save public key and selector to redis - try { - $redis->hSet('DKIM_PUB_KEYS', $domain, $pubKey); - $redis->hSet('DKIM_SELECTORS', $domain, $dkim_selector); + continue; } - catch (RedisException $e) { + if ($redis->hGet('DKIM_PUB_KEYS', $domain)) { $_SESSION['return'][] = array( 'type' => 'danger', 'log' => array(__FUNCTION__, $_action, $_data), - 'msg' => array('redis_error', $e) + 'msg' => array('dkim_domain_or_sel_invalid', $domain) ); - return false; + continue; } - // Export private key and save private key to redis - openssl_pkey_export($keypair_ressource, $privKey); - if (isset($privKey) && !empty($privKey)) { + if (!ctype_alnum($dkim_selector)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data), + 'msg' => array('dkim_domain_or_sel_invalid', $domain) + ); + continue; + } + $config = array( + "digest_alg" => "sha256", + "private_key_bits" => $key_length, + "private_key_type" => OPENSSL_KEYTYPE_RSA, + ); + if ($keypair_ressource = openssl_pkey_new($config)) { + $key_details = openssl_pkey_get_details($keypair_ressource); + $pubKey = implode(array_slice( + array_filter( + explode(PHP_EOL, $key_details['key']) + ), 1, -1) + ); + // Save public key and selector to redis try { - $redis->hSet('DKIM_PRIV_KEYS', $dkim_selector . '.' . $domain, trim($privKey)); + $redis->hSet('DKIM_PUB_KEYS', $domain, $pubKey); + $redis->hSet('DKIM_SELECTORS', $domain, $dkim_selector); } catch (RedisException $e) { $_SESSION['return'][] = array( @@ -77,23 +65,79 @@ function dkim($_action, $_data = null) { 'log' => array(__FUNCTION__, $_action, $_data), 'msg' => array('redis_error', $e) ); - return false; + continue; } + // Export private key and save private key to redis + openssl_pkey_export($keypair_ressource, $privKey); + if (isset($privKey) && !empty($privKey)) { + try { + $redis->hSet('DKIM_PRIV_KEYS', $dkim_selector . '.' . $domain, trim($privKey)); + } + catch (RedisException $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data), + 'msg' => array('redis_error', $e) + ); + continue; + } + } + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_data), + 'msg' => array('dkim_added', $domain) + ); + } + else { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data), + 'msg' => array('dkim_domain_or_sel_invalid', $domain) + ); + continue; + } + } + break; + case 'duplicate': + if ($_SESSION['mailcow_cc_role'] != "admin") { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data), + 'msg' => 'access_denied' + ); + return false; + } + $from_domain = $_data['from_domain']; + $from_domain_dkim = dkim('details', $from_domain); + if (empty($from_domain_dkim)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data), + 'msg' => array('dkim_domain_or_sel_invalid', $from_domain) + ); + continue; + } + $to_domains = (array)$_data['to_domain']; + $to_domains = array_filter($to_domains); + foreach ($to_domains as $to_domain) { + try { + $redis->hSet('DKIM_PUB_KEYS', $to_domain, $from_domain_dkim['pubkey']); + $redis->hSet('DKIM_SELECTORS', $to_domain, $from_domain_dkim['dkim_selector']); + $redis->hSet('DKIM_PRIV_KEYS', $from_domain_dkim['dkim_selector'] . '.' . $to_domain, trim($from_domain_dkim['privkey'])); + } + catch (RedisException $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data), + 'msg' => array('redis_error', $e) + ); + continue; } $_SESSION['return'][] = array( 'type' => 'success', 'log' => array(__FUNCTION__, $_action, $_data), - 'msg' => 'dkim_added' + 'msg' => array('dkim_duplicated', $from_domain, $to_domain) ); - return true; - } - else { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data), - 'msg' => 'dkim_domain_or_sel_invalid' - ); - return false; } break; case 'import': @@ -129,7 +173,7 @@ function dkim($_action, $_data = null) { $_SESSION['return'][] = array( 'type' => 'danger', 'log' => array(__FUNCTION__, $_action, $_data), - 'msg' => 'dkim_domain_or_sel_invalid' + 'msg' => array('dkim_domain_or_sel_invalid', $domain) ); return false; } @@ -137,7 +181,7 @@ function dkim($_action, $_data = null) { $_SESSION['return'][] = array( 'type' => 'danger', 'log' => array(__FUNCTION__, $_action, $_data), - 'msg' => 'dkim_domain_or_sel_invalid' + 'msg' => array('dkim_domain_or_sel_invalid', $domain) ); return false; } @@ -145,7 +189,7 @@ function dkim($_action, $_data = null) { $_SESSION['return'][] = array( 'type' => 'danger', 'log' => array(__FUNCTION__, $_action, $_data), - 'msg' => 'dkim_domain_or_sel_invalid' + 'msg' => array('dkim_domain_or_sel_invalid', $domain) ); return false; } @@ -178,7 +222,7 @@ function dkim($_action, $_data = null) { $_SESSION['return'][] = array( 'type' => 'success', 'log' => array(__FUNCTION__, $_action, $_data), - 'msg' => 'dkim_added' + 'msg' => array('dkim_added', $domain) ); return true; break; @@ -203,13 +247,7 @@ function dkim($_action, $_data = null) { } $dkimdata['dkim_txt'] = 'v=DKIM1;k=rsa;t=s;s=email;p=' . $redis_dkim_key_data; $dkimdata['dkim_selector'] = $redis->hGet('DKIM_SELECTORS', $_data); - $dkimdata['privkey'] = $redis->hGet('DKIM_PRIV_KEYS', $dkimdata['dkim_selector'] . $_data); - if ($GLOBALS['SHOW_DKIM_PRIV_KEYS'] === true) { - $dkimdata['privkey'] = base64_encode($redis->hGet('DKIM_PRIV_KEYS', $dkimdata['dkim_selector'] . '.' . $_data)); - } - else { - $dkimdata['privkey'] = base64_encode('Please set $SHOW_DKIM_PRIV_KEYS to true to show DKIM private keys.'); - } + $dkimdata['privkey'] = base64_encode($redis->hGet('DKIM_PRIV_KEYS', $dkimdata['dkim_selector'] . '.' . $_data)); } return $dkimdata; break; diff --git a/data/web/inc/functions.mailbox.inc.php b/data/web/inc/functions.mailbox.inc.php index 92d55e8b..0db72594 100644 --- a/data/web/inc/functions.mailbox.inc.php +++ b/data/web/inc/functions.mailbox.inc.php @@ -1291,86 +1291,6 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { ); } break; - case 'ratelimit': - if (!is_array($_data['object'])) { - $objects = array(); - $objects[] = $_data['object']; - } - else { - $objects = $_data['object']; - } - foreach ($objects as $object) { - $rl_value = intval($_data['rl_value']); - $rl_frame = $_data['rl_frame']; - if (!in_array($rl_frame, array('s', 'm', 'h'))) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'rl_timeframe' - ); - continue; - } - if (is_valid_domain_name($object)) { - if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $object)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'access_denied' - ); - continue; - } - } - elseif (filter_var($object, FILTER_VALIDATE_EMAIL)) { - if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $object)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'access_denied' - ); - continue; - } - } - else { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'access_denied' - ); - continue; - } - if (empty($rl_value)) { - try { - $redis->hDel('RL_VALUE', $object); - } - catch (RedisException $e) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => array('redis_error', $e) - ); - continue; - } - } - else { - try { - $redis->hSet('RL_VALUE', $object, $rl_value . ' / 1' . $rl_frame); - } - catch (RedisException $e) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => array('redis_error', $e) - ); - continue; - } - } - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => array('object_modified', $object) - ); - } - break; case 'syncjob': if (!is_array($_data['id'])) { $ids = array(); @@ -2670,51 +2590,6 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { } return $aliases; break; - case 'ratelimit': - if (is_valid_domain_name($_data)) { - if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'access_denied' - ); - return false; - } - } - elseif (filter_var($_data, FILTER_VALIDATE_EMAIL)) { - if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'access_denied' - ); - return false; - } - } - else { - return false; - } - try { - if ($rl_value = $redis->hGet('RL_VALUE', $_data)) { - $rl = explode(' / 1', $rl_value); - $data['value'] = $rl[0]; - $data['frame'] = $rl[1]; - return $data; - } - else { - return false; - } - } - catch (RedisException $e) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => array('redis_error', $e) - ); - return false; - } - return false; - break; case 'alias_details': $aliasdata = array(); $stmt = $pdo->prepare("SELECT @@ -2759,6 +2634,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { break; case 'alias_domain_details': $aliasdomaindata = array(); + $rl = ratelimit('get', 'domain', $_data); $stmt = $pdo->prepare("SELECT `alias_domain`, `target_domain`, @@ -2775,6 +2651,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { $aliasdomaindata['alias_domain'] = $row['alias_domain']; $aliasdomaindata['target_domain'] = $row['target_domain']; $aliasdomaindata['active'] = $row['active']; + $aliasdomaindata['rl'] = $rl; $aliasdomaindata['active_int'] = $row['active_int']; $aliasdomaindata['created'] = $row['created']; $aliasdomaindata['modified'] = $row['modified']; @@ -2848,6 +2725,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { AND `domain` = :domain"); $stmt->execute(array(':domain' => $row['domain'])); $MailboxDataDomain = $stmt->fetch(PDO::FETCH_ASSOC); + $rl = ratelimit('get', 'domain', $_data); $domaindata['max_new_mailbox_quota'] = ($row['quota'] * 1048576) - $MailboxDataDomain['in_use']; if ($domaindata['max_new_mailbox_quota'] > ($row['maxquota'] * 1048576)) { $domaindata['max_new_mailbox_quota'] = ($row['maxquota'] * 1048576); @@ -2864,6 +2742,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { $domaindata['relayhost'] = $row['relayhost']; $domaindata['backupmx'] = $row['backupmx']; $domaindata['backupmx_int'] = $row['backupmx_int']; + $domaindata['rl'] = $rl; $domaindata['active'] = $row['active']; $domaindata['active_int'] = $row['active_int']; $domaindata['relay_all_recipients'] = $row['relay_all_recipients']; @@ -2887,6 +2766,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { return false; } $mailboxdata = array(); + $rl = ratelimit('get', 'mailbox', $_data); $stmt = $pdo->prepare("SELECT `domain`.`backupmx`, `mailbox`.`username`, @@ -2918,6 +2798,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { $mailboxdata['max_new_quota'] = ($DomainQuota['maxquota'] * 1048576); } $mailboxdata['username'] = $row['username']; + $mailboxdata['rl'] = $rl; $mailboxdata['is_relayed'] = $row['backupmx']; $mailboxdata['name'] = $row['name']; $mailboxdata['active'] = $row['active']; diff --git a/data/web/inc/functions.ratelimit.inc.php b/data/web/inc/functions.ratelimit.inc.php new file mode 100644 index 00000000..8218a7bd --- /dev/null +++ b/data/web/inc/functions.ratelimit.inc.php @@ -0,0 +1,188 @@ + 'danger', + 'log' => array(__FUNCTION__, $_action, $_scope, $_data_log), + 'msg' => 'rl_timeframe' + ); + continue; + } + if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $object)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_scope, $_data_log), + 'msg' => 'access_denied' + ); + continue; + } + if (empty($rl_value)) { + try { + $redis->hDel('RL_VALUE', $object); + } + catch (RedisException $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_scope, $_data_log), + 'msg' => array('redis_error', $e) + ); + continue; + } + } + else { + try { + $redis->hSet('RL_VALUE', $object, $rl_value . ' / 1' . $rl_frame); + } + catch (RedisException $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_scope, $_data_log), + 'msg' => array('redis_error', $e) + ); + continue; + } + } + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_scope, $_data_log), + 'msg' => array('object_modified', $object) + ); + } + break; + case 'mailbox': + if (!is_array($_data['object'])) { + $objects = array(); + $objects[] = $_data['object']; + } + else { + $objects = $_data['object']; + } + foreach ($objects as $object) { + $rl_value = intval($_data['rl_value']); + $rl_frame = $_data['rl_frame']; + if (!in_array($rl_frame, array('s', 'm', 'h'))) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_scope, $_data_log), + 'msg' => 'rl_timeframe' + ); + continue; + } + if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $object) + || ($_SESSION['mailcow_cc_role'] != 'admin' && $_SESSION['mailcow_cc_role'] != 'domainadmin')) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_scope, $_data_log), + 'msg' => 'access_denied' + ); + continue; + } + if (empty($rl_value)) { + try { + $redis->hDel('RL_VALUE', $object); + } + catch (RedisException $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_scope, $_data_log), + 'msg' => array('redis_error', $e) + ); + continue; + } + } + else { + try { + $redis->hSet('RL_VALUE', $object, $rl_value . ' / 1' . $rl_frame); + } + catch (RedisException $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_scope, $_data_log), + 'msg' => array('redis_error', $e) + ); + continue; + } + } + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_scope, $_data_log), + 'msg' => array('object_modified', $object) + ); + } + break; + } + break; + case 'get': + switch ($_scope) { + case 'domain': + if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data)) { + return false; + } + try { + if ($rl_value = $redis->hGet('RL_VALUE', $_data)) { + $rl = explode(' / 1', $rl_value); + $data['value'] = $rl[0]; + $data['frame'] = $rl[1]; + return $data; + } + else { + return false; + } + } + catch (RedisException $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_scope, $_data_log), + 'msg' => array('redis_error', $e) + ); + return false; + } + return false; + break; + case 'mailbox': + if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data) + || ($_SESSION['mailcow_cc_role'] != 'admin' && $_SESSION['mailcow_cc_role'] != 'domainadmin')) { + return false; + } + try { + if ($rl_value = $redis->hGet('RL_VALUE', $_data)) { + $rl = explode(' / 1', $rl_value); + $data['value'] = $rl[0]; + $data['frame'] = $rl[1]; + return $data; + } + else { + return false; + } + } + catch (RedisException $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_scope, $_data_log), + 'msg' => array('redis_error', $e) + ); + return false; + } + return false; + break; + } + break; + } +} \ No newline at end of file diff --git a/data/web/inc/prerequisites.inc.php b/data/web/inc/prerequisites.inc.php index c07d4d2d..a5e03672 100644 --- a/data/web/inc/prerequisites.inc.php +++ b/data/web/inc/prerequisites.inc.php @@ -132,6 +132,7 @@ require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.quarantine.inc.php'; require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.policy.inc.php'; require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.dkim.inc.php'; require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.fwdhost.inc.php'; +require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.ratelimit.inc.php'; require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.relayhost.inc.php'; require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.rsettings.inc.php'; require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.fail2ban.inc.php'; diff --git a/data/web/js/admin.js b/data/web/js/admin.js index a4fe801e..24725f60 100644 --- a/data/web/js/admin.js +++ b/data/web/js/admin.js @@ -9,6 +9,10 @@ jQuery(function($){ e.preventDefault(); $('#import_dkim_arrow').toggleClass("animation"); }); + $("#duplicate_dkim_legend").on('click', function(e) { + e.preventDefault(); + $('#duplicate_dkim_arrow').toggleClass("animation"); + }); $("#rspamd_preset_1").on('click', function(e) { e.preventDefault(); $("form[data-id=rsetting]").find("#desc").val(lang.rsettings_preset_1); @@ -19,6 +23,14 @@ jQuery(function($){ $("form[data-id=rsetting]").find("#desc").val(lang.rsettings_preset_2); $("form[data-id=rsetting]").find("#content").val('priority = 10;\nrcpt = "/postmaster@.*/";\nwant_spam = yes;'); }); + $("#dkim_missing_keys").on('click', function(e) { + e.preventDefault(); + var domains = []; + $('.dkim_missing').each(function() { + domains.push($(this).val()); + }); + $('#dkim_add_domains').val(domains); + }); function draw_domain_admins() { ft_domainadmins = FooTable.init('#domainadminstable', { "columns": [ diff --git a/data/web/js/debug.js b/data/web/js/debug.js index 37f45746..bf087167 100644 --- a/data/web/js/debug.js +++ b/data/web/js/debug.js @@ -171,7 +171,7 @@ jQuery(function($){ {"name":"user","title":"User"}, {"name":"role","title":"Role"}, {"name":"remote","title":"IP"}, - {"name":"msg","title":lang.message}, + {"name":"msg","title":lang.message,"style":{"word-break":"break-all"}}, {"name":"call","title":"Call","breakpoints": "all"}, ], "rows": $.ajax({ diff --git a/data/web/js/mailbox.js b/data/web/js/mailbox.js index 072eea63..95b4dc61 100644 --- a/data/web/js/mailbox.js +++ b/data/web/js/mailbox.js @@ -264,6 +264,7 @@ jQuery(function($){ }, }, {"name":"max_quota_for_mbox","title":lang.mailbox_quota,"breakpoints":"xs sm","style":{"width":"125px"}}, + {"name":"rl","title":"RL","breakpoints":"xs sm md","style":{"width":"125px"}}, {"name":"backupmx","filterable": false,"style":{"maxWidth":"120px","width":"120px"},"title":lang.backup_mx,"breakpoints":"xs sm"}, {"name":"active","filterable": false,"style":{"maxWidth":"80px","width":"80px"},"title":lang.active}, {"name":"action","filterable": false,"sortable": false,"style":{"text-align":"right","maxWidth":"240px","width":"240px"},"type":"html","title":lang.action,"breakpoints":"xs sm"} @@ -280,6 +281,13 @@ jQuery(function($){ item.aliases = item.aliases_in_domain + " / " + item.max_num_aliases_for_domain; item.mailboxes = item.mboxes_in_domain + " / " + item.max_num_mboxes_for_domain; item.quota = item.quota_used_in_domain + "/" + item.max_quota_for_domain; + if (!item.rl) { + item.rl = '∞'; + } else { + item.rl = $.map(item.rl, function(e){ + return e; + }).join('/1'); + } item.max_quota_for_mbox = humanFileSize(item.max_quota_for_mbox); item.chkbox = ''; item.action = '
'; @@ -342,6 +350,7 @@ jQuery(function($){ }, }, {"name":"messages","filterable": false,"title":lang.msg_num,"breakpoints":"xs sm md"}, + {"name":"rl","title":"RL","breakpoints":"xs sm md","style":{"width":"125px"}}, {"name":"active","filterable": false,"title":lang.active}, {"name":"action","filterable": false,"sortable": false,"style":{"min-width":"250px","text-align":"right"},"type":"html","title":lang.action,"breakpoints":"xs sm md"} ], @@ -357,6 +366,13 @@ jQuery(function($){ $.each(data, function (i, item) { item.quota = item.quota_used + "/" + item.quota; item.max_quota_for_mbox = humanFileSize(item.max_quota_for_mbox); + if (!item.rl) { + item.rl = '∞'; + } else { + item.rl = $.map(item.rl, function(e){ + return e; + }).join('/1'); + } item.chkbox = ''; if (role == "admin") { item.action = '
' + diff --git a/data/web/json_api.php b/data/web/json_api.php index bbf452f9..fba933c4 100644 --- a/data/web/json_api.php +++ b/data/web/json_api.php @@ -135,6 +135,9 @@ if (isset($_SESSION['mailcow_cc_role']) || isset($_SESSION['pending_mailcow_cc_u case "dkim": process_add_return(dkim('add', $attr)); break; + case "dkim_duplicate": + process_add_return(dkim('duplicate', $attr)); + break; case "dkim_import": process_add_return(dkim('import', $attr)); break; @@ -181,6 +184,7 @@ if (isset($_SESSION['mailcow_cc_role']) || isset($_SESSION['pending_mailcow_cc_u break; } break; + case "domain": switch ($object) { case "all": @@ -208,6 +212,67 @@ if (isset($_SESSION['mailcow_cc_role']) || isset($_SESSION['pending_mailcow_cc_u } break; + case "rl-domain": + switch ($object) { + case "all": + $domains = array_merge(mailbox('get', 'domains'), mailbox('get', 'alias_domains')); + if (!empty($domains)) { + foreach ($domains as $domain) { + if ($details = ratelimit('get', 'domain', $domain)) { + $details['domain'] = $domain; + $data[] = $details; + } + else { + continue; + } + } + process_get_return($data); + } + else { + echo '{}'; + } + break; + + default: + $data = ratelimit('get', 'domain', $object); + process_get_return($data); + break; + } + break; + + case "rl-mbox": + switch ($object) { + case "all": + $domains = mailbox('get', 'domains'); + if (!empty($domains)) { + foreach ($domains as $domain) { + $mailboxes = mailbox('get', 'mailboxes', $domain); + if (!empty($mailboxes)) { + foreach ($mailboxes as $mailbox) { + if ($details = ratelimit('get', 'mailbox', $mailbox)) { + $details['mailbox'] = $mailbox; + $data[] = $details; + } + else { + continue; + } + } + } + } + process_get_return($data); + } + else { + echo '{}'; + } + break; + + default: + $data = ratelimit('get', 'mailbox', $object); + process_get_return($data); + break; + } + break; + case "relayhost": switch ($object) { case "all": @@ -968,8 +1033,11 @@ if (isset($_SESSION['mailcow_cc_role']) || isset($_SESSION['pending_mailcow_cc_u case "domain": process_edit_return(mailbox('edit', 'domain', array_merge(array('domain' => $items), $attr))); break; - case "ratelimit": - process_edit_return(mailbox('edit', 'ratelimit', array_merge(array('object' => $items), $attr))); + case "rl-domain": + process_edit_return(ratelimit('edit', 'domain', array_merge(array('object' => $items), $attr))); + break; + case "rl-mbox": + process_edit_return(ratelimit('edit', 'mailbox', array_merge(array('object' => $items), $attr))); break; case "alias-domain": process_edit_return(mailbox('edit', 'alias_domain', array_merge(array('alias_domain' => $items), $attr))); diff --git a/data/web/lang/lang.de.php b/data/web/lang/lang.de.php index 3125f2ae..ef8805e3 100644 --- a/data/web/lang/lang.de.php +++ b/data/web/lang/lang.de.php @@ -65,6 +65,7 @@ $lang['success']['saved_settings'] = "Regel wurde gespeichert"; $lang['danger']['dkim_domain_or_sel_invalid'] = 'DKIM-Domain oder Selektor nicht korrekt: %s'; $lang['success']['dkim_removed'] = 'DKIM-Key wurde entfernt'; $lang['success']['dkim_added'] = 'DKIM-Key wurde hinzugefügt'; +$lang['success']['dkim_duplicated'] = "DKIM-Key der Domain %s wurde auf Domain %s kopiert"; $lang['danger']['access_denied'] = 'Zugriff verweigert oder unvollständige/ungültige Daten'; $lang['danger']['domain_invalid'] = 'Domainname %s ist ungültig'; $lang['danger']['mailbox_quota_exceeds_domain_quota'] = 'Maximale Größe für Mailboxen überschreitet das Domain Speicherlimit'; @@ -249,6 +250,7 @@ $lang['mailbox']['kind'] = 'Art'; $lang['mailbox']['description'] = 'Beschreibung'; $lang['mailbox']['resources'] = 'Ressourcen'; $lang['mailbox']['domains'] = 'Domains'; +$lang['admin']['domain_s'] = 'Domain(s)'; $lang['mailbox']['mailboxes'] = 'Mailboxen'; $lang['mailbox']['mailbox_quota'] = 'Max. Größe einer Mailbox'; $lang['mailbox']['domain_quota'] = 'Gesamtspeicher'; @@ -436,7 +438,9 @@ $lang['admin']['no_new_rows'] = 'Keine weiteren Zeilen vorhanden'; $lang['admin']['additional_rows'] = ' zusätzliche Zeilen geladen'; // parses to 'n additional rows were added' $lang['admin']['private_key'] = 'Private Key'; $lang['admin']['import'] = 'Importieren'; +$lang['admin']['duplicate'] = 'Duplizieren'; $lang['admin']['import_private_key'] = 'Private Key importieren'; +$lang['admin']['duplicate_dkim'] = 'DKIM duplizieren'; $lang['admin']['f2b_parameters'] = 'Fail2ban Parameter'; $lang['admin']['f2b_ban_time'] = 'Banzeit (s)'; $lang['admin']['f2b_max_attempts'] = 'Max. Versuche'; @@ -453,6 +457,11 @@ $lang['admin']['dkim_keys'] = 'ARC/DKIM-Keys'; $lang['admin']['dkim_key_valid'] = 'Key gültig'; $lang['admin']['dkim_key_unused'] = 'Key ohne Zuweisung'; $lang['admin']['dkim_key_missing'] = 'Key fehlt'; +$lang['admin']['dkim_from'] = 'Von'; +$lang['admin']['dkim_to'] = 'Nach'; +$lang['admin']['dkim_from_title'] = 'Quellobjekt für Duplizierung'; +$lang['admin']['dkim_to_title'] = 'Ziel-Objekt(e) werden überschrieben'; +$lang['admin']['dkim_domains_wo_keys'] = "Domains mit fehlenden Keys auswählen"; $lang['admin']['add'] = 'Hinzufügen'; $lang['add']['add_domain_restart'] = 'Domain hinzufügen und SOGo neustarten'; $lang['add']['add_domain_only'] = 'Nur Domain hinzufügen'; diff --git a/data/web/lang/lang.en.php b/data/web/lang/lang.en.php index ed2fbf25..6f345fb0 100644 --- a/data/web/lang/lang.en.php +++ b/data/web/lang/lang.en.php @@ -68,7 +68,8 @@ $lang['warning']['session_token'] = "Form token invalid: Token mismatch"; $lang['danger']['dkim_domain_or_sel_invalid'] = "DKIM domain or selector invalid: %s"; $lang['success']['dkim_removed'] = "DKIM key %s has been removed"; -$lang['success']['dkim_added'] = "DKIM key has been saved"; +$lang['success']['dkim_added'] = "DKIM key %s has been saved"; +$lang['success']['dkim_duplicated'] = "DKIM key for domain %s has been copied to %s"; $lang['danger']['access_denied'] = "Access denied or invalid form data"; $lang['danger']['domain_invalid'] = "Domain name %s is invalid"; $lang['danger']['mailbox_quota_exceeds_domain_quota'] = "Max. quota exceeds domain quota limit"; @@ -250,6 +251,7 @@ $lang['mailbox']['description'] = 'Description'; $lang['mailbox']['alias'] = 'Alias'; $lang['mailbox']['aliases'] = 'Aliases'; $lang['mailbox']['domains'] = 'Domains'; +$lang['admin']['domain_s'] = 'Domain/s'; $lang['mailbox']['mailboxes'] = 'Mailboxes'; $lang['mailbox']['resources'] = 'Resources'; $lang['mailbox']['mailbox_quota'] = 'Max. size of a mailbox'; @@ -445,7 +447,13 @@ $lang['admin']['no_new_rows'] = 'No further rows available'; $lang['admin']['additional_rows'] = ' additional rows were added'; // parses to 'n additional rows were added' $lang['admin']['private_key'] = 'Private key'; $lang['admin']['import'] = 'Import'; +$lang['admin']['duplicate'] = 'Duplicate'; $lang['admin']['import_private_key'] = 'Import private key'; +$lang['admin']['duplicate_dkim'] = 'Duplicate DKIM record'; +$lang['admin']['dkim_from'] = 'From'; +$lang['admin']['dkim_to'] = 'To'; +$lang['admin']['dkim_from_title'] = 'Source domain to copy data from'; +$lang['admin']['dkim_to_title'] = 'Target domain/s - will be overwritten'; $lang['admin']['f2b_parameters'] = 'Fail2ban parameters'; $lang['admin']['f2b_ban_time'] = 'Ban time (s)'; $lang['admin']['f2b_max_attempts'] = 'Max. attempts'; @@ -465,6 +473,7 @@ $lang['admin']['dkim_key_unused'] = 'Key unused'; $lang['admin']['dkim_key_missing'] = 'Key missing'; $lang['admin']['dkim_add_key'] = 'Add ARC/DKIM key'; $lang['admin']['dkim_keys'] = 'ARC/DKIM keys'; +$lang['admin']['dkim_domains_wo_keys'] = "Select domains with missing keys"; $lang['admin']['add'] = 'Add'; $lang['add']['add_domain_restart'] = 'Add domain and restart SOGo'; $lang['add']['add_domain_only'] = 'Add domain only';