From e86565e2836b13be96d62790818849570c3bc6a1 Mon Sep 17 00:00:00 2001 From: Michael Kuron Date: Tue, 23 Jan 2018 19:59:06 +0100 Subject: [PATCH 001/107] Expose Postfix's recipient_canonical_maps through web UI --- data/Dockerfiles/postfix/postfix.sh | 10 + data/conf/postfix/main.cf | 3 + data/web/edit.php | 37 +++ ...hp => functions.address_rewriting.inc.php} | 235 +++++++++++++++++- data/web/inc/init_db.inc.php | 21 +- data/web/inc/prerequisites.inc.php | 2 +- data/web/js/mailbox.js | 47 ++++ data/web/json_api.php | 153 ++++++++++++ data/web/lang/lang.en.php | 8 +- data/web/mailbox.php | 30 ++- data/web/modals/mailbox.php | 39 +++ docker-compose.yml | 2 +- 12 files changed, 581 insertions(+), 6 deletions(-) rename data/web/inc/{functions.bcc.inc.php => functions.address_rewriting.inc.php} (55%) diff --git a/data/Dockerfiles/postfix/postfix.sh b/data/Dockerfiles/postfix/postfix.sh index c152606d..0620404d 100755 --- a/data/Dockerfiles/postfix/postfix.sh +++ b/data/Dockerfiles/postfix/postfix.sh @@ -145,6 +145,16 @@ query = SELECT bcc_dest FROM bcc_maps AND active='1'; EOF +cat < /opt/postfix/conf/sql/mysql_recipient_canonical_maps.cf +user = ${DBUSER} +password = ${DBPASS} +hosts = mysql +dbname = ${DBNAME} +query = SELECT new_dest FROM recipient_maps + WHERE old_dest='%s' + 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 4e8c577f..1c943dc6 100644 --- a/data/conf/postfix/main.cf +++ b/data/conf/postfix/main.cf @@ -45,6 +45,7 @@ proxy_read_maps = proxy:mysql:/opt/postfix/conf/sql/mysql_virtual_sender_acl.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, + proxy:mysql:/opt/postfix/conf/sql/mysql_recipient_canonical_maps.cf, $local_recipient_maps, $mydestination, $virtual_alias_maps, @@ -108,6 +109,8 @@ 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 +recipient_canonical_maps = proxy:mysql:/opt/postfix/conf/sql/mysql_recipient_canonical_maps.cf +recipient_canonical_classes = envelope_recipient 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/edit.php b/data/web/edit.php index b53e1794..5e337249 100644 --- a/data/web/edit.php +++ b/data/web/edit.php @@ -654,6 +654,43 @@ if (isset($_SESSION['mailcow_cc_role'])) { +

Recipient map:

