소스 검색

Merge pull request #87 from andryyy/dev

Dev to master
André Peters 8 년 전
부모
커밋
700f4ac0db
35개의 변경된 파일714개의 추가작업 그리고 234개의 파일을 삭제
  1. 0 0
      data/Dockerfiles/bind9/.empty
  2. 44 0
      data/Dockerfiles/clamav/Dockerfile
  3. 35 0
      data/Dockerfiles/clamav/bootstrap.sh
  4. 0 26
      data/Dockerfiles/pdns/Dockerfile
  5. 8 4
      data/Dockerfiles/rspamd/Dockerfile
  6. 4 0
      data/Dockerfiles/rspamd/antivirus.conf
  7. 1 0
      data/Dockerfiles/rspamd/settings.conf
  8. 0 3
      data/Dockerfiles/sogo/supervisord.conf
  9. 20 0
      data/conf/bind9/named.conf
  10. 1 0
      data/conf/nginx/server_name.active
  11. 184 14
      data/conf/nginx/site.conf
  12. 1 0
      data/conf/nginx/templates/listen_plain.template
  13. 0 0
      data/conf/nginx/templates/listen_ssl.template
  14. 1 0
      data/conf/nginx/templates/server_name.template
  15. 0 1
      data/conf/pdns/pdns_custom.lua
  16. 0 41
      data/conf/pdns/recursor.conf
  17. 1 1
      data/conf/postfix/main.cf
  18. 0 5
      data/conf/postfix/sql/mysql_virtual_alias_domain_mailbox_maps.cf
  19. 1 1
      data/conf/postfix/sql/mysql_virtual_alias_domain_maps.cf
  20. 1 1
      data/conf/postfix/sql/mysql_virtual_sender_acl.cf
  21. 22 10
      data/conf/rspamd/dynmaps/authoritative.php
  22. 87 7
      data/conf/rspamd/dynmaps/settings.php
  23. 22 10
      data/conf/rspamd/dynmaps/tags.php
  24. 53 37
      data/conf/rspamd/lua/rspamd.local.lua
  25. 32 4
      data/web/call_sogo_ctrl.php
  26. 1 1
      data/web/edit.php
  27. 62 20
      data/web/inc/functions.inc.php
  28. 2 0
      data/web/inc/vars.inc.php
  29. 4 4
      data/web/js/mailbox.js
  30. 7 4
      data/web/lang/lang.de.php
  31. 8 4
      data/web/lang/lang.en.php
  32. 43 17
      data/web/mailbox.php
  33. 15 2
      data/web/user.php
  34. 36 14
      docker-compose.yml
  35. 18 3
      generate_config.sh

+ 0 - 0
data/Dockerfiles/bind9/.empty


+ 44 - 0
data/Dockerfiles/clamav/Dockerfile

