diff --git a/data/Dockerfiles/postfix/postfix.sh b/data/Dockerfiles/postfix/postfix.sh index 326a6d18..5e60bb18 100755 --- a/data/Dockerfiles/postfix/postfix.sh +++ b/data/Dockerfiles/postfix/postfix.sh @@ -119,6 +119,28 @@ query = SELECT goto FROM alias AND active='1'; EOF +cat < /opt/postfix/conf/sql/mysql_recipient_bcc_maps.cf +user = ${DBUSER} +password = ${DBPASS} +hosts = mysql +dbname = ${DBNAME} +query = SELECT bcc_dest FROM bcc_maps + WHERE local_dest='%s' + AND type='rcpt' + AND active='1'; +EOF + +cat < /opt/postfix/conf/sql/mysql_sender_bcc_maps.cf +user = ${DBUSER} +password = ${DBPASS} +hosts = mysql +dbname = ${DBNAME} +query = SELECT bcc_dest FROM bcc_maps + WHERE local_dest='%s' + AND type='sender' + AND active='1'; +EOF + cat < /opt/postfix/conf/sql/mysql_virtual_domains_maps.cf user = ${DBUSER} password = ${DBPASS} diff --git a/data/conf/postfix/main.cf b/data/conf/postfix/main.cf index edf5c6d5..4e8c577f 100644 --- a/data/conf/postfix/main.cf +++ b/data/conf/postfix/main.cf @@ -39,7 +39,27 @@ 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_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 +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, + proxy:mysql:/opt/postfix/conf/sql/mysql_sender_bcc_maps.cf, + proxy:mysql:/opt/postfix/conf/sql/mysql_recipient_bcc_maps.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 @@ -79,10 +99,15 @@ smtpd_tls_mandatory_ciphers = high smtpd_tls_security_level = may tls_ssl_options = NO_COMPRESSION tls_high_cipherlist = EDH+CAMELLIA:EDH+aRSA:EECDH+aRSA+AESGCM:EECDH+aRSA+SHA256:EECDH:+CAMELLIA128:+AES128:+SSLv3:!aNULL:!eNULL:!LOW:!3DES:!MD5:!EXP:!PSK:!DSS:!RC4:!SEED:!IDEA:!ECDSA:kEDH:CAMELLIA128-SHA:AES128-SHA -virtual_alias_maps = proxy:mysql:/opt/postfix/conf/sql/mysql_virtual_alias_maps.cf, proxy:mysql:/opt/postfix/conf/sql/mysql_virtual_spamalias_maps.cf, proxy:mysql:/opt/postfix/conf/sql/mysql_virtual_alias_domain_maps.cf, proxy:mysql:/opt/postfix/conf/sql/mysql_virtual_alias_domain_catchall_maps.cf +virtual_alias_maps = proxy:mysql:/opt/postfix/conf/sql/mysql_virtual_alias_maps.cf, + proxy:mysql:/opt/postfix/conf/sql/mysql_virtual_spamalias_maps.cf, + proxy:mysql:/opt/postfix/conf/sql/mysql_virtual_alias_domain_maps.cf, + proxy:mysql:/opt/postfix/conf/sql/mysql_virtual_alias_domain_catchall_maps.cf virtual_gid_maps = static:5000 virtual_mailbox_base = /var/vmail/ virtual_mailbox_domains = proxy:mysql:/opt/postfix/conf/sql/mysql_virtual_domains_maps.cf +recipient_bcc_maps = proxy:mysql:/opt/postfix/conf/sql/mysql_recipient_bcc_maps.cf +sender_bcc_maps = proxy:mysql:/opt/postfix/conf/sql/mysql_sender_bcc_maps.cf virtual_mailbox_maps = proxy:mysql:/opt/postfix/conf/sql/mysql_virtual_mailbox_maps.cf virtual_minimum_uid = 104 virtual_transport = lmtp:inet:dovecot:24 diff --git a/data/web/admin.php b/data/web/admin.php index 06af3db9..0d7c6466 100644 --- a/data/web/admin.php +++ b/data/web/admin.php @@ -7,7 +7,6 @@ $_SESSION['return_to'] = $_SERVER['REQUEST_URI']; $tfa_data = get_tfa(); ?>
- @@ -127,7 +128,9 @@ $tfa_data = get_tfa();
+ + Relayhosts @@ -307,6 +310,7 @@ $tfa_data = get_tfa();
+
@@ -335,6 +339,7 @@ $tfa_data = get_tfa();
+
@@ -506,6 +511,8 @@ $tfa_data = get_tfa();
+ +
Fail2ban @@ -522,6 +529,7 @@ $tfa_data = get_tfa();
+
diff --git a/data/web/edit.php b/data/web/edit.php index e06a0229..11c924b5 100644 --- a/data/web/edit.php +++ b/data/web/edit.php @@ -608,6 +608,52 @@ if (isset($_SESSION['mailcow_cc_role'])) { +

BCC map

+
+
+ +
+ +
+ + BCC destinations can only be valid email addresses. Separated by whitespace, semicolon, new line or comma. +
+
+
+ +
+ +
+
+
+
+
+ +
+
+
+
+
+ +
+
+
+ + +
'danger', + 'msg' => sprintf($lang['danger']['access_denied']) + ); + return false; + } + $local_dest = strtolower(trim($_data['local_dest'])); + $bcc_dest = array_map('trim', preg_split( "/( |,|;|\n)/", $_data['bcc_dest'])); + $active = intval($_data['active']); + $type = $_data['type']; + if ($type != 'sender' && $type != 'rcpt') { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => 'Invalid BCC map type' + ); + return false; + } + if (empty($bcc_dest)) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => 'BCC destination cannot be empty' + ); + return false; + } + if (is_valid_domain_name($local_dest)) { + if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $local_dest)) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['access_denied']) + ); + return false; + } + $domain = idn_to_ascii($local_dest); + $local_dest_sane = '@' . idn_to_ascii($local_dest); + } + elseif (filter_var($local_dest, FILTER_VALIDATE_EMAIL)) { + if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $local_dest)) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['access_denied']) + ); + return false; + } + $domain = mailbox('get', 'mailbox_details', $local_dest)['domain']; + if (empty($domain)) { + return false; + } + $local_dest_sane = $local_dest; + } + else { + return false; + } + foreach ($bcc_dest as &$bcc_dest_e) { + if (!filter_var($bcc_dest_e, FILTER_VALIDATE_EMAIL)) { + $bcc_dest_e = null;; + } + $bcc_dest_e = strtolower($bcc_dest_e); + } + $bcc_dest = array_filter($bcc_dest); + $bcc_dest = implode(",", $bcc_dest); + if (empty($bcc_dest)) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => 'BCC map destination cannot be empty' + ); + return false; + } + try { + $stmt = $pdo->prepare("SELECT `id` FROM `bcc_maps` + WHERE `local_dest` = :local_dest AND `type` = :type"); + $stmt->execute(array(':local_dest' => $local_dest_sane, ':type' => $type)); + $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); + } + catch(PDOException $e) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => 'MySQL: '.$e + ); + return false; + } + if ($num_results != 0) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => 'A BCC map entry "' . htmlspecialchars($local_dest_sane) . '" exists for type "' . $type . '"' + ); + return false; + } + try { + $stmt = $pdo->prepare("INSERT INTO `bcc_maps` (`local_dest`, `bcc_dest`, `domain`, `active`, `type`) VALUES + (:local_dest, :bcc_dest, :domain, :active, :type)"); + $stmt->execute(array( + ':local_dest' => $local_dest_sane, + ':bcc_dest' => $bcc_dest, + ':domain' => $domain, + ':active' => $active, + ':type' => $type + )); + } + catch (PDOException $e) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => 'MySQL: '.$e + ); + return false; + } + $_SESSION['return'] = array( + 'type' => 'success', + 'msg' => 'BCC map entry saved' + ); + break; + case 'edit': + if (!isset($_SESSION['acl']['bcc_maps']) || $_SESSION['acl']['bcc_maps'] != "1" ) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['access_denied']) + ); + return false; + } + $ids = (array)$_data['id']; + foreach ($ids as $id) { + $is_now = bcc('details', $id); + if (!empty($is_now)) { + $active = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active_int']; + $bcc_dest = (!empty($_data['bcc_dest'])) ? $_data['bcc_dest'] : $is_now['bcc_dest']; + $local_dest = $is_now['local_dest']; + $type = (!empty($_data['type'])) ? $_data['type'] : $is_now['type']; + } + else { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['access_denied']) + ); + return false; + } + $bcc_dest = array_map('trim', preg_split( "/( |,|;|\n)/", $bcc_dest)); + $active = intval($_data['active']); + foreach ($bcc_dest as &$bcc_dest_e) { + if (!filter_var($bcc_dest_e, FILTER_VALIDATE_EMAIL)) { + $bcc_dest_e = null;; + } + $bcc_dest_e = strtolower($bcc_dest_e); + } + $bcc_dest = array_filter($bcc_dest); + $bcc_dest = implode(",", $bcc_dest); + if (empty($bcc_dest)) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => 'BCC map destination cannot be empty' + ); + return false; + } + try { + $stmt = $pdo->prepare("SELECT `id` FROM `bcc_maps` + WHERE `local_dest` = :local_dest AND `type` = :type"); + $stmt->execute(array(':local_dest' => $local_dest, ':type' => $type)); + $id_now = $stmt->fetch(PDO::FETCH_ASSOC)['id']; + } + catch(PDOException $e) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => 'MySQL: '.$e + ); + return false; + } + if (isset($id_now) && $id_now != $id) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => 'A BCC map entry ' . htmlspecialchars($local_dest) . ' exists for this type' + ); + return false; + } + try { + $stmt = $pdo->prepare("UPDATE `bcc_maps` SET `bcc_dest` = :bcc_dest, `active` = :active, `type` = :type WHERE `id`= :id"); + $stmt->execute(array( + ':bcc_dest' => $bcc_dest, + ':active' => $active, + ':type' => $type, + ':id' => $id + )); + } + catch (PDOException $e) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => 'MySQL: '.$e + ); + return false; + } + } + $_SESSION['return'] = array( + 'type' => 'success', + 'msg' => 'BCC map entry edited' + ); + break; + case 'details': + $bccdata = array(); + $id = intval($_data); + try { + $stmt = $pdo->prepare("SELECT `id`, + `local_dest`, + `bcc_dest`, + `active` AS `active_int`, + CASE `active` WHEN 1 THEN '".$lang['mailbox']['yes']."' ELSE '".$lang['mailbox']['no']."' END AS `active`, + `type`, + `created`, + `domain`, + `modified` FROM `bcc_maps` + WHERE `id` = :id"); + $stmt->execute(array(':id' => $id)); + $bccdata = $stmt->fetch(PDO::FETCH_ASSOC); + } + catch(PDOException $e) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => 'MySQL: '.$e + ); + return false; + } + if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $bccdata['domain'])) { + $bccdata = null; + return false; + } + return $bccdata; + break; + case 'get': + $bccdata = array(); + $all_items = array(); + $id = intval($_data); + try { + $stmt = $pdo->query("SELECT `id`, `domain` FROM `bcc_maps`"); + $all_items = $stmt->fetchAll(PDO::FETCH_ASSOC); + } + catch(PDOException $e) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => 'MySQL: '.$e + ); + return false; + } + foreach ($all_items as $i) { + if (hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $i['domain'])) { + $bccdata[] = $i['id']; + } + } + $all_items = null; + return $bccdata; + break; + case 'delete': + $ids = (array)$_data['id']; + foreach ($ids as $id) { + if (!is_numeric($id)) { + return false; + } + try { + $stmt = $pdo->prepare("SELECT `domain` FROM `bcc_maps` WHERE id = :id"); + $stmt->execute(array(':id' => $id)); + $domain = $stmt->fetch(PDO::FETCH_ASSOC)['domain']; + if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain)) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['access_denied']) + ); + return false; + } + $stmt = $pdo->prepare("DELETE FROM `bcc_maps` WHERE `id`= :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' => 'Deleted BCC map id/s ' . implode(', ', $ids) + ); + return true; + break; + } +} \ No newline at end of file diff --git a/data/web/inc/functions.customize.inc.php b/data/web/inc/functions.customize.inc.php index 167647fc..7e8f7b03 100644 --- a/data/web/inc/functions.customize.inc.php +++ b/data/web/inc/functions.customize.inc.php @@ -1,5 +1,4 @@ 'danger', @@ -3103,10 +3103,6 @@ function mailbox($_action, $_type, $_data = null, $attr = null) { } foreach ($ids as $id) { if (!is_numeric($id)) { - $_SESSION['return'] = array( - 'type' => 'danger', - 'msg' => $id - ); return false; } try { @@ -3154,10 +3150,6 @@ function mailbox($_action, $_type, $_data = null, $attr = null) { } foreach ($ids as $id) { if (!is_numeric($id)) { - $_SESSION['return'] = array( - 'type' => 'danger', - 'msg' => $id - ); return false; } try { @@ -3366,6 +3358,10 @@ function mailbox($_action, $_type, $_data = null, $attr = null) { $stmt->execute(array( ':domain' => '%@'.$domain, )); + $stmt = $pdo->prepare("DELETE FROM `bcc_maps` WHERE `local_dest` = :domain"); + $stmt->execute(array( + ':domain' => '%@'.$domain, + )); } catch (PDOException $e) { $_SESSION['return'] = array( @@ -3486,6 +3482,10 @@ function mailbox($_action, $_type, $_data = null, $attr = null) { $stmt->execute(array( ':alias_domain' => $alias_domain, )); + $stmt = $pdo->prepare("DELETE FROM `bcc_maps` WHERE `local_dest` = :alias_domain"); + $stmt->execute(array( + ':domain' => '%@'.$alias_domain, + )); } catch (PDOException $e) { $_SESSION['return'] = array( @@ -3580,6 +3580,10 @@ function mailbox($_action, $_type, $_data = null, $attr = null) { $stmt->execute(array( ':username' => $username )); + $stmt = $pdo->prepare("DELETE FROM `bcc_maps` WHERE `local_dest` = :username"); + $stmt->execute(array( + ':username' => $username + )); $stmt = $pdo->prepare("SELECT `address`, `goto` FROM `alias` WHERE `goto` REGEXP :username"); $stmt->execute(array(':username' => '(^|,)'.$username.'($|,)')); diff --git a/data/web/inc/init_db.inc.php b/data/web/inc/init_db.inc.php index 15ba214b..24a7e072 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 = "14112017_2103"; + $db_version = "16112017_2259"; $stmt = $pdo->query("SHOW TABLES LIKE 'versions'"); $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); @@ -202,6 +202,7 @@ function init_db_schema() { "syncjobs" => "TINYINT(1) NOT NULL DEFAULT '1'", "eas_reset" => "TINYINT(1) NOT NULL DEFAULT '1'", "filters" => "TINYINT(1) NOT NULL DEFAULT '1'", + "bcc_maps" => "TINYINT(1) NOT NULL DEFAULT '1'", ), "keys" => array( "fkey" => array( @@ -325,6 +326,27 @@ function init_db_schema() { ), "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" ), + "bcc_maps" => array( + "cols" => array( + "id" => "INT NOT NULL AUTO_INCREMENT", + "local_dest" => "VARCHAR(255) NOT NULL", + "bcc_dest" => "VARCHAR(255) NOT NULL", + "domain" => "VARCHAR(255) NOT NULL", + "type" => "ENUM('sender','rcpt')", + "created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)", + "modified" => "DATETIME ON UPDATE CURRENT_TIMESTAMP", + "active" => "TINYINT(1) NOT NULL DEFAULT '0'" + ), + "keys" => array( + "primary" => array( + "" => array("id") + ), + "key" => array( + "local_dest" => array("local_dest"), + ) + ), + "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" + ), "tfa" => 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 1533e24f..3f13e7dc 100644 --- a/data/web/inc/prerequisites.inc.php +++ b/data/web/inc/prerequisites.inc.php @@ -67,6 +67,7 @@ include $_SERVER['DOCUMENT_ROOT'] . '/lang/lang.'.$_SESSION['mailcow_locale'].'. require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.inc.php'; require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.mailbox.inc.php'; require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.customize.inc.php'; +require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.bcc.inc.php'; require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.domain_admin.inc.php'; require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.policy.inc.php'; require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.dkim.inc.php'; diff --git a/data/web/inc/vars.inc.php b/data/web/inc/vars.inc.php index 2b93af15..12d8b3d9 100644 --- a/data/web/inc/vars.inc.php +++ b/data/web/inc/vars.inc.php @@ -118,3 +118,6 @@ $OTP_LABEL = "mailcow UI"; // Default "to" address in relay test tool $RELAY_TO = "null@hosted.mailcow.de"; + +// Internal constants, can be ignored +define("F2B", 1); \ No newline at end of file diff --git a/data/web/js/mailbox.js b/data/web/js/mailbox.js index 9e2afa2c..9e3c6868 100644 --- a/data/web/js/mailbox.js +++ b/data/web/js/mailbox.js @@ -314,7 +314,56 @@ jQuery(function($){ } }); } - + function draw_bcc_table() { + ft_bcc_table = FooTable.init('#bcc_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":"type","title":"Type"}, + {"name":"local_dest","title":"Local destination"}, + {"name":"bcc_dest","title":"BCC destination/s"}, + {"name":"domain","title":lang.domain,"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":"180px","width":"180px"},"type":"html","title":lang.action,"breakpoints":"xs sm"} + ], + "empty": lang.empty, + "rows": $.ajax({ + dataType: 'json', + url: '/api/v1/get/bcc/all', + jsonp: false, + error: function () { + console.log('Cannot draw bcc table'); + }, + success: function (data) { + $.each(data, function (i, item) { + item.action = ''; + item.chkbox = ''; + if (item.type == 'sender') { + item.type = 'Sender'; + } else { + item.type = 'Recipient'; + } + }); + } + }), + "paging": { + "enabled": true, + "limit": 5, + "size": pagination_size + }, + "filtering": { + "enabled": true, + "position": "left", + "placeholder": lang.filter_table + }, + "sorting": { + "enabled": true + } + }); + } function draw_alias_table() { ft_alias_table = FooTable.init('#alias_table', { "columns": [ @@ -530,5 +579,6 @@ jQuery(function($){ draw_aliasdomain_table(); draw_sync_job_table(); draw_filter_table(); + draw_bcc_table(); }); diff --git a/data/web/json_api.php b/data/web/json_api.php index b622c312..9ada1044 100644 --- a/data/web/json_api.php +++ b/data/web/json_api.php @@ -258,6 +258,39 @@ if (isset($_SESSION['mailcow_cc_role']) || isset($_SESSION['pending_mailcow_cc_u )); } break; + case "bcc": + if (isset($_POST['attr'])) { + $attr = (array)json_decode($_POST['attr'], true); + if (bcc('add', $attr) === false) { + if (isset($_SESSION['return'])) { + echo json_encode($_SESSION['return']); + } + else { + echo json_encode(array( + 'type' => 'error', + 'msg' => 'Cannot add item' + )); + } + } + else { + if (isset($_SESSION['return'])) { + echo json_encode($_SESSION['return']); + } + else { + echo json_encode(array( + 'type' => 'success', + 'msg' => 'Task completed' + )); + } + } + } + else { + echo json_encode(array( + 'type' => 'error', + 'msg' => 'Cannot find attributes in post data' + )); + } + break; case "domain-policy": if (isset($_POST['attr'])) { $attr = (array)json_decode($_POST['attr'], true); @@ -1034,6 +1067,42 @@ if (isset($_SESSION['mailcow_cc_role']) || isset($_SESSION['pending_mailcow_cc_u break; } break; + case "bcc": + switch ($object) { + case "all": + $bcc_items = bcc('get'); + if (!empty($bcc_items)) { + foreach ($bcc_items as $bcc_item) { + if ($details = bcc('details', $bcc_item)) { + $data[] = $details; + } + else { + continue; + } + } + } + if (!isset($data) || empty($data)) { + echo '{}'; + } + else { + echo json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT); + } + break; + + default: + $data = bcc('details', $object); + if (!empty($data)) { + $data[] = $details; + } + if (!isset($data) || empty($data)) { + echo '{}'; + } + else { + echo json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT); + } + break; + } + break; case "policy_wl_mailbox": switch ($object) { default: @@ -1464,6 +1533,47 @@ if (isset($_SESSION['mailcow_cc_role']) || isset($_SESSION['pending_mailcow_cc_u )); } break; + case "bcc": + if (isset($_POST['items'])) { + $items = (array)json_decode($_POST['items'], true); + if (is_array($items)) { + if (bcc('delete', array('id' => $items)) === false) { + if (isset($_SESSION['return'])) { + echo json_encode($_SESSION['return']); + } + else { + echo json_encode(array( + 'type' => 'error', + 'msg' => 'Deletion of items/s failed' + )); + } + } + else { + if (isset($_SESSION['return'])) { + echo json_encode($_SESSION['return']); + } + else { + echo json_encode(array( + 'type' => 'success', + 'msg' => 'Task completed' + )); + } + } + } + else { + echo json_encode(array( + 'type' => 'error', + 'msg' => 'Cannot find id array in post data' + )); + } + } + else { + echo json_encode(array( + 'type' => 'error', + 'msg' => 'Cannot find items in post data' + )); + } + break; case "fwdhost": if (isset($_POST['items'])) { $items = (array)json_decode($_POST['items'], true); @@ -2313,7 +2423,51 @@ if (isset($_SESSION['mailcow_cc_role']) || isset($_SESSION['pending_mailcow_cc_u 'msg' => 'Incomplete post data' )); } - break; + break; + case "bcc": + if (isset($_POST['items']) && isset($_POST['attr'])) { + $items = (array)json_decode($_POST['items'], true); + $attr = (array)json_decode($_POST['attr'], true); + $postarray = array_merge(array('id' => $items), $attr); + if (is_array($postarray['id'])) { + if (bcc('edit', $postarray) === false) { + if (isset($_SESSION['return'])) { + echo json_encode($_SESSION['return']); + } + else { + echo json_encode(array( + 'type' => 'error', + 'msg' => 'Edit failed' + )); + } + exit(); + } + else { + if (isset($_SESSION['return'])) { + echo json_encode($_SESSION['return']); + } + else { + echo json_encode(array( + 'type' => 'success', + 'msg' => 'Task completed' + )); + } + } + } + else { + echo json_encode(array( + 'type' => 'error', + 'msg' => 'Incomplete post data' + )); + } + } + else { + echo json_encode(array( + 'type' => 'error', + 'msg' => 'Incomplete post data' + )); + } + break; case "resource": if (isset($_POST['items']) && isset($_POST['attr'])) { $items = (array)json_decode($_POST['items'], true); diff --git a/data/web/mailbox.php b/data/web/mailbox.php index 193b11c8..b4b8feec 100644 --- a/data/web/mailbox.php +++ b/data/web/mailbox.php @@ -21,6 +21,7 @@ $_SESSION['return_to'] = $_SERVER['REQUEST_URI'];
  • +
  • BCC maps
  • @@ -207,6 +208,34 @@ $_SESSION['return_to'] = $_SERVER['REQUEST_URI'];
    +
    +
    +
    +

    BCC maps

    +
    +

    A recipient map type entry is used, when the local destination acts as recipient of a mail. Sender maps conform to the same principle. + The local destination will not be informed about a failed delivery.

    +
    +
    +
    + +
    +
    diff --git a/data/web/modals/mailbox.php b/data/web/modals/mailbox.php index 192124c2..113b3289 100644 --- a/data/web/modals/mailbox.php +++ b/data/web/modals/mailbox.php @@ -509,6 +509,77 @@ if (!isset($_SESSION['mailcow_cc_role'])) { + +