+
+
+ +
+ +
+ + Recipient map destinations can only be valid email addresses. Separated by whitespace, semicolon, new line or comma. +
+
+
+
+
+ +
+
+
+
+
+ +
+
+
+ + + 'danger', + 'msg' => 'Recipient map destination cannot be empty' + ); + return false; + } + if (is_valid_domain_name($old_dest)) { + $old_dest_sane = '@' . idn_to_ascii($old_dest); + } + elseif (filter_var($old_dest, FILTER_VALIDATE_EMAIL)) { + $old_dest_sane = $old_dest; + } + else { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => 'Invalid original recipient specified: ' . $old_dest + ); + return false; + } + foreach ($new_dest as &$new_dest_e) { + if (!filter_var($new_dest_e, FILTER_VALIDATE_EMAIL)) { + $new_dest_e = null;; + } + $new_dest_e = strtolower($new_dest_e); + } + $new_dest = array_filter($new_dest); + $new_dest = implode(",", $new_dest); + if (empty($new_dest)) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => 'Recipient map destination cannot be empty' + ); + return false; + } + try { + $stmt = $pdo->prepare("SELECT `id` FROM `recipient_maps` + WHERE `old_dest` = :old_dest"); + $stmt->execute(array(':old_dest' => $old_dest_sane)); + $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 Recipient map entry "' . htmlspecialchars($old_dest_sane) . '" exists' + ); + return false; + } + try { + $stmt = $pdo->prepare("INSERT INTO `recipient_maps` (`old_dest`, `new_dest`, `active`) VALUES + (:old_dest, :new_dest, :active)"); + $stmt->execute(array( + ':old_dest' => $old_dest_sane, + ':new_dest' => $new_dest, + ':active' => $active + )); + } + catch (PDOException $e) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => 'MySQL: '.$e + ); + return false; + } + $_SESSION['return'] = array( + 'type' => 'success', + 'msg' => 'Recipient map entry saved' + ); + break; + case 'edit': + $ids = (array)$_data['id']; + foreach ($ids as $id) { + $is_now = recipient_map('details', $id); + if (!empty($is_now)) { + $active = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active_int']; + $new_dest = (!empty($_data['recipient_map_new'])) ? $_data['recipient_map_new'] : $is_now['recipient_map_new']; + $old_dest = $is_now['old_dest']; + } + else { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['access_denied']) + ); + return false; + } + $new_dest = array_map('trim', preg_split( "/( |,|;|\n)/", $new_dest)); + $active = intval($_data['active']); + foreach ($new_dest as &$new_dest_e) { + if (!filter_var($new_dest_e, FILTER_VALIDATE_EMAIL)) { + $new_dest_e = null;; + } + $new_dest_e = strtolower($new_dest_e); + } + $new_dest = array_filter($new_dest); + $new_dest = implode(",", $new_dest); + if (empty($new_dest)) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => 'Recipient map destination cannot be empty' + ); + return false; + } + try { + $stmt = $pdo->prepare("SELECT `id` FROM `recipient_maps` + WHERE `old_dest` = :old_dest"); + $stmt->execute(array(':old_dest' => $old_dest)); + $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 Recipient map entry ' . htmlspecialchars($old_dest) . ' exists' + ); + return false; + } + try { + $stmt = $pdo->prepare("UPDATE `recipient_maps` SET `new_dest` = :new_dest, `active` = :active WHERE `id`= :id"); + $stmt->execute(array( + ':new_dest' => $new_dest, + ':active' => $active, + ':id' => $id + )); + } + catch (PDOException $e) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => 'MySQL: '.$e + ); + return false; + } + } + $_SESSION['return'] = array( + 'type' => 'success', + 'msg' => 'Recipient map entry edited' + ); + break; + case 'details': + $mapdata = array(); + $id = intval($_data); + try { + $stmt = $pdo->prepare("SELECT `id`, + `old_dest` AS `recipient_map_old`, + `new_dest` AS `recipient_map_new`, + `active` AS `active_int`, + CASE `active` WHEN 1 THEN '".$lang['mailbox']['yes']."' ELSE '".$lang['mailbox']['no']."' END AS `active`, + `created`, + `modified` FROM `recipient_maps` + WHERE `id` = :id"); + $stmt->execute(array(':id' => $id)); + $mapdata = $stmt->fetch(PDO::FETCH_ASSOC); + } + catch(PDOException $e) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => 'MySQL: '.$e + ); + return false; + } + return $mapdata; + break; + case 'get': + $mapdata = array(); + $all_items = array(); + $id = intval($_data); + try { + $stmt = $pdo->query("SELECT `id` FROM `recipient_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) { + $mapdata[] = $i['id']; + } + $all_items = null; + return $mapdata; + break; + case 'delete': + $ids = (array)$_data['id']; + foreach ($ids as $id) { + if (!is_numeric($id)) { + return false; + } + try { + $stmt = $pdo->prepare("DELETE FROM `recipient_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 Recipient map id/s ' . implode(', ', $ids) + ); + return true; + break; + } +} diff --git a/data/web/inc/init_db.inc.php b/data/web/inc/init_db.inc.php index 6c0ef937..ef704bfb 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 = "02012018_1515"; + $db_version = "20012021_2202"; $stmt = $pdo->query("SHOW TABLES LIKE 'versions'"); $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); @@ -394,6 +394,25 @@ function init_db_schema() { ), "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" ), + "recipient_maps" => array( + "cols" => array( + "id" => "INT NOT NULL AUTO_INCREMENT", + "old_dest" => "VARCHAR(255) NOT NULL", + "new_dest" => "VARCHAR(255) NOT NULL", + "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("old_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 8aa20770..c7a75fdc 100644 --- a/data/web/inc/prerequisites.inc.php +++ b/data/web/inc/prerequisites.inc.php @@ -81,7 +81,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.address_rewriting.inc.php'; require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.domain_admin.inc.php'; require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.quarantaine.inc.php'; require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.policy.inc.php'; diff --git a/data/web/js/mailbox.js b/data/web/js/mailbox.js index 1e08a4ee..cca7331b 100644 --- a/data/web/js/mailbox.js +++ b/data/web/js/mailbox.js @@ -383,6 +383,52 @@ jQuery(function($){ } }); } + function draw_recipient_map_table() { + ft_recipient_map_table = FooTable.init('#recipient_map_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":"recipient_map_old","title":lang.recipient_map_old}, + {"name":"recipient_map_new","title":lang.recipient_map_new}, + {"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/recipient_map/all', + jsonp: false, + error: function () { + console.log('Cannot draw recipient map table'); + }, + success: function (data) { + if (role == "admin") { + $.each(data, function (i, item) { + item.action = ''; + item.chkbox = ''; + }); + } + } + }), + "paging": { + "enabled": true, + "limit": 5, + "size": pagination_size + }, + "filtering": { + "enabled": true, + "position": "left", + "connectors": false, + "placeholder": lang.filter_table + }, + "sorting": { + "enabled": true + } + }); + } function draw_alias_table() { ft_alias_table = FooTable.init('#alias_table', { "columns": [ @@ -609,5 +655,6 @@ jQuery(function($){ draw_sync_job_table(); draw_filter_table(); draw_bcc_table(); + draw_recipient_map_table(); }); diff --git a/data/web/json_api.php b/data/web/json_api.php index 856facfc..8c2c5b44 100644 --- a/data/web/json_api.php +++ b/data/web/json_api.php @@ -595,6 +595,39 @@ if (isset($_SESSION['mailcow_cc_role']) || isset($_SESSION['pending_mailcow_cc_u )); } break; + case "recipient_map": + if (isset($_POST['attr'])) { + $attr = (array)json_decode($_POST['attr'], true); + if (recipient_map('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; } break; case "get": @@ -1191,6 +1224,41 @@ if (isset($_SESSION['mailcow_cc_role']) || isset($_SESSION['pending_mailcow_cc_u break; } break; + case "recipient_map": + switch ($object) { + case "all": + $recipient_map_items = recipient_map('get'); + if (!empty($recipient_map_items)) { + foreach ($recipient_map_items as $recipient_map_item) { + if ($details = recipient_map('details', $recipient_map_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 = recipient_map('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: @@ -1739,6 +1807,47 @@ if (isset($_SESSION['mailcow_cc_role']) || isset($_SESSION['pending_mailcow_cc_u )); } break; + case "recipient_map": + if (isset($_POST['items'])) { + $items = (array)json_decode($_POST['items'], true); + if (is_array($items)) { + if (recipient_map('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); @@ -2238,6 +2347,50 @@ if (isset($_SESSION['mailcow_cc_role']) || isset($_SESSION['pending_mailcow_cc_u )); } break; + case "recipient_map": + 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 (recipient_map('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 "alias": if (isset($_POST['items']) && isset($_POST['attr'])) { $items = (array)json_decode($_POST['items'], true); diff --git a/data/web/lang/lang.en.php b/data/web/lang/lang.en.php index 37cf6661..2b3236fe 100644 --- a/data/web/lang/lang.en.php +++ b/data/web/lang/lang.en.php @@ -648,5 +648,11 @@ $lang['mailbox']['bcc_maps'] = "BCC maps"; $lang['mailbox']['bcc_to_sender'] = "Switch to sender map type"; $lang['mailbox']['bcc_to_rcpt'] = "Switch to recipient map type"; $lang['mailbox']['add_bcc_entry'] = "Add BCC map"; -$lang['mailbox']['bcc_info'] = "A recipient map type entry is used, when the local destination acts as recipient of a mail. Sender maps conform to the same principle.
+$lang['mailbox']['bcc_info'] = "BCC maps are used to silently forward copies of all messages to another address. 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."; +$lang['mailbox']['address_rewriting'] = 'Address rewriting'; +$lang['mailbox']['recipient_maps'] = 'Recipient maps'; +$lang['mailbox']['recipient_map_info'] = 'Recipient maps are used to replace the destination address on a message before it is delivered.'; +$lang['mailbox']['recipient_map_old'] = 'Original recipient'; +$lang['mailbox']['recipient_map_new'] = 'New recipient'; +$lang['mailbox']['add_recipient_map_entry'] = 'Add recipient map'; \ No newline at end of file diff --git a/data/web/mailbox.php b/data/web/mailbox.php index 19ec683e..73a628c4 100644 --- a/data/web/mailbox.php +++ b/data/web/mailbox.php @@ -21,7 +21,7 @@ $_SESSION['return_to'] = $_SERVER['REQUEST_URI'];
  • -
  • +
  • @@ -234,6 +234,34 @@ $_SESSION['return_to'] = $_SERVER['REQUEST_URI'];
    +
    +
    +

    +
    +

    +
    +
    +
    + +
    +
    + + + + +
    +
    +
    diff --git a/data/web/modals/mailbox.php b/data/web/modals/mailbox.php index 3f05f8f8..f8dca77d 100644 --- a/data/web/modals/mailbox.php +++ b/data/web/modals/mailbox.php @@ -574,6 +574,45 @@ if (!isset($_SESSION['mailcow_cc_role'])) { + + @@ -555,8 +555,8 @@ if (isset($_SESSION['mailcow_cc_role'])) { diff --git a/data/web/inc/functions.mailbox.inc.php b/data/web/inc/functions.mailbox.inc.php index 5bd9ac58..6cc5bf30 100644 --- a/data/web/inc/functions.mailbox.inc.php +++ b/data/web/inc/functions.mailbox.inc.php @@ -511,8 +511,8 @@ function mailbox($_action, $_type, $_data = null, $attr = null) { if (in_array($address, $gotos)) { continue; } - $domain = idn_to_ascii(substr(strstr($address, '@'), 1)); - $local_part = strstr($address, '@', true); + $domain = idn_to_ascii(substr(strrchr($address, '@'), 1)); + $local_part = substr($address, 0, strripos($address, '@')); $address = $local_part.'@'.$domain; $stmt = $pdo->prepare("SELECT `address` FROM `alias` WHERE `address`= :address OR `address` IN ( @@ -1713,8 +1713,8 @@ function mailbox($_action, $_type, $_data = null, $attr = null) { $gotos = array_filter($gotos); $goto = implode(",", $gotos); } - $domain = idn_to_ascii(substr(strstr($address, '@'), 1)); - $local_part = strstr($address, '@', true); + $domain = idn_to_ascii(substr(strrchr($address, '@'), 1)); + $local_part = substr($address, 0, strripos($address, '@')); if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain)) { $_SESSION['return'] = array( 'type' => 'danger', @@ -3478,7 +3478,7 @@ function mailbox($_action, $_type, $_data = null, $attr = null) { $addresses = $_data['address']; } foreach ($addresses as $address) { - $local_part = strstr($address, '@', true); + $local_part = substr($address, 0, strripos($address, '@')); $domain = mailbox('get', 'alias_details', $address)['domain']; try { $stmt = $pdo->prepare("SELECT `goto` FROM `alias` WHERE `address` = :address"); diff --git a/data/web/js/mailbox.js b/data/web/js/mailbox.js index 1ff4066d..93418d92 100644 --- a/data/web/js/mailbox.js +++ b/data/web/js/mailbox.js @@ -186,16 +186,16 @@ jQuery(function($){ item.mailboxes = item.mboxes_in_domain + " / " + item.max_num_mboxes_for_domain; item.quota = item.quota_used_in_domain + "/" + item.max_quota_for_domain; item.max_quota_for_mbox = humanFileSize(item.max_quota_for_mbox); - item.chkbox = ''; + item.chkbox = ''; item.action = '
    '; if (role == "admin") { - item.action += ' ' + lang.edit + '' + - ' ' + lang.remove + ''; + item.action += ' ' + lang.edit + '' + + ' ' + lang.remove + ''; } else { - item.action += ' ' + lang.edit + ''; + item.action += ' ' + lang.edit + ''; } - item.action += ' DNS
    '; + item.action += ' DNS'; }); } }), @@ -251,18 +251,18 @@ jQuery(function($){ item.quota = item.quota_used + "/" + item.quota; item.max_quota_for_mbox = humanFileSize(item.max_quota_for_mbox); item.username = escapeHtml(item.username); - item.chkbox = ''; + item.chkbox = ''; if (role == "admin") { item.action = ''; } else { item.action = ''; } item.in_use = '
    ' + @@ -309,11 +309,12 @@ jQuery(function($){ }, success: function (data) { $.each(data, function (i, item) { + item.name = escapeHtml(item.name); item.action = ''; - item.chkbox = ''; + item.chkbox = ''; }); } }), @@ -455,14 +456,14 @@ jQuery(function($){ success: function (data) { $.each(data, function (i, item) { item.action = ''; - item.chkbox = ''; + item.chkbox = ''; item.goto = escapeHtml(item.goto); item.address = escapeHtml(item.address); if (item.is_catch_all == 1) { - item.address = '
    Catch-All
    ' + item.address; + item.address = '
    Catch-All
    ' + escapeHtml(item.address); } if (item.goto == "null@localhost") { item.goto = '⤷ '; @@ -510,11 +511,11 @@ jQuery(function($){ success: function (data) { $.each(data, function (i, item) { item.action = '' + + ' ' + lang.edit + '' + + ' ' + lang.remove + '' + + ' DNS
    ' + ''; - item.chkbox = ''; + item.chkbox = ''; }); } }), @@ -560,7 +561,7 @@ jQuery(function($){ }, success: function (data) { $.each(data, function (i, item) { - item.log = 'Open logs' + item.log = 'Open logs' item.user2 = escapeHtml(item.user2); if (!item.exclude > 0) { item.exclude = '-'; @@ -570,7 +571,7 @@ jQuery(function($){ item.server_w_port = item.user1 + '@' + item.host1 + ':' + item.port1; item.action = ''; item.chkbox = ''; if (item.is_running == 1) { @@ -632,7 +633,7 @@ jQuery(function($){ item.filter_type = '
    ' + item.filter_type.charAt(0).toUpperCase() + item.filter_type.slice(1).toLowerCase() + '
    ' item.action = ''; item.chkbox = '' }); From 618be3bf14600e46821e38ae7231bdf8cbae5ce9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Peters?= Date: Sat, 10 Feb 2018 22:42:46 +0100 Subject: [PATCH 056/107] [Web] Even more fixes for #1017 --- data/web/inc/footer.inc.php | 4 ++-- data/web/inc/functions.inc.php | 16 ++++++---------- data/web/inc/functions.policy.inc.php | 2 +- data/web/inc/header.inc.php | 6 +++--- data/web/inc/triggers.inc.php | 7 ++++--- data/web/js/mailbox.js | 11 ++++++----- data/web/js/user.js | 18 ++++++++++-------- data/web/user.php | 20 ++++++++++---------- 8 files changed, 42 insertions(+), 42 deletions(-) diff --git a/data/web/inc/footer.inc.php b/data/web/inc/footer.inc.php index 3c92c28e..d0932514 100644 --- a/data/web/inc/footer.inc.php +++ b/data/web/inc/footer.inc.php @@ -61,7 +61,7 @@ $(document).ready(function() { type: "GET", cache: false, dataType: 'script', - url: "/api/v1/get/u2f-authentication/", + url: "/api/v1/get/u2f-authentication/", complete: function(data){ $('#u2f_status_auth').html(''); data; @@ -100,7 +100,7 @@ $(document).ready(function() { type: "GET", cache: false, dataType: 'script', - url: "/api/v1/get/u2f-registration/", + url: "/api/v1/get/u2f-registration/", complete: function(data){ data; setTimeout(function() { diff --git a/data/web/inc/functions.inc.php b/data/web/inc/functions.inc.php index f4e8d039..92f4da2b 100644 --- a/data/web/inc/functions.inc.php +++ b/data/web/inc/functions.inc.php @@ -39,7 +39,7 @@ function hasDomainAccess($username, $role, $domain) { } function hasMailboxObjectAccess($username, $role, $object) { global $pdo; - if (!filter_var($username, FILTER_VALIDATE_EMAIL) && !ctype_alnum(str_replace(array('_', '.', '-'), '', $username))) { + if (!filter_var(html_entity_decode(rawurldecode($username)), FILTER_VALIDATE_EMAIL) && !ctype_alnum(str_replace(array('_', '.', '-'), '', $username))) { return false; } if ($role != 'admin' && $role != 'domainadmin' && $role != 'user') { @@ -471,22 +471,18 @@ function user_get_alias_details($username) { )); $run = $stmt->fetchAll(PDO::FETCH_ASSOC); while ($row = array_shift($run)) { - $data['direct_aliases'] = $row['direct_aliases']; + $data['direct_aliases'][] = $row['direct_aliases']; } - $stmt = $pdo->prepare("SELECT IFNULL(GROUP_CONCAT(local_part, '@', alias_domain SEPARATOR ', '), '✘') AS `ad_alias` FROM `mailbox` + $stmt = $pdo->prepare("SELECT GROUP_CONCAT(local_part, '@', alias_domain SEPARATOR ', ') AS `ad_alias` FROM `mailbox` LEFT OUTER JOIN `alias_domain` on `target_domain` = `domain` WHERE `username` = :username ;"); $stmt->execute(array(':username' => $username)); $run = $stmt->fetchAll(PDO::FETCH_ASSOC); while ($row = array_shift($run)) { - if (empty($data['direct_aliases'])) { - $data['direct_aliases'] = $row['ad_alias']; - } - else { - // Probably faster than imploding - $data['direct_aliases'] .= ', ' . $row['ad_alias']; - } + $data['direct_aliases'][] = $row['ad_alias']; } + $data['direct_aliases'] = implode(', ', array_filter($data['direct_aliases'])); + $data['direct_aliases'] = empty($data['direct_aliases']) ? '✘' : $data['direct_aliases']; $stmt = $pdo->prepare("SELECT IFNULL(GROUP_CONCAT(`send_as` SEPARATOR ', '), '✘') AS `send_as` FROM `sender_acl` WHERE `logged_in_as` = :username AND `send_as` NOT LIKE '@%';"); $stmt->execute(array(':username' => $username)); $run = $stmt->fetchAll(PDO::FETCH_ASSOC); diff --git a/data/web/inc/functions.policy.inc.php b/data/web/inc/functions.policy.inc.php index 9609d5e1..63f178c1 100644 --- a/data/web/inc/functions.policy.inc.php +++ b/data/web/inc/functions.policy.inc.php @@ -94,7 +94,7 @@ function policy($_action, $_scope, $_data = null) { if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $object)) { $_SESSION['return'] = array( 'type' => 'danger', - 'msg' => sprintf($lang['danger']['access_denied']) + 'msg' => $object ); return false; } diff --git a/data/web/inc/header.inc.php b/data/web/inc/header.inc.php index 253692fc..0c516d39 100644 --- a/data/web/inc/header.inc.php +++ b/data/web/inc/header.inc.php @@ -1,4 +1,4 @@ - + @@ -129,11 +129,11 @@ } if (!isset($_SESSION['dual-login']) && isset($_SESSION['mailcow_cc_username'])): ?> -
  • +
  • -
  • ()
  • +
  • ()
  • diff --git a/data/web/inc/triggers.inc.php b/data/web/inc/triggers.inc.php index bce8a20f..daf1a9a7 100644 --- a/data/web/inc/triggers.inc.php +++ b/data/web/inc/triggers.inc.php @@ -43,11 +43,12 @@ if (isset($_POST["login_user"]) && isset($_POST["pass_user"])) { if (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == "admin") { if (isset($_GET["duallogin"])) { - if (filter_var($_GET["duallogin"], FILTER_VALIDATE_EMAIL)) { - if (!empty(mailbox('get', 'mailbox_details', $_GET["duallogin"]))) { + $duallogin = html_entity_decode(rawurldecode($_GET["duallogin"])); + if (filter_var($duallogin, FILTER_VALIDATE_EMAIL)) { + if (!empty(mailbox('get', 'mailbox_details', $duallogin))) { $_SESSION["dual-login"]["username"] = $_SESSION['mailcow_cc_username']; $_SESSION["dual-login"]["role"] = $_SESSION['mailcow_cc_role']; - $_SESSION['mailcow_cc_username'] = $_GET["duallogin"]; + $_SESSION['mailcow_cc_username'] = $duallogin; $_SESSION['mailcow_cc_role'] = "user"; header("Location: /user.php"); } diff --git a/data/web/js/mailbox.js b/data/web/js/mailbox.js index 93418d92..ea7ce5f6 100644 --- a/data/web/js/mailbox.js +++ b/data/web/js/mailbox.js @@ -250,7 +250,6 @@ jQuery(function($){ $.each(data, function (i, item) { item.quota = item.quota_used + "/" + item.quota; item.max_quota_for_mbox = humanFileSize(item.max_quota_for_mbox); - item.username = escapeHtml(item.username); item.chkbox = ''; if (role == "admin") { item.action = '
    ' + @@ -268,7 +267,7 @@ jQuery(function($){ item.in_use = '
    ' + '
    ' + item.percent_in_use + '%' + '
    '; - + item.username = escapeHtml(item.username); }); } }), @@ -309,12 +308,12 @@ jQuery(function($){ }, success: function (data) { $.each(data, function (i, item) { - item.name = escapeHtml(item.name); item.action = ''; item.chkbox = ''; + item.name = escapeHtml(item.name); }); } }), @@ -461,10 +460,12 @@ jQuery(function($){ '
    '; item.chkbox = ''; item.goto = escapeHtml(item.goto); - item.address = escapeHtml(item.address); if (item.is_catch_all == 1) { item.address = '
    Catch-All
    ' + escapeHtml(item.address); } + else { + item.address = escapeHtml(item.address); + } if (item.goto == "null@localhost") { item.goto = '⤷ '; } @@ -568,7 +569,7 @@ jQuery(function($){ } else { item.exclude = '' + item.exclude + ''; } - item.server_w_port = item.user1 + '@' + item.host1 + ':' + item.port1; + item.server_w_port = escapeHtml(item.user1) + '@' + item.host1 + ':' + item.port1; item.action = '
    ' + ' ' + lang.edit + '' + ' ' + lang.remove + '' + diff --git a/data/web/js/user.js b/data/web/js/user.js index 3984e855..29825769 100644 --- a/data/web/js/user.js +++ b/data/web/js/user.js @@ -62,9 +62,10 @@ jQuery(function($){ $.each(data, function (i, item) { if (acl_data.spam_alias === 1) { item.action = ''; - item.chkbox = ''; + item.chkbox = ''; + item.address = escapeHtml(item.address); } else { item.chkbox = ''; @@ -102,24 +103,25 @@ jQuery(function($){ "empty": lang.empty, "rows": $.ajax({ dataType: 'json', - url: '/api/v1/get/syncjobs/' + mailcow_cc_username + '/no_log', + url: '/api/v1/get/syncjobs/' + encodeURIComponent(mailcow_cc_username) + '/no_log', jsonp: false, error: function () { console.log('Cannot draw sync job table'); }, success: function (data) { $.each(data, function (i, item) { - item.log = 'Open logs' + item.user1 = escapeHtml(item.user1); + item.log = 'Open logs' if (!item.exclude > 0) { item.exclude = '-'; } else { - item.exclude = '' + item.exclude + ''; + item.exclude = '' + escapeHtml(item.exclude) + ''; } - item.server_w_port = item.user1 + '@' + item.host1 + ':' + item.port1; + item.server_w_port = escapeHtml(item.user1 + '@' + item.host1 + ':' + item.port1); if (acl_data.syncjobs === 1) { item.action = ''; item.chkbox = ''; } @@ -238,7 +240,7 @@ jQuery(function($){ $('#user_sieve_filter').text(lang.loading); $.ajax({ dataType: 'json', - url: '/api/v1/get/active-user-sieve/' + mailcow_cc_username, + url: '/api/v1/get/active-user-sieve/' + encodeURIComponent(mailcow_cc_username), jsonp: false, error: function () { console.log('Cannot get active sieve script'); diff --git a/data/web/user.php b/data/web/user.php index 0fb0875f..6b807945 100644 --- a/data/web/user.php +++ b/data/web/user.php @@ -164,21 +164,21 @@ elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == ' @@ -201,14 +201,14 @@ elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == ' @@ -225,7 +225,7 @@ elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == '
    :
    - +

    @@ -315,7 +315,7 @@ elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == '
    @@ -346,7 +346,7 @@ elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == '
    - +
    @@ -372,10 +372,10 @@ elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == '
    - + - +
    From c529de9c3611b6b93402edbca4ddff0e3b8176b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Peters?= Date: Sun, 11 Feb 2018 13:28:40 +0100 Subject: [PATCH 057/107] [Web] Fixes to Sieve validation (fixes #1027) --- data/web/inc/lib/sieve/extensions/fileinto.xml | 1 + data/web/inc/lib/sieve/extensions/mailbox.xml | 8 ++++++++ 2 files changed, 9 insertions(+) create mode 100644 data/web/inc/lib/sieve/extensions/mailbox.xml diff --git a/data/web/inc/lib/sieve/extensions/fileinto.xml b/data/web/inc/lib/sieve/extensions/fileinto.xml index 3b48a5c0..de3974c2 100644 --- a/data/web/inc/lib/sieve/extensions/fileinto.xml +++ b/data/web/inc/lib/sieve/extensions/fileinto.xml @@ -3,6 +3,7 @@ + diff --git a/data/web/inc/lib/sieve/extensions/mailbox.xml b/data/web/inc/lib/sieve/extensions/mailbox.xml new file mode 100644 index 00000000..c21960f3 --- /dev/null +++ b/data/web/inc/lib/sieve/extensions/mailbox.xml @@ -0,0 +1,8 @@ + + + + + + + + From 5030ce7547e1aeb765f8c8cf7a4a22fe2f77cc03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Peters?= Date: Sun, 11 Feb 2018 15:59:35 +0100 Subject: [PATCH 058/107] [Web] More and more fixes for #1017 --- data/conf/rspamd/dynmaps/settings.php | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/data/conf/rspamd/dynmaps/settings.php b/data/conf/rspamd/dynmaps/settings.php index 3ec8ddac..edd069d5 100644 --- a/data/conf/rspamd/dynmaps/settings.php +++ b/data/conf/rspamd/dynmaps/settings.php @@ -126,7 +126,7 @@ while ($row = array_shift($rows)) { - rcpt = ""; + rcpt = ; prepare("SELECT `option`, `value` FROM `filterconf` @@ -172,7 +172,7 @@ while ($row = array_shift($rows)) { - rcpt = ""; + rcpt = ; - rcpt = ""; + rcpt = ; - rcpt = ""; + rcpt = ; - rcpt = ""; + rcpt = ; - rcpt = ""; + rcpt = ; - rcpt = ""; + rcpt = ; - rcpt = ""; + rcpt = ; - rcpt = ""; + rcpt = ; Date: Sun, 11 Feb 2018 15:59:58 +0100 Subject: [PATCH 059/107] [Web] Do not break init_db when switching branches --- data/web/inc/init_db.inc.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/data/web/inc/init_db.inc.php b/data/web/inc/init_db.inc.php index 11b486cb..e11729fb 100644 --- a/data/web/inc/init_db.inc.php +++ b/data/web/inc/init_db.inc.php @@ -625,7 +625,11 @@ function init_db_schema() { $stmt = $pdo->query("SHOW TABLES LIKE 'quarantaine'"); $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); if ($num_results != 0) { - $pdo->query("RENAME TABLE `quarantaine` TO `quarantine`"); + $stmt = $pdo->query("SHOW TABLES LIKE 'quarantine'"); + $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); + if ($num_results == 0) { + $pdo->query("RENAME TABLE `quarantaine` TO `quarantine`"); + } } } $stmt = $pdo->query("SHOW TABLES LIKE '" . $table . "'"); From 74c804b9a39423873bf9e7ccf7830f384b66dca2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Peters?= Date: Mon, 12 Feb 2018 21:32:49 +0100 Subject: [PATCH 060/107] [SOGo] SOGo refuses to bind to IPv6, so force IPv4 in proxy_pass, fixes #1006 --- .gitignore | 3 +- data/conf/nginx/site.conf | 8 ++-- data/conf/nginx/templates/sogo.template | 1 + data/conf/nginx/templates/sogo_eas.template | 1 + data/conf/nginx/templates/sogo_proxy.template | 0 docker-compose.yml | 38 ++++++++++--------- 6 files changed, 28 insertions(+), 23 deletions(-) create mode 100644 data/conf/nginx/templates/sogo.template create mode 100644 data/conf/nginx/templates/sogo_eas.template create mode 100644 data/conf/nginx/templates/sogo_proxy.template diff --git a/.gitignore b/.gitignore index 798d9603..e535c710 100644 --- a/.gitignore +++ b/.gitignore @@ -3,8 +3,7 @@ data/conf/sogo/sieve.creds data/conf/dovecot/dovecot-master.passwd mailcow.conf mailcow.conf_backup -data/conf/nginx/listen*active -data/conf/nginx/server_name.active +data/conf/nginx/*.active data/conf/postfix/sql data/conf/dovecot/sql data/conf/nextcloud-*.bak diff --git a/data/conf/nginx/site.conf b/data/conf/nginx/site.conf index b84d3205..e617f77f 100644 --- a/data/conf/nginx/site.conf +++ b/data/conf/nginx/site.conf @@ -101,7 +101,7 @@ server { } location ^~ /Microsoft-Server-ActiveSync { - proxy_pass http://sogo:20000/SOGo/Microsoft-Server-ActiveSync; + include /etc/nginx/conf.d/sogo_eas.active; proxy_connect_timeout 1000; proxy_next_upstream timeout error; proxy_send_timeout 1000; @@ -123,7 +123,7 @@ server { } location ^~ /SOGo { - proxy_pass http://sogo:20000; + include /etc/nginx/conf.d/sogo.active; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Host $http_host; @@ -283,7 +283,7 @@ server { } location ^~ /Microsoft-Server-ActiveSync { - proxy_pass http://sogo:20000/SOGo/Microsoft-Server-ActiveSync; + include /etc/nginx/conf.d/templates/sogo_proxy.template; proxy_connect_timeout 1000; proxy_next_upstream timeout error; proxy_send_timeout 1000; @@ -305,7 +305,7 @@ server { } location ^~ /SOGo { - proxy_pass http://sogo:20000; + include /etc/nginx/conf.d/sogo.active; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Host $http_host; diff --git a/data/conf/nginx/templates/sogo.template b/data/conf/nginx/templates/sogo.template new file mode 100644 index 00000000..2c084389 --- /dev/null +++ b/data/conf/nginx/templates/sogo.template @@ -0,0 +1 @@ +proxy_pass http://${IPV4_NETWORK}.248:20000; diff --git a/data/conf/nginx/templates/sogo_eas.template b/data/conf/nginx/templates/sogo_eas.template new file mode 100644 index 00000000..3cea9f98 --- /dev/null +++ b/data/conf/nginx/templates/sogo_eas.template @@ -0,0 +1 @@ +proxy_pass http://${IPV4_NETWORK}.248:20000/SOGo/Microsoft-Server-ActiveSync; diff --git a/data/conf/nginx/templates/sogo_proxy.template b/data/conf/nginx/templates/sogo_proxy.template new file mode 100644 index 00000000..e69de29b diff --git a/docker-compose.yml b/docker-compose.yml index df77ba9c..8bdde1a7 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -14,7 +14,7 @@ services: - net.ipv6.conf.all.disable_ipv6=${SYSCTL_IPV6_DISABLED:-0} networks: mailcow-network: - ipv4_address: ${IPV4_NETWORK}.254 + ipv4_address: ${IPV4_NETWORK:-172.22.1}.254 aliases: - unbound @@ -31,7 +31,7 @@ services: - MYSQL_PASSWORD=${DBPASS} restart: always dns: - - ${IPV4_NETWORK}.254 + - ${IPV4_NETWORK:-172.22.1}.254 ports: - "${SQL_PORT:-127.0.0.1:13306}:3306" sysctls: @@ -49,12 +49,12 @@ services: environment: - TZ=${TZ} dns: - - ${IPV4_NETWORK}.254 + - ${IPV4_NETWORK:-172.22.1}.254 sysctls: - net.ipv6.conf.all.disable_ipv6=${SYSCTL_IPV6_DISABLED:-0} networks: mailcow-network: - ipv4_address: ${IPV4_NETWORK}.249 + ipv4_address: ${IPV4_NETWORK:-172.22.1}.249 aliases: - redis @@ -68,7 +68,7 @@ services: volumes: - ./data/conf/clamav/:/etc/clamav/ dns: - - ${IPV4_NETWORK}.254 + - ${IPV4_NETWORK:-172.22.1}.254 sysctls: - net.ipv6.conf.all.disable_ipv6=${SYSCTL_IPV6_DISABLED:-0} networks: @@ -94,7 +94,7 @@ services: sysctls: - net.ipv6.conf.all.disable_ipv6=${SYSCTL_IPV6_DISABLED:-0} dns: - - ${IPV4_NETWORK}.254 + - ${IPV4_NETWORK:-172.22.1}.254 hostname: rspamd networks: mailcow-network: @@ -135,7 +135,7 @@ services: sysctls: - net.ipv6.conf.all.disable_ipv6=${SYSCTL_IPV6_DISABLED:-0} dns: - - ${IPV4_NETWORK}.254 + - ${IPV4_NETWORK:-172.22.1}.254 networks: mailcow-network: aliases: @@ -157,9 +157,10 @@ services: sysctls: - net.ipv6.conf.all.disable_ipv6=${SYSCTL_IPV6_DISABLED:-0} dns: - - ${IPV4_NETWORK}.254 + - ${IPV4_NETWORK:-172.22.1}.254 networks: mailcow-network: + ipv4_address: ${IPV4_NETWORK:-172.22.1}.248 aliases: - sogo @@ -195,7 +196,7 @@ services: soft: 20000 hard: 40000 dns: - - ${IPV4_NETWORK}.254 + - ${IPV4_NETWORK:-172.22.1}.254 sysctls: - net.ipv6.conf.all.disable_ipv6=${SYSCTL_IPV6_DISABLED:-0} hostname: ${MAILCOW_HOSTNAME} @@ -226,7 +227,7 @@ services: - "${SUBMISSION_PORT:-587}:587" restart: always dns: - - ${IPV4_NETWORK}.254 + - ${IPV4_NETWORK:-172.22.1}.254 sysctls: - net.ipv6.conf.all.disable_ipv6=${SYSCTL_IPV6_DISABLED:-0} hostname: ${MAILCOW_HOSTNAME} @@ -241,7 +242,7 @@ services: sysctls: - net.ipv6.conf.all.disable_ipv6=${SYSCTL_IPV6_DISABLED:-0} dns: - - ${IPV4_NETWORK}.254 + - ${IPV4_NETWORK:-172.22.1}.254 networks: mailcow-network: aliases: @@ -256,6 +257,8 @@ services: command: /bin/sh -c "envsubst < /etc/nginx/conf.d/templates/listen_plain.template > /etc/nginx/conf.d/listen_plain.active && envsubst < /etc/nginx/conf.d/templates/listen_ssl.template > /etc/nginx/conf.d/listen_ssl.active && envsubst < /etc/nginx/conf.d/templates/server_name.template > /etc/nginx/conf.d/server_name.active && + envsubst < /etc/nginx/conf.d/templates/sogo.template > /etc/nginx/conf.d/sogo.active && + envsubst < /etc/nginx/conf.d/templates/sogo_eas.template > /etc/nginx/conf.d/sogo_eas.active && nginx -qt && until ping phpfpm -c1 > /dev/null; do sleep 1; done && until ping sogo -c1 > /dev/null; do sleep 1; done && @@ -265,6 +268,7 @@ services: - HTTPS_PORT=${HTTPS_PORT:-443} - HTTP_PORT=${HTTP_PORT:-80} - MAILCOW_HOSTNAME=${MAILCOW_HOSTNAME} + - IPV4_NETWORK= volumes: - ./data/web:/web:ro - ./data/conf/rspamd/dynmaps:/dynmaps:ro @@ -278,7 +282,7 @@ services: sysctls: - net.ipv6.conf.all.disable_ipv6=${SYSCTL_IPV6_DISABLED:-0} dns: - - ${IPV4_NETWORK}.254 + - ${IPV4_NETWORK:-172.22.1}.254 networks: mailcow-network: aliases: @@ -293,7 +297,7 @@ services: sysctls: - net.ipv6.conf.all.disable_ipv6=${SYSCTL_IPV6_DISABLED:-0} dns: - - ${IPV4_NETWORK}.254 + - ${IPV4_NETWORK:-172.22.1}.254 environment: - LOG_LINES=${LOG_LINES} - ADDITIONAL_SAN=${ADDITIONAL_SAN} @@ -327,13 +331,13 @@ services: privileged: true environment: - TZ=${TZ} - - IPV4_NETWORK=${IPV4_NETWORK} + - IPV4_NETWORK=${IPV4_NETWORK:-172.22.1} - SNAT_TO_SOURCE=${SNAT_TO_SOURCE:-n} network_mode: "host" sysctls: - net.ipv6.conf.all.disable_ipv6=${SYSCTL_IPV6_DISABLED:-0} dns: - - ${IPV4_NETWORK}.254 + - ${IPV4_NETWORK:-172.22.1}.254 volumes: - /lib/modules:/lib/modules:ro @@ -395,8 +399,8 @@ networks: ipam: driver: default config: - - subnet: ${IPV4_NETWORK}.0/24 - - subnet: ${IPV6_NETWORK} + - subnet: ${IPV4_NETWORK:-172.22.1}.0/24 + - subnet: ${IPV6_NETWORK:-fd4d:6169:6c63:6f77::/64} volumes: vmail-vol-1: From a3d9f5a984f8f148c77a3ed628996354f3655080 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Peters?= Date: Mon, 12 Feb 2018 21:36:55 +0100 Subject: [PATCH 061/107] [Compose] Add missing var --- docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index 8bdde1a7..6252727d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -268,7 +268,7 @@ services: - HTTPS_PORT=${HTTPS_PORT:-443} - HTTP_PORT=${HTTP_PORT:-80} - MAILCOW_HOSTNAME=${MAILCOW_HOSTNAME} - - IPV4_NETWORK= + - IPV4_NETWORK=${IPV4_NETWORK:-172.22.1} volumes: - ./data/web:/web:ro - ./data/conf/rspamd/dynmaps:/dynmaps:ro From 63f7e5930d34273ccf6ab63bc4d3d3002ba665c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Peters?= Date: Tue, 13 Feb 2018 09:07:44 +0100 Subject: [PATCH 062/107] [Nginx] Fix EAS --- data/conf/nginx/site.conf | 2 +- data/conf/nginx/templates/sogo_proxy.template | 0 2 files changed, 1 insertion(+), 1 deletion(-) delete mode 100644 data/conf/nginx/templates/sogo_proxy.template diff --git a/data/conf/nginx/site.conf b/data/conf/nginx/site.conf index e617f77f..bb2ea266 100644 --- a/data/conf/nginx/site.conf +++ b/data/conf/nginx/site.conf @@ -283,7 +283,7 @@ server { } location ^~ /Microsoft-Server-ActiveSync { - include /etc/nginx/conf.d/templates/sogo_proxy.template; + include /etc/nginx/conf.d/templates/sogo_eas.template; proxy_connect_timeout 1000; proxy_next_upstream timeout error; proxy_send_timeout 1000; diff --git a/data/conf/nginx/templates/sogo_proxy.template b/data/conf/nginx/templates/sogo_proxy.template deleted file mode 100644 index e69de29b..00000000 From 943598f7051a7a3b0da487132ad368c50ef21c5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Peters?= Date: Tue, 13 Feb 2018 09:12:54 +0100 Subject: [PATCH 063/107] [Nginx] Fix EAS... --- data/conf/nginx/site.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/conf/nginx/site.conf b/data/conf/nginx/site.conf index bb2ea266..1210d18e 100644 --- a/data/conf/nginx/site.conf +++ b/data/conf/nginx/site.conf @@ -283,7 +283,7 @@ server { } location ^~ /Microsoft-Server-ActiveSync { - include /etc/nginx/conf.d/templates/sogo_eas.template; + include /etc/nginx/conf.d/sogo_eas.active; proxy_connect_timeout 1000; proxy_next_upstream timeout error; proxy_send_timeout 1000; From a0cdc1e4ff0ab1f891327000a23a58a50f270c00 Mon Sep 17 00:00:00 2001 From: Kristian Klausen Date: Tue, 13 Feb 2018 23:45:49 +0100 Subject: [PATCH 064/107] Remove "empty" folders There seems to be no reason for this empty folders. --- data/Dockerfiles/memcached/.empty | 0 data/Dockerfiles/mysql/.empty | 0 data/Dockerfiles/nginx/.empty | 0 data/Dockerfiles/redis/.empty | 0 4 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 data/Dockerfiles/memcached/.empty delete mode 100644 data/Dockerfiles/mysql/.empty delete mode 100644 data/Dockerfiles/nginx/.empty delete mode 100644 data/Dockerfiles/redis/.empty diff --git a/data/Dockerfiles/memcached/.empty b/data/Dockerfiles/memcached/.empty deleted file mode 100644 index e69de29b..00000000 diff --git a/data/Dockerfiles/mysql/.empty b/data/Dockerfiles/mysql/.empty deleted file mode 100644 index e69de29b..00000000 diff --git a/data/Dockerfiles/nginx/.empty b/data/Dockerfiles/nginx/.empty deleted file mode 100644 index e69de29b..00000000 diff --git a/data/Dockerfiles/redis/.empty b/data/Dockerfiles/redis/.empty deleted file mode 100644 index e69de29b..00000000 From e186e350ef4272444710307436ff7137a50d991c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Peters?= Date: Wed, 14 Feb 2018 09:09:17 +0100 Subject: [PATCH 065/107] [Nginx] Fixes #1033 --- data/conf/nginx/site.conf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/conf/nginx/site.conf b/data/conf/nginx/site.conf index bb327a65..52ddf303 100644 --- a/data/conf/nginx/site.conf +++ b/data/conf/nginx/site.conf @@ -179,7 +179,7 @@ server { allow all; } - location (^/SOGo/so/ControlPanel/Products/[^/]*UI/Resources/.*\.(jpg|png|gif|css|js)$ { + location (^/SOGo/so/ControlPanel/Products/[^/]*UI/Resources/.*\.(jpg|png|gif|css|js)$) { proxy_pass http://sogo:9192/$1.SOGo/Resources/$2; proxy_set_header Host $http_host; proxy_cache sogo; @@ -364,7 +364,7 @@ server { allow all; } - location (^/SOGo/so/ControlPanel/Products/[^/]*UI/Resources/.*\.(jpg|png|gif|css|js)$ { + location (^/SOGo/so/ControlPanel/Products/[^/]*UI/Resources/.*\.(jpg|png|gif|css|js)$) { proxy_pass http://sogo:9192/$1.SOGo/Resources/$2; proxy_set_header Host $http_host; proxy_cache sogo; From 0bfd0838c27bb1dc2ac532485b4cadb48647b2bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Peters?= Date: Wed, 14 Feb 2018 11:26:55 +0100 Subject: [PATCH 066/107] [SOGo] Increase workers again --- data/conf/sogo/sogo.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/conf/sogo/sogo.conf b/data/conf/sogo/sogo.conf index 0acb8a80..195c3dfd 100644 --- a/data/conf/sogo/sogo.conf +++ b/data/conf/sogo/sogo.conf @@ -5,7 +5,7 @@ PrivateDAndTViewer ); - WOWorkersCount = "7"; + WOWorkersCount = "14"; SOGoACLsSendEMailNotifications = YES; SOGoAppointmentSendEMailNotifications = YES; SOGoDraftsFolderName = "Drafts"; From 1e40472017ce7a9158c6d2a9836bf82dfff1604d Mon Sep 17 00:00:00 2001 From: eXtremeSHOK Date: Wed, 14 Feb 2018 14:38:06 +0200 Subject: [PATCH 067/107] Enable maildir compression Currently the plugin is loaded, but actual compression is not enabled. https://wiki.dovecot.org/Plugins/Zlib --- data/conf/dovecot/dovecot.conf | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/data/conf/dovecot/dovecot.conf b/data/conf/dovecot/dovecot.conf index d35f0186..b3e45171 100644 --- a/data/conf/dovecot/dovecot.conf +++ b/data/conf/dovecot/dovecot.conf @@ -280,7 +280,11 @@ plugin { #mail_crypt_global_private_key = Date: Wed, 14 Feb 2018 17:08:03 +0200 Subject: [PATCH 068/107] Support for alpine linux detects if cp and grep are the non BusyBox versions --- generate_config.sh | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/generate_config.sh b/generate_config.sh index 5b2d23f4..b49a6a21 100755 --- a/generate_config.sh +++ b/generate_config.sh @@ -1,5 +1,14 @@ #!/bin/bash +if grep --help 2>&1 | head -n 1 | grep -q -i "busybox" ; then + echo "BusybBox grep detected, please install gnu grep, \"apk add --upgrade grep\"" + exit 1 +fi +if cp --help 2>&1 | head -n 1 | grep -q -i "busybox" ; then + echo "BusybBox cp detected, please install coreutils, \"apk add --upgrade coreutils\"" + exit 1 +fi + if [[ -f mailcow.conf ]]; then read -r -p "A config file exists and will be overwritten, are you sure you want to contine? [y/N] " response case $response in From 63002cbb74373140afd705996c9b2a46d2ac3cb1 Mon Sep 17 00:00:00 2001 From: Kristian Klausen Date: Thu, 15 Feb 2018 20:22:02 +0100 Subject: [PATCH 069/107] [Nginx] Reduce config duplication It does not make sense having a seperate server block for both http and https. According to the nginx doc [1], using the same server block for both should work. [1] http://nginx.org/en/docs/http/configuring_https_servers.html#single_http_https_server --- data/conf/nginx/site.conf | 179 +----------------- data/conf/nginx/templates/listen_ssl.template | 4 +- 2 files changed, 4 insertions(+), 179 deletions(-) diff --git a/data/conf/nginx/site.conf b/data/conf/nginx/site.conf index 52ddf303..83f913f6 100644 --- a/data/conf/nginx/site.conf +++ b/data/conf/nginx/site.conf @@ -21,182 +21,6 @@ server { charset utf-8; override_charset on; - add_header Strict-Transport-Security "max-age=15768000; includeSubDomains"; - add_header X-Content-Type-Options nosniff; - add_header X-XSS-Protection "1; mode=block"; - add_header X-Robots-Tag none; - add_header X-Download-Options noopen; - add_header X-Frame-Options "SAMEORIGIN"; - add_header X-Permitted-Cross-Domain-Policies none; - - index index.php index.html; - - include /etc/nginx/conf.d/listen_plain.active; - include /etc/nginx/conf.d/server_name.active; - - error_log /var/log/nginx/error.log; - access_log /var/log/nginx/access.log; - absolute_redirect off; - root /web; - - location ~ ^/api/v1/(.*)$ { - try_files $uri $uri/ /json_api.php?query=$1; - } - - location ^~ /.well-known/acme-challenge/ { - allow all; - default_type "text/plain"; - } - - # If behind reverse proxy, forwards the correct IP - set_real_ip_from 10.0.0.0/8; - set_real_ip_from 172.16.0.0/12; - set_real_ip_from 192.168.0.0/16; - set_real_ip_from fc00::/7; - real_ip_header X-Forwarded-For; - real_ip_recursive on; - - rewrite ^/.well-known/caldav$ /SOGo/dav/ permanent; - rewrite ^/.well-known/carddav$ /SOGo/dav/ permanent; - - location ^~ /principals { - return 301 /SOGo/dav; - } - - location ~ \.php$ { - try_files $uri =404; - fastcgi_split_path_info ^(.+\.php)(/.+)$; - fastcgi_pass phpfpm:9000; - fastcgi_index index.php; - include /etc/nginx/fastcgi_params; - fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; - fastcgi_param PATH_INFO $fastcgi_path_info; - fastcgi_param PHP_VALUE "max_execution_time = 1200 - max_input_time = 1200 - memory_limit = 64M"; - fastcgi_read_timeout 1200; - } - - location /rspamd/ { - proxy_pass http://rspamd:11334/; - proxy_set_header Host $http_host; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Real-IP $remote_addr; - proxy_redirect off; - expires $expires; - } - - location ~* ^/Autodiscover/Autodiscover.xml { - fastcgi_split_path_info ^(.+\.php)(/.+)$; - fastcgi_pass phpfpm:9000; - include /etc/nginx/fastcgi_params; - fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; - try_files /autodiscover.php =404; - } - - location ~* ^/Autodiscover/Autodiscover.json { - fastcgi_split_path_info ^(.+\.php)(/.+)$; - fastcgi_pass phpfpm:9000; - include /etc/nginx/fastcgi_params; - fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; - try_files /autodiscover-json.php =404; - } - - location ~ /(?:m|M)ail/(?:c|C)onfig-v1.1.xml { - fastcgi_split_path_info ^(.+\.php)(/.+)$; - fastcgi_pass phpfpm:9000; - include /etc/nginx/fastcgi_params; - fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; - try_files /autoconfig.php =404; - } - - location ^~ /Microsoft-Server-ActiveSync { - include /etc/nginx/conf.d/sogo_eas.active; - proxy_connect_timeout 1000; - proxy_next_upstream timeout error; - proxy_send_timeout 1000; - proxy_read_timeout 1000; - proxy_buffer_size 8k; - proxy_buffers 4 32k; - proxy_temp_file_write_size 64k; - proxy_busy_buffers_size 64k; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header Host $http_host; - proxy_set_header x-webobjects-server-protocol HTTP/1.0; - proxy_set_header x-webobjects-remote-host $remote_addr; - proxy_set_header x-webobjects-server-name $server_name; - proxy_set_header x-webobjects-server-url $client_req_scheme://$http_host; - proxy_set_header x-webobjects-server-port $server_port; - client_body_buffer_size 128k; - client_max_body_size 0; - } - - location ^~ /SOGo { - include /etc/nginx/conf.d/sogo.active; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header Host $http_host; - proxy_set_header x-webobjects-server-protocol HTTP/1.0; - proxy_set_header x-webobjects-remote-host $remote_addr; - proxy_set_header x-webobjects-server-name $server_name; - proxy_set_header x-webobjects-server-url $client_req_scheme://$http_host; - proxy_set_header x-webobjects-server-port $server_port; - client_body_buffer_size 128k; - client_max_body_size 0; - break; - } - - location /SOGo.woa/WebServerResources/ { - proxy_pass http://sogo:9192/WebServerResources/; - proxy_set_header Host $http_host; - proxy_cache sogo; - proxy_cache_valid 200 1d; - proxy_cache_use_stale error timeout invalid_header updating http_500 http_502 http_503 http_504; - #alias /usr/lib/GNUstep/SOGo/WebServerResources/; - expires $expires; - allow all; - } - - location /.woa/WebServerResources/ { - proxy_pass http://sogo:9192/WebServerResources/; - proxy_set_header Host $http_host; - proxy_cache sogo; - proxy_cache_valid 200 1d; - proxy_cache_use_stale error timeout invalid_header updating http_500 http_502 http_503 http_504; - #alias /usr/lib/GNUstep/SOGo/WebServerResources/; - expires $expires; - allow all; - } - - location /SOGo/WebServerResources/ { - proxy_pass http://sogo:9192/WebServerResources/; - proxy_set_header Host $http_host; - proxy_cache sogo; - proxy_cache_valid 200 1d; - proxy_cache_use_stale error timeout invalid_header updating http_500 http_502 http_503 http_504; - #alias /usr/lib/GNUstep/SOGo/WebServerResources/; - allow all; - } - - location (^/SOGo/so/ControlPanel/Products/[^/]*UI/Resources/.*\.(jpg|png|gif|css|js)$) { - proxy_pass http://sogo:9192/$1.SOGo/Resources/$2; - proxy_set_header Host $http_host; - proxy_cache sogo; - proxy_cache_valid 200 1d; - proxy_cache_use_stale error timeout invalid_header updating http_500 http_502 http_503 http_504; - #alias /usr/lib/GNUstep/SOGo/$1.SOGo/Resources/$2; - } - - include /etc/nginx/conf.d/site.*.custom; -} - -server { - include /etc/nginx/mime.types; - charset utf-8; - override_charset on; - - ssl on; ssl_certificate /etc/ssl/mail/cert.pem; ssl_certificate_key /etc/ssl/mail/key.pem; ssl_protocols TLSv1.2; @@ -216,6 +40,7 @@ server { index index.php index.html; + include /etc/nginx/conf.d/listen_plain.active; include /etc/nginx/conf.d/listen_ssl.active; include /etc/nginx/conf.d/server_name.active; @@ -245,7 +70,7 @@ server { rewrite ^/.well-known/carddav$ /SOGo/dav/ permanent; location ^~ /principals { - return 301 /SOGo/dav; + return 301 /SOGo/dav; } location ~ \.php$ { diff --git a/data/conf/nginx/templates/listen_ssl.template b/data/conf/nginx/templates/listen_ssl.template index bbd51db0..93ec80c6 100644 --- a/data/conf/nginx/templates/listen_ssl.template +++ b/data/conf/nginx/templates/listen_ssl.template @@ -1,2 +1,2 @@ -listen ${HTTPS_PORT} http2; -listen [::]:${HTTPS_PORT} http2; +listen ${HTTPS_PORT} ssl http2; +listen [::]:${HTTPS_PORT} ssl http2; From 03031516e98916e93575c2f3dd359fccd98e6922 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Peters?= Date: Fri, 16 Feb 2018 22:39:33 +0100 Subject: [PATCH 070/107] [Web] Fixes #1055 and changes location.reload to window = xy in footer script --- data/web/inc/ajax/destroy_tfa_auth.php | 6 ++++++ data/web/inc/footer.inc.php | 13 ++++++++++++- 2 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 data/web/inc/ajax/destroy_tfa_auth.php diff --git a/data/web/inc/ajax/destroy_tfa_auth.php b/data/web/inc/ajax/destroy_tfa_auth.php new file mode 100644 index 00000000..72c7f1e3 --- /dev/null +++ b/data/web/inc/ajax/destroy_tfa_auth.php @@ -0,0 +1,6 @@ + diff --git a/data/web/inc/footer.inc.php b/data/web/inc/footer.inc.php index 3c92c28e..a4974604 100644 --- a/data/web/inc/footer.inc.php +++ b/data/web/inc/footer.inc.php @@ -79,6 +79,17 @@ $(document).ready(function() { }); } }); + $('#ConfirmTFAModal').on('hidden.bs.modal', function(){ + $.ajax({ + type: "GET", + cache: false, + dataType: 'script', + url: '/inc/ajax/destroy_tfa_auth.php', + complete: function(data){ + window.location = window.location.href.split("#")[0]; + } + }); + }); // Set TFA modals @@ -205,7 +216,7 @@ $(document).ready(function() { $('#triggerRestartContainer').html(' '); $('#statusTriggerRestartContainer2').append(data); $('#triggerRestartContainer').html(' '); - location.reload(); + window.location = window.location.href.split("#")[0]; } }); }); From 2865c892a684fe1d614cf709173ed5ddb0be6ce0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Peters?= Date: Fri, 16 Feb 2018 22:40:51 +0100 Subject: [PATCH 071/107] [Multi] Fixes #1058 by including a 'force password update' option and also introduces a attributes json object to be used for further mailbox configurations in the future --- data/Dockerfiles/dovecot/docker-entrypoint.sh | 2 +- data/Dockerfiles/sogo/bootstrap-sogo.sh | 3 +-- data/web/edit.php | 8 ++++++++ data/web/inc/functions.inc.php | 2 +- data/web/inc/functions.mailbox.inc.php | 20 ++++++------------- data/web/inc/init_db.inc.php | 4 ++-- data/web/lang/lang.de.php | 3 +++ data/web/lang/lang.en.php | 3 +++ data/web/user.php | 3 +++ docker-compose.yml | 4 ++-- 10 files changed, 30 insertions(+), 22 deletions(-) diff --git a/data/Dockerfiles/dovecot/docker-entrypoint.sh b/data/Dockerfiles/dovecot/docker-entrypoint.sh index 9f8f5313..469edacc 100755 --- a/data/Dockerfiles/dovecot/docker-entrypoint.sh +++ b/data/Dockerfiles/dovecot/docker-entrypoint.sh @@ -82,7 +82,7 @@ cat < /usr/local/etc/dovecot/sql/dovecot-dict-sql-passdb.conf driver = mysql connect = "host=mysql dbname=${DBNAME} user=${DBUSER} password=${DBPASS}" default_pass_scheme = SSHA256 -password_query = SELECT password FROM mailbox WHERE username = '%u' AND domain IN (SELECT domain FROM domain WHERE domain='%d' AND active='1') +password_query = SELECT password FROM mailbox WHERE username = '%u' AND domain IN (SELECT domain FROM domain WHERE domain='%d' AND active='1') AND JSON_EXTRACT(attributes, "$.force_pw_update") != 1 user_query = SELECT CONCAT('maildir:/var/vmail/',maildir) AS mail, 5000 AS uid, 5000 AS gid, concat('*:bytes=', quota) AS quota_rule FROM mailbox WHERE username = '%u' AND active = '1' iterate_query = SELECT username FROM mailbox WHERE active='1'; EOF diff --git a/data/Dockerfiles/sogo/bootstrap-sogo.sh b/data/Dockerfiles/sogo/bootstrap-sogo.sh index c360e30e..07365981 100755 --- a/data/Dockerfiles/sogo/bootstrap-sogo.sh +++ b/data/Dockerfiles/sogo/bootstrap-sogo.sh @@ -19,14 +19,13 @@ mysql --host mysql -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "DROP VIEW IF EXISTS so mysql --host mysql -u ${DBUSER} -p${DBPASS} ${DBNAME} << EOF CREATE VIEW sogo_view (c_uid, domain, c_name, c_password, c_cn, mail, aliases, ad_aliases, home, kind, multiple_bookings) AS -SELECT mailbox.username, mailbox.domain, mailbox.username, mailbox.password, mailbox.name, mailbox.username, IFNULL(GROUP_CONCAT(ga.aliases SEPARATOR ' '), ''), IFNULL(gda.ad_alias, ''), CONCAT('/var/vmail/', maildir), mailbox.kind, mailbox.multiple_bookings FROM mailbox +SELECT mailbox.username, mailbox.domain, mailbox.username, if(json_extract(attributes, '$.force_pw_update') = '0', password, 'invalid'), mailbox.name, mailbox.username, IFNULL(GROUP_CONCAT(ga.aliases SEPARATOR ' '), ''), IFNULL(gda.ad_alias, ''), CONCAT('/var/vmail/', maildir), mailbox.kind, mailbox.multiple_bookings FROM mailbox LEFT OUTER JOIN grouped_mail_aliases ga ON ga.username REGEXP CONCAT('(^|,)', mailbox.username, '($|,)') LEFT OUTER JOIN grouped_domain_alias_address gda ON gda.username = mailbox.username WHERE mailbox.active = '1' GROUP BY mailbox.username; EOF - mkdir -p /var/lib/sogo/GNUstep/Defaults/ # Generate plist header with timezone data diff --git a/data/web/edit.php b/data/web/edit.php index 8374dc76..8a12c665 100644 --- a/data/web/edit.php +++ b/data/web/edit.php @@ -390,6 +390,7 @@ if (isset($_SESSION['mailcow_cc_role'])) {
    +
    @@ -476,6 +477,13 @@ if (isset($_SESSION['mailcow_cc_role'])) {
    +
    +
    +
    + +
    +
    +
    diff --git a/data/web/inc/functions.inc.php b/data/web/inc/functions.inc.php index f7f08403..25d88d78 100644 --- a/data/web/inc/functions.inc.php +++ b/data/web/inc/functions.inc.php @@ -414,7 +414,7 @@ function edit_user_account($postarray) { } $password_hashed = hash_password($password_new); try { - $stmt = $pdo->prepare("UPDATE `mailbox` SET `password` = :password_hashed WHERE `username` = :username"); + $stmt = $pdo->prepare("UPDATE `mailbox` SET `password` = :password_hashed, `attributes` = JSON_SET(`attributes`, '$.force_pw_update', '0') WHERE `username` = :username"); $stmt->execute(array( ':password_hashed' => $password_hashed, ':username' => $username diff --git a/data/web/inc/functions.mailbox.inc.php b/data/web/inc/functions.mailbox.inc.php index 5bd9ac58..89e02166 100644 --- a/data/web/inc/functions.mailbox.inc.php +++ b/data/web/inc/functions.mailbox.inc.php @@ -1954,6 +1954,7 @@ function mailbox($_action, $_type, $_data = null, $attr = null) { $is_now = mailbox('get', 'mailbox_details', $username); if (!empty($is_now)) { $active = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active_int']; + (int)$force_pw_update = (isset($_data['force_pw_update'])) ? intval($_data['force_pw_update']) : intval($is_now['attributes']['force_pw_update']); $name = (!empty($_data['name'])) ? $_data['name'] : $is_now['name']; $domain = $is_now['domain']; $quota_m = (!empty($_data['quota'])) ? $_data['quota'] : ($is_now['quota'] / 1048576); @@ -2113,24 +2114,11 @@ function mailbox($_action, $_type, $_data = null, $attr = null) { } $password_hashed = hash_password($password); try { - $stmt = $pdo->prepare("UPDATE `alias` SET - `active` = :active - WHERE `address` = :address"); - $stmt->execute(array( - ':address' => $username, - ':active' => $active - )); $stmt = $pdo->prepare("UPDATE `mailbox` SET - `active` = :active, `password` = :password_hashed, - `name`= :name, - `quota` = :quota_b WHERE `username` = :username"); $stmt->execute(array( ':password_hashed' => $password_hashed, - ':active' => $active, - ':name' => $name, - ':quota_b' => $quota_b, ':username' => $username )); } @@ -2153,12 +2141,14 @@ function mailbox($_action, $_type, $_data = null, $attr = null) { $stmt = $pdo->prepare("UPDATE `mailbox` SET `active` = :active, `name`= :name, - `quota` = :quota_b + `quota` = :quota_b, + `attributes` = JSON_SET(`attributes`, '$.force_pw_update', :force_pw_update) WHERE `username` = :username"); $stmt->execute(array( ':active' => $active, ':name' => $name, ':quota_b' => $quota_b, + ':force_pw_update' => $force_pw_update, ':username' => $username )); } @@ -3070,6 +3060,7 @@ function mailbox($_action, $_type, $_data = null, $attr = null) { `mailbox`.`domain`, `mailbox`.`quota`, `quota2`.`bytes`, + `attributes`, `quota2`.`messages` FROM `mailbox`, `quota2`, `domain` WHERE `mailbox`.`kind` NOT REGEXP 'location|thing|group' AND `mailbox`.`username` = `quota2`.`username` AND `domain`.`domain` = `mailbox`.`domain` AND `mailbox`.`username` = :mailbox"); @@ -3097,6 +3088,7 @@ function mailbox($_action, $_type, $_data = null, $attr = null) { $mailboxdata['active_int'] = $row['active_int']; $mailboxdata['domain'] = $row['domain']; $mailboxdata['quota'] = $row['quota']; + $mailboxdata['attributes'] = json_decode($row['attributes'], true); $mailboxdata['quota_used'] = intval($row['bytes']); $mailboxdata['percent_in_use'] = round((intval($row['bytes']) / intval($row['quota'])) * 100); $mailboxdata['messages'] = $row['messages']; diff --git a/data/web/inc/init_db.inc.php b/data/web/inc/init_db.inc.php index 24729c83..58091721 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 = "08022018_1219"; + $db_version = "16022018_1419"; $stmt = $pdo->query("SHOW TABLES LIKE 'versions'"); $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); @@ -191,9 +191,9 @@ function init_db_schema() { "domain" => "VARCHAR(255) NOT NULL", "tls_enforce_in" => "TINYINT(1) NOT NULL DEFAULT '0'", "tls_enforce_out" => "TINYINT(1) NOT NULL DEFAULT '0'", + "attributes" => "JSON DEFAULT '{}'", "kind" => "VARCHAR(100) NOT NULL DEFAULT ''", "multiple_bookings" => "TINYINT(1) NOT NULL DEFAULT '0'", - "wants_tagged_subject" => "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.php b/data/web/lang/lang.de.php index a874eea1..8f4733b9 100644 --- a/data/web/lang/lang.de.php +++ b/data/web/lang/lang.de.php @@ -100,6 +100,7 @@ $lang['warning']['spam_alias_temp_error'] = 'Kann zur Zeit keinen Spam-Alias ers $lang['danger']['spam_alias_max_exceeded'] = 'Maximale Anzahl an Spam-Alias-Adressen erreicht'; $lang['danger']['validity_missing'] = 'Bitte geben Sie eine Gültigkeitsdauer an'; $lang['user']['loading'] = "Lade..."; +$lang['user']['force_pw_update'] = 'Das Passwort für diesen Benutzer muss geändert werden, damit die Zugriffssperre auf die Groupwarekomponenten wieder freigeschaltet wird.'; $lang['user']['active_sieve'] = "Aktiver Filter"; $lang['user']['show_sieve_filters'] = "Zeige aktiven Filter des Benutzers"; $lang['user']['no_active_filter'] = "Kein aktiver Filter vorhanden"; @@ -320,6 +321,8 @@ $lang['edit']['max_mailboxes'] = 'Max. Mailboxanzahl:'; $lang['edit']['title'] = 'Objekt bearbeiten'; $lang['edit']['target_address'] = 'Ziel-Adresse(n) (getrennt durch Komma):'; $lang['edit']['active'] = 'Aktiv'; +$lang['edit']['force_pw_update'] = 'Erzwinge Passwortänderung bei nächstem Login'; +$lang['edit']['force_pw_update_info'] = 'Dem Benutzer wird lediglich der Zugang zur mailcow UI ermöglicht.'; $lang['edit']['target_domain'] = 'Ziel-Domain:'; $lang['edit']['password'] = 'Passwort:'; $lang['edit']['ratelimit'] = 'Limit ausgehender Nachrichten/Stunde:'; diff --git a/data/web/lang/lang.en.php b/data/web/lang/lang.en.php index 3fc99872..474ad773 100644 --- a/data/web/lang/lang.en.php +++ b/data/web/lang/lang.en.php @@ -100,6 +100,7 @@ $lang['warning']['spam_alias_temp_error'] = "Temporary error: Cannot add spam al $lang['danger']['spam_alias_max_exceeded'] = "Max. allowed spam alias addresses exceeded"; $lang['danger']['validity_missing'] = 'Please assign a period of validity'; $lang['user']['loading'] = "Loading..."; +$lang['user']['force_pw_update'] = 'You must set a new password to be able to access groupware related services.'; $lang['user']['active_sieve'] = "Active filter"; $lang['user']['show_sieve_filters'] = "Show active user sieve filter"; $lang['user']['no_active_filter'] = "No active filter available"; @@ -321,6 +322,8 @@ $lang['edit']['max_mailboxes'] = 'Max. possible mailboxes'; $lang['edit']['title'] = 'Edit object'; $lang['edit']['target_address'] = 'Goto address/es (comma-separated)'; $lang['edit']['active'] = 'Active'; +$lang['edit']['force_pw_update'] = 'Force password update at next login'; +$lang['edit']['force_pw_update_info'] = 'This user will only be able to login to mailcow UI.'; $lang['edit']['target_domain'] = 'Target domain'; $lang['edit']['password'] = 'Password'; $lang['edit']['ratelimit'] = 'Outgoing rate limit/h'; diff --git a/data/web/user.php b/data/web/user.php index 0fb0875f..6c9069e5 100644 --- a/data/web/user.php +++ b/data/web/user.php @@ -89,6 +89,9 @@ elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == '
    + +
    +

    []

    []

    diff --git a/docker-compose.yml b/docker-compose.yml index 9439a345..e4d29209 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -128,7 +128,7 @@ services: - phpfpm sogo-mailcow: - image: mailcow/sogo:1.16 + image: mailcow/sogo:1.17 build: ./data/Dockerfiles/sogo environment: - DBNAME=${DBNAME} @@ -149,7 +149,7 @@ services: - sogo dovecot-mailcow: - image: mailcow/dovecot:1.20 + image: mailcow/dovecot:1.22 build: ./data/Dockerfiles/dovecot cap_add: - NET_BIND_SERVICE From 2a3040de12e3b333c3d22b1684426e9ea5299168 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Peters?= Date: Fri, 16 Feb 2018 22:42:28 +0100 Subject: [PATCH 072/107] [Web] Add missing info block to force pw update function in edit --- data/web/edit.php | 1 + 1 file changed, 1 insertion(+) diff --git a/data/web/edit.php b/data/web/edit.php index 8a12c665..912570cd 100644 --- a/data/web/edit.php +++ b/data/web/edit.php @@ -481,6 +481,7 @@ if (isset($_SESSION['mailcow_cc_role'])) {
    +
    From 280431f98d2531cb086262ca5a39d3b2de69b987 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Peters?= Date: Sat, 17 Feb 2018 08:51:09 +0100 Subject: [PATCH 073/107] Fix conflict --- docker-compose.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index e2612adc..1016cfde 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -165,11 +165,7 @@ services: - sogo dovecot-mailcow: -<<<<<<< HEAD - image: mailcow/dovecot:1.21 -======= image: mailcow/dovecot:1.22 ->>>>>>> master build: ./data/Dockerfiles/dovecot cap_add: - NET_BIND_SERVICE From 31a9bb446c6fd2e4a5834a6c9f7df4eb5a082f3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Peters?= Date: Sat, 17 Feb 2018 08:51:41 +0100 Subject: [PATCH 074/107] [Netfilter] Fixes a f2boptions not defined error --- data/Dockerfiles/netfilter/server.py | 41 ++++++++++++++-------------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/data/Dockerfiles/netfilter/server.py b/data/Dockerfiles/netfilter/server.py index 3b03eb1b..5bb66547 100644 --- a/data/Dockerfiles/netfilter/server.py +++ b/data/Dockerfiles/netfilter/server.py @@ -25,21 +25,22 @@ RULES[5] = 'SOGo.+ Login from \'([0-9a-f\.:]+)\' for user .+ might not have work RULES[6] = 'mailcow UI: Invalid password for .+ by ([0-9a-f\.:]+)' if not r.get('F2B_OPTIONS'): - f2options = {} - f2options['ban_time'] = int - f2options['max_attempts'] = int - f2options['retry_window'] = int - f2options['netban_ipv4'] = int - f2options['netban_ipv6'] = int - f2options['ban_time'] = r.get('F2B_BAN_TIME') or 1800 - f2options['max_attempts'] = r.get('F2B_MAX_ATTEMPTS') or 10 - f2options['retry_window'] = r.get('F2B_RETRY_WINDOW') or 600 - f2options['netban_ipv4'] = r.get('F2B_NETBAN_IPV4') or 24 - f2options['netban_ipv6'] = r.get('F2B_NETBAN_IPV6') or 64 - r.set('F2B_OPTIONS', json.dumps(f2options, ensure_ascii=False)) + f2boptions = {} + f2boptions['ban_time'] = int + f2boptions['max_attempts'] = int + f2boptions['retry_window'] = int + f2boptions['netban_ipv4'] = int + f2boptions['netban_ipv6'] = int + f2boptions['ban_time'] = r.get('F2B_BAN_TIME') or 1800 + f2boptions['max_attempts'] = r.get('F2B_MAX_ATTEMPTS') or 10 + f2boptions['retry_window'] = r.get('F2B_RETRY_WINDOW') or 600 + f2boptions['netban_ipv4'] = r.get('F2B_NETBAN_IPV4') or 24 + f2boptions['netban_ipv6'] = r.get('F2B_NETBAN_IPV6') or 64 + r.set('F2B_OPTIONS', json.dumps(f2boptions, ensure_ascii=False)) else: try: - f2options = json.loads(r.get('F2B_OPTIONS')) + f2boptions = {} + f2boptions = json.loads(r.get('F2B_OPTIONS')) except ValueError, e: print 'Error loading F2B options: F2B_OPTIONS is not json' raise SystemExit(1) @@ -52,11 +53,11 @@ log = {} quit_now = False def ban(address): - BAN_TIME = int(f2options['ban_time']) - MAX_ATTEMPTS = int(f2options['max_attempts']) - RETRY_WINDOW = int(f2options['retry_window']) - NETBAN_IPV4 = '/' + str(f2options['netban_ipv4']) - NETBAN_IPV6 = '/' + str(f2options['netban_ipv6']) + BAN_TIME = int(f2boptions['ban_time']) + MAX_ATTEMPTS = int(f2boptions['max_attempts']) + RETRY_WINDOW = int(f2boptions['retry_window']) + NETBAN_IPV4 = '/' + str(f2boptions['netban_ipv4']) + NETBAN_IPV6 = '/' + str(f2boptions['netban_ipv6']) WHITELIST = r.hgetall('F2B_WHITELIST') ip = ipaddress.ip_address(address.decode('ascii')) @@ -225,8 +226,8 @@ def snat(snat_target): def autopurge(): while not quit_now: - BAN_TIME = f2options['ban_time'] - MAX_ATTEMPTS = f2options['max_attempts'] + BAN_TIME = f2boptions['ban_time'] + MAX_ATTEMPTS = f2boptions['max_attempts'] QUEUE_UNBAN = r.hgetall('F2B_QUEUE_UNBAN') if QUEUE_UNBAN: for net in QUEUE_UNBAN: From b81930e416a41c3e4a4ee68d9645baadda574c35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Peters?= Date: Sat, 17 Feb 2018 09:50:39 +0100 Subject: [PATCH 075/107] [Web] Database schema test --- data/web/inc/init_db.inc.php | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/data/web/inc/init_db.inc.php b/data/web/inc/init_db.inc.php index ef07d51f..33855380 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 = "16022018_1419"; + $db_version = "17022018_0839"; $stmt = $pdo->query("SHOW TABLES LIKE 'versions'"); $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); @@ -21,6 +21,10 @@ function init_db_schema() { AND active = '1' AND address NOT LIKE '@%' GROUP BY goto;", + "grouped_sender_acl" => "CREATE VIEW grouped_sender_acl (username, send_as_acl) AS + SELECT logged_in_as, IFNULL(GROUP_CONCAT(send_as SEPARATOR ' '), '') AS send_as_acl FROM sender_acl + WHERE send_as NOT LIKE '@%' + GROUP BY logged_in_as;", "grouped_domain_alias_address" => "CREATE VIEW grouped_domain_alias_address (username, ad_alias) AS SELECT username, IFNULL(GROUP_CONCAT(local_part, '@', alias_domain SEPARATOR ' '), '') AS ad_alias FROM mailbox LEFT OUTER JOIN alias_domain ON target_domain=domain @@ -154,7 +158,7 @@ function init_db_schema() { ), "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" ), - "quarantine" => array( + "quarantaine" => array( "cols" => array( "id" => "INT NOT NULL AUTO_INCREMENT", "qid" => "VARCHAR(30) NOT NULL", @@ -245,10 +249,10 @@ 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'", - "quarantine" => "TINYINT(1) NOT NULL DEFAULT '1'", - "bcc_maps" => "TINYINT(1) NOT NULL DEFAULT '0'", + "quarantaine" => "TINYINT(1) NOT NULL DEFAULT '1'", + "bcc_maps" => "TINYINT(1) NOT NULL DEFAULT '1'", "recipient_maps" => "TINYINT(1) NOT NULL DEFAULT '0'", - ), + ), "keys" => array( "fkey" => array( "fk_username" => array( @@ -622,6 +626,7 @@ function init_db_schema() { ); foreach ($tables as $table => $properties) { + // Migrate to quarantine if ($table == 'quarantine') { $stmt = $pdo->query("SHOW TABLES LIKE 'quarantaine'"); $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); @@ -633,7 +638,7 @@ function init_db_schema() { } } } - $stmt = $pdo->query("SHOW TABLES LIKE '" . $table . "'"); + $stmt = $pdo->query("SHOW TABLES LIKE '" . $table . "'"); $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); if ($num_results != 0) { $stmt = $pdo->prepare("SELECT CONCAT('ALTER TABLE ', `table_schema`, '.', `table_name`, ' DROP FOREIGN KEY ', `constraint_name`, ';') AS `FKEY_DROP` FROM `information_schema`.`table_constraints` From 2bdc3f94c0f57f7edbaba8b29e784da49ce892f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Peters?= Date: Sat, 17 Feb 2018 09:50:58 +0100 Subject: [PATCH 076/107] [Web] Database schema test --- data/Dockerfiles/postfix/postfix.sh | 4 ++-- data/web/inc/functions.mailbox.inc.php | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/data/Dockerfiles/postfix/postfix.sh b/data/Dockerfiles/postfix/postfix.sh index 0620404d..2d3d0e64 100755 --- a/data/Dockerfiles/postfix/postfix.sh +++ b/data/Dockerfiles/postfix/postfix.sh @@ -39,7 +39,7 @@ query = SELECT IF(EXISTS( SELECT CONCAT('%u', '@', target_domain) FROM alias_domain WHERE alias_domain='%d' ) - ) AND mailbox.tls_enforce_in = '1' AND mailbox.active = '1' + ) AND json_extract(`attributes`, '$.tls_enforce_in') = '1' AND mailbox.active = '1' ), 'reject_plaintext_session', NULL) AS 'tls_enforce_in'; EOF @@ -58,7 +58,7 @@ query = SELECT GROUP_CONCAT(transport SEPARATOR '') AS transport_maps WHERE alias_domain = '%d' ) ) - AND mailbox.tls_enforce_out = '1' + AND json_extract(`attributes`, '$.tls_enforce_out') = '1' AND mailbox.active = '1' ), 'smtp_enforced_tls:', 'smtp:') AS 'transport' UNION ALL diff --git a/data/web/inc/functions.mailbox.inc.php b/data/web/inc/functions.mailbox.inc.php index c6f3cff2..89e02166 100644 --- a/data/web/inc/functions.mailbox.inc.php +++ b/data/web/inc/functions.mailbox.inc.php @@ -511,8 +511,8 @@ function mailbox($_action, $_type, $_data = null, $attr = null) { if (in_array($address, $gotos)) { continue; } - $domain = idn_to_ascii(substr(strrchr($address, '@'), 1)); - $local_part = substr($address, 0, strripos($address, '@')); + $domain = idn_to_ascii(substr(strstr($address, '@'), 1)); + $local_part = strstr($address, '@', true); $address = $local_part.'@'.$domain; $stmt = $pdo->prepare("SELECT `address` FROM `alias` WHERE `address`= :address OR `address` IN ( @@ -1713,8 +1713,8 @@ function mailbox($_action, $_type, $_data = null, $attr = null) { $gotos = array_filter($gotos); $goto = implode(",", $gotos); } - $domain = idn_to_ascii(substr(strrchr($address, '@'), 1)); - $local_part = substr($address, 0, strripos($address, '@')); + $domain = idn_to_ascii(substr(strstr($address, '@'), 1)); + $local_part = strstr($address, '@', true); if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain)) { $_SESSION['return'] = array( 'type' => 'danger', @@ -3470,7 +3470,7 @@ function mailbox($_action, $_type, $_data = null, $attr = null) { $addresses = $_data['address']; } foreach ($addresses as $address) { - $local_part = substr($address, 0, strripos($address, '@')); + $local_part = strstr($address, '@', true); $domain = mailbox('get', 'alias_details', $address)['domain']; try { $stmt = $pdo->prepare("SELECT `goto` FROM `alias` WHERE `address` = :address"); From e5e4f6705c69775dd34b52a2bfd645e38204279c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Peters?= Date: Sat, 17 Feb 2018 10:10:55 +0100 Subject: [PATCH 077/107] Update init_db.inc.php --- data/web/inc/init_db.inc.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/web/inc/init_db.inc.php b/data/web/inc/init_db.inc.php index 33855380..ebdc06a6 100644 --- a/data/web/inc/init_db.inc.php +++ b/data/web/inc/init_db.inc.php @@ -191,7 +191,7 @@ function init_db_schema() { "domain" => "VARCHAR(255) NOT NULL", "tls_enforce_in" => "TINYINT(1) NOT NULL DEFAULT '0'", "tls_enforce_out" => "TINYINT(1) NOT NULL DEFAULT '0'", - "attributes" => "JSON DEFAULT '{}'", + "attributes" => "JSON", "kind" => "VARCHAR(100) NOT NULL DEFAULT ''", "multiple_bookings" => "TINYINT(1) NOT NULL DEFAULT '0'", "created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)", From 0b4333ca6b774f9823033450f577fec33ab47fdc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Peters?= Date: Sat, 17 Feb 2018 10:18:07 +0100 Subject: [PATCH 078/107] [Web] JSON must not have a default value --- data/web/inc/init_db.inc.php | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/data/web/inc/init_db.inc.php b/data/web/inc/init_db.inc.php index 33855380..c9fa534a 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 = "17022018_0839"; + $db_version = "17022018_0859"; $stmt = $pdo->query("SHOW TABLES LIKE 'versions'"); $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); @@ -191,7 +191,7 @@ function init_db_schema() { "domain" => "VARCHAR(255) NOT NULL", "tls_enforce_in" => "TINYINT(1) NOT NULL DEFAULT '0'", "tls_enforce_out" => "TINYINT(1) NOT NULL DEFAULT '0'", - "attributes" => "JSON DEFAULT '{}'", + "attributes" => "JSON", "kind" => "VARCHAR(100) NOT NULL DEFAULT ''", "multiple_bookings" => "TINYINT(1) NOT NULL DEFAULT '0'", "created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)", @@ -638,6 +638,22 @@ function init_db_schema() { } } } + // Migrate tls_enforce_* options + if ($table == 'mailbox') { + $stmt = $pdo->query("SHOW TABLES LIKE 'mailbox'"); + $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); + if ($num_results != 0) { + $stmt = $pdo->query("SHOW COLUMNS FROM `mailbox` LIKE '%tls_enforce%'"); + $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); + if ($num_results != 0) { + $stmt = $pdo->query("SELECT `username`, `tls_enforce_in`, `tls_enforce_out` FROM `mailbox`"); + $tls_options_rows = $stmt->fetchAll(PDO::FETCH_ASSOC); + while ($row = array_shift($tls_options_rows)) { + $tls_options[$row['username']] = array('tls_enforce_in' => $row['tls_enforce_in'], 'tls_enforce_out' => $row['tls_enforce_out']); + } + } + } + } $stmt = $pdo->query("SHOW TABLES LIKE '" . $table . "'"); $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); if ($num_results != 0) { @@ -784,6 +800,13 @@ function init_db_schema() { } // Reset table attributes $pdo->query("ALTER TABLE `" . $table . "` " . $properties['attr'] . ";"); + // Migrate tls_enforce_* options + foreach($tls_options as $tls_user => $tls_options) { + $stmt = $pdo->prepare("UPDATE `mailbox` SET `attributes` = JSON_SET(`attributes`, '$.tls_enforce_in', :tls_enforce_in), + `attributes` = JSON_SET(`attributes`, '$.tls_enforce_out', :tls_enforce_out) + WHERE `username` = :username"); + $stmt->execute(array(':tls_enforce_in' => $tls_options['tls_enforce_in'], ':tls_enforce_out' => $tls_options['tls_enforce_out'], ':username' => $tls_user)); + } } // Recreate SQL views From 090ef6dbc5ae6a918ed52a1f89def2a3fd88750a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Peters?= Date: Sat, 17 Feb 2018 10:34:18 +0100 Subject: [PATCH 079/107] [Web] Further work on attributes --- data/web/inc/functions.mailbox.inc.php | 4 ++-- data/web/inc/init_db.inc.php | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/data/web/inc/functions.mailbox.inc.php b/data/web/inc/functions.mailbox.inc.php index 89e02166..00d0f99c 100644 --- a/data/web/inc/functions.mailbox.inc.php +++ b/data/web/inc/functions.mailbox.inc.php @@ -1152,7 +1152,7 @@ function mailbox($_action, $_type, $_data = null, $attr = null) { return false; } try { - $stmt = $pdo->prepare("UPDATE `mailbox` SET `tls_enforce_out` = :tls_out, `tls_enforce_in` = :tls_in WHERE `username` = :username"); + $stmt = $pdo->prepare("UPDATE `mailbox` SET `attributes` = JSON_SET(`attributes`, '$.tls_enforce_out', :tls_out), `attributes` = JSON_SET(`attributes`, '$.tls_enforce_in', :tls_in) WHERE `username` = :username"); $stmt->execute(array( ':tls_out' => $tls_enforce_out, ':tls_in' => $tls_enforce_in, @@ -2402,7 +2402,7 @@ function mailbox($_action, $_type, $_data = null, $attr = null) { $_data = $_SESSION['mailcow_cc_username']; } try { - $stmt = $pdo->prepare("SELECT `tls_enforce_out`, `tls_enforce_in` FROM `mailbox` WHERE `username` = :username"); + $stmt = $pdo->prepare("SELECT JSON_EXTRACT(`attributes`, '$.tls_enforce_out') AS `tls_enforce_out`, JSON_EXTRACT(`attributes`, '$.tls_enforce_in') AS `tls_enforce_in` FROM `mailbox` WHERE `username` = :username"); $stmt->execute(array(':username' => $_data)); $policydata = $stmt->fetch(PDO::FETCH_ASSOC); } diff --git a/data/web/inc/init_db.inc.php b/data/web/inc/init_db.inc.php index c9fa534a..23a3d0a0 100644 --- a/data/web/inc/init_db.inc.php +++ b/data/web/inc/init_db.inc.php @@ -189,8 +189,6 @@ function init_db_schema() { "quota" => "BIGINT(20) NOT NULL DEFAULT '102400'", "local_part" => "VARCHAR(255) NOT NULL", "domain" => "VARCHAR(255) NOT NULL", - "tls_enforce_in" => "TINYINT(1) NOT NULL DEFAULT '0'", - "tls_enforce_out" => "TINYINT(1) NOT NULL DEFAULT '0'", "attributes" => "JSON", "kind" => "VARCHAR(100) NOT NULL DEFAULT ''", "multiple_bookings" => "TINYINT(1) NOT NULL DEFAULT '0'", @@ -801,6 +799,7 @@ function init_db_schema() { // Reset table attributes $pdo->query("ALTER TABLE `" . $table . "` " . $properties['attr'] . ";"); // Migrate tls_enforce_* options + $stmt = $pdo->query("UPDATE `mailbox` SET `attributes` = '{}' WHERE `attributes` IS NULL;"); foreach($tls_options as $tls_user => $tls_options) { $stmt = $pdo->prepare("UPDATE `mailbox` SET `attributes` = JSON_SET(`attributes`, '$.tls_enforce_in', :tls_enforce_in), `attributes` = JSON_SET(`attributes`, '$.tls_enforce_out', :tls_enforce_out) From f29451f03cfda59da3a34ab0b1331a3cf5b743b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Peters?= Date: Sat, 17 Feb 2018 10:40:20 +0100 Subject: [PATCH 080/107] [Web] Further work on attributes --- data/web/inc/init_db.inc.php | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/data/web/inc/init_db.inc.php b/data/web/inc/init_db.inc.php index 23a3d0a0..345b720a 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 = "17022018_0859"; + $db_version = "17022018_0839"; $stmt = $pdo->query("SHOW TABLES LIKE 'versions'"); $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); @@ -798,14 +798,7 @@ function init_db_schema() { } // Reset table attributes $pdo->query("ALTER TABLE `" . $table . "` " . $properties['attr'] . ";"); - // Migrate tls_enforce_* options - $stmt = $pdo->query("UPDATE `mailbox` SET `attributes` = '{}' WHERE `attributes` IS NULL;"); - foreach($tls_options as $tls_user => $tls_options) { - $stmt = $pdo->prepare("UPDATE `mailbox` SET `attributes` = JSON_SET(`attributes`, '$.tls_enforce_in', :tls_enforce_in), - `attributes` = JSON_SET(`attributes`, '$.tls_enforce_out', :tls_enforce_out) - WHERE `username` = :username"); - $stmt->execute(array(':tls_enforce_in' => $tls_options['tls_enforce_in'], ':tls_enforce_out' => $tls_options['tls_enforce_out'], ':username' => $tls_user)); - } + } // Recreate SQL views @@ -842,6 +835,14 @@ DELIMITER ;'; // Insert new DB schema version $stmt = $pdo->query("REPLACE INTO `versions` (`application`, `version`) VALUES ('db_schema', '" . $db_version . "');"); + // Migrate tls_enforce_* options + $stmt = $pdo->query("UPDATE `mailbox` SET `attributes` = '{}' WHERE `attributes` IS NULL;"); + foreach($tls_options as $tls_user => $tls_options) { + $stmt = $pdo->prepare("UPDATE `mailbox` SET `attributes` = JSON_SET(`attributes`, '$.tls_enforce_in', :tls_enforce_in), + `attributes` = JSON_SET(`attributes`, '$.tls_enforce_out', :tls_enforce_out) + WHERE `username` = :username"); + $stmt->execute(array(':tls_enforce_in' => $tls_options['tls_enforce_in'], ':tls_enforce_out' => $tls_options['tls_enforce_out'], ':username' => $tls_user)); + } $_SESSION['return'] = array( 'type' => 'success', 'msg' => 'Database initialisation completed' From ab720bf164c8b3f4dc536699b68314263f1334f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Peters?= Date: Sat, 17 Feb 2018 11:12:16 +0100 Subject: [PATCH 081/107] [Web, Postfix] Move TLS policy to mailbox attributes --- data/Dockerfiles/postfix/postfix.sh | 4 ++-- data/web/inc/functions.mailbox.inc.php | 16 ++++++++++------ 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/data/Dockerfiles/postfix/postfix.sh b/data/Dockerfiles/postfix/postfix.sh index 2d3d0e64..88b7b94f 100755 --- a/data/Dockerfiles/postfix/postfix.sh +++ b/data/Dockerfiles/postfix/postfix.sh @@ -39,7 +39,7 @@ query = SELECT IF(EXISTS( SELECT CONCAT('%u', '@', target_domain) FROM alias_domain WHERE alias_domain='%d' ) - ) AND json_extract(`attributes`, '$.tls_enforce_in') = '1' AND mailbox.active = '1' + ) AND json_extract(attributes, '$.tls_enforce_in') = '1' AND mailbox.active = '1' ), 'reject_plaintext_session', NULL) AS 'tls_enforce_in'; EOF @@ -58,7 +58,7 @@ query = SELECT GROUP_CONCAT(transport SEPARATOR '') AS transport_maps WHERE alias_domain = '%d' ) ) - AND json_extract(`attributes`, '$.tls_enforce_out') = '1' + AND json_extract(attributes, '$.tls_enforce_out') = '1' AND mailbox.active = '1' ), 'smtp_enforced_tls:', 'smtp:') AS 'transport' UNION ALL diff --git a/data/web/inc/functions.mailbox.inc.php b/data/web/inc/functions.mailbox.inc.php index 00d0f99c..f0d10397 100644 --- a/data/web/inc/functions.mailbox.inc.php +++ b/data/web/inc/functions.mailbox.inc.php @@ -1154,8 +1154,8 @@ function mailbox($_action, $_type, $_data = null, $attr = null) { try { $stmt = $pdo->prepare("UPDATE `mailbox` SET `attributes` = JSON_SET(`attributes`, '$.tls_enforce_out', :tls_out), `attributes` = JSON_SET(`attributes`, '$.tls_enforce_in', :tls_in) WHERE `username` = :username"); $stmt->execute(array( - ':tls_out' => $tls_enforce_out, - ':tls_in' => $tls_enforce_in, + ':tls_out' => intval($tls_enforce_out), + ':tls_in' => intval($tls_enforce_in), ':username' => $username )); } @@ -2392,7 +2392,7 @@ function mailbox($_action, $_type, $_data = null, $attr = null) { return $mailboxes; break; case 'tls_policy': - $policydata = array(); + $attrs = array(); if (isset($_data) && filter_var($_data, FILTER_VALIDATE_EMAIL)) { if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data)) { return false; @@ -2402,9 +2402,9 @@ function mailbox($_action, $_type, $_data = null, $attr = null) { $_data = $_SESSION['mailcow_cc_username']; } try { - $stmt = $pdo->prepare("SELECT JSON_EXTRACT(`attributes`, '$.tls_enforce_out') AS `tls_enforce_out`, JSON_EXTRACT(`attributes`, '$.tls_enforce_in') AS `tls_enforce_in` FROM `mailbox` WHERE `username` = :username"); + $stmt = $pdo->prepare("SELECT `attributes` FROM `mailbox` WHERE `username` = :username"); $stmt->execute(array(':username' => $_data)); - $policydata = $stmt->fetch(PDO::FETCH_ASSOC); + $attrs = $stmt->fetch(PDO::FETCH_ASSOC); } catch(PDOException $e) { $_SESSION['return'] = array( @@ -2413,7 +2413,11 @@ function mailbox($_action, $_type, $_data = null, $attr = null) { ); return false; } - return $policydata; + $attrs = json_decode($attrs['attributes'], true); + return array( + 'tls_enforce_in' => $attrs['tls_enforce_in'], + 'tls_enforce_out' => $attrs['tls_enforce_out'] + ); break; case 'filters': $filters = array(); From 2284a35658f8f04e3f8dfa546254c0f9a1bf6599 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Peters?= Date: Sat, 17 Feb 2018 11:46:34 +0100 Subject: [PATCH 082/107] [Web] Apply fix for renaming quarantaine --- data/web/inc/init_db.inc.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/web/inc/init_db.inc.php b/data/web/inc/init_db.inc.php index 345b720a..aeccb1b7 100644 --- a/data/web/inc/init_db.inc.php +++ b/data/web/inc/init_db.inc.php @@ -158,7 +158,7 @@ function init_db_schema() { ), "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" ), - "quarantaine" => array( + "quarantine" => array( "cols" => array( "id" => "INT NOT NULL AUTO_INCREMENT", "qid" => "VARCHAR(30) NOT NULL", @@ -247,7 +247,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'", - "quarantaine" => "TINYINT(1) NOT NULL DEFAULT '1'", + "quarantine" => "TINYINT(1) NOT NULL DEFAULT '1'", "bcc_maps" => "TINYINT(1) NOT NULL DEFAULT '1'", "recipient_maps" => "TINYINT(1) NOT NULL DEFAULT '0'", ), From fc53a69a441a4e0e3e9499f194f8b62205ba9727 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Peters?= Date: Sun, 18 Feb 2018 20:59:32 +0100 Subject: [PATCH 083/107] [Helper] Nextcloud 13; Additional header for Nextcloud site [PHP-FPM] Build gd with TTF support --- data/Dockerfiles/phpfpm/Dockerfile | 31 ++++++++++++++++++++- data/assets/nextcloud/nextcloud.conf | 1 + data/assets/nextcloud/site.nextcloud.custom | 1 + docker-compose.yml | 2 +- helper-scripts/nextcloud.sh | 2 +- 5 files changed, 34 insertions(+), 3 deletions(-) diff --git a/data/Dockerfiles/phpfpm/Dockerfile b/data/Dockerfiles/phpfpm/Dockerfile index 45d2fa84..00681ec2 100644 --- a/data/Dockerfiles/phpfpm/Dockerfile +++ b/data/Dockerfiles/phpfpm/Dockerfile @@ -33,6 +33,12 @@ RUN apk add -U --no-cache libxml2-dev \ imagemagick-dev \ imagemagick \ libtool \ + freetype \ + libpng \ + libjpeg-turbo \ + freetype-dev \ + libpng-dev \ + libjpeg-turbo-dev\ gettext-dev \ openldap-dev \ librsvg \ @@ -46,10 +52,33 @@ RUN apk add -U --no-cache libxml2-dev \ && docker-php-ext-enable redis apcu memcached imagick mailparse \ && pecl clear-cache \ && docker-php-ext-configure intl \ + && docker-php-ext-configure gd \ + --with-gd \ + --enable-gd-native-ttf \ + --with-freetype-dir=/usr/include/ \ + --with-png-dir=/usr/include/ \ + --with-jpeg-dir=/usr/include/ \ && docker-php-ext-install -j 4 intl gettext ldap sockets soap pdo pdo_mysql xmlrpc gd zip pcntl opcache \ && docker-php-ext-configure imap --with-imap --with-imap-ssl \ && docker-php-ext-install -j 4 imap \ - && apk del --purge autoconf g++ make libxml2-dev icu-dev imap-dev openssl-dev cyrus-sasl-dev pcre-dev libpng-dev libpng-dev libjpeg-turbo-dev libwebp-dev zlib-dev imagemagick-dev + && apk del --purge autoconf \ + g++ \ + make \ + libxml2-dev \ + icu-dev \ + imap-dev \ + openssl-dev \ + cyrus-sasl-dev \ + pcre-dev \ + libpng-dev \ + libpng-dev \ + libjpeg-turbo-dev \ + libwebp-dev \ + zlib-dev \ + imagemagick-dev \ + freetype-dev \ + libpng-dev \ + libjpeg-turbo-dev COPY ./docker-entrypoint.sh / diff --git a/data/assets/nextcloud/nextcloud.conf b/data/assets/nextcloud/nextcloud.conf index 1e6c3726..4759bff2 100644 --- a/data/assets/nextcloud/nextcloud.conf +++ b/data/assets/nextcloud/nextcloud.conf @@ -24,6 +24,7 @@ server { add_header X-Robots-Tag none; add_header X-Download-Options noopen; add_header X-Permitted-Cross-Domain-Policies none; + add_header X-Frame-Options "SAMEORIGIN"; server_name NC_SUBD; diff --git a/data/assets/nextcloud/site.nextcloud.custom b/data/assets/nextcloud/site.nextcloud.custom index 6901df76..f7d6dae0 100644 --- a/data/assets/nextcloud/site.nextcloud.custom +++ b/data/assets/nextcloud/site.nextcloud.custom @@ -33,6 +33,7 @@ add_header X-Robots-Tag none; add_header X-Download-Options noopen; add_header X-Permitted-Cross-Domain-Policies none; + add_header X-Frame-Options "SAMEORIGIN"; access_log off; } location ~ \.(?:png|html|ttf|ico|jpg|jpeg)$ { diff --git a/docker-compose.yml b/docker-compose.yml index 1016cfde..c7af8ce8 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -102,7 +102,7 @@ services: - rspamd php-fpm-mailcow: - image: mailcow/phpfpm:1.11 + image: mailcow/phpfpm:1.12 build: ./data/Dockerfiles/phpfpm command: "php-fpm -d date.timezone=${TZ} -d expose_php=0" depends_on: diff --git a/helper-scripts/nextcloud.sh b/helper-scripts/nextcloud.sh index a1420f40..5040cd95 100755 --- a/helper-scripts/nextcloud.sh +++ b/helper-scripts/nextcloud.sh @@ -64,7 +64,7 @@ elif [[ ${NC_INSTALL} == "y" ]]; then ADMIN_NC_PASS=$( Date: Mon, 19 Feb 2018 10:17:29 +0100 Subject: [PATCH 084/107] [Web, Dovecot, Postfix] Fix JSON attribute for login --- data/Dockerfiles/dovecot/docker-entrypoint.sh | 2 +- data/Dockerfiles/postfix/postfix.sh | 4 ++-- data/web/inc/init_db.inc.php | 5 +++-- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/data/Dockerfiles/dovecot/docker-entrypoint.sh b/data/Dockerfiles/dovecot/docker-entrypoint.sh index 469edacc..b1f78870 100755 --- a/data/Dockerfiles/dovecot/docker-entrypoint.sh +++ b/data/Dockerfiles/dovecot/docker-entrypoint.sh @@ -82,7 +82,7 @@ cat < /usr/local/etc/dovecot/sql/dovecot-dict-sql-passdb.conf driver = mysql connect = "host=mysql dbname=${DBNAME} user=${DBUSER} password=${DBPASS}" default_pass_scheme = SSHA256 -password_query = SELECT password FROM mailbox WHERE username = '%u' AND domain IN (SELECT domain FROM domain WHERE domain='%d' AND active='1') AND JSON_EXTRACT(attributes, "$.force_pw_update") != 1 +password_query = SELECT password FROM mailbox WHERE username = '%u' AND domain IN (SELECT domain FROM domain WHERE domain='%d' AND active='1') AND JSON_EXTRACT(attributes, "$.force_pw_update") NOT LIKE '%1%' user_query = SELECT CONCAT('maildir:/var/vmail/',maildir) AS mail, 5000 AS uid, 5000 AS gid, concat('*:bytes=', quota) AS quota_rule FROM mailbox WHERE username = '%u' AND active = '1' iterate_query = SELECT username FROM mailbox WHERE active='1'; EOF diff --git a/data/Dockerfiles/postfix/postfix.sh b/data/Dockerfiles/postfix/postfix.sh index 88b7b94f..a80e6900 100755 --- a/data/Dockerfiles/postfix/postfix.sh +++ b/data/Dockerfiles/postfix/postfix.sh @@ -39,7 +39,7 @@ query = SELECT IF(EXISTS( SELECT CONCAT('%u', '@', target_domain) FROM alias_domain WHERE alias_domain='%d' ) - ) AND json_extract(attributes, '$.tls_enforce_in') = '1' AND mailbox.active = '1' + ) AND json_extract(attributes, '$.tls_enforce_in') LIKE '%1%' AND mailbox.active = '1' ), 'reject_plaintext_session', NULL) AS 'tls_enforce_in'; EOF @@ -58,7 +58,7 @@ query = SELECT GROUP_CONCAT(transport SEPARATOR '') AS transport_maps WHERE alias_domain = '%d' ) ) - AND json_extract(attributes, '$.tls_enforce_out') = '1' + AND json_extract(attributes, '$.tls_enforce_out') LIKE '%1%' AND mailbox.active = '1' ), 'smtp_enforced_tls:', 'smtp:') AS 'transport' UNION ALL diff --git a/data/web/inc/init_db.inc.php b/data/web/inc/init_db.inc.php index aeccb1b7..77368166 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 = "17022018_0839"; + $db_version = "19022018_0839"; $stmt = $pdo->query("SHOW TABLES LIKE 'versions'"); $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); @@ -835,8 +835,9 @@ DELIMITER ;'; // Insert new DB schema version $stmt = $pdo->query("REPLACE INTO `versions` (`application`, `version`) VALUES ('db_schema', '" . $db_version . "');"); - // Migrate tls_enforce_* options + // Migrate tls_enforce_* options and add force_pw_update attribute $stmt = $pdo->query("UPDATE `mailbox` SET `attributes` = '{}' WHERE `attributes` IS NULL;"); + $stmt = $pdo->query("UPDATE `mailbox` SET `attributes` = JSON_SET(`attributes`, '$.force_pw_update', 0) WHERE JSON_EXTRACT(`attributes`, '$.force_pw_update') IS NULL;"); foreach($tls_options as $tls_user => $tls_options) { $stmt = $pdo->prepare("UPDATE `mailbox` SET `attributes` = JSON_SET(`attributes`, '$.tls_enforce_in', :tls_enforce_in), `attributes` = JSON_SET(`attributes`, '$.tls_enforce_out', :tls_enforce_out) From ff3328ea8c9a14cb4282394916a47c151e29d676 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Peters?= Date: Mon, 19 Feb 2018 12:56:45 +0100 Subject: [PATCH 085/107] [SOGo] Use indigo theme, copy logo and theme.js to image --- data/Dockerfiles/sogo/Dockerfile | 2 + data/Dockerfiles/sogo/bootstrap-sogo.sh | 2 +- data/Dockerfiles/sogo/sogo-full.svg | 48 ++++++++++++++++++++ data/Dockerfiles/sogo/theme.js | 60 +++++++++++++++++++++++++ data/conf/sogo/sogo.conf | 1 + docker-compose.yml | 2 +- 6 files changed, 113 insertions(+), 2 deletions(-) create mode 100644 data/Dockerfiles/sogo/sogo-full.svg create mode 100644 data/Dockerfiles/sogo/theme.js diff --git a/data/Dockerfiles/sogo/Dockerfile b/data/Dockerfiles/sogo/Dockerfile index 1bfca949..b00e2f73 100644 --- a/data/Dockerfiles/sogo/Dockerfile +++ b/data/Dockerfiles/sogo/Dockerfile @@ -42,6 +42,8 @@ RUN mkdir /usr/share/doc/sogo \ COPY ./bootstrap-sogo.sh / COPY syslog-ng.conf /etc/syslog-ng/syslog-ng.conf COPY supervisord.conf /etc/supervisor/supervisord.conf +COPY theme.js /usr/lib/GNUstep/SOGo/WebServerResources/js/theme.js +COPY sogo-full.svg /usr/lib/GNUstep/SOGo/WebServerResources/img/sogo-full.svg CMD exec /usr/bin/supervisord -c /etc/supervisor/supervisord.conf diff --git a/data/Dockerfiles/sogo/bootstrap-sogo.sh b/data/Dockerfiles/sogo/bootstrap-sogo.sh index 07365981..87bf05f7 100755 --- a/data/Dockerfiles/sogo/bootstrap-sogo.sh +++ b/data/Dockerfiles/sogo/bootstrap-sogo.sh @@ -19,7 +19,7 @@ mysql --host mysql -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "DROP VIEW IF EXISTS so mysql --host mysql -u ${DBUSER} -p${DBPASS} ${DBNAME} << EOF CREATE VIEW sogo_view (c_uid, domain, c_name, c_password, c_cn, mail, aliases, ad_aliases, home, kind, multiple_bookings) AS -SELECT mailbox.username, mailbox.domain, mailbox.username, if(json_extract(attributes, '$.force_pw_update') = '0', password, 'invalid'), mailbox.name, mailbox.username, IFNULL(GROUP_CONCAT(ga.aliases SEPARATOR ' '), ''), IFNULL(gda.ad_alias, ''), CONCAT('/var/vmail/', maildir), mailbox.kind, mailbox.multiple_bookings FROM mailbox +SELECT mailbox.username, mailbox.domain, mailbox.username, if(json_extract(attributes, '$.force_pw_update') LIKE '%0%', password, 'invalid'), mailbox.name, mailbox.username, IFNULL(GROUP_CONCAT(ga.aliases SEPARATOR ' '), ''), IFNULL(gda.ad_alias, ''), CONCAT('/var/vmail/', maildir), mailbox.kind, mailbox.multiple_bookings FROM mailbox LEFT OUTER JOIN grouped_mail_aliases ga ON ga.username REGEXP CONCAT('(^|,)', mailbox.username, '($|,)') LEFT OUTER JOIN grouped_domain_alias_address gda ON gda.username = mailbox.username WHERE mailbox.active = '1' diff --git a/data/Dockerfiles/sogo/sogo-full.svg b/data/Dockerfiles/sogo/sogo-full.svg new file mode 100644 index 00000000..5e0d81df --- /dev/null +++ b/data/Dockerfiles/sogo/sogo-full.svg @@ -0,0 +1,48 @@ + + + +image/svg+xml \ No newline at end of file diff --git a/data/Dockerfiles/sogo/theme.js b/data/Dockerfiles/sogo/theme.js new file mode 100644 index 00000000..4efbf824 --- /dev/null +++ b/data/Dockerfiles/sogo/theme.js @@ -0,0 +1,60 @@ +/* -*- Mode: javascript; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ + +(function() { + 'use strict'; + + angular.module('SOGo.Common') + .config(configure) + + /** + * @ngInject + */ + configure.$inject = ['$mdThemingProvider']; + function configure($mdThemingProvider) { + + /** + * Define the Alternative theme + */ + $mdThemingProvider.theme('mailcow') + .primaryPalette('indigo', { + 'default': '700', // top toolbar + 'hue-1': '400', + 'hue-2': '600', // sidebar toolbar + 'hue-3': 'A700' + }) + .accentPalette('indigo', { + 'default': '500', // fab buttons + 'hue-1': '50', // center list toolbar + 'hue-2': '400', + 'hue-3': 'A700' + }) + .backgroundPalette('grey', { + 'default': '50', // center list background + 'hue-1': '100', + 'hue-2': '200', + 'hue-3': '300' + }); + $mdThemingProvider.theme('default') + .primaryPalette('indigo', { + 'default': '700', // top toolbar + 'hue-1': '400', + 'hue-2': '600', // sidebar toolbar + 'hue-3': 'A700' + }) + .accentPalette('indigo', { + 'default': '500', // fab buttons + 'hue-1': '50', // center list toolbar + 'hue-2': '400', + 'hue-3': 'A700' + }) + .backgroundPalette('grey', { + 'default': '50', // center list background + 'hue-1': '100', + 'hue-2': '200', + 'hue-3': '300' + }); + + $mdThemingProvider.setDefaultTheme('mailcow'); + $mdThemingProvider.generateThemesOnDemand(false); + } +})(); diff --git a/data/conf/sogo/sogo.conf b/data/conf/sogo/sogo.conf index 195c3dfd..73cc2864 100644 --- a/data/conf/sogo/sogo.conf +++ b/data/conf/sogo/sogo.conf @@ -14,6 +14,7 @@ SOGoEnableEMailAlarms = NO; SOGoFoldersSendEMailNotifications = YES; SOGoForwardEnabled = YES; + SOGoUIAdditionalJSFiles = (js/theme.js); // Multi-domain setup // Domains are isolated, you can define visibility options here. diff --git a/docker-compose.yml b/docker-compose.yml index c7af8ce8..f849bb4f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -142,7 +142,7 @@ services: - phpfpm sogo-mailcow: - image: mailcow/sogo:1.17 + image: mailcow/sogo:1.18 build: ./data/Dockerfiles/sogo environment: - DBNAME=${DBNAME} From 24bd644359e1b301855456db08a30438d33b197c Mon Sep 17 00:00:00 2001 From: Levin Date: Thu, 15 Feb 2018 12:08:18 +0000 Subject: [PATCH 086/107] Added dns option for watchdog --- docker-compose.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docker-compose.yml b/docker-compose.yml index f849bb4f..8c44cd3d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -361,6 +361,8 @@ services: - USE_WATCHDOG=${USE_WATCHDOG:-n} - WATCHDOG_NOTIFY_EMAIL=${WATCHDOG_NOTIFY_EMAIL} - MAILCOW_HOSTNAME=${MAILCOW_HOSTNAME} + dns: + - ${IPV4_NETWORK:-172.22.1}.254 networks: mailcow-network: aliases: From 6e91504f6f1fee7e20a9cabaa5e24a670bb65dfb Mon Sep 17 00:00:00 2001 From: eXtremeSHOK Date: Tue, 20 Feb 2018 00:28:59 +0200 Subject: [PATCH 087/107] Update generate_config.sh added --no-cache option, thanks --- generate_config.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/generate_config.sh b/generate_config.sh index b49a6a21..d9a9f7ec 100755 --- a/generate_config.sh +++ b/generate_config.sh @@ -1,11 +1,11 @@ #!/bin/bash if grep --help 2>&1 | head -n 1 | grep -q -i "busybox" ; then - echo "BusybBox grep detected, please install gnu grep, \"apk add --upgrade grep\"" + echo "BusybBox grep detected, please install gnu grep, \"apk add --no-cache --upgrade grep\"" exit 1 fi if cp --help 2>&1 | head -n 1 | grep -q -i "busybox" ; then - echo "BusybBox cp detected, please install coreutils, \"apk add --upgrade coreutils\"" + echo "BusybBox cp detected, please install coreutils, \"apk add --no-cache --upgrade coreutils\"" exit 1 fi From 40885b7fd6f38c3fc5014d402544487e60fca2f1 Mon Sep 17 00:00:00 2001 From: eXtremeSHOK Date: Tue, 20 Feb 2018 00:39:53 +0200 Subject: [PATCH 088/107] Update generate_config.sh exit on error and pipefail minor fix " ; then" to ";then" --- generate_config.sh | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/generate_config.sh b/generate_config.sh index d9a9f7ec..aa600a02 100755 --- a/generate_config.sh +++ b/generate_config.sh @@ -1,10 +1,14 @@ #!/bin/bash -if grep --help 2>&1 | head -n 1 | grep -q -i "busybox" ; then ++#exit on error and pipefail ++set -o errexit ++set -o pipefail + +if grep --help 2>&1 | head -n 1 | grep -q -i "busybox"; then echo "BusybBox grep detected, please install gnu grep, \"apk add --no-cache --upgrade grep\"" exit 1 fi -if cp --help 2>&1 | head -n 1 | grep -q -i "busybox" ; then +if cp --help 2>&1 | head -n 1 | grep -q -i "busybox"; then echo "BusybBox cp detected, please install coreutils, \"apk add --no-cache --upgrade coreutils\"" exit 1 fi From de04016996017a16727ec744f36b2dbce4253686 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Peters?= Date: Tue, 20 Feb 2018 08:34:49 +0100 Subject: [PATCH 089/107] Update generate_config.sh --- generate_config.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/generate_config.sh b/generate_config.sh index aa600a02..d8cbe8ac 100755 --- a/generate_config.sh +++ b/generate_config.sh @@ -4,11 +4,11 @@ +set -o errexit +set -o pipefail -if grep --help 2>&1 | head -n 1 | grep -q -i "busybox"; then +if grep --help 2>&1 | grep -q -i "busybox"; then echo "BusybBox grep detected, please install gnu grep, \"apk add --no-cache --upgrade grep\"" exit 1 fi -if cp --help 2>&1 | head -n 1 | grep -q -i "busybox"; then +if cp --help 2>&1 | grep -q -i "busybox"; then echo "BusybBox cp detected, please install coreutils, \"apk add --no-cache --upgrade coreutils\"" exit 1 fi From d052a87cc3219a665165f609bcae858813cf66ea Mon Sep 17 00:00:00 2001 From: eXtremeSHOK Date: Tue, 20 Feb 2018 10:31:01 +0200 Subject: [PATCH 090/107] Support for alpine linux rebased on dev replaces #1047 --- update.sh | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/update.sh b/update.sh index ceb4311c..fba5da72 100755 --- a/update.sh +++ b/update.sh @@ -1,10 +1,13 @@ #!/bin/bash +#exit on error and pipefail +set -o errexit +set -o pipefail + for bin in curl docker-compose docker git awk sha1sum; do if [[ -z $(which ${bin}) ]]; then echo "Cannot find ${bin}, exiting..."; exit 1; fi done -set -o pipefail export LC_ALL=C DATE=$(date +%Y-%m-%d_%H_%M_%S) BRANCH=$(git rev-parse --abbrev-ref HEAD) @@ -30,6 +33,9 @@ done [[ ! -f mailcow.conf ]] && { echo "mailcow.conf is missing"; exit 1;} +if grep --help 2>&1 | head -n 1 | grep -q -i "busybox"; then echo "BusybBox grep detected, please install gnu grep, \"apk add --no-cache --upgrade grep\""; exit 1; fi +if cp --help 2>&1 | head -n 1 | grep -q -i "busybox"; then echo "BusybBox cp detected, please install coreutils, \"apk add --no-cache --upgrade coreutils\""; exit 1; fi + CONFIG_ARRAY=( "SKIP_LETS_ENCRYPT" "USE_WATCHDOG" @@ -118,7 +124,7 @@ curl -o /dev/null google.com -sm3 if [[ $? != 0 ]]; then echo -e "\e[31mfailed\e[0m" exit 1 - else +else echo -e "\e[32mOK\e[0m" fi @@ -135,7 +141,7 @@ fi if [[ -f mailcow.conf ]]; then source mailcow.conf - else +else echo -e "\e[31mNo mailcow.conf - is mailcow installed?\e[0m" exit 1 fi @@ -183,11 +189,14 @@ fi echo -e "\e[32mFetching new docker-compose version...\e[0m" sleep 2 -if [[ $(curl -sL -w "%{http_code}" https://www.servercow.de/docker-compose/latest.php -o /dev/null) == "200" ]]; then +if [[ ! -z $(which pip) && $(pip list --local | grep -c docker-compose) == 1 ]]; then + true + #prevent breaking a working docker-compose installed with pip +elif [[ $(curl -sL -w "%{http_code}" https://www.servercow.de/docker-compose/latest.php -o /dev/null) == "200" ]]; then LATEST_COMPOSE=$(curl -#L https://www.servercow.de/docker-compose/latest.php) curl -#L https://github.com/docker/compose/releases/download/${LATEST_COMPOSE}/docker-compose-$(uname -s)-$(uname -m) > $(which docker-compose) chmod +x $(which docker-compose) - else +else echo -e "\e[33mCannot determine latest docker-compose version, skipping...\e[0m" fi @@ -239,7 +248,7 @@ if [[ ! -z ${IMGS_TO_DELETE[*]} ]]; then read -r -p "Do you want to delete old image tags right now? [y/N] " response if [[ "$response" =~ ^([yY][eE][sS]|[yY])+$ ]]; then docker rmi ${IMGS_TO_DELETE[*]} - else + else echo "OK, skipped." fi fi From eb4dd632ae846926c7633d0b03d1028bc2dc03e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Peters?= Date: Thu, 22 Feb 2018 09:16:16 +0100 Subject: [PATCH 091/107] [Web] Fix autodiscover triggering fail2ban implementation, fixes #1069 --- .../override.d/worker-controller-password.inc | 0 data/web/autodiscover.php | 150 +++++++++--------- 2 files changed, 74 insertions(+), 76 deletions(-) create mode 100644 data/conf/rspamd/override.d/worker-controller-password.inc diff --git a/data/conf/rspamd/override.d/worker-controller-password.inc b/data/conf/rspamd/override.d/worker-controller-password.inc new file mode 100644 index 00000000..e69de29b diff --git a/data/web/autodiscover.php b/data/web/autodiscover.php index a8d8073b..88f91da8 100644 --- a/data/web/autodiscover.php +++ b/data/web/autodiscover.php @@ -36,9 +36,8 @@ $opt = [ $pdo = new PDO($dsn, $database_user, $database_pass, $opt); $login_user = strtolower(trim($_SERVER['PHP_AUTH_USER'])); $login_pass = trim(htmlspecialchars_decode($_SERVER['PHP_AUTH_PW'])); -$login_role = check_login($login_user, $login_pass); -if (!isset($_SERVER['PHP_AUTH_USER']) OR $login_role !== "user") { +if (empty($_SERVER['PHP_AUTH_USER']) || empty($_SERVER['PHP_AUTH_PW'])) { try { $json = json_encode( array( @@ -62,35 +61,36 @@ if (!isset($_SERVER['PHP_AUTH_USER']) OR $login_role !== "user") { header('HTTP/1.0 401 Unauthorized'); exit(0); } -else { - if (isset($_SERVER['PHP_AUTH_USER']) && isset($_SERVER['PHP_AUTH_PW'])) { - if ($login_role === "user") { - header("Content-Type: application/xml"); - echo '' . PHP_EOL; + +$login_role = check_login($login_user, $login_pass); + +if ($login_role === "user") { + header("Content-Type: application/xml"); + echo '' . PHP_EOL; ?> time(), - "ua" => $_SERVER['HTTP_USER_AGENT'], - "user" => $_SERVER['PHP_AUTH_USER'], - "service" => "Error: invalid or missing request data" - ) - ); - $redis->lPush('AUTODISCOVER_LOG', $json); - $redis->lTrim('AUTODISCOVER_LOG', 0, 100); - } - catch (RedisException $e) { - $_SESSION['return'] = array( - 'type' => 'danger', - 'msg' => 'Redis: '.$e - ); - return false; - } - list($usec, $sec) = explode(' ', microtime()); + if(!$data) { + try { + $json = json_encode( + array( + "time" => time(), + "ua" => $_SERVER['HTTP_USER_AGENT'], + "user" => $_SERVER['PHP_AUTH_USER'], + "service" => "Error: invalid or missing request data" + ) + ); + $redis->lPush('AUTODISCOVER_LOG', $json); + $redis->lTrim('AUTODISCOVER_LOG', 0, 100); + } + catch (RedisException $e) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => 'Redis: '.$e + ); + return false; + } + list($usec, $sec) = explode(' ', microtime()); ?> @@ -101,50 +101,50 @@ else { Request->EMailAddress; - } catch (Exception $e) { - $email = $_SERVER['PHP_AUTH_USER']; - } + exit(0); + } + try { + $discover = new SimpleXMLElement($data); + $email = $discover->Request->EMailAddress; + } catch (Exception $e) { + $email = $_SERVER['PHP_AUTH_USER']; + } - $username = trim($email); - try { - $stmt = $pdo->prepare("SELECT `name` FROM `mailbox` WHERE `username`= :username"); - $stmt->execute(array(':username' => $username)); - $MailboxData = $stmt->fetch(PDO::FETCH_ASSOC); - } - catch(PDOException $e) { - die("Failed to determine name from SQL"); - } - if (!empty($MailboxData['name'])) { - $displayname = $MailboxData['name']; - } - else { - $displayname = $email; - } - try { - $json = json_encode( - array( - "time" => time(), - "ua" => $_SERVER['HTTP_USER_AGENT'], - "user" => $_SERVER['PHP_AUTH_USER'], - "service" => $autodiscover_config['autodiscoverType'] - ) - ); - $redis->lPush('AUTODISCOVER_LOG', $json); - $redis->lTrim('AUTODISCOVER_LOG', 0, 100); - } - catch (RedisException $e) { - $_SESSION['return'] = array( - 'type' => 'danger', - 'msg' => 'Redis: '.$e - ); - return false; - } - if ($autodiscover_config['autodiscoverType'] == 'imap') { + $username = trim($email); + try { + $stmt = $pdo->prepare("SELECT `name` FROM `mailbox` WHERE `username`= :username"); + $stmt->execute(array(':username' => $username)); + $MailboxData = $stmt->fetch(PDO::FETCH_ASSOC); + } + catch(PDOException $e) { + die("Failed to determine name from SQL"); + } + if (!empty($MailboxData['name'])) { + $displayname = $MailboxData['name']; + } + else { + $displayname = $email; + } + try { + $json = json_encode( + array( + "time" => time(), + "ua" => $_SERVER['HTTP_USER_AGENT'], + "user" => $_SERVER['PHP_AUTH_USER'], + "service" => $autodiscover_config['autodiscoverType'] + ) + ); + $redis->lPush('AUTODISCOVER_LOG', $json); + $redis->lTrim('AUTODISCOVER_LOG', 0, 100); + } + catch (RedisException $e) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => 'Redis: '.$e + ); + return false; + } + if ($autodiscover_config['autodiscoverType'] == 'imap') { ?> @@ -190,8 +190,8 @@ else { en:en @@ -210,11 +210,9 @@ else { From fbe24f39a274fb8a3bdc6b45f085703c068f1f9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Peters?= Date: Thu, 22 Feb 2018 09:16:49 +0100 Subject: [PATCH 092/107] [Web] Show volume usage for vmail, start listing system info in UI --- data/web/css/debug.css | 2 +- data/web/debug.php | 24 +++++++++++++++++++++++- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/data/web/css/debug.css b/data/web/css/debug.css index 585d1905..e46a5b07 100644 --- a/data/web/css/debug.css +++ b/data/web/css/debug.css @@ -34,4 +34,4 @@ table.footable>tbody>tr.footable-empty>td { } .inputMissingAttr { border-color: #FF4136; -} \ No newline at end of file +} diff --git a/data/web/debug.php b/data/web/debug.php index f9685ab4..f6ced28f 100644 --- a/data/web/debug.php +++ b/data/web/debug.php @@ -16,7 +16,7 @@ $_SESSION['return_to'] = $_SERVER['REQUEST_URI'];
  • Rspamd settings map
  • -
  • Containers
  • +
  • Containers & System
  • + 'df', 'dir' => '/var/vmail'); + $vmail_df = explode(',', json_decode(docker('dovecot-mailcow', 'post', 'exec', $exec_fields), true)); + ?>
    +
    +
    +

    Disk usage

    +
    +
    +
    +
    +

    /var/vmail on

    +

    / ()

    +
    +
    +
    +
    +
    +
    +
    +
    +

    Container information

    From f3896195d457e0acf0a5be38e800e7af9f146d7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Peters?= Date: Thu, 22 Feb 2018 09:19:01 +0100 Subject: [PATCH 093/107] Update worker-controller-password.inc --- data/conf/rspamd/override.d/worker-controller-password.inc | 1 + 1 file changed, 1 insertion(+) diff --git a/data/conf/rspamd/override.d/worker-controller-password.inc b/data/conf/rspamd/override.d/worker-controller-password.inc index e69de29b..9a5984d1 100644 --- a/data/conf/rspamd/override.d/worker-controller-password.inc +++ b/data/conf/rspamd/override.d/worker-controller-password.inc @@ -0,0 +1 @@ +# Placeholder From 7a850f91b58f8cf15d7d5833eaf44e1a1058502e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Peters?= Date: Thu, 22 Feb 2018 09:20:23 +0100 Subject: [PATCH 094/107] [Web] Nginx should wait for Rspamd, remove vmail vol from watchdog --- docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index 8c44cd3d..2f443e79 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -263,6 +263,7 @@ services: until ping phpfpm -c1 > /dev/null; do sleep 1; done && until ping sogo -c1 > /dev/null; do sleep 1; done && until ping redis -c1 > /dev/null; do sleep 1; done && + until ping rspamd -c1 > /dev/null; do sleep 1; done && exec nginx -g 'daemon off;'" environment: - HTTPS_PORT=${HTTPS_PORT:-443} @@ -349,7 +350,6 @@ services: sysctls: - net.ipv6.conf.all.disable_ipv6=${SYSCTL_IPV6_DISABLED:-0} volumes: - - vmail-vol-1:/vmail:ro - rspamd-sock:/rspamd-sock restart: always environment: From bbbe52f560139c4c383f99b0d6b7baa7126fcef9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Peters?= Date: Thu, 22 Feb 2018 09:20:46 +0100 Subject: [PATCH 095/107] [SOGo] Add blue (default) and red theme --- data/Dockerfiles/sogo/Dockerfile | 3 +- .../sogo/{theme.js => theme-blue.js} | 0 data/Dockerfiles/sogo/theme-green.js | 61 +++++++++++++++++++ data/conf/sogo/sogo.conf | 2 +- 4 files changed, 64 insertions(+), 2 deletions(-) rename data/Dockerfiles/sogo/{theme.js => theme-blue.js} (100%) create mode 100644 data/Dockerfiles/sogo/theme-green.js diff --git a/data/Dockerfiles/sogo/Dockerfile b/data/Dockerfiles/sogo/Dockerfile index b00e2f73..bb429326 100644 --- a/data/Dockerfiles/sogo/Dockerfile +++ b/data/Dockerfiles/sogo/Dockerfile @@ -42,7 +42,8 @@ RUN mkdir /usr/share/doc/sogo \ COPY ./bootstrap-sogo.sh / COPY syslog-ng.conf /etc/syslog-ng/syslog-ng.conf COPY supervisord.conf /etc/supervisor/supervisord.conf -COPY theme.js /usr/lib/GNUstep/SOGo/WebServerResources/js/theme.js +COPY theme-blue.js /usr/lib/GNUstep/SOGo/WebServerResources/js/theme-blue.js +COPY theme-green.js /usr/lib/GNUstep/SOGo/WebServerResources/js/theme-green.js COPY sogo-full.svg /usr/lib/GNUstep/SOGo/WebServerResources/img/sogo-full.svg CMD exec /usr/bin/supervisord -c /etc/supervisor/supervisord.conf diff --git a/data/Dockerfiles/sogo/theme.js b/data/Dockerfiles/sogo/theme-blue.js similarity index 100% rename from data/Dockerfiles/sogo/theme.js rename to data/Dockerfiles/sogo/theme-blue.js diff --git a/data/Dockerfiles/sogo/theme-green.js b/data/Dockerfiles/sogo/theme-green.js new file mode 100644 index 00000000..35a6f788 --- /dev/null +++ b/data/Dockerfiles/sogo/theme-green.js @@ -0,0 +1,61 @@ +/* -*- Mode: javascript; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ + +(function() { + 'use strict'; + + angular.module('SOGo.Common') + .config(configure) + + /** + * @ngInject + */ + configure.$inject = ['$mdThemingProvider']; + function configure($mdThemingProvider) { + + + /** + * Define the Alternative theme + */ + $mdThemingProvider.theme('mailcow') + .primaryPalette('green', { + 'default': '600', // top toolbar + 'hue-1': '200', + 'hue-2': '600', // sidebar toolbar + 'hue-3': 'A700' + }) + .accentPalette('green', { + 'default': '600', // fab buttons + 'hue-1': '50', // center list toolbar + 'hue-2': '400', + 'hue-3': 'A700' + }) + .backgroundPalette('grey', { + 'default': '50', // center list background + 'hue-1': '50', + 'hue-2': '100', + 'hue-3': '100' + }); + $mdThemingProvider.theme('default') + .primaryPalette('green', { + 'default': '600', // top toolbar + 'hue-1': '200', + 'hue-2': '600', // sidebar toolbar + 'hue-3': 'A700' + }) + .accentPalette('green', { + 'default': '600', // fab buttons + 'hue-1': '50', // center list toolbar + 'hue-2': '400', + 'hue-3': 'A700' + }) + .backgroundPalette('grey', { + 'default': '50', // center list background + 'hue-1': '50', + 'hue-2': '100', + 'hue-3': '100' + }); + + $mdThemingProvider.setDefaultTheme('mailcow'); + $mdThemingProvider.generateThemesOnDemand(false); + } +})(); diff --git a/data/conf/sogo/sogo.conf b/data/conf/sogo/sogo.conf index 73cc2864..40e3928c 100644 --- a/data/conf/sogo/sogo.conf +++ b/data/conf/sogo/sogo.conf @@ -14,7 +14,7 @@ SOGoEnableEMailAlarms = NO; SOGoFoldersSendEMailNotifications = YES; SOGoForwardEnabled = YES; - SOGoUIAdditionalJSFiles = (js/theme.js); + SOGoUIAdditionalJSFiles = (js/theme-blue.js); // Multi-domain setup // Domains are isolated, you can define visibility options here. From 944b91a4b85635b62db14a2c9f5d6c6aada0560e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Peters?= Date: Thu, 22 Feb 2018 09:21:37 +0100 Subject: [PATCH 096/107] [Dockerapi] Add du command, push version --- data/Dockerfiles/dockerapi/server.py | 17 +++++++++++++++-- docker-compose.yml | 2 +- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/data/Dockerfiles/dockerapi/server.py b/data/Dockerfiles/dockerapi/server.py index eae472ed..57ed3570 100644 --- a/data/Dockerfiles/dockerapi/server.py +++ b/data/Dockerfiles/dockerapi/server.py @@ -67,10 +67,23 @@ class container_post(Resource): if not request.json or not 'cmd' in request.json: return jsonify(type='danger', msg='cmd is missing') - if request.json['cmd'] == 'sieve_list' and request.json['username']: + if request.json['cmd'] == 'df' and request.json['dir']: try: for container in docker_client.containers.list(filters={"id": container_id}): - return container.exec_run(["/bin/bash", "-c", "/usr/local/bin/doveadm sieve list -u '" + request.json['username'].replace("'", "'\\''") + "'"], user='vmail') + # Should be changed to be able to validate a path + directory = re.sub('[^0-9a-zA-Z/]+', '', request.json['dir']) + df_return = container.exec_run(["/bin/bash", "-c", "/bin/df -H " + directory + " | /usr/bin/tail -n1 | /usr/bin/tr -s [:blank:] | /usr/bin/tr ' ' ','"], user='nobody') + if df_return.exit_code == 0: + return df_return.output.rstrip() + else: + return "0,0,0,0,0,0" + except Exception as e: + return jsonify(type='danger', msg=str(e)) + elif request.json['cmd'] == 'sieve_list' and request.json['username']: + try: + for container in docker_client.containers.list(filters={"id": container_id}): + sieve_return = container.exec_run(["/bin/bash", "-c", "/usr/local/bin/doveadm sieve list -u '" + request.json['username'].replace("'", "'\\''") + "'"], user='vmail') + return sieve_return.output except Exception as e: return jsonify(type='danger', msg=str(e)) elif request.json['cmd'] == 'sieve_print' and request.json['script_name'] and request.json['username']: diff --git a/docker-compose.yml b/docker-compose.yml index 2f443e79..5e167164 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -369,7 +369,7 @@ services: - watchdog dockerapi-mailcow: - image: mailcow/dockerapi:1.8 + image: mailcow/dockerapi:1.9 restart: always build: ./data/Dockerfiles/dockerapi sysctls: From 165f6cb80291abb2f598a5d2d1c21b39fdd12b45 Mon Sep 17 00:00:00 2001 From: David Escala Date: Mon, 12 Feb 2018 08:23:48 +0100 Subject: [PATCH 097/107] Catalan language support --- data/web/inc/vars.inc.php | 2 +- data/web/lang/lang.ca.php | 536 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 537 insertions(+), 1 deletion(-) create mode 100644 data/web/lang/lang.ca.php diff --git a/data/web/inc/vars.inc.php b/data/web/inc/vars.inc.php index 4d3fd07a..da9af554 100644 --- a/data/web/inc/vars.inc.php +++ b/data/web/inc/vars.inc.php @@ -76,7 +76,7 @@ $DETECT_LANGUAGE = true; $DEFAULT_LANG = 'de'; // Available languages -$AVAILABLE_LANGUAGES = array('de', 'en', 'es', 'fr', 'nl', 'pl', 'pt', 'ru', 'it'); +$AVAILABLE_LANGUAGES = array('de', 'en', 'es', 'fr', 'nl', 'pl', 'pt', 'ru', 'it', 'ca'); // Change theme (default: lumen) // Needs to be one of those: cerulean, cosmo, cyborg, darkly, flatly, journal, lumen, paper, readable, sandstone, diff --git a/data/web/lang/lang.ca.php b/data/web/lang/lang.ca.php new file mode 100644 index 00000000..32a10dad --- /dev/null +++ b/data/web/lang/lang.ca.php @@ -0,0 +1,536 @@ +Important: Un reinici pot trigar una estona, si et plau espera a que acabi.'; + +$lang['footer']['confirm_delete'] = "Confirma l'esborrat "; +$lang['footer']['delete_these_items'] = 'Si et plau confirma els canvis al objecte amb id:'; +$lang['footer']['delete_now'] = 'Esborrar ara'; +$lang['footer']['cancel'] = 'Cancel·lar'; + +$lang['danger']['dkim_domain_or_sel_invalid'] = "Domini DKIM o selector incorrecte"; +$lang['success']['dkim_removed'] = "La clau DKIM %s s'ha esborrat"; +$lang['success']['dkim_added'] = "La clau DKIM s'ha desat"; +$lang['danger']['access_denied'] = "Accés denegat o dades incorrectes"; +$lang['danger']['domain_invalid'] = "Nom de domini incorrecte"; +$lang['danger']['mailbox_quota_exceeds_domain_quota'] = "La quota màxima sobrepassa el límit del domini"; +$lang['danger']['object_is_not_numeric'] = "El valor %s no és numèric"; +$lang['success']['domain_added'] = "S'ha afegit el domini %s"; +$lang['success']['items_deleted'] = "S'ha esborrat %s"; +$lang['danger']['alias_empty'] = "L'adreça de l'àlias no es pot deixar buida"; +$lang['danger']['last_key'] = 'No es pot esborrar la úlitma clau'; +$lang['danger']['goto_empty'] = "L'adreça \"goto\" no es pot deixar buida"; +$lang['danger']['policy_list_from_exists'] = "Ja existeix un registre amb aquest nom"; +$lang['danger']['policy_list_from_invalid'] = "El registre no té un format vàlid"; +$lang['danger']['alias_invalid'] = "L'adreça de l'alias és incorrecta"; +$lang['danger']['goto_invalid'] = "L'adreça del \"goto\" és incorrecta"; +$lang['danger']['alias_domain_invalid'] = "L'àlies del domini no és vàlid"; +$lang['danger']['target_domain_invalid'] = "El domini \"goto\" no és vàlid"; +$lang['danger']['object_exists'] = "L' objecte %s ja existeix"; +$lang['danger']['domain_exists'] = "El domini %s ja existeix"; +$lang['danger']['alias_goto_identical'] = "Les adreces d'àlies i 'goto' no poden ser iguals"; +$lang['danger']['aliasd_targetd_identical'] = "El domini àlies no pot ser igual al de destí"; +$lang['danger']['maxquota_empty'] = 'La quota màxima no pot ser 0.'; +$lang['success']['alias_added'] = "S'ha afegit el/s àlies"; +$lang['success']['alias_modified'] = "S'han desat els canvis fets al àlies"; +$lang['success']['mailbox_modified'] = "S'han desat els canvis fets a la bústia %s"; +$lang['success']['resource_modified'] = "S'han desat els canvis fets al recurs %s"; +$lang['success']['object_modified'] = "S'han desat els canvis fets a l'objecte %s"; +$lang['success']['f2b_modified'] = "S'han desat els canvis fets als parametres del Fail2ban"; +$lang['danger']['targetd_not_found'] = "No s'ha trobat el domini destí"; +$lang['success']['aliasd_added'] = "S'ha afegit l'àlies de domini %s"; +$lang['success']['aliasd_modified'] = "S'han desat els canvis fets a l'àlies de domini %s"; +$lang['success']['domain_modified'] = "S'han desat els canvis fets al domini %s"; +$lang['success']['domain_admin_modified'] = "S'ha modificat l'administrador de dominis %s"; +$lang['success']['domain_admin_added'] = "S'ha afegit l'administrador de dominis %s"; +$lang['success']['admin_modified'] = "Els canvis fets a l'administrador s'han desat"; +$lang['danger']['username_invalid'] = "El nom d'usuari no es pot fer servir"; +$lang['danger']['password_mismatch'] = "La confirmació de contrasenya no encaixa"; +$lang['danger']['password_complexity'] = "La contrasenya no compleix els requisits"; +$lang['danger']['password_empty'] = "La contrasenya no es pot deixar en blanc"; +$lang['danger']['login_failed'] = "L'inici de sessió ha fallat"; +$lang['danger']['mailbox_invalid'] = "El nom de la bústia no és vàlid"; +$lang['danger']['description_invalid'] = "La descripció del recurs no és vàlida"; +$lang['danger']['resource_invalid'] = "El nom del recurs no és vàlid"; +$lang['danger']['is_alias'] = "%s ja està definida com una direcció àlies"; +$lang['danger']['is_alias_or_mailbox'] = "%s ja està definit como un àlies o una bústia"; +$lang['danger']['is_spam_alias'] = "%s ja està definida com una adreça àlies de spam"; +$lang['danger']['quota_not_0_not_numeric'] = "La quaota ha de ser numèrica i >= 0"; +$lang['danger']['domain_not_found'] = "No s'ha trobat el domini"; +$lang['danger']['max_mailbox_exceeded'] = "S'ha arribat al màxim de bústies (%d de %d)"; +$lang['danger']['max_alias_exceeded'] = "S'ha arribat al màxim d'àlies"; +$lang['danger']['mailbox_quota_exceeded'] = "La quota exedeix el límit del domini (màx. %d MiB)"; +$lang['danger']['mailbox_quota_left_exceeded'] = "No queda espai suficient (espai lliure: %d MiB)"; +$lang['success']['mailbox_added'] = "S'ha afegit la bústia %s"; +$lang['success']['resource_added'] = "S'ha afegit el recurs %s"; +$lang['success']['domain_removed'] = "S'ha elminat el domini %s"; +$lang['success']['alias_removed'] = "S'ha esborrat l'àlies %s"; +$lang['success']['alias_domain_removed'] = "S'ha esborrat l'àlies de domini %s"; +$lang['success']['domain_admin_removed'] = "S'ha esborrat l'administrador de dominis %s"; +$lang['success']['mailbox_removed'] = "S'ha esborrat la bústia %s"; +$lang['success']['eas_reset'] = "S'ha fet un reset als dispositius ActiveSync de l'usuari %s"; +$lang['success']['resource_removed'] = "S'ha esborrat el recurs %s"; +$lang['danger']['max_quota_in_use'] = "La quota de la bústia ha de ser meś gran o igual a %d MiB"; +$lang['danger']['domain_quota_m_in_use'] = "La quota del domini ha de ser més gran o igual a %d MiB"; +$lang['danger']['mailboxes_in_use'] = "El número màxim de bústies ha de ser més gran o igual a %d"; +$lang['danger']['aliases_in_use'] = "El número màxim d'àlies ha de ser més gran o igual a %d"; +$lang['danger']['sender_acl_invalid'] = "L'ACL d'emissor no és vàlid"; +$lang['danger']['domain_not_empty'] = "No es pot esborrar un domini que no està buit"; +$lang['danger']['validity_missing'] = 'Si et plau posa un període de validesa'; +$lang['user']['loading'] = "Carregant..."; +$lang['user']['force_pw_update'] = "Has d'establir una nova contrassenya per poder accedir."; +$lang['user']['active_sieve'] = "Filtre actiu"; +$lang['user']['show_sieve_filters'] = "Mostra el filtre 'sieve' de l'usuari"; +$lang['user']['no_active_filter'] = "Actualment no hi ha cap filtre"; +$lang['user']['messages'] = "missatges"; // "123 messages" +$lang['user']['in_use'] = "Usat"; +$lang['user']['user_change_fn'] = ""; +$lang['user']['user_settings'] = "Configuració de l'usuari"; +$lang['user']['mailbox_details'] = 'Detalls de la bústia'; +$lang['user']['change_password'] = 'Canviar la contrasenya'; +$lang['user']['client_configuration'] = 'Guies de configuració per als clients de correu més habituals'; +$lang['user']['new_password'] = 'Contrasenya nova:'; +$lang['user']['save_changes'] = 'Desar els canvis'; +$lang['user']['password_now'] = 'Contrasenya actual (per validar els canvis):'; +$lang['user']['new_password_repeat'] = 'Confirmació de la contrasenya nova:'; +$lang['user']['new_password_description'] = 'Requisits: 6 caracters, lletres i números.'; +$lang['user']['spam_aliases'] = "Àlies d'email temporals"; +$lang['user']['alias'] = 'Àlies'; +$lang['user']['shared_aliases'] = 'Adreces àlies compartides'; +$lang['user']['shared_aliases_desc'] = "Un àlies compartit no es veu afectat per la configuració de l'usuari. L'administrador pot configurar un filtre de spam a nivell de domini."; +$lang['user']['direct_aliases'] = 'Adreces àlies directes'; +$lang['user']['direct_aliases_desc'] = "Els àlies directes sí que es veuen afectat per la configuració de l'usuari"; +$lang['user']['is_catch_all'] = 'Adreça atrapa-ho-tot'; +$lang['user']['aliases_also_send_as'] = 'Pot enviar com a aquests remitents'; +$lang['user']['aliases_send_as_all'] = "Al enviar no es verificarà l'adreça remitent per aquests dominis:"; +$lang['user']['alias_create_random'] = 'Generar àlies aleatori'; +$lang['user']['alias_extend_all'] = 'Afegir 1 hora als àlies'; +$lang['user']['alias_valid_until'] = 'Vàlid fins'; +$lang['user']['alias_remove_all'] = 'Esborrar tots els àlies'; +$lang['user']['alias_time_left'] = 'Temps restant'; +$lang['user']['alias_full_date'] = 'd.m.Y, H:i:s T'; +$lang['user']['alias_select_validity'] = 'Període de validesa'; +$lang['user']['sync_jobs'] = 'Feines de sincronització'; +$lang['user']['hour'] = 'Hora'; +$lang['user']['hours'] = 'Hores'; +$lang['user']['day'] = 'Dia'; +$lang['user']['week'] = 'Setmana'; +$lang['user']['weeks'] = 'Setmanes'; +$lang['user']['spamfilter'] = 'Filtre de spam'; +$lang['admin']['spamfilter'] = 'Filtre de spam'; +$lang['user']['spamfilter_wl'] = 'Llista blanca'; +$lang['user']['spamfilter_wl_desc'] = 'Les adreces de la lista blanca mai es clasificaran como a spam. Es pot fer serivir *@exemple.org.'; +$lang['user']['spamfilter_bl'] = 'Llista negra'; +$lang['user']['spamfilter_bl_desc'] = 'Les adreces de la lista negra sempre es clasificaran como a spam. Es pot fer servir *@exemple.org'; +$lang['user']['spamfilter_behavior'] = 'Classificació'; +$lang['user']['spamfilter_table_rule'] = 'Regla'; +$lang['user']['spamfilter_table_action'] = 'Acció'; +$lang['user']['spamfilter_table_empty'] = 'No hay datos para mostrar'; +$lang['user']['spamfilter_table_remove'] = 'Esborrar'; +$lang['user']['spamfilter_table_add'] = 'Afegir element'; +$lang['user']['spamfilter_green'] = 'Verd: el missatge no és spam'; +$lang['user']['spamfilter_yellow'] = "Groc: el missatge pot ser spam, s'etiquetarà com a spam i es mourà a la carpeta de correu brossa"; +$lang['user']['spamfilter_red'] = 'Vermell: El missatge és spam i, per tant, el servidor el refusarà'; +$lang['user']['spamfilter_default_score'] = 'Valors per defecte:'; +$lang['user']['spamfilter_hint'] = 'El primer valor representa el "llindar inferior de la qualificació de spam", el segon representa el "llindar superior de la qualificació de spam".'; +$lang['user']['spamfilter_table_domain_policy'] = "n/a (política del domini)"; +$lang['user']['waiting'] = "Esperant"; +$lang['user']['status'] = "Estat"; +$lang['user']['running'] = "En marxa"; + +$lang['user']['tls_policy_warning'] = "Avís: Al forçar el xifrat en les comunicacions es poden perdre missatges.
    Els missatges que no es puguin rebre o enviar xifrats, rebotaran.
    Aquesta opció s'aplica a l'adreça principal (nom d'usuari) i totes les adreces derivades d'àlies que només tinguin aquesta bústia com a destí"; +$lang['user']['tls_policy'] = "Política d'encriptació"; +$lang['user']['tls_enforce_in'] = 'Forçar TLS al rebre'; +$lang['user']['tls_enforce_out'] = 'Forçar TLS al enviar'; +$lang['user']['no_record'] = 'Sense registre'; + + +$lang['user']['tag_handling'] = 'Al rebre un missatge etiquetat'; +$lang['user']['tag_in_subfolder'] = 'Moure a subcarpeta'; +$lang['user']['tag_in_subject'] = 'Marcar al assumpte'; +$lang['user']['tag_in_none'] = 'No fer res'; +$lang['user']['tag_help_explain'] = 'Moure a subcarpeta: es crearà una subcarpeta anomenada com la etiqueta a INBOX ("INBOX/Facebook") i es mourà el correu allà.
    +Marcar al assumpte: s\'afegirà el nom de la etiqueta al assumpte del missatge, per exemple: "[Facebook] Les meves notícies".'; +$lang['user']['tag_help_example'] = 'Ejemplo de una dirección email etiquetada: mi+Facebook@ejemplo.org'; +$lang['user']['eas_reset'] = "Fer un reset de la cache d'ActiveSync del dispositiu"; +$lang['user']['eas_reset_now'] = "Resetejar cache d'ActiveSync"; +$lang['user']['eas_reset_help'] = 'El reset serveix per recuperar perfils ActiveSync trencats.
    Atenció: Tots els elements es tornaran a descarregar!'; + +$lang['user']['encryption'] = 'Xifrat'; +$lang['user']['username'] = 'Usuari'; +$lang['user']['last_run'] = 'Última execució'; +$lang['user']['excludes'] = 'Exclosos'; +$lang['user']['interval'] = 'Intèrval'; +$lang['user']['active'] = 'Actiu'; +$lang['user']['action'] = 'Acció'; +$lang['user']['edit'] = 'Editar'; +$lang['user']['remove'] = 'Esborrar'; +$lang['user']['create_syncjob'] = 'Afegir treball de sincronitzaió'; + +$lang['start']['mailcow_apps_detail'] = 'Tria una aplicació (de moment només SOGo) per a accedir als teus correus, calendari, contactes i més.'; +$lang['start']['mailcow_panel_detail'] = "Els administradors del domini poden crear, modificar o esborrar bústies i àlies, configurar i obtenir informació detallada sobre els seus dominis
    + Els usuaris d'e-mail poden crear àlies temporals (spam àlies), canviar la seva contrasenya i la configuració del seu filtre anti-spam."; + + +$lang['start']['help'] = "Mostrar/Ocultar panell d'ajuda"; +$lang['header']['mailcow_settings'] = 'Configuració'; +$lang['header']['administration'] = 'Administració'; +$lang['header']['mailboxes'] = 'Bústies'; +$lang['header']['user_settings'] = "Preferències d'usuari"; +$lang['mailbox']['domain'] = 'Domini'; +$lang['mailbox']['spam_aliases'] = 'Temp. àlies'; +$lang['mailbox']['multiple_bookings'] = 'Múltiples reserves'; +$lang['mailbox']['kind'] = 'Tipus'; +$lang['mailbox']['description'] = 'Descripció'; +$lang['mailbox']['alias'] = 'Àlies'; +$lang['mailbox']['aliases'] = 'Àlies'; +$lang['mailbox']['domains'] = 'Dominis'; +$lang['mailbox']['mailboxes'] = 'Bústies'; +$lang['mailbox']['resources'] = 'Recursos'; +$lang['mailbox']['mailbox_quota'] = 'Mida màx. de quota'; +$lang['mailbox']['domain_quota'] = 'Quota'; +$lang['mailbox']['active'] = 'Actiu'; +$lang['mailbox']['action'] = 'Acció'; +$lang['mailbox']['backup_mx'] = 'Backup MX'; +$lang['mailbox']['domain_aliases'] = 'Àlies de domini'; +$lang['mailbox']['target_domain'] = 'Domini destí'; +$lang['mailbox']['target_address'] = 'Direcció Goto'; +$lang['mailbox']['username'] = "Nom d'usuari"; +$lang['mailbox']['fname'] = 'Nom complert'; +$lang['mailbox']['filter_table'] = 'Filtrar taula'; +$lang['mailbox']['yes'] = '✔'; +$lang['mailbox']['no'] = '✘'; +$lang['mailbox']['in_use'] = 'En ús (%)'; +$lang['mailbox']['msg_num'] = 'Missatge #'; +$lang['mailbox']['remove'] = 'Esborrar'; +$lang['mailbox']['edit'] = 'Editar'; +$lang['mailbox']['no_record'] = "No hi ha cap registre per l'objecte %s"; +$lang['mailbox']['no_record_single'] = "No hi ha cap registre"; +$lang['mailbox']['add_domain'] = 'Afegir domini'; +$lang['mailbox']['add_domain_alias'] = 'Afegir àlies de domini'; +$lang['mailbox']['add_mailbox'] = 'Afegir bústia'; +$lang['mailbox']['add_resource'] = 'Afegir recurs'; +$lang['mailbox']['add_alias'] = 'Afegir àlies'; +$lang['mailbox']['add_domain_record_first'] = 'Primer afegeix un domini'; +$lang['mailbox']['empty'] = 'Cap resultat'; +$lang['mailbox']['toggle_all'] = "Tots"; +$lang['mailbox']['quick_actions'] = 'Accions'; +$lang['mailbox']['activate'] = 'Activar'; +$lang['mailbox']['deactivate'] = 'Desactivar'; +$lang['mailbox']['owner'] = 'Propietari'; +$lang['mailbox']['mins_interval'] = 'Intèrval (min)'; +$lang['mailbox']['last_run'] = 'Última execució'; +$lang['mailbox']['excludes'] = 'Exclou'; +$lang['mailbox']['last_run_reset'] = 'Executar a continuació'; +$lang['mailbox']['sieve_info'] = 'Podeu emmagatzemar diversos filtres per usuari, però només un filtre previ i un filtre posterior poden estar actius al mateix temps.
    +Cada filtre es processarà en l\'ordre descrit. Ni un script que falli, ni un "keep" emès farà que es deixin de processar els altres scripts.
    +Filtre previ → Filtre d\'usuari → Filtre posterior → Filtre global'; +$lang['info']['no_action'] = 'No hi ha cap acció aplicable'; + + +$lang['edit']['syncjob'] = 'Sync job'; +$lang['edit']['username'] = 'Usuari'; +$lang['edit']['hostname'] = 'Host'; +$lang['edit']['encryption'] = 'Xifrat'; +$lang['edit']['maxage'] = 'Salta missatges més vells del número de dies
    (0 = no salta res)'; +$lang['edit']['maxbytespersecond'] = 'Màx. bytes per segon
    (0 = il.limitat)'; +$lang['edit']['automap'] = 'Provar de mapejar automàticament ("Sent items", "Sent" => "Sent" etc.)'; +$lang['edit']['skipcrossduplicates'] = 'Saltar missatges duplicats entre carpetes (només es desa el primer)'; +$lang['add']['automap'] = $lang['edit']['automap']; +$lang['add']['skipcrossduplicates'] = $lang['edit']['skipcrossduplicates']; +$lang['edit']['subfolder2'] = 'Sincronitza a la subcarpeta destí
    (buit = no usar subcarpeta)'; +$lang['edit']['mins_interval'] = 'Intèrval (min)'; +$lang['edit']['exclude'] = 'Excloure els objectes (regex)'; +$lang['edit']['save'] = 'Desar els canvis'; +$lang['edit']['max_mailboxes'] = 'Màx. bústies possibles:'; +$lang['edit']['title'] = "Editar l'objecte"; +$lang['edit']['target_address'] = 'Direcció/ns goto (separades per coma):'; +$lang['edit']['active'] = 'Actiu'; +$lang['edit']['force_pw_update'] = "Forçar l'actualització de la contrassenya al proper login"; +$lang['edit']['force_pw_update_info'] = 'Aquest usuari només podrà accedir a la interfície de gestió.'; +$lang['edit']['target_domain'] = 'Domini destí:'; +$lang['edit']['password'] = 'Contrasenya:'; +$lang['edit']['password_repeat'] = 'Confirmació de contrasenya (repetir):'; +$lang['edit']['domain_admin'] = 'Editar administrador del domini'; +$lang['edit']['domain'] = 'Editar domini'; +$lang['edit']['edit_alias_domain'] = 'Editar àlies de domini'; +$lang['edit']['domains'] = 'Dominis'; +$lang['edit']['alias'] = 'Editar àlies'; +$lang['edit']['mailbox'] = 'Editar bustia'; +$lang['edit']['description'] = 'Descripció:'; +$lang['edit']['max_aliases'] = 'Màx. àlies:'; +$lang['edit']['max_quota'] = 'Màx. quota per bústia (MiB):'; +$lang['edit']['domain_quota'] = 'Quota de domini:'; +$lang['edit']['backup_mx_options'] = 'Opcions backup MX:'; +$lang['edit']['relay_domain'] = 'Domini de retransmisió (relay)'; +$lang['edit']['relay_all'] = 'Retransmetre tods els recipients'; +$lang['edit']['relay_all_info'] = "Si tries no retransmetre a tods els recipients, necessitàs afegir una bústia \"blind\"(\"cega\") per cada recipient que s'hagi de retransmetre."; +$lang['edit']['full_name'] = 'Nom complet'; +$lang['edit']['quota_mb'] = 'Quota (MiB)'; +$lang['edit']['sender_acl'] = 'Permetre enviar com a'; +$lang['edit']['previous'] = 'Pàgina anterior'; +$lang['edit']['unchanged_if_empty'] = 'Si no hay cambios dejalo en blanco'; +$lang['edit']['dont_check_sender_acl'] = 'No verifiques remitente para el dominio %s'; +$lang['edit']['multiple_bookings'] = 'Reserves múltiples'; +$lang['edit']['kind'] = 'Tipus'; +$lang['edit']['resource'] = 'Recurs'; + +$lang['add']['syncjob'] = 'Afegir sync job'; +$lang['add']['syncjob_hint'] = 'Tingues en compte que les contrasenyes es desen sense xifrar!'; +$lang['add']['hostname'] = 'Hostname'; +$lang['add']['port'] = 'Port'; +$lang['add']['username'] = 'Username'; +$lang['add']['enc_method'] = 'Mètode de xifrat'; +$lang['add']['mins_interval'] = 'Intèrval (minuts)'; +$lang['add']['exclude'] = $lang['edit']['exclude']; +$lang['add']['delete2duplicates'] = 'Eliminar els duplicats al destí'; +$lang['add']['delete1'] = "Esborrar de l'origen un cop s'han copiat"; +$lang['add']['delete2'] = "Esborrar els missatges a destí que no son al origen"; +$lang['edit']['delete2duplicates'] = $lang['add']['delete2duplicates']; +$lang['edit']['delete1'] = $lang['add']['delete1']; +$lang['edit']['delete2'] = $lang['add']['delete2']; + +$lang['add']['domain'] = 'Domini'; +$lang['add']['active'] = 'Actiu'; +$lang['add']['multiple_bookings'] = 'Reserves múltiples'; +$lang['add']['description'] = 'Descripció:'; +$lang['add']['max_aliases'] = 'Màx. àlies possibles:'; +$lang['add']['max_mailboxes'] = 'Màx. bústies possibles:'; +$lang['add']['mailbox_quota_m'] = 'Màx. quota per bústia (MiB):'; +$lang['add']['domain_quota_m'] = 'Quota total del domini (MiB):'; +$lang['add']['backup_mx_options'] = $lang['edit']['backup_mx_options']; +$lang['add']['relay_all'] = $lang['edit']['relay_all']; +$lang['add']['relay_domain'] = $lang['edit']['relay_domain']; +$lang['add']['relay_all_info'] = $lang['edit']['relay_all_info']; +$lang['add']['alias_address'] = 'Dirección/es àlies:'; +$lang['add']['alias_address_info'] = 'Adreces de correu completes, o @exemple.com per a atrapar tots els missatges per a un domini (separats per coma). Només dominis interns.'; +$lang['add']['alias_domain_info'] = 'Només noms de domini vàlids (separats per coma).'; +$lang['add']['target_address'] = 'Adreces goto:'; +$lang['add']['target_address_info'] = 'Adreces de corru completes (separades per coma).'; +$lang['add']['alias_domain'] = 'Domini àlies'; +$lang['add']['select'] = 'Si et plau selecciona...'; +$lang['add']['target_domain'] = 'Domini destí:'; +$lang['add']['kind'] = 'Kind'; +$lang['add']['mailbox_username'] = "Nom d'usuari (part de l'esquerra de l'adreça @):"; +$lang['add']['full_name'] = 'Nom complet:'; +$lang['add']['quota_mb'] = 'Quota (MiB):'; +$lang['add']['select_domain'] = 'Si et plau, primer selecciona un domini'; +$lang['add']['password'] = 'Constrasenya:'; +$lang['add']['password_repeat'] = 'Confirmació de contrasenya (repetir):'; +$lang['add']['restart_sogo_hint'] = "Necessites reiniciar el contenidor del servei SOGo després d'afegir un nou domini"; +$lang['add']['goto_null'] = 'Descartar mail silenciosament'; +$lang['add']['validation_success'] = 'Validated successfully'; +$lang['add']['activate_filter_warn'] = 'All other filters will be deactivated, when active is checked.'; +$lang['add']['validate'] = 'Validar'; +$lang['mailbox']['add_filter'] = 'Afegir filter'; +$lang['add']['sieve_desc'] = 'Short description'; +$lang['edit']['sieve_desc'] = 'Short description'; +$lang['add']['sieve_type'] = 'Filter type'; +$lang['edit']['sieve_type'] = 'Filter type'; +$lang['mailbox']['set_prefilter'] = 'Mark as prefilter'; +$lang['mailbox']['set_postfilter'] = 'Mark as postfilter'; +$lang['mailbox']['filters'] = 'Filtres'; +$lang['mailbox']['sync_jobs'] = 'Sync jobs'; +$lang['mailbox']['inactive'] = 'Inactiu'; +$lang['edit']['validate_save'] = 'Validar i desar'; + + +$lang['login']['username'] = "Nom d'usuari"; +$lang['login']['password'] = 'Contrasenya'; +$lang['login']['login'] = 'Inici de sessió'; +$lang['login']['delayed'] = "Pots iniciar de sessió passats %s segons."; + +$lang['tfa']['tfa'] = "Autenticació de dos factors"; +$lang['tfa']['set_tfa'] = "Definir el mètode d'autenticació de dos factors"; +$lang['tfa']['yubi_otp'] = "Autenticació OTP de Yubico"; +$lang['tfa']['key_id'] = "Un identificador per la teva YubiKey"; +$lang['tfa']['key_id_totp'] = "Un identificador per la teva clau"; +$lang['tfa']['api_register'] = 'mailcow fa servir la Yubico Cloud API. Obté una API key per la teva clau aquí'; +$lang['tfa']['u2f'] = "Autenticació U2F"; +$lang['tfa']['none'] = "Desactivar"; +$lang['tfa']['delete_tfa'] = "Desactivar TFA"; +$lang['tfa']['disable_tfa'] = "Desactivar TFA fins al següent login"; +$lang['tfa']['confirm'] = "Confirma"; +$lang['tfa']['totp'] = "OTP basat en temps (Google Authenticator etc.)"; +$lang['tfa']['select'] = "Si et plau, selecciona"; +$lang['tfa']['waiting_usb_auth'] = "Esperant el dispositiu USB...

    Apreta el botó del teu dispositiu USB U2F ara."; +$lang['tfa']['waiting_usb_register'] = "Esperant el dispositiu USB...

    Posa el teu password i confirma el registre del teu U2F apretant el botó del teu dispositiiu USB U2F."; +$lang['tfa']['scan_qr_code'] = "Escaneja el codi següent amb la teva app d'autenticació o entra'l manualment."; +$lang['tfa']['enter_qr_code'] = "El teu codi TOTP, si el teu dispositiu no pot escanejar codis QR"; +$lang['tfa']['confirm_totp_token'] = "Confirma els canvis introduint el codi generat"; + +$lang['admin']['no_new_rows'] = 'No hi ha més files'; +$lang['admin']['additional_rows'] = ' files addicionals afegides'; // parses to 'n additional rows were added' +$lang['admin']['private_key'] = 'Clau privada'; +$lang['admin']['import'] = 'Importar'; +$lang['admin']['import_private_key'] = 'Importar clau privada'; +$lang['admin']['f2b_parameters'] = 'Fail2ban'; +$lang['admin']['f2b_ban_time'] = 'Temsp de bloqueig (s)'; +$lang['admin']['f2b_max_attempts'] = 'Intents màx.'; +$lang['admin']['f2b_retry_window'] = 'Finestra de reintent (s) per intents màx.'; +$lang['admin']['f2b_netban_ipv4'] = 'Suxarxa IPv4 on aplicar el bloqueig (8-32)'; +$lang['admin']['f2b_netban_ipv6'] = 'Suxarxa IPv6 on aplicar el bloqueig (8-128)'; +$lang['admin']['f2b_whitelist'] = 'Llista blanca de xarxes/hosts'; +$lang['admin']['search_domain_da'] = 'Buscar dominis'; + + + +$lang['admin']['dkim_key_length'] = 'Mida de la clau DKIM (bits)'; +$lang['admin']['dkim_key_valid'] = 'Vàlida'; +$lang['admin']['dkim_key_unused'] = 'No es fa servir'; +$lang['admin']['dkim_key_missing'] = 'No té clau'; +$lang['admin']['dkim_add_key'] = 'Afegir registre ARC/DKIM'; +$lang['admin']['dkim_keys'] = 'Registres ARC/DKIM'; +$lang['admin']['add'] = 'Afegir'; +$lang['add']['add_domain_restart'] = 'Afegir el domini i reiniciar SOGo'; +$lang['add']['add_domain_only'] = 'Afegir el domini'; +$lang['admin']['configuration'] = 'Configuració'; +$lang['admin']['password'] = 'Contrasenya'; +$lang['admin']['password_repeat'] = 'Confirmació de la contrasenya (repetir)'; +$lang['admin']['active'] = 'Actiu'; +$lang['admin']['inactive'] = 'Inactiu'; +$lang['admin']['action'] = 'Acció'; +$lang['admin']['add_domain_admin'] = 'Afegir Administrador del dominio'; +$lang['admin']['admin_domains'] = 'Asignaciones de dominio'; +$lang['admin']['domain_admins'] = 'Administradores de dominio'; +$lang['admin']['username'] = "Nom d'usuari"; +$lang['admin']['edit'] = 'Editar'; +$lang['admin']['remove'] = 'Esborrar'; +$lang['admin']['save'] = 'Desar els canvis'; +$lang['admin']['admin'] = 'Administrador'; +$lang['admin']['admin_details'] = "Editar detalls de l'administrador"; +$lang['admin']['unchanged_if_empty'] = "Si no hi ha canvis, deixa'l en blanc"; +$lang['admin']['yes'] = '✔'; +$lang['admin']['no'] = '✘'; +$lang['admin']['access'] = 'Accés'; +$lang['admin']['no_record'] = 'Cap registre'; +$lang['admin']['filter_table'] = 'Filtrar taula'; +$lang['admin']['empty'] = 'Sense resultats'; +$lang['admin']['refresh'] = 'Refrescar'; +$lang['admin']['to_top'] = 'Tornar a dalt'; +$lang['admin']['in_use_by'] = 'En ús per'; +$lang['admin']['refresh'] = 'Refrescar'; +$lang['admin']['to_top'] = 'Tornar a dalt'; +$lang['admin']['in_use_by'] = 'En ús per'; +$lang['admin']['forwarding_hosts'] = 'Forwarding Hosts'; +$lang['admin']['forwarding_hosts_hint'] = "Els missatges entrants s'accepten de forma incondicional a qualsevol host que apareix aquí. Aquests hosts no es comproven a cap DNSBL ni estan sotmesos a greylisting. El spam rebut mai no es rebutja, però opcionalment es pot arxivar a la carpeta Junk. L'ús més comú d'això és especificar altres servidors de correu en els quals s'ha configurat una regla que reenvia correus electrònics entrants a aquest servidor"; +$lang['admin']['forwarding_hosts_add_hint'] = "Podeu especificar adreces IPv4/IPv6, xarxes en notació CIDR, noms de host (que es resoldran a adreces IP) o noms de domini (que es resoldran a les adreces IP consultant els registres SPF o, si no n'hi ha, registres MX )."; +$lang['admin']['relayhosts_hint'] = 'Defineix aquí els relayhosts per després poder-los seleccionar als dominis.'; +$lang['admin']['add_relayhost_add_hint'] = "Tingues en compte que les dades d'autenticació al relayhost es desaran sense xifrar."; +$lang['admin']['host'] = 'Host'; +$lang['admin']['source'] = 'Origen'; +$lang['admin']['add_forwarding_host'] = 'Afegir Forwarding Host'; +$lang['admin']['add_relayhost'] = 'Afegir Relayhost'; +$lang['success']['forwarding_host_removed'] = "Forwarding host %s esborrat"; +$lang['success']['forwarding_host_added'] = "Forwarding host %s afegit"; +$lang['success']['relayhost_removed'] = "Relayhost %s esborrat"; +$lang['success']['relayhost_added'] = "Relayhost %s afegit"; +$lang['diagnostics']['dns_records'] = 'Registres DNS'; +$lang['diagnostics']['dns_records_24hours'] = "Tingues en compte que els canvis realitzats als DNS poden trigar fins a 24 hores a reflectir correctament el seu estat actual en aquesta pàgina. Es tracta d'una manera de veure fàcilment com configurar els registres DNS i comprovar si tots els registres DNS son correctes."; +$lang['diagnostics']['dns_records_name'] = 'Nom'; +$lang['diagnostics']['dns_records_type'] = 'Tipus'; +$lang['diagnostics']['dns_records_data'] = 'Valor correcte'; +$lang['diagnostics']['dns_records_status'] = 'Valor actual'; +$lang['diagnostics']['optional'] = 'Aquest registre és opcional.'; +$lang['diagnostics']['cname_from_a'] = 'Valor derivat de registre A/AAAA. Això és compatible sempre que el registre assenyali el recurs correcte.'; + +$lang['admin']['relay_from'] = '"From:" adreça'; +$lang['admin']['api_allow_from'] = "Permetre l'accés a la API des d'aquestes IPs"; +$lang['admin']['api_key'] = "API key"; +$lang['admin']['activate_api'] = "Activar API"; +$lang['admin']['regen_api_key'] = "Regenerar API key"; + +$lang['admin']['quarantaine'] = "Quarantena"; +$lang['admin']['quarantaine_retention_size'] = "Retentions per mailbox:"; +$lang['admin']['quarantaine_max_size'] = "Mida màxima en MiB (més grans es descarten):"; +$lang['admin']['quarantaine_exclude_domains'] = "Excloure els dominis i àlies de domini:"; + +$lang['admin']['ui_texts'] = "Etiquetes i textos de la UI"; +$lang['admin']['help_text'] = "Text alternatiu per a l'ajuda de sota la casella de login (es permet HTML)"; +$lang['admin']['title_name'] = 'Nom del lloc "mailcow UI"'; +$lang['admin']['main_name'] = 'Nom de "mailcow UI"'; +$lang['admin']['apps_name'] = 'Nom de "mailcow Apps"'; + +$lang['admin']['customize'] = "Personalitzar"; +$lang['admin']['change_logo'] = "Canviar el logo"; +$lang['admin']['logo_info'] = "La vostra imatge es reduirà a una alçada de 40 píxels per a la barra de navegació superior i un màx. ample de 250 px per a la pàgina d'inici. És molt recomanable un gràfic escalable"; +$lang['admin']['upload'] = "Pujar"; +$lang['admin']['app_links'] = "Enllaços a App"; +$lang['admin']['app_name'] = "Nom de la App"; +$lang['admin']['link'] = "Enllaç"; +$lang['admin']['remove_row'] = "Eliminar fila"; +$lang['admin']['add_row'] = "Afegir fila"; +$lang['admin']['reset_default'] = "Restablir"; +$lang['admin']['merged_vars_hint'] = 'Les files en gris venen de vars.(local.)inc.php i no es poden modificar.'; +$lang['mailbox']['waiting'] = "Esperant"; +$lang['mailbox']['status'] = "Estat"; +$lang['mailbox']['running'] = "Executant-se"; + +$lang['edit']['spam_score'] = "Especifica una puntuació de spam"; +$lang['edit']['spam_policy'] = "Afegeix o treu elementes a la white-/blacklist"; +$lang['edit']['spam_alias'] = "Crear o canviar alies temporals limitats per temps"; +$lang['danger']['img_tmp_missing'] = "No s'ha validat el fitxer de la imatge: el fitxer temporal no s'ha trobat"; +$lang['danger']['img_invalid'] = "No s'ha validat el fitxer de la imatge"; +$lang['danger']['invalid_mime_type'] = "Mime type invàlid"; +$lang['success']['upload_success'] = "El fitxer s'ha pujat"; +$lang['success']['app_links'] = "S'han desat els canvis als enallaços a App"; +$lang['success']['ui_texts'] = "S'han desat els canvis als noms de App"; +$lang['success']['reset_main_logo'] = "Restablir el logo per defecte"; +$lang['success']['items_released'] = "S'han alliberat els elements seleccionats"; +$lang['danger']['imagick_exception'] = "Error: exepció de Imagick mentre es llegia la imatge"; + +$lang['quarantine']['quarantine'] = "Quarantena"; +$lang['quarantine']['qinfo'] = "El sistema de quarantena desa a la base de dades els missatges que han estat refusats. El missatge, al emissor, li consta com a no lliurat."; +$lang['quarantine']['release'] = "Alliberar"; +$lang['quarantine']['empty'] = 'No hi ha elements'; +$lang['quarantine']['toggle_all'] = 'Marcar tots'; +$lang['quarantine']['quick_actions'] = 'Accions'; +$lang['quarantine']['remove'] = 'Esborrar'; +$lang['quarantine']['received'] = "Rebut"; +$lang['quarantine']['action'] = "Acció"; +$lang['quarantine']['rcpt'] = "Receptor"; +$lang['quarantine']['rcpt'] = "Recipient"; +$lang['quarantine']['qid'] = "Rspamd QID"; +$lang['quarantine']['sender'] = "Emissor"; +$lang['quarantine']['show_item'] = "Mostrar"; +$lang['quarantine']['check_hash'] = "Comprovar el hash del fitxer a VT"; +$lang['quarantine']['qitem'] = "Element en quarantena"; +$lang['quarantine']['subj'] = "Assumpte"; +$lang['quarantine']['text_plain_content'] = "Contingut (text/plain)"; +$lang['quarantine']['text_from_html_content'] = "Contingut (a partir del HTML)"; +$lang['quarantine']['atts'] = "Adjunts"; + +$lang['header']['quarantine'] = "Quarantena"; +$lang['header']['debug'] = "Debug"; + +$lang['quarantine']['release_body'] = "Hem adjuntat el teu missatge com a eml en aquest missatge."; +$lang['danger']['release_send_failed'] = "No s'ha pogut alliberar el missatge: %s"; +$lang['quarantine']['release_subject'] = "Element potencialment perillós %s en quarantena"; + +$lang['mailbox']['bcc_map_type'] = "BCC type"; +$lang['mailbox']['bcc_type'] = "BCC type"; +$lang['mailbox']['bcc_sender_map'] = "Sender map"; +$lang['mailbox']['bcc_rcpt_map'] = "Recipient map"; +$lang['mailbox']['bcc_local_dest'] = "Destinació local"; +$lang['mailbox']['bcc_destinations'] = "Destí/ns BCC"; + +$lang['mailbox']['bcc'] = "BCC"; +$lang['mailbox']['bcc_maps'] = "BCC maps"; +$lang['mailbox']['bcc_to_sender'] = "Canviar a 'Sender map'"; +$lang['mailbox']['bcc_to_rcpt'] = "Canviar a 'Recipient map'"; +$lang['mailbox']['add_bcc_entry'] = "Afegir BCC map"; +$lang['mailbox']['bcc_info'] = "Els 'BCC maps' s'utilitzen per enviar còpies silencioses de tots els missatges a una altra adreça. S'utilitza una entrada del tipus 'Recipient map' quan la destinació local actua com a destinatari d'un correu. Els 'Sender map' segueixen el mateix principi.
    + La destinació local no serà informada sobre un lliurament fallit."; +$lang['mailbox']['address_rewriting'] = "Re-escriptura d'adreces"; +$lang['mailbox']['recipient_maps'] = 'Recipient maps'; +$lang['mailbox']['recipient_map_info'] = "Els 'Recipient maps' es fan servir per canviar l'adreça del destinatari abans de lliurar el missatge"; +$lang['mailbox']['recipient_map_old'] = 'Destinatari original'; +$lang['mailbox']['recipient_map_new'] = 'Nou destinatari'; +$lang['mailbox']['add_recipient_map_entry'] = "Afegir 'Recipient map'"; +$lang['mailbox']['add_sender_map_entry'] = "Afegir 'Sender map'"; From c95cf982f41f0d6ec13e51865c5c6f95a6846836 Mon Sep 17 00:00:00 2001 From: David Escala Date: Sat, 24 Feb 2018 09:55:06 +0100 Subject: [PATCH 098/107] Adds catalan language flag A side efect is the IconDrawer flag images are replaced by the ones from country-flag-icons by Wil Linssen, which in turn come from the Wikipedia And the US flag is relaced by the UK flag for english --- data/web/inc/languages.min.css | 2 +- data/web/inc/languages.png | Bin 45164 -> 60615 bytes 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/data/web/inc/languages.min.css b/data/web/inc/languages.min.css index d0e5162c..7293e888 100644 --- a/data/web/inc/languages.min.css +++ b/data/web/inc/languages.min.css @@ -1 +1 @@ -.lang-xs{background-position:0 -473px;min-width:14px;height:11px;min-height:11px;max-height:11px;background-repeat:no-repeat;display:inline-block;background-image:url(languages.png)}.lang-sm{background-position:0 -1172px;min-width:22px;height:16px;min-height:16px;max-height:16px;background-repeat:no-repeat;display:inline-block;background-image:url(languages.png)}.lang-lg{background-position:0 -2134px;min-width:30px;height:22px;min-height:22px;max-height:22px;background-repeat:no-repeat;display:inline-block;background-image:url(languages.png)}.lang-xs[lang=ar]{background-position:0 0}.lang-xs[lang=be]{background-position:0 -11px}.lang-xs[lang=bg]{background-position:0 -22px}.lang-xs[lang=cs]{background-position:0 -33px}.lang-xs[lang=da]{background-position:0 -44px}.lang-xs[lang=de]{background-position:0 -55px}.lang-xs[lang=el]{background-position:0 -66px}.lang-xs[lang=en]{background-position:0 -77px}.lang-xs[lang=es]{background-position:0 -88px}.lang-xs[lang=et]{background-position:0 -99px}.lang-xs[lang=fi]{background-position:0 -110px}.lang-xs[lang=fr]{background-position:0 -121px}.lang-xs[lang=ga]{background-position:0 -132px}.lang-xs[lang=hi]{background-position:0 -143px}.lang-xs[lang=hr]{background-position:0 -154px}.lang-xs[lang=hu]{background-position:0 -165px}.lang-xs[lang=in]{background-position:0 -176px}.lang-xs[lang=is]{background-position:0 -187px}.lang-xs[lang=it]{background-position:0 -198px}.lang-xs[lang=iw]{background-position:0 -209px}.lang-xs[lang=ja]{background-position:0 -220px}.lang-xs[lang=ko]{background-position:0 -231px}.lang-xs[lang=lt]{background-position:0 -242px}.lang-xs[lang=lv]{background-position:0 -253px}.lang-xs[lang=mk]{background-position:0 -264px}.lang-xs[lang=ms]{background-position:0 -275px}.lang-xs[lang=mt]{background-position:0 -286px}.lang-xs[lang=nl]{background-position:0 -297px}.lang-xs[lang=no]{background-position:0 -308px}.lang-xs[lang=pl]{background-position:0 -319px}.lang-xs[lang=pt]{background-position:0 -330px}.lang-xs[lang=ro]{background-position:0 -341px}.lang-xs[lang=ru]{background-position:0 -352px}.lang-xs[lang=sk]{background-position:0 -363px}.lang-xs[lang=sl]{background-position:0 -374px}.lang-xs[lang=sq]{background-position:0 -385px}.lang-xs[lang=sr]{background-position:0 -396px}.lang-xs[lang=sv]{background-position:0 -407px}.lang-xs[lang=th]{background-position:0 -418px}.lang-xs[lang=tr]{background-position:0 -429px}.lang-xs[lang=uk]{background-position:0 -440px}.lang-xs[lang=vi]{background-position:0 -451px}.lang-xs[lang=zh]{background-position:0 -462px}.lang-sm[lang=ar]{background-position:0 -484px}.lang-sm[lang=be]{background-position:0 -500px}.lang-sm[lang=bg]{background-position:0 -516px}.lang-sm[lang=cs]{background-position:0 -532px}.lang-sm[lang=da]{background-position:0 -548px}.lang-sm[lang=de]{background-position:0 -564px}.lang-sm[lang=el]{background-position:0 -580px}.lang-sm[lang=en]{background-position:0 -596px}.lang-sm[lang=es]{background-position:0 -612px}.lang-sm[lang=et]{background-position:0 -628px}.lang-sm[lang=fi]{background-position:0 -644px}.lang-sm[lang=fr]{background-position:0 -660px}.lang-sm[lang=ga]{background-position:0 -676px}.lang-sm[lang=hi]{background-position:0 -692px}.lang-sm[lang=hr]{background-position:0 -708px}.lang-sm[lang=hu]{background-position:0 -724px}.lang-sm[lang=in]{background-position:0 -740px}.lang-sm[lang=is]{background-position:0 -756px}.lang-sm[lang=it]{background-position:0 -772px}.lang-sm[lang=iw]{background-position:0 -788px}.lang-sm[lang=ja]{background-position:0 -804px}.lang-sm[lang=ko]{background-position:0 -820px}.lang-sm[lang=lt]{background-position:0 -836px}.lang-sm[lang=lv]{background-position:0 -852px}.lang-sm[lang=mk]{background-position:0 -868px}.lang-sm[lang=ms]{background-position:0 -884px}.lang-sm[lang=mt]{background-position:0 -900px}.lang-sm[lang=nl]{background-position:0 -916px}.lang-sm[lang=no]{background-position:0 -932px}.lang-sm[lang=pl]{background-position:0 -948px}.lang-sm[lang=pt]{background-position:0 -964px}.lang-sm[lang=ro]{background-position:0 -980px}.lang-sm[lang=ru]{background-position:0 -996px}.lang-sm[lang=sk]{background-position:0 -1012px}.lang-sm[lang=sl]{background-position:0 -1028px}.lang-sm[lang=sq]{background-position:0 -1044px}.lang-sm[lang=sr]{background-position:0 -1060px}.lang-sm[lang=sv]{background-position:0 -1076px}.lang-sm[lang=th]{background-position:0 -1092px}.lang-sm[lang=tr]{background-position:0 -1108px}.lang-sm[lang=uk]{background-position:0 -1124px}.lang-sm[lang=vi]{background-position:0 -1140px}.lang-sm[lang=zh]{background-position:0 -1156px}.lang-lg[lang=ar]{background-position:0 -1188px}.lang-lg[lang=be]{background-position:0 -1210px}.lang-lg[lang=bg]{background-position:0 -1232px}.lang-lg[lang=cs]{background-position:0 -1254px}.lang-lg[lang=da]{background-position:0 -1276px}.lang-lg[lang=de]{background-position:0 -1298px}.lang-lg[lang=el]{background-position:0 -1320px}.lang-lg[lang=en]{background-position:0 -1342px}.lang-lg[lang=es]{background-position:0 -1364px}.lang-lg[lang=et]{background-position:0 -1386px}.lang-lg[lang=fi]{background-position:0 -1408px}.lang-lg[lang=fr]{background-position:0 -1430px}.lang-lg[lang=ga]{background-position:0 -1452px}.lang-lg[lang=hi]{background-position:0 -1474px}.lang-lg[lang=hr]{background-position:0 -1496px}.lang-lg[lang=hu]{background-position:0 -1518px}.lang-lg[lang=in]{background-position:0 -1540px}.lang-lg[lang=is]{background-position:0 -1562px}.lang-lg[lang=it]{background-position:0 -1584px}.lang-lg[lang=iw]{background-position:0 -1606px}.lang-lg[lang=ja]{background-position:0 -1628px}.lang-lg[lang=ko]{background-position:0 -1650px}.lang-lg[lang=lt]{background-position:0 -1672px}.lang-lg[lang=lv]{background-position:0 -1694px}.lang-lg[lang=mk]{background-position:0 -1716px}.lang-lg[lang=ms]{background-position:0 -1738px}.lang-lg[lang=mt]{background-position:0 -1760px}.lang-lg[lang=nl]{background-position:0 -1782px}.lang-lg[lang=no]{background-position:0 -1804px}.lang-lg[lang=pl]{background-position:0 -1826px}.lang-lg[lang=pt]{background-position:0 -1848px}.lang-lg[lang=ro]{background-position:0 -1870px}.lang-lg[lang=ru]{background-position:0 -1892px}.lang-lg[lang=sk]{background-position:0 -1914px}.lang-lg[lang=sl]{background-position:0 -1936px}.lang-lg[lang=sq]{background-position:0 -1958px}.lang-lg[lang=sr]{background-position:0 -1980px}.lang-lg[lang=sv]{background-position:0 -2002px}.lang-lg[lang=th]{background-position:0 -2024px}.lang-lg[lang=tr]{background-position:0 -2046px}.lang-lg[lang=uk]{background-position:0 -2068px}.lang-lg[lang=vi]{background-position:0 -2090px}.lang-lg[lang=zh]{background-position:0 -2112px}.lang-lbl-en:after,.lang-lbl-full:after,.lang-lbl:after{content:"Unknown language"}.lang-lbl[lang=ar]:after{content:"\000627\000644\000639\000631\000628\00064A\000629"}.lang-lbl[lang=be]:after{content:"\000411\000435\00043B\000430\000440\000443\000441\00043A\000456"}.lang-lbl[lang=bg]:after{content:"\000411\00044A\00043B\000433\000430\000440\000441\00043A\000438"}.lang-lbl[lang=cs]:after{content:"\00010Ce\000161tina"}.lang-lbl[lang=da]:after{content:"Dansk"}.lang-lbl[lang=de]:after{content:"Deutsch"}.lang-lbl[lang=el]:after{content:"\000395\0003BB\0003BB\0003B7\0003BD\0003B9\0003BA\0003AC"}.lang-lbl[lang=en]:after{content:"English"}.lang-lbl[lang=es]:after{content:"Espa\0000F1ol"}.lang-lbl[lang=et]:after{content:"Eesti"}.lang-lbl[lang=fi]:after{content:"Suomi"}.lang-lbl[lang=fr]:after{content:"Fran\0000E7ais"}.lang-lbl[lang=ga]:after{content:"Gaeilge"}.lang-lbl[lang=hi]:after{content:"\000939\00093F\000902\000926\000940"}.lang-lbl[lang=hr]:after{content:"Hrvatski"}.lang-lbl[lang=hu]:after{content:"Magyar"}.lang-lbl[lang=in]:after{content:"Bahasa\000020indonesia"}.lang-lbl[lang=is]:after{content:"\0000CDslenska"}.lang-lbl[lang=it]:after{content:"Italiano"}.lang-lbl[lang=iw]:after{content:"\0005E2\0005D1\0005E8\0005D9\0005EA"}.lang-lbl[lang=ja]:after{content:"\0065E5\00672C\008A9E"}.lang-lbl[lang=ko]:after{content:"\00D55C\00AD6D\00C5B4"}.lang-lbl[lang=lt]:after{content:"Lietuvi\000173"}.lang-lbl[lang=lv]:after{content:"Latvie\000161u"}.lang-lbl[lang=mk]:after{content:"\00041C\000430\00043A\000435\000434\00043E\00043D\000441\00043A\000438"}.lang-lbl[lang=ms]:after{content:"Bahasa\000020melayu"}.lang-lbl[lang=mt]:after{content:"Malti"}.lang-lbl[lang=nl]:after{content:"Nederlands"}.lang-lbl[lang=no]:after{content:"Norsk"}.lang-lbl[lang=pl]:after{content:"Polski"}.lang-lbl[lang=pt]:after{content:"Portugu\0000EAs"}.lang-lbl[lang=ro]:after{content:"Rom\0000E2n\000103"}.lang-lbl[lang=ru]:after{content:"\000420\000443\000441\000441\00043A\000438\000439"}.lang-lbl[lang=sk]:after{content:"Sloven\00010Dina"}.lang-lbl[lang=sl]:after{content:"Sloven\000161\00010Dina"}.lang-lbl[lang=sq]:after{content:"Shqipe"}.lang-lbl[lang=sr]:after{content:"\000421\000440\00043F\000441\00043A\000438"}.lang-lbl[lang=sv]:after{content:"Svenska"}.lang-lbl[lang=th]:after{content:"\000E44\000E17\000E22"}.lang-lbl[lang=tr]:after{content:"T\0000FCrk\0000E7e"}.lang-lbl[lang=uk]:after{content:"\000423\00043A\000440\000430\000457\00043D\000441\00044C\00043A\000430"}.lang-lbl[lang=vi]:after{content:"Ti\001EBFng\000020vi\001EC7t"}.lang-lbl[lang=zh]:after{content:"\004E2D\006587"}.lang-lbl-en[lang=ar]:after{content:"Arabic"}.lang-lbl-en[lang=be]:after{content:"Belarusian"}.lang-lbl-en[lang=bg]:after{content:"Bulgarian"}.lang-lbl-en[lang=cs]:after{content:"Czech"}.lang-lbl-en[lang=da]:after{content:"Danish"}.lang-lbl-en[lang=de]:after{content:"German"}.lang-lbl-en[lang=el]:after{content:"Greek"}.lang-lbl-en[lang=en]:after{content:"English"}.lang-lbl-en[lang=es]:after{content:"Spanish"}.lang-lbl-en[lang=et]:after{content:"Estonian"}.lang-lbl-en[lang=fi]:after{content:"Finnish"}.lang-lbl-en[lang=fr]:after{content:"French"}.lang-lbl-en[lang=ga]:after{content:"Irish"}.lang-lbl-en[lang=hi]:after{content:"Hindi"}.lang-lbl-en[lang=hr]:after{content:"Croatian"}.lang-lbl-en[lang=hu]:after{content:"Hungarian"}.lang-lbl-en[lang=in]:after{content:"Indonesian"}.lang-lbl-en[lang=is]:after{content:"Icelandic"}.lang-lbl-en[lang=it]:after{content:"Italian"}.lang-lbl-en[lang=iw]:after{content:"Hebrew"}.lang-lbl-en[lang=ja]:after{content:"Japanese"}.lang-lbl-en[lang=ko]:after{content:"Korean"}.lang-lbl-en[lang=lt]:after{content:"Lithuanian"}.lang-lbl-en[lang=lv]:after{content:"Latvian"}.lang-lbl-en[lang=mk]:after{content:"Macedonian"}.lang-lbl-en[lang=ms]:after{content:"Malay"}.lang-lbl-en[lang=mt]:after{content:"Maltese"}.lang-lbl-en[lang=nl]:after{content:"Dutch"}.lang-lbl-en[lang=no]:after{content:"Norwegian"}.lang-lbl-en[lang=pl]:after{content:"Polish"}.lang-lbl-en[lang=pt]:after{content:"Portuguese"}.lang-lbl-en[lang=ro]:after{content:"Romanian"}.lang-lbl-en[lang=ru]:after{content:"Russian"}.lang-lbl-en[lang=sk]:after{content:"Slovak"}.lang-lbl-en[lang=sl]:after{content:"Slovenian"}.lang-lbl-en[lang=sq]:after{content:"Albanian"}.lang-lbl-en[lang=sr]:after{content:"Serbian"}.lang-lbl-en[lang=sv]:after{content:"Swedish"}.lang-lbl-en[lang=th]:after{content:"Thai"}.lang-lbl-en[lang=tr]:after{content:"Turkish"}.lang-lbl-en[lang=uk]:after{content:"Ukrainian"}.lang-lbl-en[lang=vi]:after{content:"Vietnamese"}.lang-lbl-en[lang=zh]:after{content:"Chinese"}.lang-lbl-full[lang=ar]:after{content:"\000627\000644\000639\000631\000628\00064A\000629\0000A0/\0000A0Arabic"}.lang-lbl-full[lang=be]:after{content:"\000411\000435\00043B\000430\000440\000443\000441\00043A\000456\0000A0/\0000A0Belarusian"}.lang-lbl-full[lang=bg]:after{content:"\000411\00044A\00043B\000433\000430\000440\000441\00043A\000438\0000A0/\0000A0Bulgarian"}.lang-lbl-full[lang=cs]:after{content:"\00010Ce\000161tina\0000A0/\0000A0Czech"}.lang-lbl-full[lang=da]:after{content:"Dansk\0000A0/\0000A0Danish"}.lang-lbl-full[lang=de]:after{content:"Deutsch\0000A0/\0000A0German"}.lang-lbl-full[lang=el]:after{content:"\000395\0003BB\0003BB\0003B7\0003BD\0003B9\0003BA\0003AC\0000A0/\0000A0Greek"}.lang-lbl-full[lang=en]:after{content:"English\0000A0/\0000A0English"}.lang-lbl-full[lang=es]:after{content:"Espa\0000F1ol\0000A0/\0000A0Spanish"}.lang-lbl-full[lang=et]:after{content:"Eesti\0000A0/\0000A0Estonian"}.lang-lbl-full[lang=fi]:after{content:"Suomi\0000A0/\0000A0Finnish"}.lang-lbl-full[lang=fr]:after{content:"Fran\0000E7ais\0000A0/\0000A0French"}.lang-lbl-full[lang=ga]:after{content:"Gaeilge\0000A0/\0000A0Irish"}.lang-lbl-full[lang=hi]:after{content:"\000939\00093F\000902\000926\000940\0000A0/\0000A0Hindi"}.lang-lbl-full[lang=hr]:after{content:"Hrvatski\0000A0/\0000A0Croatian"}.lang-lbl-full[lang=hu]:after{content:"Magyar\0000A0/\0000A0Hungarian"}.lang-lbl-full[lang=in]:after{content:"Bahasa\000020indonesia\0000A0/\0000A0Indonesian"}.lang-lbl-full[lang=is]:after{content:"\0000CDslenska\0000A0/\0000A0Icelandic"}.lang-lbl-full[lang=it]:after{content:"Italiano\0000A0/\0000A0Italian"}.lang-lbl-full[lang=iw]:after{content:"\0005E2\0005D1\0005E8\0005D9\0005EA\0000A0/\0000A0Hebrew"}.lang-lbl-full[lang=ja]:after{content:"\0065E5\00672C\008A9E\0000A0/\0000A0Japanese"}.lang-lbl-full[lang=ko]:after{content:"\00D55C\00AD6D\00C5B4\0000A0/\0000A0Korean"}.lang-lbl-full[lang=lt]:after{content:"Lietuvi\000173\0000A0/\0000A0Lithuanian"}.lang-lbl-full[lang=lv]:after{content:"Latvie\000161u\0000A0/\0000A0Latvian"}.lang-lbl-full[lang=mk]:after{content:"\00041C\000430\00043A\000435\000434\00043E\00043D\000441\00043A\000438\0000A0/\0000A0Macedonian"}.lang-lbl-full[lang=ms]:after{content:"Bahasa\000020melayu\0000A0/\0000A0Malay"}.lang-lbl-full[lang=mt]:after{content:"Malti\0000A0/\0000A0Maltese"}.lang-lbl-full[lang=nl]:after{content:"Nederlands\0000A0/\0000A0Dutch"}.lang-lbl-full[lang=no]:after{content:"Norsk\0000A0/\0000A0Norwegian"}.lang-lbl-full[lang=pl]:after{content:"Polski\0000A0/\0000A0Polish"}.lang-lbl-full[lang=pt]:after{content:"Portugu\0000EAs\0000A0/\0000A0Portuguese"}.lang-lbl-full[lang=ro]:after{content:"Rom\0000E2n\000103\0000A0/\0000A0Romanian"}.lang-lbl-full[lang=ru]:after{content:"\000420\000443\000441\000441\00043A\000438\000439\0000A0/\0000A0Russian"}.lang-lbl-full[lang=sk]:after{content:"Sloven\00010Dina\0000A0/\0000A0Slovak"}.lang-lbl-full[lang=sl]:after{content:"Sloven\000161\00010Dina\0000A0/\0000A0Slovenian"}.lang-lbl-full[lang=sq]:after{content:"Shqipe\0000A0/\0000A0Albanian"}.lang-lbl-full[lang=sr]:after{content:"\000421\000440\00043F\000441\00043A\000438\0000A0/\0000A0Serbian"}.lang-lbl-full[lang=sv]:after{content:"Svenska\0000A0/\0000A0Swedish"}.lang-lbl-full[lang=th]:after{content:"\000E44\000E17\000E22\0000A0/\0000A0Thai"}.lang-lbl-full[lang=tr]:after{content:"T\0000FCrk\0000E7e\0000A0/\0000A0Turkish"}.lang-lbl-full[lang=uk]:after{content:"\000423\00043A\000440\000430\000457\00043D\000441\00044C\00043A\000430\0000A0/\0000A0Ukrainian"}.lang-lbl-full[lang=vi]:after{content:"Ti\001EBFng\000020vi\001EC7t\0000A0/\0000A0Vietnamese"}.lang-lbl-full[lang=zh]:after{content:"\004E2D\006587\0000A0/\0000A0Chinese"}.lang-lg:before,.lang-sm:before,.lang-xs:before{content:'\0000A0'}.lang-xs.lang-lbl,.lang-xs.lang-lbl-en,.lang-xs.lang-lbl-full{padding-left:16px}.lang-sm.lang-lbl,.lang-sm.lang-lbl-en,.lang-sm.lang-lbl-full{padding-left:24px}.lang-lg.lang-lbl,.lang-lg.lang-lbl-en,.lang-lg.lang-lbl-full{padding-left:32px}.lang-lg.lang-lbl-en:before,.lang-lg.lang-lbl-full:before,.lang-lg.lang-lbl:before,.lang-sm.lang-lbl-en:before,.lang-sm.lang-lbl-full:before,.lang-sm.lang-lbl:before,.lang-xs.lang-lbl-en:before,.lang-xs.lang-lbl-full:before,.lang-xs.lang-lbl:before{content:''}.lang-lg,.lang-lg:after{top:0;position:relative}.lang-sm{top:1px;position:relative}.lang-sm:after{top:-1px;position:relative}.lang-xs{top:4px;position:relative}.lang-xs:after{top:-4px;position:relative}.lead>.lang-lg{top:2px}.lead>.lang-lg:after{top:-2px}.lead>.lang-sm{top:6px}.lead>.lang-sm:after{top:-6px}.lead>.lang-xs{top:8px}.lead>.lang-xs:after{top:-8px}small>.lang-sm{top:-1px}small>.lang-sm:after{top:1px}small>.lang-xs{top:2px}small>.lang-xs:after{top:-2px}h1>.lang-lg{top:9px}h1>.lang-lg:after{top:-9px}h1>.lang-sm{top:12px}h1>.lang-sm:after{top:-12px}h1>.lang-xs{top:14px}h1>.lang-xs:after{top:-14px}h2>.lang-lg{top:5px}h2>.lang-lg:after{top:-5px}h2>.lang-sm{top:8px}h2>.lang-sm:after{top:-8px}h2>.lang-xs{top:10px}h2>.lang-xs:after{top:-10px}h3>.lang-lg{top:1px}h3>.lang-lg:after{top:-1px}h3>.lang-sm{top:5px}h3>.lang-sm:after{top:-5px}h3>.lang-xs{top:8px}h3>.lang-xs:after{top:-8px}h4>.lang-lg{top:-1px}h4>.lang-lg:after,h4>.lang-sm{top:1px}h4>.lang-sm:after{top:-1px}h4>.lang-xs{top:4px}h4>.lang-xs:after{top:-4px}h5>.lang-sm,h5>.lang-sm:after{top:0}h5>.lang-xs{top:2px}h5>.lang-xs:after{top:-2px}h6>.lang-sm,h6>.lang-sm:after{top:0}h6>.lang-xs{top:1px}h6>.lang-xs:after{top:-1px}.btn>.lang-sm{top:2px}.btn>.lang-sm:after{top:-2px}.btn>.lang-xs{top:4px}.btn>.lang-xs:after{top:-4px}.btn.btn-xs>.lang-sm,.btn.btn-xs>.lang-sm:after{top:0}.btn.btn-xs>.lang-xs{top:3px}.btn.btn-xs>.lang-xs:after{top:-3px}.btn.btn-sm>.lang-sm,.btn.btn-sm>.lang-sm:after{top:0}.btn.btn-sm>.lang-xs{top:3px}.btn.btn-sm>.lang-xs:after{top:-3px}.btn.btn-lg>.lang-lg{top:1px}.btn.btn-lg>.lang-lg:after{top:-1px}.btn.btn-lg>.lang-sm{top:3px}.btn.btn-lg>.lang-sm:after{top:-3px}.btn.btn-lg>.lang-xs{top:6px}.btn.btn-lg>.lang-xs:after{top:-6px} +.lang-lg,.lang-sm,.lang-xs{background-repeat:no-repeat;display:inline-block;background-image:url(languages.png)}.lang-sm,.lang-sm:after,.lang-xs,.lang-xs:after{position:relative}.lang-xs{background-position:0 -484px;min-width:14px;height:11px;min-height:11px;max-height:11px}.lang-sm{background-position:0 -1199px;min-width:22px;height:16px;min-height:16px;max-height:16px}.lang-lg{background-position:0 -2134px;min-width:30px;height:22px;min-height:22px;max-height:22px}.lang-xs[lang=ar]{background-position:0 0}.lang-xs[lang=be]{background-position:0 -11px}.lang-xs[lang=bg]{background-position:0 -22px}.lang-xs[lang=cs]{background-position:0 -33px}.lang-xs[lang=da]{background-position:0 -44px}.lang-xs[lang=de]{background-position:0 -55px}.lang-xs[lang=el]{background-position:0 -66px}.lang-xs[lang=en]{background-position:0 -77px}.lang-xs[lang=es]{background-position:0 -88px}.lang-xs[lang=et]{background-position:0 -99px}.lang-xs[lang=fi]{background-position:0 -110px}.lang-xs[lang=fr]{background-position:0 -121px}.lang-xs[lang=ga]{background-position:0 -132px}.lang-xs[lang=hi]{background-position:0 -143px}.lang-xs[lang=hr]{background-position:0 -154px}.lang-xs[lang=hu]{background-position:0 -165px}.lang-xs[lang=in]{background-position:0 -176px}.lang-xs[lang=is]{background-position:0 -187px}.lang-xs[lang=it]{background-position:0 -198px}.lang-xs[lang=iw]{background-position:0 -209px}.lang-xs[lang=ja]{background-position:0 -220px}.lang-xs[lang=ko]{background-position:0 -231px}.lang-xs[lang=lt]{background-position:0 -242px}.lang-xs[lang=lv]{background-position:0 -253px}.lang-xs[lang=mk]{background-position:0 -264px}.lang-xs[lang=ms]{background-position:0 -275px}.lang-xs[lang=mt]{background-position:0 -286px}.lang-xs[lang=nl]{background-position:0 -297px}.lang-xs[lang=no]{background-position:0 -308px}.lang-xs[lang=pl]{background-position:0 -319px}.lang-xs[lang=pt]{background-position:0 -330px}.lang-xs[lang=ro]{background-position:0 -341px}.lang-xs[lang=ru]{background-position:0 -352px}.lang-xs[lang=sk]{background-position:0 -363px}.lang-xs[lang=sl]{background-position:0 -374px}.lang-xs[lang=sq]{background-position:0 -385px}.lang-xs[lang=sr]{background-position:0 -396px}.lang-xs[lang=sv]{background-position:0 -407px}.lang-xs[lang=th]{background-position:0 -418px}.lang-xs[lang=tr]{background-position:0 -429px}.lang-xs[lang=uk]{background-position:0 -440px}.lang-xs[lang=vi]{background-position:0 -451px}.lang-xs[lang=zh]{background-position:0 -462px}.lang-xs[lang=ca]{background-position:0 -473px}.lang-sm[lang=ar]{background-position:0 -495px}.lang-sm[lang=be]{background-position:0 -511px}.lang-sm[lang=bg]{background-position:0 -527px}.lang-sm[lang=cs]{background-position:0 -543px}.lang-sm[lang=da]{background-position:0 -559px}.lang-sm[lang=de]{background-position:0 -575px}.lang-sm[lang=el]{background-position:0 -591px}.lang-sm[lang=en]{background-position:0 -607px}.lang-sm[lang=es]{background-position:0 -623px}.lang-sm[lang=et]{background-position:0 -639px}.lang-sm[lang=fi]{background-position:0 -655px}.lang-sm[lang=fr]{background-position:0 -671px}.lang-sm[lang=ga]{background-position:0 -687px}.lang-sm[lang=hi]{background-position:0 -703px}.lang-sm[lang=hr]{background-position:0 -719px}.lang-sm[lang=hu]{background-position:0 -735px}.lang-sm[lang=in]{background-position:0 -751px}.lang-sm[lang=is]{background-position:0 -767px}.lang-sm[lang=it]{background-position:0 -783px}.lang-sm[lang=iw]{background-position:0 -799px}.lang-sm[lang=ja]{background-position:0 -815px}.lang-sm[lang=ko]{background-position:0 -831px}.lang-sm[lang=lt]{background-position:0 -847px}.lang-sm[lang=lv]{background-position:0 -863px}.lang-sm[lang=mk]{background-position:0 -879px}.lang-sm[lang=ms]{background-position:0 -895px}.lang-sm[lang=mt]{background-position:0 -911px}.lang-sm[lang=nl]{background-position:0 -927px}.lang-sm[lang=no]{background-position:0 -943px}.lang-sm[lang=pl]{background-position:0 -959px}.lang-sm[lang=pt]{background-position:0 -975px}.lang-sm[lang=ro]{background-position:0 -991px}.lang-sm[lang=ru]{background-position:0 -1007px}.lang-sm[lang=sk]{background-position:0 -1023px}.lang-sm[lang=sl]{background-position:0 -1039px}.lang-sm[lang=sq]{background-position:0 -1055px}.lang-sm[lang=sr]{background-position:0 -1071px}.lang-sm[lang=sv]{background-position:0 -1087px}.lang-sm[lang=th]{background-position:0 -1103px}.lang-sm[lang=tr]{background-position:0 -1119px}.lang-sm[lang=uk]{background-position:0 -1135px}.lang-sm[lang=vi]{background-position:0 -1151px}.lang-sm[lang=zh]{background-position:0 -1167px}.lang-sm[lang=ca]{background-position:0 -1183px}.lang-lg[lang=ar]{background-position:0 -1188px}.lang-lg[lang=be]{background-position:0 -1210px}.lang-lg[lang=bg]{background-position:0 -1232px}.lang-lg[lang=cs]{background-position:0 -1254px}.lang-lg[lang=da]{background-position:0 -1276px}.lang-lg[lang=de]{background-position:0 -1298px}.lang-lg[lang=el]{background-position:0 -1320px}.lang-lg[lang=en]{background-position:0 -1342px}.lang-lg[lang=es]{background-position:0 -1364px}.lang-lg[lang=et]{background-position:0 -1386px}.lang-lg[lang=fi]{background-position:0 -1408px}.lang-lg[lang=fr]{background-position:0 -1430px}.lang-lg[lang=ga]{background-position:0 -1452px}.lang-lg[lang=hi]{background-position:0 -1474px}.lang-lg[lang=hr]{background-position:0 -1496px}.lang-lg[lang=hu]{background-position:0 -1518px}.lang-lg[lang=in]{background-position:0 -1540px}.lang-lg[lang=is]{background-position:0 -1562px}.lang-lg[lang=it]{background-position:0 -1584px}.lang-lg[lang=iw]{background-position:0 -1606px}.lang-lg[lang=ja]{background-position:0 -1628px}.lang-lg[lang=ko]{background-position:0 -1650px}.lang-lg[lang=lt]{background-position:0 -1672px}.lang-lg[lang=lv]{background-position:0 -1694px}.lang-lg[lang=mk]{background-position:0 -1716px}.lang-lg[lang=ms]{background-position:0 -1738px}.lang-lg[lang=mt]{background-position:0 -1760px}.lang-lg[lang=nl]{background-position:0 -1782px}.lang-lg[lang=no]{background-position:0 -1804px}.lang-lg[lang=pl]{background-position:0 -1826px}.lang-lg[lang=pt]{background-position:0 -1848px}.lang-lg[lang=ro]{background-position:0 -1870px}.lang-lg[lang=ru]{background-position:0 -1892px}.lang-lg[lang=sk]{background-position:0 -1914px}.lang-lg[lang=sl]{background-position:0 -1936px}.lang-lg[lang=sq]{background-position:0 -1958px}.lang-lg[lang=sr]{background-position:0 -1980px}.lang-lg[lang=sv]{background-position:0 -2002px}.lang-lg[lang=th]{background-position:0 -2024px}.lang-lg[lang=tr]{background-position:0 -2046px}.lang-lg[lang=uk]{background-position:0 -2068px}.lang-lg[lang=vi]{background-position:0 -2090px}.lang-lg[lang=zh]{background-position:0 -2112px}.lang-lbl-en:after,.lang-lbl-full:after,.lang-lbl:after{content:"Unknown language"}.lang-lbl[lang=ar]:after{content:"\000627\000644\000639\000631\000628\00064A\000629"}.lang-lbl[lang=be]:after{content:"\000411\000435\00043B\000430\000440\000443\000441\00043A\000456"}.lang-lbl[lang=bg]:after{content:"\000411\00044A\00043B\000433\000430\000440\000441\00043A\000438"}.lang-lbl[lang=ca]:after{content:"Catal\0000E0"}.lang-lbl[lang=cs]:after{content:"\00010Ce\000161tina"}.lang-lbl[lang=da]:after{content:"Dansk"}.lang-lbl[lang=de]:after{content:"Deutsch"}.lang-lbl[lang=el]:after{content:"\000395\0003BB\0003BB\0003B7\0003BD\0003B9\0003BA\0003AC"}.lang-lbl[lang=en]:after{content:"English"}.lang-lbl[lang=es]:after{content:"Espa\0000F1ol"}.lang-lbl[lang=et]:after{content:"Eesti"}.lang-lbl[lang=fi]:after{content:"Suomi"}.lang-lbl[lang=fr]:after{content:"Fran\0000E7ais"}.lang-lbl[lang=ga]:after{content:"Gaeilge"}.lang-lbl[lang=hi]:after{content:"\000939\00093F\000902\000926\000940"}.lang-lbl[lang=hr]:after{content:"Hrvatski"}.lang-lbl[lang=hu]:after{content:"Magyar"}.lang-lbl[lang=in]:after{content:"Bahasa\000020indonesia"}.lang-lbl[lang=is]:after{content:"\0000CDslenska"}.lang-lbl[lang=it]:after{content:"Italiano"}.lang-lbl[lang=iw]:after{content:"\0005E2\0005D1\0005E8\0005D9\0005EA"}.lang-lbl[lang=ja]:after{content:"\0065E5\00672C\008A9E"}.lang-lbl[lang=ko]:after{content:"\00D55C\00AD6D\00C5B4"}.lang-lbl[lang=lt]:after{content:"Lietuvi\000173"}.lang-lbl[lang=lv]:after{content:"Latvie\000161u"}.lang-lbl[lang=mk]:after{content:"\00041C\000430\00043A\000435\000434\00043E\00043D\000441\00043A\000438"}.lang-lbl[lang=ms]:after{content:"Bahasa\000020melayu"}.lang-lbl[lang=mt]:after{content:"Malti"}.lang-lbl[lang=nl]:after{content:"Nederlands"}.lang-lbl[lang=no]:after{content:"Norsk"}.lang-lbl[lang=pl]:after{content:"Polski"}.lang-lbl[lang=pt]:after{content:"Portugu\0000EAs"}.lang-lbl[lang=ro]:after{content:"Rom\0000E2n\000103"}.lang-lbl[lang=ru]:after{content:"\000420\000443\000441\000441\00043A\000438\000439"}.lang-lbl[lang=sk]:after{content:"Sloven\00010Dina"}.lang-lbl[lang=sl]:after{content:"Sloven\000161\00010Dina"}.lang-lbl[lang=sq]:after{content:"Shqipe"}.lang-lbl[lang=sr]:after{content:"\000421\000440\00043F\000441\00043A\000438"}.lang-lbl[lang=sv]:after{content:"Svenska"}.lang-lbl[lang=th]:after{content:"\000E44\000E17\000E22"}.lang-lbl[lang=tr]:after{content:"T\0000FCrk\0000E7e"}.lang-lbl[lang=uk]:after{content:"\000423\00043A\000440\000430\000457\00043D\000441\00044C\00043A\000430"}.lang-lbl[lang=vi]:after{content:"Ti\001EBFng\000020vi\001EC7t"}.lang-lbl[lang=zh]:after{content:"\004E2D\006587"}.lang-lbl-en[lang=ar]:after{content:"Arabic"}.lang-lbl-en[lang=be]:after{content:"Belarusian"}.lang-lbl-en[lang=bg]:after{content:"Bulgarian"}.lang-lbl-en[lang=ca]:after{content:"Catalan"}.lang-lbl-en[lang=cs]:after{content:"Czech"}.lang-lbl-en[lang=da]:after{content:"Danish"}.lang-lbl-en[lang=de]:after{content:"German"}.lang-lbl-en[lang=el]:after{content:"Greek"}.lang-lbl-en[lang=en]:after{content:"English"}.lang-lbl-en[lang=es]:after{content:"Spanish"}.lang-lbl-en[lang=et]:after{content:"Estonian"}.lang-lbl-en[lang=fi]:after{content:"Finnish"}.lang-lbl-en[lang=fr]:after{content:"French"}.lang-lbl-en[lang=ga]:after{content:"Irish"}.lang-lbl-en[lang=hi]:after{content:"Hindi"}.lang-lbl-en[lang=hr]:after{content:"Croatian"}.lang-lbl-en[lang=hu]:after{content:"Hungarian"}.lang-lbl-en[lang=in]:after{content:"Indonesian"}.lang-lbl-en[lang=is]:after{content:"Icelandic"}.lang-lbl-en[lang=it]:after{content:"Italian"}.lang-lbl-en[lang=iw]:after{content:"Hebrew"}.lang-lbl-en[lang=ja]:after{content:"Japanese"}.lang-lbl-en[lang=ko]:after{content:"Korean"}.lang-lbl-en[lang=lt]:after{content:"Lithuanian"}.lang-lbl-en[lang=lv]:after{content:"Latvian"}.lang-lbl-en[lang=mk]:after{content:"Macedonian"}.lang-lbl-en[lang=ms]:after{content:"Malay"}.lang-lbl-en[lang=mt]:after{content:"Maltese"}.lang-lbl-en[lang=nl]:after{content:"Dutch"}.lang-lbl-en[lang=no]:after{content:"Norwegian"}.lang-lbl-en[lang=pl]:after{content:"Polish"}.lang-lbl-en[lang=pt]:after{content:"Portuguese"}.lang-lbl-en[lang=ro]:after{content:"Romanian"}.lang-lbl-en[lang=ru]:after{content:"Russian"}.lang-lbl-en[lang=sk]:after{content:"Slovak"}.lang-lbl-en[lang=sl]:after{content:"Slovenian"}.lang-lbl-en[lang=sq]:after{content:"Albanian"}.lang-lbl-en[lang=sr]:after{content:"Serbian"}.lang-lbl-en[lang=sv]:after{content:"Swedish"}.lang-lbl-en[lang=th]:after{content:"Thai"}.lang-lbl-en[lang=tr]:after{content:"Turkish"}.lang-lbl-en[lang=uk]:after{content:"Ukrainian"}.lang-lbl-en[lang=vi]:after{content:"Vietnamese"}.lang-lbl-en[lang=zh]:after{content:"Chinese"}.lang-lbl-full[lang=ar]:after{content:"\000627\000644\000639\000631\000628\00064A\000629\0000A0/\0000A0Arabic"}.lang-lbl-full[lang=be]:after{content:"\000411\000435\00043B\000430\000440\000443\000441\00043A\000456\0000A0/\0000A0Belarusian"}.lang-lbl-full[lang=bg]:after{content:"\000411\00044A\00043B\000433\000430\000440\000441\00043A\000438\0000A0/\0000A0Bulgarian"}.lang-lbl-full[lang=ca]:after{content:"Catal\0000E0\0000A0/\0000A0Catalan"}.lang-lbl-full[lang=cs]:after{content:"\00010Ce\000161tina\0000A0/\0000A0Czech"}.lang-lbl-full[lang=da]:after{content:"Dansk\0000A0/\0000A0Danish"}.lang-lbl-full[lang=de]:after{content:"Deutsch\0000A0/\0000A0German"}.lang-lbl-full[lang=el]:after{content:"\000395\0003BB\0003BB\0003B7\0003BD\0003B9\0003BA\0003AC\0000A0/\0000A0Greek"}.lang-lbl-full[lang=en]:after{content:"English\0000A0/\0000A0English"}.lang-lbl-full[lang=es]:after{content:"Espa\0000F1ol\0000A0/\0000A0Spanish"}.lang-lbl-full[lang=et]:after{content:"Eesti\0000A0/\0000A0Estonian"}.lang-lbl-full[lang=fi]:after{content:"Suomi\0000A0/\0000A0Finnish"}.lang-lbl-full[lang=fr]:after{content:"Fran\0000E7ais\0000A0/\0000A0French"}.lang-lbl-full[lang=ga]:after{content:"Gaeilge\0000A0/\0000A0Irish"}.lang-lbl-full[lang=hi]:after{content:"\000939\00093F\000902\000926\000940\0000A0/\0000A0Hindi"}.lang-lbl-full[lang=hr]:after{content:"Hrvatski\0000A0/\0000A0Croatian"}.lang-lbl-full[lang=hu]:after{content:"Magyar\0000A0/\0000A0Hungarian"}.lang-lbl-full[lang=in]:after{content:"Bahasa\000020indonesia\0000A0/\0000A0Indonesian"}.lang-lbl-full[lang=is]:after{content:"\0000CDslenska\0000A0/\0000A0Icelandic"}.lang-lbl-full[lang=it]:after{content:"Italiano\0000A0/\0000A0Italian"}.lang-lbl-full[lang=iw]:after{content:"\0005E2\0005D1\0005E8\0005D9\0005EA\0000A0/\0000A0Hebrew"}.lang-lbl-full[lang=ja]:after{content:"\0065E5\00672C\008A9E\0000A0/\0000A0Japanese"}.lang-lbl-full[lang=ko]:after{content:"\00D55C\00AD6D\00C5B4\0000A0/\0000A0Korean"}.lang-lbl-full[lang=lt]:after{content:"Lietuvi\000173\0000A0/\0000A0Lithuanian"}.lang-lbl-full[lang=lv]:after{content:"Latvie\000161u\0000A0/\0000A0Latvian"}.lang-lbl-full[lang=mk]:after{content:"\00041C\000430\00043A\000435\000434\00043E\00043D\000441\00043A\000438\0000A0/\0000A0Macedonian"}.lang-lbl-full[lang=ms]:after{content:"Bahasa\000020melayu\0000A0/\0000A0Malay"}.lang-lbl-full[lang=mt]:after{content:"Malti\0000A0/\0000A0Maltese"}.lang-lbl-full[lang=nl]:after{content:"Nederlands\0000A0/\0000A0Dutch"}.lang-lbl-full[lang=no]:after{content:"Norsk\0000A0/\0000A0Norwegian"}.lang-lbl-full[lang=pl]:after{content:"Polski\0000A0/\0000A0Polish"}.lang-lbl-full[lang=pt]:after{content:"Portugu\0000EAs\0000A0/\0000A0Portuguese"}.lang-lbl-full[lang=ro]:after{content:"Rom\0000E2n\000103\0000A0/\0000A0Romanian"}.lang-lbl-full[lang=ru]:after{content:"\000420\000443\000441\000441\00043A\000438\000439\0000A0/\0000A0Russian"}.lang-lbl-full[lang=sk]:after{content:"Sloven\00010Dina\0000A0/\0000A0Slovak"}.lang-lbl-full[lang=sl]:after{content:"Sloven\000161\00010Dina\0000A0/\0000A0Slovenian"}.lang-lbl-full[lang=sq]:after{content:"Shqipe\0000A0/\0000A0Albanian"}.lang-lbl-full[lang=sr]:after{content:"\000421\000440\00043F\000441\00043A\000438\0000A0/\0000A0Serbian"}.lang-lbl-full[lang=sv]:after{content:"Svenska\0000A0/\0000A0Swedish"}.lang-lbl-full[lang=th]:after{content:"\000E44\000E17\000E22\0000A0/\0000A0Thai"}.lang-lbl-full[lang=tr]:after{content:"T\0000FCrk\0000E7e\0000A0/\0000A0Turkish"}.lang-lbl-full[lang=uk]:after{content:"\000423\00043A\000440\000430\000457\00043D\000441\00044C\00043A\000430\0000A0/\0000A0Ukrainian"}.lang-lbl-full[lang=vi]:after{content:"Ti\001EBFng\000020vi\001EC7t\0000A0/\0000A0Vietnamese"}.lang-lbl-full[lang=zh]:after{content:"\004E2D\006587\0000A0/\0000A0Chinese"}.lang-lg:before,.lang-sm:before,.lang-xs:before{content:'\0000A0'}.lang-xs.lang-lbl,.lang-xs.lang-lbl-en,.lang-xs.lang-lbl-full{padding-left:16px}.lang-sm.lang-lbl,.lang-sm.lang-lbl-en,.lang-sm.lang-lbl-full{padding-left:24px}.lang-lg.lang-lbl,.lang-lg.lang-lbl-en,.lang-lg.lang-lbl-full{padding-left:32px}.lang-lg.lang-lbl-en:before,.lang-lg.lang-lbl-full:before,.lang-lg.lang-lbl:before,.lang-sm.lang-lbl-en:before,.lang-sm.lang-lbl-full:before,.lang-sm.lang-lbl:before,.lang-xs.lang-lbl-en:before,.lang-xs.lang-lbl-full:before,.lang-xs.lang-lbl:before{content:''}.lang-lg,.lang-lg:after{top:0;position:relative}.lang-sm{top:1px}.lang-sm:after{top:-1px}.lang-xs{top:4px}.lang-xs:after{top:-4px}.lead>.lang-lg{top:2px}.lead>.lang-lg:after{top:-2px}.lead>.lang-sm{top:6px}.lead>.lang-sm:after{top:-6px}.lead>.lang-xs{top:8px}.lead>.lang-xs:after{top:-8px}small>.lang-sm{top:-1px}small>.lang-sm:after{top:1px}small>.lang-xs{top:2px}small>.lang-xs:after{top:-2px}h1>.lang-lg{top:9px}h1>.lang-lg:after{top:-9px}h1>.lang-sm{top:12px}h1>.lang-sm:after{top:-12px}h1>.lang-xs{top:14px}h1>.lang-xs:after{top:-14px}h2>.lang-lg{top:5px}h2>.lang-lg:after{top:-5px}h2>.lang-sm{top:8px}h2>.lang-sm:after{top:-8px}h2>.lang-xs{top:10px}h2>.lang-xs:after{top:-10px}h3>.lang-lg{top:1px}h3>.lang-lg:after{top:-1px}h3>.lang-sm{top:5px}h3>.lang-sm:after{top:-5px}h3>.lang-xs{top:8px}h3>.lang-xs:after{top:-8px}h4>.lang-lg{top:-1px}h4>.lang-lg:after,h4>.lang-sm{top:1px}h4>.lang-sm:after{top:-1px}h4>.lang-xs{top:4px}h4>.lang-xs:after{top:-4px}h5>.lang-sm,h5>.lang-sm:after{top:0}h5>.lang-xs{top:2px}h5>.lang-xs:after{top:-2px}h6>.lang-sm,h6>.lang-sm:after{top:0}h6>.lang-xs{top:1px}h6>.lang-xs:after{top:-1px}.btn>.lang-sm{top:2px}.btn>.lang-sm:after{top:-2px}.btn>.lang-xs{top:4px}.btn>.lang-xs:after{top:-4px}.btn.btn-xs>.lang-sm,.btn.btn-xs>.lang-sm:after{top:0}.btn.btn-xs>.lang-xs{top:3px}.btn.btn-xs>.lang-xs:after{top:-3px}.btn.btn-sm>.lang-sm,.btn.btn-sm>.lang-sm:after{top:0}.btn.btn-sm>.lang-xs{top:3px}.btn.btn-sm>.lang-xs:after{top:-3px}.btn.btn-lg>.lang-lg{top:1px}.btn.btn-lg>.lang-lg:after{top:-1px}.btn.btn-lg>.lang-sm{top:3px}.btn.btn-lg>.lang-sm:after{top:-3px}.btn.btn-lg>.lang-xs{top:6px}.btn.btn-lg>.lang-xs:after{top:-6px} \ No newline at end of file diff --git a/data/web/inc/languages.png b/data/web/inc/languages.png index c88e039bee8b2e24ef686d866083f48acd32d961..f5f1a2f0d5c6d0ba80704530217705366948787d 100644 GIT binary patch literal 60615 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 wAWEe*+<{907*qoM6N<$g7r|DWB>pF From 3829135d936dc2ce57412ea1c6ad986f064e214f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Peters?= Date: Mon, 26 Feb 2018 09:19:14 +0100 Subject: [PATCH 099/107] [ClamAV] Add directory to make bootstrap not complain about missing directories --- data/Dockerfiles/clamd/bootstrap.sh | 1 + docker-compose.yml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/data/Dockerfiles/clamd/bootstrap.sh b/data/Dockerfiles/clamd/bootstrap.sh index ffe582c9..c4c9e04c 100755 --- a/data/Dockerfiles/clamd/bootstrap.sh +++ b/data/Dockerfiles/clamd/bootstrap.sh @@ -7,6 +7,7 @@ if [[ "${SKIP_CLAMD}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then fi # Create log pipes +mkdir /var/log/clamav touch /var/log/clamav/clamd.log /var/log/clamav/freshclam.log mkfifo -m 600 /tmp/logpipe_clamd mkfifo -m 600 /tmp/logpipe_freshclam diff --git a/docker-compose.yml b/docker-compose.yml index 5e167164..d5d2f962 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -59,7 +59,7 @@ services: - redis clamd-mailcow: - image: mailcow/clamd:1.8 + image: mailcow/clamd:1.9 build: ./data/Dockerfiles/clamd restart: always environment: From 1aa940abc955a2952b00d0492f39c5ec6ec21bcb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Peters?= Date: Mon, 26 Feb 2018 17:44:24 +0100 Subject: [PATCH 100/107] [Web] Add missing string --- data/web/lang/lang.en.php | 1 + 1 file changed, 1 insertion(+) diff --git a/data/web/lang/lang.en.php b/data/web/lang/lang.en.php index a9145f30..d1d08b24 100644 --- a/data/web/lang/lang.en.php +++ b/data/web/lang/lang.en.php @@ -435,6 +435,7 @@ $lang['diagnostics']['optional'] = 'This record is optional.'; $lang['diagnostics']['cname_from_a'] = 'Value derived from A/AAAA record. This is supported as long as the record points to the correct resource.'; $lang['admin']['relay_from'] = '"From:" address'; +$lang['admin']['relay_run'] = "Run test"; $lang['admin']['api_allow_from'] = "Allow API access from these IPs"; $lang['admin']['api_key'] = "API key"; $lang['admin']['activate_api'] = "Activate API"; From 410cbf55b62f753c8bfb5b28332df3d8d69f0d6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Peters?= Date: Mon, 26 Feb 2018 18:53:56 +0100 Subject: [PATCH 101/107] Update dovecot.conf --- data/conf/dovecot/dovecot.conf | 2 -- 1 file changed, 2 deletions(-) diff --git a/data/conf/dovecot/dovecot.conf b/data/conf/dovecot/dovecot.conf index b3e45171..9acbfbe7 100644 --- a/data/conf/dovecot/dovecot.conf +++ b/data/conf/dovecot/dovecot.conf @@ -281,10 +281,8 @@ plugin { #mail_crypt_global_public_key = Date: Mon, 26 Feb 2018 21:57:32 +0100 Subject: [PATCH 102/107] [Web] Fix update for mailbox --- data/web/inc/functions.mailbox.inc.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/web/inc/functions.mailbox.inc.php b/data/web/inc/functions.mailbox.inc.php index f0d10397..695bde3b 100644 --- a/data/web/inc/functions.mailbox.inc.php +++ b/data/web/inc/functions.mailbox.inc.php @@ -2115,7 +2115,7 @@ function mailbox($_action, $_type, $_data = null, $attr = null) { $password_hashed = hash_password($password); try { $stmt = $pdo->prepare("UPDATE `mailbox` SET - `password` = :password_hashed, + `password` = :password_hashed WHERE `username` = :username"); $stmt->execute(array( ':password_hashed' => $password_hashed, From fc37a5aba5c40b35059375ee46f5e47325df8053 Mon Sep 17 00:00:00 2001 From: Pascal Jufer Date: Tue, 27 Feb 2018 10:29:13 +0100 Subject: [PATCH 103/107] Reorder navigation items --- data/web/debug.php | 126 ++++++++++++++++++------------------ data/web/inc/header.inc.php | 4 +- 2 files changed, 65 insertions(+), 65 deletions(-) diff --git a/data/web/debug.php b/data/web/debug.php index f6ced28f..09616791 100644 --- a/data/web/debug.php +++ b/data/web/debug.php @@ -9,14 +9,7 @@ $_SESSION['return_to'] = $_SERVER['REQUEST_URI'];
    -
    -
    -
    -

    Rspamd UI

    -
    -
    -
    -
    - -
    -
    - -
    -
    -
    - -
    - -
    -
    -
    - -
    - -
    -
    -
    -
    - -
    -
    - -
    -
    - Rspamd UI -
    -
    -
    -
    -
    - -
    -
    -
    -

    Rspamd settings map

    -
    -
    - -
    -
    -
    - 'df', 'dir' => '/var/vmail'); $vmail_df = explode(',', json_decode(docker('dovecot-mailcow', 'post', 'exec', $exec_fields), true)); ?> -
    +

    Disk usage

    @@ -327,6 +273,60 @@ $_SESSION['return_to'] = $_SERVER['REQUEST_URI'];
    +
    +
    +
    +

    Rspamd UI

    +
    +
    +
    +
    +
    +
    +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    +
    + +
    +
    +
    +
    +
    + Rspamd UI +
    +
    +
    +
    +
    + +
    +
    +
    +

    Rspamd settings map

    +
    +
    + +
    +
    +
    +
    diff --git a/data/web/inc/header.inc.php b/data/web/inc/header.inc.php index 0c516d39..3b464a49 100644 --- a/data/web/inc/header.inc.php +++ b/data/web/inc/header.inc.php @@ -75,8 +75,8 @@ if (isset($_SESSION['mailcow_cc_role'])) { if ($_SESSION['mailcow_cc_role'] == 'admin') { ?> - > > + > $val): ?>
  • - From 0553dc59596ddae12bad0a7f4576cf6a653d01d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Peters?= Date: Tue, 27 Feb 2018 15:02:31 +0100 Subject: [PATCH 104/107] [Postfix] Fix query --- data/Dockerfiles/postfix/postfix.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/Dockerfiles/postfix/postfix.sh b/data/Dockerfiles/postfix/postfix.sh index a80e6900..489f676f 100755 --- a/data/Dockerfiles/postfix/postfix.sh +++ b/data/Dockerfiles/postfix/postfix.sh @@ -39,7 +39,7 @@ query = SELECT IF(EXISTS( SELECT CONCAT('%u', '@', target_domain) FROM alias_domain WHERE alias_domain='%d' ) - ) AND json_extract(attributes, '$.tls_enforce_in') LIKE '%1%' AND mailbox.active = '1' + ) AND json_extract(attributes, '$.tls_enforce_in') LIKE '%%1%%' AND mailbox.active = '1' ), 'reject_plaintext_session', NULL) AS 'tls_enforce_in'; EOF @@ -58,7 +58,7 @@ query = SELECT GROUP_CONCAT(transport SEPARATOR '') AS transport_maps WHERE alias_domain = '%d' ) ) - AND json_extract(attributes, '$.tls_enforce_out') LIKE '%1%' + AND json_extract(attributes, '$.tls_enforce_out') LIKE '%%1%%' AND mailbox.active = '1' ), 'smtp_enforced_tls:', 'smtp:') AS 'transport' UNION ALL From 2b786c13de99c10a6524fbfa7649a09e884c0dde Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Peters?= Date: Tue, 27 Feb 2018 15:02:49 +0100 Subject: [PATCH 105/107] [Web] Add default json attributes when adding mailbox --- data/web/inc/functions.mailbox.inc.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/web/inc/functions.mailbox.inc.php b/data/web/inc/functions.mailbox.inc.php index 695bde3b..ff6a56d5 100644 --- a/data/web/inc/functions.mailbox.inc.php +++ b/data/web/inc/functions.mailbox.inc.php @@ -891,8 +891,8 @@ function mailbox($_action, $_type, $_data = null, $attr = null) { return false; } try { - $stmt = $pdo->prepare("INSERT INTO `mailbox` (`username`, `password`, `name`, `maildir`, `quota`, `local_part`, `domain`, `active`) - VALUES (:username, :password_hashed, :name, :maildir, :quota_b, :local_part, :domain, :active)"); + $stmt = $pdo->prepare("INSERT INTO `mailbox` (`username`, `password`, `name`, `maildir`, `quota`, `local_part`, `domain`, `attributes`, `active`) + VALUES (:username, :password_hashed, :name, :maildir, :quota_b, :local_part, :domain, '{\"force_pw_update\": \"0\", \"tls_enforce_in\": \"0\", \"tls_enforce_out\": \"0\"}', :active)"); $stmt->execute(array( ':username' => $username, ':password_hashed' => $password_hashed, From 6a01411460f305aa672d05f7bb9bb0b37a37a99a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Peters?= Date: Tue, 27 Feb 2018 15:12:21 +0100 Subject: [PATCH 106/107] [Dovecot] Fix imapsync --- data/Dockerfiles/dovecot/imapsync_cron.pl | 9 ++++----- docker-compose.yml | 2 +- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/data/Dockerfiles/dovecot/imapsync_cron.pl b/data/Dockerfiles/dovecot/imapsync_cron.pl index 47f9bbf7..bb9cafba 100755 --- a/data/Dockerfiles/dovecot/imapsync_cron.pl +++ b/data/Dockerfiles/dovecot/imapsync_cron.pl @@ -76,16 +76,15 @@ while ($row = $sth->fetchrow_arrayref()) { "--subscribeall", "--nofoldersizes", "--skipsize", - "--buffersize 8192000", - "--skipheader 'X-*'", - "--split1 3000", - "--split2 3000", + "--buffersize", "8192000", + "--split1", "3000", + "--split2", "3000", "--fastio1", "--fastio2", ($exclude eq "" ? () : ("--exclude", $exclude)), ($subfolder2 eq "" ? () : ('--subfolder2', $subfolder2)), ($maxage eq "0" ? () : ('--maxage', $maxage)), - ($maxbytespersecond eq "0" ? () : ('--maxbytespersecond', $maxage)), + ($maxbytespersecond eq "0" ? () : ('--maxbytespersecond', $maxbytespersecond)), ($delete2duplicates ne "1" ? () : ('--delete2duplicates')), ($delete1 ne "1" ? () : ('--delete')), ($delete2 ne "1" ? () : ('--delete2')), diff --git a/docker-compose.yml b/docker-compose.yml index d5d2f962..bbd83f54 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -165,7 +165,7 @@ services: - sogo dovecot-mailcow: - image: mailcow/dovecot:1.22 + image: mailcow/dovecot:1.23 build: ./data/Dockerfiles/dovecot cap_add: - NET_BIND_SERVICE From 5106eea86f0e0ea0c7918b328564b485d0bd859f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Peters?= Date: Tue, 27 Feb 2018 15:45:08 +0100 Subject: [PATCH 107/107] [Web] Fix sorting by returning a number, fixes #1092 --- data/web/js/mailbox.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/web/js/mailbox.js b/data/web/js/mailbox.js index ea7ce5f6..7f0129be 100644 --- a/data/web/js/mailbox.js +++ b/data/web/js/mailbox.js @@ -165,7 +165,7 @@ jQuery(function($){ }, "sortValue": function(value){ res = value.split("/"); - return res[0]; + return Number(res[0]); }, }, {"name":"max_quota_for_mbox","title":lang.mailbox_quota,"breakpoints":"xs sm"}, @@ -229,7 +229,7 @@ jQuery(function($){ }, "sortValue": function(value){ res = value.split("/"); - return res[0]; + return Number(res[0]); }, }, {"name":"spam_aliases","filterable": false,"title":lang.spam_aliases,"breakpoints":"xs sm md"},