@@ -0,0 +1,44 @@
+FROM debian:latest
+MAINTAINER https://m-ko.de Markus Kosmal <code@cnfg.io>
+
+# Debian Base to use
+ENV DEBIAN_VERSION jessie
+
+# initial install of av daemon
+RUN echo "deb http://http.debian.net/debian/ $DEBIAN_VERSION main contrib non-free" > /etc/apt/sources.list && \
+    echo "deb http://http.debian.net/debian/ $DEBIAN_VERSION-updates main contrib non-free" >> /etc/apt/sources.list && \
+    echo "deb http://security.debian.org/ $DEBIAN_VERSION/updates main contrib non-free" >> /etc/apt/sources.list && \
+    apt-get update && \
+    DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends -y -qq \
+        clamav-daemon \
+        clamav-freshclam \
+        libclamunrar7 \
+        wget && \
+    apt-get clean && \
+    rm -rf /var/lib/apt/lists/*
+
+# initial update of av databases
+RUN wget -O /var/lib/clamav/main.cvd http://db.local.clamav.net/main.cvd && \
+    wget -O /var/lib/clamav/daily.cvd http://db.local.clamav.net/daily.cvd && \
+    wget -O /var/lib/clamav/bytecode.cvd http://db.local.clamav.net/bytecode.cvd && \
+    chown clamav:clamav /var/lib/clamav/*.cvd
+
+# permission juggling
+RUN mkdir /var/run/clamav && \
+    chown clamav:clamav /var/run/clamav && \
+    chmod 750 /var/run/clamav
+
+# av configuration update
+RUN sed -i 's/^Foreground .*$/Foreground true/g' /etc/clamav/clamd.conf && \
+    echo "TCPSocket 3310" >> /etc/clamav/clamd.conf && \
+    sed -i 's/^Foreground .*$/Foreground true/g' /etc/clamav/freshclam.conf
+
+# volume provision
+VOLUME ["/var/lib/clamav"]
+
+# port provision
+EXPOSE 3310
+
+# av daemon bootstrapping
+ADD bootstrap.sh /
+CMD ["/bootstrap.sh"]

+ 35 - 0
data/Dockerfiles/clamav/bootstrap.sh

@@ -0,0 +1,35 @@
+#!/bin/bash
+# bootstrap clam av service and clam av database updater shell script
+# presented by mko (Markus Kosmal<code@cnfg.io>)
+set -m
+
+# start clam service itself and the updater in background as daemon
+freshclam -d &
+clamd &
+
+# recognize PIDs
+pidlist=`jobs -p`
+
+# initialize latest result var
+latest_exit=0
+
+# define shutdown helper
+function shutdown() {
+    trap "" SUBS
+
+    for single in $pidlist; do
+        if ! kill -0 $pidlist 2>/dev/null; then
+            wait $pidlist
+            exitcode=$?
+        fi
+    done
+
+    kill $pidlist 2>/dev/null
+}
+
+# run shutdown
+trap terminate SUBS
+wait
+
+# return received result
+exit $latest_exit

+ 0 - 26
data/Dockerfiles/pdns/Dockerfile

@@ -1,26 +0,0 @@
-FROM ubuntu:xenial
-MAINTAINER Andre Peters <andre.peters@debinux.de>
-
-ENV DEBIAN_FRONTEND noninteractive
-ENV LC_ALL C
-
-RUN dpkg-divert --local --rename --add /sbin/initctl \
-    && ln -sf /bin/true /sbin/initctl \
-    && dpkg-divert --local --rename --add /usr/bin/ischroot \
-    && ln -sf /bin/true /usr/bin/ischroot
-
-RUN echo 'deb http://repo.powerdns.com/ubuntu xenial-rec-40 main' > /etc/apt/sources.list.d/pdns.list
-
-RUN echo 'Package: pdns-*\n\
-Pin: origin repo.powerdns.com\n\
-Pin-Priority: 600\n' > /etc/apt/preferences.d/pdns
-
-RUN apt-key adv --fetch-keys http://repo.powerdns.com/FD380FBB-pub.asc \
-	&& apt-get update \
-	&& apt-get install -y --force-yes pdns-recursor
-
-CMD ["/usr/sbin/pdns_recursor"]
-
-EXPOSE 53/udp
-
-RUN apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*

+ 8 - 4
data/Dockerfiles/rspamd/Dockerfile

@@ -12,13 +12,17 @@ RUN dpkg-divert --local --rename --add /sbin/initctl \
 RUN apt-key adv --fetch-keys http://rspamd.com/apt-stable/gpg.key \
     && echo "deb http://rspamd.com/apt-stable/ xenial main" > /etc/apt/sources.list.d/rspamd.list \
     && apt-get update \
-    && apt-get -y install rspamd ca-certificates
+    && apt-get -y install rspamd ca-certificates python-pip
 
 RUN echo '.include $LOCAL_CONFDIR/local.d/rspamd.conf.local' > /etc/rspamd/rspamd.conf.local
-# "Hardcoded" - we need them
-RUN echo 'settings = "http://nginx:8081/settings.php";' > /etc/rspamd/modules.d/settings.conf
 
-CMD ["/usr/bin/rspamd","-f", "-u", "_rspamd", "-g", "_rspamd"]
+ADD settings.conf /etc/rspamd/modules.d/settings.conf
+ADD antivirus.conf /etc/rspamd/modules.d/antivirus.conf
+
+RUN pip install -U oletools
+
+# Give Nginx/PHP time to restart
+CMD /bin/sleep 30; /usr/bin/rspamd -f -u _rspamd -g _rspamd
 
 RUN apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
 

+ 4 - 0
data/Dockerfiles/rspamd/antivirus.conf

@@ -0,0 +1,4 @@
+antivirus {
+	.include(try=true,priority=1,duplicate=merge) "$LOCAL_CONFDIR/local.d/antivirus.conf"
+	.include(try=true,priority=10) "$LOCAL_CONFDIR/override.d/antivirus.conf"
+}

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

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

+ 0 - 3
data/Dockerfiles/sogo/supervisord.conf

@@ -7,9 +7,6 @@ redirect_stderr=true
 autostart=true
 stdout_syslog=true
 
-[group:sogo-group]
-programs=reconf-domains,sogo
-
 [program:sogo]
 command=/usr/sbin/sogod
 user=sogo

+ 20 - 0
data/conf/bind9/named.conf

@@ -0,0 +1,20 @@
+acl internal_networks {
+        127.0.0.0/8;
+        192.168.0.0/16;
+        172.16.0.0/12;
+        10.0.0.0/8;
+};
+
+options {
+        directory "/var/bind";
+        allow-recursion { internal_networks; };
+        listen-on { any; };
+        listen-on-v6 { none; };
+        pid-file "/var/run/named/named.pid";
+        allow-transfer { none; };
+        dnssec-enable yes;
+        dnssec-validation yes;
+        dnssec-lookaside auto;
+};
+
+include "/etc/bind/bind.keys";

+ 1 - 0
data/conf/nginx/server_name.active

@@ -0,0 +1 @@
+server_name logs.servercow.de autodiscover.* autoconfig.*;

+ 184 - 14
data/conf/nginx/site.conf

@@ -1,6 +1,6 @@
 proxy_cache_path /tmp levels=1:2 keys_zone=sogo:10m inactive=24h  max_size=1g;
 server {
-  include /etc/nginx/conf.d/listen.active;
+  include /etc/nginx/conf.d/listen_ssl.active;
   include /etc/nginx/mime.types;
   charset utf-8;
   override_charset on;
@@ -13,13 +13,171 @@ server {
   add_header Strict-Transport-Security "max-age=15768000; includeSubDomains";
   ssl_ecdh_curve secp384r1;
   index index.php index.html;
-  server_name _ autodiscover.* autoconfig.*;
+  include /etc/nginx/conf.d/server_name.active;
   error_log  /var/log/nginx/error.log;
   access_log /var/log/nginx/access.log;
   root /web;
 
+  location ^~ /.well-known/acme-challenge/ {
+	  allow all;
+    default_type "text/plain";
+  }
+
+  # If behind reverse proxy, forwards the correct IP
+  set_real_ip_from 172.22.1.1;
+  real_ip_header X-Forwarded-For;
+  real_ip_recursive on;
+
+  location = /principals/ {
+    rewrite ^ $scheme://$host:$server_port/SOGo/dav;
+    allow all;
+  }
+
+  location ~ \.php$ {
+    try_files $uri =404;
+    fastcgi_split_path_info ^(.+\.php)(/.+)$;
+    fastcgi_pass phpfpm:9000;
+    fastcgi_index index.php;
+    include /etc/nginx/fastcgi_params;
+    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
+    fastcgi_param PATH_INFO $fastcgi_path_info;
+    fastcgi_param PHP_VALUE "max_execution_time = 1200
+                             max_input_time = 1200
+                             memory_limit = 64M";
+    fastcgi_read_timeout 1200;
+  }
+
+  rewrite ^(/save.+)$ /rspamd$1 last;
+  location /rspamd/ {
+    proxy_pass       http://172.22.1.253:11334/;
+    proxy_set_header Host      $host;
+    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+    proxy_set_header X-Real-IP $remote_addr;
+    add_header Strict-Transport-Security "max-age=31536000; includeSubdomains";
+    add_header X-Content-Type-Options nosniff;
+    add_header X-Frame-Options SAMEORIGIN;
+    add_header X-XSS-Protection "1; mode=block";
+  }
+
+  location ^~ /inc/init.sql {
+    deny all;
+  }
+
+  location ~ /(?:a|A)utodiscover/(?:a|A)utodiscover.xml {
+		fastcgi_split_path_info ^(.+\.php)(/.+)$;
+		fastcgi_pass phpfpm:9000;
+		include /etc/nginx/fastcgi_params;
+    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
+    try_files /autodiscover.php =404;
+  }
+
+  location ~ /(?:m|M)ail/(?:c|C)onfig-v1.1.xml {
+		fastcgi_split_path_info ^(.+\.php)(/.+)$;
+		fastcgi_pass phpfpm:9000;
+		include /etc/nginx/fastcgi_params;
+    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
+    try_files /autoconfig.php =404;
+  }
+
+  location ^~ /Microsoft-Server-ActiveSync {
+    proxy_pass http://172.22.1.252:20000/SOGo/Microsoft-Server-ActiveSync;
+    proxy_connect_timeout 1000;
+    proxy_next_upstream timeout error;
+    proxy_send_timeout 1000;
+    proxy_read_timeout 1000;
+    proxy_buffer_size 8k;
+    proxy_buffers 4 32k;
+    proxy_temp_file_write_size 64k;
+    proxy_busy_buffers_size 64k;
+    proxy_set_header X-Real-IP $remote_addr;
+    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+    proxy_set_header Host $host;
+    proxy_set_header x-webobjects-server-protocol HTTP/1.0;
+    proxy_set_header x-webobjects-remote-host $remote_addr;
+    proxy_set_header x-webobjects-server-name $server_name;
+    proxy_set_header x-webobjects-server-url $scheme://$host:$server_port;
+    proxy_set_header x-webobjects-server-port $server_port;
+    client_body_buffer_size 128k;
+    client_max_body_size 100m;
+  }
+
+  location ^~ /SOGo {
+    proxy_pass http://172.22.1.252:20000;
+    proxy_set_header X-Real-IP $remote_addr;
+    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+    proxy_set_header Host $host;
+    proxy_set_header x-webobjects-server-protocol HTTP/1.0;
+    proxy_set_header x-webobjects-remote-host $remote_addr;
+    proxy_set_header x-webobjects-server-name $server_name;
+    proxy_set_header x-webobjects-server-url $scheme://$host:$server_port;
+    proxy_set_header x-webobjects-server-port $server_port;
+    client_body_buffer_size 128k;
+    client_max_body_size 100m;
+    break;
+  }
+
+  location /SOGo.woa/WebServerResources/ {
+    proxy_pass http://172.22.1.252:9192/WebServerResources/;
+    proxy_set_header Host $host;
+    proxy_cache sogo;
+    proxy_cache_valid 200 1d;
+    proxy_cache_use_stale error timeout invalid_header updating http_500 http_502 http_503 http_504;
+    #alias /usr/lib/GNUstep/SOGo/WebServerResources/;
+    allow all;
+  }
+
+  location /.woa/WebServerResources/ {
+    proxy_pass http://172.22.1.252:9192/WebServerResources/;
+    proxy_set_header Host $host;
+    proxy_cache sogo;
+    proxy_cache_valid 200 1d;
+    proxy_cache_use_stale error timeout invalid_header updating http_500 http_502 http_503 http_504;
+    #alias /usr/lib/GNUstep/SOGo/WebServerResources/;
+    allow all;
+  }
+
+  location /SOGo/WebServerResources/ {
+    proxy_pass http://172.22.1.252:9192/WebServerResources/;
+    proxy_set_header Host $host;
+    proxy_cache sogo;
+    proxy_cache_valid 200 1d;
+    proxy_cache_use_stale error timeout invalid_header updating http_500 http_502 http_503 http_504;
+    #alias /usr/lib/GNUstep/SOGo/WebServerResources/;
+    allow all;
+  }
+
+  location (^/SOGo/so/ControlPanel/Products/[^/]*UI/Resources/.*\.(jpg|png|gif|css|js)$ {
+    proxy_pass http://172.22.1.252:9192/$1.SOGo/Resources/$2;
+    proxy_set_header Host $host;
+    proxy_cache sogo;
+    proxy_cache_valid 200 1d;
+    proxy_cache_use_stale error timeout invalid_header updating http_500 http_502 http_503 http_504;
+    #alias /usr/lib/GNUstep/SOGo/$1.SOGo/Resources/$2;
+  }
+}
+server {
+  include /etc/nginx/conf.d/listen_plain.active;
+  include /etc/nginx/mime.types;
+  charset utf-8;
+  override_charset on;
+  index index.php index.html;
+  include /etc/nginx/conf.d/server_name.active;
+  error_log  /var/log/nginx/error.log;
+  access_log /var/log/nginx/access.log;
+  root /web;
+
+  location ^~ /.well-known/acme-challenge/ {
+	  allow all;
+    default_type "text/plain";
+  }
+
+  # If behind reverse proxy, forwards the correct IP
+  set_real_ip_from 172.22.1.1;
+  real_ip_header X-Forwarded-For;
+  real_ip_recursive on;
+
   location = /principals/ {
-    rewrite ^ https://$host/SOGo/dav;
+    rewrite ^ $scheme://$host:$server_port/SOGo/dav;
     allow all;
   }
 
@@ -42,6 +200,7 @@ server {
     proxy_pass       http://172.22.1.253:11334/;
     proxy_set_header Host      $host;
     proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+    proxy_set_header X-Real-IP $remote_addr;
     add_header Strict-Transport-Security "max-age=31536000; includeSubdomains";
     add_header X-Content-Type-Options nosniff;
     add_header X-Frame-Options SAMEORIGIN;
@@ -52,12 +211,20 @@ server {
     deny all;
   }
 
-  if ($host ~* autodiscover\.(.*)) {
-    rewrite ^(.*) /autodiscover.php last;
+  location ~ /(?:a|A)utodiscover/(?:a|A)utodiscover.xml {
+		fastcgi_split_path_info ^(.+\.php)(/.+)$;
+		fastcgi_pass phpfpm:9000;
+		include /etc/nginx/fastcgi_params;
+    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
+    try_files /autodiscover.php =404;
   }
 
-  if ($host ~* autoconfig\.(.*)) {
-    rewrite ^(.*) /autoconfig.php last;
+  location ~ /(?:m|M)ail/(?:c|C)onfig-v1.1.xml {
+		fastcgi_split_path_info ^(.+\.php)(/.+)$;
+		fastcgi_pass phpfpm:9000;
+		include /etc/nginx/fastcgi_params;
+    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
+    try_files /autoconfig.php =404;
   }
 
   location ^~ /Microsoft-Server-ActiveSync {
@@ -92,13 +259,6 @@ server {
     proxy_set_header x-webobjects-server-name $server_name;
     proxy_set_header x-webobjects-server-url $scheme://$host:$server_port;
     proxy_set_header x-webobjects-server-port $server_port;
-    #proxy_connect_timeout 90;
-    #proxy_send_timeout 90;
-    #proxy_read_timeout 90;
-    #proxy_buffer_size 4k;
-    #proxy_buffers 4 32k;
-    #proxy_busy_buffers_size 64k;
-    #proxy_temp_file_write_size 64k;
     client_body_buffer_size 128k;
     client_max_body_size 100m;
     break;
@@ -114,6 +274,16 @@ server {
     allow all;
   }
 
+  location /.woa/WebServerResources/ {
+    proxy_pass http://172.22.1.252:9192/WebServerResources/;
+    proxy_set_header Host $host;
+    proxy_cache sogo;
+    proxy_cache_valid 200 1d;
+    proxy_cache_use_stale error timeout invalid_header updating http_500 http_502 http_503 http_504;
+    #alias /usr/lib/GNUstep/SOGo/WebServerResources/;
+    allow all;
+  }
+
   location /SOGo/WebServerResources/ {
     proxy_pass http://172.22.1.252:9192/WebServerResources/;
     proxy_set_header Host $host;

+ 1 - 0
data/conf/nginx/templates/listen_plain.template

@@ -0,0 +1 @@
+listen ${HTTP_PORT};

+ 0 - 0
data/conf/nginx/listen.template → data/conf/nginx/templates/listen_ssl.template


+ 1 - 0
data/conf/nginx/templates/server_name.template

@@ -0,0 +1 @@
+server_name ${MAILCOW_HOSTNAME} autodiscover.* autoconfig.*;

+ 0 - 1
data/conf/pdns/pdns_custom.lua

@@ -1 +0,0 @@
-addNTA("mailcow-network", "nta for local")

+ 0 - 41
data/conf/pdns/recursor.conf

@@ -1,41 +0,0 @@
-allow-from=127.0.0.0/8, 192.168.0.0/16, 172.16.0.0/12, 10.0.0.0/8
-config-dir=/etc/powerdns
-daemon=no
-disable-syslog=yes
-dnssec=process
-dnssec-log-bogus=yes
-dont-query=10.0.0.0/8, 100.64.0.0/10, 169.254.0.0/16, 192.168.0.0/16, 172.16.0.0/12, ::1/128, fc00::/7, fe80::/10, 0.0.0.0/8, 192.0.0.0/24, 192.0.2.0/24, 198.51.100.0/24, 203.0.113.0/24, 240.0.0.0/4, ::/96, ::ffff:0:0/96, 100::/64, 2001:db8::/32
-export-etc-hosts=off
-# forward-zones=
-forward-zones-recurse=mailcow-network.=127.0.0.11
-local-address=0.0.0.0
-local-port=53
-loglevel=6
-# lowercase-outgoing=no
-lua-config-file=/etc/powerdns/pdns_custom.lua
-# max-cache-entries=1000000
-# max-cache-ttl=86400
-# max-mthreads=2048
-# max-negative-ttl=3600
-# max-packetcache-entries=500000
-# max-qperq=50
-# max-tcp-clients=128
-# max-tcp-per-client=0
-# max-total-msec=7000
-# minimum-ttl-override=0
-# network-timeout=1500
-# packetcache-servfail-ttl=60
-# packetcache-ttl=3600
-quiet=yes
-# security-poll-suffix=secpoll.powerdns.com.
-# serve-rfc1918=yes
-# server-down-max-fails=64
-# server-down-throttle-time=60
-setgid=pdns
-setuid=pdns
-# spoof-nearmiss-max=20
-# stack-size=200000
-# threads=2
-# trace=off
-version-string=PowerDNS Recursor
-webserver=no

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

@@ -83,7 +83,7 @@ virtual_alias_maps = proxy:mysql:/opt/postfix/conf/sql/mysql_virtual_alias_maps.
 virtual_gid_maps = static:5000
 virtual_mailbox_base = /var/vmail/
 virtual_mailbox_domains = proxy:mysql:/opt/postfix/conf/sql/mysql_virtual_domains_maps.cf
-virtual_mailbox_maps = proxy:mysql:/opt/postfix/conf/sql/mysql_virtual_mailbox_maps.cf, proxy:mysql:/opt/postfix/conf/sql/mysql_virtual_alias_domain_mailbox_maps.cf
+virtual_mailbox_maps = proxy:mysql:/opt/postfix/conf/sql/mysql_virtual_mailbox_maps.cf
 virtual_minimum_uid = 104
 virtual_transport = lmtp:inet:dovecot:24
 virtual_uid_maps = static:5000

+ 0 - 5
data/conf/postfix/sql/mysql_virtual_alias_domain_mailbox_maps.cf

@@ -1,5 +0,0 @@
-user = mailcow
-password = mysafepasswd
-hosts = mysql
-dbname = mailcow
-query = SELECT maildir FROM mailbox,alias_domain WHERE alias_domain.alias_domain = '%d' and mailbox.username = CONCAT('%u', '@', alias_domain.target_domain) AND mailbox.active = 1 AND alias_domain.active='1'

+ 1 - 1
data/conf/postfix/sql/mysql_virtual_alias_domain_maps.cf

@@ -2,4 +2,4 @@ user = mailcow
 password = mysafepasswd
 hosts = mysql
 dbname = mailcow
-query = SELECT goto FROM alias,alias_domain WHERE alias_domain.alias_domain = '%d' and alias.address = CONCAT('%u', '@', alias_domain.target_domain) AND alias.active = 1 AND alias_domain.active='1'
+query = SELECT username FROM mailbox,alias_domain WHERE alias_domain.alias_domain = '%d' and mailbox.username = CONCAT('%u', '@', alias_domain.target_domain) AND mailbox.active = 1 AND alias_domain.active='1'

+ 1 - 1
data/conf/postfix/sql/mysql_virtual_sender_acl.cf

@@ -2,4 +2,4 @@ user = mailcow
 password = mysafepasswd
 hosts = mysql
 dbname = mailcow
-query = SELECT goto FROM alias WHERE address='%s' AND active='1' AND domain IN(SELECT domain FROM domain WHERE domain='%d' AND active='1') UNION SELECT logged_in_as FROM sender_acl WHERE send_as='@%d' OR send_as='%s' OR send_as IN ( SELECT CONCAT ('@',target_domain) FROM alias_domain WHERE alias_domain = '%d') OR send_as IN ( SELECT CONCAT ('%u','@',target_domain) FROM alias_domain WHERE alias_domain = '%d' ) AND logged_in_as NOT IN (SELECT goto FROM alias WHERE address='%s') UNION SELECT goto FROM alias,alias_domain WHERE alias_domain.alias_domain = '%d' AND alias.address = CONCAT('%u','@',alias_domain.target_domain) AND alias.active ='1' AND alias_domain.active='1'
+query = SELECT goto FROM alias WHERE address='%s' AND active='1' AND domain IN(SELECT domain FROM domain WHERE domain='%d' AND active='1') UNION SELECT logged_in_as FROM sender_acl WHERE send_as='@%d' OR send_as='%s' OR send_as IN ( SELECT CONCAT ('@',target_domain) FROM alias_domain WHERE alias_domain = '%d') OR send_as IN ( SELECT CONCAT ('%u','@',target_domain) FROM alias_domain WHERE alias_domain = '%d' ) AND logged_in_as NOT IN (SELECT goto FROM alias WHERE address='%s') UNION SELECT username FROM mailbox,alias_domain WHERE alias_domain.alias_domain = '%d' AND mailbox.username = CONCAT('%u','@',alias_domain.target_domain) AND mailbox.active ='1' AND alias_domain.active='1'

+ 22 - 10
data/conf/rspamd/dynmaps/authoritative.php

@@ -1,22 +1,34 @@
 <?php
+require_once "vars.inc.php";
 ini_set('error_reporting', 0);
+$has_object = 0;
 header('Content-Type: text/plain');
-require_once "vars.inc.php";
 $dsn = $database_type . ':host=' . $database_host . ';dbname=' . $database_name;
 $opt = [
     PDO::ATTR_ERRMODE            => PDO::ERRMODE_EXCEPTION,
     PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
     PDO::ATTR_EMULATE_PREPARES   => false,
 ];
-$pdo = new PDO($dsn, $database_user, $database_pass, $opt);
-$stmt = $pdo->query("SELECT `domain` FROM `domain`");
-$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
-while ($row = array_shift($rows)) {
-  echo strtolower(trim($row['domain'])) . PHP_EOL;
+try {
+  $pdo = new PDO($dsn, $database_user, $database_pass, $opt);
+  $stmt = $pdo->query("SELECT `domain` FROM `domain`");
+  $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
+  while ($row = array_shift($rows)) {
+    $has_object = 1;
+    echo strtolower(trim($row['domain'])) . PHP_EOL;
+  }
+  $stmt = $pdo->query("SELECT `alias_domain` FROM `alias_domain`");
+  $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
+  while ($row = array_shift($rows)) {
+    $has_object = 1;
+    echo strtolower(trim($row['alias_domain'])) . PHP_EOL;
+  }
+  if ($has_object == 0) {
+    echo "dummy@domain.local";
+  }
 }
-$stmt = $pdo->query("SELECT `alias_domain` FROM `alias_domain`");
-$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
-while ($row = array_shift($rows)) {
-  echo strtolower(trim($row['alias_domain'])) . PHP_EOL;
+catch (PDOException $e) {
+  echo "dummy@domain.local";
+  exit;
 }
 ?>

+ 87 - 7
data/conf/rspamd/dynmaps/settings.php

@@ -4,6 +4,11 @@ The match section performs AND operation on different matches: for example, if y
 then the rule matches only when from AND rcpt match. For similar matches, the OR rule applies: if you have multiple rcpt matches,
 then any of these will trigger the rule. If a rule is triggered then no more rules are matched.
 */
