diff --git a/data/Dockerfiles/postfix/postfix.sh b/data/Dockerfiles/postfix/postfix.sh index 640538b0..9837eeef 100755 --- a/data/Dockerfiles/postfix/postfix.sh +++ b/data/Dockerfiles/postfix/postfix.sh @@ -20,12 +20,26 @@ dbname = ${DBNAME} query = SELECT IF( EXISTS( SELECT 'TLS_ACTIVE' FROM alias LEFT OUTER JOIN mailbox ON mailbox.username = alias.goto WHERE (address='%s' OR address IN (SELECT CONCAT('%u', '@', target_domain) FROM alias_domain WHERE alias_domain='%d')) AND mailbox.tls_enforce_in = '1' AND mailbox.active = '1'), 'reject_plaintext_session', NULL) AS 'tls_enforce_in'; EOF -cat < /opt/postfix/conf/sql/mysql_tls_enforce_out_policy.cf +cat < /opt/postfix/conf/sql/mysql_sender_dependent_default_transport_maps.cf user = ${DBUSER} password = ${DBPASS} hosts = mysql dbname = ${DBNAME} -query = SELECT IF( EXISTS( SELECT 'TLS_ACTIVE' FROM alias LEFT OUTER JOIN mailbox ON mailbox.username = alias.goto WHERE (address='%s' OR address IN (SELECT CONCAT('%u', '@', target_domain) FROM alias_domain WHERE alias_domain='%d')) AND mailbox.tls_enforce_out = '1' AND mailbox.active = '1'), 'smtp_enforced_tls:', NULL) AS 'tls_enforce_out'; +query = SELECT GROUP_CONCAT(transport SEPARATOR '') AS transport_maps + FROM ( + SELECT IF(EXISTS(SELECT 'smtp_type' FROM alias LEFT OUTER JOIN mailbox ON mailbox.username = alias.goto WHERE (address = '%s' OR address IN (SELECT CONCAT('%u', '@', target_domain) FROM alias_domain WHERE alias_domain = '%d')) AND mailbox.tls_enforce_out = '1' AND mailbox.active = '1'), 'smtp_enforced_tls:', 'smtp:') AS 'transport' + UNION ALL + SELECT hostname AS transport FROM relayhosts LEFT OUTER JOIN domain ON domain.relayhost = relayhosts.id WHERE relayhosts.active = '1' AND domain = '%d' OR domain IN (SELECT target_domain FROM alias_domain WHERE alias_domain = '%d') + ) + AS transport_view; +EOF + +cat < /opt/postfix/conf/sql/mysql_sasl_passwd_maps.cf +user = ${DBUSER} +password = ${DBPASS} +hosts = mysql +dbname = ${DBNAME} +query = SELECT CONCAT_WS(':', username, password) AS auth_data FROM relayhosts WHERE id IN (SELECT relayhost FROM domain WHERE CONCAT('@', domain) = '%s'); EOF cat < /opt/postfix/conf/sql/mysql_virtual_alias_domain_catchall_maps.cf @@ -110,6 +124,5 @@ if [[ $? != 0 ]]; then exit 1 else postfix -c /opt/postfix/conf start - supervisorctl restart postfix-maillog sleep 126144000 fi diff --git a/data/conf/postfix/main.cf b/data/conf/postfix/main.cf index 82bfae85..fe6fd3ab 100644 --- a/data/conf/postfix/main.cf +++ b/data/conf/postfix/main.cf @@ -39,11 +39,11 @@ postscreen_greet_ttl = 2d postscreen_greet_wait = 3s postscreen_non_smtp_command_enable = no postscreen_pipelining_enable = no -proxy_read_maps = proxy:mysql:/opt/postfix/conf/sql/mysql_virtual_sender_acl.cf, proxy:mysql:/opt/postfix/conf/sql/mysql_tls_enforce_out_policy.cf, proxy:mysql:/opt/postfix/conf/sql/mysql_tls_enforce_in_policy.cf, $local_recipient_maps $mydestination $virtual_alias_maps $virtual_alias_domains $virtual_mailbox_maps $virtual_mailbox_domains $relay_recipient_maps $relay_domains $canonical_maps $sender_canonical_maps $recipient_canonical_maps $relocated_maps $transport_maps $mynetworks $smtpd_sender_login_maps +proxy_read_maps = proxy:mysql:/opt/postfix/conf/sql/mysql_virtual_sender_acl.cf, proxy:mysql:/opt/postfix/conf/sql/mysql_sender_dependent_default_transport_maps.cf, proxy:mysql:/opt/postfix/conf/sql/mysql_sasl_passwd_maps.cf, proxy:mysql:/opt/postfix/conf/sql/mysql_tls_enforce_in_policy.cf, $local_recipient_maps $mydestination $virtual_alias_maps $virtual_alias_domains $virtual_mailbox_maps $virtual_mailbox_domains $relay_recipient_maps $relay_domains $canonical_maps $sender_canonical_maps $recipient_canonical_maps $relocated_maps $transport_maps $mynetworks $smtpd_sender_login_maps queue_run_delay = 300s relay_domains = proxy:mysql:/opt/postfix/conf/sql/mysql_virtual_relay_domain_maps.cf relay_recipient_maps = proxy:mysql:/opt/postfix/conf/sql/mysql_relay_recipient_maps.cf -sender_dependent_default_transport_maps = proxy:mysql:/opt/postfix/conf/sql/mysql_tls_enforce_out_policy.cf +sender_dependent_default_transport_maps = proxy:mysql:/opt/postfix/conf/sql/mysql_sender_dependent_default_transport_maps.cf smtp_tls_CAfile = /etc/ssl/certs/ca-certificates.crt smtp_tls_cert_file = /etc/ssl/mail/cert.pem smtp_tls_key_file = /etc/ssl/mail/key.pem @@ -94,3 +94,8 @@ mydestination = localhost.localdomain, localhost #content_filter=zeyple # Prefere IPv4, useful for v4-only envs smtp_address_preference = ipv4 +smtp_sender_dependent_authentication = yes +smtp_sasl_auth_enable = yes +smtp_sasl_password_maps = proxy:mysql:/opt/postfix/conf/sql/mysql_sasl_passwd_maps.cf +smtp_sasl_security_options = +smtp_sasl_mechanism_filter = plain, login diff --git a/data/web/admin.php b/data/web/admin.php index 284bf31b..bb53e61e 100644 --- a/data/web/admin.php +++ b/data/web/admin.php @@ -121,6 +121,18 @@ $tfa_data = get_tfa();
+
+ +
+
@@ -254,6 +266,7 @@ XYZ
+
@@ -291,6 +304,7 @@ XYZ
+
@@ -318,6 +332,48 @@ XYZ
+ + +
+
Relayhosts
+
+

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

