diff --git a/data/Dockerfiles/bind9/.empty b/data/Dockerfiles/bind9/.empty new file mode 100644 index 00000000..e69de29b diff --git a/data/Dockerfiles/clamav/Dockerfile b/data/Dockerfiles/clamav/Dockerfile new file mode 100755 index 00000000..40954bc7 --- /dev/null +++ b/data/Dockerfiles/clamav/Dockerfile @@ -0,0 +1,44 @@ +FROM debian:latest +MAINTAINER https://m-ko.de Markus Kosmal + +# 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"] diff --git a/data/Dockerfiles/clamav/bootstrap.sh b/data/Dockerfiles/clamav/bootstrap.sh new file mode 100755 index 00000000..635e93ea --- /dev/null +++ b/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) +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 diff --git a/data/Dockerfiles/pdns/Dockerfile b/data/Dockerfiles/pdns/Dockerfile deleted file mode 100644 index b56dcf9c..00000000 --- a/data/Dockerfiles/pdns/Dockerfile +++ /dev/null @@ -1,26 +0,0 @@ -FROM ubuntu:xenial -MAINTAINER Andre Peters - -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/* diff --git a/data/Dockerfiles/rspamd/Dockerfile b/data/Dockerfiles/rspamd/Dockerfile index d4d4508d..bd94f25e 100644 --- a/data/Dockerfiles/rspamd/Dockerfile +++ b/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/* diff --git a/data/Dockerfiles/rspamd/antivirus.conf b/data/Dockerfiles/rspamd/antivirus.conf new file mode 100644 index 00000000..cb8049d4 --- /dev/null +++ b/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" +} diff --git a/data/Dockerfiles/rspamd/settings.conf b/data/Dockerfiles/rspamd/settings.conf new file mode 100644 index 00000000..4449f091 --- /dev/null +++ b/data/Dockerfiles/rspamd/settings.conf @@ -0,0 +1 @@ +settings = "http://nginx:8081/settings.php"; diff --git a/data/Dockerfiles/sogo/supervisord.conf b/data/Dockerfiles/sogo/supervisord.conf index 5f2d4d28..a2d32e76 100644 --- a/data/Dockerfiles/sogo/supervisord.conf +++ b/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 diff --git a/data/conf/bind9/named.conf b/data/conf/bind9/named.conf new file mode 100644 index 00000000..e3776155 --- /dev/null +++ b/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"; diff --git a/data/conf/nginx/server_name.active b/data/conf/nginx/server_name.active new file mode 100644 index 00000000..ce429200 --- /dev/null +++ b/data/conf/nginx/server_name.active @@ -0,0 +1 @@ +server_name logs.servercow.de autodiscover.* autoconfig.*; diff --git a/data/conf/nginx/site.conf b/data/conf/nginx/site.conf index 467b56e4..d724b4f2 100644 --- a/data/conf/nginx/site.conf +++ b/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,23 @@ 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 ^ https://$host/SOGo/dav; + rewrite ^ $scheme://$host:$server_port/SOGo/dav; allow all; } @@ -42,6 +52,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 +63,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 +111,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 +126,164 @@ 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; + 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 ^ $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; diff --git a/data/conf/nginx/templates/listen_plain.template b/data/conf/nginx/templates/listen_plain.template new file mode 100644 index 00000000..68133480 --- /dev/null +++ b/data/conf/nginx/templates/listen_plain.template @@ -0,0 +1 @@ +listen ${HTTP_PORT}; diff --git a/data/conf/nginx/listen.template b/data/conf/nginx/templates/listen_ssl.template similarity index 100% rename from data/conf/nginx/listen.template rename to data/conf/nginx/templates/listen_ssl.template diff --git a/data/conf/nginx/templates/server_name.template b/data/conf/nginx/templates/server_name.template new file mode 100644 index 00000000..261a1ece --- /dev/null +++ b/data/conf/nginx/templates/server_name.template @@ -0,0 +1 @@ +server_name ${MAILCOW_HOSTNAME} autodiscover.* autoconfig.*; diff --git a/data/conf/pdns/pdns_custom.lua b/data/conf/pdns/pdns_custom.lua deleted file mode 100644 index 18588bf2..00000000 --- a/data/conf/pdns/pdns_custom.lua +++ /dev/null @@ -1 +0,0 @@ -addNTA("mailcow-network", "nta for local") diff --git a/data/conf/pdns/recursor.conf b/data/conf/pdns/recursor.conf deleted file mode 100644 index f3270b5b..00000000 --- a/data/conf/pdns/recursor.conf +++ /dev/null @@ -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 diff --git a/data/conf/postfix/main.cf b/data/conf/postfix/main.cf index 1a96eff2..cbfc4e0c 100644 --- a/data/conf/postfix/main.cf +++ b/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 diff --git a/data/conf/postfix/sql/mysql_virtual_alias_domain_mailbox_maps.cf b/data/conf/postfix/sql/mysql_virtual_alias_domain_mailbox_maps.cf deleted file mode 100644 index 03061109..00000000 --- a/data/conf/postfix/sql/mysql_virtual_alias_domain_mailbox_maps.cf +++ /dev/null @@ -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' diff --git a/data/conf/postfix/sql/mysql_virtual_alias_domain_maps.cf b/data/conf/postfix/sql/mysql_virtual_alias_domain_maps.cf index 2718bb00..0ead5905 100644 --- a/data/conf/postfix/sql/mysql_virtual_alias_domain_maps.cf +++ b/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' diff --git a/data/conf/postfix/sql/mysql_virtual_sender_acl.cf b/data/conf/postfix/sql/mysql_virtual_sender_acl.cf index 02107e6e..daf13118 100644 --- a/data/conf/postfix/sql/mysql_virtual_sender_acl.cf +++ b/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' diff --git a/data/conf/rspamd/dynmaps/authoritative.php b/data/conf/rspamd/dynmaps/authoritative.php index b2c101f7..ffbfacf6 100644 --- a/data/conf/rspamd/dynmaps/authoritative.php +++ b/data/conf/rspamd/dynmaps/authoritative.php @@ -1,22 +1,34 @@ 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; } ?> \ No newline at end of file diff --git a/data/conf/rspamd/dynmaps/settings.php b/data/conf/rspamd/dynmaps/settings.php index 90697821..9be1f696 100644 --- a/data/conf/rspamd/dynmaps/settings.php +++ b/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 { + rcpt = "/\+.*/"; + rcpt = ""; 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 = "/\+.*/"; + rcpt = ""; + rcpt = "/\+.*/"; + rcpt = ""; priority = high; + + rcpt = "/\+.*/"; + rcpt = ""; 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 = "/\+.*/"; + rcpt = ""; + rcpt = "/\+.*/"; + rcpt = ""; priority = high; + + rcpt = "/\+.*/"; + rcpt = ""; 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 = "/\+.*/"; + rcpt = ""; + rcpt = "/\+.*/"; + rcpt = ""; 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; } ?> diff --git a/data/conf/rspamd/lua/rspamd.local.lua b/data/conf/rspamd/lua/rspamd.local.lua index a441b0cc..133961c0 100644 --- a/data/conf/rspamd/lua/rspamd.local.lua +++ b/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 +} diff --git a/data/web/call_sogo_ctrl.php b/data/web/call_sogo_ctrl.php index c548bd56..4d6dc952 100644 --- a/data/web/call_sogo_ctrl.php +++ b/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 '' . $response['faultString'] . ''; } else { - echo 'OK'; + 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 '' . $response['faultString'] . ''; + } + else { + echo 'OK'; + } } } 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 '' . $response['faultString'] . ''; } else { - echo 'OK'; + 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 '' . $response['faultString'] . ''; + } + else { + echo 'OK'; + } } } ?> \ No newline at end of file diff --git a/data/web/edit.php b/data/web/edit.php index 996c21f8..5879df6d 100644 --- a/data/web/edit.php +++ b/data/web/edit.php @@ -416,7 +416,7 @@ if (isset($_SESSION['mailcow_cc_role']) && ($_SESSION['mailcow_cc_role'] == "adm
- '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 ', '), '✘') 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 ', '), '✘') 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 ', '), '✘') as `address` FROM `alias` WHERE `goto` = :username AND `address` LIKE '@%';"); - $stmt->execute(array(':username' => $username)); + $stmt = $pdo->prepare("SELECT IFNULL(GROUP_CONCAT(`address` SEPARATOR ', '), '✘') 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"; } diff --git a/data/web/inc/vars.inc.php b/data/web/inc/vars.inc.php index b8270ba7..db3f4219 100644 --- a/data/web/inc/vars.inc.php +++ b/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,}'; ?> diff --git a/data/web/js/mailbox.js b/data/web/js/mailbox.js index d12a37c3..eedd51cb 100644 --- a/data/web/js/mailbox.js +++ b/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(); diff --git a/data/web/lang/lang.de.php b/data/web/lang/lang.de.php index f63f1557..160a306e 100644 --- a/data/web/lang/lang.de.php +++ b/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 %s (abmelden)'; $lang['header']['logged_in_as_logout_dual'] = 'Eingeloggt als %s [%s]'; $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'; diff --git a/data/web/lang/lang.en.php b/data/web/lang/lang.en.php index 7abafe13..0758e538 100644 --- a/data/web/lang/lang.en.php +++ b/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 %s (logout)'; $lang['header']['logged_in_as_logout_dual'] = 'Logged in as %s [%s]'; $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'; diff --git a/data/web/mailbox.php b/data/web/mailbox.php index 08798ffe..a12a078f 100644 --- a/data/web/mailbox.php +++ b/data/web/mailbox.php @@ -91,7 +91,7 @@ $_SESSION['return_to'] = $_SERVER['REQUEST_URI']; endforeach; else: ?> - + @@ -101,7 +101,7 @@ $_SESSION['return_to'] = $_SERVER['REQUEST_URI']; ?> - + @@ -137,6 +137,7 @@ $_SESSION['return_to'] = $_SERVER['REQUEST_URI']; + @@ -145,6 +146,7 @@ $_SESSION['return_to'] = $_SERVER['REQUEST_URI']; / +
@@ -180,15 +183,20 @@ $_SESSION['return_to'] = $_SERVER['REQUEST_URI']; } else { ?> - + + + - + @@ -227,6 +235,7 @@ $_SESSION['return_to'] = $_SERVER['REQUEST_URI']; - + + + - + @@ -296,6 +310,7 @@ $_SESSION['return_to'] = $_SERVER['REQUEST_URI']; - + + + - + @@ -365,6 +385,7 @@ $_SESSION['return_to'] = $_SERVER['REQUEST_URI']; - - + + + + - + diff --git a/data/web/user.php b/data/web/user.php index 253715b6..94f2a762 100644 --- a/data/web/user.php +++ b/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): ?>
-
🔑 []
+
?? []
@@ -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); ?>

@@ -109,6 +110,18 @@ elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == '

+
+
:
+
+
+
+ % +
+
+

/ ,

+
+
+
@@ -402,7 +415,7 @@ elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == ' Log - + diff --git a/docker-compose.yml b/docker-compose.yml index 34b917f6..3e1a34c7 100644 --- a/docker-compose.yml +++ b/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: diff --git a/generate_config.sh b/generate_config.sh index 213bdcdb..cebb6ed5 100755 --- a/generate_config.sh +++ b/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=$(