+function parse_email($email) {
+  if(!filter_var($email, FILTER_VALIDATE_EMAIL)) return false;
+  $a = strrpos($email, '@');
+  return array('local' => substr($email, 0, $a), 'domain' => substr($email, $a));
+}
 header('Content-Type: text/plain');
 require_once "vars.inc.php";
 
@@ -15,7 +20,15 @@ $opt = [
     PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
     PDO::ATTR_EMULATE_PREPARES   => false,
 ];
-$pdo = new PDO($dsn, $database_user, $database_pass, $opt);
+try {
+  $pdo = new PDO($dsn, $database_user, $database_pass, $opt);
+  $stmt = $pdo->query("SELECT * FROM `filterconf`");
+}
+catch (PDOException $e) {
+  echo 'settings { }';
+  exit;
+}
+
 ?>
 settings {
 <?php
@@ -48,13 +61,27 @@ while ($row = array_shift($rows)) {
 <?php
         }
     }
+    $local = parse_email($row['object'])['local'];
+    $domain = parse_email($row['object'])['domain'];
+    if (!empty($local) && !empty($local)) {
+?>
+		rcpt = "/<?=$local;?>\+.*<?=$domain;?>/";
+<?php
+    }
 ?>
 		rcpt = "<?=$row['object'];?>";
 <?php
