This commit is contained in:
2017-09-13 16:14:14 +02:00
354 changed files with 43829 additions and 9889 deletions

View File

@@ -1,6 +1,9 @@
# --------------------------------------------------------------------------
# Please create a file "extra.conf" for persistent overrides to dovecot.conf
# --------------------------------------------------------------------------
auth_mechanisms = plain login
#mail_debug = yes
log_path = /var/log/mail.log
log_path = syslog
disable_plaintext_auth = yes
# Uncomment on NFS share
#mmap_disable = yes
@@ -10,9 +13,8 @@ disable_plaintext_auth = yes
login_log_format_elements = "user=<%u> method=%m rip=%r lip=%l mpid=%e %c %k"
mail_home = /var/vmail/%d/%n
mail_location = maildir:~/
mail_plugins = quota acl zlib antispam
auth_username_chars = abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890.-_@
ssl_protocols = !SSLv3 !SSLv2
mail_plugins = quota acl zlib listescape #mail_crypt
ssl_protocols = !SSLv3
ssl_prefer_server_ciphers = yes
ssl_cipher_list = EDH+CAMELLIA:EDH+aRSA:EECDH+aRSA+AESGCM:EECDH+aRSA+SHA256:EECDH:+CAMELLIA128:+AES128:+SSLv3:!aNULL:!eNULL:!LOW:!3DES:!MD5:!EXP:!PSK:!DSS:!RC4:!SEED:!IDEA:!ECDSA:kEDH:CAMELLIA128-SHA:AES128-SHA
ssl_options = no_compression
@@ -24,14 +26,20 @@ auth_master_user_separator = *
mail_prefetch_count = 30
passdb {
driver = passwd-file
args = /etc/dovecot/dovecot-master.passwd
args = /usr/local/etc/dovecot/dovecot-master.passwd
master = yes
pass = yes
}
passdb {
args = /etc/dovecot/sql/dovecot-mysql.conf
args = /usr/local/etc/dovecot/sql/dovecot-mysql.conf
driver = sql
}
# Set doveadm_password=your-secret-password in data/conf/dovecot/extra.conf (create if missing)
service doveadm {
inet_listener {
port = 12345
}
}
namespace inbox {
inbox = yes
location =
@@ -131,7 +139,10 @@ namespace inbox {
auto = subscribe
special_use = \Junk
}
mailbox "Junk E-mail" {
mailbox "Junk-E-Mail" {
special_use = \Junk
}
mailbox "Junk E-Mail" {
special_use = \Junk
}
mailbox "Spam" {
@@ -199,15 +210,15 @@ listen = *,[::]
ssl_cert = </etc/ssl/mail/cert.pem
ssl_key = </etc/ssl/mail/key.pem
userdb {
args = /etc/dovecot/sql/dovecot-mysql.conf
args = /usr/local/etc/dovecot/sql/dovecot-mysql.conf
driver = sql
}
protocol imap {
mail_plugins = quota imap_quota imap_acl acl zlib imap_zlib antispam
mail_plugins = quota imap_quota imap_acl acl zlib imap_zlib imap_sieve listescape #mail_crypt
}
protocol lmtp {
mail_plugins = quota sieve acl zlib
auth_socket_path = /var/run/dovecot/auth-master
mail_plugins = quota sieve acl zlib listescape #mail_crypt
auth_socket_path = /usr/local/var/run/dovecot/auth-master
}
protocol sieve {
managesieve_logout_format = bytes=%i/%o
@@ -218,22 +229,32 @@ plugin {
acl = vfile
quota = dict:Userquota::proxy::sqlquota
quota_rule2 = Trash:storage=+100%%
antispam_backend = mailtrain
antispam_spam = Junk
antispam_trash = Trash
antispam_mail_sendmail = /usr/local/bin/rspamd-pipe
antispam_mail_spam = learn_spam
antispam_mail_notspam = learn_ham
# Do not complain about empty parameter
antispam_mail_sendmail_args = --blind
sieve = /var/vmail/sieve/%u.sieve
sieve_plugins = sieve_imapsieve sieve_extprograms
# From elsewhere to Spam folder
imapsieve_mailbox1_name = Junk
imapsieve_mailbox1_causes = COPY
imapsieve_mailbox1_before = file:/usr/local/lib/dovecot/sieve/report-spam.sieve
# END
# From Spam folder to elsewhere
imapsieve_mailbox2_name = *
imapsieve_mailbox2_from = Junk
imapsieve_mailbox2_causes = COPY
imapsieve_mailbox2_before = file:/usr/local/lib/dovecot/sieve/report-ham.sieve
# END
sieve_pipe_bin_dir = /usr/local/lib/dovecot/sieve
sieve_global_extensions = +vnd.dovecot.pipe +vnd.dovecot.execute
sieve_after = /var/vmail/sieve/global.sieve
sieve_max_script_size = 1M
sieve_quota_max_scripts = 0
sieve_quota_max_storage = 0
listescape_char = "\\"
#mail_crypt_global_private_key = </mail_crypt/ecprivkey.pem
#mail_crypt_global_public_key = </mail_crypt/ecpubkey.pem
#mail_crypt_save_version = 2
}
dict {
sqlquota = mysql:/etc/dovecot/sql/dovecot-dict-sql.conf
sqlquota = mysql:/usr/local/etc/dovecot/sql/dovecot-dict-sql.conf
}
remote 127.0.0.1 {
disable_plaintext_auth = no
@@ -245,3 +266,4 @@ service imap-postlogin {
unix_listener imap-postlogin {
}
}
!include_try /usr/local/etc/dovecot/extra.conf

View File

@@ -9,16 +9,13 @@ if header :contains "X-Spam-Flag" "YES" {
}
if allof (
envelope :detail :matches "to" "*",
header :contains "X-Moo-Tag" "YES",
mailboxexists "INBOX/${s}"
) {
fileinto "INBOX/${s}";
}
elsif allof (
envelope :detail :matches "to" "*",
header :contains "X-Moo-Tag" "YES"
) {
set :lower "s" "${1}";
fileinto :create "INBOX/${s}";
set :lower :upperfirst "tag" "${1}";
if mailboxexists "INBOX/${1}" {
fileinto "INBOX/${1}";
} else {
fileinto :create "INBOX/${tag}";
}
}

View File

@@ -1,15 +0,0 @@
connect = "host=mysql dbname=mailcow user=mailcow password=mysafepasswd"
map {
pattern = priv/quota/storage
table = quota2
username_field = username
value_field = bytes
}
map {
pattern = priv/quota/messages
table = quota2
username_field = username
value_field = messages
}

View File

@@ -1,6 +0,0 @@
driver = mysql
connect = "host=mysql dbname=mailcow user=mailcow password=mysafepasswd"
default_pass_scheme = SSHA256
password_query = SELECT password FROM mailbox WHERE username = '%u' AND domain IN (SELECT domain FROM domain WHERE domain='%d' AND active='1')
user_query = SELECT CONCAT('maildir:/var/vmail/',maildir) AS mail, 5000 AS uid, 5000 AS gid, concat('*:bytes=', quota) AS quota_rule FROM mailbox WHERE username = '%u' AND active = '1'
iterate_query = SELECT username FROM mailbox WHERE active='1';

View File

@@ -1,5 +1,6 @@
server {
listen 8081;
listen [::]:8081;
index index.php index.html;
server_name _;
error_log /var/log/nginx/error.log;

View File

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

View File

@@ -1,6 +1,17 @@
server_tokens off;
# includes to http {
proxy_cache_path /tmp levels=1:2 keys_zone=sogo:10m inactive=24h max_size=1g;
server_names_hash_bucket_size 64;
# }
map $http_x_forwarded_proto $client_req_scheme {
default $scheme;
https https;
}
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;
@@ -10,17 +21,46 @@ server {
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_prefer_server_ciphers on;
ssl_ciphers 'EDH+CAMELLIA:EDH+aRSA:EECDH+aRSA+AESGCM:EECDH+aRSA+SHA256:EECDH:+CAMELLIA128:+AES128:+SSLv3:!aNULL:!eNULL:!LOW:!3DES:!MD5:!EXP:!PSK:!DSS:!RC4:!SEED:!IDEA:!ECDSA:kEDH:CAMELLIA128-SHA:AES128-SHA';
add_header Strict-Transport-Security "max-age=15768000; includeSubDomains";
#ssl_session_cache shared:SSL:50m;
#ssl_session_timeout 1d;
#ssl_session_tickets off;
#add_header X-Frame-Options SAMEORIGIN;
#add_header X-Content-Type-Options nosniff;
#add_header X-XSS-Protection "1; mode=block";
#add_header Referrer-Policy: no-referrer-when-downgrade;
#add_header Strict-Transport-Security "max-age=15768000; includeSubDomains; preload";
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;
absolute_redirect off;
root /web;
location = /principals/ {
rewrite ^ https://$host/SOGo/dav;
location ~ ^/api/v1/(.*)$ {
try_files $uri $uri/ /json_api.php?query=$1;
}
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;
rewrite ^/.well-known/caldav$ /SOGo/dav/ permanent;
rewrite ^/.well-known/carddav$ /SOGo/dav/ permanent;
location ^~ /principals {
return 301 /SOGo/dav;
}
location ~ \.php$ {
@@ -37,27 +77,28 @@ server {
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 Host $http_host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
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";
proxy_set_header X-Real-IP $remote_addr;
proxy_redirect off;
}
location ^~ /inc/init.sql {
deny all;
location ~* ^/Autodiscover/Autodiscover.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 ~* autodiscover\.(.*)) {
rewrite ^(.*) /autodiscover.php last;
}
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 {
@@ -72,41 +113,44 @@ server {
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 Host $http_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-url $client_req_scheme://$http_host;
proxy_set_header x-webobjects-server-port $server_port;
client_body_buffer_size 128k;
client_max_body_size 100m;
client_max_body_size 0;
}
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 Host $http_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-url $client_req_scheme://$http_host;
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;
client_max_body_size 0;
break;
}
location /SOGo.woa/WebServerResources/ {
proxy_pass http://172.22.1.252:9192/WebServerResources/;
proxy_set_header Host $host;
proxy_set_header Host $http_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 $http_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;
@@ -116,7 +160,7 @@ server {
location /SOGo/WebServerResources/ {
proxy_pass http://172.22.1.252:9192/WebServerResources/;
proxy_set_header Host $host;
proxy_set_header Host $http_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;
@@ -126,7 +170,154 @@ server {
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_set_header Host $http_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;
absolute_redirect off;
root /web;
location ~ ^/api/v1/(.*)$ {
try_files $uri $uri/ /json_api.php?query=$1;
}
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;
rewrite ^/.well-known/caldav$ /SOGo/dav/ permanent;
rewrite ^/.well-known/carddav$ /SOGo/dav/ permanent;
location ^~ /principals {
return 301 /SOGo/dav;
}
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;
}
location /rspamd/ {
proxy_pass http://172.22.1.253:11334/;
proxy_set_header Host $http_host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Real-IP $remote_addr;
proxy_redirect off;
}
location ~* ^/Autodiscover/Autodiscover.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 $http_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 $client_req_scheme://$http_host;
proxy_set_header x-webobjects-server-port $server_port;
client_body_buffer_size 128k;
client_max_body_size 0;
}
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 $http_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 $client_req_scheme://$http_host;
proxy_set_header x-webobjects-server-port $server_port;
client_body_buffer_size 128k;
client_max_body_size 0;
break;
}
location /SOGo.woa/WebServerResources/ {
proxy_pass http://172.22.1.252:9192/WebServerResources/;
proxy_set_header Host $http_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 $http_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 $http_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 $http_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;

View File

@@ -0,0 +1,2 @@
listen ${HTTP_PORT};
listen [::]:${HTTP_PORT};

View File

@@ -0,0 +1,2 @@
listen ${HTTPS_PORT} http2;
listen [::]:${HTTPS_PORT} http2;

View File

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

View File

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

View File

@@ -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

View File

@@ -9,7 +9,7 @@ smtpd_relay_restrictions = permit_mynetworks permit_sasl_authenticated defer_una
alias_maps = hash:/etc/aliases
alias_database = hash:/etc/aliases
relayhost =
mynetworks = 127.0.0.0/8 [::ffff:127.0.0.0]/104 [::1]/128 10.0.0.0/8 172.16.0.0/12 192.168.0.0/16
mynetworks = 127.0.0.0/8 [::ffff:127.0.0.0]/104 [::1]/128 10.0.0.0/8 172.16.0.0/12 192.168.0.0/16 [fd4d:6169:6c63:6f77::]/64
mailbox_size_limit = 0
recipient_delimiter = +
inet_interfaces = all
@@ -24,7 +24,7 @@ milter_default_action = accept
milter_protocol = 6
minimal_backoff_time = 300s
plaintext_reject_code = 550
postscreen_access_list = permit_mynetworks, cidr:/opt/postfix/conf/postscreen_access.cidr
postscreen_access_list = permit_mynetworks, cidr:/opt/postfix/conf/postscreen_access.cidr, tcp:127.0.0.1:10027
postscreen_bare_newline_enable = no
postscreen_blacklist_action = drop
postscreen_cache_cleanup_interval = 24h
@@ -39,11 +39,11 @@ postscreen_greet_ttl = 2d
postscreen_greet_wait = 3s
postscreen_non_smtp_command_enable = no
postscreen_pipelining_enable = no
proxy_read_maps = proxy:mysql:/opt/postfix/conf/sql/mysql_virtual_sender_acl.cf, proxy:mysql:/opt/postfix/conf/sql/mysql_tls_enforce_out_policy.cf, proxy:mysql:/opt/postfix/conf/sql/mysql_tls_enforce_in_policy.cf, $local_recipient_maps $mydestination $virtual_alias_maps $virtual_alias_domains $virtual_mailbox_maps $virtual_mailbox_domains $relay_recipient_maps $relay_domains $canonical_maps $sender_canonical_maps $recipient_canonical_maps $relocated_maps $transport_maps $mynetworks $smtpd_sender_login_maps
proxy_read_maps = proxy:mysql:/opt/postfix/conf/sql/mysql_virtual_sender_acl.cf, proxy:mysql:/opt/postfix/conf/sql/mysql_sender_dependent_default_transport_maps.cf, proxy:mysql:/opt/postfix/conf/sql/mysql_sasl_passwd_maps.cf, proxy:mysql:/opt/postfix/conf/sql/mysql_tls_enforce_in_policy.cf, $local_recipient_maps $mydestination $virtual_alias_maps $virtual_alias_domains $virtual_mailbox_maps $virtual_mailbox_domains $relay_recipient_maps $relay_domains $canonical_maps $sender_canonical_maps $recipient_canonical_maps $relocated_maps $transport_maps $mynetworks $smtpd_sender_login_maps
queue_run_delay = 300s
relay_domains = proxy:mysql:/opt/postfix/conf/sql/mysql_virtual_relay_domain_maps.cf
relay_recipient_maps = proxy:mysql:/opt/postfix/conf/sql/mysql_relay_recipient_maps.cf
sender_dependent_default_transport_maps = proxy:mysql:/opt/postfix/conf/sql/mysql_tls_enforce_out_policy.cf
sender_dependent_default_transport_maps = proxy:mysql:/opt/postfix/conf/sql/mysql_sender_dependent_default_transport_maps.cf
smtp_tls_CAfile = /etc/ssl/certs/ca-certificates.crt
smtp_tls_cert_file = /etc/ssl/mail/cert.pem
smtp_tls_key_file = /etc/ssl/mail/key.pem
@@ -83,11 +83,19 @@ 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
smtpd_milters = inet:rmilter:9900
non_smtpd_milters = inet:rmilter:9900
smtpd_milters = inet:rspamd:9900
non_smtpd_milters = inet:rspamd:9900
milter_mail_macros = i {mail_addr} {client_addr} {client_name} {auth_authen}
mydestination = localhost.localdomain, localhost
#content_filter=zeyple
# Prefere IPv4, useful for v4-only envs
smtp_address_preference = ipv4
smtp_sender_dependent_authentication = yes
smtp_sasl_auth_enable = yes
smtp_sasl_password_maps = proxy:mysql:/opt/postfix/conf/sql/mysql_sasl_passwd_maps.cf
smtp_sasl_security_options =
smtp_sasl_mechanism_filter = plain, login

View File

@@ -16,6 +16,7 @@ smtp_enforced_tls unix - - n - - smtp
-o smtp_tls_security_level=encrypt
-o syslog_name=enforced-tls-smtp
-o smtp_delivery_status_filter=pcre:/opt/postfix/conf/smtp_dsn_filter
tlsproxy unix - - n - 0 tlsproxy
dnsblog unix - - n - 0 dnsblog
pickup fifo n - n 60 1 pickup
@@ -43,3 +44,16 @@ anvil unix - - n - 1 anvil
scache unix - - n - 1 scache
maildrop unix - n n - - pipe flags=DRhu
user=vmail argv=/usr/bin/maildrop -d ${recipient}
zeyple unix - n n - - pipe
user=zeyple argv=/usr/local/bin/zeyple.py ${recipient}
127.0.0.1:10026 inet n - n - 10 smtpd
-o content_filter=
-o receive_override_options=no_unknown_recipient_checks,no_header_body_checks,no_milters
-o smtpd_helo_restrictions=
-o smtpd_client_restrictions=
-o smtpd_sender_restrictions=
-o smtpd_recipient_restrictions=permit_mynetworks,reject
-o mynetworks=127.0.0.0/8
-o smtpd_authorized_xforward_hosts=127.0.0.0/8
127.0.0.1:10027 inet n n n - 0 spawn user=nobody argv=/usr/local/bin/whitelist_forwardinghosts.sh

View File

@@ -1,5 +0,0 @@
user = mailcow
password = mysafepasswd
hosts = mysql
dbname = mailcow
query = SELECT DISTINCT CASE WHEN '%d' IN (SELECT domain FROM domain WHERE relay_all_recipients=1 AND domain='%d' AND backupmx=1) THEN '%s' ELSE (SELECT goto FROM alias WHERE address='%s' AND active='1') END AS result;

View File

@@ -1,5 +0,0 @@
user = mailcow
password = mysafepasswd
hosts = mysql
dbname = mailcow
query = SELECT IF( EXISTS( SELECT 'TLS_ACTIVE' FROM alias LEFT OUTER JOIN mailbox ON mailbox.username = alias.address WHERE (address='%s' OR address IN (SELECT CONCAT('%u', '@', target_domain) FROM alias_domain WHERE alias_domain='%d')) AND mailbox.tls_enforce_in = '1' AND mailbox.active = '1'), 'reject_plaintext_session', 'DUNNO') AS 'tls_enforce_in';

View File

@@ -1,5 +0,0 @@
user = mailcow
password = mysafepasswd
hosts = mysql
dbname = mailcow
query = SELECT IF( EXISTS( SELECT 'TLS_ACTIVE' FROM alias LEFT OUTER JOIN mailbox ON mailbox.username = alias.address WHERE (address='%s' OR address IN (SELECT CONCAT('%u', '@', target_domain) FROM alias_domain WHERE alias_domain='%d')) AND mailbox.tls_enforce_out = '1' AND mailbox.active = '1'), 'smtp_enforced_tls:', 'DUNNO') AS 'tls_enforce_out';

View File

@@ -1,6 +0,0 @@
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('@', alias_domain.target_domain) AND alias.active = 1 AND alias_domain.active='1'

View File

@@ -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'

View File

@@ -1,5 +0,0 @@
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'

View File

@@ -1,5 +0,0 @@
user = mailcow
password = mysafepasswd
hosts = mysql
dbname = mailcow
query = SELECT goto FROM alias WHERE address='%s' AND active='1';

View File

@@ -1,5 +0,0 @@
user = mailcow
password = mysafepasswd
hosts = mysql
dbname = mailcow
query = SELECT alias_domain from alias_domain WHERE alias_domain='%s' AND active='1' UNION SELECT domain FROM domain WHERE domain='%s' AND active = '1' AND backupmx = '0'

View File

@@ -1,5 +0,0 @@
user = mailcow
password = mysafepasswd
hosts = mysql
dbname = mailcow
query = SELECT maildir FROM mailbox WHERE username='%s' AND active = '1'

View File

@@ -1,5 +0,0 @@
user = mailcow
password = mysafepasswd
hosts = mysql
dbname = mailcow
query = SELECT domain FROM domain WHERE domain='%s' AND backupmx = '1' AND active = '1'

View File

@@ -1,5 +0,0 @@
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'

View File

@@ -1,5 +0,0 @@
user = mailcow
password = mysafepasswd
hosts = mysql
dbname = mailcow
query = SELECT goto FROM spamalias WHERE address='%s' AND validity >= UNIX_TIMESTAMP()

View File

@@ -1,42 +0,0 @@
bind_socket = inet:9900;
spamd {
servers = r:rspamd:11333;
connect_timeout = 1s;
results_timeout = 20s;
error_time = 10;
dead_time = 300;
maxerrors = 10;
reject_message = "Spam or virus message rejected due to high detection score";
whitelist = 127.0.0.1/32, [::1]/128;
spamd_soft_fail = yes;
rspamd_metric = "default";
extended_spam_headers = yes;
spam_header = "X-Spam-Flag";
spam_header_value = "YES";
};
redis {
servers_grey = redis:6379;
servers_limits = redis:6379;
servers_id = redis:6379;
id_prefix = "message_id.";
grey_prefix = "grey.";
white_prefix = "white.";
connect_timeout = 1s;
error_time = 10;
dead_time = 300;
maxerrors = 10;
};
tempdir = /tmp;
tempfiles_mode = 00600;
max_size = 20M;
strict_auth = yes;
use_dcc = no;
limits {
enable = false;
};
greylisting {
enable = false;
}
dkim {
enable = false;
};

View File

@@ -0,0 +1 @@
1

View File

@@ -0,0 +1,25 @@
local custom_keywords = {
['customrl'] = {},
}
function custom_keywords.customrl.get_value(task)
local rspamd_logger = require "rspamd_logger"
if task:has_symbol('DYN_RL') then
rspamd_logger.infox(rspamd_config, "task has a dynamic ratelimit symbol, processing...")
return "check"
else
rspamd_logger.infox(rspamd_config, "task has no dynamic ratelimit symbol, skipping...")
return
end
end
function custom_keywords.customrl.get_limit(task)
local rspamd_logger = require "rspamd_logger"
local dyn_rl_symbol = task:get_symbol("DYN_RL")
if dyn_rl_symbol then
local rl_value = dyn_rl_symbol[1].options[1]
rspamd_logger.infox(rspamd_config, "dynamic ratelimit symbol has option %s, returning...", rl_value)
return rl_value
end
end
-- returning custom keywords
return custom_keywords

View File

@@ -1,22 +0,0 @@
<?php
ini_set('error_reporting', 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;
}
$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;
}
?>

View File

@@ -0,0 +1,44 @@
<?php
header('Content-Type: text/plain');
ini_set('error_reporting', 0);
$redis = new Redis();
$redis->connect('redis-mailcow', 6379);
function in_net($addr, $net) {
$net = explode('/', $net);
if (count($net) > 1) {
$mask = $net[1];
}
$net = inet_pton($net[0]);
$addr = inet_pton($addr);
$length = strlen($net); // 4 for IPv4, 16 for IPv6
if (strlen($net) != strlen($addr)) {
return false;
}
if (!isset($mask)) {
$mask = $length * 8;
}
$addr_bin = '';
$net_bin = '';
for ($i = 0; $i < $length; ++$i) {
$addr_bin .= str_pad(decbin(ord(substr($addr, $i, $i+1))), 8, '0', STR_PAD_LEFT);
$net_bin .= str_pad(decbin(ord(substr($net, $i, $i+1))), 8, '0', STR_PAD_LEFT);
}
return substr($addr_bin, 0, $mask) == substr($net_bin, 0, $mask);
}
try {
foreach ($redis->hGetAll('WHITELISTED_FWD_HOST') as $host => $source) {
if (in_net($_GET['host'], $host)) {
echo '200 PERMIT';
exit;
}
}
echo '200 DUNNO';
}
catch (RedisException $e) {
echo '200 DUNNO';
exit;
}
?>

View File

@@ -4,21 +4,105 @@ 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.
*/
ini_set('error_reporting', '0');
header('Content-Type: text/plain');
require_once "vars.inc.php";
ini_set('error_reporting', 0);
$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);
try {
$pdo = new PDO($dsn, $database_user, $database_pass, $opt);
$stmt = $pdo->query("SELECT * FROM `filterconf`");
}
catch (PDOException $e) {
echo 'settings { }';
exit;
}
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));
}
function ucl_rcpts($object, $type) {
global $pdo;
if ($type == 'mailbox') {
// Standard aliases
$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' => '%' . $object . '%',
':object_address' => $object
));
$standard_aliases = $stmt->fetchAll(PDO::FETCH_ASSOC);
while ($row = array_shift($standard_aliases)) {
$local = parse_email($row['address'])['local'];
$domain = parse_email($row['address'])['domain'];
if (!empty($local) && !empty($domain)) {
$rcpt[] = '/' . $local . '\+.*' . $domain . '/i';
}
$rcpt[] = $row['address'];
}
// Aliases by alias domains
$stmt = $pdo->prepare("SELECT CONCAT(`local_part`, '@', `alias_domain`.`alias_domain`) AS `alias` FROM `mailbox`
LEFT OUTER JOIN `alias_domain` ON `mailbox`.`domain` = `alias_domain`.`target_domain`
WHERE `mailbox`.`username` = :object");
$stmt->execute(array(
':object' => $object
));
$by_domain_aliases = $stmt->fetchAll(PDO::FETCH_ASSOC);
array_filter($by_domain_aliases);
while ($row = array_shift($by_domain_aliases)) {
if (!empty($row['alias'])) {
$local = parse_email($row['alias'])['local'];
$domain = parse_email($row['alias'])['domain'];
if (!empty($local) && !empty($domain)) {
$rcpt[] = '/' . $local . '\+.*' . $domain . '/i';
}
$rcpt[] = $row['alias'];
}
}
// Mailbox self
$local = parse_email($row['object'])['local'];
$domain = parse_email($row['object'])['domain'];
if (!empty($local) && !empty($domain)) {
$rcpt[] = '/' . $local . '\+.*' . $domain . '/i';
}
$rcpt[] = $object;
}
elseif ($type == 'domain') {
// Domain self
$rcpt[] = '/.*@' . $object . '/i';
$stmt = $pdo->prepare("SELECT `alias_domain` FROM `alias_domain`
WHERE `target_domain` = :object");
$stmt->execute(array(':object' => $object));
$alias_domains = $stmt->fetchAll(PDO::FETCH_ASSOC);
array_filter($alias_domains);
while ($row = array_shift($alias_domains)) {
$rcpt[] = '/.*@' . $row['alias_domain'] . '/i';
}
}
if (!empty($rcpt)) {
return $rcpt;
}
return false;
}
?>
settings {
<?php
/*
// Start custom scores for users
*/
$stmt = $pdo->query("SELECT DISTINCT `object` FROM `filterconf` WHERE `option` = 'highspamlevel' OR `option` = 'lowspamlevel'");
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
@@ -26,45 +110,18 @@ while ($row = array_shift($rows)) {
$username_sane = preg_replace("/[^a-zA-Z0-9]+/", "", $row['object']);
?>
score_<?=$username_sane;?> {
priority = low;
priority = 4;
<?php
foreach (ucl_rcpts($row['object'], strpos($row['object'], '@') === FALSE ? 'domain' : 'mailbox') as $rcpt) {
?>
rcpt = "<?=$rcpt;?>";
<?php
}
$stmt = $pdo->prepare("SELECT `option`, `value` FROM `filterconf`
WHERE (`option` = 'highspamlevel' OR `option` = 'lowspamlevel')
AND `object`= :object");
$stmt->execute(array(':object' => $row['object']));
$spamscore = $stmt->fetchAll(PDO::FETCH_COLUMN|PDO::FETCH_GROUP);
$stmt = $pdo->prepare("SELECT GROUP_CONCAT(REPLACE(`value`, '*', '.*') SEPARATOR '|') AS `value` FROM `filterconf`
WHERE (`object`= :object OR `object`= :object_domain)
AND (`option` = 'blacklist_from' OR `option` = 'whitelist_from')");
$stmt->execute(array(':object' => $row['object'], ':object_domain' => substr(strrchr($row['object'], "@"), 1)));
$grouped_lists = $stmt->fetchAll(PDO::FETCH_COLUMN);
$value_sane = preg_replace("/\.\./", ".", (preg_replace("/\*/", ".*", $grouped_lists[0])));
?>
from = "/^((?!<?=$value_sane;?>).)*$/";
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']));
$rows_aliases_1 = $stmt->fetchAll(PDO::FETCH_ASSOC);
while ($row_aliases_1 = array_shift($rows_aliases_1)) {
?>
rcpt = "<?=$row_aliases_1['address'];?>";
<?php
}
$stmt = $pdo->prepare("SELECT CONCAT(`local_part`, '@', `alias_domain`.`alias_domain`) AS `aliases` FROM `mailbox`
LEFT OUTER JOIN `alias_domain` on `mailbox`.`domain` = `alias_domain`.`target_domain`
WHERE `mailbox`.`username` = :object");
$stmt->execute(array(':object' => $row['object']));
$rows_aliases_2 = $stmt->fetchAll(PDO::FETCH_ASSOC);
array_filter($rows_aliases_2);
while ($row_aliases_2 = array_shift($rows_aliases_2)) {
if (!empty($row_aliases_2['aliases'])) {
?>
rcpt = "<?=$row_aliases_2['aliases'];?>";
<?php
}
}
?>
apply "default" {
actions {
@@ -95,56 +152,76 @@ while ($row = array_shift($rows)) {
$grouped_lists = $stmt->fetchAll(PDO::FETCH_COLUMN);
$value_sane = preg_replace("/\.\./", ".", (preg_replace("/\*/", ".*", $grouped_lists[0])));
?>
from = "/(<?=$value_sane;?>)/";
from = "/(<?=$value_sane;?>)/i";
<?php
if (!filter_var(trim($row['object']), FILTER_VALIDATE_EMAIL)) {
?>
priority = medium;
rcpt = "/.*@<?=$row['object'];?>/";
priority = 5;
<?php
$stmt = $pdo->prepare("SELECT `alias_domain` FROM `alias_domain`
WHERE `target_domain` = :object");
$stmt->execute(array(':object' => $row['object']));
$rows_domain_aliases = $stmt->fetchAll(PDO::FETCH_ASSOC);
array_filter($rows_domain_aliases);
while ($row_domain_aliases = array_shift($rows_domain_aliases)) {
foreach (ucl_rcpts($row['object'], strpos($row['object'], '@') === FALSE ? 'domain' : 'mailbox') as $rcpt) {
?>
rcpt = "/.*@<?=$row_domain_aliases['alias_domain'];?>/";
rcpt = "<?=$rcpt;?>";
<?php
}
}
else {
?>
priority = high;
rcpt = "<?=$row['object'];?>";
priority = 6;
<?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']));
$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)) {
foreach (ucl_rcpts($row['object'], strpos($row['object'], '@') === FALSE ? 'domain' : 'mailbox') as $rcpt) {
?>
rcpt = "<?=$row_aliases_wl_1['address'];?>";
rcpt = "<?=$rcpt;?>";
<?php
}
$stmt = $pdo->prepare("SELECT CONCAT(`local_part`, '@', `alias_domain`.`alias_domain`) AS `aliases` FROM `mailbox`
LEFT OUTER JOIN `alias_domain` on `mailbox`.`domain` = `alias_domain`.`target_domain`
WHERE `mailbox`.`username` = :object");
$stmt->execute(array(':object' => $row['object']));
$rows_aliases_wl_2 = $stmt->fetchAll(PDO::FETCH_ASSOC);
array_filter($rows_aliases_wl_2);
while ($row_aliases_wl_2 = array_shift($rows_aliases_wl_2)) {
if (!empty($row_aliases_wl_2['aliases'])) {
?>
rcpt = "<?=$row_aliases_wl_2['aliases'];?>";
<?php
}
}
}
?>
apply "default" {
MAILCOW_MOO = -999.0;
MAILCOW_WHITE = -999.0;
}
symbols [
"MAILCOW_WHITE"
]
}
whitelist_header_<?=$username_sane;?> {
<?php
$stmt = $pdo->prepare("SELECT GROUP_CONCAT(REPLACE(`value`, '*', '.*') SEPARATOR '|') AS `value` FROM `filterconf`
WHERE `object`= :object
AND `option` = 'whitelist_from'");
$stmt->execute(array(':object' => $row['object']));
$grouped_lists = $stmt->fetchAll(PDO::FETCH_COLUMN);
$value_sane = preg_replace("/\.\./", ".", (preg_replace("/\*/", ".*", $grouped_lists[0])));
?>
request_header = {
"From" = "(<?=$value_sane;?>)";
}
<?php
if (!filter_var(trim($row['object']), FILTER_VALIDATE_EMAIL)) {
?>
priority = 5;
<?php
foreach (ucl_rcpts($row['object'], strpos($row['object'], '@') === FALSE ? 'domain' : 'mailbox') as $rcpt) {
?>
rcpt = "<?=$rcpt;?>";
<?php
}
}
else {
?>
priority = 6;
<?php
foreach (ucl_rcpts($row['object'], strpos($row['object'], '@') === FALSE ? 'domain' : 'mailbox') as $rcpt) {
?>
rcpt = "<?=$rcpt;?>";
<?php
}
}
?>
apply "default" {
MAILCOW_WHITE = -999.0;
}
symbols [
"MAILCOW_WHITE"
]
}
<?php
}
@@ -167,58 +244,78 @@ while ($row = array_shift($rows)) {
$grouped_lists = $stmt->fetchAll(PDO::FETCH_COLUMN);
$value_sane = preg_replace("/\.\./", ".", (preg_replace("/\*/", ".*", $grouped_lists[0])));
?>
from = "/(<?=$value_sane;?>)/";
from = "/(<?=$value_sane;?>)/i";
<?php
if (!filter_var(trim($row['object']), FILTER_VALIDATE_EMAIL)) {
?>
priority = medium;
rcpt = "/.*@<?=$row['object'];?>/";
priority = 5;
<?php
$stmt = $pdo->prepare("SELECT `alias_domain` FROM `alias_domain`
WHERE `target_domain` = :object");
$stmt->execute(array(':object' => $row['object']));
$rows_domain_aliases = $stmt->fetchAll(PDO::FETCH_ASSOC);
array_filter($rows_domain_aliases);
while ($row_domain_aliases = array_shift($rows_domain_aliases)) {
foreach (ucl_rcpts($row['object'], strpos($row['object'], '@') === FALSE ? 'domain' : 'mailbox') as $rcpt) {
?>
rcpt = "/.*@<?=$row_domain_aliases['alias_domain'];?>/";
rcpt = "<?=$rcpt;?>";
<?php
}
}
else {
?>
priority = high;
rcpt = "<?=$row['object'];?>";
priority = 6;
<?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']));
$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)) {
foreach (ucl_rcpts($row['object'], strpos($row['object'], '@') === FALSE ? 'domain' : 'mailbox') as $rcpt) {
?>
rcpt = "<?=$row_aliases_bl_1['address'];?>";
rcpt = "<?=$rcpt;?>";
<?php
}
$stmt = $pdo->prepare("SELECT CONCAT(`local_part`, '@', `alias_domain`.`alias_domain`) AS `aliases` FROM `mailbox`
LEFT OUTER JOIN `alias_domain` on `mailbox`.`domain` = `alias_domain`.`target_domain`
WHERE `mailbox`.`username` = :object");
$stmt->execute(array(':object' => $row['object']));
$rows_aliases_bl_2 = $stmt->fetchAll(PDO::FETCH_ASSOC);
array_filter($rows_aliases_bl_2);
while ($row_aliases_bl_2 = array_shift($rows_aliases_bl_2)) {
if (!empty($row_aliases_bl_2['aliases'])) {
?>
rcpt = "<?=$row_aliases_bl_2['aliases'];?>";
<?php
}
}
}
?>
apply "default" {
MAILCOW_MOO = 999.0;
MAILCOW_BLACK = 999.0;
}
symbols [
"MAILCOW_BLACK"
]
}
blacklist_header_<?=$username_sane;?> {
<?php
$stmt = $pdo->prepare("SELECT GROUP_CONCAT(REPLACE(`value`, '*', '.*') SEPARATOR '|') AS `value` FROM `filterconf`
WHERE `object`= :object
AND `option` = 'blacklist_from'");
$stmt->execute(array(':object' => $row['object']));
$grouped_lists = $stmt->fetchAll(PDO::FETCH_COLUMN);
$value_sane = preg_replace("/\.\./", ".", (preg_replace("/\*/", ".*", $grouped_lists[0])));
?>
request_header = {
"From" = "(<?=$value_sane;?>)";
}
<?php
if (!filter_var(trim($row['object']), FILTER_VALIDATE_EMAIL)) {
?>
priority = 5;
<?php
foreach (ucl_rcpts($row['object'], strpos($row['object'], '@') === FALSE ? 'domain' : 'mailbox') as $rcpt) {
?>
rcpt = "<?=$rcpt;?>";
<?php
}
}
else {
?>
priority = 6;
<?php
foreach (ucl_rcpts($row['object'], strpos($row['object'], '@') === FALSE ? 'domain' : 'mailbox') as $rcpt) {
?>
rcpt = "<?=$rcpt;?>";
<?php
}
}
?>
apply "default" {
MAILCOW_BLACK = 999.0;
}
symbols [
"MAILCOW_BLACK"
]
}
<?php
}
?>
}
}

View File

@@ -1,22 +0,0 @@
<?php
ini_set('error_reporting', 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;
}
$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;
}
?>

View File

@@ -1 +0,0 @@
../../../web/inc/vars.inc.php

View File

@@ -0,0 +1,3 @@
<?php
require_once('../../../web/inc/vars.inc.php');
?>

View File

@@ -0,0 +1,7 @@
clamav {
attachments_only = false;
symbol = "CLAM_VIRUS";
type = "clamav";
log_clean = true;
servers = "clamd:3310";
}

View File

@@ -0,0 +1,30 @@
# If false, messages with empty envelope from are not signed
allow_envfrom_empty = false;
# If true, envelope/header domain mismatch is ignored
allow_hdrfrom_mismatch = false;
# If true, multiple from headers are allowed (but only first is used)
allow_hdrfrom_multiple = true;
# If true, username does not need to contain matching domain
allow_username_mismatch = false;
# If false, messages from authenticated users are not selected for signing
auth_only = true;
# Default path to key, can include '$domain' and '$selector' variables
path = "/data/dkim/keys/$domain.dkim";
# Default selector to use
selector = "dkim";
# If false, messages from local networks are not selected for signing
sign_local = true;
# Symbol to add when message is signed
symbol = "ARC_SIGNED";
# Whether to fallback to global config
try_fallback = true;
# Domain to use for DKIM signing: can be "header" or "envelope"
use_domain = "envelope";
# Whether to normalise domains to eSLD
use_esld = false;
# Whether to get keys from Redis
use_redis = true;
# Hash for DKIM keys in Redis
key_prefix = "DKIM_PRIV_KEYS";
# Selector map
selector_prefix = "DKIM_SELECTORS";

View File

@@ -1,34 +0,0 @@
sign_condition =<<EOD
return function(task)
local smtp_from = task:get_from('smtp')
local mime_from = task:get_from('mime')
local rspamd_logger = require "rspamd_logger"
if smtp_from[1]['domain'] ~= nil and smtp_from[1]['domain'] ~= '' then
domain = smtp_from[1]['domain']
rspamd_logger.infox(task, "set domain found in smtp from field to %s", domain)
if not task:get_user() then
rspamd_logger.infox(task, "found domain in smtp header field, but user is not authenticated - skipped")
return false
end
elseif mime_from[1]['domain'] ~= nil and mime_from[1]['domain'] ~= '' then
domain = mime_from[1]['domain']
rspamd_logger.infox(task, "set domain found in mime from field to %s", domain)
else
rspamd_logger.infox(task, "cannot determine domain for dkim signing")
return false
end
local keyfile = io.open("/data/dkim/keys/" .. domain .. ".dkim")
if keyfile then
rspamd_logger.infox(task, "found dkim key file for domain %s", domain)
keyfile:close()
return {
key = "/data/dkim/keys/" .. domain .. ".dkim",
domain = domain,
selector = "dkim"
}
else
rspamd_logger.infox(task, "no key file for domain %s - skipped", domain)
end
return false
end
EOD;

View File

@@ -0,0 +1,30 @@
# If false, messages with empty envelope from are not signed
allow_envfrom_empty = false;
# If true, envelope/header domain mismatch is ignored
allow_hdrfrom_mismatch = false;
# If true, multiple from headers are allowed (but only first is used)
allow_hdrfrom_multiple = true;
# If true, username does not need to contain matching domain
allow_username_mismatch = true;
# If false, messages from authenticated users are not selected for signing
auth_only = true;
# Default path to key, can include '$domain' and '$selector' variables
path = "/data/dkim/keys/$domain.dkim";
# Default selector to use
selector = "dkim";
# If false, messages from local networks are not selected for signing
sign_local = true;
# Symbol to add when message is signed
symbol = "DKIM_SIGNED";
# Whether to fallback to global config
try_fallback = true;
# Domain to use for DKIM signing: can be "header" or "envelope"
use_domain = "envelope";
# Whether to normalise domains to eSLD
use_esld = false;
# Whether to get keys from Redis
use_redis = true;
# Hash for DKIM keys in Redis
key_prefix = "DKIM_PRIV_KEYS";
# Selector map
selector_prefix = "DKIM_SELECTORS";

View File

@@ -0,0 +1,22 @@
rules {
DKIM_FAIL {
action = "add header";
expression = "R_DKIM_REJECT & !MAILLIST & !MAILCOW_WHITE & !MAILCOW_BLACK";
require_action = ["no action", "greylist"];
}
VIRUS_FOUND {
action = "reject";
expression = "CLAM_VIRUS & !MAILCOW_WHITE";
honor_action = ["reject"];
}
WHITELIST_FORWARDING_HOST_NO_REJECT {
action = "add header";
expression = "WHITELISTED_FWD_HOST";
require_action = ["reject"];
}
WHITELIST_FORWARDING_HOST_NO_GREYLIST {
action = "no action";
expression = "WHITELISTED_FWD_HOST";
require_action = ["greylist", "soft reject"];
}
}

View File

@@ -0,0 +1,39 @@
use = ["spam-header", "x-spamd-result", "x-rspamd-queue-id", "authentication-results"];
skip_local = false;
skip_authenticated = true;
routines {
spam-header {
header = "X-Spam-Flag";
value = "YES";
remove = 1;
}
authentication-results {
header = "Authentication-Results";
remove = 1;
spf_symbols {
pass = "R_SPF_ALLOW";
fail = "R_SPF_FAIL";
softfail = "R_SPF_SOFTFAIL";
neutral = "R_SPF_NEUTRAL";
temperror = "R_SPF_DNSFAIL";
none = "R_SPF_NA";
permerror = "R_SPF_PERMFAIL";
}
dkim_symbols {
pass = "R_DKIM_ALLOW";
fail = "R_DKIM_REJECT";
temperror = "R_DKIM_TEMPFAIL";
none = "R_DKIM_NA";
permerror = "R_DKIM_PERMFAIL";
}
dmarc_symbols {
pass = "DMARC_POLICY_ALLOW";
permerror = "DMARC_BAD_POLICY";
temperror = "DMARC_DNSFAIL";
none = "DMARC_NA";
reject = "DMARC_POLICY_REJECT";
softfail = "DMARC_POLICY_SOFTFAIL";
quarantine = "DMARC_POLICY_QUARANTINE";
}
}
}

View File

@@ -0,0 +1,34 @@
# Extensions that are treated as 'bad'
# Number is score multiply factor
bad_extensions = {
scr = 4,
lnk = 4,
exe = 1,
jar = 2,
com = 4,
bat = 4,
ace = 4,
arj = 4,
cab = 3,
};
# Extensions that are particularly penalized for archives
bad_archive_extensions = {
pptx = 0.5,
docx = 0.5,
xlsx = 0.5,
pdf = 1.0,
jar = 3,
js = 0.5,
vbs = 7,
};
# Used to detect another archive in archive
archive_extensions = {
zip = 1,
arj = 1,
rar = 1,
ace = 1,
7z = 1,
cab = 1,
};

View File

@@ -0,0 +1,22 @@
RCPT_MAILCOW_DOMAIN {
type = "rcpt";
filter = "email:domain"
map = "redis://DOMAIN_MAP"
}
RCPT_WANTS_SUBJECT_TAG {
type = "rcpt";
filter = "email:addr"
map = "redis://RCPT_WANTS_SUBJECT_TAG"
}
WHITELISTED_FWD_HOST {
type = "ip";
map = "redis://WHITELISTED_FWD_HOST"
}
KEEP_SPAM {
type = "ip";
map = "redis://KEEP_SPAM"
action = "accept";
}

View File

@@ -0,0 +1,7 @@
timeout = 1.0;
symbol_bad_mx = "MX_INVALID";
symbol_no_mx = "MX_MISSING";
symbol_good_mx = "MX_GOOD";
expire = 86400;
key_prefix = "rmx";
enabled = true;

View File

@@ -1,3 +1,9 @@
dns {
enable_dnssec = true;
enable_dnssec = true;
}
map_watch_interval = 15s;
dns {
timeout = 4s;
retransmits = 5;
}
disable_monitored = true;

View File

@@ -1 +1,5 @@
# rspamd.conf.local
history_redis {}
worker "log_helper" {
count = 1;
}

View File

@@ -7,69 +7,104 @@ rspamd_config.MAILCOW_AUTH = {
end
}
rspamd_config.MAILCOW_MOO = function (task)
return true
end
local modify_subject_map = rspamd_config:add_map({
url = 'http://nginx: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',
type = 'map',
description = 'Map of domains we are authoritative for'
})
rspamd_config.ADD_DELIMITER_TAG = {
rspamd_config:register_symbol({
name = 'TAG_MOO',
type = 'postfilter',
callback = function(task)
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 tagged_rcpt = task:get_symbol("TAGGED_RCPT")
local mailcow_domain = task:get_symbol("RCPT_MAILCOW_DOMAIN")
local domain = task:get_recipients(1)[1]['domain']
if tagged_rcpt and mailcow_domain then
local tag = tagged_rcpt[1].options[1]
rspamd_logger.infox("found tag: %s", tag)
local action = task:get_metric_action('default')
rspamd_logger.infox("metric action now: %s", action)
local user_env, tag_env = user_env_tagged:match("([^+]+)+(.*)")
local user_to, tag_to = user_to_tagged:match("([^+]+)+(.*)")
if action ~= 'no action' and action ~= 'greylist' then
rspamd_logger.infox("skipping tag handler for action: %s", action)
task:set_metric_action('default', action)
return true
end
local authdomain = auth_domain_map:get_key(domain)
local wants_subject_tag = task:get_symbol("RCPT_WANTS_SUBJECT_TAG")
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)
local user_untagged = user .. '@' .. domain
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")
if wants_subject_tag then
rspamd_logger.infox("user wants subject modified for tagged mail")
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_milter_reply({
remove_headers = {['Subject'] = 1},
add_headers = {['Subject'] = new_sbj}
})
else
rspamd_logger.infox("Add X-Moo-Tag header")
task:set_rmilter_reply({
task:set_milter_reply({
add_headers = {['X-Moo-Tag'] = 'YES'}
})
end
else
rspamd_logger.infox("Skip delimiter handling for untagged message or authenticated user")
end
return false
end
}
end,
priority = 11
})
rspamd_config:register_symbol({
name = 'DYN_RL_CHECK',
type = 'prefilter',
callback = function(task)
local util = require("rspamd_util")
local redis_params = rspamd_parse_redis_server('dyn_rl')
local rspamd_logger = require "rspamd_logger"
local envfrom = task:get_from(1)
local env_from_domain = envfrom[1].domain:lower() -- get smtp from domain in lower case
local env_from_addr = envfrom[1].addr:lower() -- get smtp from addr in lower case
local function redis_cb_user(err, data)
if err or type(data) ~= 'string' then
rspamd_logger.infox(rspamd_config, "dynamic ratelimit request for user %s returned invalid or empty data (\"%s\") or error (\"%s\") - trying dynamic ratelimit for domain...", env_from_addr, data, err)
local function redis_key_cb_domain(err, data)
if err or type(data) ~= 'string' then
rspamd_logger.infox(rspamd_config, "dynamic ratelimit request for domain %s returned invalid or empty data (\"%s\") or error (\"%s\")", env_from_domain, data, err)
else
rspamd_logger.infox(rspamd_config, "found dynamic ratelimit in redis for domain %s with value %s", env_from_domain, data)
task:insert_result('DYN_RL', 0.0, data)
end
end
local redis_ret_domain = rspamd_redis_make_request(task,
redis_params, -- connect params
env_from_domain, -- hash key
false, -- is write
redis_key_cb_domain, --callback
'HGET', -- command
{'RL_VALUE', env_from_domain} -- arguments
)
if not redis_ret_domain then
rspamd_logger.infox(rspamd_config, "cannot make request to load ratelimit for domain")
end
else
rspamd_logger.infox(rspamd_config, "found dynamic ratelimit in redis for user %s with value %s", env_from_addr, data)
task:insert_result('DYN_RL', 0.0, data)
end
end
local redis_ret_user = rspamd_redis_make_request(task,
redis_params, -- connect params
env_from_addr, -- hash key
false, -- is write
redis_cb_user, --callback
'HGET', -- command
{'RL_VALUE', env_from_addr} -- arguments
)
if not redis_ret_user then
rspamd_logger.infox(rspamd_config, "cannot make request to load ratelimit for user")
end
return true
end,
priority = 20
})

View File

@@ -5,3 +5,4 @@ secure_ip = "172.16.0.0/12";
secure_ip = "10.0.0.0/8";
secure_ip = "127.0.0.1";
secure_ip = "::1";
secure_ip = "fd4d:6169:6c63:6f77::/64"

View File

@@ -0,0 +1,7 @@
bind_socket = "rspamd:9900";
milter = true;
upstream {
name = "localhost";
default = true;
hosts = "rspamd:11333"
}

View File

@@ -63,6 +63,11 @@
SOGoTrashFolderName = "Trash";
SOGoVacationEnabled = YES;
SOGoCacheCleanupInterval = 900;
SOGoMaximumFailedLoginCount = 10;
SOGoMaximumFailedLoginInterval = 900;
SOGoFailedLoginBlockInterval = 900;
MySQL4Encoding = "utf8mb4";
//SOGoDebugRequests = YES;
//SoDebugBaseURL = YES;
@@ -73,4 +78,5 @@
//MySQL4DebugEnabled = YES;
//SOGoUIxDebugEnabled = YES;
//WODontZipResponse = YES;
WOLogFile = "/dev/sogo_log";
}

View File

@@ -0,0 +1,26 @@
server:
verbosity: 1
interface: 0.0.0.0
interface: ::0
logfile: /dev/stdout
do-ip4: yes
do-ip6: yes
do-udp: yes
do-tcp: yes
do-daemonize: no
access-control: 172.22.1.0/24 allow
access-control: fd4d:6169:6c63:6f77::/64 allow
directory: "/etc/unbound"
username: unbound
auto-trust-anchor-file: trusted-key.key
private-address: 10.0.0.0/8
private-address: 172.16.0.0/12
private-address: 192.168.0.0/16
private-address: 169.254.0.0/16
private-address: fd00::/8
private-address: fe80::/10
root-hints: "/etc/unbound/root.hints"
hide-identity: yes
hide-version: yes
max-udp-size: 4096
msg-buffer-size: 65552