From ef89e74163e7a9d4130e073293853355e2bdeadc Mon Sep 17 00:00:00 2001 From: Michael Kuron Date: Sun, 25 Jun 2017 11:44:12 +0200 Subject: [PATCH 01/22] Travis-CI --- .travis.yml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..f4a17849 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,15 @@ +sudo: required + +services: + - docker + +script: +- echo 'Europe/Berlin' | MAILCOW_HOSTNAME=build.mailcow ./generate_config.sh +- docker-compose pull --ignore-pull-failures --parallel +- docker-compose build +- docker login --username=$DOCKER_HUB_USERNAME --password=$DOCKER_HUB_PASSWORD +- docker-compose push + +branches: + only: + - master From bb77ba41c0a96ca844f34c5402d5eb2e0c868f80 Mon Sep 17 00:00:00 2001 From: andryyy Date: Sun, 25 Jun 2017 12:13:07 +0200 Subject: [PATCH 02/22] Add travis to auto push images --- .travis.yml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index f4a17849..c7614088 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,15 +1,16 @@ sudo: required - services: - - docker - +- docker script: - echo 'Europe/Berlin' | MAILCOW_HOSTNAME=build.mailcow ./generate_config.sh - docker-compose pull --ignore-pull-failures --parallel - docker-compose build - docker login --username=$DOCKER_HUB_USERNAME --password=$DOCKER_HUB_PASSWORD - docker-compose push - branches: only: - master +env: + global: + - secure: MpxpTwD7f0CNEVLitSpVmocK7O9r+BwFE1deEHK4AlQo/oc9cOlhGe1EL3mx9zbglPmjlDg/8kMUGv6vSirIabfBo9Szjps76bHckFr9lr2Ykkg0e29oC8pgPpSXD1eY/1ZIN/FvIkxpUFLETo1okS/j9q/A0DCGFmti0n3EoMORsgRz9CpNAiEh0zpSd6+euPAGHuczuCrDuO84my9bIOCjA/+aPunHNeXiuM8yIM2SxCSyGtIKT0+jvquIvLF58VxivysXBlRfhDn8fhB09nXA2Ru/derYQACfcmNSn9Pd4bDpebPJW5B9H/XA8xjb58uKinUlncbAMB/QnxoT75j9YRWJZRSQ+34XNYP6ZgK9soZ2TC6djQyEKTUu45Kp/1s+poSn42m9jytJJTmmK0KxsZTRcC8JD5nrjIMZWPUNNTwC5L4+I7ZRWg2WooK3LNyq1Ng8Hn6W77wSgsvAJw2HD3Lx58AprGUhHuBeaIZRuSN9aKwZrl9vKQJLqPnOp/nF2EC6kot5HYYtcotGtETXPUDih21gWD5ZM2BqVqYfQQnJnNMgeYmMdj6QQuTFqhuNJf7hXRIRkTnD3j1gDOLKQZazW0+N2JE8XWDFwi6fKScDsxT85lJti9HmzHa7+k4RVHmUYuDgRoPuzUgjWHvPsiz3/Z8WQ9JYpH84S8w= + - secure: fWzZisT6nGDNL4lf6tXB07eFG2drgBakHxzdF/NFVvzuP861RFR6omuL+ED0PgXrEHDJBxaBLv52je8irmUXrAH1CNr7T8DWiZo/h5h609Uzr+38T1NnIu4krL0Wo6/CDwlLKnzqTq9yBIZLQSHVJmo8AOpo1JPIi2ajodqj9ZfmAxDQTQl+G6zvQjtqIkYHsHY7A44Rto0f14ykn7w2S82Jn6Ry89VNI5V1WEO3sMpM/XekNP/HokNcRIuntL/0+kuLvTJ5akGoTjBQxSnSW95opzPeGky74HRU2obExJYqKvF0VfVJRNAqejwjIiFIbbjqV0Sk5391kFuhuBErQQDM1bOHGdxZ41HsJH29qNWIl7C33Yl10qERoqecgsJ1N/bS2ZEmWqm/zQh5GClCXPvYmzEqMYsMGM3vjbKdjDlc1Wh2w/eFclsXN9LSXh1mc35rtj46frcT6e5Kof87AIfC9hTgDvk9kAsyjaHMkSHSZthbZXCIcsD8qriNm5UqfFBYD79mPIP1S2YMQ2jscCsjHOZgYVrcm0kzDF21J1w6H0Lo7d1jw37LYlegBdtLQ9gYgqY2D5m+nxWuVoD5FZmpR+5JGtK+ootyLFF8aiFoHXd4op1JCxRLjgkmnZKXzw3kTQSpE7oa7CgzchtQmK2nqcqla1b5Qk7ilVcjooo= From b6bc1eaf0a0a747308775c28155f5924047e71dc Mon Sep 17 00:00:00 2001 From: andryyy Date: Sun, 25 Jun 2017 18:09:42 +0200 Subject: [PATCH 03/22] Fix autodiscover, pass ports to phpfpm-mailcow for further features and autodiscover --- data/web/autodiscover.php | 47 ++++++++++++++++++--------------------- data/web/inc/vars.inc.php | 22 ++++++++++++++++++ docker-compose.yml | 8 +++++++ 3 files changed, 52 insertions(+), 25 deletions(-) diff --git a/data/web/autodiscover.php b/data/web/autodiscover.php index 819dc7ee..730b729f 100644 --- a/data/web/autodiscover.php +++ b/data/web/autodiscover.php @@ -1,7 +1,7 @@ 'yes', 'autodiscoverType' => 'activesync', 'imap' => array( @@ -19,7 +19,7 @@ $config = array( ) ); -if(file_exists('inc/vars.local.inc.php')) { +if (file_exists('inc/vars.local.inc.php')) { include_once 'inc/vars.local.inc.php'; } @@ -29,17 +29,13 @@ error_reporting(0); $data = trim(file_get_contents("php://input")); -// Desktop client needs IMAP, unless it's Outlook 2013 or higher on Windows -if (strpos($data, 'autodiscover/outlook/responseschema')) { // desktop client - $config['autodiscoverType'] = 'imap'; - if ($config['useEASforOutlook'] == 'yes' && - strpos($_SERVER['HTTP_USER_AGENT'], 'Outlook') !== FALSE && // Outlook - strpos($_SERVER['HTTP_USER_AGENT'], 'Windows NT') !== FALSE && // Windows - preg_match('/Outlook (1[5-9]\.|[2-9]|1[0-9][0-9])/', $_SERVER['HTTP_USER_AGENT']) && // Outlook 2013 (version 15) or higher - strpos($_SERVER['HTTP_USER_AGENT'], 'MS Connectivity Analyzer') === FALSE // https://testconnectivity.microsoft.com doesn't support EAS for Outlook - ) { - $config['autodiscoverType'] = 'activesync'; - } +if ($autodiscover_config['useEASforOutlook'] == 'yes' && + preg_match('/(Outlook|Office).+(1[5-9]\.)/', $_SERVER['HTTP_USER_AGENT']) && + strpos($_SERVER['HTTP_USER_AGENT'], 'Mac') === false) { + $autodiscover_config['autodiscoverType'] = 'activesync'; +} +else { + $autodiscover_config['autodiscoverType'] = 'imap'; } $dsn = "$database_type:host=$database_host;dbname=$database_name"; @@ -56,13 +52,14 @@ if (!isset($_SERVER['PHP_AUTH_USER']) OR $as !== "user") { header('WWW-Authenticate: Basic realm=""'); header('HTTP/1.0 401 Unauthorized'); exit; -} else { +} +else { if (isset($_SERVER['PHP_AUTH_USER']) && isset($_SERVER['PHP_AUTH_PW'])) { if ($as === "user") { header("Content-Type: application/xml"); echo ''; - if(!$data) { + if (!$data) { list($usec, $sec) = explode(' ', microtime()); echo ''; echo ''; @@ -74,7 +71,7 @@ if (!isset($_SERVER['PHP_AUTH_USER']) OR $as !== "user") { $discover = new SimpleXMLElement($data); $email = $discover->Request->EMailAddress; - if ($config['autodiscoverType'] == 'imap') { + if ($autodiscover_config['autodiscoverType'] == 'imap') { ?> @@ -85,22 +82,22 @@ if (!isset($_SERVER['PHP_AUTH_USER']) OR $as !== "user") { settings IMAP - - + + off off - + on SMTP - - + + off off - + on on off @@ -121,7 +118,7 @@ if (!isset($_SERVER['PHP_AUTH_USER']) OR $as !== "user") { prepare("SELECT `name` FROM `mailbox` WHERE `username`= :username"); @@ -148,8 +145,8 @@ if (!isset($_SERVER['PHP_AUTH_USER']) OR $as !== "user") { MobileSync - - + + diff --git a/data/web/inc/vars.inc.php b/data/web/inc/vars.inc.php index f2d09c5c..7ee06c3a 100644 --- a/data/web/inc/vars.inc.php +++ b/data/web/inc/vars.inc.php @@ -17,6 +17,28 @@ $database_name = getenv('DBNAME'); // Other variables $mailcow_hostname = getenv('MAILCOW_HOSTNAME'); +// Autodiscover settings +$autodiscover_config = array( + // Enable the autodiscover service for Outlook desktop clients + 'useEASforOutlook' => 'yes', + // General autodiscover service type: "activesync" or "imap" + 'autodiscoverType' => 'activesync', + 'imap' => array( + 'server' => $mailcow_hostname, + 'port' => getenv('IMAPS_PORT'), + 'ssl' => 'on', + ), + 'smtp' => array( + 'server' => $mailcow_hostname, + 'port' => getenv('SMTPS_PORT'), + 'ssl' => 'on' + ), + 'activesync' => array( + 'url' => 'https://'.$mailcow_hostname.'/Microsoft-Server-ActiveSync' + ) +); + + // Where to go after adding and editing objects // Can be "form" or "previous" // "form" will stay in the current form, "previous" will redirect to previous page diff --git a/docker-compose.yml b/docker-compose.yml index 2c9f6666..ad092f1c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -122,6 +122,14 @@ services: - DBUSER=${DBUSER} - DBPASS=${DBPASS} - MAILCOW_HOSTNAME=${MAILCOW_HOSTNAME} + - IMAP_PORT=${IMAP_PORT:-143} + - IMAPS_PORT=${IMAPS_PORT:-993} + - POP_PORT=${POP_PORT:-110} + - POPS_PORT=${POPS_PORT:-995} + - SIEVE_PORT=${SIEVE_PORT:-4190} + - SUBMISSION_PORT=${SUBMISSION_PORT:-587} + - SMTPS_PORT=${SMTPS_PORT:-465} + - SMTP_PORT=${SMTP_PORT:-25} restart: always logging: options: From 170e030dd65c92abf301510ef5b6cea582a5d7e9 Mon Sep 17 00:00:00 2001 From: andryyy Date: Sun, 25 Jun 2017 18:10:29 +0200 Subject: [PATCH 04/22] Configure autodiscover in vars.inc.php --- data/web/autodiscover.php | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/data/web/autodiscover.php b/data/web/autodiscover.php index 730b729f..fd4181f6 100644 --- a/data/web/autodiscover.php +++ b/data/web/autodiscover.php @@ -1,30 +1,10 @@ 'yes', - 'autodiscoverType' => 'activesync', - 'imap' => array( - 'server' => $mailcow_hostname, - 'port' => '993', - 'ssl' => 'on', - ), - 'smtp' => array( - 'server' => $mailcow_hostname, - 'port' => '465', - 'ssl' => 'on' - ), - 'activesync' => array( - 'url' => 'https://'.$mailcow_hostname.'/Microsoft-Server-ActiveSync' - ) -); - if (file_exists('inc/vars.local.inc.php')) { include_once 'inc/vars.local.inc.php'; } -/* ---------- DO NOT MODIFY ANYTHING BEYOND THIS LINE. IGNORE AT YOUR OWN RISK. ---------- */ - error_reporting(0); $data = trim(file_get_contents("php://input")); From 468b74c8608570f071dccde15455de5e63112474 Mon Sep 17 00:00:00 2001 From: andryyy Date: Sun, 25 Jun 2017 20:17:31 +0200 Subject: [PATCH 05/22] Changes to syntax --- data/web/autodiscover.php | 190 ++++++++++++++++++++------------------ 1 file changed, 100 insertions(+), 90 deletions(-) diff --git a/data/web/autodiscover.php b/data/web/autodiscover.php index fd4181f6..92328e7f 100644 --- a/data/web/autodiscover.php +++ b/data/web/autodiscover.php @@ -1,102 +1,112 @@ PDO::ERRMODE_EXCEPTION, - PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, - PDO::ATTR_EMULATE_PREPARES => false, + PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, + PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, + PDO::ATTR_EMULATE_PREPARES => false, ]; $pdo = new PDO($dsn, $database_user, $database_pass, $opt); $login_user = strtolower(trim($_SERVER['PHP_AUTH_USER'])); -$as = check_login($login_user, $_SERVER['PHP_AUTH_PW']); +$login_role = check_login($login_user, $_SERVER['PHP_AUTH_PW']); -if (!isset($_SERVER['PHP_AUTH_USER']) OR $as !== "user") { - header('WWW-Authenticate: Basic realm=""'); - header('HTTP/1.0 401 Unauthorized'); - exit; +if (!isset($_SERVER['PHP_AUTH_USER']) OR $login_role !== "user") { + header('WWW-Authenticate: Basic realm=""'); + header('HTTP/1.0 401 Unauthorized'); + exit(0); } else { - if (isset($_SERVER['PHP_AUTH_USER']) && isset($_SERVER['PHP_AUTH_PW'])) { - if ($as === "user") { + if (isset($_SERVER['PHP_AUTH_USER']) && isset($_SERVER['PHP_AUTH_PW'])) { + if ($login_role === "user") { header("Content-Type: application/xml"); - echo ''; - - if (!$data) { + echo '' . PHP_EOL; +?> + +'; - echo ''; - echo '600Invalid Request'; - echo ''; - echo ''; +?> + + + 600 + Invalid Request + + + + +Request->EMailAddress; if ($autodiscover_config['autodiscoverType'] == 'imap') { - ?> +?> - - - - - email - settings - - IMAP - - - off - - off - - on - - - SMTP - - - off - - off - - on - on - off - - - CalDAV - https:///SOGo/dav//Calendar - off - - - - CardDAV - https:///SOGo/dav//Contacts - off - - - + + + + + email + settings + + IMAP + + + off + + off + + on + + + SMTP + + + off + + off + + on + on + off + + + CalDAV + https:///SOGo/dav//Calendar + off + + + + CardDAV + https:///SOGo/dav//Contacts + off + + + - +?> - en:en - - - - - - - - MobileSync - - - - - + en:en + + + + + + + + MobileSync + + + + + - +?> From 4f93bfd04fc0aa5728c201a426c176c74cc578fe Mon Sep 17 00:00:00 2001 From: andryyy Date: Sun, 25 Jun 2017 21:33:26 +0200 Subject: [PATCH 06/22] Initial commit: Set fail2ban parameters in UI --- data/web/admin.php | 24 +++++++++++++ data/web/inc/functions.inc.php | 65 ++++++++++++++++++++++++++++++++++ data/web/json_api.php | 35 ++++++++++++++++++ 3 files changed, 124 insertions(+) diff --git a/data/web/admin.php b/data/web/admin.php index 22e7ee16..d6a879f5 100644 --- a/data/web/admin.php +++ b/data/web/admin.php @@ -269,6 +269,30 @@ $tfa_data = get_tfa(); + +
+
Fail2Ban parameters
+
+ +
+
+ + +
+
+ + +
+
+ + +
+ +
+
+
diff --git a/data/web/inc/functions.inc.php b/data/web/inc/functions.inc.php index 9498cf30..e54f8b0d 100644 --- a/data/web/inc/functions.inc.php +++ b/data/web/inc/functions.inc.php @@ -229,6 +229,7 @@ function check_login($user, $pass) { } if (!isset($_SESSION['ldelay'])) { $_SESSION['ldelay'] = "0"; + error_log("Mailcow UI: Invalid password for " . $user . " by " . $_SERVER['REMOTE_ADDR']); } elseif (!isset($_SESSION['mailcow_cc_username'])) { $_SESSION['ldelay'] = $_SESSION['ldelay']+0.5; @@ -1434,4 +1435,68 @@ function get_logs($container, $lines = 100) { } return false; } +function get_f2b_parameters() { + global $lang; + global $redis; + $data = array(); + if ($_SESSION['mailcow_cc_role'] != "admin") { + return false; + } + try { + $data['ban_time'] = $redis->Get('F2B_BAN_TIME'); + $data['max_attempts'] = $redis->Get('F2B_MAX_ATTEMPTS'); + $data['retry_window'] = $redis->Get('F2B_RETRY_WINDOW'); + } + catch (RedisException $e) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => 'Redis: '.$e + ); + return false; + } + return $data; +} +function edit_f2b_parameters($postarray) { + global $lang; + global $redis; + if ($_SESSION['mailcow_cc_role'] != "admin") { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['access_denied']) + ); + return false; + } + $is_now = get_f2b_parameters(); + if (!empty($is_now)) { + $ban_time = intval((isset($postarray['ban_time'])) ? $postarray['ban_time'] : $is_now['ban_time']); + $max_attempts = intval((isset($postarray['max_attempts'])) ? $postarray['max_attempts'] : $is_now['active_int']); + $retry_window = intval((isset($postarray['retry_window'])) ? $postarray['retry_window'] : $is_now['retry_window']); + } + else { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['access_denied']) + ); + return false; + } + $ban_time = ($ban_time < 60) ? 60 : $ban_time; + $max_attempts = ($max_attempts < 1) ? 1 : $max_attempts; + $retry_window = ($retry_window < 1) ? 1 : $retry_window; + try { + $redis->Set('F2B_BAN_TIME', $ban_time); + $redis->Set('F2B_MAX_ATTEMPTS', $max_attempts); + $redis->Set('F2B_RETRY_WINDOW', $retry_window); + } + catch (RedisException $e) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => 'Redis: '.$e + ); + return false; + } + $_SESSION['return'] = array( + 'type' => 'success', + 'msg' => 'Saved changes to Fail2ban configuration' + ); +} ?> diff --git a/data/web/json_api.php b/data/web/json_api.php index b3b22a7d..d891f88d 100644 --- a/data/web/json_api.php +++ b/data/web/json_api.php @@ -1921,6 +1921,41 @@ if (isset($_SESSION['mailcow_cc_role']) || isset($_SESSION['pending_mailcow_cc_u )); } break; + case "fail2ban": + // No items + if (isset($_POST['attr'])) { + $attr = (array)json_decode($_POST['attr'], true); + if (edit_f2b_parameters($attr) === 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' + )); + } + break; case "admin": // No items as there is only one admin if (isset($_POST['attr'])) { From 2d7224e869fe9d55af5970ad992495e495e29304 Mon Sep 17 00:00:00 2001 From: Phoenix Eve Aspacio Date: Mon, 26 Jun 2017 07:22:41 +0800 Subject: [PATCH 07/22] Update edit.css --- data/web/css/edit.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/web/css/edit.css b/data/web/css/edit.css index 605a1010..fc40e624 100644 --- a/data/web/css/edit.css +++ b/data/web/css/edit.css @@ -9,7 +9,7 @@ table.footable>tbody>tr.footable-empty>td { overflow: visible !important; } .table-responsive { - overflow: visible !important; + overflow-x: scroll !important; } .footer-add-item { display:block; From 877bc74dbed8699e37bc3ebf7c07190c3d189ed5 Mon Sep 17 00:00:00 2001 From: Phoenix Eve Aspacio Date: Mon, 26 Jun 2017 07:22:58 +0800 Subject: [PATCH 08/22] Update user.css --- data/web/css/user.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/web/css/user.css b/data/web/css/user.css index 2222b624..f544efd3 100644 --- a/data/web/css/user.css +++ b/data/web/css/user.css @@ -9,7 +9,7 @@ table.footable>tbody>tr.footable-empty>td { overflow: visible !important; } .table-responsive { - overflow: visible !important; + overflow-x: scroll !important; } .footer-add-item { display:block; From c4f25b7dde611de28a5c8b0b6792680f1fa1c656 Mon Sep 17 00:00:00 2001 From: Phoenix Eve Aspacio Date: Mon, 26 Jun 2017 07:23:17 +0800 Subject: [PATCH 09/22] Update mailbox.css --- data/web/css/mailbox.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/web/css/mailbox.css b/data/web/css/mailbox.css index 8f711669..a76e6316 100644 --- a/data/web/css/mailbox.css +++ b/data/web/css/mailbox.css @@ -9,7 +9,7 @@ table.footable>tbody>tr.footable-empty>td { overflow: visible !important; } .table-responsive { - overflow: visible !important; + overflow-x: scroll !important; } .footer-add-item { display:block; From 48557a4de97eca71da879160a68b181e375181bc Mon Sep 17 00:00:00 2001 From: Phoenix Eve Aspacio Date: Mon, 26 Jun 2017 07:23:48 +0800 Subject: [PATCH 10/22] Update admin.css --- data/web/css/admin.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/web/css/admin.css b/data/web/css/admin.css index 0253f33c..3acd0308 100644 --- a/data/web/css/admin.css +++ b/data/web/css/admin.css @@ -9,7 +9,7 @@ table.footable>tbody>tr.footable-empty>td { overflow: visible !important; } .table-responsive { - overflow: visible !important; + overflow-x: scroll !important; } body { overflow-y:scroll; From a08ad047089b7086a480bb9d787e4ead1cd1c08b Mon Sep 17 00:00:00 2001 From: Phoenix Eve Aspacio Date: Mon, 26 Jun 2017 07:33:33 +0800 Subject: [PATCH 11/22] Update admin.css --- data/web/css/admin.css | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/data/web/css/admin.css b/data/web/css/admin.css index 3acd0308..d2bb2489 100644 --- a/data/web/css/admin.css +++ b/data/web/css/admin.css @@ -9,7 +9,12 @@ table.footable>tbody>tr.footable-empty>td { overflow: visible !important; } .table-responsive { - overflow-x: scroll !important; + overflow: visible !important; +} +@media screen and (max-width: 767px) { + .table-responsive { + overflow-x: scroll !important; + } } body { overflow-y:scroll; From d4f4c0f0c9de6380cfd1210ed54cb4b76585f9b4 Mon Sep 17 00:00:00 2001 From: Phoenix Eve Aspacio Date: Mon, 26 Jun 2017 07:33:53 +0800 Subject: [PATCH 12/22] Update user.css --- data/web/css/user.css | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/data/web/css/user.css b/data/web/css/user.css index f544efd3..07d4e745 100644 --- a/data/web/css/user.css +++ b/data/web/css/user.css @@ -9,7 +9,12 @@ table.footable>tbody>tr.footable-empty>td { overflow: visible !important; } .table-responsive { - overflow-x: scroll !important; + overflow: visible !important; +} +@media screen and (max-width: 767px) { + .table-responsive { + overflow-x: scroll !important; + } } .footer-add-item { display:block; From 38c74e5e24e35f740147652dd957cac67b89f113 Mon Sep 17 00:00:00 2001 From: Phoenix Eve Aspacio Date: Mon, 26 Jun 2017 07:34:21 +0800 Subject: [PATCH 13/22] Update mailbox.css --- data/web/css/mailbox.css | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/data/web/css/mailbox.css b/data/web/css/mailbox.css index a76e6316..9f07debe 100644 --- a/data/web/css/mailbox.css +++ b/data/web/css/mailbox.css @@ -9,7 +9,12 @@ table.footable>tbody>tr.footable-empty>td { overflow: visible !important; } .table-responsive { - overflow-x: scroll !important; + overflow: visible !important; +} +@media screen and (max-width: 767px) { + .table-responsive { + overflow-x: scroll !important; + } } .footer-add-item { display:block; @@ -30,4 +35,3 @@ table.footable>tbody>tr.footable-empty>td { .inputMissingAttr { border-color: #FF4136; } - From 0488c9a250f2716b882dc63b71db1e922ffdc311 Mon Sep 17 00:00:00 2001 From: Phoenix Eve Aspacio Date: Mon, 26 Jun 2017 07:34:41 +0800 Subject: [PATCH 14/22] Update edit.css --- data/web/css/edit.css | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/data/web/css/edit.css b/data/web/css/edit.css index fc40e624..fe4d9fff 100644 --- a/data/web/css/edit.css +++ b/data/web/css/edit.css @@ -9,7 +9,12 @@ table.footable>tbody>tr.footable-empty>td { overflow: visible !important; } .table-responsive { - overflow-x: scroll !important; + overflow: visible !important; +} +@media screen and (max-width: 767px) { + .table-responsive { + overflow-x: scroll !important; + } } .footer-add-item { display:block; From 6cd44b41365386b4f080ab3ec7a42ce0b480cc47 Mon Sep 17 00:00:00 2001 From: andryyy Date: Mon, 26 Jun 2017 23:17:46 +0200 Subject: [PATCH 15/22] Remove old code --- data/conf/rspamd/lua/rspamd.local.lua | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/data/conf/rspamd/lua/rspamd.local.lua b/data/conf/rspamd/lua/rspamd.local.lua index db8a08d5..75f52892 100644 --- a/data/conf/rspamd/lua/rspamd.local.lua +++ b/data/conf/rspamd/lua/rspamd.local.lua @@ -7,9 +7,6 @@ rspamd_config.MAILCOW_AUTH = { end } -local redis_params -redis_params = rspamd_parse_redis_server('tag_settings') -if redis_params then rspamd_config:register_symbol({ name = 'TAG_MOO', type = 'postfilter', @@ -20,11 +17,6 @@ rspamd_config:register_symbol({ local tagged_rcpt = task:get_symbol("TAGGED_RCPT") local mailcow_domain = task:get_symbol("RCPT_MAILCOW_DOMAIN") - local user = task:get_recipients(0)[1]['user'] - local domain = task:get_recipients(0)[1]['domain'] - local rcpt = user .. '@' .. domain - - if tagged_rcpt and mailcow_domain then local tag = tagged_rcpt[1].options[1] rspamd_logger.infox("found tag: %s", tag) @@ -57,5 +49,5 @@ rspamd_config:register_symbol({ end, priority = 11 }) -end + From b9ffcf2bf8b5210c1c932ac1011c0173c9ce8379 Mon Sep 17 00:00:00 2001 From: andryyy Date: Mon, 26 Jun 2017 23:18:05 +0200 Subject: [PATCH 16/22] Add whitelist function to Fail2ban --- data/Dockerfiles/fail2ban/logwatch.py | 37 +++++++++++++++++++++++---- data/web/admin.php | 4 +++ data/web/inc/functions.inc.php | 26 +++++++++++++++++++ 3 files changed, 62 insertions(+), 5 deletions(-) diff --git a/data/Dockerfiles/fail2ban/logwatch.py b/data/Dockerfiles/fail2ban/logwatch.py index d2a8d52f..f8d0a5c2 100644 --- a/data/Dockerfiles/fail2ban/logwatch.py +++ b/data/Dockerfiles/fail2ban/logwatch.py @@ -12,7 +12,7 @@ import redis import time import json -r = redis.StrictRedis(host='172.22.1.249', port=6379, db=0) +r = redis.StrictRedis(host='172.22.1.249', decode_responses=True, port=6379, db=0) RULES = { 'mailcowdockerized_postfix-mailcow_1': 'warning: .*\[([0-9a-f\.:]+)\]: SASL .* authentication failed', 'mailcowdockerized_dovecot-mailcow_1': '-login: Disconnected \(auth failed, .*\): user=.*, method=.*, rip=([0-9a-f\.:]+),', @@ -32,6 +32,7 @@ def ban(address): BAN_TIME = int(r.get("F2B_BAN_TIME")) MAX_ATTEMPTS = int(r.get("F2B_MAX_ATTEMPTS")) RETRY_WINDOW = int(r.get("F2B_RETRY_WINDOW")) + WHITELIST = r.hgetall("F2B_WHITELIST") ip = ipaddress.ip_address(address.decode('ascii')) if type(ip) is ipaddress.IPv6Address and ip.ipv4_mapped: @@ -39,7 +40,19 @@ def ban(address): address = str(ip) if ip.is_private or ip.is_loopback: return - + + self_network = ipaddress.ip_network(address.decode('ascii')) + if WHITELIST: + for wl_key in WHITELIST: + wl_net = ipaddress.ip_network(wl_key.decode('ascii'), False) + if wl_net.overlaps(self_network): + log['time'] = int(round(time.time())) + log['priority'] = "info" + log['message'] = "Address %s is whitelisted by rule %s" % (self_network, wl_net) + r.lpush("F2B_LOG", json.dumps(log, ensure_ascii=False)) + print "Address %s is whitelisted by rule %s" % (self_network, wl_net) + return + net = ipaddress.ip_network((address + ('/24' if type(ip) is ipaddress.IPv4Address else '/64')).decode('ascii'), strict=False) net = str(net) @@ -48,10 +61,10 @@ def ban(address): active_window = RETRY_WINDOW else: active_window = time.time() - bans[net]['last_attempt'] - + bans[net]['attempts'] += 1 bans[net]['last_attempt'] = time.time() - + active_window = time.time() - bans[net]['last_attempt'] if bans[net]['attempts'] >= MAX_ATTEMPTS: @@ -66,6 +79,7 @@ def ban(address): else: subprocess.call(["ip6tables", "-I", "INPUT", "-s", net, "-j", "REJECT"]) subprocess.call(["ip6tables", "-I", "FORWARD", "-s", net, "-j", "REJECT"]) + r.hset("F2B_ACTIVE_BANS", "%s" % net, log['time'] + BAN_TIME) else: log['time'] = int(round(time.time())) log['priority'] = "warn" @@ -76,6 +90,13 @@ def ban(address): def unban(net): log['time'] = int(round(time.time())) log['priority'] = "info" + r.lpush("F2B_LOG", json.dumps(log, ensure_ascii=False)) + if not net in bans: + log['message'] = "%s is not banned, skipping unban and deleting from queue (if any)" % net + r.lpush("F2B_LOG", json.dumps(log, ensure_ascii=False)) + print "%s is not banned, skipping unban and deleting from queue (if any)" % net + r.hdel("F2B_QUEUE_UNBAN", "%s" % net) + return log['message'] = "Unbanning %s" % net r.lpush("F2B_LOG", json.dumps(log, ensure_ascii=False)) print "Unbanning %s" % net @@ -85,6 +106,8 @@ def unban(net): else: subprocess.call(["ip6tables", "-D", "INPUT", "-s", net, "-j", "REJECT"]) subprocess.call(["ip6tables", "-D", "FORWARD", "-s", net, "-j", "REJECT"]) + r.hdel("F2B_ACTIVE_BANS", "%s" % net) + r.hdel("F2B_QUEUE_UNBAN", "%s" % net) del bans[net] def quit(signum, frame): @@ -117,11 +140,15 @@ def autopurge(): while not quit_now: BAN_TIME = int(r.get("F2B_BAN_TIME")) MAX_ATTEMPTS = int(r.get("F2B_MAX_ATTEMPTS")) + QUEUE_UNBAN = r.hgetall("F2B_QUEUE_UNBAN") + if QUEUE_UNBAN: + for net in QUEUE_UNBAN: + unban(str(net)) for net in bans.copy(): if bans[net]['attempts'] >= MAX_ATTEMPTS: if time.time() - bans[net]['last_attempt'] > BAN_TIME: unban(net) - time.sleep(60) + time.sleep(30) if __name__ == '__main__': threads = [] diff --git a/data/web/admin.php b/data/web/admin.php index d6a879f5..787129f0 100644 --- a/data/web/admin.php +++ b/data/web/admin.php @@ -289,6 +289,10 @@ $tfa_data = get_tfa();
+
+ + +
diff --git a/data/web/inc/functions.inc.php b/data/web/inc/functions.inc.php index e54f8b0d..82a4ab1c 100644 --- a/data/web/inc/functions.inc.php +++ b/data/web/inc/functions.inc.php @@ -1446,6 +1446,16 @@ function get_f2b_parameters() { $data['ban_time'] = $redis->Get('F2B_BAN_TIME'); $data['max_attempts'] = $redis->Get('F2B_MAX_ATTEMPTS'); $data['retry_window'] = $redis->Get('F2B_RETRY_WINDOW'); + $wl = $redis->hGetAll('F2B_WHITELIST'); + if (is_array($wl)) { + foreach ($wl as $key => $value) { + $tmp_data[] = $key; + } + $data['whitelist'] = implode(PHP_EOL, $tmp_data); + } + else { + $data['whitelist'] = ""; + } } catch (RedisException $e) { $_SESSION['return'] = array( @@ -1479,6 +1489,7 @@ function edit_f2b_parameters($postarray) { ); return false; } + $wl = $postarray['whitelist']; $ban_time = ($ban_time < 60) ? 60 : $ban_time; $max_attempts = ($max_attempts < 1) ? 1 : $max_attempts; $retry_window = ($retry_window < 1) ? 1 : $retry_window; @@ -1486,6 +1497,21 @@ function edit_f2b_parameters($postarray) { $redis->Set('F2B_BAN_TIME', $ban_time); $redis->Set('F2B_MAX_ATTEMPTS', $max_attempts); $redis->Set('F2B_RETRY_WINDOW', $retry_window); + $redis->Del('F2B_WHITELIST'); + if(!empty($wl)) { + $wl_array = array_map('trim', preg_split( "/( |,|;|\n)/", $wl)); + if (is_array($wl_array)) { + foreach ($wl_array as $wl_item) { + $cidr = explode('/', $wl_item); + if (filter_var($cidr[0], FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) && (!isset($cidr[1]) || ($cidr[1] >= 8 && $cidr[1] <= 32))) { + $redis->hSet('F2B_WHITELIST', $wl_item, 1); + } + elseif (filter_var($cidr[0], FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) && (!isset($cidr[1]) || ($cidr[1] >= 16 && $cidr[1] <= 128))) { + $redis->hSet('F2B_WHITELIST', $wl_item, 1); + } + } + } + } } catch (RedisException $e) { $_SESSION['return'] = array( From 433e0c8f9a7b33a6e58e538ea3d1238d459cf932 Mon Sep 17 00:00:00 2001 From: andryyy Date: Tue, 27 Jun 2017 09:32:57 +0200 Subject: [PATCH 17/22] Allow /0 to whitelist all, push f2b version in yml --- data/web/inc/functions.inc.php | 4 ++-- docker-compose.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/data/web/inc/functions.inc.php b/data/web/inc/functions.inc.php index 82a4ab1c..68a09531 100644 --- a/data/web/inc/functions.inc.php +++ b/data/web/inc/functions.inc.php @@ -1503,10 +1503,10 @@ function edit_f2b_parameters($postarray) { if (is_array($wl_array)) { foreach ($wl_array as $wl_item) { $cidr = explode('/', $wl_item); - if (filter_var($cidr[0], FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) && (!isset($cidr[1]) || ($cidr[1] >= 8 && $cidr[1] <= 32))) { + if (filter_var($cidr[0], FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) && (!isset($cidr[1]) || ($cidr[1] >= 0 && $cidr[1] <= 32))) { $redis->hSet('F2B_WHITELIST', $wl_item, 1); } - elseif (filter_var($cidr[0], FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) && (!isset($cidr[1]) || ($cidr[1] >= 16 && $cidr[1] <= 128))) { + elseif (filter_var($cidr[0], FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) && (!isset($cidr[1]) || ($cidr[1] >= 0 && $cidr[1] <= 128))) { $redis->hSet('F2B_WHITELIST', $wl_item, 1); } } diff --git a/docker-compose.yml b/docker-compose.yml index ad092f1c..51e190b7 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -319,7 +319,7 @@ services: - acme fail2ban-mailcow: - image: mailcow/fail2ban:1.2 + image: mailcow/fail2ban:1.3 build: ./data/Dockerfiles/fail2ban depends_on: - dovecot-mailcow From e9ea0712f2dac83b9cb517e60e518050ba14d598 Mon Sep 17 00:00:00 2001 From: andryyy Date: Tue, 27 Jun 2017 10:26:48 +0200 Subject: [PATCH 18/22] Add SKIP_FAIL2BAN var --- data/Dockerfiles/fail2ban/logwatch.py | 6 ++++++ docker-compose.yml | 2 +- generate_config.sh | 2 ++ 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/data/Dockerfiles/fail2ban/logwatch.py b/data/Dockerfiles/fail2ban/logwatch.py index f8d0a5c2..b7430988 100644 --- a/data/Dockerfiles/fail2ban/logwatch.py +++ b/data/Dockerfiles/fail2ban/logwatch.py @@ -1,6 +1,7 @@ #!/usr/bin/env python2 import re +import os import time import atexit import signal @@ -12,6 +13,11 @@ import redis import time import json +yes_regex = re.compile(r'([yY][eE][sS]|[yY])+$') +if re.search(yes_regex, os.getenv('SKIP_FAIL2BAN', 0)): + print "Skipping Fail2ban container..." + raise SystemExit + r = redis.StrictRedis(host='172.22.1.249', decode_responses=True, port=6379, db=0) RULES = { 'mailcowdockerized_postfix-mailcow_1': 'warning: .*\[([0-9a-f\.:]+)\]: SASL .* authentication failed', diff --git a/docker-compose.yml b/docker-compose.yml index 51e190b7..e286ca94 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -331,6 +331,7 @@ services: privileged: true environment: - TZ=${TZ} + - SKIP_FAIL2BAN=${SKIP_FAIL2BAN:-no} network_mode: "host" dns: - 172.22.1.254 @@ -338,7 +339,6 @@ services: volumes: - /var/run/docker.sock:/var/run/docker.sock:ro - /lib/modules:/lib/modules:ro - ipv6nat: image: robbertkl/ipv6nat restart: always diff --git a/generate_config.sh b/generate_config.sh index 43b46cfe..4cd17074 100755 --- a/generate_config.sh +++ b/generate_config.sh @@ -81,6 +81,8 @@ ADDITIONAL_SAN= # To never run acme-mailcow for Let's Encrypt, set this to y SKIP_LETS_ENCRYPT=n +# To never run fail2ban-mailcow +SKIP_FAIL2BAN=n EOF From 00242b1ec82266b12fa46f11ad2ec16e6554fd4c Mon Sep 17 00:00:00 2001 From: andryyy Date: Tue, 27 Jun 2017 19:54:26 +0200 Subject: [PATCH 19/22] Allow to add untracked .conf files --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 9dcc83b0..9ea8b05b 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,6 @@ data/assets/ssl/* data/web/.well-known/acme-challenge data/conf/rspamd/local.d/* data/conf/rspamd/override.d/* +!data/conf/nginx/dynmaps.conf +!data/conf/nginx/site.conf +data/conf/nginx/*.conf From f7bce8b81a3a398c267ab5a89786a431468ed035 Mon Sep 17 00:00:00 2001 From: andryyy Date: Tue, 27 Jun 2017 20:15:53 +0200 Subject: [PATCH 20/22] Copy dhparams if not found --- data/Dockerfiles/acme/docker-entrypoint.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/data/Dockerfiles/acme/docker-entrypoint.sh b/data/Dockerfiles/acme/docker-entrypoint.sh index 9882e22f..3ca6952c 100755 --- a/data/Dockerfiles/acme/docker-entrypoint.sh +++ b/data/Dockerfiles/acme/docker-entrypoint.sh @@ -36,6 +36,8 @@ else fi fi +[[ ! -f ${ACME_BASE}/dhparams.pem ]] && cp ${SSL_EXAMPLE}/dhparams.pem ${ACME_BASE}/dhparams.pem + while true; do if [[ "${SKIP_LETS_ENCRYPT}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then echo "SKIP_LETS_ENCRYPT=y, skipping Let's Encrypt..." From a58c5a3240aba312da453e3991f54086d7fa3668 Mon Sep 17 00:00:00 2001 From: andryyy Date: Tue, 27 Jun 2017 20:16:43 +0200 Subject: [PATCH 21/22] Push version of acme-mailcow --- docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index e286ca94..8847c80f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -293,7 +293,7 @@ services: acme-mailcow: depends_on: - nginx-mailcow - image: mailcow/acme:1.4 + image: mailcow/acme:1.5 build: ./data/Dockerfiles/acme dns: - 172.22.1.254 From b0584c3622e6c401b2da366dd57faeb578b8bc47 Mon Sep 17 00:00:00 2001 From: andryyy Date: Wed, 28 Jun 2017 10:50:51 +0200 Subject: [PATCH 22/22] Use , as IFS for additional san --- data/Dockerfiles/acme/docker-entrypoint.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/Dockerfiles/acme/docker-entrypoint.sh b/data/Dockerfiles/acme/docker-entrypoint.sh index 3ca6952c..f23efd08 100755 --- a/data/Dockerfiles/acme/docker-entrypoint.sh +++ b/data/Dockerfiles/acme/docker-entrypoint.sh @@ -46,7 +46,7 @@ while true; do declare -a SQL_DOMAIN_ARR declare -a VALIDATED_CONFIG_DOMAINS declare -a ADDITIONAL_VALIDATED_SAN - IFS=' ' read -r -a ADDITIONAL_SAN_ARR <<< "${ADDITIONAL_SAN}" + IFS=',' read -r -a ADDITIONAL_SAN_ARR <<< "${ADDITIONAL_SAN}" IPV4=$(curl -4s https://mailcow.email/ip.php) while read line; do