Przeglądaj źródła

Merge branch 'master' of https://github.com/andryyy/mailcow-dockerized into recipient_map

Conflicts:
	data/web/inc/init_db.inc.php
Michael Kuron 7 lat temu
rodzic
commit
c30448c4d8
42 zmienionych plików z 712 dodań i 436 usunięć
  1. 30 12
      data/Dockerfiles/clamd/Dockerfile
  2. 26 20
      data/Dockerfiles/dovecot/imapsync_cron.pl
  3. 62 58
      data/Dockerfiles/fail2ban/logwatch.py
  4. 6 16
      data/Dockerfiles/phpfpm/Dockerfile
  5. 3 1
      data/Dockerfiles/postfix/Dockerfile
  6. 1 1
      data/Dockerfiles/postfix/whitelist_forwardinghosts.sh
  7. 1 1
      data/Dockerfiles/rspamd/settings.conf
  8. 7 4
      data/Dockerfiles/sogo/bootstrap-sogo.sh
  9. 1 0
      data/Dockerfiles/watchdog/watchdog.sh
  10. 11 88
      data/assets/nextcloud/nextcloud.conf
  11. 47 0
      data/conf/clamav/clamd.conf
  12. 17 0
      data/conf/clamav/freshclam.conf
  13. 1 1
      data/conf/nginx/dynmaps.conf
  14. 31 16
      data/conf/nginx/site.conf
  15. 7 0
      data/conf/phpfpm/php-conf.d/opcache-recommended.ini
  16. 12 0
      data/conf/phpfpm/php-fpm.d/system.conf
  17. 11 0
      data/conf/phpfpm/php-fpm.d/www.conf
  18. 1 1
      data/conf/postfix/main.cf
  19. 2 2
      data/conf/rspamd/dynmaps/settings.php
  20. 1 1
      data/conf/rspamd/local.d/greylist.conf
  21. 1 1
      data/conf/rspamd/override.d/worker-controller.inc
  22. 2 2
      data/conf/sogo/sogo.conf
  23. 5 3
      data/conf/unbound/unbound.conf
  24. 47 28
      data/web/admin.php
  25. 19 0
      data/web/css/mailcow.css
  26. 26 1
      data/web/edit.php
  27. 3 1
      data/web/inc/footer.inc.php
  28. 12 0
      data/web/inc/functions.fail2ban.inc.php
  29. 9 2
      data/web/inc/functions.inc.php
  30. 50 16
      data/web/inc/functions.mailbox.inc.php
  31. 7 5
      data/web/inc/header.inc.php
  32. 9 5
      data/web/inc/init_db.inc.php
  33. 2 2
      data/web/js/quarantaine.js
  34. 11 0
      data/web/lang/lang.de.php
  35. 11 0
      data/web/lang/lang.en.php
  36. 21 0
      data/web/modals/mailbox.php
  37. 21 0
      data/web/modals/user.php
  38. 7 9
      data/web/user.php
  39. 33 30
      docker-compose.yml
  40. 8 0
      generate_config.sh
  41. 6 3
      helper-scripts/nextcloud.sh
  42. 124 106
      update.sh

+ 30 - 12
data/Dockerfiles/clamd/Dockerfile

@@ -6,20 +6,38 @@ LABEL maintainer "André Peters <andre.peters@servercow.de>"
 COPY dl_files.sh bootstrap.sh ./
 COPY dl_files.sh bootstrap.sh ./
 
 
 # Installation
 # Installation
-RUN apk add --update \
-	&& apk add --no-cache clamav clamav-libunrar curl bash tini \
+ENV CLAMAV 0.99.3
+
+RUN apk add --no-cache --virtual build-dependencies alpine-sdk ncurses-dev zlib-dev bzip2-dev pcre-dev linux-headers fts-dev libxml2-dev libressl-dev \
+  && apk add --no-cache curl bash tini libxml2 libbz2 pcre fts libressl \
+  && wget -O - https://www.clamav.net/downloads/production/clamav-${CLAMAV}.tar.gz | tar xfvz - \
+  && cd clamav-${CLAMAV} \
+  && LIBS=-lfts ./configure \
+  --prefix=/usr \
+  --libdir=/usr/lib \
+  --sysconfdir=/etc/clamav \
+  --mandir=/usr/share/man \
+  --infodir=/usr/share/info \
+  --without-iconv \
+  --disable-llvm \
+  --with-user=clamav \
+  --with-group=clamav \
+  --with-dbdir=/var/lib/clamav \
+  --enable-clamdtop \
+  --enable-bigstack \
+  --with-pcre \
+  && make -j4 \
+  && make install \
+  && make clean \
+  && cd .. && rm -rf clamav-${CLAMAV} \
+  && apk del build-dependencies \
+  && addgroup -S clamav \
+  && adduser -S -D -h /var/lib/clamav -s /sbin/nologin -G clamav -g clamav clamav \
+  && mkdir -p /run/clamav \
+  && chown clamav:clamav /run/clamav \
 	&& chmod +x /dl_files.sh \
 	&& chmod +x /dl_files.sh \
 	&& set -ex; /bin/bash /dl_files.sh \
 	&& set -ex; /bin/bash /dl_files.sh \
-	&& mkdir /run/clamav \
-	&& chown clamav:clamav /run/clamav \
-	&& chmod 750 /run/clamav \
-	&& sed -i '/Foreground yes/s/^#//g' /etc/clamav/clamd.conf \
-	&& sed -i '/TCPSocket 3310/s/^#//g' /etc/clamav/clamd.conf \
-  && sed -i 's#LogFile /var/log/clamav/clamd.log#LogFile /tmp/logpipe_clamd#g' /etc/clamav/clamd.conf \
-	&& sed -i 's/#PhishingSignatures yes/PhishingSignatures no/g' /etc/clamav/clamd.conf \
-	&& sed -i 's/#PhishingScanURLs yes/PhishingScanURLs no/g' /etc/clamav/clamd.conf \
-  && sed -i 's#UpdateLogFile /var/log/clamav/freshclam.log#UpdateLogFile /tmp/logpipe_freshclam#g' /etc/clamav/freshclam.conf \
-	&& sed -i '/Foreground yes/s/^#//g' /etc/clamav/freshclam.conf
+	&& chmod 750 /run/clamav
 
 
 # Port provision
 # Port provision
 EXPOSE 3310
 EXPOSE 3310

+ 26 - 20
data/Dockerfiles/dovecot/imapsync_cron.pl

@@ -33,26 +33,29 @@ open my $file, '<', "/etc/sogo/sieve.creds";
 my $creds = <$file>; 
 my $creds = <$file>; 
 close $file;
 close $file;
 my ($master_user, $master_pass) = split /:/, $creds;
 my ($master_user, $master_pass) = split /:/, $creds;
-my $sth = $dbh->prepare("SELECT id, user1, user2, host1, authmech1, password1, exclude, port1, enc1, delete2duplicates, maxage, subfolder2, delete1, delete2 FROM imapsync WHERE active = 1 AND (UNIX_TIMESTAMP(NOW()) - UNIX_TIMESTAMP(last_run) > mins_interval * 60 OR last_run IS NULL) ORDER BY last_run");
+my $sth = $dbh->prepare("SELECT id, user1, user2, host1, authmech1, password1, exclude, port1, enc1, delete2duplicates, maxage, subfolder2, delete1, delete2, automap, skipcrossduplicates, maxbytespersecond FROM imapsync WHERE active = 1 AND (UNIX_TIMESTAMP(NOW()) - UNIX_TIMESTAMP(last_run) > mins_interval * 60 OR last_run IS NULL) ORDER BY last_run");
 $sth->execute();
 $sth->execute();
 my $row;
 my $row;
 
 
 while ($row = $sth->fetchrow_arrayref()) {
 while ($row = $sth->fetchrow_arrayref()) {
 
 
-  $id                 = @$row[0];
-  $user1              = @$row[1];
-  $user2              = @$row[2];
-  $host1              = @$row[3];
-  $authmech1          = @$row[4];
-  $password1          = @$row[5];
-  $exclude            = @$row[6];
-  $port1              = @$row[7];
-  $enc1               = @$row[8];
-  $delete2duplicates  = @$row[9];
-  $maxage             = @$row[10];
-  $subfolder2         = @$row[11];
-  $delete1            = @$row[12];
-  $delete2            = @$row[13];
+  $id                  = @$row[0];
+  $user1               = @$row[1];
+  $user2               = @$row[2];
+  $host1               = @$row[3];
+  $authmech1           = @$row[4];
+  $password1           = @$row[5];
+  $exclude             = @$row[6];
+  $port1               = @$row[7];
+  $enc1                = @$row[8];
+  $delete2duplicates   = @$row[9];
+  $maxage              = @$row[10];
+  $subfolder2          = @$row[11];
+  $delete1             = @$row[12];
+  $delete2             = @$row[13];
+  $automap             = @$row[14];
+  $skipcrossduplicates = @$row[15];
+  $maxbytespersecond   = @$row[16];
 
 
   $is_running = $dbh->prepare("UPDATE imapsync SET is_running = 1 WHERE id = ?");
   $is_running = $dbh->prepare("UPDATE imapsync SET is_running = 1 WHERE id = ?");
   $is_running->bind_param( 1, ${id} );
   $is_running->bind_param( 1, ${id} );
@@ -72,11 +75,14 @@ while ($row = $sth->fetchrow_arrayref()) {
 	"--tmpdir", "/tmp",
 	"--tmpdir", "/tmp",
 	"--subscribeall",
 	"--subscribeall",
 	($exclude eq ""	? () : ("--exclude", $exclude)),
 	($exclude eq ""	? () : ("--exclude", $exclude)),
-	($subfolder2 eq ""	? () : ('--subfolder2', $subfolder2)),
-	($maxage eq "0"	? () : ('--maxage', $maxage)),
-	($delete2duplicates	ne "1"	? () : ('--delete2duplicates')),
-	($delete1	ne "1"	? () : ('--delete')),
-    ($delete2   ne "1"  ? () : ('--delete2')),
+	($subfolder2 eq "" ? () : ('--subfolder2', $subfolder2)),
+	($maxage eq "0" ? () : ('--maxage', $maxage)),
+  ($maxbytespersecond eq "0" ? () : ('--maxbytespersecond', $maxage)),
+	($delete2duplicates	ne "1" ? () : ('--delete2duplicates')),
+	($delete1	ne "1" ? () : ('--delete')),
+  ($delete2 ne "1" ? () : ('--delete2')),
+  ($automap ne "1" ? () : ('--automap')),
+  ($skipcrossduplicates ne "1" ? () : ('--skipcrossduplicates')),
 	(!defined($enc1) ? () : ($enc1)),
 	(!defined($enc1) ? () : ($enc1)),
 	"--host1", $host1,
 	"--host1", $host1,
 	"--user1", $user1,
 	"--user1", $user1,

+ 62 - 58
data/Dockerfiles/fail2ban/logwatch.py

@@ -14,11 +14,11 @@ import json
 
 
 yes_regex = re.compile(r'([yY][eE][sS]|[yY])+$')
 yes_regex = re.compile(r'([yY][eE][sS]|[yY])+$')
 if re.search(yes_regex, os.getenv('SKIP_FAIL2BAN', 0)):
 if re.search(yes_regex, os.getenv('SKIP_FAIL2BAN', 0)):
-  print "SKIP_FAIL2BAN=y, Skipping Fail2ban container..."
+  print 'SKIP_FAIL2BAN=y, Skipping Fail2ban container...'
   time.sleep(31536000)
   time.sleep(31536000)
   raise SystemExit
   raise SystemExit
 
 
-r = redis.StrictRedis(host='172.22.1.249', decode_responses=True, port=6379, db=0)
+r = redis.StrictRedis(host=os.getenv('IPV4_NETWORK', '172.22.1') + '.249', decode_responses=True, port=6379, db=0)
 pubsub = r.pubsub()
 pubsub = r.pubsub()
 
 
 RULES = {}
 RULES = {}
@@ -29,19 +29,23 @@ RULES[4] = '-login: Aborted login \(tried to use disallowed .+\): user=.+, rip=(
 RULES[5] = 'SOGo.+ Login from \'([0-9a-f\.:]+)\' for user .+ might not have worked'
 RULES[5] = 'SOGo.+ Login from \'([0-9a-f\.:]+)\' for user .+ might not have worked'
 RULES[6] = 'mailcow UI: Invalid password for .+ by ([0-9a-f\.:]+)'
 RULES[6] = 'mailcow UI: Invalid password for .+ by ([0-9a-f\.:]+)'
 
 
-r.setnx("F2B_BAN_TIME", "1800")
-r.setnx("F2B_MAX_ATTEMPTS", "10")
-r.setnx("F2B_RETRY_WINDOW", "600")
+r.setnx('F2B_BAN_TIME', '1800')
+r.setnx('F2B_MAX_ATTEMPTS', '10')
+r.setnx('F2B_RETRY_WINDOW', '600')
+r.setnx('F2B_NETBAN_IPV6', '64')
+r.setnx('F2B_NETBAN_IPV4', '24')
 
 
 bans = {}
 bans = {}
 log = {}
 log = {}
 quit_now = False
 quit_now = False
 
 
 def ban(address):
 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")
+  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')
+  NETBAN_IPV6 = '/' + str(r.get('F2B_NETBAN_IPV6'))
+  NETBAN_IPV4 = '/' + str(r.get('F2B_NETBAN_IPV4'))
 
 
   ip = ipaddress.ip_address(address.decode('ascii'))
   ip = ipaddress.ip_address(address.decode('ascii'))
   if type(ip) is ipaddress.IPv6Address and ip.ipv4_mapped:
   if type(ip) is ipaddress.IPv6Address and ip.ipv4_mapped:
@@ -56,13 +60,13 @@ def ban(address):
       wl_net = ipaddress.ip_network(wl_key.decode('ascii'), False)
       wl_net = ipaddress.ip_network(wl_key.decode('ascii'), False)
       if wl_net.overlaps(self_network):
       if wl_net.overlaps(self_network):
         log['time'] = int(round(time.time()))
         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)
+        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
         return
 
 
-  net = ipaddress.ip_network((address + ('/24' if type(ip) is ipaddress.IPv4Address else '/64')).decode('ascii'), strict=False)
+  net = ipaddress.ip_network((address + (NETBAN_IPV4 if type(ip) is ipaddress.IPv4Address else NETBAN_IPV6)).decode('ascii'), strict=False)
   net = str(net)
   net = str(net)
 
 
   if not net in bans or time.time() - bans[net]['last_attempt'] > RETRY_WINDOW:
   if not net in bans or time.time() - bans[net]['last_attempt'] > RETRY_WINDOW:
@@ -78,45 +82,45 @@ def ban(address):
 
 
   if bans[net]['attempts'] >= MAX_ATTEMPTS:
   if bans[net]['attempts'] >= MAX_ATTEMPTS:
     log['time'] = int(round(time.time()))
     log['time'] = int(round(time.time()))
-    log['priority'] = "crit"
-    log['message'] = "Banning %s" % net
-    r.lpush("F2B_LOG", json.dumps(log, ensure_ascii=False))
-    print "Banning %s for %d minutes" % (net, BAN_TIME / 60)
+    log['priority'] = 'crit'
+    log['message'] = 'Banning %s' % net
+    r.lpush('F2B_LOG', json.dumps(log, ensure_ascii=False))
+    print 'Banning %s for %d minutes' % (net, BAN_TIME / 60)
     if type(ip) is ipaddress.IPv4Address:
     if type(ip) is ipaddress.IPv4Address:
-      subprocess.call(["iptables", "-I", "INPUT", "-s", net, "-j", "REJECT"])
-      subprocess.call(["iptables", "-I", "FORWARD", "-s", net, "-j", "REJECT"])
+      subprocess.call(['iptables', '-I', 'INPUT', '-s', net, '-j', 'REJECT'])
+      subprocess.call(['iptables', '-I', 'FORWARD', '-s', net, '-j', 'REJECT'])
     else:
     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)
+      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:
   else:
     log['time'] = int(round(time.time()))
     log['time'] = int(round(time.time()))
-    log['priority'] = "warn"
-    log['message'] = "%d more attempts in the next %d seconds until %s is banned" % (MAX_ATTEMPTS - bans[net]['attempts'], RETRY_WINDOW, net)
-    r.lpush("F2B_LOG", json.dumps(log, ensure_ascii=False))
-    print "%d more attempts in the next %d seconds until %s is banned" % (MAX_ATTEMPTS - bans[net]['attempts'], RETRY_WINDOW, net)
+    log['priority'] = 'warn'
+    log['message'] = '%d more attempts in the next %d seconds until %s is banned' % (MAX_ATTEMPTS - bans[net]['attempts'], RETRY_WINDOW, net)
+    r.lpush('F2B_LOG', json.dumps(log, ensure_ascii=False))
+    print '%d more attempts in the next %d seconds until %s is banned' % (MAX_ATTEMPTS - bans[net]['attempts'], RETRY_WINDOW, net)
 
 
 def unban(net):
 def unban(net):
   log['time'] = int(round(time.time()))
   log['time'] = int(round(time.time()))
-  log['priority'] = "info"
-  r.lpush("F2B_LOG", json.dumps(log, ensure_ascii=False))
+  log['priority'] = 'info'
+  r.lpush('F2B_LOG', json.dumps(log, ensure_ascii=False))
   if not net in bans:
   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)
+    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
     return
-  log['message'] = "Unbanning %s" % net
-  r.lpush("F2B_LOG", json.dumps(log, ensure_ascii=False))
-  print "Unbanning %s" % net
+  log['message'] = 'Unbanning %s' % net
+  r.lpush('F2B_LOG', json.dumps(log, ensure_ascii=False))
+  print 'Unbanning %s' % net
   if type(ipaddress.ip_network(net.decode('ascii'))) is ipaddress.IPv4Network:
   if type(ipaddress.ip_network(net.decode('ascii'))) is ipaddress.IPv4Network:
-    subprocess.call(["iptables", "-D", "INPUT", "-s", net, "-j", "REJECT"])
-    subprocess.call(["iptables", "-D", "FORWARD", "-s", net, "-j", "REJECT"])
+    subprocess.call(['iptables', '-D', 'INPUT', '-s', net, '-j', 'REJECT'])
+    subprocess.call(['iptables', '-D', 'FORWARD', '-s', net, '-j', 'REJECT'])
   else:
   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)
+    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]
   del bans[net]
 
 
 def quit(signum, frame):
 def quit(signum, frame):
@@ -125,21 +129,21 @@ def quit(signum, frame):
 
 
 def clear():
 def clear():
   log['time'] = int(round(time.time()))
   log['time'] = int(round(time.time()))
-  log['priority'] = "info"
-  log['message'] = "Clearing all bans"
-  r.lpush("F2B_LOG", json.dumps(log, ensure_ascii=False))
-  print "Clearing all bans"
+  log['priority'] = 'info'
+  log['message'] = 'Clearing all bans'
+  r.lpush('F2B_LOG', json.dumps(log, ensure_ascii=False))
+  print 'Clearing all bans'
   for net in bans.copy():
   for net in bans.copy():
     unban(net)
     unban(net)
   pubsub.unsubscribe()
   pubsub.unsubscribe()
 
 
 def watch():
 def watch():
   log['time'] = int(round(time.time()))
   log['time'] = int(round(time.time()))
