diff --git a/data/Dockerfiles/postfix/postfix.sh b/data/Dockerfiles/postfix/postfix.sh index ac96c0ce..d89d1150 100755 --- a/data/Dockerfiles/postfix/postfix.sh +++ b/data/Dockerfiles/postfix/postfix.sh @@ -38,6 +38,23 @@ for cert_dir in /etc/ssl/mail/*/ ; do done postmap -F hash:/opt/postfix/conf/sni.map; +cat < /opt/postfix/conf/sql/mysql_relay_ne.cf +# Autogenerated by mailcow +user = ${DBUSER} +password = ${DBPASS} +hosts = unix:/var/run/mysqld/mysqld.sock +dbname = ${DBNAME} +query = SELECT IF(EXISTS(SELECT address, domain FROM alias + WHERE address = '%s' + AND domain IN ( + SELECT domain FROM domain + WHERE backupmx = '1' + AND relay_all_recipients = '1' + AND relay_unknown_only = '1') + + ), 'lmtp:inet:dovecot:24', NULL) AS 'transport' +EOF + cat < /opt/postfix/conf/sql/mysql_relay_recipient_maps.cf # Autogenerated by mailcow user = ${DBUSER} diff --git a/data/conf/postfix/main.cf b/data/conf/postfix/main.cf index f09dbfb6..565edd39 100644 --- a/data/conf/postfix/main.cf +++ b/data/conf/postfix/main.cf @@ -186,6 +186,7 @@ mail_name = Postcow # Use custom_transport.pcre for custom transports transport_maps = pcre:/opt/postfix/conf/custom_transport.pcre, pcre:/opt/postfix/conf/local_transport, + proxy:mysql:/opt/postfix/conf/sql/mysql_relay_ne.cf, proxy:mysql:/opt/postfix/conf/sql/mysql_transport_maps.cf smtp_sasl_auth_soft_bounce = no postscreen_discard_ehlo_keywords = silent-discard, dsn diff --git a/data/web/admin.php b/data/web/admin.php index 4a72936c..82d69b4c 100644 --- a/data/web/admin.php +++ b/data/web/admin.php @@ -137,7 +137,10 @@ if (!isset($_SESSION['gal']) && $license_cache = $redis->Get('LICENSE_STATUS_CAC
- +
+ Read-Write + +
diff --git a/data/web/edit.php b/data/web/edit.php index 70bcfb9e..b75a2317 100644 --- a/data/web/edit.php +++ b/data/web/edit.php @@ -25,7 +25,7 @@ if (isset($_SESSION['mailcow_cc_role'])) { if (!empty($result)) { ?>

-
+
@@ -96,7 +96,7 @@ if (isset($_SESSION['mailcow_cc_role'])) { if (!empty($result)) { ?>

-
+
@@ -194,7 +194,7 @@ if (isset($_SESSION['mailcow_cc_role'])) { if (!empty($result)) { ?>

-
+
@@ -258,6 +258,7 @@ if (isset($_SESSION['mailcow_cc_role'])) { +
@@ -317,9 +318,13 @@ if (isset($_SESSION['mailcow_cc_role'])) {
-
+

+ +
+

+
@@ -576,7 +581,7 @@ if (isset($_SESSION['mailcow_cc_role'])) {
@@ -945,7 +950,7 @@ if (isset($_SESSION['mailcow_cc_role'])) { if (!empty($result)) { ?>

-
+
@@ -996,7 +1001,7 @@ if (isset($_SESSION['mailcow_cc_role'])) { if (!empty($result)) { ?>

:

-
+
@@ -1042,7 +1047,7 @@ if (isset($_SESSION['mailcow_cc_role'])) { if (!empty($result)) { ?>

:

-
+
diff --git a/data/web/inc/functions.mailbox.inc.php b/data/web/inc/functions.mailbox.inc.php index deaebbbc..a77d734a 100644 --- a/data/web/inc/functions.mailbox.inc.php +++ b/data/web/inc/functions.mailbox.inc.php @@ -446,9 +446,16 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { } $active = intval($_data['active']); $relay_all_recipients = intval($_data['relay_all_recipients']); + $relay_unknown_only = intval($_data['relay_unknown_only']); $backupmx = intval($_data['backupmx']); $gal = intval($_data['gal']); - ($relay_all_recipients == 1) ? $backupmx = '1' : null; + if ($relay_all_recipients == 1) { + $backupmx = '1'; + } + if ($relay_unknown_only == 1) { + $backupmx = 1; + $relay_all_recipients = 1; + } if (!is_valid_domain_name($domain)) { $_SESSION['return'][] = array( 'type' => 'danger', @@ -495,8 +502,8 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { $stmt->execute(array( ':domain' => '%@' . $domain )); - $stmt = $pdo->prepare("INSERT INTO `domain` (`domain`, `description`, `aliases`, `mailboxes`, `defquota`, `maxquota`, `quota`, `backupmx`, `gal`, `active`, `relay_all_recipients`) - VALUES (:domain, :description, :aliases, :mailboxes, :defquota, :maxquota, :quota, :backupmx, :gal, :active, :relay_all_recipients)"); + $stmt = $pdo->prepare("INSERT INTO `domain` (`domain`, `description`, `aliases`, `mailboxes`, `defquota`, `maxquota`, `quota`, `backupmx`, `gal`, `active`, `relay_unknown_only`, `relay_all_recipients`) + VALUES (:domain, :description, :aliases, :mailboxes, :defquota, :maxquota, :quota, :backupmx, :gal, :active, :relay_unknown_only, :relay_all_recipients)"); $stmt->execute(array( ':domain' => $domain, ':description' => $description, @@ -508,6 +515,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { ':backupmx' => $backupmx, ':gal' => $gal, ':active' => $active, + ':relay_unknown_only' => $relay_unknown_only, ':relay_all_recipients' => $relay_all_recipients )); try { @@ -1802,8 +1810,8 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { ); continue; } + $domain = idn_to_ascii(substr(strstr($address, '@'), 1), 0, INTL_IDNA_VARIANT_UTS46); if ($is_now['address'] != $address) { - $domain = idn_to_ascii(substr(strstr($address, '@'), 1), 0, INTL_IDNA_VARIANT_UTS46); $local_part = strstr($address, '@', true); $address = $local_part.'@'.$domain; if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain)) { @@ -1919,6 +1927,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { `address` = :address, `public_comment` = :public_comment, `private_comment` = :private_comment, + `domain` = :domain, `goto` = :goto, `sogo_visible`= :sogo_visible, `active`= :active @@ -1927,6 +1936,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { ':address' => $address, ':public_comment' => $public_comment, ':private_comment' => $private_comment, + ':domain' => $domain, ':goto' => $goto, ':sogo_visible' => $sogo_visible, ':active' => $active, @@ -1995,6 +2005,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { $backupmx = (isset($_data['backupmx'])) ? intval($_data['backupmx']) : $is_now['backupmx_int']; $gal = (isset($_data['gal'])) ? intval($_data['gal']) : $is_now['gal_int']; $relay_all_recipients = (isset($_data['relay_all_recipients'])) ? intval($_data['relay_all_recipients']) : $is_now['relay_all_recipients_int']; + $relay_unknown_only = (isset($_data['relay_unknown_only'])) ? intval($_data['relay_unknown_only']) : $is_now['relay_unknown_only_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 = (isset($_data['mailboxes']) && $_data['mailboxes'] != '') ? intval($_data['mailboxes']) : $is_now['max_num_mboxes_for_domain']; @@ -2002,7 +2013,13 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { $maxquota = (!empty($_data['maxquota'])) ? $_data['maxquota'] : ($is_now['max_quota_for_mbox'] / 1048576); $quota = (!empty($_data['quota'])) ? $_data['quota'] : ($is_now['max_quota_for_domain'] / 1048576); $description = (!empty($_data['description'])) ? $_data['description'] : $is_now['description']; - ($relay_all_recipients == '1') ? $backupmx = '1' : null; + if ($relay_all_recipients == '1') { + $backupmx = '1'; + } + if ($relay_unknown_only == '1') { + $backupmx = '1'; + $relay_all_recipients = '1'; + } } else { $_SESSION['return'][] = array( @@ -2096,6 +2113,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { } $stmt = $pdo->prepare("UPDATE `domain` SET `relay_all_recipients` = :relay_all_recipients, + `relay_unknown_only` = :relay_unknown_only, `backupmx` = :backupmx, `gal` = :gal, `active` = :active, @@ -2109,6 +2127,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { WHERE `domain` = :domain"); $stmt->execute(array( ':relay_all_recipients' => $relay_all_recipients, + ':relay_unknown_only' => $relay_unknown_only, ':backupmx' => $backupmx, ':gal' => $gal, ':active' => $active, @@ -3178,10 +3197,12 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { `quota`, `relayhost`, `relay_all_recipients` as `relay_all_recipients_int`, + `relay_unknown_only` as `relay_unknown_only_int`, `backupmx` as `backupmx_int`, `gal` as `gal_int`, `active` as `active_int`, CASE `relay_all_recipients` WHEN 1 THEN '".$lang['mailbox']['yes']."' ELSE '".$lang['mailbox']['no']."' END AS `relay_all_recipients`, + CASE `relay_unknown_only` WHEN 1 THEN '".$lang['mailbox']['yes']."' ELSE '".$lang['mailbox']['no']."' END AS `relay_unknown_only`, CASE `backupmx` WHEN 1 THEN '".$lang['mailbox']['yes']."' ELSE '".$lang['mailbox']['no']."' END AS `backupmx`, CASE `gal` WHEN 1 THEN '".$lang['mailbox']['yes']."' ELSE '".$lang['mailbox']['no']."' END AS `gal`, CASE `active` WHEN 1 THEN '".$lang['mailbox']['yes']."' ELSE '".$lang['mailbox']['no']."' END AS `active` @@ -3228,7 +3249,9 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { $domaindata['active'] = $row['active']; $domaindata['active_int'] = $row['active_int']; $domaindata['relay_all_recipients'] = $row['relay_all_recipients']; + $domaindata['relay_unknown_only'] = $row['relay_unknown_only']; $domaindata['relay_all_recipients_int'] = $row['relay_all_recipients_int']; + $domaindata['relay_unknown_only_int'] = $row['relay_unknown_only_int']; $stmt = $pdo->prepare("SELECT COUNT(*) AS `alias_count` FROM `alias` WHERE (`domain`= :domain OR `domain` IN (SELECT `alias_domain` FROM `alias_domain` WHERE `target_domain` = :domain2)) AND `address` NOT IN ( diff --git a/data/web/inc/init_db.inc.php b/data/web/inc/init_db.inc.php index 84c2ae5a..f122b220 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 = "05032020_0715"; + $db_version = "03042020_0915"; $stmt = $pdo->query("SHOW TABLES LIKE 'versions'"); $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); @@ -181,6 +181,7 @@ function init_db_schema() { "skip_ip_check" => "TINYINT(1) NOT NULL DEFAULT '0'", "created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)", "modified" => "DATETIME ON UPDATE NOW(0)", + "access" => "ENUM('ro', 'rw') NOT NULL DEFAULT 'rw'", "active" => "TINYINT(1) NOT NULL DEFAULT '1'" ), "keys" => array( @@ -218,6 +219,7 @@ function init_db_schema() { "backupmx" => "TINYINT(1) NOT NULL DEFAULT '0'", "gal" => "TINYINT(1) NOT NULL DEFAULT '1'", "relay_all_recipients" => "TINYINT(1) NOT NULL DEFAULT '0'", + "relay_unknown_only" => "TINYINT(1) NOT NULL DEFAULT '0'", "created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)", "modified" => "DATETIME ON UPDATE CURRENT_TIMESTAMP", "active" => "TINYINT(1) NOT NULL DEFAULT '1'" diff --git a/data/web/lang/lang.de.json b/data/web/lang/lang.de.json index b0412874..9a8983f5 100644 --- a/data/web/lang/lang.de.json +++ b/data/web/lang/lang.de.json @@ -742,7 +742,9 @@ "backup_mx_options": "Backup MX Optionen", "relay_domain": "Diese Domain relayen", "relay_all": "Alle Empfänger-Adressen relayen", - "relay_all_info": "Wenn nicht alle Empfänger-Adressen relayt werden sollen, müssen \"blinde\" Mailboxen für jede Adresse, die relayt werden soll, erstellen werden.", + "relay_all_info": "↪ Wenn nicht alle Empfänger-Adressen relayt werden sollen, müssen \"blinde\" Mailboxen für jede Adresse, die relayt werden soll, erstellen werden.", + "relay_unknown_only": "Nur nicht-lokale Mailboxen relayen. Existente Mailboxen werden weiterhin lokal zugestellt.", + "relay_transport_info": "
Info
Transport Maps können erstellt werden, um individuelle Ziele für eine Relay Domain zu definieren.", "full_name": "Voller Name", "quota_mb": "Speicherplatz (MiB)", "sender_acl": "Darf Nachrichten versenden als", @@ -821,8 +823,10 @@ "domain_quota_m": "Domain Speicherplatz gesamt (MiB)", "backup_mx_options": "Backup MX Optionen", "relay_all": "Alle Empfänger-Adressen relayen", - "relay_domain": "Relay Domain", - "relay_all_info": "Wenn Sie nicht alle Empfänger-Adressen relayen möchten, müssen Sie eine Mailbox für jede Adresse, die relayt werden soll, erstellen.", + "relay_domain": "Diese Domain relayen", + "relay_all_info": "↪ Wenn nicht alle Empfänger-Adressen relayt werden sollen, müssen \"blinde\" Mailboxen für jede Adresse, die relayt werden soll, erstellen werden.", + "relay_unknown_only": "Nur nicht-lokale Mailboxen relayen. Existente Mailboxen werden weiterhin lokal zugestellt.", + "relay_transport_info": "
Info
Transport Maps können erstellt werden, um individuelle Ziele für eine Relay Domain zu definieren.", "alias_address": "Alias-Adresse(n)", "alias_address_info": "Vollständige E-Mail-Adresse(n) oder @example.com, um alle Nachrichten einer Domain weiterzuleiten. Getrennt durch Komma. Nur eigene Domains.", "alias_domain_info": "Nur gültige Domains. Getrennt durch Komma.", diff --git a/data/web/lang/lang.en.json b/data/web/lang/lang.en.json index f75d8b09..c91e8213 100644 --- a/data/web/lang/lang.en.json +++ b/data/web/lang/lang.en.json @@ -739,9 +739,11 @@ "max_quota": "Max. quota per mailbox (MiB)", "domain_quota": "Domain quota", "backup_mx_options": "Backup MX options", - "relay_domain": "Relay domain", + "relay_domain": "Relay this domain", "relay_all": "Relay all recipients", - "relay_all_info": "If you choose not to relay all recipients, you will need to add a (\"blind\") mailbox for every single recipient that should be relayed.", + "relay_all_info": "↪ If you choose not to relay all recipients, you will need to add a (\"blind\") mailbox for every single recipient that should be relayed.", + "relay_unknown_only": "Relay non-existing mailboxes only. Existing mailboxes will be delivered locally.", + "relay_transport_info": "
Info
You can define transport maps for a custom destination for this domain. If not set, a MX lookup will be made.", "full_name": "Full name", "quota_mb": "Quota (MiB)", "sender_acl": "Allow to send as", @@ -821,7 +823,9 @@ "backup_mx_options": "Backup MX options", "relay_all": "Relay all recipients", "relay_domain": "Relay this domain", - "relay_all_info": "If you choose not to relay all recipients, you will need to add a (\"blind\") mailbox for every single recipient that should be relayed.", + "relay_all_info": "↪ If you choose not to relay all recipients, you will need to add a (\"blind\") mailbox for every single recipient that should be relayed.", + "relay_unknown_only": "Relay non-existing mailboxes only. Existing mailboxes will be delivered locally.", + "relay_transport_info": "
Info
You can define transport maps for a custom destination for this domain. If not set, a MX lookup will be made.", "alias_address": "Alias address/es", "alias_address_info": "Full email address/es or @example.com, to catch all messages for a domain (comma-separated). mailcow domains only.", "alias_domain_info": "Valid domain names only (comma-separated).", diff --git a/data/web/modals/mailbox.php b/data/web/modals/mailbox.php index b86d185c..b3f4b8a5 100644 --- a/data/web/modals/mailbox.php +++ b/data/web/modals/mailbox.php @@ -45,6 +45,7 @@ if (!isset($_SESSION['mailcow_cc_role'])) {
0 = ∞ +
@@ -162,10 +163,14 @@ if (!isset($_SESSION['mailcow_cc_role'])) {
- -
- -

+ +
+ +

+ +
+

+
diff --git a/docker-compose.yml b/docker-compose.yml index 4ad98a57..b03438d2 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -246,7 +246,7 @@ services: - dovecot postfix-mailcow: - image: mailcow/postfix:1.46 + image: mailcow/postfix:1.47 depends_on: - mysql-mailcow volumes: