From b99820d0117e364dec337915dde7d697116ee532 Mon Sep 17 00:00:00 2001 From: andryyy Date: Thu, 20 Dec 2018 11:23:35 +0100 Subject: [PATCH] [Web] Allow to set transport maps, rename relayhosts to sender-dependent transports --- data/web/admin.php | 137 +++++-- data/web/edit.php | 53 +++ .../{relay_check.php => transport_check.php} | 40 +- data/web/inc/functions.relayhost.inc.php | 174 -------- data/web/inc/functions.transports.inc.php | 385 ++++++++++++++++++ data/web/inc/init_db.inc.php | 22 +- data/web/inc/prerequisites.inc.php | 2 +- data/web/js/admin.js | 74 +++- data/web/js/debug.js | 2 +- data/web/js/mailbox.js | 62 +++ data/web/json_api.php | 36 ++ data/web/lang/lang.de.php | 35 +- data/web/lang/lang.en.php | 32 +- data/web/modals/admin.php | 17 +- 14 files changed, 820 insertions(+), 251 deletions(-) rename data/web/inc/ajax/{relay_check.php => transport_check.php} (65%) delete mode 100644 data/web/inc/functions.relayhost.inc.php create mode 100644 data/web/inc/functions.transports.inc.php diff --git a/data/web/admin.php b/data/web/admin.php index 2241da86..07c0f7e6 100644 --- a/data/web/admin.php +++ b/data/web/admin.php @@ -10,6 +10,7 @@ $tfa_data = get_tfa(); @@ -178,6 +179,101 @@ $tfa_data = get_tfa(); +
+
+
+
+

+
+
+
+
+
+ + + +
+
+ +

+
+
+
+
+ + +
+
+ + +
+
+ + +
+ +
+
+
+
+
+ +
+
+
+

+
+
+
+
+
+ + + +
+
+ +

+
+
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+

+ +
+
+
+
+
+
+ + +
- -
-
Relayhosts
-
-

-
-
-
-
-
- - - -
-
- -