-  log['priority'] = "info"
-  log['message'] = "Watching Redis channel F2B_CHANNEL"
-  r.lpush("F2B_LOG", json.dumps(log, ensure_ascii=False))
-  pubsub.subscribe("F2B_CHANNEL")
-  print "Subscribing to Redis channel F2B_CHANNEL"
+  log['priority'] = 'info'
+  log['message'] = 'Watching Redis channel F2B_CHANNEL'
+  r.lpush('F2B_LOG', json.dumps(log, ensure_ascii=False))
+  pubsub.subscribe('F2B_CHANNEL')
+  print 'Subscribing to Redis channel F2B_CHANNEL'
   while True:
   while True:
     for item in pubsub.listen():
     for item in pubsub.listen():
       for rule_id, rule_regex in RULES.iteritems():
       for rule_id, rule_regex in RULES.iteritems():
@@ -150,18 +154,18 @@ def watch():
             ip = ipaddress.ip_address(addr.decode('ascii'))
             ip = ipaddress.ip_address(addr.decode('ascii'))
             if ip.is_private or ip.is_loopback:
             if ip.is_private or ip.is_loopback:
               continue
               continue
-            print "%s matched rule id %d" % (addr, rule_id)
+            print '%s matched rule id %d' % (addr, rule_id)
             log['time'] = int(round(time.time()))
             log['time'] = int(round(time.time()))
-            log['priority'] = "warn"
-            log['message'] = "%s matched rule id %d" % (addr, rule_id)
-            r.lpush("F2B_LOG", json.dumps(log, ensure_ascii=False))
+            log['priority'] = 'warn'
+            log['message'] = '%s matched rule id %d' % (addr, rule_id)
+            r.lpush('F2B_LOG', json.dumps(log, ensure_ascii=False))
             ban(addr)
             ban(addr)
 
 
 def autopurge():
 def autopurge():
   while not quit_now:
   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")
+    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:
     if QUEUE_UNBAN:
       for net in QUEUE_UNBAN:
       for net in QUEUE_UNBAN:
         unban(str(net))
         unban(str(net))

+ 6 - 16
data/Dockerfiles/phpfpm/Dockerfile

@@ -1,10 +1,11 @@
 FROM php:7.1-fpm-alpine
 FROM php:7.1-fpm-alpine
 LABEL maintainer "Andre Peters <andre.peters@servercow.de>"
 LABEL maintainer "Andre Peters <andre.peters@servercow.de>"
 
 
-ENV REDIS_PECL 3.1.4
-ENV MEMCACHED_PECL 3.0.3
-ENV APCU_PECL 5.1.8
+ENV REDIS_PECL 3.1.6
+ENV MEMCACHED_PECL 3.0.4
+ENV APCU_PECL 5.1.9
 ENV IMAGICK_PECL 3.4.3
 ENV IMAGICK_PECL 3.4.3
+ENV MAILPARSE_PECL 3.0.2
 
 
 RUN apk add -U --no-cache libxml2-dev \
 RUN apk add -U --no-cache libxml2-dev \
 	icu-dev \
 	icu-dev \
@@ -41,27 +42,16 @@ RUN apk add -U --no-cache libxml2-dev \
     Net_Sieve \
     Net_Sieve \
     NET_SMTP \
     NET_SMTP \
     Mail_mime \
     Mail_mime \
-	&& pecl install redis-${REDIS_PECL} memcached-${MEMCACHED_PECL} APCu-${APCU_PECL} imagick-${IMAGICK_PECL} mailparse \
+	&& pecl install redis-${REDIS_PECL} memcached-${MEMCACHED_PECL} APCu-${APCU_PECL} imagick-${IMAGICK_PECL} mailparse-${MAILPARSE_PECL} \
 	&& docker-php-ext-enable redis apcu memcached imagick mailparse \
 	&& docker-php-ext-enable redis apcu memcached imagick mailparse \
 	&& pecl clear-cache \
 	&& pecl clear-cache \
 	&& docker-php-ext-configure intl \
 	&& docker-php-ext-configure intl \
   && docker-php-ext-install -j 4 intl gettext ldap sockets soap pdo pdo_mysql xmlrpc gd zip pcntl opcache \
   && 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-configure imap --with-imap --with-imap-ssl \
 	&& docker-php-ext-install -j 4 imap \
 	&& 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 \
-	&& { \
-  echo 'opcache.enable=1'; \
-  echo 'opcache.enable_cli=1'; \
-  echo 'opcache.interned_strings_buffer=8'; \
-  echo 'opcache.max_accelerated_files=10000'; \
-  echo 'opcache.memory_consumption=128'; \
-  echo 'opcache.save_comments=1'; \
-  echo 'opcache.revalidate_freq=1'; \
-} > /usr/local/etc/php/conf.d/opcache-recommended.ini
+	&& 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
 
 
 COPY ./docker-entrypoint.sh /
 COPY ./docker-entrypoint.sh /
 
 
-EXPOSE 9000
-
 ENTRYPOINT ["/docker-entrypoint.sh"]
 ENTRYPOINT ["/docker-entrypoint.sh"]
 CMD ["php-fpm"]
 CMD ["php-fpm"]

+ 3 - 1
data/Dockerfiles/postfix/Dockerfile

