diff --git a/.gitignore b/.gitignore
index 9dcc83b0f..9ea8b05b5 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
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 000000000..c76140888
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,16 @@
+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
+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=
diff --git a/data/Dockerfiles/acme/docker-entrypoint.sh b/data/Dockerfiles/acme/docker-entrypoint.sh
index 9882e22f8..f23efd088 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..."
@@ -44,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
diff --git a/data/Dockerfiles/fail2ban/logwatch.py b/data/Dockerfiles/fail2ban/logwatch.py
index d2a8d52f5..b74309881 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,7 +13,12 @@ import redis
import time
import json
-r = redis.StrictRedis(host='172.22.1.249', port=6379, db=0)
+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',
'mailcowdockerized_dovecot-mailcow_1': '-login: Disconnected \(auth failed, .*\): user=.*, method=.*, rip=([0-9a-f\.:]+),',
@@ -32,6 +38,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 +46,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 +67,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 +85,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 +96,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 +112,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 +146,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/conf/rspamd/lua/rspamd.local.lua b/data/conf/rspamd/lua/rspamd.local.lua
index db8a08d55..75f528921 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
+
diff --git a/data/web/admin.php b/data/web/admin.php
index 22e7ee160..787129f08 100644
--- a/data/web/admin.php
+++ b/data/web/admin.php
@@ -269,6 +269,34 @@ $tfa_data = get_tfa();
+
+
+
Fail2Ban parameters
+
+
diff --git a/data/web/autodiscover.php b/data/web/autodiscover.php
deleted file mode 100644
index 3baab0490..000000000
--- a/data/web/autodiscover.php
+++ /dev/null
@@ -1,164 +0,0 @@
- '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"));
-
-// 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'], 'Windows NT') !== FALSE && // Windows
- preg_match('/(Outlook|Office) (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';
- }
-}
-
-$dsn = "$database_type:host=$database_host;dbname=$database_name";
-$opt = [
- 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']);
-
-if (!isset($_SERVER['PHP_AUTH_USER']) OR $as !== "user") {
- header('WWW-Authenticate: Basic realm=""');
- header('HTTP/1.0 401 Unauthorized');
- exit;
-} else {
- if (isset($_SERVER['PHP_AUTH_USER']) && isset($_SERVER['PHP_AUTH_PW'])) {
- if ($as === "user") {
- header("Content-Type: application/xml");
- echo '
';
-
- if(!$data) {
- list($usec, $sec) = explode(' ', microtime());
- echo '';
- echo '';
- echo '600Invalid Request';
- echo '';
- echo '';
- exit(0);
- }
- $discover = new SimpleXMLElement($data);
- $email = $discover->Request->EMailAddress;
-
- if ($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
-
-
-
-
- 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 = utf8_encode($MailboxData['name']);
- }
- else {
- $displayname = $email;
- }
- ?>
-
- en:en
-
-
-
-
-
-
-
- MobileSync
-
-
-
-
-
-
-
-
-
diff --git a/data/web/css/admin.css b/data/web/css/admin.css
index 0253f33c9..d2bb24897 100644
--- a/data/web/css/admin.css
+++ b/data/web/css/admin.css
@@ -11,6 +11,11 @@ table.footable>tbody>tr.footable-empty>td {
.table-responsive {
overflow: visible !important;
}
+@media screen and (max-width: 767px) {
+ .table-responsive {
+ overflow-x: scroll !important;
+ }
+}
body {
overflow-y:scroll;
}
diff --git a/data/web/css/edit.css b/data/web/css/edit.css
index 605a10102..fe4d9fffa 100644
--- a/data/web/css/edit.css
+++ b/data/web/css/edit.css
@@ -11,6 +11,11 @@ table.footable>tbody>tr.footable-empty>td {
.table-responsive {
overflow: visible !important;
}
+@media screen and (max-width: 767px) {
+ .table-responsive {
+ overflow-x: scroll !important;
+ }
+}
.footer-add-item {
display:block;
text-align: center;
diff --git a/data/web/css/mailbox.css b/data/web/css/mailbox.css
index 8f7116695..9f07debe9 100644
--- a/data/web/css/mailbox.css
+++ b/data/web/css/mailbox.css
@@ -11,6 +11,11 @@ table.footable>tbody>tr.footable-empty>td {
.table-responsive {
overflow: visible !important;
}
+@media screen and (max-width: 767px) {
+ .table-responsive {
+ overflow-x: scroll !important;
+ }
+}
.footer-add-item {
display:block;
text-align: center;
@@ -30,4 +35,3 @@ table.footable>tbody>tr.footable-empty>td {
.inputMissingAttr {
border-color: #FF4136;
}
-
diff --git a/data/web/css/user.css b/data/web/css/user.css
index 2222b624a..07d4e7457 100644
--- a/data/web/css/user.css
+++ b/data/web/css/user.css
@@ -11,6 +11,11 @@ table.footable>tbody>tr.footable-empty>td {
.table-responsive {
overflow: visible !important;
}
+@media screen and (max-width: 767px) {
+ .table-responsive {
+ overflow-x: scroll !important;
+ }
+}
.footer-add-item {
display:block;
text-align: center;
diff --git a/data/web/inc/functions.inc.php b/data/web/inc/functions.inc.php
index 9498cf307..68a095316 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,94 @@ 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');
+ $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(
+ '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;
+ }
+ $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;
+ try {
+ $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] >= 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] >= 0 && $cidr[1] <= 128))) {
+ $redis->hSet('F2B_WHITELIST', $wl_item, 1);
+ }
+ }
+ }
+ }
+ }
+ 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/inc/vars.inc.php b/data/web/inc/vars.inc.php
index f2d09c5c7..7ee06c3a9 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/data/web/json_api.php b/data/web/json_api.php
index b3b22a7d4..d891f88d6 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'])) {
diff --git a/docker-compose.yml b/docker-compose.yml
index 2c9f6666e..8847c80f0 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:
@@ -285,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
@@ -311,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
@@ -323,6 +331,7 @@ services:
privileged: true
environment:
- TZ=${TZ}
+ - SKIP_FAIL2BAN=${SKIP_FAIL2BAN:-no}
network_mode: "host"
dns:
- 172.22.1.254
@@ -330,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 43b46cfec..4cd170743 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