-
-
- - -
-
- - -
-
- - -
- -
-
-
-
diff --git a/data/web/edit.php b/data/web/edit.php index 188f2cb9..fed7e2c9 100644 --- a/data/web/edit.php +++ b/data/web/edit.php @@ -693,6 +693,59 @@ if (isset($_SESSION['mailcow_cc_role'])) { +

+
+ +
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+
+
+ +
+
+
+
+
+ +
+
+
+ + + '; + } + else { + echo 'No MX records for ' . $hostname . ' were found in DNS, skipping and using hostname as next-hop.
'; + } + } // Use port 25 if no port was given $port = (empty($port)) ? 25 : $port; - $username = $relayhost_details['username']; - $password = $relayhost_details['password']; + $username = $transport_details['username']; + $password = $transport_details['password']; $mail = new PHPMailer; $mail->Timeout = 10; @@ -73,7 +99,7 @@ if (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == "admi $mail->send(); } else { - echo "Unknown relayhost."; + echo "Unknown transport."; } } else { diff --git a/data/web/inc/functions.relayhost.inc.php b/data/web/inc/functions.relayhost.inc.php deleted file mode 100644 index a3e1ffda..00000000 --- a/data/web/inc/functions.relayhost.inc.php +++ /dev/null @@ -1,174 +0,0 @@ - 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => 'access_denied' - ); - return false; - } - $hostname = trim($_data['hostname']); - $username = str_replace(':', '\:', trim($_data['username'])); - $password = str_replace(':', '\:', trim($_data['password'])); - if (empty($hostname)) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('invalid_host', htmlspecialchars($host)) - ); - return false; - } - try { - $stmt = $pdo->prepare("INSERT INTO `relayhosts` (`hostname`, `username` ,`password`, `active`) - VALUES (:hostname, :username, :password, :active)"); - $stmt->execute(array( - ':hostname' => $hostname, - ':username' => $username, - ':password' => str_replace(':', '\:', $password), - ':active' => '1' - )); - } - catch (PDOException $e) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('mysql_error', $e) - ); - return false; - } - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('relayhost_added', htmlspecialchars(implode(', ', $hosts))) - ); - break; - case 'edit': - if ($_SESSION['mailcow_cc_role'] != "admin") { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => 'access_denied' - ); - return false; - } - $ids = (array)$_data['id']; - foreach ($ids as $id) { - $is_now = relayhost('details', $id); - if (!empty($is_now)) { - $hostname = (!empty($_data['hostname'])) ? trim($_data['hostname']) : $is_now['hostname']; - $username = (isset($_data['username'])) ? trim($_data['username']) : $is_now['username']; - $password = (isset($_data['password'])) ? trim($_data['password']) : $is_now['password']; - $active = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active_int']; - } - else { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('relayhost_invalid', $id) - ); - continue; - } - try { - $stmt = $pdo->prepare("UPDATE `relayhosts` SET - `hostname` = :hostname, - `username` = :username, - `password` = :password, - `active` = :active - WHERE `id` = :id"); - $stmt->execute(array( - ':id' => $id, - ':hostname' => $hostname, - ':username' => $username, - ':password' => $password, - ':active' => $active - )); - } - catch (PDOException $e) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('mysql_error', $e) - ); - continue; - } - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('object_modified', htmlspecialchars(implode(', ', $hostnames))) - ); - } - break; - case 'delete': - if ($_SESSION['mailcow_cc_role'] != "admin") { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => 'access_denied' - ); - return false; - } - $ids = (array)$_data['id']; - foreach ($ids as $id) { - try { - $stmt = $pdo->prepare("DELETE FROM `relayhosts` WHERE `id`= :id"); - $stmt->execute(array(':id' => $id)); - $stmt = $pdo->prepare("UPDATE `domain` SET `relayhost` = '0' WHERE `relayhost`= :id"); - $stmt->execute(array(':id' => $id)); - } - catch (PDOException $e) { - $_SESSION['return'][] = array( - 'type' => 'danger', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('mysql_error', $e) - ); - continue; - } - $_SESSION['return'][] = array( - 'type' => 'success', - 'log' => array(__FUNCTION__, $_action, $_data_log), - 'msg' => array('relayhost_removed', htmlspecialchars($id)) - ); - } - break; - case 'get': - if ($_SESSION['mailcow_cc_role'] != "admin") { - return false; - } - $relayhosts = array(); - $stmt = $pdo->query("SELECT `id`, `hostname`, `username` FROM `relayhosts`"); - $relayhosts = $stmt->fetchAll(PDO::FETCH_ASSOC); - return $relayhosts; - break; - case 'details': - if ($_SESSION['mailcow_cc_role'] != "admin" || !isset($_data)) { - return false; - } - $relayhostdata = array(); - $stmt = $pdo->prepare("SELECT `id`, - `hostname`, - `username`, - `password`, - `active` AS `active_int`, - CONCAT(LEFT(`password`, 3), '...') AS `password_short`, - CASE `active` WHEN 1 THEN '".$lang['mailbox']['yes']."' ELSE '".$lang['mailbox']['no']."' END AS `active` - FROM `relayhosts` - WHERE `id` = :id"); - $stmt->execute(array(':id' => $_data)); - $relayhostdata = $stmt->fetch(PDO::FETCH_ASSOC); - if (!empty($relayhostdata)) { - $stmt = $pdo->prepare("SELECT GROUP_CONCAT(`domain` SEPARATOR ', ') AS `used_by_domains` FROM `domain` WHERE `relayhost` = :id"); - $stmt->execute(array(':id' => $_data)); - $used_by_domains = $stmt->fetch(PDO::FETCH_ASSOC)['used_by_domains']; - $used_by_domains = (empty($used_by_domains)) ? '' : $used_by_domains; - $relayhostdata['used_by_domains'] = $used_by_domains; - } - return $relayhostdata; - break; - } -} \ No newline at end of file diff --git a/data/web/inc/functions.transports.inc.php b/data/web/inc/functions.transports.inc.php new file mode 100644 index 00000000..4f30645f --- /dev/null +++ b/data/web/inc/functions.transports.inc.php @@ -0,0 +1,385 @@ + 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => 'access_denied' + ); + return false; + } + $hostname = trim($_data['hostname']); + $username = str_replace(':', '\:', trim($_data['username'])); + $password = str_replace(':', '\:', trim($_data['password'])); + if (empty($hostname)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('invalid_host', htmlspecialchars($host)) + ); + return false; + } + try { + $stmt = $pdo->prepare("INSERT INTO `relayhosts` (`hostname`, `username` ,`password`, `active`) + VALUES (:hostname, :username, :password, :active)"); + $stmt->execute(array( + ':hostname' => $hostname, + ':username' => $username, + ':password' => str_replace(':', '\:', $password), + ':active' => '1' + )); + } + catch (PDOException $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('mysql_error', $e) + ); + return false; + } + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('relayhost_added', htmlspecialchars(implode(', ', $hosts))) + ); + break; + case 'edit': + if ($_SESSION['mailcow_cc_role'] != "admin") { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => 'access_denied' + ); + return false; + } + $ids = (array)$_data['id']; + foreach ($ids as $id) { + $is_now = relayhost('details', $id); + if (!empty($is_now)) { + $hostname = (!empty($_data['hostname'])) ? trim($_data['hostname']) : $is_now['hostname']; + $username = (isset($_data['username'])) ? trim($_data['username']) : $is_now['username']; + $password = (isset($_data['password'])) ? trim($_data['password']) : $is_now['password']; + $active = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active_int']; + } + else { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('relayhost_invalid', $id) + ); + continue; + } + try { + $stmt = $pdo->prepare("UPDATE `relayhosts` SET + `hostname` = :hostname, + `username` = :username, + `password` = :password, + `active` = :active + WHERE `id` = :id"); + $stmt->execute(array( + ':id' => $id, + ':hostname' => $hostname, + ':username' => $username, + ':password' => $password, + ':active' => $active + )); + } + catch (PDOException $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('mysql_error', $e) + ); + continue; + } + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('object_modified', htmlspecialchars(implode(', ', $hostnames))) + ); + } + break; + case 'delete': + if ($_SESSION['mailcow_cc_role'] != "admin") { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => 'access_denied' + ); + return false; + } + $ids = (array)$_data['id']; + foreach ($ids as $id) { + try { + $stmt = $pdo->prepare("DELETE FROM `relayhosts` WHERE `id`= :id"); + $stmt->execute(array(':id' => $id)); + $stmt = $pdo->prepare("UPDATE `domain` SET `relayhost` = '0' WHERE `relayhost`= :id"); + $stmt->execute(array(':id' => $id)); + } + catch (PDOException $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('mysql_error', $e) + ); + continue; + } + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('relayhost_removed', htmlspecialchars($id)) + ); + } + break; + case 'get': + if ($_SESSION['mailcow_cc_role'] != "admin") { + return false; + } + $relayhosts = array(); + $stmt = $pdo->query("SELECT `id`, `hostname`, `username` FROM `relayhosts`"); + $relayhosts = $stmt->fetchAll(PDO::FETCH_ASSOC); + return $relayhosts; + break; + case 'details': + if ($_SESSION['mailcow_cc_role'] != "admin" || !isset($_data)) { + return false; + } + $relayhostdata = array(); + $stmt = $pdo->prepare("SELECT `id`, + `hostname`, + `username`, + `password`, + `active` AS `active_int`, + CONCAT(LEFT(`password`, 3), '...') AS `password_short`, + CASE `active` WHEN 1 THEN '".$lang['mailbox']['yes']."' ELSE '".$lang['mailbox']['no']."' END AS `active` + FROM `relayhosts` + WHERE `id` = :id"); + $stmt->execute(array(':id' => $_data)); + $relayhostdata = $stmt->fetch(PDO::FETCH_ASSOC); + if (!empty($relayhostdata)) { + $stmt = $pdo->prepare("SELECT GROUP_CONCAT(`domain` SEPARATOR ', ') AS `used_by_domains` FROM `domain` WHERE `relayhost` = :id"); + $stmt->execute(array(':id' => $_data)); + $used_by_domains = $stmt->fetch(PDO::FETCH_ASSOC)['used_by_domains']; + $used_by_domains = (empty($used_by_domains)) ? '' : $used_by_domains; + $relayhostdata['used_by_domains'] = $used_by_domains; + } + return $relayhostdata; + break; + } +} +function transport($_action, $_data = null) { + global $pdo; + global $lang; + $_data_log = $_data; + switch ($_action) { + case 'add': + if ($_SESSION['mailcow_cc_role'] != "admin") { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => 'access_denied' + ); + return false; + } + $destination = trim($_data['destination']); + $nexthop = trim($_data['nexthop']); + $username = str_replace(':', '\:', trim($_data['username'])); + $password = str_replace(':', '\:', trim($_data['password'])); + if (empty($destination) || (is_valid_domain_name(preg_replace('/^' . preg_quote('.', '/') . '/', '', $destination)) === false && $destination != '*')) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => 'invalid_destination' + ); + return false; + } + if (empty($nexthop)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('invalid_nexthop') + ); + return false; + } + if (!empty($username)) { + $transports = transport('get'); + if (!empty($transports)) { + foreach ($transports as $transport) { + if (transport('details', $transport['id'])['nexthop'] == $nexthop && !empty(transport('details', $transport['id'])['username'])) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => 'invalid_nexthop_authenticated' + ); + return false; + } + } + } + } + try { + $stmt = $pdo->prepare("INSERT INTO `transports` (`nexthop`, `destination`, `username` ,`password`, `active`) + VALUES (:nexthop, :destination, :username, :password, :active)"); + $stmt->execute(array( + ':nexthop' => $nexthop, + ':destination' => $destination, + ':username' => $username, + ':password' => str_replace(':', '\:', $password), + ':active' => '1' + )); + $stmt = $pdo->prepare("UPDATE `transports` SET + `username` = :username, + `password` = :password + WHERE `nexthop` = :nexthop"); + $stmt->execute(array( + ':nexthop' => $nexthop, + ':username' => $username, + ':password' => $password + )); + } + catch (PDOException $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('mysql_error', $e) + ); + return false; + } + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('relayhost_added', htmlspecialchars(implode(', ', $hosts))) + ); + break; + case 'edit': + if ($_SESSION['mailcow_cc_role'] != "admin") { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => 'access_denied' + ); + return false; + } + $ids = (array)$_data['id']; + foreach ($ids as $id) { + $is_now = transport('details', $id); + if (!empty($is_now)) { + $destination = (!empty($_data['destination'])) ? trim($_data['destination']) : $is_now['destination']; + $nexthop = (!empty($_data['nexthop'])) ? trim($_data['nexthop']) : $is_now['nexthop']; + $username = (isset($_data['username'])) ? trim($_data['username']) : $is_now['username']; + $password = (isset($_data['password'])) ? trim($_data['password']) : $is_now['password']; + $active = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active_int']; + } + else { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('relayhost_invalid', $id) + ); + continue; + } + try { + $stmt = $pdo->prepare("UPDATE `transports` SET + `destination` = :destination, + `nexthop` = :nexthop, + `username` = :username, + `password` = :password, + `active` = :active + WHERE `id` = :id"); + $stmt->execute(array( + ':id' => $id, + ':destination' => $destination, + ':nexthop' => $nexthop, + ':username' => $username, + ':password' => $password, + ':active' => $active + )); + $stmt = $pdo->prepare("UPDATE `transports` SET + `username` = :username, + `password` = :password + WHERE `nexthop` = :nexthop"); + $stmt->execute(array( + ':nexthop' => $nexthop, + ':username' => $username, + ':password' => $password + )); + } + catch (PDOException $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('mysql_error', $e) + ); + continue; + } + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('object_modified', htmlspecialchars(implode(', ', $hostnames))) + ); + } + break; + case 'delete': + if ($_SESSION['mailcow_cc_role'] != "admin") { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => 'access_denied' + ); + return false; + } + $ids = (array)$_data['id']; + foreach ($ids as $id) { + try { + $stmt = $pdo->prepare("DELETE FROM `transports` WHERE `id`= :id"); + $stmt->execute(array(':id' => $id)); + } + catch (PDOException $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('mysql_error', $e) + ); + continue; + } + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('relayhost_removed', htmlspecialchars($id)) + ); + } + break; + case 'get': + if ($_SESSION['mailcow_cc_role'] != "admin") { + return false; + } + $transports = array(); + $stmt = $pdo->query("SELECT `id`, `destination`, `nexthop`, `username` FROM `transports`"); + $transports = $stmt->fetchAll(PDO::FETCH_ASSOC); + return $transports; + break; + case 'details': + if ($_SESSION['mailcow_cc_role'] != "admin" || !isset($_data)) { + return false; + } + $transportdata = array(); + $stmt = $pdo->prepare("SELECT `id`, + `destination`, + `nexthop`, + `username`, + `password`, + `active` AS `active_int`, + CONCAT(LEFT(`password`, 3), '...') AS `password_short`, + CASE `active` WHEN 1 THEN '".$lang['mailbox']['yes']."' ELSE '".$lang['mailbox']['no']."' END AS `active` + FROM `transports` + WHERE `id` = :id"); + $stmt->execute(array(':id' => $_data)); + $transportdata = $stmt->fetch(PDO::FETCH_ASSOC); + return $transportdata; + break; + } +} \ No newline at end of file diff --git a/data/web/inc/init_db.inc.php b/data/web/inc/init_db.inc.php index 3b17f145..052c1b0e 100644 --- a/data/web/inc/init_db.inc.php +++ b/data/web/inc/init_db.inc.php @@ -3,7 +3,7 @@ function init_db_schema() { try { global $pdo; - $db_version = "14112018_0717"; + $db_version = "15122018_0717"; $stmt = $pdo->query("SHOW TABLES LIKE 'versions'"); $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); @@ -109,6 +109,26 @@ function init_db_schema() { ), "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" ), + "transports" => array( + "cols" => array( + "id" => "INT NOT NULL AUTO_INCREMENT", + "destination" => "VARCHAR(255) NOT NULL", + "nexthop" => "VARCHAR(255) NOT NULL", + "username" => "VARCHAR(255) NOT NULL", + "password" => "VARCHAR(255) NOT NULL", + "active" => "TINYINT(1) NOT NULL DEFAULT '1'" + ), + "keys" => array( + "primary" => array( + "" => array("id") + ), + "key" => array( + "destination" => array("destination"), + "nexthop" => array("nexthop"), + ) + ), + "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" + ), "alias" => array( "cols" => array( "id" => "INT NOT NULL AUTO_INCREMENT", diff --git a/data/web/inc/prerequisites.inc.php b/data/web/inc/prerequisites.inc.php index 9eca849a..5ed34478 100644 --- a/data/web/inc/prerequisites.inc.php +++ b/data/web/inc/prerequisites.inc.php @@ -147,7 +147,7 @@ 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.mailq.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.transports.inc.php'; require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.rsettings.inc.php'; require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.tls_policy_maps.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 07bf27b6..c53f47d5 100644 --- a/data/web/js/admin.js +++ b/data/web/js/admin.js @@ -5,6 +5,8 @@ jQuery(function($){ var entityMap={"&":"&","<":"<",">":">",'"':""","'":"'","/":"/","`":"`","=":"="}; function escapeHtml(n){return String(n).replace(/[&<>"'`=\/]/g,function(n){return entityMap[n]})} function humanFileSize(i){if(Math.abs(i)<1024)return i+" B";var B=["KiB","MiB","GiB","TiB","PiB","EiB","ZiB","YiB"],e=-1;do{i/=1024,++e}while(Math.abs(i)>=1024&&e Test' + + ' Test' + ' ' + lang.edit + '' + - ' ' + lang.remove + '' + + ' ' + lang.remove + '' + '
'; item.chkbox = ''; }); + } else if (table == 'transportstable') { + $.each(data, function (i, item) { + if (item.username) { + item.username = '' + item.username + ''; + } + item.action = ''; + item.chkbox = ''; + }); } else if (table == 'queuetable') { $.each(data, function (i, item) { item.chkbox = ''; @@ -264,6 +305,7 @@ jQuery(function($){ draw_admins(); draw_fwd_hosts(); draw_relayhosts(); + draw_transport_maps(); draw_queue(); // Relayhost $('#testRelayhostModal').on('show.bs.modal', function (e) { @@ -290,6 +332,32 @@ jQuery(function($){ } }); }) + // Transport + $('#testTransportModal').on('show.bs.modal', function (e) { + $('#test_transport_result').text("-"); + button = $(e.relatedTarget) + if (button != null) { + $('#transport_id').val(button.data('transport-id')); + $('#transport_type').val(button.data('transport-type')); + } + }) + $('#test_transport').on('click', function (e) { + e.preventDefault(); + prev = $('#test_transport').text(); + $(this).prop("disabled",true); + $(this).html(' '); + $.ajax({ + type: 'GET', + url: 'inc/ajax/transport_check.php', + dataType: 'text', + data: $('#test_transport_form').serialize(), + complete: function (data) { + $('#test_transport_result').html(data.responseText); + $('#test_transport').prop("disabled",false); + $('#test_transport').text(prev); + } + }); + }) // DKIM private key modal $('#showDKIMprivKey').on('show.bs.modal', function (e) { $('#priv_key_pre').text("-"); diff --git a/data/web/js/debug.js b/data/web/js/debug.js index b294931c..4c5119cb 100644 --- a/data/web/js/debug.js +++ b/data/web/js/debug.js @@ -592,7 +592,7 @@ jQuery(function($){ if (item.rl_hash == null) { item.rl_hash = "err"; } - item.indicator = '  '; + item.indicator = ' '; if (item.rl_hash != 'err') { item.action = ' ' + lang.reset_limit + ''; } diff --git a/data/web/js/mailbox.js b/data/web/js/mailbox.js index 1f2b4bd2..3551afa2 100644 --- a/data/web/js/mailbox.js +++ b/data/web/js/mailbox.js @@ -646,6 +646,67 @@ jQuery(function($){ } }); } + function draw_transport_maps_table() { + ft_transport_maps_table = FooTable.init('#transport_maps_table', { + "columns": [ + {"name":"chkbox","title":"","style":{"maxWidth":"60px","width":"60px"},"filterable": false,"sortable": false,"type":"html"}, + {"sorted": true,"name":"id","title":"ID","style":{"maxWidth":"60px","width":"60px","text-align":"center"}}, + {"name":"dest","title":lang.tls_map_dest}, + {"name":"parameters","title":lang.tls_map_parameters}, + {"name":"active","filterable": false,"style":{"maxWidth":"80px","width":"80px"},"title":lang.active}, + {"name":"action","filterable": false,"sortable": false,"style":{"text-align":"right","maxWidth":"180px","width":"180px"},"type":"html","title":(role == "admin" ? lang.action : ""),"breakpoints":"xs sm"} + ], + "empty": lang.empty, + "rows": $.ajax({ + dataType: 'json', + url: '/api/v1/get/transport-map/all', + jsonp: false, + error: function () { + console.log('Cannot draw transport map table'); + }, + success: function (data) { + if (role == "admin") { + $.each(data, function (i, item) { + item.dest = escapeHtml(item.dest); + if (item.parameters == '') { + item.parameters = '-'; + } else { + item.parameters = '' + escapeHtml(item.parameters) + ''; + } + item.action = ''; + item.chkbox = ''; + }); + } + } + }), + "paging": { + "enabled": true, + "limit": 5, + "size": pagination_size + }, + "filtering": { + "enabled": true, + "delay": 100, + "position": "left", + "connectors": false, + "placeholder": lang.filter_table + }, + "sorting": { + "enabled": true + }, + "on": { + "ready.ft.table": function(e, ft){ + table_mailbox_ready(ft, 'transport_maps_table'); + }, + "after.ft.paging": function(e, ft){ + paging_mailbox_after(ft, 'transport_maps_table'); + } + } + }); + } function draw_alias_table() { ft_alias_table = FooTable.init('#alias_table', { "columns": [ @@ -925,5 +986,6 @@ jQuery(function($){ draw_bcc_table(); draw_recipient_map_table(); draw_tls_policy_table(); + draw_transport_maps_table(); }); diff --git a/data/web/json_api.php b/data/web/json_api.php index c9db5eb6..6a876f8f 100644 --- a/data/web/json_api.php +++ b/data/web/json_api.php @@ -102,6 +102,9 @@ if (isset($_SESSION['mailcow_cc_role']) || isset($_SESSION['pending_mailcow_cc_u case "relayhost": process_add_return(relayhost('add', $attr)); break; + case "transport": + process_add_return(transport('add', $attr)); + break; case "rsetting": process_add_return(rsettings('add', $attr)); break; @@ -320,6 +323,33 @@ if (isset($_SESSION['mailcow_cc_role']) || isset($_SESSION['pending_mailcow_cc_u } break; + case "transport": + switch ($object) { + case "all": + $transports = transport('get'); + if (!empty($transports)) { + foreach ($transports as $transport) { + if ($details = transport('details', $transport['id'])) { + $data[] = $details; + } + else { + continue; + } + } + process_get_return($data); + } + else { + echo '{}'; + } + break; + + default: + $data = transport('details', $object); + process_get_return($data); + break; + } + break; + case "rsetting": switch ($object) { case "all": @@ -990,6 +1020,9 @@ if (isset($_SESSION['mailcow_cc_role']) || isset($_SESSION['pending_mailcow_cc_u case "relayhost": process_delete_return(relayhost('delete', array('id' => $items))); break; + case "transport": + process_delete_return(transport('delete', array('id' => $items))); + break; case "rsetting": process_delete_return(rsettings('delete', array('id' => $items))); break; @@ -1107,6 +1140,9 @@ if (isset($_SESSION['mailcow_cc_role']) || isset($_SESSION['pending_mailcow_cc_u case "relayhost": process_edit_return(relayhost('edit', array_merge(array('id' => $items), $attr))); break; + case "transport": + process_edit_return(transport('edit', array_merge(array('id' => $items), $attr))); + break; case "rsetting": process_edit_return(rsettings('edit', array_merge(array('id' => $items), $attr))); break; diff --git a/data/web/lang/lang.de.php b/data/web/lang/lang.de.php index ba031d8e..8a763cb6 100644 --- a/data/web/lang/lang.de.php +++ b/data/web/lang/lang.de.php @@ -29,6 +29,7 @@ $lang['success']['verified_u2f_login'] = "U2F Anmeldung verifiziert"; $lang['success']['verified_yotp_login'] = "Yubico OTP Anmeldung verifiziert"; $lang['danger']['yotp_verification_failed'] = "Yubico OTP Verifizierung fehlgeschlagen: %s"; $lang['danger']['ip_list_empty'] = "Liste erlaubter IPs darf nicht leer sein"; +$lang['danger']['invalid_destination'] = "Ziel-Format ist ungültig"; $lang['danger']['rspamd_ui_pw_length'] = "Rspamd UI Passwort muss mindestens 6 Zeichen lang sein"; $lang['success']['rspamd_ui_pw_set'] = "Rspamd UI Passwort wurde gesetzt"; $lang['success']['queue_command_success'] = "Queue-Aufgabe erfolgreich ausgeführt"; @@ -65,7 +66,7 @@ $lang['success']['settings_map_added'] = "Regel wurde gespeichert"; $lang['danger']['settings_map_invalid'] = "Regel ID %s ist ungültig"; $lang['success']['settings_map_removed'] = "Regeln wurden entfernt: %s"; $lang['danger']['invalid_host'] = "Ungültiger Host: %s"; -$lang['danger']['relayhost_invalid'] = "Relayhost %s ist ungültig"; +$lang['danger']['relayhost_invalid'] = "Mapeintrag %s ist ungültig"; $lang['success']['saved_settings'] = "Regel wurde gespeichert"; $lang['danger']['dkim_domain_or_sel_invalid'] = 'DKIM-Domain oder Selektor nicht korrekt: %s'; @@ -392,7 +393,10 @@ $lang['acl']['prohibited'] = 'Untersagt durch Richtlinie'; $lang['add']['generate'] = 'generieren'; $lang['add']['syncjob'] = 'Syncjob hinzufügen'; $lang['add']['syncjob_hint'] = 'Passwörter werden unverschlüsselt abgelegt!'; -$lang['add']['hostname'] = 'Servername'; +$lang['add']['hostname'] = 'Host'; +$lang['add']['destination'] = 'Ziel'; +$lang['add']['nexthop'] = 'Next hop'; +$lang['edit']['nexthop'] = 'Next hop'; $lang['add']['port'] = 'Port'; $lang['add']['username'] = 'Benutzername'; $lang['add']['enc_method'] = 'Verschlüsselung'; @@ -572,12 +576,29 @@ $lang['admin']['message'] = 'Nachricht'; $lang['admin']['forwarding_hosts'] = 'Weiterleitungs-Hosts'; $lang['admin']['forwarding_hosts_hint'] = 'Eingehende Nachrichten werden von den hier gelisteten Hosts bedingungslos akzeptiert. Diese Hosts werden dann nicht mit DNSBLs abgeglichen oder Greylisting unterworfen. Von ihnen empfangener Spam wird nie abgelehnt, optional kann er aber in den Spam-Ordner einsortiert werden. Die übliche Verwendung für diese Funktion ist, um Mailserver anzugeben, auf denen eine Weiterleitung zu Ihrem mailcow-Server eingerichtet wurde.'; $lang['admin']['forwarding_hosts_add_hint'] = 'Sie können entweder IPv4/IPv6-Adressen, Netzwerke in CIDR-Notation, Hostnamen (die zu IP-Adressen aufgelöst werden), oder Domainnamen (die zu IP-Adressen aufgelöst werden, indem ihr SPF-Record abgefragt wird oder, in dessen Abwesenheit, ihre MX-Records) angeben.'; -$lang['admin']['relayhosts_hint'] = 'Erstellen Sie Relayhosts, um diese im Einstellungsdialog einer Domain auszuwählen.'; -$lang['admin']['add_relayhost_add_hint'] = 'Bitte beachten Sie, dass Relayhost Anmeldedaten im Klartext gespeichert werden.'; +$lang['admin']['relayhosts_hint'] = 'Erstellen Sie senderabhängige Transporte, um diese im Einstellungsdialog einer Domain auszuwählen.
+ Der Transporttyp lautet immer "smtp:". Benutzereinstellungen bezüglich Verschlüsselungsrichtlinie werden beim Transport berücksichtigt.'; +$lang['admin']['transports_hint'] = 'Transport Maps überwiegen senderabhängige Transport Maps und ignorieren die individuellen Einstellungen eines Benutzers bezüglich Verschlüsselungsrichtlinie, da der Absender bei Ermittlung der Transportregel nicht berücksichtigt wird.
+ Der Transport erfolgt immer via "smtp:".
+ Ein Eintrag in der TLS Policy Map kann eine Verschlüsselung erzwingen.
+ Die Authentifizierung wird anhand des Host Parameters ermittelt.'; +$lang['admin']['add_relayhost_hint'] = 'Bitte beachten Sie, dass Anmeldedaten klartext gespeichert werden.
+ Angelegte Transporte dieser Art sind senderabhängig und müssen erst einer Domain zugewiesen werden, bevor sie als Transport verwendet werden.
+ Diese Einstellungen entsprechen demach nicht dem "relayhost" Parameter in Postfix.'; +$lang['admin']['add_transports_hint'] = 'Bitte beachten Sie, dass Anmeldedaten klartext gespeichert werden.'; $lang['admin']['host'] = 'Host'; $lang['admin']['source'] = 'Quelle'; $lang['admin']['add_forwarding_host'] = 'Weiterleitungs-Host hinzufügen'; -$lang['admin']['add_relayhost'] = 'Relayhost hinzufügen'; +$lang['admin']['add_relayhost'] = 'Senderabhängigen Transport hinzufügen'; +$lang['admin']['add_transport'] = 'Transport hinzufügen'; +$lang['admin']['relayhosts'] = 'Senderabhängige Transport Maps'; +$lang['admin']['transport_maps'] = 'Transport Maps'; +$lang['admin']['routing'] = 'Routing'; +$lang['admin']['credentials_transport_warning'] = 'Warnung: Das Hinzufügen einer neuen Regel bewirkt die Aktualisierung der Authentifizierungsdaten aller vorhandenen Einträge mit identischem Host.'; + +$lang['admin']['destination'] = 'Ziel'; +$lang['admin']['nexthop'] = 'Next hop'; + $lang['admin']['api_allow_from'] = "IP-Adressen für Zugriff"; $lang['admin']['api_key'] = "API-Key"; $lang['admin']['activate_api'] = "API aktivieren"; @@ -594,8 +615,8 @@ $lang['admin']['quarantine_exclude_domains'] = "Domains und Alias-Domains aussch $lang['success']['forwarding_host_removed'] = "Weiterleitungs-Host %s wurde entfernt"; $lang['success']['forwarding_host_added'] = "Weiterleitungs-Host %s wurde hinzugefügt"; -$lang['success']['relayhost_removed'] = "Relayhost %s wurde entfernt"; -$lang['success']['relayhost_added'] = "Relayhost %s wurde hinzugefügt"; +$lang['success']['relayhost_removed'] = "Mapeintrag %s wurde entfernt"; +$lang['success']['relayhost_added'] = "Mapeintrag %s wurde hinzugefügt"; $lang['diagnostics']['dns_records'] = 'DNS-Einträge'; $lang['diagnostics']['dns_records_24hours'] = 'Bitte beachten Sie, dass es bis zu 24 Stunden dauern kann, bis Änderungen an Ihren DNS-Einträgen als aktueller Status auf dieser Seite dargestellt werden. Diese Seite ist nur als Hilfsmittel gedacht, um die korrekten Werte für DNS-Einträge anzuzeigen und zu überprüfen, ob die Daten im DNS hinterlegt sind.'; $lang['diagnostics']['dns_records_name'] = 'Name'; diff --git a/data/web/lang/lang.en.php b/data/web/lang/lang.en.php index 4191446a..887385c7 100644 --- a/data/web/lang/lang.en.php +++ b/data/web/lang/lang.en.php @@ -30,6 +30,7 @@ $lang['success']['verified_u2f_login'] = "Verified U2F login"; $lang['success']['verified_yotp_login'] = "Verified Yubico OTP login"; $lang['danger']['yotp_verification_failed'] = "Yubico OTP verification failed: %s"; $lang['danger']['ip_list_empty'] = "List of allowed IPs cannot be empty"; +$lang['danger']['invalid_destination'] = "Destination format is invalid"; $lang['danger']['rspamd_ui_pw_length'] = "Rspamd UI password should be at least 6 chars long"; $lang['success']['rspamd_ui_pw_set'] = "Rspamd UI password successfully set"; $lang['success']['queue_command_success'] = "Queue command completed successfully"; @@ -66,7 +67,7 @@ $lang['success']['settings_map_added'] = "Added settings map entry"; $lang['danger']['settings_map_invalid'] = "Settings map ID %s invalid"; $lang['success']['settings_map_removed'] = "Removed settings map ID %s"; $lang['danger']['invalid_host'] = "Invalid host specified: %s"; -$lang['danger']['relayhost_invalid'] = "Relayhost %s is invalid"; +$lang['danger']['relayhost_invalid'] = "Map entry %s is invalid"; $lang['success']['saved_settings'] = "Saved settings"; $lang['success']['db_init_complete'] = "Database initialization completed"; @@ -405,7 +406,10 @@ $lang['acl']['prohibited'] = 'Prohibited by ACL'; $lang['add']['generate'] = 'generate'; $lang['add']['syncjob'] = 'Add sync job'; $lang['add']['syncjob_hint'] = 'Be aware that passwords need to be saved plain-text!'; -$lang['add']['hostname'] = 'Hostname'; +$lang['add']['hostname'] = 'Host'; +$lang['add']['destination'] = 'Ziel'; +$lang['add']['nexthop'] = 'Next hop'; +$lang['edit']['nexthop'] = 'Next hop'; $lang['add']['port'] = 'Port'; $lang['add']['username'] = 'Username'; $lang['add']['enc_method'] = 'Encryption method'; @@ -596,16 +600,28 @@ $lang['admin']['in_use_by'] = 'In use by'; $lang['admin']['forwarding_hosts'] = 'Forwarding Hosts'; $lang['admin']['forwarding_hosts_hint'] = 'Incoming messages are unconditionally accepted from any hosts listed here. These hosts are then not checked against DNSBLs or subjected to greylisting. Spam received from them is never rejected, but optionally it can be filed into the Junk folder. The most common use for this is to specify mail servers on which you have set up a rule that forwards incoming emails to your mailcow server.'; $lang['admin']['forwarding_hosts_add_hint'] = 'You can either specify IPv4/IPv6 addresses, networks in CIDR notation, host names (which will be resolved to IP addresses), or domain names (which will be resolved to IP addresses by querying SPF records or, in their absence, MX records).'; -$lang['admin']['relayhosts_hint'] = 'Define relayhosts here to be able to select them in a domains configuration dialog.'; -$lang['admin']['add_relayhost_add_hint'] = 'Please be aware that relayhost authentication data will be stored as plain text.'; +$lang['admin']['relayhosts_hint'] = 'Define sender-dependent transports to be able to select them in a domains configuration dialog.
+ The transport service is always "smtp:". A users individual outbound TLS policy setting is taken into account.'; +$lang['admin']['transports_hint'] = 'A transport map entry overrules a sender-dependent transport map.'; +$lang['admin']['add_relayhost_hint'] = 'Please be aware that authentication data, if any, will be stored as plain text.'; +$lang['admin']['add_transports_hint'] = 'Please be aware that authentication data, if any, will be stored as plain text.'; $lang['admin']['host'] = 'Host'; $lang['admin']['source'] = 'Source'; -$lang['admin']['add_forwarding_host'] = 'Add Forwarding Host'; -$lang['admin']['add_relayhost'] = 'Add Relayhost'; +$lang['admin']['add_forwarding_host'] = 'Add forwarding host'; +$lang['admin']['add_relayhost'] = 'Add sender-dependent transport'; +$lang['admin']['add_transport'] = 'Add transport'; +$lang['admin']['relayhosts'] = 'Sender-dependent transports'; +$lang['admin']['transport_maps'] = 'Transport Maps'; +$lang['admin']['routing'] = 'Routing'; +$lang['admin']['credentials_transport_warning'] = 'Warning: Adding a new transport map entry will update the credentials for all entries with a matching nexthop column.'; + +$lang['admin']['destination'] = 'Destination'; +$lang['admin']['nexthop'] = 'Next hop'; + $lang['success']['forwarding_host_removed'] = "Forwarding host %s has been removed"; $lang['success']['forwarding_host_added'] = "Forwarding host %s has been added"; -$lang['success']['relayhost_removed'] = "Relayhost %s has been removed"; -$lang['success']['relayhost_added'] = "Relayhost %s has been added"; +$lang['success']['relayhost_removed'] = "Map entry %s has been removed"; +$lang['success']['relayhost_added'] = "Map entry %s has been added"; $lang['diagnostics']['dns_records'] = 'DNS Records'; $lang['diagnostics']['dns_records_24hours'] = 'Please note that changes made to DNS may take up to 24 hours to correctly have their current state reflected on this page. It is intended as a way for you to easily see how to configure your DNS records and to check whether all your records are correctly stored in DNS.'; $lang['diagnostics']['dns_records_name'] = 'Name'; diff --git a/data/web/modals/admin.php b/data/web/modals/admin.php index 5a995a76..2303519e 100644 --- a/data/web/modals/admin.php +++ b/data/web/modals/admin.php @@ -151,17 +151,18 @@ if (!isset($_SESSION['mailcow_cc_role'])) {
- -