@@ -26,7 +26,9 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
 	syslog-ng-core \
 	syslog-ng-core \
 	syslog-ng-mod-redis \
 	syslog-ng-mod-redis \
 	&& rm -rf /var/lib/apt/lists/* \
 	&& rm -rf /var/lib/apt/lists/* \
-	&& touch /etc/default/locale
+	&& touch /etc/default/locale \
+  && printf '#!/bin/bash\n/usr/sbin/postconf -c /opt/postfix/conf "$@"' > /usr/local/sbin/postconf \
+  && chmod +x /usr/local/sbin/postconf
 
 
 RUN addgroup --system --gid 600 zeyple
 RUN addgroup --system --gid 600 zeyple
 RUN adduser --system --home /var/lib/zeyple --no-create-home --uid 600 --gid 600 --disabled-login zeyple
 RUN adduser --system --home /var/lib/zeyple --no-create-home --uid 600 --gid 600 --disabled-login zeyple

+ 1 - 1
data/Dockerfiles/postfix/whitelist_forwardinghosts.sh

@@ -6,7 +6,7 @@ while read QUERY; do
 		echo "500 dunno"
 		echo "500 dunno"
 		continue
 		continue
 	fi
 	fi
-	result=$(curl -s http://172.22.1.251:8081/forwardinghosts.php?host=${QUERY[1]})
+	result=$(curl -s http://nginx:8081/forwardinghosts.php?host=${QUERY[1]})
 	logger -t whitelist_forwardinghosts -p mail.info "Look up ${QUERY[1]} on whitelist, result $result"
 	logger -t whitelist_forwardinghosts -p mail.info "Look up ${QUERY[1]} on whitelist, result $result"
 	echo ${result}
 	echo ${result}
 done
 done

+ 1 - 1
data/Dockerfiles/rspamd/settings.conf

@@ -1 +1 @@
-settings = "http://172.22.1.251:8081/settings.php";
+settings = "http://nginx:8081/settings.php";

+ 7 - 4
data/Dockerfiles/sogo/bootstrap-sogo.sh

@@ -18,11 +18,13 @@ done
 mysql --host mysql -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "DROP VIEW IF EXISTS sogo_view"
 mysql --host mysql -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "DROP VIEW IF EXISTS sogo_view"
 
 
 mysql --host mysql -u ${DBUSER} -p${DBPASS} ${DBNAME} << EOF
 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(ga.aliases, ''), 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 = mailbox.username
+CREATE VIEW sogo_view (c_uid, domain, c_name, c_password, c_cn, mail, aliases, sa_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(gsa.send_as_acl, ''), 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_sender_acl gsa ON gsa.username = mailbox.username
 LEFT OUTER JOIN grouped_domain_alias_address gda ON gda.username = mailbox.username
 LEFT OUTER JOIN grouped_domain_alias_address gda ON gda.username = mailbox.username
-WHERE mailbox.active = '1';
+WHERE mailbox.active = '1'
+GROUP BY mailbox.username;
 EOF
 EOF
 
 
 
 
@@ -67,6 +69,7 @@ while read line
                     <key>MailFieldNames</key>
                     <key>MailFieldNames</key>
                     <array>
                     <array>
                         <string>aliases</string>
                         <string>aliases</string>
+                        <string>sa_aliases</string>
                         <string>ad_aliases</string>
                         <string>ad_aliases</string>
                     </array>
                     </array>
                     <key>KindFieldName</key>
                     <key>KindFieldName</key>

+ 1 - 0
data/Dockerfiles/watchdog/watchdog.sh

@@ -191,6 +191,7 @@ phpfpm_checks() {
     host_ip=$(get_container_ip php-fpm-mailcow)
     host_ip=$(get_container_ip php-fpm-mailcow)
     err_c_cur=${err_count}
     err_c_cur=${err_count}
     cgi-fcgi -bind -connect ${host_ip}:9000 | grep "Content-type" 1>&2; err_count=$(( ${err_count} + ($? * 2)))
     cgi-fcgi -bind -connect ${host_ip}:9000 | grep "Content-type" 1>&2; err_count=$(( ${err_count} + ($? * 2)))
+    cgi-fcgi -bind -connect ${host_ip}:9001 | grep "Content-type" 1>&2; err_count=$(( ${err_count} + ($? * 2)))
     /usr/lib/nagios/plugins/check_ping -4 -H ${host_ip} -w 2000,10% -c 4000,100% -p2 1>&2; err_count=$(( ${err_count} + $? ))
     /usr/lib/nagios/plugins/check_ping -4 -H ${host_ip} -w 2000,10% -c 4000,100% -p2 1>&2; err_count=$(( ${err_count} + $? ))
     [ ${err_c_cur} -eq ${err_count} ] && [ ! $((${err_count} - 1)) -lt 0 ] && err_count=$((${err_count} - 1)) diff_c=1
     [ ${err_c_cur} -eq ${err_count} ] && [ ! $((${err_count} - 1)) -lt 0 ] && err_count=$((${err_count} - 1)) diff_c=1
     [ ${err_c_cur} -ne ${err_count} ] && diff_c=$(( ${err_c_cur} - ${err_count} ))
     [ ${err_c_cur} -ne ${err_count} ] && diff_c=$(( ${err_c_cur} - ${err_count} ))

+ 11 - 88
data/assets/nextcloud/nextcloud.conf

@@ -25,7 +25,7 @@ server {
   add_header X-Download-Options noopen;
   add_header X-Download-Options noopen;
   add_header X-Permitted-Cross-Domain-Policies none;
   add_header X-Permitted-Cross-Domain-Policies none;
 
 
-  server_name NC_SERVER_SUB;
+  server_name NC_SUBD;
 
 
   root /web/nextcloud/;
   root /web/nextcloud/;
 
 
@@ -38,95 +38,14 @@ server {
   location = /.well-known/carddav {
   location = /.well-known/carddav {
     return 301 $client_req_scheme_nc://$host/remote.php/dav;
     return 301 $client_req_scheme_nc://$host/remote.php/dav;
   }
   }
+
   location = /.well-known/caldav {
   location = /.well-known/caldav {
     return 301 $client_req_scheme_nc://$host/remote.php/dav;
     return 301 $client_req_scheme_nc://$host/remote.php/dav;
   }
   }
 
 
-  gzip on;
-  gzip_vary on;
-  gzip_comp_level 4;
-  gzip_min_length 256;
-  gzip_proxied expired no-cache no-store private no_last_modified no_etag auth;
-  gzip_types application/atom+xml application/javascript application/json application/ld+json application/manifest+json application/rss+xml application/vnd.geo+json application/vnd.ms-fontobject application/x-font-ttf application/x-web-app-manifest+json application/xhtml+xml application/xml font/opentype image/bmp image/svg+xml image/x-icon text/cache-manifest text/css text/plain text/vcard text/vnd.rim.location.xloc text/vtt text/x-component text/x-cross-domain-policy;
-
-  set_real_ip_from 172.22.1.1;
-  real_ip_header X-Forwarded-For;
-  real_ip_recursive on;
-
-  location / {
-    rewrite ^ /index.php$uri;
-  }
-
-  location ~ ^/(?:build|tests|config|lib|3rdparty|templates|data)/ {
-    deny all;
-  }
-
-  location ~ ^/(?:\.|autotest|occ|issue|indie|db_|console) {
-    deny all;
-  }
-
-  location ~ ^/(?:index|remote|public|cron|core/ajax/update|status|ocs/v[12]|updater/.+|ocs-provider/.+)\.php(?:$|/) {
-    fastcgi_split_path_info ^(.+\.php)(/.*)$;
-    include fastcgi_params;
-    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
-    fastcgi_param PATH_INFO $fastcgi_path_info;
-    #Avoid sending the security headers twice
-    fastcgi_param modHeadersAvailable true;
-    fastcgi_param front_controller_active true;
-    fastcgi_pass phpfpm:9000;
-    fastcgi_intercept_errors on;
-    fastcgi_request_buffering off;
-    client_max_body_size 10G;
-  }
-
-  location ~ ^/(?:updater|ocs-provider)(?:$|/) {
-    try_files $uri/ =404;
-    index index.php;
-  }
-
-  location ~ \.(?:css|js|woff|svg|gif)$ {
-    try_files $uri /index.php$uri$is_args$args;
-    add_header Cache-Control "public, max-age=15778463";
-    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-Permitted-Cross-Domain-Policies none;
-    access_log off;
-  }
-
-  location ~ \.(?:png|html|ttf|ico|jpg|jpeg)$ {
-    try_files $uri /index.php$uri$is_args$args;
-    access_log off;
-  }
-}
-server {
-  include /etc/nginx/conf.d/listen_ssl.active;
-  include /etc/nginx/mime.types;
-  charset utf-8;
-  override_charset on;
-
-  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-Permitted-Cross-Domain-Policies none;
-
-  server_name NC_SERVER_SUB;
-
-  root /web/nextcloud/;
-
-  location = /robots.txt {
-    allow all;
-    log_not_found off;
-    access_log off;
-  }
-
-  location = /.well-known/carddav {
-    return 301 $client_req_scheme_nc://$host/remote.php/dav;
-  }
-  location = /.well-known/caldav {
-    return 301 $client_req_scheme_nc://$host/remote.php/dav;
+  location ^~ /.well-known/acme-challenge/ {
+    default_type "text/plain";
+    root /web;
   }
   }
 
 
   gzip on;
   gzip on;
@@ -135,8 +54,10 @@ server {
   gzip_min_length 256;
   gzip_min_length 256;
   gzip_proxied expired no-cache no-store private no_last_modified no_etag auth;
   gzip_proxied expired no-cache no-store private no_last_modified no_etag auth;
   gzip_types application/atom+xml application/javascript application/json application/ld+json application/manifest+json application/rss+xml application/vnd.geo+json application/vnd.ms-fontobject application/x-font-ttf application/x-web-app-manifest+json application/xhtml+xml application/xml font/opentype image/bmp image/svg+xml image/x-icon text/cache-manifest text/css text/plain text/vcard text/vnd.rim.location.xloc text/vtt text/x-component text/x-cross-domain-policy;
   gzip_types application/atom+xml application/javascript application/json application/ld+json application/manifest+json application/rss+xml application/vnd.geo+json application/vnd.ms-fontobject application/x-font-ttf application/x-web-app-manifest+json application/xhtml+xml application/xml font/opentype image/bmp image/svg+xml image/x-icon text/cache-manifest text/css text/plain text/vcard text/vnd.rim.location.xloc text/vtt text/x-component text/x-cross-domain-policy;
-
-  set_real_ip_from 172.22.1.1;
+  set_real_ip_from fd00::/8;
+  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;
   real_ip_header X-Forwarded-For;
   real_ip_header X-Forwarded-For;
   real_ip_recursive on;
   real_ip_recursive on;
 
 
@@ -157,11 +78,13 @@ server {
     include fastcgi_params;
     include fastcgi_params;
     fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
     fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
     fastcgi_param PATH_INFO $fastcgi_path_info;
     fastcgi_param PATH_INFO $fastcgi_path_info;
+    #Avoid sending the security headers twice
     fastcgi_param modHeadersAvailable true;
     fastcgi_param modHeadersAvailable true;
     fastcgi_param front_controller_active true;
     fastcgi_param front_controller_active true;
     fastcgi_pass phpfpm:9000;
     fastcgi_pass phpfpm:9000;
     fastcgi_intercept_errors on;
     fastcgi_intercept_errors on;
     fastcgi_request_buffering off;
     fastcgi_request_buffering off;
+    client_max_body_size 10G;
   }
   }
 
 
   location ~ ^/(?:updater|ocs-provider)(?:$|/) {
   location ~ ^/(?:updater|ocs-provider)(?:$|/) {

+ 47 - 0
data/conf/clamav/clamd.conf

@@ -0,0 +1,47 @@
+LogFile /tmp/logpipe_clamd
+LogTime yes
+LogClean yes
+ExtendedDetectionInfo yes
+PidFile /run/clamav/clamd.pid
+OfficialDatabaseOnly no
+LocalSocket /run/clamav/clamd.sock
+TCPSocket 3310
+StreamMaxLength 25M
+MaxThreads 10
+ReadTimeout 10
+CommandReadTimeout 3
+SendBufTimeout 200
+MaxQueue 80
+IdleTimeout 20
+SelfCheck 3600
+User clamav
+AllowSupplementaryGroups yes
+Foreground yes
+DetectPUA yes
+# See https://github.com/vrtadmin/clamav-faq/blob/master/faq/faq-pua.md
+#ExcludePUA NetTool
+#ExcludePUA PWTool
+#IncludePUA Spy
+#IncludePUA Scanner
+#IncludePUA RAT
+AlgorithmicDetection yes
+ScanOLE2 yes
+OLE2BlockMacros yes
+ScanPDF yes
+ScanSWF yes
+ScanXMLDOCS yes
+ScanHWP3 yes
+ScanMail yes
+PhishingSignatures no
+PhishingScanURLs no
+HeuristicScanPrecedence yes
+ScanHTML yes
+ScanArchive yes
+MaxScanSize 50M
+MaxFileSize 25M
+MaxRecursion 5
+MaxFiles 200
+ScanOnAccess no
+Bytecode yes
+BytecodeSecurity TrustSigned
+BytecodeTimeout 1000

+ 17 - 0
data/conf/clamav/freshclam.conf

@@ -0,0 +1,17 @@
+UpdateLogFile /tmp/logpipe_freshclam
+LogTime yes
+PidFile /run/clamav/freshclam.pid
+DatabaseOwner clamav
+AllowSupplementaryGroups yes
+DNSDatabaseInfo current.cvd.clamav.net
+DatabaseMirror database.clamav.net
+MaxAttempts 4
+ScriptedUpdates yes
+Checks 6
+NotifyClamd /etc/clamav/clamd.conf
+Foreground yes
+ConnectTimeout 20
+ReceiveTimeout 20
+TestDatabases yes
+Bytecode yes
+

+ 1 - 1
data/conf/nginx/dynmaps.conf

@@ -10,7 +10,7 @@ server {
   location ~ \.php$ {
   location ~ \.php$ {
     try_files $uri =404;
     try_files $uri =404;
     fastcgi_split_path_info ^(.+\.php)(/.+)$;
     fastcgi_split_path_info ^(.+\.php)(/.+)$;
-    fastcgi_pass phpfpm:9000;
+    fastcgi_pass phpfpm:9001;
     fastcgi_index index.php;
     fastcgi_index index.php;
     include fastcgi_params;
     include fastcgi_params;
     fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
     fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;

+ 31 - 16
data/conf/nginx/site.conf

@@ -7,6 +7,13 @@ map $http_x_forwarded_proto $client_req_scheme {
      https https;
      https https;
 }
 }
 
 
+server {
+  listen 80 default_server;
+  listen [::]:80 default_server;
+  include /etc/nginx/conf.d/server_name.active;
+  return 301 https://$host$request_uri;
+}
+
 server {
 server {
   include /etc/nginx/mime.types;
   include /etc/nginx/mime.types;
   charset utf-8;
   charset utf-8;
@@ -17,6 +24,7 @@ server {
   add_header X-XSS-Protection "1; mode=block";
   add_header X-XSS-Protection "1; mode=block";
   add_header X-Robots-Tag none;
   add_header X-Robots-Tag none;
   add_header X-Download-Options noopen;
   add_header X-Download-Options noopen;
+  add_header X-Frame-Options "SAMEORIGIN";
   add_header X-Permitted-Cross-Domain-Policies none;
   add_header X-Permitted-Cross-Domain-Policies none;
 
 
   index index.php index.html;
   index index.php index.html;
@@ -39,7 +47,10 @@ server {
   }
   }
 
 
   # If behind reverse proxy, forwards the correct IP
   # If behind reverse proxy, forwards the correct IP
-  set_real_ip_from 172.22.1.1;
+  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 fd00::/8;
   real_ip_header X-Forwarded-For;
   real_ip_header X-Forwarded-For;
   real_ip_recursive on;
   real_ip_recursive on;
 
 
@@ -65,7 +76,7 @@ server {
   }
   }
 
 
   location /rspamd/ {
   location /rspamd/ {
-    proxy_pass       http://172.22.1.253:11334/;
+    proxy_pass       http://rspamd:11334/;
     proxy_set_header Host      $http_host;
     proxy_set_header Host      $http_host;
     proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
     proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
     proxy_set_header X-Real-IP $remote_addr;
     proxy_set_header X-Real-IP $remote_addr;
@@ -97,7 +108,7 @@ server {
   }
   }
 
 
   location ^~ /Microsoft-Server-ActiveSync {
   location ^~ /Microsoft-Server-ActiveSync {
-    proxy_pass http://172.22.1.252:20000/SOGo/Microsoft-Server-ActiveSync;
+    proxy_pass http://sogo:20000/SOGo/Microsoft-Server-ActiveSync;
     proxy_connect_timeout 1000;
     proxy_connect_timeout 1000;
     proxy_next_upstream timeout error;
     proxy_next_upstream timeout error;
     proxy_send_timeout 1000;
     proxy_send_timeout 1000;
@@ -119,7 +130,7 @@ server {
   }
   }
 
 
   location ^~ /SOGo {
   location ^~ /SOGo {
-    proxy_pass http://172.22.1.252:20000;
+    proxy_pass http://sogo:20000;
     proxy_set_header X-Real-IP $remote_addr;
     proxy_set_header X-Real-IP $remote_addr;
     proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
     proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
     proxy_set_header Host $http_host;
     proxy_set_header Host $http_host;
@@ -134,7 +145,7 @@ server {
   }
   }
 
 
   location /SOGo.woa/WebServerResources/ {
   location /SOGo.woa/WebServerResources/ {
-    proxy_pass http://172.22.1.252:9192/WebServerResources/;
+    proxy_pass http://sogo:9192/WebServerResources/;
     proxy_set_header Host $http_host;
     proxy_set_header Host $http_host;
     proxy_cache sogo;
     proxy_cache sogo;
     proxy_cache_valid 200 1d;
     proxy_cache_valid 200 1d;
@@ -144,7 +155,7 @@ server {
   }
   }
 
 
   location /.woa/WebServerResources/ {
   location /.woa/WebServerResources/ {
-    proxy_pass http://172.22.1.252:9192/WebServerResources/;
+    proxy_pass http://sogo:9192/WebServerResources/;
     proxy_set_header Host $http_host;
     proxy_set_header Host $http_host;
     proxy_cache sogo;
     proxy_cache sogo;
     proxy_cache_valid 200 1d;
     proxy_cache_valid 200 1d;
@@ -154,7 +165,7 @@ server {
   }
   }
 
 
   location /SOGo/WebServerResources/ {
   location /SOGo/WebServerResources/ {
-    proxy_pass http://172.22.1.252:9192/WebServerResources/;
+    proxy_pass http://sogo:9192/WebServerResources/;
     proxy_set_header Host $http_host;
     proxy_set_header Host $http_host;
     proxy_cache sogo;
     proxy_cache sogo;
     proxy_cache_valid 200 1d;
     proxy_cache_valid 200 1d;
@@ -164,7 +175,7 @@ server {
   }
   }
 
 
   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://172.22.1.252:9192/$1.SOGo/Resources/$2;
+    proxy_pass http://sogo:9192/$1.SOGo/Resources/$2;
     proxy_set_header Host $http_host;
     proxy_set_header Host $http_host;
     proxy_cache sogo;
     proxy_cache sogo;
     proxy_cache_valid 200 1d;
     proxy_cache_valid 200 1d;
@@ -195,6 +206,7 @@ server {
   add_header X-XSS-Protection "1; mode=block";
   add_header X-XSS-Protection "1; mode=block";
   add_header X-Robots-Tag none;
   add_header X-Robots-Tag none;
   add_header X-Download-Options noopen;
   add_header X-Download-Options noopen;
+  add_header X-Frame-Options "SAMEORIGIN";
   add_header X-Permitted-Cross-Domain-Policies none;
   add_header X-Permitted-Cross-Domain-Policies none;
 
 
   index index.php index.html;
   index index.php index.html;
@@ -217,7 +229,10 @@ server {
   }
   }
 
 
   # If behind reverse proxy, forwards the correct IP
   # If behind reverse proxy, forwards the correct IP
-  set_real_ip_from 172.22.1.1;
+  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 fd00::/8;
   real_ip_header X-Forwarded-For;
   real_ip_header X-Forwarded-For;
   real_ip_recursive on;
   real_ip_recursive on;
 
 
@@ -243,7 +258,7 @@ server {
   }
   }
 
 
   location /rspamd/ {
   location /rspamd/ {
-    proxy_pass       http://172.22.1.253:11334/;
+    proxy_pass       http://rspamd:11334/;
     proxy_set_header Host      $http_host;
     proxy_set_header Host      $http_host;
     proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
     proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
     proxy_set_header X-Real-IP $remote_addr;
     proxy_set_header X-Real-IP $remote_addr;
@@ -275,7 +290,7 @@ server {
   }
   }
 
 
   location ^~ /Microsoft-Server-ActiveSync {
   location ^~ /Microsoft-Server-ActiveSync {
-    proxy_pass http://172.22.1.252:20000/SOGo/Microsoft-Server-ActiveSync;
+    proxy_pass http://sogo:20000/SOGo/Microsoft-Server-ActiveSync;
     proxy_connect_timeout 1000;
     proxy_connect_timeout 1000;
     proxy_next_upstream timeout error;
     proxy_next_upstream timeout error;
     proxy_send_timeout 1000;
     proxy_send_timeout 1000;
@@ -297,7 +312,7 @@ server {
   }
   }
 
 
   location ^~ /SOGo {
   location ^~ /SOGo {
-    proxy_pass http://172.22.1.252:20000;
+    proxy_pass http://sogo:20000;
     proxy_set_header X-Real-IP $remote_addr;
     proxy_set_header X-Real-IP $remote_addr;
     proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
     proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
     proxy_set_header Host $http_host;
     proxy_set_header Host $http_host;
@@ -312,7 +327,7 @@ server {
   }
   }
 
 
   location /SOGo.woa/WebServerResources/ {
   location /SOGo.woa/WebServerResources/ {
-    proxy_pass http://172.22.1.252:9192/WebServerResources/;
+    proxy_pass http://sogo:9192/WebServerResources/;
     proxy_set_header Host $http_host;
     proxy_set_header Host $http_host;
     proxy_cache sogo;
     proxy_cache sogo;
     proxy_cache_valid 200 1d;
     proxy_cache_valid 200 1d;
@@ -322,7 +337,7 @@ server {
   }
   }
 
 
   location /.woa/WebServerResources/ {
   location /.woa/WebServerResources/ {
-    proxy_pass http://172.22.1.252:9192/WebServerResources/;
+    proxy_pass http://sogo:9192/WebServerResources/;
     proxy_set_header Host $http_host;
     proxy_set_header Host $http_host;
     proxy_cache sogo;
     proxy_cache sogo;
     proxy_cache_valid 200 1d;
     proxy_cache_valid 200 1d;
@@ -332,7 +347,7 @@ server {
   }
   }
 
 
   location /SOGo/WebServerResources/ {
   location /SOGo/WebServerResources/ {
-    proxy_pass http://172.22.1.252:9192/WebServerResources/;
+    proxy_pass http://sogo:9192/WebServerResources/;
     proxy_set_header Host $http_host;
     proxy_set_header Host $http_host;
     proxy_cache sogo;
     proxy_cache sogo;
     proxy_cache_valid 200 1d;
     proxy_cache_valid 200 1d;
@@ -342,7 +357,7 @@ server {
   }
   }
 
 
   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://172.22.1.252:9192/$1.SOGo/Resources/$2;
+    proxy_pass http://sogo:9192/$1.SOGo/Resources/$2;
     proxy_set_header Host $http_host;
     proxy_set_header Host $http_host;
     proxy_cache sogo;
     proxy_cache sogo;
     proxy_cache_valid 200 1d;
     proxy_cache_valid 200 1d;

+ 7 - 0
data/conf/phpfpm/php-conf.d/opcache-recommended.ini

@@ -0,0 +1,7 @@
+opcache.enable=1
+opcache.enable_cli=1
+opcache.interned_strings_buffer=8
+opcache.max_accelerated_files=10000
+opcache.memory_consumption=128
+opcache.save_comments=1
+opcache.revalidate_freq=1

+ 12 - 0
data/conf/phpfpm/php-fpm.d/system.conf

@@ -0,0 +1,12 @@
+[system]
+user = www-data
+group = www-data
+pm = dynamic
+pm.max_children = 10
+pm.start_servers = 2
+pm.min_spare_servers = 2
+pm.max_spare_servers = 4
+listen = [::]:9001
+access.log = /proc/self/fd/2
+clear_env = no
+catch_workers_output = yes

+ 11 - 0
data/conf/phpfpm/php-fpm.d/www.conf

@@ -0,0 +1,11 @@
+[www]
+user = www-data
+group = www-data
+pm = ondemand
+pm.max_children = 20
+pm.process_idle_timeout = 20s
+pm.max_requests = 800
+listen = [::]:9000
+access.log = /proc/self/fd/2
+clear_env = no
+catch_workers_output = yes

+ 1 - 1
data/conf/postfix/main.cf

@@ -9,7 +9,7 @@ smtpd_relay_restrictions = permit_mynetworks permit_sasl_authenticated defer_una
 alias_maps = hash:/etc/aliases
 alias_maps = hash:/etc/aliases
 alias_database = hash:/etc/aliases
 alias_database = hash:/etc/aliases
 relayhost =
 relayhost =
-mynetworks = 127.0.0.0/8 [::ffff:127.0.0.0]/104 [::1]/128 10.0.0.0/8 172.16.0.0/12 192.168.0.0/16 [fd4d:6169:6c63:6f77::]/64
+mynetworks = 127.0.0.0/8 [::ffff:127.0.0.0]/104 [::1]/128 10.0.0.0/8 172.16.0.0/12 192.168.0.0/16 [fd::]/8
 mailbox_size_limit = 0
 mailbox_size_limit = 0
 recipient_delimiter = +
 recipient_delimiter = +
 inet_interfaces = all
 inet_interfaces = all

+ 2 - 2
data/conf/rspamd/dynmaps/settings.php

@@ -196,7 +196,7 @@ while ($row = array_shift($rows)) {
 	}
 	}
 	whitelist_header_<?=$username_sane;?> {
 	whitelist_header_<?=$username_sane;?> {
 <?php
 <?php
-	$stmt = $pdo->prepare("SELECT GROUP_CONCAT(REPLACE(CONCAT('^', `value`, '$'), '*', '.*') SEPARATOR '|') AS `value` FROM `filterconf`
+	$stmt = $pdo->prepare("SELECT GROUP_CONCAT(REPLACE(CONCAT('\<', `value`, '\>'), '*', '.*') SEPARATOR '|') AS `value` FROM `filterconf`
 		WHERE `object`= :object
 		WHERE `object`= :object
 			AND `option` = 'whitelist_from'");
 			AND `option` = 'whitelist_from'");
 	$stmt->execute(array(':object' => $row['object']));
 	$stmt->execute(array(':object' => $row['object']));
@@ -288,7 +288,7 @@ while ($row = array_shift($rows)) {
 	}
 	}
 	blacklist_header_<?=$username_sane;?> {
 	blacklist_header_<?=$username_sane;?> {
 <?php
 <?php
-	$stmt = $pdo->prepare("SELECT GROUP_CONCAT(REPLACE(CONCAT('^', `value`, '$'), '*', '.*') SEPARATOR '|') AS `value` FROM `filterconf`
+	$stmt = $pdo->prepare("SELECT GROUP_CONCAT(REPLACE(CONCAT('\<', `value`, '\>'), '*', '.*') SEPARATOR '|') AS `value` FROM `filterconf`
 		WHERE `object`= :object
 		WHERE `object`= :object
 			AND `option` = 'blacklist_from'");
 			AND `option` = 'blacklist_from'");
 	$stmt->execute(array(':object' => $row['object']));
 	$stmt->execute(array(':object' => $row['object']));

+ 1 - 1
data/conf/rspamd/local.d/greylist.conf

@@ -1 +1 @@
-whitelisted_ip = "http://172.22.1.251:8081/forwardinghosts.php";
+whitelisted_ip = "http://nginx:8081/forwardinghosts.php";

+ 1 - 1
data/conf/rspamd/override.d/worker-controller.inc

@@ -4,6 +4,6 @@ secure_ip = "172.16.0.0/12";
 secure_ip = "10.0.0.0/8";
 secure_ip = "10.0.0.0/8";
 secure_ip = "127.0.0.1";
 secure_ip = "127.0.0.1";
 secure_ip = "::1";
 secure_ip = "::1";
-secure_ip = "fd4d:6169:6c63:6f77::/64"
+secure_ip = "fd00::/8"
 .include(try=true; priority=10) "$CONFDIR/override.d/worker-controller-password.inc"
 .include(try=true; priority=10) "$CONFDIR/override.d/worker-controller-password.inc"
 .include(try=true; priority=20) "$CONFDIR/override.d/worker-controller.custom.inc" 
 .include(try=true; priority=20) "$CONFDIR/override.d/worker-controller.custom.inc" 

+ 2 - 2
data/conf/sogo/sogo.conf

@@ -5,7 +5,7 @@
         PrivateDAndTViewer
         PrivateDAndTViewer
     );
     );
 
 
-    WOWorkersCount = "20";
+    WOWorkersCount = "7";
     SOGoACLsSendEMailNotifications = YES;
     SOGoACLsSendEMailNotifications = YES;
     SOGoAppointmentSendEMailNotifications = YES;
     SOGoAppointmentSendEMailNotifications = YES;
     SOGoDraftsFolderName = "Drafts";
     SOGoDraftsFolderName = "Drafts";
@@ -36,7 +36,7 @@
     SOGoMailingMechanism = smtp;
     SOGoMailingMechanism = smtp;
     SOGoSMTPAuthenticationType = plain;
     SOGoSMTPAuthenticationType = plain;
 
 
-    SxVMemLimit = 512;
+    SxVMemLimit = 384;
 
 
     SOGoMaximumPingInterval = 354;
     SOGoMaximumPingInterval = 354;
 
 

+ 5 - 3
data/conf/unbound/unbound.conf

@@ -8,8 +8,11 @@ server:
   do-udp: yes
   do-udp: yes
   do-tcp: yes
   do-tcp: yes
   do-daemonize: no
   do-daemonize: no
-  access-control: 172.22.1.0/24 allow
-  access-control: fd4d:6169:6c63:6f77::/64 allow
+  access-control: 10.0.0.0/8 allow
+  access-control: 172.16.0.0/12 allow
+  access-control: 192.168.0.0/16 allow
+  access-control: fd00::/8 allow
+  access-control: fe80::/10 allow
   directory: "/etc/unbound"
   directory: "/etc/unbound"
   username: unbound
   username: unbound
   auto-trust-anchor-file: trusted-key.key
   auto-trust-anchor-file: trusted-key.key
@@ -19,7 +22,6 @@ server:
   private-address: 169.254.0.0/16
   private-address: 169.254.0.0/16
   private-address: fd00::/8
   private-address: fd00::/8
   private-address: fe80::/10
   private-address: fe80::/10
-  private-address: fd4d:6169:6c63:6f77::/64
   root-hints: "/etc/unbound/root.hints"
   root-hints: "/etc/unbound/root.hints"
   hide-identity: yes
   hide-identity: yes
   hide-version: yes
   hide-version: yes

+ 47 - 28
data/web/admin.php

@@ -168,29 +168,31 @@ $tfa_data = get_tfa();
             if (!empty($dkim = dkim('details', $domain))) {
             if (!empty($dkim = dkim('details', $domain))) {
           ?>
           ?>
             <div class="row">
             <div class="row">
-              <div class="col-xs-1"><input type="checkbox" data-id="dkim" name="multi_select" value="<?=$domain;?>" /></div>
-              <div class="col-xs-2">
+              <div class="col-md-1"><input type="checkbox" data-id="dkim" name="multi_select" value="<?=$domain;?>" /></div>
+              <div class="col-md-3">
                 <p>Domain: <strong><?=htmlspecialchars($domain);?></strong>
                 <p>Domain: <strong><?=htmlspecialchars($domain);?></strong>
-                  <p><span class="label label-success"><?=$lang['admin']['dkim_key_valid'];?></span></p>
-                  <p><span class="label label-primary">Selector '<?=$dkim['dkim_selector'];?>'</span></p>
-                  <p><span class="label label-info"><?=$dkim['length'];?> bit</span></p>
+                  <p class="dkim-label"><span class="label label-success"><?=$lang['admin']['dkim_key_valid'];?></span></p>
+                  <p class="dkim-label"><span class="label label-primary">Selector '<?=$dkim['dkim_selector'];?>'</span></p>
+                  <p class="dkim-label"><span class="label label-info"><?=$dkim['length'];?> bit</span></p>
                 </p>
                 </p>
               </div>
               </div>
-              <div class="col-xs-9">
+              <div class="col-md-8">
                   <pre><?=$dkim['dkim_txt'];?></pre>
                   <pre><?=$dkim['dkim_txt'];?></pre>
                   <p data-toggle="modal" data-target="#showDKIMprivKey" id="dkim_priv" style="cursor:pointer;margin-top:-8pt" data-priv-key="<?=$dkim['privkey'];?>"><small>↪ Private key</small></p>
                   <p data-toggle="modal" data-target="#showDKIMprivKey" id="dkim_priv" style="cursor:pointer;margin-top:-8pt" data-priv-key="<?=$dkim['privkey'];?>"><small>↪ Private key</small></p>
               </div>
               </div>
+              <hr class="visible-xs visible-sm">
             </div>
             </div>
           <?php
           <?php
           }
           }
           else {
           else {
           ?>
           ?>
           <div class="row">
           <div class="row">
-              <div class="col-xs-1"><input type="checkbox" data-id="dkim" name="multi_select" value="<?=$domain;?>" disabled /></div>
-            <div class="col-xs-2">
+              <div class="col-md-1"><input type="checkbox" data-id="dkim" name="multi_select" value="<?=$domain;?>" disabled /></div>
+            <div class="col-md-3">
               <p>Domain: <strong><?=htmlspecialchars($domain);?></strong><br /><span class="label label-danger"><?=$lang['admin']['dkim_key_missing'];?></span></p>
               <p>Domain: <strong><?=htmlspecialchars($domain);?></strong><br /><span class="label label-danger"><?=$lang['admin']['dkim_key_missing'];?></span></p>
             </div>
             </div>
-            <div class="col-xs-9"><pre>-</pre></div>
+            <div class="col-md-8"><pre>-</pre></div>
+              <hr class="visible-xs visible-sm">
           </div>
           </div>
           <?php
           <?php
           }
           }
@@ -198,29 +200,31 @@ $tfa_data = get_tfa();
             if (!empty($dkim = dkim('details', $alias_domain))) {
             if (!empty($dkim = dkim('details', $alias_domain))) {
             ?>
             ?>
               <div class="row">
               <div class="row">
-              <div class="col-xs-1"><input type="checkbox" data-id="dkim" name="multi_select" value="<?=$alias_domain;?>" /></div>
-                <div class="col-xs-1 col-xs-offset-1">
+              <div class="col-md-1"><input type="checkbox" data-id="dkim" name="multi_select" value="<?=$alias_domain;?>" /></div>
+                <div class="col-md-2 col-md-offset-1">
                   <p><small>↳ Alias-Domain: <strong><?=htmlspecialchars($alias_domain);?></strong></small>
                   <p><small>↳ Alias-Domain: <strong><?=htmlspecialchars($alias_domain);?></strong></small>
-                    <p><span class="label label-success"><?=$lang['admin']['dkim_key_valid'];?></span></p>
-                    <p><span class="label label-primary">Selector '<?=$dkim['dkim_selector'];?>'</span></p>
-                    <p><span class="label label-info"><?=$dkim['length'];?> bit</span></p>
+                    <p class="dkim-label"><span class="label label-success"><?=$lang['admin']['dkim_key_valid'];?></span></p>
+                    <p class="dkim-label"><span class="label label-primary">Selector '<?=$dkim['dkim_selector'];?>'</span></p>
+                    <p class="dkim-label"><span class="label label-info"><?=$dkim['length'];?> bit</span></p>
                 </p>
                 </p>
                 </div>
                 </div>
-                <div class="col-xs-9">
+                <div class="col-md-8">
                   <pre><?=$dkim['dkim_txt'];?></pre>
                   <pre><?=$dkim['dkim_txt'];?></pre>
                   <p data-toggle="modal" data-target="#showDKIMprivKey" id="dkim_priv" style="cursor:pointer;margin-top:-8pt" data-priv-key="<?=$dkim['privkey'];?>"><small>↪ Private key</small></p>
                   <p data-toggle="modal" data-target="#showDKIMprivKey" id="dkim_priv" style="cursor:pointer;margin-top:-8pt" data-priv-key="<?=$dkim['privkey'];?>"><small>↪ Private key</small></p>
                 </div>
                 </div>
+              <hr class="visible-xs visible-sm">
               </div>
               </div>
             <?php
             <?php
             }
             }
             else {
             else {
             ?>
             ?>
             <div class="row">
             <div class="row">
-              <div class="col-xs-1"><input type="checkbox" data-id="dkim" name="multi_select" value="<?=$domain;?>" disabled /></div>
-              <div class="col-xs-1 col-xs-offset-1">
+              <div class="col-md-1"><input type="checkbox" data-id="dkim" name="multi_select" value="<?=$domain;?>" disabled /></div>
+              <div class="col-md-2 col-md-offset-1">
                 <p><small>↳ Alias-Domain: <strong><?=htmlspecialchars($alias_domain);?></strong><br /></small><span class="label label-danger"><?=$lang['admin']['dkim_key_missing'];?></span></p>
                 <p><small>↳ Alias-Domain: <strong><?=htmlspecialchars($alias_domain);?></strong><br /></small><span class="label label-danger"><?=$lang['admin']['dkim_key_missing'];?></span></p>
               </div>
               </div>
-              <div class="col-xs-9"><pre>-</pre></div>
+              <div class="col-md-8"><pre>-</pre></div>
+              <hr class="visible-xs visible-sm">
             </div>
             </div>
             <?php
             <?php
             }
             }
@@ -230,18 +234,19 @@ $tfa_data = get_tfa();
           if (!empty($dkim = dkim('details', $blind))) {
           if (!empty($dkim = dkim('details', $blind))) {
           ?>
           ?>
             <div class="row">
             <div class="row">
-              <div class="col-xs-1"><input type="checkbox" data-id="dkim" name="multi_select" value="<?=$blind;?>" /></div>
-              <div class="col-xs-2">
+              <div class="col-md-1"><input type="checkbox" data-id="dkim" name="multi_select" value="<?=$blind;?>" /></div>
+              <div class="col-md-3">
                 <p>Domain: <strong><?=htmlspecialchars($blind);?></strong>
                 <p>Domain: <strong><?=htmlspecialchars($blind);?></strong>
-                  <p><span class="label label-warning"><?=$lang['admin']['dkim_key_unused'];?></span></p>
-                  <p><span class="label label-primary">Selector '<?=$dkim['dkim_selector'];?>'</span></p>
-                  <p><span class="label label-info"><?=$dkim['length'];?> bit</span></p>
+                  <p class="dkim-label"><span class="label label-warning"><?=$lang['admin']['dkim_key_unused'];?></span></p>
+                  <p class="dkim-label"><span class="label label-primary">Selector '<?=$dkim['dkim_selector'];?>'</span></p>
+                  <p class="dkim-label"><span class="label label-info"><?=$dkim['length'];?> bit</span></p>
                 </p>
                 </p>
                 </div>
                 </div>
-                <div class="col-xs-9">
+                <div class="col-md-8">
                   <pre><?=$dkim['dkim_txt'];?></pre>
                   <pre><?=$dkim['dkim_txt'];?></pre>
                   <p data-toggle="modal" data-target="#showDKIMprivKey" id="dkim_priv" style="cursor:pointer;margin-top:-8pt" data-priv-key="<?=$dkim['privkey'];?>"><small>↪ Private key</small></p>
                   <p data-toggle="modal" data-target="#showDKIMprivKey" id="dkim_priv" style="cursor:pointer;margin-top:-8pt" data-priv-key="<?=$dkim['privkey'];?>"><small>↪ Private key</small></p>
                 </div>
                 </div>
+                <hr class="visible-xs visible-sm">
             </div>
             </div>
           <?php
           <?php
           }
           }
@@ -346,6 +351,20 @@ $tfa_data = get_tfa();
             <label for="retry_window"><?=$lang['admin']['f2b_retry_window'];?>:</label>
             <label for="retry_window"><?=$lang['admin']['f2b_retry_window'];?>:</label>
             <input type="number" class="form-control" id="retry_window" name="retry_window" value="<?=$f2b_data['retry_window'];?>" required>
             <input type="number" class="form-control" id="retry_window" name="retry_window" value="<?=$f2b_data['retry_window'];?>" required>
           </div>
           </div>
+          <div class="form-group">
+            <label for="netban_ipv4"><?=$lang['admin']['f2b_netban_ipv4'];?>:</label>
+            <div class="input-group">
+              <span class="input-group-addon">/</span>
+              <input type="number" class="form-control" id="netban_ipv4" name="netban_ipv4" value="<?=$f2b_data['netban_ipv4'];?>" required>
+            </div>
+          </div>
+          <div class="form-group">
+            <label for="netban_ipv6"><?=$lang['admin']['f2b_netban_ipv6'];?>:</label>
+            <div class="input-group">
+              <span class="input-group-addon">/</span>
+              <input type="number" class="form-control" id="netban_ipv6" name="netban_ipv6" value="<?=$f2b_data['netban_ipv6'];?>" required>
+            </div>
+          </div>
           <div class="form-group">
           <div class="form-group">
             <label for="whitelist"><?=$lang['admin']['f2b_whitelist'];?>:</label>
             <label for="whitelist"><?=$lang['admin']['f2b_whitelist'];?>:</label>
             <textarea class="form-control" id="whitelist" name="whitelist" rows="5"><?=$f2b_data['whitelist'];?></textarea>
             <textarea class="form-control" id="whitelist" name="whitelist" rows="5"><?=$f2b_data['whitelist'];?></textarea>
@@ -427,7 +446,7 @@ $tfa_data = get_tfa();
               ?>
               ?>
             </select>
             </select>
           </div>
           </div>
-          <button class="btn btn-success" id="edit_selected" data-item="self" data-id="quarantaine" data-api-url='edit/quarantaine' data-api-attr='{"action":"settings"}' href="#"><span class="glyphicon glyphicon-check"></span> <?=$lang['admin']['save'];?></button>
+          <button class="btn btn-default" id="edit_selected" data-item="self" data-id="quarantaine" data-api-url='edit/quarantaine' data-api-attr='{"action":"settings"}' href="#"><span class="glyphicon glyphicon-check"></span> <?=$lang['admin']['save'];?></button>
         </form>
         </form>
       </div>
       </div>
     </div>
     </div>
@@ -441,7 +460,7 @@ $tfa_data = get_tfa();
         <form class="form-inline" role="form" method="post" enctype="multipart/form-data">
         <form class="form-inline" role="form" method="post" enctype="multipart/form-data">
           <p>
           <p>
             <input type="file" name="main_logo" class="filestyle" data-buttonName="btn-default" data-buttonText="Select" accept="image/gif, image/jpeg, image/pjpeg, image/x-png, image/png, image/svg+xml">
             <input type="file" name="main_logo" class="filestyle" data-buttonName="btn-default" data-buttonText="Select" accept="image/gif, image/jpeg, image/pjpeg, image/x-png, image/png, image/svg+xml">
-            <button name="submit_main_logo" type="submit" class="btn btn-success"><span class="glyphicon glyphicon-cloud-upload"></span> <?=$lang['admin']['upload'];?></button>
+            <button name="submit_main_logo" type="submit" class="btn btn-default"><span class="glyphicon glyphicon-cloud-upload"></span> <?=$lang['admin']['upload'];?></button>
           </p>
           </p>
         </form>
         </form>
         <?php
         <?php
@@ -501,7 +520,7 @@ $tfa_data = get_tfa();
             ?>
             ?>
           </table>
           </table>
           <p><div class="btn-group">
           <p><div class="btn-group">
-            <button class="btn btn-sm btn-success" id="edit_selected" data-item="admin" data-id="app_links" data-reload="no" data-api-url='edit/app_links' data-api-attr='{}' href="#"><span class="glyphicon glyphicon-check"></span> <?=$lang['admin']['save'];?></button>
+            <button class="btn btn-sm btn-default" id="edit_selected" data-item="admin" data-id="app_links" data-reload="no" data-api-url='edit/app_links' data-api-attr='{}' href="#"><span class="glyphicon glyphicon-check"></span> <?=$lang['admin']['save'];?></button>
             <button class="btn btn-sm btn-default" type="button" id="add_app_link_row"><?=$lang['admin']['add_row'];?></button>
             <button class="btn btn-sm btn-default" type="button" id="add_app_link_row"><?=$lang['admin']['add_row'];?></button>
           </div></p>
           </div></p>
         </form>
         </form>
@@ -526,7 +545,7 @@ $tfa_data = get_tfa();
             <label for="help_text"><?=$lang['admin']['help_text'];?>:</label>
             <label for="help_text"><?=$lang['admin']['help_text'];?>:</label>
             <textarea class="form-control" id="help_text" name="help_text" rows="7"><?=$ui_texts['help_text'];?></textarea>
             <textarea class="form-control" id="help_text" name="help_text" rows="7"><?=$ui_texts['help_text'];?></textarea>
           </div>
           </div>
-          <button class="btn btn-success" id="edit_selected" data-item="ui" data-id="uitexts" data-api-url='edit/ui_texts' data-api-attr='{}' href="#"><span class="glyphicon glyphicon-check"></span> <?=$lang['admin']['save'];?></button>
+          <button class="btn btn-default" id="edit_selected" data-item="ui" data-id="uitexts" data-api-url='edit/ui_texts' data-api-attr='{}' href="#"><span class="glyphicon glyphicon-check"></span> <?=$lang['admin']['save'];?></button>
         </form>
         </form>
       </div>
       </div>
     </div>
     </div>

+ 19 - 0
data/web/css/mailcow.css

@@ -117,3 +117,22 @@ legend {
 .lang-link-disabled  {
 .lang-link-disabled  {
 	cursor: not-allowed;
 	cursor: not-allowed;
 }
 }
+.dkim-label {
+  margin: 0 0 2px !important;
+}
+.overlay {
+  background: #fff;
+  position: absolute;
+  z-index: 10000;
+  top: 0; right: 0; bottom: 0; left: 0;
+  opacity: 0.7;
+}
+nav .glyphicon {
+  font-size: 12px !important;
+}
+.logged-in-as {
+  border-left: 1px solid #E7E7E7;
+}
+#top {
+  padding-top: 70px;
+}

+ 26 - 1
data/web/edit.php

@@ -704,6 +704,8 @@ if (isset($_SESSION['mailcow_cc_role'])) {
             <input type="hidden" value="0" name="delete2duplicates">
             <input type="hidden" value="0" name="delete2duplicates">
             <input type="hidden" value="0" name="delete1">
             <input type="hidden" value="0" name="delete1">
             <input type="hidden" value="0" name="delete2">
             <input type="hidden" value="0" name="delete2">
+            <input type="hidden" value="0" name="automap">
+            <input type="hidden" value="0" name="skipcrossduplicates">
             <input type="hidden" value="0" name="active">
             <input type="hidden" value="0" name="active">
             <div class="form-group">
             <div class="form-group">
               <label class="control-label col-sm-2" for="host1"><?=$lang['edit']['hostname'];?></label>
               <label class="control-label col-sm-2" for="host1"><?=$lang['edit']['hostname'];?></label>
@@ -743,6 +745,7 @@ if (isset($_SESSION['mailcow_cc_role'])) {
               <label class="control-label col-sm-2" for="mins_interval"><?=$lang['edit']['mins_interval'];?></label>
               <label class="control-label col-sm-2" for="mins_interval"><?=$lang['edit']['mins_interval'];?></label>
               <div class="col-sm-10">
               <div class="col-sm-10">
                 <input type="number" class="form-control" name="mins_interval" min="1" max="3600" value="<?=htmlspecialchars($result['mins_interval'], ENT_QUOTES, 'UTF-8');?>" required>
                 <input type="number" class="form-control" name="mins_interval" min="1" max="3600" value="<?=htmlspecialchars($result['mins_interval'], ENT_QUOTES, 'UTF-8');?>" required>
+                <small class="help-block">10-3600</small>
               </div>
               </div>
             </div>
             </div>
             <div class="form-group">
             <div class="form-group">
@@ -754,7 +757,15 @@ if (isset($_SESSION['mailcow_cc_role'])) {
             <div class="form-group">
             <div class="form-group">
               <label class="control-label col-sm-2" for="maxage"><?=$lang['edit']['maxage'];?></label>
               <label class="control-label col-sm-2" for="maxage"><?=$lang['edit']['maxage'];?></label>
               <div class="col-sm-10">
               <div class="col-sm-10">
-              <input type="number" class="form-control" name="maxage" id="maxage" value="<?=htmlspecialchars($result['maxage'], ENT_QUOTES, 'UTF-8');?>">
+              <input type="number" class="form-control" name="maxage" id="maxage" min="0" max="32000" value="<?=htmlspecialchars($result['maxage'], ENT_QUOTES, 'UTF-8');?>">
+              <small class="help-block">0-32000</small>
+              </div>
+            </div>
+            <div class="form-group">
+              <label class="control-label col-sm-2" for="maxbytespersecond"><?=$lang['edit']['maxbytespersecond'];?></label>
+              <div class="col-sm-10">
+              <input type="number" class="form-control" name="maxbytespersecond" id="maxbytespersecond" min="0" max="125000000" value="<?=htmlspecialchars($result['maxbytespersecond'], ENT_QUOTES, 'UTF-8');?>">
+              <small class="help-block">0-125000000</small>
               </div>
               </div>
             </div>
             </div>
             <div class="form-group">
             <div class="form-group">
@@ -784,6 +795,20 @@ if (isset($_SESSION['mailcow_cc_role'])) {
                 </div>
                 </div>
               </div>
               </div>
             </div>
             </div>
+            <div class="form-group">
+              <div class="col-sm-offset-2 col-sm-10">
+                <div class="checkbox">
+                <label><input type="checkbox" value="1" name="automap" <?=($result['automap']=="1") ? "checked" : "";?>> <?=$lang['edit']['automap'];?></label>
+                </div>
+              </div>
+            </div>
+            <div class="form-group">
+              <div class="col-sm-offset-2 col-sm-10">
+                <div class="checkbox">
+                <label><input type="checkbox" value="1" name="skipcrossduplicates" <?=($result['skipcrossduplicates']=="1") ? "checked" : "";?>> <?=$lang['edit']['skipcrossduplicates'];?></label>
+                </div>
+              </div>
+            </div>
             <div class="form-group">
             <div class="form-group">
               <div class="col-sm-offset-2 col-sm-10">
               <div class="col-sm-offset-2 col-sm-10">
                 <div class="checkbox">
                 <div class="checkbox">

+ 3 - 1
data/web/inc/footer.inc.php

@@ -22,7 +22,9 @@ function setLang(sel) {
   $.post( "<?= $_SERVER['REQUEST_URI']; ?>", {lang: sel} );
   $.post( "<?= $_SERVER['REQUEST_URI']; ?>", {lang: sel} );
   window.location.href = window.location.pathname + window.location.search;
   window.location.href = window.location.pathname + window.location.search;
 }
 }
-
+$(window).load(function() {
+  $(".overlay").hide();
+});
 $(document).ready(function() {
 $(document).ready(function() {
   window.mailcow_alert_box = function(message, type) {
   window.mailcow_alert_box = function(message, type) {
     msg = $('<span/>').html(message).text();
     msg = $('<span/>').html(message).text();

+ 12 - 0
data/web/inc/functions.fail2ban.inc.php

@@ -12,6 +12,8 @@ function fail2ban($_action, $_data = null) {
         $data['ban_time'] = $redis->Get('F2B_BAN_TIME');
         $data['ban_time'] = $redis->Get('F2B_BAN_TIME');
         $data['max_attempts'] = $redis->Get('F2B_MAX_ATTEMPTS');
         $data['max_attempts'] = $redis->Get('F2B_MAX_ATTEMPTS');
         $data['retry_window'] = $redis->Get('F2B_RETRY_WINDOW');
         $data['retry_window'] = $redis->Get('F2B_RETRY_WINDOW');
+        $data['netban_ipv4'] = $redis->Get('F2B_NETBAN_IPV4');
+        $data['netban_ipv6'] = $redis->Get('F2B_NETBAN_IPV6');
         $wl = $redis->hGetAll('F2B_WHITELIST');
         $wl = $redis->hGetAll('F2B_WHITELIST');
         if (is_array($wl)) {
         if (is_array($wl)) {
           foreach ($wl as $key => $value) {
           foreach ($wl as $key => $value) {
@@ -50,6 +52,8 @@ function fail2ban($_action, $_data = null) {
         $ban_time = intval((isset($_data['ban_time'])) ? $_data['ban_time'] : $is_now['ban_time']);
         $ban_time = intval((isset($_data['ban_time'])) ? $_data['ban_time'] : $is_now['ban_time']);
         $max_attempts = intval((isset($_data['max_attempts'])) ? $_data['max_attempts'] : $is_now['active_int']);
         $max_attempts = intval((isset($_data['max_attempts'])) ? $_data['max_attempts'] : $is_now['active_int']);
         $retry_window = intval((isset($_data['retry_window'])) ? $_data['retry_window'] : $is_now['retry_window']);
         $retry_window = intval((isset($_data['retry_window'])) ? $_data['retry_window'] : $is_now['retry_window']);
+        $netban_ipv4 = intval((isset($_data['netban_ipv4'])) ? $_data['netban_ipv4'] : $is_now['netban_ipv4']);
+        $netban_ipv6 = intval((isset($_data['netban_ipv6'])) ? $_data['netban_ipv6'] : $is_now['netban_ipv6']);
       }
       }
       else {
       else {
         $_SESSION['return'] = array(
         $_SESSION['return'] = array(
@@ -60,12 +64,20 @@ function fail2ban($_action, $_data = null) {
       }
       }
       $wl = $_data['whitelist'];
       $wl = $_data['whitelist'];
       $ban_time = ($ban_time < 60) ? 60 : $ban_time;
       $ban_time = ($ban_time < 60) ? 60 : $ban_time;
+
+      $netban_ipv4 = ($netban_ipv4 < 8) ? 8 : $netban_ipv4;
+      $netban_ipv6 = ($netban_ipv6 < 8) ? 8 : $netban_ipv6;
+      $netban_ipv4 = ($netban_ipv4 > 32) ? 32 : $netban_ipv4;
+      $netban_ipv6 = ($netban_ipv6 > 128) ? 128 : $netban_ipv6;
+
       $max_attempts = ($max_attempts < 1) ? 1 : $max_attempts;
       $max_attempts = ($max_attempts < 1) ? 1 : $max_attempts;
       $retry_window = ($retry_window < 1) ? 1 : $retry_window;
       $retry_window = ($retry_window < 1) ? 1 : $retry_window;
       try {
       try {
         $redis->Set('F2B_BAN_TIME', $ban_time);
         $redis->Set('F2B_BAN_TIME', $ban_time);
         $redis->Set('F2B_MAX_ATTEMPTS', $max_attempts);
         $redis->Set('F2B_MAX_ATTEMPTS', $max_attempts);
         $redis->Set('F2B_RETRY_WINDOW', $retry_window);
         $redis->Set('F2B_RETRY_WINDOW', $retry_window);
+        $redis->Set('F2B_NETBAN_IPV4', $netban_ipv4);
+        $redis->Set('F2B_NETBAN_IPV6', $netban_ipv6);
         $redis->Del('F2B_WHITELIST');
         $redis->Del('F2B_WHITELIST');
         if(!empty($wl)) {
         if(!empty($wl)) {
           $wl_array = array_map('trim', preg_split( "/( |,|;|\n)/", $wl));
           $wl_array = array_map('trim', preg_split( "/( |,|;|\n)/", $wl));

+ 9 - 2
data/web/inc/functions.inc.php

@@ -459,8 +459,9 @@ function user_get_alias_details($username) {
     while ($row = array_shift($run)) {
     while ($row = array_shift($run)) {
       $data['shared_aliases'] = $row['shared_aliases'];
       $data['shared_aliases'] = $row['shared_aliases'];
     }
     }
-    $stmt = $pdo->prepare("SELECT IFNULL(GROUP_CONCAT(`address` SEPARATOR ', '), '&#10008;') AS `direct_aliases` FROM `alias`
+    $stmt = $pdo->prepare("SELECT GROUP_CONCAT(`address` SEPARATOR ', ') AS `direct_aliases` FROM `alias`
       WHERE `goto` = :username_goto
       WHERE `goto` = :username_goto
+      AND `address` NOT LIKE '@%'
       AND `address` != :username_address");
       AND `address` != :username_address");
     $stmt->execute(
     $stmt->execute(
       array(
       array(
@@ -477,7 +478,13 @@ function user_get_alias_details($username) {
     $stmt->execute(array(':username' => $username));
     $stmt->execute(array(':username' => $username));
     $run = $stmt->fetchAll(PDO::FETCH_ASSOC);
     $run = $stmt->fetchAll(PDO::FETCH_ASSOC);
     while ($row = array_shift($run)) {
     while ($row = array_shift($run)) {
-      $data['ad_alias'] = $row['ad_alias'];
+      if (empty($data['direct_aliases'])) {
+        $data['direct_aliases'] = $row['ad_alias'];
+      }
+      else {
+        // Probably faster than imploding
+        $data['direct_aliases'] .= ', ' . $row['ad_alias'];
+      }
     }
     }
     $stmt = $pdo->prepare("SELECT IFNULL(GROUP_CONCAT(`send_as` SEPARATOR ', '), '&#10008;') AS `send_as` FROM `sender_acl` WHERE `logged_in_as` = :username AND `send_as` NOT LIKE '@%';");
     $stmt = $pdo->prepare("SELECT IFNULL(GROUP_CONCAT(`send_as` SEPARATOR ', '), '&#10008;') AS `send_as` FROM `sender_acl` WHERE `logged_in_as` = :username AND `send_as` NOT LIKE '@%';");
     $stmt->execute(array(':username' => $username));
     $stmt->execute(array(':username' => $username));

+ 50 - 16
data/web/inc/functions.mailbox.inc.php

@@ -213,24 +213,30 @@ function mailbox($_action, $_type, $_data = null, $attr = null) {
             return false;
             return false;
           }
           }
           $active  = intval($_data['active']);
           $active  = intval($_data['active']);
-          $delete2duplicates = intval($_data['delete2duplicates']);
-          $delete1  = intval($_data['delete1']);
-          $delete2  = intval($_data['delete2']);
-          $port1            = $_data['port1'];
-          $host1            = strtolower($_data['host1']);
-          $password1        = $_data['password1'];
-          $exclude          = $_data['exclude'];
-          $maxage           = $_data['maxage'];
-          $subfolder2       = $_data['subfolder2'];
-          $user1            = $_data['user1'];
-          $mins_interval    = $_data['mins_interval'];
-          $enc1             = $_data['enc1'];
+          $delete2duplicates    = intval($_data['delete2duplicates']);
+          $delete1              = intval($_data['delete1']);
+          $delete2              = intval($_data['delete2']);
+          $skipcrossduplicates  = intval($_data['skipcrossduplicates']);
+          $automap              = intval($_data['automap']);
+          $port1                = $_data['port1'];
+          $host1                = strtolower($_data['host1']);
+          $password1            = $_data['password1'];
+          $exclude              = $_data['exclude'];
+          $maxage               = $_data['maxage'];
+          $maxbytespersecond    = $_data['maxbytespersecond'];
+          $subfolder2           = $_data['subfolder2'];
+          $user1                = $_data['user1'];
+          $mins_interval        = $_data['mins_interval'];
+          $enc1                = $_data['enc1'];
           if (empty($subfolder2)) {
           if (empty($subfolder2)) {
             $subfolder2 = "";
             $subfolder2 = "";
           }
           }
           if (!isset($maxage) || !filter_var($maxage, FILTER_VALIDATE_INT, array('options' => array('min_range' => 1, 'max_range' => 32767)))) {
           if (!isset($maxage) || !filter_var($maxage, FILTER_VALIDATE_INT, array('options' => array('min_range' => 1, 'max_range' => 32767)))) {
             $maxage = "0";
             $maxage = "0";
           }
           }
+          if (!isset($maxbytespersecond) || !filter_var($maxbytespersecond, FILTER_VALIDATE_INT, array('options' => array('min_range' => 1, 'max_range' => 125000000)))) {
+            $maxbytespersecond = "0";
+          }
           if (!filter_var($port1, FILTER_VALIDATE_INT, array('options' => array('min_range' => 1, 'max_range' => 65535)))) {
           if (!filter_var($port1, FILTER_VALIDATE_INT, array('options' => array('min_range' => 1, 'max_range' => 65535)))) {
             $_SESSION['return'] = array(
             $_SESSION['return'] = array(
               'type' => 'danger',
               'type' => 'danger',
@@ -287,14 +293,17 @@ function mailbox($_action, $_type, $_data = null, $attr = null) {
             return false;
             return false;
           }
           }
           try {
           try {
-            $stmt = $pdo->prepare("INSERT INTO `imapsync` (`user2`, `exclude`, `delete1`, `delete2`, `maxage`, `subfolder2`, `host1`, `authmech1`, `user1`, `password1`, `mins_interval`, `port1`, `enc1`, `delete2duplicates`, `active`)
-              VALUES (:user2, :exclude, :delete1, :delete2, :maxage, :subfolder2, :host1, :authmech1, :user1, :password1, :mins_interval, :port1, :enc1, :delete2duplicates, :active)");
+            $stmt = $pdo->prepare("INSERT INTO `imapsync` (`user2`, `exclude`, `delete1`, `delete2`, `automap`, `skipcrossduplicates`, `maxbytespersecond`, `maxage`, `subfolder2`, `host1`, `authmech1`, `user1`, `password1`, `mins_interval`, `port1`, `enc1`, `delete2duplicates`, `active`)
+              VALUES (:user2, :exclude, :delete1, :delete2, :automap, :skipcrossduplicates, :maxbytespersecond, :maxage, :subfolder2, :host1, :authmech1, :user1, :password1, :mins_interval, :port1, :enc1, :delete2duplicates, :active)");
             $stmt->execute(array(
             $stmt->execute(array(
               ':user2' => $username,
               ':user2' => $username,
               ':exclude' => $exclude,
               ':exclude' => $exclude,
               ':maxage' => $maxage,
               ':maxage' => $maxage,
               ':delete1' => $delete1,
               ':delete1' => $delete1,
               ':delete2' => $delete2,
               ':delete2' => $delete2,
+              ':automap' => $automap,
+              ':skipcrossduplicates' => $skipcrossduplicates,
+              ':maxbytespersecond' => $maxbytespersecond,
               ':subfolder2' => $subfolder2,
               ':subfolder2' => $subfolder2,
               ':host1' => $host1,
               ':host1' => $host1,
               ':authmech1' => 'PLAIN',
               ':authmech1' => 'PLAIN',
@@ -1444,6 +1453,8 @@ function mailbox($_action, $_type, $_data = null, $attr = null) {
               $delete2duplicates = (isset($_data['delete2duplicates'])) ? intval($_data['delete2duplicates']) : $is_now['delete2duplicates'];
               $delete2duplicates = (isset($_data['delete2duplicates'])) ? intval($_data['delete2duplicates']) : $is_now['delete2duplicates'];
               $delete1 = (isset($_data['delete1'])) ? intval($_data['delete1']) : $is_now['delete1'];
               $delete1 = (isset($_data['delete1'])) ? intval($_data['delete1']) : $is_now['delete1'];
               $delete2 = (isset($_data['delete2'])) ? intval($_data['delete2']) : $is_now['delete2'];
               $delete2 = (isset($_data['delete2'])) ? intval($_data['delete2']) : $is_now['delete2'];
+              $automap = (isset($_data['automap'])) ? intval($_data['automap']) : $is_now['automap'];
+              $skipcrossduplicates = (isset($_data['skipcrossduplicates'])) ? intval($_data['skipcrossduplicates']) : $is_now['skipcrossduplicates'];
               $port1 = (!empty($_data['port1'])) ? $_data['port1'] : $is_now['port1'];
               $port1 = (!empty($_data['port1'])) ? $_data['port1'] : $is_now['port1'];
               $password1 = (!empty($_data['password1'])) ? $_data['password1'] : $is_now['password1'];
               $password1 = (!empty($_data['password1'])) ? $_data['password1'] : $is_now['password1'];
               $host1 = (!empty($_data['host1'])) ? $_data['host1'] : $is_now['host1'];
               $host1 = (!empty($_data['host1'])) ? $_data['host1'] : $is_now['host1'];
@@ -1452,6 +1463,7 @@ function mailbox($_action, $_type, $_data = null, $attr = null) {
               $mins_interval = (!empty($_data['mins_interval'])) ? $_data['mins_interval'] : $is_now['mins_interval'];
               $mins_interval = (!empty($_data['mins_interval'])) ? $_data['mins_interval'] : $is_now['mins_interval'];
               $exclude = (!empty($_data['exclude'])) ? $_data['exclude'] : $is_now['exclude'];
               $exclude = (!empty($_data['exclude'])) ? $_data['exclude'] : $is_now['exclude'];
               $maxage = (isset($_data['maxage']) && $_data['maxage'] != "") ? intval($_data['maxage']) : $is_now['maxage'];
               $maxage = (isset($_data['maxage']) && $_data['maxage'] != "") ? intval($_data['maxage']) : $is_now['maxage'];
+              $maxbytespersecond = (isset($_data['maxbytespersecond']) && $_data['maxbytespersecond'] != "") ? intval($_data['maxbytespersecond']) : $is_now['maxbytespersecond'];
             }
             }
             else {
             else {
               $_SESSION['return'] = array(
               $_SESSION['return'] = array(
@@ -1466,6 +1478,9 @@ function mailbox($_action, $_type, $_data = null, $attr = null) {
             if (!isset($maxage) || !filter_var($maxage, FILTER_VALIDATE_INT, array('options' => array('min_range' => 1, 'max_range' => 32767)))) {
             if (!isset($maxage) || !filter_var($maxage, FILTER_VALIDATE_INT, array('options' => array('min_range' => 1, 'max_range' => 32767)))) {
               $maxage = "0";
               $maxage = "0";
             }
             }
+            if (!isset($maxbytespersecond) || !filter_var($maxbytespersecond, FILTER_VALIDATE_INT, array('options' => array('min_range' => 1, 'max_range' => 125000000)))) {
+              $maxbytespersecond = "0";
+            }
             if (!filter_var($port1, FILTER_VALIDATE_INT, array('options' => array('min_range' => 1, 'max_range' => 65535)))) {
             if (!filter_var($port1, FILTER_VALIDATE_INT, array('options' => array('min_range' => 1, 'max_range' => 65535)))) {
               $_SESSION['return'] = array(
               $_SESSION['return'] = array(
                 'type' => 'danger',
                 'type' => 'danger',
@@ -1502,14 +1517,33 @@ function mailbox($_action, $_type, $_data = null, $attr = null) {
               return false;
               return false;
             }
             }
             try {
             try {
-              $stmt = $pdo->prepare("UPDATE `imapsync` SET `delete1` = :delete1, `delete2` = :delete2, `maxage` = :maxage, `subfolder2` = :subfolder2, `exclude` = :exclude, `host1` = :host1, `last_run` = :last_run, `user1` = :user1, `password1` = :password1, `mins_interval` = :mins_interval, `port1` = :port1, `enc1` = :enc1, `delete2duplicates` = :delete2duplicates, `active` = :active
-                WHERE `id` = :id");
+              $stmt = $pdo->prepare("UPDATE `imapsync` SET `delete1` = :delete1,
+                `delete2` = :delete2,
+                `automap` = :automap,
+                `skipcrossduplicates` = :skipcrossduplicates,
+                `maxage` = :maxage,
+                `maxbytespersecond` = :maxbytespersecond,
+                `subfolder2` = :subfolder2,
+                `exclude` = :exclude,
+                `host1` = :host1,
+                `last_run` = :last_run,
+                `user1` = :user1,
+                `password1` = :password1,
+                `mins_interval` = :mins_interval,
+                `port1` = :port1,
+                `enc1` = :enc1,
+                `delete2duplicates` = :delete2duplicates,
+                `active` = :active
+                  WHERE `id` = :id");
               $stmt->execute(array(
               $stmt->execute(array(
                 ':delete1' => $delete1,
                 ':delete1' => $delete1,
                 ':delete2' => $delete2,
                 ':delete2' => $delete2,
+                ':automap' => $automap,
+                ':skipcrossduplicates' => $skipcrossduplicates,
                 ':id' => $id,
                 ':id' => $id,
                 ':exclude' => $exclude,
                 ':exclude' => $exclude,
                 ':maxage' => $maxage,
                 ':maxage' => $maxage,
+                ':maxbytespersecond' => $maxbytespersecond,
                 ':subfolder2' => $subfolder2,
                 ':subfolder2' => $subfolder2,
                 ':host1' => $host1,
                 ':host1' => $host1,
                 ':user1' => $user1,
                 ':user1' => $user1,

+ 7 - 5
data/web/inc/header.inc.php

@@ -4,6 +4,7 @@
 <meta charset="utf-8">
 <meta charset="utf-8">
 <meta http-equiv="X-UA-Compatible" content="IE=edge">
 <meta http-equiv="X-UA-Compatible" content="IE=edge">
 <meta name="viewport" content="width=device-width, initial-scale=1">
 <meta name="viewport" content="width=device-width, initial-scale=1">
+<meta http-equiv="Referrer-Policy" content="same-origin">
 <title><?=$UI_TEXTS['title_name'];?></title>
 <title><?=$UI_TEXTS['title_name'];?></title>
 <!--[if lt IE 9]>
 <!--[if lt IE 9]>
   <script src="/js/html5shiv.min.js"></script>
   <script src="/js/html5shiv.min.js"></script>
@@ -33,7 +34,8 @@
 <link rel="shortcut icon" href="/favicon.png" type="image/png">
 <link rel="shortcut icon" href="/favicon.png" type="image/png">
 <link rel="icon" href="/favicon.png" type="image/png">
 <link rel="icon" href="/favicon.png" type="image/png">
 </head>
 </head>
-<body style="padding-top: 70px;" id="top">
+<body id="top">
+<div class="overlay"></div>
 <nav class="navbar navbar-default navbar-fixed-top" role="navigation">
 <nav class="navbar navbar-default navbar-fixed-top" role="navigation">
   <div class="container-fluid">
   <div class="container-fluid">
     <div class="navbar-header">
     <div class="navbar-header">
@@ -93,12 +95,12 @@
         <?php
         <?php
         if (isset($_SESSION['mailcow_cc_role'])) {
         if (isset($_SESSION['mailcow_cc_role'])) {
         ?>
         ?>
-        <li<?= (preg_match("/quarantaine/i", $_SERVER['REQUEST_URI'])) ? ' class="active"' : ''; ?>><a href="/quarantaine.php"><span style="font-size: 12px;" class="glyphicon glyphicon-briefcase"></span> <?= $lang['header']['quarantaine']; ?></a></li>
+        <li<?= (preg_match("/quarantaine/i", $_SERVER['REQUEST_URI'])) ? ' class="active"' : ''; ?>><a href="/quarantaine.php"><span class="glyphicon glyphicon-briefcase"></span> <?= $lang['header']['quarantaine']; ?></a></li>
         <?php
         <?php
         }
         }
         if ($_SESSION['mailcow_cc_role'] == 'admin') {
         if ($_SESSION['mailcow_cc_role'] == 'admin') {
         ?>
         ?>
-        <li><a href data-toggle="modal" data-container="sogo-mailcow" data-target="#RestartContainer"><span style="font-size: 12px;" class="glyphicon glyphicon-refresh"></span> <?= $lang['header']['restart_sogo']; ?></a></li>
+        <li><a href data-toggle="modal" data-container="sogo-mailcow" data-target="#RestartContainer"><span class="glyphicon glyphicon-refresh"></span> <?= $lang['header']['restart_sogo']; ?></a></li>
         <?php
         <?php
         }
         }
         ?>
         ?>
@@ -126,11 +128,11 @@
         }
         }
         if (!isset($_SESSION['dual-login']) && isset($_SESSION['mailcow_cc_username'])):
         if (!isset($_SESSION['dual-login']) && isset($_SESSION['mailcow_cc_username'])):
         ?>
         ?>
-          <li><a href="#" style="border-left: 1px solid #E7E7E7;" onclick="logout.submit()"><?= sprintf($lang['header']['logged_in_as_logout'], $_SESSION['mailcow_cc_username']); ?></a></li>
+          <li class="logged-in-as"><a href="#" onclick="logout.submit()"><b><?= $_SESSION['mailcow_cc_username']; ?></b> <span class="glyphicon glyphicon-log-out"></span></a></li>
         <?php
         <?php
         elseif (isset($_SESSION['dual-login'])):
         elseif (isset($_SESSION['dual-login'])):
         ?>
         ?>
-          <li><a href="#" style="border-left: 1px solid #E7E7E7;" onclick="logout.submit()"><?= sprintf($lang['header']['logged_in_as_logout_dual'], $_SESSION['mailcow_cc_username'], $_SESSION['dual-login']['username']); ?></a></li>
+          <li class="logged-in-as"><a href="#" onclick="logout.submit()"><b><?= $_SESSION['mailcow_cc_username']; ?> <span class="text-info">(<?= $_SESSION['dual-login']['username']; ?>)</span> </b><span class="glyphicon glyphicon-log-out"></span></a></li>
         <?php
         <?php
         endif;
         endif;
         ?>
         ?>

+ 9 - 5
data/web/inc/init_db.inc.php

@@ -3,7 +3,7 @@ function init_db_schema() {
   try {
   try {
     global $pdo;
     global $pdo;
 
 
-    $db_version = "20012021_2202";
+    $db_version = "27012018_1721";
 
 
     $stmt = $pdo->query("SHOW TABLES LIKE 'versions'");
     $stmt = $pdo->query("SHOW TABLES LIKE 'versions'");
     $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
     $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
@@ -21,13 +21,14 @@ function init_db_schema() {
       AND active = '1'
       AND active = '1'
       AND address NOT LIKE '@%'
       AND address NOT LIKE '@%'
       GROUP BY goto;",
       GROUP BY goto;",
-    "grouped_sender_acl" => "CREATE VIEW grouped_sender_acl (username, send_as) AS
-      SELECT logged_in_as, IFNULL(GROUP_CONCAT(send_as SEPARATOR ' '), '') AS send_as FROM sender_acl
+    "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 '@%'
       WHERE send_as NOT LIKE '@%'
       GROUP BY logged_in_as;",
       GROUP BY logged_in_as;",
     "grouped_domain_alias_address" => "CREATE VIEW grouped_domain_alias_address (username, ad_alias) 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
       SELECT username, IFNULL(GROUP_CONCAT(local_part, '@', alias_domain SEPARATOR ' '), '') AS ad_alias FROM mailbox
-      LEFT OUTER JOIN alias_domain on target_domain=domain GROUP BY username;",
+      LEFT OUTER JOIN alias_domain ON target_domain=domain
+      GROUP BY username;",
     "sieve_before" => "CREATE VIEW sieve_before (id, username, script_name, script_data) AS
     "sieve_before" => "CREATE VIEW sieve_before (id, username, script_name, script_data) AS
       SELECT md5(script_data), username, script_name, script_data FROM sieve_filters
       SELECT md5(script_data), username, script_name, script_data FROM sieve_filters
       WHERE filter_type = 'prefilter';",
       WHERE filter_type = 'prefilter';",
@@ -353,12 +354,15 @@ function init_db_schema() {
           "password1" => "VARCHAR(255) NOT NULL",
           "password1" => "VARCHAR(255) NOT NULL",
           "exclude" => "VARCHAR(500) NOT NULL DEFAULT ''",
           "exclude" => "VARCHAR(500) NOT NULL DEFAULT ''",
           "maxage" => "SMALLINT NOT NULL DEFAULT '0'",
           "maxage" => "SMALLINT NOT NULL DEFAULT '0'",
-          "mins_interval" => "VARCHAR(50) NOT NULL",
+          "mins_interval" => "VARCHAR(50) NOT NULL DEFAULT '0'",
+          "maxbytespersecond" => "VARCHAR(50) NOT NULL DEFAULT '0'",
           "port1" => "SMALLINT NOT NULL",
           "port1" => "SMALLINT NOT NULL",
           "enc1" => "ENUM('TLS','SSL','PLAIN') DEFAULT 'TLS'",
           "enc1" => "ENUM('TLS','SSL','PLAIN') DEFAULT 'TLS'",
           "delete2duplicates" => "TINYINT(1) NOT NULL DEFAULT '1'",
           "delete2duplicates" => "TINYINT(1) NOT NULL DEFAULT '1'",
           "delete1" => "TINYINT(1) NOT NULL DEFAULT '0'",
           "delete1" => "TINYINT(1) NOT NULL DEFAULT '0'",
           "delete2" => "TINYINT(1) NOT NULL DEFAULT '0'",
           "delete2" => "TINYINT(1) NOT NULL DEFAULT '0'",
+          "automap" => "TINYINT(1) NOT NULL DEFAULT '0'",
+          "skipcrossduplicates" => "TINYINT(1) NOT NULL DEFAULT '0'",
           "is_running" => "TINYINT(1) NOT NULL DEFAULT '0'",
           "is_running" => "TINYINT(1) NOT NULL DEFAULT '0'",
           "returned_text" => "TEXT",
           "returned_text" => "TEXT",
           "last_run" => "TIMESTAMP NULL DEFAULT NULL",
           "last_run" => "TIMESTAMP NULL DEFAULT NULL",

+ 2 - 2
data/web/js/quarantaine.js

@@ -15,7 +15,7 @@ jQuery(function($){
         {"name":"sender","title":lang.sender,"breakpoints":"xs sm"},
         {"name":"sender","title":lang.sender,"breakpoints":"xs sm"},
         {"name":"rcpt","title":lang.rcpt, "type": "text"},
         {"name":"rcpt","title":lang.rcpt, "type": "text"},
         {"name":"created","formatter":function unix_time_format(tm) { var date = new Date(tm ? tm * 1000 : 0); return date.toLocaleString();},"title":lang.received,"style":{"width":"170px"}},
         {"name":"created","formatter":function unix_time_format(tm) { var date = new Date(tm ? tm * 1000 : 0); return date.toLocaleString();},"title":lang.received,"style":{"width":"170px"}},
-        {"name":"action","filterable": false,"sortable": false,"style":{"text-align":"right"},"style":{"width":"205px"},"type":"html","title":lang.action,"breakpoints":"xs sm"}
+        {"name":"action","filterable": false,"sortable": false,"style":{"text-align":"right"},"style":{"width":"220px"},"type":"html","title":lang.action,"breakpoints":"xs sm"}
       ],
       ],
       "rows": $.ajax({
       "rows": $.ajax({
         dataType: 'json',
         dataType: 'json',
@@ -81,4 +81,4 @@ jQuery(function($){
   }
   }
   // Initial table drawings
   // Initial table drawings
   draw_quarantaine_table();
   draw_quarantaine_table();
-});
+});

+ 11 - 0
data/web/lang/lang.de.php

@@ -101,6 +101,7 @@ $lang['danger']['spam_alias_max_exceeded'] = 'Maximale Anzahl an Spam-Alias-Adre
 $lang['danger']['validity_missing'] = 'Bitte geben Sie eine Gültigkeitsdauer an';
 $lang['danger']['validity_missing'] = 'Bitte geben Sie eine Gültigkeitsdauer an';
 $lang['user']['loading'] = "Lade...";
 $lang['user']['loading'] = "Lade...";
 $lang['user']['active_sieve'] = "Aktiver Filter";
 $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";
 $lang['user']['no_active_filter'] = "Kein aktiver Filter vorhanden";
 $lang['user']['on'] = 'Ein';
 $lang['user']['on'] = 'Ein';
 $lang['user']['off'] = 'Aus';
 $lang['user']['off'] = 'Aus';
@@ -122,7 +123,9 @@ $lang['user']['spam_aliases'] = 'Temporäre E-Mail Aliasse';
 $lang['user']['alias'] = 'Alias';
 $lang['user']['alias'] = 'Alias';
 $lang['user']['aliases'] = 'Aliasse';
 $lang['user']['aliases'] = 'Aliasse';
 $lang['user']['shared_aliases'] = 'Geteilte Alias-Adressen';
 $lang['user']['shared_aliases'] = 'Geteilte Alias-Adressen';
+$lang['user']['shared_aliases_desc'] = 'Geteilte Alias-Adressen werden nicht bei benutzerdefinierten Einstellungen wie die des Spam-Filters oder der Verschlüsselungsrichtlinie berücksichtigt. Entsprechende Spam-Filter können lediglich von einem Administrator vorgenommen werden.';
 $lang['user']['direct_aliases'] = 'Direkte Alias-Adressen';
 $lang['user']['direct_aliases'] = 'Direkte Alias-Adressen';
+$lang['user']['direct_aliases_desc'] = 'Nur direkte Alias-Adressen werden für benutzerdefinierte Einstellungen berücksichtigt.';
 $lang['user']['domain_aliases'] = 'Domain-Alias Adressen';
 $lang['user']['domain_aliases'] = 'Domain-Alias Adressen';
 $lang['user']['is_catch_all'] = 'Ist Catch-All Adresse für Domain(s)';
 $lang['user']['is_catch_all'] = 'Ist Catch-All Adresse für Domain(s)';
 $lang['user']['aliases_also_send_as'] = 'Darf außerdem versenden als Benutzer';
 $lang['user']['aliases_also_send_as'] = 'Darf außerdem versenden als Benutzer';
@@ -305,6 +308,12 @@ $lang['edit']['encryption'] = 'Verschlüsselung';
 $lang['edit']['maxage'] = 'Maximales Alter in Tagen einer Nachricht, die kopiert werden soll</br ><small>(0 = alle Nachrichten kopieren)</small>';
 $lang['edit']['maxage'] = 'Maximales Alter in Tagen einer Nachricht, die kopiert werden soll</br ><small>(0 = alle Nachrichten kopieren)</small>';
 $lang['edit']['subfolder2'] = 'Ziel-Ordner<br><small>(leer = kein Unterordner)</small>';
 $lang['edit']['subfolder2'] = 'Ziel-Ordner<br><small>(leer = kein Unterordner)</small>';
 $lang['edit']['mins_interval'] = 'Intervall (min)';
 $lang['edit']['mins_interval'] = 'Intervall (min)';
+$lang['edit']['maxbytespersecond'] = 'Max. Übertragungsrate in Bytes/s (0 für unlimitiert)';
+$lang['edit']['automap'] = 'Ordner automatisch mappen ("Sent items", "Sent" => "Sent" etc.)';
+$lang['edit']['skipcrossduplicates'] = 'Duplikate auch über Ordner hinweg überspringen ("first come, first serve")';
+$lang['add']['maxbytespersecond'] = 'Max. Übertragungsrate in Bytes/s (0 für unlimitiert)';
+$lang['add']['automap'] = 'Ordner automatisch mappen ("Sent items", "Sent" => "Sent" etc.)';
+$lang['add']['skipcrossduplicates'] = 'Duplikate auch über Ordner hinweg überspringen ("first come, first serve")';
 $lang['edit']['exclude'] = 'Elemente ausschließen (Regex)';
 $lang['edit']['exclude'] = 'Elemente ausschließen (Regex)';
 $lang['edit']['archive'] = 'Archiv-Zugriff';
 $lang['edit']['archive'] = 'Archiv-Zugriff';
 $lang['edit']['max_mailboxes'] = 'Max. Mailboxanzahl:';
 $lang['edit']['max_mailboxes'] = 'Max. Mailboxanzahl:';
@@ -463,6 +472,8 @@ $lang['admin']['f2b_parameters'] = 'Fail2ban Parameter';
 $lang['admin']['f2b_ban_time'] = 'Banzeit (s)';
 $lang['admin']['f2b_ban_time'] = 'Banzeit (s)';
 $lang['admin']['f2b_max_attempts'] = 'Max. Versuche';
 $lang['admin']['f2b_max_attempts'] = 'Max. Versuche';
 $lang['admin']['f2b_retry_window'] = 'Wiederholungen im Zeitraum von (s)';
 $lang['admin']['f2b_retry_window'] = 'Wiederholungen im Zeitraum von (s)';
+$lang['admin']['f2b_netban_ipv4'] = 'Netzbereich für IPv4 Bans (8-32)';
+$lang['admin']['f2b_netban_ipv6'] = 'Netzbereich für IPv6 Bans (8-128)';
 $lang['admin']['f2b_whitelist'] = 'Whitelist für Netzwerke und Hosts';
 $lang['admin']['f2b_whitelist'] = 'Whitelist für Netzwerke und Hosts';
 $lang['admin']['restrictions'] = 'Postfix Restriktionen';
 $lang['admin']['restrictions'] = 'Postfix Restriktionen';
 $lang['admin']['rr'] = 'Postfix Empfänger Restriktionen';
 $lang['admin']['rr'] = 'Postfix Empfänger Restriktionen';

+ 11 - 0
data/web/lang/lang.en.php

@@ -101,6 +101,7 @@ $lang['danger']['spam_alias_max_exceeded'] = "Max. allowed spam alias addresses
 $lang['danger']['validity_missing'] = 'Please assign a period of validity';
 $lang['danger']['validity_missing'] = 'Please assign a period of validity';
 $lang['user']['loading'] = "Loading...";
 $lang['user']['loading'] = "Loading...";
 $lang['user']['active_sieve'] = "Active filter";
 $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";
 $lang['user']['no_active_filter'] = "No active filter available";
 $lang['user']['on'] = "On";
 $lang['user']['on'] = "On";
 $lang['user']['off'] = "Off";
 $lang['user']['off'] = "Off";
@@ -122,7 +123,9 @@ $lang['user']['spam_aliases'] = 'Temporary email aliases';
 $lang['user']['alias'] = 'Alias';
 $lang['user']['alias'] = 'Alias';
 $lang['user']['aliases'] = 'Aliases';
 $lang['user']['aliases'] = 'Aliases';
 $lang['user']['shared_aliases'] = 'Shared alias addresses';
 $lang['user']['shared_aliases'] = 'Shared alias addresses';
+$lang['user']['shared_aliases_desc'] = 'A shared alias address is not affected by any user specific settings. A custom spam filter setting can be archived by a domain-wide policy set by an administrator..';
 $lang['user']['direct_aliases'] = 'Direct alias addresses';
 $lang['user']['direct_aliases'] = 'Direct alias addresses';
+$lang['user']['direct_aliases_desc'] = 'Direct alias addresses are affected by spam filter and TLS policy settings.';
 $lang['user']['domain_aliases'] = 'Domain alias addresses';
 $lang['user']['domain_aliases'] = 'Domain alias addresses';
 $lang['user']['is_catch_all'] = 'Catch-all for domain/s';
 $lang['user']['is_catch_all'] = 'Catch-all for domain/s';
 $lang['user']['aliases_also_send_as'] = 'Also allowed to send as user';
 $lang['user']['aliases_also_send_as'] = 'Also allowed to send as user';
@@ -303,6 +306,12 @@ $lang['edit']['username'] = 'Username';
 $lang['edit']['hostname'] = 'Hostname';
 $lang['edit']['hostname'] = 'Hostname';
 $lang['edit']['encryption'] = 'Encryption';
 $lang['edit']['encryption'] = 'Encryption';
 $lang['edit']['maxage'] = 'Maximum age of messages in days that will be polled from remote<br><small>(0 = ignore age)</small>';
 $lang['edit']['maxage'] = 'Maximum age of messages in days that will be polled from remote<br><small>(0 = ignore age)</small>';
+$lang['edit']['maxbytespersecond'] = 'Max. bytes per second (0 equals to unlimited)';
+$lang['edit']['automap'] = 'Try to automap folders ("Sent items", "Sent" => "Sent" etc.)';
+$lang['edit']['skipcrossduplicates'] = 'Skip duplicate messages across folders (first come, first serve)';
+$lang['add']['maxbytespersecond'] = 'Max. bytes per second (0 equals to unlimited)';
+$lang['add']['automap'] = 'Try to automap folders ("Sent items", "Sent" => "Sent" etc.)';
+$lang['add']['skipcrossduplicates'] = 'Skip duplicate messages across folders (first come, first serve)';
 $lang['edit']['subfolder2'] = 'Sync into subfolder on destination<br><small>(empty = do not use subfolder)</small>';
 $lang['edit']['subfolder2'] = 'Sync into subfolder on destination<br><small>(empty = do not use subfolder)</small>';
 $lang['edit']['mins_interval'] = 'Interval (min)';
 $lang['edit']['mins_interval'] = 'Interval (min)';
 $lang['edit']['exclude'] = 'Exclude objects (regex)';
 $lang['edit']['exclude'] = 'Exclude objects (regex)';
@@ -463,6 +472,8 @@ $lang['admin']['f2b_parameters'] = 'Fail2ban parameters';
 $lang['admin']['f2b_ban_time'] = 'Ban time (s)';
 $lang['admin']['f2b_ban_time'] = 'Ban time (s)';
 $lang['admin']['f2b_max_attempts'] = 'Max. attempts';
 $lang['admin']['f2b_max_attempts'] = 'Max. attempts';
 $lang['admin']['f2b_retry_window'] = 'Retry window (s) for max. attempts';
 $lang['admin']['f2b_retry_window'] = 'Retry window (s) for max. attempts';
+$lang['admin']['f2b_netban_ipv4'] = 'IPv4 subnet size to apply ban on (8-32)';
+$lang['admin']['f2b_netban_ipv6'] = 'IPv6 subnet size to apply ban on (8-128)';
 $lang['admin']['f2b_whitelist'] = 'Whitelisted networks/hosts';
 $lang['admin']['f2b_whitelist'] = 'Whitelisted networks/hosts';
 $lang['admin']['search_domain_da'] = 'Search domains';
 $lang['admin']['search_domain_da'] = 'Search domains';
 $lang['admin']['restrictions'] = 'Postfix Restrictions';
 $lang['admin']['restrictions'] = 'Postfix Restrictions';

+ 21 - 0
data/web/modals/mailbox.php

@@ -391,6 +391,13 @@ if (!isset($_SESSION['mailcow_cc_role'])) {
             <small class="help-block">0-32000</small>
             <small class="help-block">0-32000</small>
 						</div>
 						</div>
 					</div>
 					</div>
+					<div class="form-group">
+						<label class="control-label col-sm-2" for="maxbytespersecond"><?=$lang['edit']['maxbytespersecond'];?></label>
+						<div class="col-sm-10">
+						<input type="number" class="form-control" name="maxbytespersecond" id="maxbytespersecond" min="0" max="125000000" value="0">
+            <small class="help-block">0-125000000</small>
+						</div>
+					</div>
 					<div class="form-group">
 					<div class="form-group">
 						<label class="control-label col-sm-2" for="exclude"><?=$lang['add']['exclude'];?></label>
 						<label class="control-label col-sm-2" for="exclude"><?=$lang['add']['exclude'];?></label>
 						<div class="col-sm-10">
 						<div class="col-sm-10">
@@ -418,6 +425,20 @@ if (!isset($_SESSION['mailcow_cc_role'])) {
 							</div>
 							</div>
 						</div>
 						</div>
 					</div>
 					</div>
+          <div class="form-group">
+						<div class="col-sm-offset-2 col-sm-10">
+							<div class="checkbox">
+							<label><input type="checkbox" value="1" name="automap"> <?=$lang['add']['automap'];?></label>
+							</div>
+						</div>
+					</div>
+          <div class="form-group">
+						<div class="col-sm-offset-2 col-sm-10">
+							<div class="checkbox">
+							<label><input type="checkbox" value="1" name="skipcrossduplicates"> <?=$lang['add']['skipcrossduplicates'];?></label>
+							</div>
+						</div>
+					</div>
 					<div class="form-group">
 					<div class="form-group">
 						<div class="col-sm-offset-2 col-sm-10">
 						<div class="col-sm-offset-2 col-sm-10">
 							<div class="checkbox">
 							<div class="checkbox">

+ 21 - 0
data/web/modals/user.php

@@ -70,6 +70,13 @@ if (!isset($_SESSION['mailcow_cc_role'])) {
             <small class="help-block">0-32000</small>
             <small class="help-block">0-32000</small>
 						</div>
 						</div>
 					</div>
 					</div>
+					<div class="form-group">
+						<label class="control-label col-sm-2" for="maxbytespersecond"><?=$lang['edit']['maxbytespersecond'];?></label>
+						<div class="col-sm-10">
+						<input type="number" class="form-control" name="maxbytespersecond" id="maxbytespersecond" min="0" max="125000000" value="0">
+            <small class="help-block">0-125000000</small>
+						</div>
+					</div>
 					<div class="form-group">
 					<div class="form-group">
 						<label class="control-label col-sm-2" for="exclude"><?=$lang['add']['exclude'];?></label>
 						<label class="control-label col-sm-2" for="exclude"><?=$lang['add']['exclude'];?></label>
 						<div class="col-sm-10">
 						<div class="col-sm-10">
@@ -97,6 +104,20 @@ if (!isset($_SESSION['mailcow_cc_role'])) {
 							</div>
 							</div>
 						</div>
 						</div>
 					</div>
 					</div>
+          <div class="form-group">
+						<div class="col-sm-offset-2 col-sm-10">
+							<div class="checkbox">
+							<label><input type="checkbox" value="1" name="automap"> <?=$lang['add']['automap'];?></label>
+							</div>
+						</div>
+					</div>
+          <div class="form-group">
+						<div class="col-sm-offset-2 col-sm-10">
+							<div class="checkbox">
+							<label><input type="checkbox" value="1" name="skipcrossduplicates"> <?=$lang['add']['skipcrossduplicates'];?></label>
+							</div>
+						</div>
+					</div>
 					<div class="form-group">
 					<div class="form-group">
 						<div class="col-sm-offset-2 col-sm-10">
 						<div class="col-sm-offset-2 col-sm-10">
 							<div class="checkbox">
 							<div class="checkbox">

+ 7 - 9
data/web/user.php

@@ -97,7 +97,7 @@ elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == '
   <div class="row">
   <div class="row">
     <div class="col-md-3 col-xs-5 text-right">  <span class="glyphicon glyphicon-filter"></span></div>
     <div class="col-md-3 col-xs-5 text-right">  <span class="glyphicon glyphicon-filter"></span></div>
     <div class="col-md-9 col-xs-7">
     <div class="col-md-9 col-xs-7">
-    <p><a href="#userFilterModal" data-toggle="modal">[Show active user sieve filter]</a></p>
+    <p><a href="#userFilterModal" data-toggle="modal">[<?=$lang['user']['show_sieve_filters'];?>]</a></p>
     </div>
     </div>
   </div>
   </div>
   <hr>
   <hr>
@@ -105,24 +105,22 @@ elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == '
   $user_get_alias_details = user_get_alias_details($username);
   $user_get_alias_details = user_get_alias_details($username);
   ?>
   ?>
   <div class="row">
   <div class="row">
-    <div class="col-md-3 col-xs-5 text-right"><?=$lang['user']['direct_aliases'];?>:</div>
+    <div class="col-md-3 col-xs-5 text-right"><?=$lang['user']['direct_aliases'];?>:
+      <p class="small"><?=$lang['user']['direct_aliases_desc'];?></p>
+    </div>
     <div class="col-md-9 col-xs-7">
     <div class="col-md-9 col-xs-7">
     <p><?=$user_get_alias_details['direct_aliases'];?></p>
     <p><?=$user_get_alias_details['direct_aliases'];?></p>
     </div>
     </div>
   </div>
   </div>
   <div class="row">
   <div class="row">
-    <div class="col-md-3 col-xs-5 text-right"><?=$lang['user']['shared_aliases'];?>:</div>
+    <div class="col-md-3 col-xs-5 text-right"><?=$lang['user']['shared_aliases'];?>:
+      <p class="small"><?=$lang['user']['shared_aliases_desc'];?></p>
+    </div>
     <div class="col-md-9 col-xs-7">
     <div class="col-md-9 col-xs-7">
     <p><?=$user_get_alias_details['shared_aliases'];?></p>
     <p><?=$user_get_alias_details['shared_aliases'];?></p>
     </div>
     </div>
   </div>
   </div>
   <hr>
   <hr>
-  <div class="row">
-    <div class="col-md-3 col-xs-5 text-right"><?=$lang['user']['domain_aliases'];?>:</div>
-    <div class="col-md-9 col-xs-7">
-    <p><?=$user_get_alias_details['ad_alias'];?></p>
-    </div>
-  </div>
   <div class="row">
   <div class="row">
     <div class="col-md-3 col-xs-5 text-right"><?=$lang['user']['aliases_also_send_as'];?>:</div>
     <div class="col-md-3 col-xs-5 text-right"><?=$lang['user']['aliases_also_send_as'];?>:</div>
     <div class="col-md-9 col-xs-7">
     <div class="col-md-9 col-xs-7">

+ 33 - 30
docker-compose.yml

@@ -12,7 +12,7 @@ services:
       restart: always
       restart: always
       networks:
       networks:
         mailcow-network:
         mailcow-network:
-          ipv4_address: 172.22.1.254
+          ipv4_address: ${IPV4_NETWORK}.254
           aliases:
           aliases:
             - unbound
             - unbound
 
 
@@ -29,10 +29,9 @@ services:
         - MYSQL_PASSWORD=${DBPASS}
         - MYSQL_PASSWORD=${DBPASS}
       restart: always
       restart: always
       dns:
       dns:
-        - 172.22.1.254
+        - ${IPV4_NETWORK}.254
       networks:
       networks:
         mailcow-network:
         mailcow-network:
-          ipv4_address: 172.22.1.250
           aliases:
           aliases:
             - mysql
             - mysql
 
 
@@ -44,29 +43,31 @@ services:
       environment:
       environment:
         - TZ=${TZ}
         - TZ=${TZ}
       dns:
       dns:
-        - 172.22.1.254
+        - ${IPV4_NETWORK}.254
       networks:
       networks:
         mailcow-network:
         mailcow-network:
-          ipv4_address: 172.22.1.249
+          ipv4_address: ${IPV4_NETWORK}.249
           aliases:
           aliases:
             - redis
             - redis
 
 
     clamd-mailcow:
     clamd-mailcow:
-      image: mailcow/clamd:1.6
+      image: mailcow/clamd:1.8
       build: ./data/Dockerfiles/clamd
       build: ./data/Dockerfiles/clamd
       restart: always
       restart: always
       environment:
       environment:
-        - SKIP_CLAMD=${SKIP_CLAMD:-n}
         - TZ=${TZ}
         - TZ=${TZ}
+        - SKIP_CLAMD=${SKIP_CLAMD:-n}
+      volumes:
+        - ./data/conf/clamav/:/etc/clamav/
       dns:
       dns:
-        - 172.22.1.254
+        - ${IPV4_NETWORK}.254
       networks:
       networks:
         mailcow-network:
         mailcow-network:
           aliases:
           aliases:
             - clamd
             - clamd
 
 
     rspamd-mailcow:
     rspamd-mailcow:
-      image: mailcow/rspamd:1.15
+      image: mailcow/rspamd:1.16
       build: ./data/Dockerfiles/rspamd
       build: ./data/Dockerfiles/rspamd
       stop_grace_period: 30s
       stop_grace_period: 30s
       depends_on:
       depends_on:
@@ -82,16 +83,15 @@ services:
         - rspamd-vol-1:/var/lib/rspamd
         - rspamd-vol-1:/var/lib/rspamd
       restart: always
       restart: always
       dns:
       dns:
-        - 172.22.1.254
+        - ${IPV4_NETWORK}.254
       hostname: rspamd
       hostname: rspamd
       networks:
       networks:
         mailcow-network:
         mailcow-network:
-          ipv4_address: 172.22.1.253
           aliases:
           aliases:
             - rspamd
             - rspamd
 
 
     php-fpm-mailcow:
     php-fpm-mailcow:
-      image: mailcow/phpfpm:1.8
+      image: mailcow/phpfpm:1.9
       build: ./data/Dockerfiles/phpfpm
       build: ./data/Dockerfiles/phpfpm
       command: "php-fpm -d date.timezone=${TZ} -d expose_php=0"
       command: "php-fpm -d date.timezone=${TZ} -d expose_php=0"
       depends_on:
       depends_on:
@@ -101,6 +101,9 @@ services:
         - ./data/conf/rspamd/dynmaps:/dynmaps:ro
         - ./data/conf/rspamd/dynmaps:/dynmaps:ro
         - dkim-vol-1:/data/dkim
         - dkim-vol-1:/data/dkim
         - ./data/conf/rspamd/meta_exporter:/meta_exporter:ro
         - ./data/conf/rspamd/meta_exporter:/meta_exporter:ro
+        - ./data/conf/phpfpm/php-fpm.d/www.conf:/usr/local/etc/php-fpm.d/www.conf
+        - ./data/conf/phpfpm/php-fpm.d/system.conf:/usr/local/etc/php-fpm.d/system.conf
+        - ./data/conf/phpfpm/php-conf.d/opcache-recommended.ini:/usr/local/etc/php/conf.d/opcache-recommended.ini
       environment:
       environment:
         - LOG_LINES=${LOG_LINES}
         - LOG_LINES=${LOG_LINES}
         - TZ=${TZ}
         - TZ=${TZ}
@@ -118,14 +121,14 @@ services:
         - SMTP_PORT=${SMTP_PORT:-25}
         - SMTP_PORT=${SMTP_PORT:-25}
       restart: always
       restart: always
       dns:
       dns:
-        - 172.22.1.254
+        - ${IPV4_NETWORK}.254
       networks:
       networks:
         mailcow-network:
         mailcow-network:
           aliases:
           aliases:
             - phpfpm
             - phpfpm
 
 
     sogo-mailcow:
     sogo-mailcow:
-      image: mailcow/sogo:1.13
+      image: mailcow/sogo:1.14
       build: ./data/Dockerfiles/sogo
       build: ./data/Dockerfiles/sogo
       environment:
       environment:
         - DBNAME=${DBNAME}
         - DBNAME=${DBNAME}
@@ -138,15 +141,14 @@ services:
         - ./data/conf/sogo/:/etc/sogo/
         - ./data/conf/sogo/:/etc/sogo/
       restart: always
       restart: always
       dns:
       dns:
-        - 172.22.1.254
+        - ${IPV4_NETWORK}.254
       networks:
       networks:
         mailcow-network:
         mailcow-network:
-          ipv4_address: 172.22.1.252
           aliases:
           aliases:
             - sogo
             - sogo
 
 
     dovecot-mailcow:
     dovecot-mailcow:
-      image: mailcow/dovecot:1.17
+      image: mailcow/dovecot:1.18
       build: ./data/Dockerfiles/dovecot
       build: ./data/Dockerfiles/dovecot
       cap_add:
       cap_add:
         - NET_BIND_SERVICE
         - NET_BIND_SERVICE
@@ -176,7 +178,7 @@ services:
           soft: 20000
           soft: 20000
           hard: 40000
           hard: 40000
       dns:
       dns:
-        - 172.22.1.254
+        - ${IPV4_NETWORK}.254
       hostname: ${MAILCOW_HOSTNAME}
       hostname: ${MAILCOW_HOSTNAME}
       networks:
       networks:
         mailcow-network:
         mailcow-network:
@@ -197,13 +199,15 @@ services:
         - DBNAME=${DBNAME}
         - DBNAME=${DBNAME}
         - DBUSER=${DBUSER}
         - DBUSER=${DBUSER}
         - DBPASS=${DBPASS}
         - DBPASS=${DBPASS}
+      cap_add:
+        - NET_BIND_SERVICE
       ports:
       ports:
         - "${SMTP_PORT:-25}:25"
         - "${SMTP_PORT:-25}:25"
         - "${SMTPS_PORT:-465}:465"
         - "${SMTPS_PORT:-465}:465"
         - "${SUBMISSION_PORT:-587}:587"
         - "${SUBMISSION_PORT:-587}:587"
       restart: always
       restart: always
       dns:
       dns:
-        - 172.22.1.254
+        - ${IPV4_NETWORK}.254
       hostname: ${MAILCOW_HOSTNAME}
       hostname: ${MAILCOW_HOSTNAME}
       networks:
       networks:
         mailcow-network:
         mailcow-network:
@@ -214,7 +218,7 @@ services:
       image: memcached:alpine
       image: memcached:alpine
       restart: always
       restart: always
       dns:
       dns:
-        - 172.22.1.254
+        - ${IPV4_NETWORK}.254
       networks:
       networks:
         mailcow-network:
         mailcow-network:
           aliases:
           aliases:
@@ -249,10 +253,9 @@ services:
         - "${HTTP_BIND:-0.0.0.0}:${HTTP_PORT:-80}:${HTTP_PORT:-80}"
         - "${HTTP_BIND:-0.0.0.0}:${HTTP_PORT:-80}:${HTTP_PORT:-80}"
       restart: always
       restart: always
       dns:
       dns:
-        - 172.22.1.254
+        - ${IPV4_NETWORK}.254
       networks:
       networks:
         mailcow-network:
         mailcow-network:
-          ipv4_address: 172.22.1.251
           aliases:
           aliases:
             - nginx
             - nginx
 
 
@@ -263,7 +266,7 @@ services:
       image: mailcow/acme:1.28
       image: mailcow/acme:1.28
       build: ./data/Dockerfiles/acme
       build: ./data/Dockerfiles/acme
       dns:
       dns:
-        - 172.22.1.254
+        - ${IPV4_NETWORK}.254
       environment:
       environment:
         - LOG_LINES=${LOG_LINES}
         - LOG_LINES=${LOG_LINES}
         - ADDITIONAL_SAN=${ADDITIONAL_SAN}
         - ADDITIONAL_SAN=${ADDITIONAL_SAN}
@@ -284,7 +287,7 @@ services:
             - acme
             - acme
 
 
     fail2ban-mailcow:
     fail2ban-mailcow:
-      image: mailcow/fail2ban:1.10
+      image: mailcow/fail2ban:1.11
       build: ./data/Dockerfiles/fail2ban
       build: ./data/Dockerfiles/fail2ban
       stop_grace_period: 30s
       stop_grace_period: 30s
       depends_on:
       depends_on:
@@ -298,16 +301,17 @@ services:
       environment:
       environment:
         - TZ=${TZ}
         - TZ=${TZ}
         - SKIP_FAIL2BAN=${SKIP_FAIL2BAN:-n}
         - SKIP_FAIL2BAN=${SKIP_FAIL2BAN:-n}
+        - IPV4_NETWORK=${IPV4_NETWORK}
       network_mode: "host"
       network_mode: "host"
       dns:
       dns:
-        - 172.22.1.254
+        - ${IPV4_NETWORK}.254
       volumes:
       volumes:
         - /lib/modules:/lib/modules:ro
         - /lib/modules:/lib/modules:ro
 
 
     watchdog-mailcow:
     watchdog-mailcow:
-      image: mailcow/watchdog:1.12
+      image: mailcow/watchdog:1.13
       # Debug
       # Debug
-      #command: /watchdog.sh
+      command: /watchdog.sh
       build: ./data/Dockerfiles/watchdog
       build: ./data/Dockerfiles/watchdog
       volumes:
       volumes:
         - vmail-vol-1:/vmail:ro
         - vmail-vol-1:/vmail:ro
@@ -323,7 +327,6 @@ services:
         - MAILCOW_HOSTNAME=${MAILCOW_HOSTNAME}
         - MAILCOW_HOSTNAME=${MAILCOW_HOSTNAME}
       networks:
       networks:
         mailcow-network:
         mailcow-network:
-          ipv4_address: 172.22.1.248
           aliases:
           aliases:
             - watchdog
             - watchdog
 
 
@@ -358,8 +361,8 @@ networks:
     ipam:
     ipam:
       driver: default
       driver: default
       config:
       config:
-        - subnet: 172.22.1.0/24
-        - subnet: fd4d:6169:6c63:6f77::/64
+        - subnet: ${IPV4_NETWORK}.0/24
+        - subnet: ${IPV6_NETWORK}
 
 
 volumes:
 volumes:
   vmail-vol-1:
   vmail-vol-1:

+ 8 - 0
generate_config.sh

@@ -101,8 +101,16 @@ USE_WATCHDOG=n
 # Send notifications by mail (no DKIM signature, sent from watchdog@MAILCOW_HOSTNAME)
 # Send notifications by mail (no DKIM signature, sent from watchdog@MAILCOW_HOSTNAME)
 #WATCHDOG_NOTIFY_EMAIL=
 #WATCHDOG_NOTIFY_EMAIL=
 
 
+# Max log lines per service to keep in Redis logs
 LOG_LINES=9999
 LOG_LINES=9999
 
 
+# Internal IPv4 /24 subnet, format n.n.n. (expands to n.n.n.0/24)
+IPV4_NETWORK=172.22.1
+
+# Internal IPv6 subnet in fd00::/8
+IPV6_NETWORK=fd4d:6169:6c63:6f77::/64
+
+
 EOF
 EOF
 
 
 mkdir -p data/assets/ssl
 mkdir -p data/assets/ssl

+ 6 - 3
helper-scripts/nextcloud.sh

@@ -79,10 +79,12 @@ elif [[ ${NC_INSTALL} == "y" ]]; then
 	  /web/nextcloud/occ config:system:set redis port --value=6379 --type=integer; \
 	  /web/nextcloud/occ config:system:set redis port --value=6379 --type=integer; \
 	  /web/nextcloud/occ config:system:set memcache.locking --value='\OC\Memcache\Redis' --type=string; \
 	  /web/nextcloud/occ config:system:set memcache.locking --value='\OC\Memcache\Redis' --type=string; \
 	  /web/nextcloud/occ config:system:set memcache.local --value='\OC\Memcache\Redis' --type=string; \
 	  /web/nextcloud/occ config:system:set memcache.local --value='\OC\Memcache\Redis' --type=string; \
-	  /web/nextcloud/occ config:system:set trusted_proxies 0 --value=fd4d:6169:6c63:6f77::1; \
-	  /web/nextcloud/occ config:system:set trusted_proxies 1 --value=172.22.1.0/24; \
+	  /web/nextcloud/occ config:system:set trusted_domains 1 --value=${MAILCOW_HOSTNAME}; \
+    /web/nextcloud/occ config:system:set trusted_proxies 0 --value=${IPV6_NETWORK}; \
+	  /web/nextcloud/occ config:system:set trusted_proxies 1 --value=${IPV4_NETWORK}.0/24; \
 	  /web/nextcloud/occ config:system:set overwritewebroot --value=/nextcloud; \
 	  /web/nextcloud/occ config:system:set overwritewebroot --value=/nextcloud; \
 	  /web/nextcloud/occ config:system:set overwritehost --value=${MAILCOW_HOSTNAME}; \
 	  /web/nextcloud/occ config:system:set overwritehost --value=${MAILCOW_HOSTNAME}; \
+	  /web/nextcloud/occ config:system:set overwriteprotocol --value=https; \
 	  /web/nextcloud/occ config:system:set mail_smtpmode --value=smtp; \
 	  /web/nextcloud/occ config:system:set mail_smtpmode --value=smtp; \
 	  /web/nextcloud/occ config:system:set mail_smtpauthtype --value=LOGIN; \
 	  /web/nextcloud/occ config:system:set mail_smtpauthtype --value=LOGIN; \
 	  /web/nextcloud/occ config:system:set mail_from_address --value=nextcloud; \
 	  /web/nextcloud/occ config:system:set mail_from_address --value=nextcloud; \
@@ -94,10 +96,11 @@ elif [[ ${NC_INSTALL} == "y" ]]; then
 	  /web/nextcloud/occ config:system:set user_backends 0 class --value=OC_User_IMAP"
 	  /web/nextcloud/occ config:system:set user_backends 0 class --value=OC_User_IMAP"
 
 
 	if [[ ${NC_TYPE} == "subdomain" ]]; then
 	if [[ ${NC_TYPE} == "subdomain" ]]; then
+		docker exec -it -u www-data $(docker ps -f name=php-fpm-mailcow -q) /web/nextcloud/occ config:system:set trusted_domains 1 --value=${NC_SUBD}
 		docker exec -it -u www-data $(docker ps -f name=php-fpm-mailcow -q) /web/nextcloud/occ config:system:set overwritewebroot --value=/
 		docker exec -it -u www-data $(docker ps -f name=php-fpm-mailcow -q) /web/nextcloud/occ config:system:set overwritewebroot --value=/
 		docker exec -it -u www-data $(docker ps -f name=php-fpm-mailcow -q) /web/nextcloud/occ config:system:set overwritehost --value=${NC_SUBD}
 		docker exec -it -u www-data $(docker ps -f name=php-fpm-mailcow -q) /web/nextcloud/occ config:system:set overwritehost --value=${NC_SUBD}
 		cp ./data/assets/nextcloud/nextcloud.conf ./data/conf/nginx/
 		cp ./data/assets/nextcloud/nextcloud.conf ./data/conf/nginx/
-		sed -i 's/NC_SUBD/${NC_SUBD}/g' ./data/conf/nginx/nextcloud.conf
+		sed -i "s/NC_SUBD/${NC_SUBD}/g" ./data/conf/nginx/nextcloud.conf
 	elif [[ ${NC_TYPE} == "subfolder" ]]; then
 	elif [[ ${NC_TYPE} == "subfolder" ]]; then
 		cp ./data/assets/nextcloud/site.nextcloud.custom ./data/conf/nginx/
 		cp ./data/assets/nextcloud/site.nextcloud.custom ./data/conf/nginx/
 	fi
 	fi

+ 124 - 106
update.sh

@@ -1,72 +1,91 @@
 #!/bin/bash
 #!/bin/bash
 
 
 for bin in curl docker-compose docker git awk sha1sum; do
 for bin in curl docker-compose docker git awk sha1sum; do
-	if [[ -z $(which ${bin}) ]]; then echo "Cannot find ${bin}, exiting..."; exit 1; fi
+  if [[ -z $(which ${bin}) ]]; then echo "Cannot find ${bin}, exiting..."; exit 1; fi
 done
 done
 
 
 [[ ! -f mailcow.conf ]] && { echo "mailcow.conf is missing"; exit 1;}
 [[ ! -f mailcow.conf ]] && { echo "mailcow.conf is missing"; exit 1;}
 
 
-CONFIG_ARRAY=("SKIP_LETS_ENCRYPT" "USE_WATCHDOG" "WATCHDOG_NOTIFY_EMAIL" "SKIP_CLAMD" "SKIP_IP_CHECK" "SKIP_FAIL2BAN" "ADDITIONAL_SAN" "DOVEADM_PORT")
+CONFIG_ARRAY=("SKIP_LETS_ENCRYPT" "USE_WATCHDOG" "WATCHDOG_NOTIFY_EMAIL" "SKIP_CLAMD" "SKIP_IP_CHECK" "SKIP_FAIL2BAN" "ADDITIONAL_SAN" "DOVEADM_PORT" "IPV4_NETWORK" "IPV6_NETWORK" "LOG_LINES")
 sed -i '$a\' mailcow.conf
 sed -i '$a\' mailcow.conf
 for option in ${CONFIG_ARRAY[@]}; do
 for option in ${CONFIG_ARRAY[@]}; do
-	if [[ ${option} == "ADDITIONAL_SAN" ]]; then
-		if ! grep -q ${option} mailcow.conf; then
-			echo "Adding new option \"${option}\" to mailcow.conf"
-			echo "${option}=" >> mailcow.conf
-		fi
-	elif [[ ${option} == "COMPOSE_PROJECT_NAME" ]]; then
-		if ! grep -q ${option} mailcow.conf; then
-			echo "Adding new option \"${option}\" to mailcow.conf"
-			echo "COMPOSE_PROJECT_NAME=mailcow-dockerized" >> mailcow.conf
-		fi
-	elif [[ ${option} == "DOVEADM_PORT" ]]; then
-		if ! grep -q ${option} mailcow.conf; then
-			echo "Adding new option \"${option}\" to mailcow.conf"
-			echo "DOVEADM_PORT=127.0.0.1:19991" >> mailcow.conf
-		fi
-	elif [[ ${option} == "WATCHDOG_NOTIFY_EMAIL" ]]; then
-		if ! grep -q ${option} mailcow.conf; then
-			echo "Adding new option \"${option}\" to mailcow.conf"
-			echo "WATCHDOG_NOTIFY_EMAIL=" >> mailcow.conf
-		fi
+  if [[ ${option} == "ADDITIONAL_SAN" ]]; then
+    if ! grep -q ${option} mailcow.conf; then
+      echo "Adding new option \"${option}\" to mailcow.conf"
+      echo "${option}=" >> mailcow.conf
+    fi
+  elif [[ ${option} == "COMPOSE_PROJECT_NAME" ]]; then
+    if ! grep -q ${option} mailcow.conf; then
+      echo "Adding new option \"${option}\" to mailcow.conf"
+      echo "COMPOSE_PROJECT_NAME=mailcow-dockerized" >> mailcow.conf
+    fi
+  elif [[ ${option} == "DOVEADM_PORT" ]]; then
+    if ! grep -q ${option} mailcow.conf; then
+      echo "Adding new option \"${option}\" to mailcow.conf"
+      echo "DOVEADM_PORT=127.0.0.1:19991" >> mailcow.conf
+    fi
+  elif [[ ${option} == "WATCHDOG_NOTIFY_EMAIL" ]]; then
+    if ! grep -q ${option} mailcow.conf; then
+      echo "Adding new option \"${option}\" to mailcow.conf"
+      echo "WATCHDOG_NOTIFY_EMAIL=" >> mailcow.conf
+    fi
   elif [[ ${option} == "LOG_LINES" ]]; then
   elif [[ ${option} == "LOG_LINES" ]]; then
     if ! grep -q ${option} mailcow.conf; then
     if ! grep -q ${option} mailcow.conf; then
       echo "Adding new option \"${option}\" to mailcow.conf"
       echo "Adding new option \"${option}\" to mailcow.conf"
+      echo '# Max log lines per service to keep in Redis logs' >> mailcow.conf
       echo "LOG_LINES=9999" >> mailcow.conf
       echo "LOG_LINES=9999" >> mailcow.conf
     fi
     fi
-	elif ! grep -q ${option} mailcow.conf; then
-		echo "Adding new option \"${option}\" to mailcow.conf"
-		echo "${option}=n" >> mailcow.conf
-	fi
+  elif [[ ${option} == "IPV4_NETWORK" ]]; then
+    if ! grep -q ${option} mailcow.conf; then
+      echo "Adding new option \"${option}\" to mailcow.conf"
+      echo '# Internal IPv4 /24 subnet, format n.n.n. (expands to n.n.n.0/24)' >> mailcow.conf
+      echo "IPV4_NETWORK=172.22.1" >> mailcow.conf
+    fi
+  elif [[ ${option} == "IPV6_NETWORK" ]]; then
+    if ! grep -q ${option} mailcow.conf; then
+      echo "Adding new option \"${option}\" to mailcow.conf"
+      echo '# Internal IPv6 subnet in fd00::/8' >> mailcow.conf
+      echo "IPV6_NETWORK=fd4d:6169:6c63:6f77::/64" >> mailcow.conf
+    fi
+  elif ! grep -q ${option} mailcow.conf; then
+    echo "Adding new option \"${option}\" to mailcow.conf"
+    echo "${option}=n" >> mailcow.conf
+  fi
 done
 done
 
 
 echo -en "Checking internet connection... "
 echo -en "Checking internet connection... "
 curl -o /dev/null google.com -sm3
 curl -o /dev/null google.com -sm3
 if [[ $? != 0 ]]; then
 if [[ $? != 0 ]]; then
-	echo -e "\e[31mfailed\e[0m"
-	exit 1
-else
-	echo -e "\e[32mOK\e[0m"
+  echo -e "\e[31mfailed\e[0m"
+  exit 1
+  else
+  echo -e "\e[32mOK\e[0m"
 fi
 fi
 
 
 set -o pipefail
 set -o pipefail
 export LC_ALL=C
 export LC_ALL=C
 DATE=$(date +%Y-%m-%d_%H_%M_%S)
 DATE=$(date +%Y-%m-%d_%H_%M_%S)
 BRANCH=$(git rev-parse --abbrev-ref HEAD)
 BRANCH=$(git rev-parse --abbrev-ref HEAD)
-
-case "${1}" in
-	--check|-c)
-		echo "Checking remote code for updates..."
-		git fetch origin ${BRANCH}
-		if ! git diff origin/${BRANCH} --quiet; then
-			echo "Updated code is available."
-			exit 0
-		else
-			echo "No updates available."
-			exit 3
-		fi
-	;;
-esac
+declare -a DC_PARAMS
+
+while (($#)); do
+  case "${1}" in
+    --check|-c)
+      echo "Checking remote code for updates..."
+      git fetch origin ${BRANCH}
+      if [[ $(git branch ${BRANCH} --contains $(git rev-parse origin/${BRANCH}) > /dev/null 2> /dev/null; echo $?) != 0 ]]; then
+        echo "Updated code is available."
+        exit 0
+      else
+        echo "No updates available."
+        exit 3
+      fi
+    ;;
+    --no-start)
+      DC_PARAMS=(${DC_PARAMS[@]} "--no-start")
+    ;;
+  esac
+done
 
 
 echo -e "\e[32mChecking for newer update script...\e[0m"
 echo -e "\e[32mChecking for newer update script...\e[0m"
 SHA1_1=$(sha1sum update.sh)
 SHA1_1=$(sha1sum update.sh)
@@ -74,22 +93,22 @@ git fetch origin ${BRANCH}
 git checkout origin/${BRANCH} update.sh
 git checkout origin/${BRANCH} update.sh
 SHA1_2=$(sha1sum update.sh)
 SHA1_2=$(sha1sum update.sh)
 if [[ ${SHA1_1} != ${SHA1_2} ]]; then
 if [[ ${SHA1_1} != ${SHA1_2} ]]; then
-	echo "update.sh changed, please run this script again, exiting."
-	chmod +x update.sh
-	exit 0
+  echo "update.sh changed, please run this script again, exiting."
+  chmod +x update.sh
+  exit 0
 fi
 fi
 
 
 if [[ -f mailcow.conf ]]; then
 if [[ -f mailcow.conf ]]; then
-	source mailcow.conf
-else
-	echo -e "\e[31mNo mailcow.conf - is mailcow installed?\e[0m"
-	exit 1
+  source mailcow.conf
+  else
+  echo -e "\e[31mNo mailcow.conf - is mailcow installed?\e[0m"
+  exit 1
 fi
 fi
 
 
 read -r -p "Are you sure you want to update mailcow: dockerized? All containers will be stopped. [y/N] " response
 read -r -p "Are you sure you want to update mailcow: dockerized? All containers will be stopped. [y/N] " response
 if [[ ! "$response" =~ ^([yY][eE][sS]|[yY])+$ ]]; then
 if [[ ! "$response" =~ ^([yY][eE][sS]|[yY])+$ ]]; then
-	echo "OK, exiting."
-	exit 0
+  echo "OK, exiting."
+  exit 0
 fi
 fi
 
 
 echo -e "Stopping mailcow... "
 echo -e "Stopping mailcow... "
@@ -109,31 +128,31 @@ git merge -Xtheirs -Xpatience -m "After update on ${DATE}"
 # Need to use a variable to not pass return codes of if checks
 # Need to use a variable to not pass return codes of if checks
 MERGE_RETURN=$?
 MERGE_RETURN=$?
 if [[ ${MERGE_RETURN} == 128 ]]; then
 if [[ ${MERGE_RETURN} == 128 ]]; then
-	echo -e "\e[31m\nOh no, what happened?\n=> You most likely added files to your local mailcow instance that were now added to the official mailcow repository. Please move them to another location before updating mailcow.\e[0m"
-	exit 1
+  echo -e "\e[31m\nOh no, what happened?\n=> You most likely added files to your local mailcow instance that were now added to the official mailcow repository. Please move them to another location before updating mailcow.\e[0m"
+  exit 1
 elif [[ ${MERGE_RETURN} == 1 ]]; then
 elif [[ ${MERGE_RETURN} == 1 ]]; then
-	echo -e "\e[93mPotenial conflict, trying to fix...\e[0m"
-	git status --porcelain | grep -E "UD|DU" | awk '{print $2}' | xargs rm -v
-	git add -A
-	git commit -m "After update on ${DATE}" > /dev/null
-	git checkout .
-	echo -e "\e[32mRemoved and recreated files if necessary.\e[0m"
+  echo -e "\e[93mPotenial conflict, trying to fix...\e[0m"
+  git status --porcelain | grep -E "UD|DU" | awk '{print $2}' | xargs rm -v
+  git add -A
+  git commit -m "After update on ${DATE}" > /dev/null
+  git checkout .
+  echo -e "\e[32mRemoved and recreated files if necessary.\e[0m"
 elif [[ ${MERGE_RETURN} != 0 ]]; then
 elif [[ ${MERGE_RETURN} != 0 ]]; then
-	echo -e "\e[31m\nOh no, something went wrong. Please check the error message above.\e[0m"
-	echo
-	echo "Run docker-compose up -d to restart your stack without updates or try again after fixing the mentioned errors."
-	exit 1
+  echo -e "\e[31m\nOh no, something went wrong. Please check the error message above.\e[0m"
+  echo
+  echo "Run docker-compose up -d to restart your stack without updates or try again after fixing the mentioned errors."
+  exit 1
 fi
 fi
 
 
 
 
 echo -e "\e[32mFetching new docker-compose version...\e[0m"
 echo -e "\e[32mFetching new docker-compose version...\e[0m"
 sleep 2
 sleep 2
 if [[ $(curl -sL -w "%{http_code}" https://www.servercow.de/docker-compose/latest.php -o /dev/null) == "200" ]]; then
 if [[ $(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
-	echo -e "\e[33mCannot determine latest docker-compose version, skipping...\e[0m"
+  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
+  echo -e "\e[33mCannot determine latest docker-compose version, skipping...\e[0m"
 fi
 fi
 
 
 echo -e "\e[32mFetching new images, if any...\e[0m"
 echo -e "\e[32mFetching new images, if any...\e[0m"
@@ -146,48 +165,47 @@ cp -n data/assets/ssl-example/*.pem data/assets/ssl/
 
 
 echo -e "\e[32mStarting mailcow...\e[0m"
 echo -e "\e[32mStarting mailcow...\e[0m"
 sleep 2
 sleep 2
-docker-compose up -d --remove-orphans
+docker-compose up -d --remove-orphans ${DC_PARAMS[@]}
 
 
 echo -e "\e[32mCollecting garbage...\e[0m"
 echo -e "\e[32mCollecting garbage...\e[0m"
 IMGS_TO_DELETE=()
 IMGS_TO_DELETE=()
 for container in $(grep -oP "image: \Kmailcow.+" docker-compose.yml); do
 for container in $(grep -oP "image: \Kmailcow.+" docker-compose.yml); do
-	REPOSITORY=${container/:*}
-	TAG=${container/*:}
-	V_MAIN=${container/*.}
-	V_SUB=${container/*.}
-
-	EXISTING_TAGS=$(docker images | grep ${REPOSITORY} | awk '{ print $2 }')
-	for existing_tag in ${EXISTING_TAGS[@]}; do
-		V_MAIN_EXISTING=${existing_tag/*.}
-		V_SUB_EXISTING=${existing_tag/*.}
-
-		# Not an integer
-		[[ ! $V_MAIN_EXISTING =~ ^[0-9]+$ ]] && continue
-		[[ ! $V_SUB_EXISTING =~ ^[0-9]+$ ]] && continue
-
-		if [[ $V_MAIN_EXISTING == "latest" ]]; then
-			echo "Found deprecated label \"latest\" for repository $REPOSITORY, it should be deleted."
-			IMGS_TO_DELETE+=($REPOSITORY:$existing_tag)
-		elif [[ $V_MAIN_EXISTING -lt $V_MAIN ]]; then
-			echo "Found tag $existing_tag for $REPOSITORY, which is older than the current tag $TAG and should be deleted."
-			IMGS_TO_DELETE+=($REPOSITORY:$existing_tag)
-		elif [[ $V_SUB_EXISTING -lt $V_SUB ]]; then
-			echo "Found tag $existing_tag for $REPOSITORY, which is older than the current tag $TAG and should be deleted."
-			IMGS_TO_DELETE+=($REPOSITORY:$existing_tag)
-		fi
-	done
+  REPOSITORY=${container/:*}
+  TAG=${container/*:}
+  V_MAIN=${container/*.}
+  V_SUB=${container/*.}
+  EXISTING_TAGS=$(docker images | grep ${REPOSITORY} | awk '{ print $2 }')
+  for existing_tag in ${EXISTING_TAGS[@]}; do
+    V_MAIN_EXISTING=${existing_tag/*.}
+    V_SUB_EXISTING=${existing_tag/*.}
+    # Not an integer
+    [[ ! $V_MAIN_EXISTING =~ ^[0-9]+$ ]] && continue
+    [[ ! $V_SUB_EXISTING =~ ^[0-9]+$ ]] && continue
+
+    if [[ $V_MAIN_EXISTING == "latest" ]]; then
+      echo "Found deprecated label \"latest\" for repository $REPOSITORY, it should be deleted."
+      IMGS_TO_DELETE+=($REPOSITORY:$existing_tag)
+    elif [[ $V_MAIN_EXISTING -lt $V_MAIN ]]; then
+      echo "Found tag $existing_tag for $REPOSITORY, which is older than the current tag $TAG and should be deleted."
+      IMGS_TO_DELETE+=($REPOSITORY:$existing_tag)
+    elif [[ $V_SUB_EXISTING -lt $V_SUB ]]; then
+      echo "Found tag $existing_tag for $REPOSITORY, which is older than the current tag $TAG and should be deleted."
+      IMGS_TO_DELETE+=($REPOSITORY:$existing_tag)
+    fi
+  done
 done
 done
+
 if [[ ! -z ${IMGS_TO_DELETE[*]} ]]; then
 if [[ ! -z ${IMGS_TO_DELETE[*]} ]]; then
-	echo "Run the following command to delete unused image tags:"
-	echo
-	echo "    docker rmi ${IMGS_TO_DELETE[*]}"
-	echo
-	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
-		echo "OK, skipped."
-	fi
+  echo "Run the following command to delete unused image tags:"
+  echo
+  echo "    docker rmi ${IMGS_TO_DELETE[*]}"
+  echo
+  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
+    echo "OK, skipped."
+  fi
 fi
 fi
 echo -e "\e[32mFurther cleanup...\e[0m"
 echo -e "\e[32mFurther cleanup...\e[0m"
 echo "If you want to cleanup further garbage collected by Docker, please make sure all containers are up and running before cleaning your system by executing \"docker system prune\""
 echo "If you want to cleanup further garbage collected by Docker, please make sure all containers are up and running before cleaning your system by executing \"docker system prune\""