-	$stmt = $pdo->prepare("SELECT `address` FROM `alias` WHERE `goto` = :object_goto AND `address` NOT LIKE '@%' AND `address` != :object_address");
-	$stmt->execute(array(':object_goto' => $row['object'], ':object_address' => $row['object']));
+	$stmt = $pdo->prepare("SELECT `address` FROM `alias` WHERE `goto` LIKE :object_goto AND `address` NOT LIKE '@%' AND `address` != :object_address");
+	$stmt->execute(array(':object_goto' => '%' . $row['object'] . '%', ':object_address' => $row['object']));
 	$rows_aliases_1 = $stmt->fetchAll(PDO::FETCH_ASSOC);
 	while ($row_aliases_1 = array_shift($rows_aliases_1)) {
+    $local = parse_email($row_aliases_1['address'])['local'];
+    $domain = parse_email($row_aliases_1['address'])['domain'];
+    if (!empty($local) && !empty($local)) {
+?>
+		rcpt = "/<?=$local;?>\+.*<?=$domain;?>/";
+<?php
+    }
 ?>
 		rcpt = "<?=$row_aliases_1['address'];?>";
 <?php
@@ -67,6 +94,13 @@ while ($row = array_shift($rows)) {
 	array_filter($rows_aliases_2);
 	while ($row_aliases_2 = array_shift($rows_aliases_2)) {
     if (!empty($row_aliases_2['aliases'])) {
+      $local = parse_email($row_aliases_2['aliases'])['local'];
+      $domain = parse_email($row_aliases_2['aliases'])['domain'];
+      if (!empty($local) && !empty($local)) {
+?>
+		rcpt = "/<?=$local;?>\+.*<?=$domain;?>/";
+<?php
+      }
 ?>
 		rcpt = "<?=$row_aliases_2['aliases'];?>";
 <?php
@@ -123,14 +157,30 @@ while ($row = array_shift($rows)) {
 	else {
 ?>
 		priority = high;
+<?php
+    $local = parse_email($row['object'])['local'];
+    $domain = parse_email($row['object'])['domain'];
+    if (!empty($local) && !empty($local)) {
+?>
+		rcpt = "/<?=$local;?>\+.*<?=$domain;?>/";
+<?php
+    }
+?>
 		rcpt = "<?=$row['object'];?>";
 <?php
 	}
-	$stmt = $pdo->prepare("SELECT `address` FROM `alias` WHERE `goto` = :object_goto AND `address` NOT LIKE '@%' AND `address` != :object_address");
-	$stmt->execute(array(':object_goto' => $row['object'], ':object_address' => $row['object']));
+	$stmt = $pdo->prepare("SELECT `address` FROM `alias` WHERE `goto` LIKE :object_goto AND `address` NOT LIKE '@%' AND `address` != :object_address");
+	$stmt->execute(array(':object_goto' => '%' . $row['object'] . '%', ':object_address' => $row['object']));
 	$rows_aliases_wl_1 = $stmt->fetchAll(PDO::FETCH_ASSOC);
 	array_filter($rows_aliases_wl_1);
 	while ($row_aliases_wl_1 = array_shift($rows_aliases_wl_1)) {
+      $local = parse_email($row_aliases_wl_1['address'])['local'];
+      $domain = parse_email($row_aliases_wl_1['address'])['domain'];
+      if (!empty($local) && !empty($local)) {
+?>
+		rcpt = "/<?=$local;?>\+.*<?=$domain;?>/";
+<?php
+      }
 ?>
 		rcpt = "<?=$row_aliases_wl_1['address'];?>";
 <?php
@@ -143,6 +193,13 @@ while ($row = array_shift($rows)) {
 	array_filter($rows_aliases_wl_2);
 	while ($row_aliases_wl_2 = array_shift($rows_aliases_wl_2)) {
     if (!empty($row_aliases_wl_2['aliases'])) {
+      $local = parse_email($row_aliases_wl_2['aliases'])['local'];
+      $domain = parse_email($row_aliases_wl_2['aliases'])['domain'];
+      if (!empty($local) && !empty($local)) {
+?>
+		rcpt = "/<?=$local;?>\+.*<?=$domain;?>/";
+<?php
+      }
 ?>
 		rcpt = "<?=$row_aliases_wl_2['aliases'];?>";
 <?php
@@ -195,14 +252,30 @@ while ($row = array_shift($rows)) {
 	else {
 ?>
 		priority = high;
+<?php
+    $local = parse_email($row['object'])['local'];
+    $domain = parse_email($row['object'])['domain'];
+    if (!empty($local) && !empty($local)) {
+?>
+		rcpt = "/<?=$local;?>\+.*<?=$domain;?>/";
+<?php
+    }
+?>
 		rcpt = "<?=$row['object'];?>";
 <?php
 	}
-	$stmt = $pdo->prepare("SELECT `address` FROM `alias` WHERE `goto` = :object_goto AND `address` NOT LIKE '@%' AND `address` != :object_address");
-	$stmt->execute(array(':object_goto' => $row['object'], ':object_address' => $row['object']));
+	$stmt = $pdo->prepare("SELECT `address` FROM `alias` WHERE `goto` LIKE :object_goto AND `address` NOT LIKE '@%' AND `address` != :object_address");
+	$stmt->execute(array(':object_goto' => '%' . $row['object'] . '%', ':object_address' => $row['object']));
 	$rows_aliases_bl_1 = $stmt->fetchAll(PDO::FETCH_ASSOC);
 	array_filter($rows_aliases_bl_1);
 	while ($row_aliases_bl_1 = array_shift($rows_aliases_bl_1)) {
+      $local = parse_email($row_aliases_bl_1['address'])['local'];
+      $domain = parse_email($row_aliases_bl_1['address'])['domain'];
+      if (!empty($local) && !empty($local)) {
+?>
+		rcpt = "/<?=$local;?>\+.*<?=$domain;?>/";
+<?php
+      }
 ?>
 		rcpt = "<?=$row_aliases_bl_1['address'];?>";
 <?php
@@ -215,6 +288,13 @@ while ($row = array_shift($rows)) {
 	array_filter($rows_aliases_bl_2);
 	while ($row_aliases_bl_2 = array_shift($rows_aliases_bl_2)) {
     if (!empty($row_aliases_bl_2['aliases'])) {
+      $local = parse_email($row_aliases_bl_2['aliases'])['local'];
+      $domain = parse_email($row_aliases_bl_2['aliases'])['domain'];
+      if (!empty($local) && !empty($local)) {
+?>
+		rcpt = "/<?=$local;?>\+.*<?=$domain;?>/";
+<?php
+      }
 ?>
 		rcpt = "<?=$row_aliases_bl_2['aliases'];?>";
 <?php

+ 22 - 10
data/conf/rspamd/dynmaps/tags.php

@@ -1,22 +1,34 @@
 <?php
+require_once "vars.inc.php";
 ini_set('error_reporting', 0);
+$has_object = 0;
 header('Content-Type: text/plain');
-require_once "vars.inc.php";
 $dsn = $database_type . ':host=' . $database_host . ';dbname=' . $database_name;
 $opt = [
     PDO::ATTR_ERRMODE            => PDO::ERRMODE_EXCEPTION,
     PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
     PDO::ATTR_EMULATE_PREPARES   => false,
 ];
-$pdo = new PDO($dsn, $database_user, $database_pass, $opt);
-$stmt = $pdo->query("SELECT `username` FROM `mailbox` WHERE `wants_tagged_subject` = '1'");
-$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
-while ($row = array_shift($rows)) {
-  echo strtolower(trim($row['username'])) . PHP_EOL;
+try {
+  $pdo = new PDO($dsn, $database_user, $database_pass, $opt);
+  $stmt = $pdo->query("SELECT `username` FROM `mailbox` WHERE `wants_tagged_subject` = '1'");
+  $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
+  while ($row = array_shift($rows)) {
+    $has_object = 1;
+    echo strtolower(trim($row['username'])) . PHP_EOL;
+  }
+  $stmt = $pdo->query("SELECT CONCAT(mailbox.local_part, '@', alias_domain.alias_domain) as `tag_ad` FROM `mailbox` INNER JOIN `alias_domain` ON mailbox.domain = alias_domain.target_domain WHERE mailbox.wants_tagged_subject='1';");
+  $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
+  while ($row = array_shift($rows)) {
+    $has_object = 1;
+    echo strtolower(trim($row['tag_ad'])) . PHP_EOL;
+  }
+  if ($has_object == 0) {
+    echo "dummy@domain.local";
+  }
 }
-$stmt = $pdo->query("SELECT CONCAT(mailbox.local_part, '@', alias_domain.alias_domain) as `tag_ad` FROM `mailbox` INNER JOIN `alias_domain` ON mailbox.domain = alias_domain.target_domain WHERE mailbox.wants_tagged_subject='1';");
-$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
-while ($row = array_shift($rows)) {
-  echo strtolower(trim($row['tag_ad'])) . PHP_EOL;
+catch (PDOException $e) {
+  echo "dummy@domain.local";
+  exit;
 }
 ?>

+ 53 - 37
data/conf/rspamd/lua/rspamd.local.lua

@@ -11,69 +11,85 @@ rspamd_config.MAILCOW_MOO = function (task)
 	return true
 end
 
-local modify_subject_map = rspamd_config:add_map({
-  url = 'http://nginx:8081/tags.php',
+modify_subject_map = rspamd_config:add_map({
+  url = 'http://172.22.1.251:8081/tags.php',
   type = 'map',
   description = 'Map of users to use subject tags for'
 })
 
-local auth_domain_map = rspamd_config:add_map({
-  url = 'http://nginx:8081/authoritative.php',
+auth_domain_map = rspamd_config:add_map({
+  url = 'http://172.22.1.251:8081/authoritative.php',
   type = 'map',
   description = 'Map of domains we are authoritative for'
 })
 
 rspamd_config.ADD_DELIMITER_TAG = {
   callback = function(task)
-    tag = nil
-    local tag_env = nil
-    local tag_to = nil
-
+    local tag = nil
     local util = require("rspamd_util")
     local rspamd_logger = require "rspamd_logger"
-
-    local user_env_tagged = task:get_recipients(1)[1]['user']
-    local user_to_tagged = task:get_recipients(2)[1]['user']
-
+    local user_tagged = task:get_recipients(2)[1]['user']
     local domain = task:get_recipients(1)[1]['domain']
-
-    local user_env, tag_env = user_env_tagged:match("([^+]+)+(.*)")
-    local user_to, tag_to = user_to_tagged:match("([^+]+)+(.*)")
-
+    local user, tag = user_tagged:match("([^+]+)+(.*)")
     local authdomain = auth_domain_map:get_key(domain)
 
-    if tag_env then
-      tag = tag_env
-      user = user_env
-    elseif tag_to then
-      tag = tag_to
-      user = user_env
-    end
-
     if tag and authdomain then
-      rspamd_logger.infox("Domain %s is part of mailcow, start reading tag settings", domain)
+      rspamd_logger.infox("domain: %1, tag: %2", domain, tag)
       local user_untagged = user .. '@' .. domain
-      rspamd_logger.infox("Querying tag settings for user %1", user_untagged)
+      rspamd_logger.infox("querying tag settings for user %1", user_untagged)
       if modify_subject_map:get_key(user_untagged) then
-        rspamd_logger.infox("User wants subject modified for tagged mail")
+        rspamd_logger.infox("found user in map for subject rewrite")
         local sbj = task:get_header('Subject')
-        if tag then
-          rspamd_logger.infox("Found tag %1, will modify subject header", tag)
-          new_sbj = '=?UTF-8?B?' .. tostring(util.encode_base64('[' .. tag .. '] ' .. sbj)) .. '?='
-          task:set_rmilter_reply({
-            remove_headers = {['Subject'] = 1},
-            add_headers = {['Subject'] = new_sbj}
-          })
-        end
+        new_sbj = '=?UTF-8?B?' .. tostring(util.encode_base64('[' .. tag .. '] ' .. sbj)) .. '?='
+        task:set_rmilter_reply({
+          remove_headers = {['Subject'] = 1},
+          add_headers = {['Subject'] = new_sbj}
+        })
       else
-        rspamd_logger.infox("Add X-Moo-Tag header")
+        rspamd_logger.infox("add X-Moo-Tag header")
         task:set_rmilter_reply({
           add_headers = {['X-Moo-Tag'] = 'YES'}
         })
       end
     else
-      rspamd_logger.infox("Skip delimiter handling for untagged message or authenticated user")
+      rspamd_logger.infox("skip delimiter handling for untagged message or authenticated user")
     end
     return false
   end
 }
+
+rspamd_config.MRAPTOR = {
+  callback = function(task)
+    local parts = task:get_parts()
+    local rspamd_logger = require "rspamd_logger"
+    local rspamd_regexp = require "rspamd_regexp"
+
+    if parts then
+      for _,p in ipairs(parts) do
+        local mtype,subtype = p:get_type()
+        local re = rspamd_regexp.create_cached('/(office|word|excel)/i')
+        if re:match(subtype) then
+          local content = tostring(p:get_content())
+          local filename = p:get_filename()
+
+          local file = os.tmpname()
+          f = io.open(file, "a+")
+          f:write(content)
+          f:close()
+
+          local scan = assert(io.popen('PATH=/usr/bin:/usr/local/bin mraptor ' .. file .. '> /dev/null 2>&1; echo $?', 'r'))
+          local result = scan:read('*all')
+          local exit_code = string.match(result, "%d+")
+          rspamd_logger.infox(exit_code)
+          scan:close()
+
+          if exit_code == "20" then
+            rspamd_logger.infox("Reject dangerous macro in office file " .. filename)
+            task:set_pre_result(rspamd_actions['reject'], 'Dangerous macro in office file ' .. filename)
+          end
+
+        end
+      end
+    end
+  end
+}

+ 32 - 4
data/web/call_sogo_ctrl.php

@@ -6,7 +6,7 @@ if (!isset($_SESSION['mailcow_cc_role']) OR !in_array($_SESSION['mailcow_cc_role
 	exit();
 }
 if ($_GET['ACTION'] == "start") {
-	$request = xmlrpc_encode_request("supervisor.startProcessGroup", 'sogo-group', array('encoding'=>'utf-8'));
+	$request = xmlrpc_encode_request("supervisor.startProcess", 'reconf-domains', array('encoding'=>'utf-8'));
 	$context = stream_context_create(array('http' => array(
 	'method' => "POST",
 	'header' => "Content-Length: " . strlen($request),
@@ -18,11 +18,25 @@ if ($_GET['ACTION'] == "start") {
 		echo '<b><span class="pull-right text-warning">' . $response['faultString'] . '</span></b>';
 	}
 	else {
-		echo '<b><span class="pull-right text-success">OK</span></b>';
+    sleep(4);
+    $request = xmlrpc_encode_request("supervisor.startProcess", 'sogo', array('encoding'=>'utf-8'));
+    $context = stream_context_create(array('http' => array(
+    'method' => "POST",
+    'header' => "Content-Length: " . strlen($request),
+    'content' => $request
+    )));
+    $file = @file_get_contents("http://sogo:9191/RPC2", false, $context) or die("Cannot connect to $remote_server:$listener_port");
+    $response = xmlrpc_decode($file);
+    if (isset($response['faultString'])) {
+      echo '<b><span class="pull-right text-warning">' . $response['faultString'] . '</span></b>';
+    }
+    else {
+      echo '<b><span class="pull-right text-success">OK</span></b>';
+    }
 	}
 }
 elseif ($_GET['ACTION'] == "stop") {
-	$request = xmlrpc_encode_request("supervisor.stopProcessGroup", 'sogo-group', array('encoding'=>'utf-8'));
+	$request = xmlrpc_encode_request("supervisor.stopProcess", 'sogo', array('encoding'=>'utf-8'));
 	$context = stream_context_create(array('http' => array(
 	'method' => "POST",
 	'header' => "Content-Length: " . strlen($request),
@@ -34,7 +48,21 @@ elseif ($_GET['ACTION'] == "stop") {
 		echo '<b><span class="pull-right text-warning">' . $response['faultString'] . '</span></b>';
 	}
 	else {
-		echo '<b><span class="pull-right text-success">OK</span></b>';
+    sleep(1);
+    $request = xmlrpc_encode_request("supervisor.stopProcess", 'reconf-domains', array('encoding'=>'utf-8'));
+    $context = stream_context_create(array('http' => array(
+    'method' => "POST",
+    'header' => "Content-Length: " . strlen($request),
+    'content' => $request
+    )));
+    $file = @file_get_contents("http://sogo:9191/RPC2", false, $context) or die("Cannot connect to $remote_server:$listener_port");
+    $response = xmlrpc_decode($file);
+    if (isset($response['faultString'])) {
+      echo '<b><span class="pull-right text-warning">' . $response['faultString'] . '</span></b>';
+    }
+    else {
+      echo '<b><span class="pull-right text-success">OK</span></b>';
+    }
 	}
 }
 ?>

+ 1 - 1
data/web/edit.php

@@ -416,7 +416,7 @@ if (isset($_SESSION['mailcow_cc_role']) && ($_SESSION['mailcow_cc_role'] == "adm
         <div class="form-group">
           <label class="control-label col-sm-2" for="sender_acl"><?=$lang['edit']['sender_acl'];?>:</label>
           <div class="col-sm-10">
-            <select data-width="50%" style="width:100%" id="sender_acl" name="sender_acl[]" size="10" multiple>
+            <select data-width="100%" style="width:100%" id="sender_acl" name="sender_acl[]" size="10" multiple>
             <?php
             $sender_acl_handles = mailbox_get_sender_acl_handles($mailbox);
 

+ 62 - 20
data/web/inc/functions.inc.php

@@ -253,6 +253,8 @@ function edit_admin_account($postarray) {
 	}
 	$username       = $postarray['admin_user'];
 	$username_now   = $_SESSION['mailcow_cc_username'];
+  $password       = $postarray['admin_pass'];
+  $password2      = $postarray['admin_pass2'];
 
 	if (!ctype_alnum(str_replace(array('_', '.', '-'), '', $username)) || empty ($username)) {
 		$_SESSION['return'] = array(
@@ -262,15 +264,22 @@ function edit_admin_account($postarray) {
 		return false;
 	}
 
-	if (!empty($postarray['admin_pass']) && !empty($postarray['admin_pass2'])) {
-		if ($postarray['admin_pass'] != $postarray['admin_pass2']) {
+	if (!empty($password) && !empty($password2)) {
+    if (!preg_match('/' . $GLOBALS['PASSWD_REGEP'] . '/', $password)) {
+      $_SESSION['return'] = array(
+        'type' => 'danger',
+        'msg' => sprintf($lang['danger']['password_complexity'])
+      );
+      return false;
+    }
+		if ($password != $password2) {
 			$_SESSION['return'] = array(
 				'type' => 'danger',
 				'msg' => sprintf($lang['danger']['password_mismatch'])
 			);
 			return false;
 		}
-		$password_hashed = hash_password($postarray['admin_pass']);
+		$password_hashed = hash_password($password);
 		try {
 			$stmt = $pdo->prepare("UPDATE `admin` SET 
 				`modified` = :modified,
@@ -585,9 +594,7 @@ function edit_user_account($postarray) {
 				);
 				return false;
 			}
-			if (strlen($password_new) < "6" ||
-				!preg_match('/[A-Za-z]/', $password_new) ||
-				!preg_match('/[0-9]/', $password_new)) {
+			if (!preg_match('/' . $GLOBALS['PASSWD_REGEP'] . '/', $password_new)) {
 					$_SESSION['return'] = array(
 						'type' => 'danger',
 						'msg' => sprintf($lang['danger']['password_complexity'])
@@ -1459,8 +1466,11 @@ function user_get_alias_details($username) {
   }
   try {
     $data['address'] = $username;
-    $stmt = $pdo->prepare("SELECT IFNULL(GROUP_CONCAT(`address` SEPARATOR ', '), '&#10008;') AS `aliases` FROM `alias` WHERE `goto` = :username_goto AND `address` NOT LIKE '@%' AND `address` != :username_address");
-    $stmt->execute(array(':username_goto' => $username, ':username_address' => $username));
+    $stmt = $pdo->prepare("SELECT IFNULL(GROUP_CONCAT(`address` SEPARATOR ', '), '&#10008;') AS `aliases` FROM `alias`
+      WHERE `goto` LIKE :username_goto
+      AND `address` NOT LIKE '@%'
+      AND `address` != :username_address");
+    $stmt->execute(array(':username_goto' => '%' . $username . '%', ':username_address' => $username));
     $run = $stmt->fetchAll(PDO::FETCH_ASSOC);
     while ($row = array_shift($run)) {
       $data['aliases'] = $row['aliases'];
@@ -1485,8 +1495,8 @@ function user_get_alias_details($username) {
     while ($row = array_shift($run)) {
       $data['aliases_send_as_all'] = $row['send_as'];
     }
-    $stmt = $pdo->prepare("SELECT IFNULL(GROUP_CONCAT(`address` SEPARATOR ', '), '&#10008;') as `address` FROM `alias` WHERE `goto` = :username AND `address` LIKE '@%';");
-    $stmt->execute(array(':username' => $username));
+    $stmt = $pdo->prepare("SELECT IFNULL(GROUP_CONCAT(`address` SEPARATOR ', '), '&#10008;') as `address` FROM `alias` WHERE `goto` LIKE :username AND `address` LIKE '@%';");
+    $stmt->execute(array(':username' => '%' . $username . '%'));
     $run = $stmt->fetchAll(PDO::FETCH_ASSOC);
     while ($row = array_shift($run)) {
       $data['is_catch_all'] = $row['address'];
@@ -1515,7 +1525,7 @@ function add_domain_admin($postarray) {
 	global $pdo;
 	$username		= strtolower(trim($postarray['username']));
 	$password		= $postarray['password'];
-	$password2		= $postarray['password2'];
+	$password2  = $postarray['password2'];
 	isset($postarray['active']) ? $active = '1' : $active = '0';
 	if ($_SESSION['mailcow_cc_role'] != "admin") {
 		$_SESSION['return'] = array(
@@ -1571,6 +1581,13 @@ function add_domain_admin($postarray) {
 		}
 	}
 	if (!empty($password) && !empty($password2)) {
+    if (!preg_match('/' . $GLOBALS['PASSWD_REGEP'] . '/', $password)) {
+      $_SESSION['return'] = array(
+        'type' => 'danger',
+        'msg' => sprintf($lang['danger']['password_complexity'])
+      );
+      return false;
+    }
 		if ($password != $password2) {
 			$_SESSION['return'] = array(
 				'type' => 'danger',
@@ -1711,6 +1728,7 @@ function get_domain_admins() {
 }
 function get_domain_admin_details($domain_admin) {
 	global $pdo;
+
 	global $lang;
   $domainadmindata = array();
 	if (isset($domain_admin) && $_SESSION['mailcow_cc_role'] != "admin") {
@@ -2169,6 +2187,13 @@ function edit_domain_admin($postarray) {
     }
 
     if (!empty($password) && !empty($password2)) {
+      if (!preg_match('/' . $GLOBALS['PASSWD_REGEP'] . '/', $password)) {
+        $_SESSION['return'] = array(
+          'type' => 'danger',
+          'msg' => sprintf($lang['danger']['password_complexity'])
+        );
+        return false;
+      }
       if ($password != $password2) {
         $_SESSION['return'] = array(
           'type' => 'danger',
@@ -2262,14 +2287,12 @@ function edit_domain_admin($postarray) {
         );
         return false;
       }
-      if (strlen($password_new) < "6" ||
-        !preg_match('/[A-Za-z]/', $password_new) ||
-        !preg_match('/[0-9]/', $password_new)) {
-          $_SESSION['return'] = array(
-            'type' => 'danger',
-            'msg' => sprintf($lang['danger']['password_complexity'])
-          );
-          return false;
+      if (!preg_match('/' . $GLOBALS['PASSWD_REGEP'] . '/', $password_new)) {
+        $_SESSION['return'] = array(
+          'type' => 'danger',
+          'msg' => sprintf($lang['danger']['password_complexity'])
+        );
+        return false;
       }
       $password_hashed = hash_password($password_new);
       try {
@@ -2991,6 +3014,13 @@ function mailbox_add_mailbox($postarray) {
 	}
 
 	if (!empty($password) && !empty($password2)) {
+    if (!preg_match('/' . $GLOBALS['PASSWD_REGEP'] . '/', $password)) {
+      $_SESSION['return'] = array(
+        'type' => 'danger',
+        'msg' => sprintf($lang['danger']['password_complexity'])
+      );
+      return false;
+    }
 		if ($password != $password2) {
 			$_SESSION['return'] = array(
 				'type' => 'danger',
@@ -3735,6 +3765,13 @@ function mailbox_edit_mailbox($postarray) {
     }
   }
 	if (!empty($password) && !empty($password2)) {
+    if (!preg_match('/' . $GLOBALS['PASSWD_REGEP'] . '/', $password)) {
+      $_SESSION['return'] = array(
+        'type' => 'danger',
+        'msg' => sprintf($lang['danger']['password_complexity'])
+      );
+      return false;
+    }
 		if ($password != $password2) {
 			$_SESSION['return'] = array(
 				'type' => 'danger',
@@ -4313,9 +4350,13 @@ function mailbox_get_mailbox_details($mailbox) {
     $DomainQuota  = $stmt->fetch(PDO::FETCH_ASSOC);
 
     $stmt = $pdo->prepare("SELECT COALESCE(SUM(`quota`), 0) as `in_use` FROM `mailbox` WHERE `kind` NOT REGEXP 'location|thing|group' AND `domain` = :domain AND `username` != :username");
-    $stmt->execute(array(':domain' => $row['domain'], ':username' => $row['username']));
+    $stmt->execute(array(':domain' => $row['domain'], ':username' => $mailbox));
     $MailboxUsage	= $stmt->fetch(PDO::FETCH_ASSOC);
 
+    $stmt = $pdo->prepare("SELECT IFNULL(COUNT(`address`), 0) AS `sa_count` FROM `spamalias` WHERE `goto` = :address AND `validity` >= :unixnow");
+    $stmt->execute(array(':address' => $mailbox, ':unixnow' => time()));
+    $SpamaliasUsage	= $stmt->fetch(PDO::FETCH_ASSOC);
+
     $mailboxdata['max_new_quota'] = ($DomainQuota['quota'] * 1048576) - $MailboxUsage['in_use'];
     if ($mailboxdata['max_new_quota'] > ($DomainQuota['maxquota'] * 1048576)) {
       $mailboxdata['max_new_quota'] = ($DomainQuota['maxquota'] * 1048576);
@@ -4331,6 +4372,7 @@ function mailbox_get_mailbox_details($mailbox) {
     $mailboxdata['quota_used'] = intval($row['bytes']);
     $mailboxdata['percent_in_use'] = round((intval($row['bytes']) / intval($row['quota'])) * 100);
     $mailboxdata['messages'] = $row['messages'];
+    $mailboxdata['spam_aliases'] = $SpamaliasUsage['sa_count'];
     if ($mailboxdata['percent_in_use'] >= 90) {
       $mailboxdata['percent_class'] = "danger";
     }

+ 2 - 0
data/web/inc/vars.inc.php

@@ -35,4 +35,6 @@ $DEFAULT_LANG = "en";
 // See https://bootswatch.com/
 $DEFAULT_THEME = "lumen";
 
+// Password complexity as regular expression
+$PASSWD_REGEP = '.{4,}';
 ?>

+ 4 - 4
data/web/js/mailbox.js

@@ -18,10 +18,10 @@ $(document).ready(function() {
 			return this.each(function(){
 				$(this).on('keyup', function(e){
 					var $this = $(this),
-                        search = $this.val().toLowerCase(),
-                        target = $this.attr('data-filters'),
-                        $target = $(target),
-                        $rows = $target.find('tbody #data');
+            search = $this.val().toLowerCase(),
+            target = $this.attr('data-filters'),
+            $target = $(target),
+            $rows = $target.find('tbody #data');
 					$target.find('tbody .filterTable_no_results').remove();
 					if(search == '') {
 						$target.find('tbody #no-data').show();

+ 7 - 4
data/web/lang/lang.de.php

@@ -57,7 +57,7 @@ $lang['danger']['exit_code_not_null'] = 'Fehler: Exit-Code ist %d';
 $lang['danger']['mailbox_not_available'] = 'Mailbox nicht verfügbar';
 $lang['danger']['username_invalid'] = 'Benutzername kann nicht verwendet werden';
 $lang['danger']['password_mismatch'] = 'Passwort-Wiederholung stimmt nicht überein';
-$lang['danger']['password_complexity'] = 'Passwort entspricht nicht den Vorgaben (Klein- und Großschreibung und mindestens eine Ziffer, mindestens 6 Zeichen lang)';
+$lang['danger']['password_complexity'] = 'Passwort entspricht nicht den Richtlinien';
 $lang['danger']['password_empty'] = 'Passwort darf nicht leer sein';
 $lang['danger']['login_failed'] = 'Anmeldung fehlgeschlagen';
 $lang['danger']['mailbox_invalid'] = 'Mailboxname ist ungültig';
@@ -92,6 +92,8 @@ $lang['danger']['spam_alias_max_exceeded'] = 'Maximale Anzahl an Spam-Alias-Adre
 $lang['danger']['validity_missing'] = 'Bitte geben Sie eine Gültigkeitsdauer an';
 $lang['user']['on'] = 'Ein';
 $lang['user']['off'] = 'Aus';
+$lang['user']['messages'] = "Nachrichten";
+$lang['user']['in_use'] = "Verwendet";
 $lang['user']['user_change_fn'] = '';
 $lang['user']['user_settings'] = 'Benutzereinstellungen';
 $lang['user']['mailbox_settings'] = 'Mailbox-Einstellungen';
@@ -108,8 +110,8 @@ $lang['user']['alias'] = 'Alias';
 $lang['user']['aliases'] = 'Aliasse';
 $lang['user']['domain_aliases'] = 'Domain-Alias Adressen';
 $lang['user']['is_catch_all'] = 'Ist Catch-All Adresse für Domain(s)';
-$lang['user']['aliases_also_send_as'] = 'Darf außerdem versenden als';
-$lang['user']['aliases_send_as_all'] = 'Absender für folgende Domains nicht prüfen';
+$lang['user']['aliases_also_send_as'] = 'Darf außerdem versenden als Benutzer';
+$lang['user']['aliases_send_as_all'] = 'Absender für folgende Domains und zugehörige Alias-Domains nicht prüfen';
 $lang['user']['alias_create_random'] = 'Zufälligen Alias generieren';
 $lang['user']['alias_extend_all'] = 'Gültigkeit +1h';
 $lang['user']['alias_valid_until'] = 'Gültig bis';
@@ -207,6 +209,7 @@ $lang['header']['logged_in_as_logout'] = 'Eingeloggt als <b>%s</b> (abmelden)';
 $lang['header']['logged_in_as_logout_dual'] = 'Eingeloggt als <b>%s <span class="text-info">[%s]</span></b>';
 $lang['header']['locale'] = 'Sprache';
 $lang['mailbox']['domain'] = 'Domain';
+$lang['mailbox']['spam_aliases'] = 'Temp. Alias';
 $lang['mailbox']['alias'] = 'Alias';
 $lang['mailbox']['aliases'] = 'Aliasse';
 $lang['mailbox']['multiple_bookings'] = 'Mehrfachbuchen';
@@ -307,7 +310,7 @@ $lang['edit']['dkim_txt_name'] = 'TXT-Record Name:';
 $lang['edit']['dkim_txt_value'] = 'TXT-Record Wert:';
 $lang['edit']['previous'] = 'Vorherige Seite';
 $lang['edit']['unchanged_if_empty'] = 'Unverändert, wenn leer';
-$lang['edit']['dont_check_sender_acl'] = 'Absender für Domain %s nicht prüfen';
+$lang['edit']['dont_check_sender_acl'] = 'Absender für Domain %s u. Alias-Dom. nicht prüfen';
 $lang['edit']['multiple_bookings'] = 'Mehrfaches Buchen';
 $lang['edit']['kind'] = 'Art';
 $lang['edit']['resource'] = 'Ressource';

+ 8 - 4
data/web/lang/lang.en.php

@@ -59,7 +59,7 @@ $lang['danger']['exit_code_not_null'] = "Error: Exit code was %d";
 $lang['danger']['mailbox_not_available'] = "Mailbox not available";
 $lang['danger']['username_invalid'] = "Username cannot be used";
 $lang['danger']['password_mismatch'] = "Confirmation password is not identical";
-$lang['danger']['password_complexity'] = "Password does not meet requirements (upper and lowercase letters and at least one number, min. 6 characters long)";
+$lang['danger']['password_complexity'] = "Password does not meet the policy";
 $lang['danger']['password_empty'] = "Password must not be empty";
 $lang['danger']['login_failed'] = "Login failed";
 $lang['danger']['mailbox_invalid'] = "Mailbox name is invalid";
@@ -94,6 +94,8 @@ $lang['danger']['spam_alias_max_exceeded'] = "Max. allowed spam alias addresses
 $lang['danger']['validity_missing'] = 'Please assign a period of validity';
 $lang['user']['on'] = "On";
 $lang['user']['off'] = "Off";
+$lang['user']['messages'] = "messages"; // "123 messages"
+$lang['user']['in_use'] = "Used";
 $lang['user']['user_change_fn'] = "";
 $lang['user']['user_settings'] = 'User settings';
 $lang['user']['mailbox_settings'] = 'Mailbox settings';
@@ -110,8 +112,8 @@ $lang['user']['alias'] = 'Alias';
 $lang['user']['aliases'] = 'Aliases';
 $lang['user']['domain_aliases'] = 'Domain alias addresses';
 $lang['user']['is_catch_all'] = 'Catch-all for domain/s';
-$lang['user']['aliases_also_send_as'] = 'Also allowed to send as';
-$lang['user']['aliases_send_as_all'] = 'Do not check sender access for following domain/s';
+$lang['user']['aliases_also_send_as'] = 'Also allowed to send as user';
+$lang['user']['aliases_send_as_all'] = 'Do not check sender access for the following domain(s) and its alias domains';
 $lang['user']['alias_create_random'] = 'Generate random alias';
 $lang['user']['alias_extend_all'] = 'Extend aliases by 1 hour';
 $lang['user']['alias_valid_until'] = 'Valid until';
@@ -209,6 +211,7 @@ $lang['header']['logged_in_as_logout'] = 'Logged in as <b>%s</b> (logout)';
 $lang['header']['logged_in_as_logout_dual'] = 'Logged in as <b>%s <span class="text-info">[%s]</span></b>';
 $lang['header']['locale'] = 'Language';
 $lang['mailbox']['domain'] = 'Domain';
+$lang['mailbox']['spam_aliases'] = 'Temp. alias';
 $lang['mailbox']['multiple_bookings'] = 'Multiple bookings';
 $lang['mailbox']['kind'] = 'Kind';
 $lang['mailbox']['description'] = 'Description';
@@ -245,6 +248,7 @@ $lang['mailbox']['add_domain_alias'] = 'Add domain alias';
 $lang['mailbox']['add_mailbox'] = 'Add mailbox';
 $lang['mailbox']['add_resource'] = 'Add resource';
 $lang['mailbox']['add_alias'] = 'Add alias';
+$lang['mailbox']['add_domain_record_first'] = 'Please add a domain first';
 
 $lang['info']['no_action'] = 'No action applicable';
 
@@ -310,7 +314,7 @@ $lang['edit']['dkim_txt_name'] = 'TXT record name:';
 $lang['edit']['dkim_txt_value'] = 'TXT record value:';
 $lang['edit']['previous'] = 'Previous page';
 $lang['edit']['unchanged_if_empty'] = 'If unchanged leave blank';
-$lang['edit']['dont_check_sender_acl'] = 'Do not check sender for domain %s';
+$lang['edit']['dont_check_sender_acl'] = "Disable sender check for domain %s + alias domains";
 $lang['edit']['multiple_bookings'] = 'Multiple bookings';
 $lang['edit']['kind'] = 'Kind';
 $lang['edit']['resource'] = 'Resource';

+ 43 - 17
data/web/mailbox.php

@@ -91,7 +91,7 @@ $_SESSION['return_to'] = $_SERVER['REQUEST_URI'];
             endforeach;
             else:
 							?>
-              <tr id="no-data"><td colspan="8" style="text-align: center; font-style: italic;"><?=$lang['mailbox']['no_record_single'];?></td></tr>
+              <tr id="no-data"><td colspan="999" style="text-align: center; font-style: italic;"><?=$lang['mailbox']['no_record_single'];?></td></tr>
             <?php
             endif;
             ?>
@@ -101,7 +101,7 @@ $_SESSION['return_to'] = $_SERVER['REQUEST_URI'];
 						?>
 					<tfoot>
 						<tr id="no-data">
-							<td colspan="8" style="text-align: center; font-style: normal; border-top: 1px solid #e7e7e7;">
+							<td colspan="999" style="text-align: center; font-style: normal; border-top: 1px solid #e7e7e7;">
 								<a href="/add.php?domain"><?=$lang['mailbox']['add_domain'];?></a>
 							</td>
 						</tr>
@@ -137,6 +137,7 @@ $_SESSION['return_to'] = $_SERVER['REQUEST_URI'];
 							<th class="sort-table" style="min-width: 98px;"><?=$lang['mailbox']['fname'];?></th>
 							<th class="sort-table" style="min-width: 86px;"><?=$lang['mailbox']['domain'];?></th>
 							<th class="sort-table" style="min-width: 75px;"><?=$lang['mailbox']['quota'];?></th>
+							<th class="sort-table" style="min-width: 75px;"><?=$lang['mailbox']['spam_aliases'];?></th>
 							<th class="sort-table" style="min-width: 99px;"><?=$lang['mailbox']['in_use'];?></th>
 							<th class="sort-table" style="min-width: 100px;"><?=$lang['mailbox']['msg_num'];?></th>
 							<th class="sort-table" style="min-width: 76px;"><?=$lang['mailbox']['active'];?></th>
@@ -145,6 +146,7 @@ $_SESSION['return_to'] = $_SERVER['REQUEST_URI'];
 					</thead>
 					<tbody>
 						<?php
+					if (!empty($domains)) {
             foreach (mailbox_get_domains() as $domain) {
               $mailboxes = mailbox_get_mailboxes($domain);
               if (!empty($mailboxes)) {
@@ -156,6 +158,7 @@ $_SESSION['return_to'] = $_SERVER['REQUEST_URI'];
 							<td><?=htmlspecialchars($mailboxdata['name'], ENT_QUOTES, 'UTF-8');?></td>
 							<td><?=htmlspecialchars($mailboxdata['domain']);?></td>
 							<td><?=formatBytes($mailboxdata['quota_used'], 2);?> / <?=formatBytes($mailboxdata['quota'], 2);?></td>
+							<td><?=$mailboxdata['spam_aliases'];?></td>
 							<td style="min-width:120px;">
 								<div class="progress">
 									<div class="progress-bar progress-bar-<?=$mailboxdata['percent_class'];?>" role="progressbar" aria-valuenow="<?=$mailboxdata['percent_in_use'];?>" aria-valuemin="0" aria-valuemax="100" style="min-width:2em;width: <?=$mailboxdata['percent_in_use'];?>%;">
@@ -180,15 +183,20 @@ $_SESSION['return_to'] = $_SERVER['REQUEST_URI'];
               }
               else {
                   ?>
-                  <tr id="no-data"><td colspan="8" style="text-align: center; font-style: italic;"><?=sprintf($lang['mailbox']['no_record'], $domain);?></td></tr>
+                  <tr id="no-data"><td colspan="999" style="text-align: center; font-style: italic;"><?=sprintf($lang['mailbox']['no_record'], $domain);?></td></tr>
                   <?php
               }
             }
+					} else {
+					?>
+						<tr id="no-data"><td colspan="999" style="text-align: center; font-style: italic;"><?=$lang['mailbox']['add_domain_record_first'];?></td></tr>
+						<?php
+					}
 						?>
 					</tbody>
 					<tfoot>
 						<tr id="no-data">
-							<td colspan="8" style="text-align: center; border-top: 1px solid #e7e7e7;">
+							<td colspan="999" style="text-align: center; border-top: 1px solid #e7e7e7;">
 								<a href="/add.php?mailbox"><?=$lang['mailbox']['add_mailbox'];?></a>
 							</td>
 						</tr>
@@ -227,6 +235,7 @@ $_SESSION['return_to'] = $_SERVER['REQUEST_URI'];
 					</thead>
 					<tbody>
 						<?php
+					if (!empty($domains)) {
             foreach (mailbox_get_domains() as $domain) {
               $resources = mailbox_get_resources($domain);
               if (!empty($resources)) {
@@ -251,15 +260,20 @@ $_SESSION['return_to'] = $_SERVER['REQUEST_URI'];
               }
               else {
                   ?>
-                  <tr id="no-data"><td colspan="8" style="text-align: center; font-style: italic;"><?=sprintf($lang['mailbox']['no_record'], $domain);?></td></tr>
+                  <tr id="no-data"><td colspan="999" style="text-align: center; font-style: italic;"><?=sprintf($lang['mailbox']['no_record'], $domain);?></td></tr>
                   <?php
               }
             }
+					} else {
+						?>
+						<tr id="no-data"><td colspan="999" style="text-align: center; font-style: italic;"><?=$lang['mailbox']['add_domain_record_first'];?></td></tr>
+						<?php
+					}
 						?>
 					</tbody>
 					<tfoot>
 						<tr id="no-data">
-							<td colspan="8" style="text-align: center; border-top: 1px solid #e7e7e7;">
+							<td colspan="999" style="text-align: center; border-top: 1px solid #e7e7e7;">
 								<a href="/add.php?resource"><?=$lang['mailbox']['add_resource'];?></a>
 							</td>
 						</tr>
@@ -296,6 +310,7 @@ $_SESSION['return_to'] = $_SERVER['REQUEST_URI'];
 					</thead>
 					<tbody>
 					<?php
+				if (!empty($domains)) {
           foreach (mailbox_get_domains() as $domain) {
             $alias_domains = mailbox_get_alias_domains($domain);
             if (!empty($alias_domains)) {
@@ -318,15 +333,20 @@ $_SESSION['return_to'] = $_SERVER['REQUEST_URI'];
             }
             else {
 	        ?>
-                  <tr id="no-data"><td colspan="8" style="text-align: center; font-style: italic;"><?=sprintf($lang['mailbox']['no_record'], $domain);?></td></tr>
+                  <tr id="no-data"><td colspan="999" style="text-align: center; font-style: italic;"><?=sprintf($lang['mailbox']['no_record'], $domain);?></td></tr>
 	        <?php
             }
           }
+				} else {
           ?>
+						<tr id="no-data"><td colspan="999" style="text-align: center; font-style: italic;"><?=$lang['mailbox']['add_domain_record_first'];?></td></tr>
+					<?php
+				}
+					?>
 					</tbody>
 					<tfoot>
 						<tr id="no-data">
-							<td colspan="8" style="text-align: center; border-top: 1px solid #e7e7e7;">
+							<td colspan="999" style="text-align: center; border-top: 1px solid #e7e7e7;">
 								<a href="/add.php?aliasdomain"><?=$lang['mailbox']['add_domain_alias'];?></a>
 							</td>
 						</tr>
@@ -365,6 +385,7 @@ $_SESSION['return_to'] = $_SERVER['REQUEST_URI'];
 					</thead>
 					<tbody>
 					<?php
+				if (!empty($domains)) {
           foreach (array_merge(mailbox_get_domains(), mailbox_get_alias_domains()) as $domain) {
             $aliases = mailbox_get_aliases($domain);
             if (!empty($aliases)) {
@@ -392,19 +413,24 @@ $_SESSION['return_to'] = $_SERVER['REQUEST_URI'];
 							</td>
 						</tr>
 						<?php
-                }
-              }
-              else {
-                  ?>
-                  <tr id="no-data"><td colspan="8" style="text-align: center; font-style: italic;"><?=sprintf($lang['mailbox']['no_record'], $domain);?></td></tr>
-                  <?php
-              }
-            }
+							}
+						}
+						else {
+								?>
+								<tr id="no-data"><td colspan="999" style="text-align: center; font-style: italic;"><?=sprintf($lang['mailbox']['no_record'], $domain);?></td></tr>
+								<?php
+						}
+          }
+				} else {
+						?>
+						<tr id="no-data"><td colspan="999" style="text-align: center; font-style: italic;"><?=$lang['mailbox']['add_domain_record_first'];?></td></tr>
+						<?php
+				}
 						?>
 					</tbody>
 					<tfoot>
 						<tr id="no-data">
-							<td colspan="8" style="text-align: center; border-top: 1px solid #e7e7e7;">
+							<td colspan="999" style="text-align: center; border-top: 1px solid #e7e7e7;">
 								<a href="/add.php?alias"><?=$lang['mailbox']['add_alias'];?></a>
 							</td>
 						</tr>

+ 15 - 2
data/web/user.php

@@ -31,7 +31,7 @@ if (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'doma
               foreach ($tfa_data['additional'] as $key_info): ?>
                 <form style="display:inline;" method="post">
                 <input type="hidden" name="unset_tfa_key" value="<?=$key_info['id'];?>" />
-                <div class="label label-default">🔑 <?=$key_info['key_id'];?> <a href="#" style="font-weight:bold;color:white" onClick="$(this).closest('form').submit()">[<?=strtolower($lang['admin']['remove']);?>]</a></div>
+                <div class="label label-default">?? <?=$key_info['key_id'];?> <a href="#" style="font-weight:bold;color:white" onClick="$(this).closest('form').submit()">[<?=strtolower($lang['admin']['remove']);?>]</a></div>
               </form>
               <?php endforeach;
               endif;?>
@@ -63,6 +63,7 @@ elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == '
 	$_SESSION['return_to'] = $_SERVER['REQUEST_URI'];
 	$username = $_SESSION['mailcow_cc_username'];
 	$get_tls_policy = get_tls_policy($_SESSION['mailcow_cc_username']);
+  $mailboxdata = mailbox_get_mailbox_details($username);
 ?>
 <div class="container">
 <h3><?=$lang['user']['user_settings'];?></h3>
@@ -109,6 +110,18 @@ elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == '
     </div>
   </div>
   <hr>
+  <div class="row">
+    <div class="col-md-3 col-xs-5 text-right"><?=$lang['user']['in_use'];?>:</div>
+    <div class="col-md-5 col-xs-7">
+      <div class="progress">
+        <div class="progress-bar progress-bar-<?=$mailboxdata['percent_class'];?>" role="progressbar" aria-valuenow="<?=$mailboxdata['percent_in_use'];?>" aria-valuemin="0" aria-valuemax="100" style="min-width:2em;width: <?=$mailboxdata['percent_in_use'];?>%;">
+          <?=$mailboxdata['percent_in_use'];?>%
+        </div>
+      </div>
+      <p><?=formatBytes($mailboxdata['quota_used'], 2);?> / <?=formatBytes($mailboxdata['quota'], 2);?>, <?=$mailboxdata['messages'];?> <?=$lang['user']['messages'];?></p>
+    </div>
+  </div>
+  <hr>
   <?php // Show tagging options ?>
   <form class="form-horizontal" role="form" method="post">
   <?php $get_tagging_options = get_delimiter_action()['wants_tagged_subject'];?>
@@ -402,7 +415,7 @@ elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == '
 				<th class="sort-table" style="min-width: 35px;"><?=$lang['user']['interval'];?></th>
 				<th class="sort-table" style="min-width: 35px;"><?=$lang['user']['last_run'];?></th>
 				<th class="sort-table" style="min-width: 35px;">Log</th>
-				<th class="sort-table" style="max-width: 35px;"><?=$lang['user']['active'];?></th>
+				<th class="sort-table" style="max-width: 95px;"><?=$lang['user']['active'];?></th>
 				<th style="text-align: right; min-width: 200px;" data-sortable="false"><?=$lang['user']['action'];?></th>
 			</tr>
 			</thead>

+ 36 - 14
docker-compose.yml

@@ -1,19 +1,20 @@
 version: '2.1'
 
 services:
-    pdns-mailcow:
-      image: andryyy/mailcow-dockerized:pdns
+    bind9-mailcow:
+      image: resystit/bind9
+      command: "named -c /etc/bind/named.conf -g -u named -4"
       depends_on:
         mysql-mailcow:
           condition: service_healthy
       volumes:
-        - ./data/conf/pdns/:/etc/powerdns/
+        - ./data/conf/bind9/named.conf:/etc/bind/named.conf
       restart: always
       networks:
         mailcow-network:
           ipv4_address: 172.22.1.254
           aliases:
-            - pdns
+            - bind9
 
     mysql-mailcow:
       image: mariadb:10.1
@@ -42,7 +43,7 @@ services:
     redis-mailcow:
       image: redis
       depends_on:
-        - pdns-mailcow
+        - bind9-mailcow
       volumes:
         - redis-vol-1:/data/
       restart: always
@@ -56,8 +57,10 @@ services:
 
     rspamd-mailcow:
       image: andryyy/mailcow-dockerized:rspamd
+      build: ./data/Dockerfiles/rspamd
       depends_on:
-        - nginx-mailcow
+        nginx-mailcow:
+          condition: service_healthy
       volumes:
         - ./data/conf/rspamd/override.d/:/etc/rspamd/override.d:ro
         - ./data/conf/rspamd/local.d/:/etc/rspamd/local.d:ro
@@ -76,9 +79,10 @@ services:
 
     php-fpm-mailcow:
       image: andryyy/mailcow-dockerized:phpfpm
+      build: ./data/Dockerfiles/php-fpm
       command: "php-fpm -d date.timezone=${TZ}"
       depends_on:
-        - pdns-mailcow
+        - bind9-mailcow
       volumes:
         - ./data/web:/web:ro
         - ./data/conf/rspamd/dynmaps:/dynmaps:ro
@@ -99,8 +103,9 @@ services:
 
     sogo-mailcow:
       image: andryyy/mailcow-dockerized:sogo
+      build: ./data/Dockerfiles/sogo
       depends_on:
-        - pdns-mailcow
+        - bind9-mailcow
       environment:
         - DBNAME=${DBNAME}
         - DBUSER=${DBUSER}
@@ -121,8 +126,9 @@ services:
 
     rmilter-mailcow:
       image: andryyy/mailcow-dockerized:rmilter
+      build: ./data/Dockerfiles/rmilter
       depends_on:
-        - pdns-mailcow
+        - bind9-mailcow
       volumes:
         - ./data/conf/rmilter/:/etc/rmilter.conf.d/:ro
       restart: always
@@ -136,8 +142,9 @@ services:
 
     dovecot-mailcow:
       image: andryyy/mailcow-dockerized:dovecot
+      build: ./data/Dockerfiles/dovecot
       depends_on:
-        - pdns-mailcow
+        - bind9-mailcow
       volumes:
         - ./data/conf/dovecot:/etc/dovecot
         - ./data/assets/ssl:/etc/ssl/mail/:ro
@@ -165,11 +172,13 @@ services:
 
     postfix-mailcow:
       image: andryyy/mailcow-dockerized:postfix
+      build: ./data/Dockerfiles/postfix
       depends_on:
-        - pdns-mailcow
+        - bind9-mailcow
       volumes:
         - ./data/conf/postfix:/opt/postfix/conf
         - ./data/assets/ssl:/etc/ssl/mail/:ro
+        - postfix-vol-1:/var/spool/postfix
       environment:
         - DBNAME=${DBNAME}
         - DBUSER=${DBUSER}
@@ -191,7 +200,7 @@ services:
     memcached-mailcow:
       image: memcached
       depends_on:
-        - pdns-mailcow
+        - bind9-mailcow
       restart: always
       dns:
         - 172.22.1.254
@@ -206,9 +215,19 @@ services:
         - sogo-mailcow
         - php-fpm-mailcow
       image: nginx:mainline
-      command: /bin/bash -c "envsubst < /etc/nginx/conf.d/listen.template > /etc/nginx/conf.d/listen.active && nginx -g 'daemon off;'"
+      healthcheck:
+        test: ["CMD", "ping", "php-fpm-mailcow", "-c", "10"]
+        interval: 10s
+        timeout: 30s
+        retries: 5
+      command: /bin/bash -c "envsubst < /etc/nginx/conf.d/templates/listen_plain.template > /etc/nginx/conf.d/listen_plain.active &&
+        envsubst < /etc/nginx/conf.d/templates/listen_ssl.template > /etc/nginx/conf.d/listen_ssl.active &&
+        envsubst < /etc/nginx/conf.d/templates/server_name.template > /etc/nginx/conf.d/server_name.active &&
+        nginx -g 'daemon off;'"
       environment:
         - HTTPS_PORT=${HTTPS_PORT:-443}
+        - HTTP_PORT=${HTTP_PORT:-80}
+        - MAILCOW_HOSTNAME=${MAILCOW_HOSTNAME}
       volumes:
         - ./data/web:/web:ro
         - ./data/conf/rspamd/dynmaps:/dynmaps:ro
@@ -218,10 +237,12 @@ services:
         - 172.22.1.254
       dns_search: mailcow-network
       ports:
-        - "${HTTPS_PORT:-443}:${HTTPS_PORT:-443}"
+        - "${HTTPS_BIND:-0.0.0.0}:${HTTPS_PORT:-443}:${HTTPS_PORT:-443}"
+        - "${HTTP_BIND:-127.0.0.1}:${HTTP_PORT:-80}:${HTTP_PORT:-80}"
       restart: always
       networks:
         mailcow-network:
+          ipv4_address: 172.22.1.251
           aliases:
             - nginx
 
@@ -239,3 +260,4 @@ volumes:
   dkim-vol-1:
   redis-vol-1:
   rspamd-vol-1:
+  postfix-vol-1:

+ 18 - 3
generate_config.sh

@@ -16,8 +16,11 @@ if [ -z "$MAILCOW_HOSTNAME" ]; then
   read -p "Hostname (FQDN): " -ei "mx.example.org" MAILCOW_HOSTNAME
 fi
 
+[[ -a /etc/timezone ]] && TZ=$(cat /etc/timezone)
 if [ -z "$TZ" ]; then
   read -p "Timezone: " -ei "Europe/Berlin" TZ
+else
+  read -p "Timezone: " -ei ${TZ} TZ
 fi
 
 cat << EOF > mailcow.conf
@@ -40,11 +43,23 @@ DBPASS=$(</dev/urandom tr -dc A-Za-z0-9 | head -c 28)
 DBROOT=$(</dev/urandom tr -dc A-Za-z0-9 | head -c 28)
 
 # ------------------------------
-# Misc configuration
+# HTTP/S Bindings
 # ------------------------------
-# You should leave that alone
-# Can also be 11.22.33.44:25 or 0.0.0.0:465 etc. for specific bindings
+
+# You should use HTTPS, but in case of SSL offloaded reverse proxies:
+HTTP_PORT=8080
+HTTP_BIND=0.0.0.0
+
 HTTPS_PORT=443
+HTTPS_BIND=0.0.0.0
+
+# ------------------------------
+# Other bindings
+# ------------------------------
+# You should leave that alone
+# Format: 11.22.33.44:25 or 0.0.0.0:465 etc.
+# Do _not_ use IP:PORT in HTTPS_BIND or HTTPS_PORT
+
 SMTP_PORT=25
 SMTPS_PORT=465
 SUBMISSION_PORT=587