+
+
+ + +
+
+ + +
+
+ + +
+ +
+
+
+
+
diff --git a/data/web/css/admin.css b/data/web/css/admin.css index 7f8897a0..aa07d7a3 100644 --- a/data/web/css/admin.css +++ b/data/web/css/admin.css @@ -41,4 +41,15 @@ body.modal-open { -moz-transform:rotateX(180deg); -webkit-transform:rotateX(180deg); transform:rotateX(180deg); -} \ No newline at end of file +} +.anchor { + display: block; + height: 65px; + margin-top: -65px; + visibility: hidden; +} +.scrollboxFixed { + position: fixed; + top: 65px; + z-index: 1; +} diff --git a/data/web/edit.php b/data/web/edit.php index 1f250535..eb0eb375 100644 --- a/data/web/edit.php +++ b/data/web/edit.php @@ -138,6 +138,7 @@ if (isset($_SESSION['mailcow_cc_role']) && ($_SESSION['mailcow_cc_role'] == "adm $domain = $_GET["domain"]; $result = mailbox('get', 'domain_details', $domain); $rl = mailbox('get', 'domain_ratelimit', $domain); + $rlyhosts = relayhost('get'); if (!empty($result)) { ?>

@@ -178,6 +179,21 @@ if (isset($_SESSION['mailcow_cc_role']) && ($_SESSION['mailcow_cc_role'] == "adm
+
+ +
+ +
+
diff --git a/data/web/inc/functions.mailbox.inc.php b/data/web/inc/functions.mailbox.inc.php index c81f3a88..3f302783 100644 --- a/data/web/inc/functions.mailbox.inc.php +++ b/data/web/inc/functions.mailbox.inc.php @@ -260,8 +260,8 @@ function mailbox($_action, $_type, $_data = null) { return false; } try { - $stmt = $pdo->prepare("INSERT INTO `domain` (`domain`, `description`, `aliases`, `mailboxes`, `maxquota`, `quota`, `transport`, `backupmx`, `active`, `relay_all_recipients`) - VALUES (:domain, :description, :aliases, :mailboxes, :maxquota, :quota, 'virtual', :backupmx, :active, :relay_all_recipients)"); + $stmt = $pdo->prepare("INSERT INTO `domain` (`domain`, `description`, `aliases`, `mailboxes`, `maxquota`, `quota`, `backupmx`, `active`, `relay_all_recipients`) + VALUES (:domain, :description, :aliases, :mailboxes, :maxquota, :quota, :backupmx, :active, :relay_all_recipients)"); $stmt->execute(array( ':domain' => $domain, ':description' => $description, @@ -1441,6 +1441,7 @@ function mailbox($_action, $_type, $_data = null) { $active = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active_int']; $backupmx = (isset($_data['backupmx'])) ? intval($_data['backupmx']) : $is_now['backupmx_int']; $relay_all_recipients = (isset($_data['relay_all_recipients'])) ? intval($_data['relay_all_recipients']) : $is_now['relay_all_recipients_int']; + $relayhost = (isset($_data['relayhost'])) ? intval($_data['relayhost']) : $is_now['relayhost']; $aliases = (!empty($_data['aliases'])) ? $_data['aliases'] : $is_now['max_num_aliases_for_domain']; $mailboxes = (!empty($_data['mailboxes'])) ? $_data['mailboxes'] : $is_now['max_num_mboxes_for_domain']; $maxquota = (!empty($_data['maxquota'])) ? $_data['maxquota'] : ($is_now['max_quota_for_mbox'] / 1048576); @@ -1531,6 +1532,7 @@ function mailbox($_action, $_type, $_data = null) { `active` = :active, `quota` = :quota, `maxquota` = :maxquota, + `relayhost` = :relayhost, `mailboxes` = :mailboxes, `aliases` = :aliases, `description` = :description @@ -1541,6 +1543,7 @@ function mailbox($_action, $_type, $_data = null) { ':active' => $active, ':quota' => $quota, ':maxquota' => $maxquota, + ':relayhost' => $relayhost, ':mailboxes' => $mailboxes, ':aliases' => $aliases, ':description' => $description, @@ -2470,7 +2473,7 @@ function mailbox($_action, $_type, $_data = null) { ':domain' => $_data )); $row = $stmt->fetch(PDO::FETCH_ASSOC); - if (!empty($row)) { + if (!empty($row)) { $_data = $row['target_domain']; } $stmt = $pdo->prepare("SELECT @@ -2480,6 +2483,7 @@ function mailbox($_action, $_type, $_data = null) { `mailboxes`, `maxquota`, `quota`, + `relayhost`, `relay_all_recipients` as `relay_all_recipients_int`, `backupmx` as `backupmx_int`, `active` as `active_int`, @@ -2514,6 +2518,7 @@ function mailbox($_action, $_type, $_data = null) { $domaindata['max_num_mboxes_for_domain'] = $row['mailboxes']; $domaindata['max_quota_for_mbox'] = $row['maxquota'] * 1048576; $domaindata['max_quota_for_domain'] = $row['quota'] * 1048576; + $domaindata['relayhost'] = $row['relayhost']; $domaindata['backupmx'] = $row['backupmx']; $domaindata['backupmx_int'] = $row['backupmx_int']; $domaindata['active'] = $row['active']; diff --git a/data/web/inc/functions.relayhost.inc.php b/data/web/inc/functions.relayhost.inc.php new file mode 100644 index 00000000..249dacc3 --- /dev/null +++ b/data/web/inc/functions.relayhost.inc.php @@ -0,0 +1,179 @@ + 'danger', + 'msg' => sprintf($lang['danger']['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', + 'msg' => 'Invalid host specified: '. 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' => $password, + ':active' => '1' + )); + } + catch (PDOException $e) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => 'MySQL: '.$e + ); + return false; + } + $_SESSION['return'] = array( + 'type' => 'success', + 'msg' => sprintf($lang['success']['relayhost_added'], htmlspecialchars(implode(', ', $hosts))) + ); + break; + case 'edit': + if ($_SESSION['mailcow_cc_role'] != "admin") { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['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 = (!empty($_data['username'])) ? trim($_data['username']) : $is_now['username']; + $password = (!empty($_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', + 'msg' => 'Relayhost invalid' + ); + return false; + } + 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', + 'msg' => 'MySQL: '.$e + ); + return false; + } + } + $_SESSION['return'] = array( + 'type' => 'success', + 'msg' => sprintf($lang['success']['object_modified'], htmlspecialchars(implode(', ', $hostnames))) + ); + break; + case 'delete': + if ($_SESSION['mailcow_cc_role'] != "admin") { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['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', + 'msg' => 'MySQL: '.$e + ); + return false; + } + } + $_SESSION['return'] = array( + 'type' => 'success', + 'msg' => sprintf($lang['success']['relayhost_removed'], htmlspecialchars(implode(', ', $hostnames))) + ); + break; + case 'get': + if ($_SESSION['mailcow_cc_role'] != "admin") { + return false; + } + $relayhosts = array(); + try { + $stmt = $pdo->query("SELECT `id`, `hostname`, `username` FROM `relayhosts`"); + $relayhosts = $stmt->fetchAll(PDO::FETCH_ASSOC); + } + catch(PDOException $e) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => 'MySQL: '.$e + ); + } + return $relayhosts; + break; + case 'details': + if ($_SESSION['mailcow_cc_role'] != "admin" || !isset($_data)) { + return false; + } + $relayhostdata = array(); + try { + $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; + } + } + catch(PDOException $e) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => 'MySQL: '.$e + ); + } + return $relayhostdata; + break; + } +} \ No newline at end of file diff --git a/data/web/inc/header.inc.php b/data/web/inc/header.inc.php index 533711de..98d42ef1 100644 --- a/data/web/inc/header.inc.php +++ b/data/web/inc/header.inc.php @@ -29,7 +29,7 @@ - +