GoWiki
Index
Sections
Search
Login
openbsd
Mail Server
# OpenBSD Mail Server Sources: * https://poolp.org/posts/2019-09-14/setting-up-a-mail-server-with-opensmtpd-dovecot-and-rspamd/ * https://github.com/vedetta-com/caesonia * https://tools.ietf.org/html/rfc8314#section-3.3 * changelist(5) * http://arc-spec.org/ * https://github.com/hardware/mailserver/tree/master/rootfs/etc/rspamd/local.d ## Intro How to setup an OpenBSD 6.6 Mail Server with: * OpenSMTPD (mta) * spamd (tarpit) * rspamd (spamfilter, DKIM) * Kopano (IMAPS, webapp, z-push) not included POP3 and CalDav. * bgp-spamd (blacklists) **Note:** This is mostly a copy/past guide. It is still recommended to read the man-pages to 'understand' why and how things work. **Note:** You need to replace all occurrences of `example.com` or `example.org` with your domain of choice. Ports required: * SSH: 22 * BGP: 179 * SMTP: 25 * SMTPS: 465 * Submission: 587 * IMAPS: 993 * HTTP: 80 * HTTPS: 443 * CalDav: 8443 ## Mail-Flow ### Incoming mail (blacklisted) ``` pf -> relay to spamd (tarpit) ``` ### Incoming mail (greylisted) ``` pf -> relay to opensmtpd -> rspamd (greylist handling) ``` ### Incoming mail (whitelisted) ``` pf -> relay to opensmtpd -> rspamd (spam filtering) -> deliver to mda (kopano-dagent) ``` ### Outgoing mail (webapp) ``` kopano (kopano-spooler) -> opensmtpd -> rspamd (DKIM) -> relay out ``` ### Outgoing mail (SMTP) ``` opensmtpd (AUTH) -> rspamd (DKIM) -> relay out ``` ## DNS Basic DNS config: ``` cat <<EOF IN MX 10 mx.example.com. @ 86400 IN TXT "v=spf1 a:mx.example.com -all" IN CAA 128 issue "letsencrypt.org" IN CAA 128 issuewild ";" mx IN A <your ip> mail IN A <your ip> mta-sts IN A <your ip> _mta-sts IN TXT "v=STSv1; id=$(date +%Y%m%d%H%M%SZ)" EOF ``` ## Rspamd Install: ``` pkg_add rspamd opensmtpd-filter-rspamd redis p5-Mail-SpamAssassin ``` Change the controller password: ``` mkdir /etc/rspamd/{local.d,override.d} echo "password = \"$(rspamadm pw)\";" > /etc/rspamd/local.d/worker-controller.inc echo 'local_addrs = "127.0.0.0/8, ::1";' > /etc/rspamd/local.d/options.inc echo "enabled = true;" > /etc/rspamd/local.d/mx_check.conf cat <<EOF> /etc/rspamd/local.d/classifier-bayes.conf servers = "127.0.0.1"; backend = "redis"; EOF cat <<EOF> /etc/rspamd/local.d/redis.conf servers = "127.0.0.1"; write_servers = "127.0.0.1"; EOF cat <<EOF> /etc/rspamd/local.d/neural.conf servers = 127.0.0.1:6379; # Redis server to store learning data and ANN train { max_train = 1k; # Number of trains per epoch max_usages = 20; # Number of learn iterations while ANN data is valid spam_score = 8; # Score to learn spam ham_score = -2; # Score to learn ham learning_rate = 0.01; # Rate of learning (Torch only) max_iterations = 25; # Maximum iterations of learning (Torch only) } timeout = 20; # Increase redis timeout EOF cat <<EOF> /etc/rspamd/local.d/force_actions.conf rules { NOMX { action = "reject"; expression = "MX_MISSING"; message = "Sending domain has no MX record"; } } EOF cat <<EOF> /etc/rspamd/local.d/multimap.conf spammy_tld_env_from { type = "from"; filter = "email:domain:tld"; map = "https://raw.githubusercontent.com/mxroute/rspamd_rules/master/lists/tlds.txt"; symbol = "SPAMMY_TLD_ENVFROM"; description = "Sending TLD likely spam"; score = 1.5; regexp = true; } spammy_tld_from { type = "header"; header = "from"; filter = "email:domain:tld"; map = "https://raw.githubusercontent.com/mxroute/rspamd_rules/master/lists/tlds.txt"; symbol = "SPAMMY_TLD_FROM"; require_symbols = "!SPAMMY_TLD_ENVFROM"; description = "Sending TLD likely spam"; score = 1.5; regexp = true; } MXROUTE_BODY_SPAM { type = "content"; filter = "text"; map = "https://raw.githubusercontent.com/mxroute/rspamd_rules/master/lists/body-spam.map"; symbol = "MXROUTE_BODY_SPAM"; prefilter = true; action = "reject"; regexp = true; message = "The text of this email contained a string that we identified to be spam."; } SPAMMY_SUBJECT { type = "header"; header = "subject"; map = "https://raw.githubusercontent.com/mxroute/rspamd_rules/master/lists/subject-spam.map"; symbol = "SPAMMY_SUBJ"; prefilter = true; action = "reject"; regexp = true; } SENDER_FROM_BLACKLIST { type = "from"; map = "https://raw.githubusercontent.com/mxroute/rspamd_rules/master/lists/blacklisted-sender-strings.map"; regexp = true; description = "Blacklisted sender"; prefilter = true; filter = "email:addr" action = "reject"; message = "This sender has been blocked for matching a known spam trend"; } MXROUTE_EXPLORING { type = "content"; filter = "text"; map = "https://raw.githubusercontent.com/mxroute/rspamd_rules/master/lists/exploring.map"; symbol = "MXROUTE_EXPLORING"; prefilter = true; action = "add_header"; regexp = true; message = "We are observing this data to see how many and what kind of data it returns."; } EOF cat <<EOF> /etc/rspamd/local.d/mx_check.conf timeout = 5.0; symbol_bad_mx = "MX_INVALID"; symbol_no_mx = "MX_MISSING"; symbol_good_mx = "MX_GOOD"; expire = 86400; key_prefix = "rmx"; enabled = true; greylist_invalid = false; exclude_domains = [ "https://raw.githubusercontent.com/mxroute/rspamd_rules/master/lists/mxcheck_exclude.inc", ]; EOF cat <<EOF> /etc/rspamd/override.d/actions.conf reject = 15; add_header = 8; greylist = 40; EOF cat <<EOF> /etc/rspamd/override.d/fuzzy_check.conf rule "rspamd.com" { servers = ""; } EOF cat <<EOF> /etc/rspamd/override.d/metrics.conf symbol "MIME_BASE64_TEXT_BOGUS" { weight = 3; description = "Has text part encoded in base64 that does not contain any 8bit characters"; } symbol "DMARC_POLICY_REJECT" { weight = 100; description = "Domain owner requested this be rejected by DMARC"; } symbol "MXROUTE_BODY_SPAM" { weight = 100; description = "Message identified by us as likely spam"; } symbol "ONCE_RECEIVED_STRICT" { weight = 0; description = "Lets kill this rule"; } symbol "DBL_ABUSE" { weight = 100; description = "Email mentions domain listed at Spamhaus DBL"; } symbol "NO_BOUNCE" { weight = 100; description = "No bounce loops"; } symbol "FREEMAIL_TO" { weight = 0.0; description "Freemail recipients"; } symbol "SPAMMY_SUBJ" { weight = 100; description = "Email subject matches spam trend"; } symbol "IP_SCORE" { weight = 0.0; description = "IP reputation"; } symbol "FROM_NEQ_DISPLAY_NAME" { weight = 0.0; description = "Display name contains an email address different to the From address"; } symbol "R_SUSPICIOUS_URL" { weight = 3.0; description = "Suspicious URL"; } symbol "FORGED_SENDER" { weight = 1.5; description = "From address has been spoofed"; } symbol "FREEMAIL_FROM" { weight = 3.0; description = "From address has been spoofed"; } symbol "R_RATELIMIT" { weight = 9; description = "Rate limited"; } symbol "BROKEN_HEADERS" { weight = 0; description = "Header check"; } symbol "FORGED_RECIPIENTS" { weight = 0; description = "Recipient check"; } symbol "HAS_PHPMAILER_SIG" { weight = 2; description = "PHP Mailer header" } symbol "SPAM_FLAG" { weight = 7; description = "Identified as spam by previous server" } symbol "MIME_BAD_ATTACHMENT" { weight = 0; description = "Bad attachment" } symbol "BLACKLIST_DMARC" { weight = 0; description = "Mail comes from the whitelisted domain and has valid failed DMARC and DKIM policies"; } symbol "MX_INVALID" { weight = 0; description = "From domain has no valid MX records or this rule sucks"; } symbol "DBL_SPAM" { weight = 15; description = "Contains reference domain listed at SpamHaus"; } symbol "SPOOF_REPLYTO" { weight = 0; description = "Spoofing reply-to has valid use cases so setting score to 0"; } EOF ``` ### DKIM Generate keys: **Note:** add the outputs of `rspamadm dkim_keygen` to your DNS config. ``` mkdir -p /var/lib/rspamd/dkim rspamadm dkim_keygen -s 'dkim' -b 2048 -d example.com -k /var/lib/rspamd/dkim/example.com.dkim.key rspamadm dkim_keygen -s 'eddsa' -d example.com -t ed25519 -k /var/lib/rspamd/dkim/example.com.eddsa.key chmod 640 /var/lib/rspamd/dkim/* chgrp _rspamd /var/lib/rspamd/dkim/* ``` Config: ``` cat <<EOF> /etc/rspamd/local.d/dkim_signing.conf # allow_username_mismatch = true; domain { example.com { selectors [ { path = "/var/lib/rspamd/dkim/example.com.dkim.key"; selector = "dkim"; }, { path = "/var/lib/rspamd/dkim/example.com.eddsa.key"; selector = "eddsa"; } ] } example.org { path = "/var/lib/rspamd/dkim/example.org.eddsa.key"; selector = "eddsa"; } } EOF cat <<'EOF'> /etc/rspamd/local.d/worker-normal.inc dkim_signing { .include(try=true; priority=1,duplicate=merge) "$LOCAL_CONFDIR/local.d/dkim_signing.conf" } EOF ``` ### Start Rspamd version `1.9.4` causes zombie processes on shutdown. Stop workaround: ``` sed -i '/rc_cmd/d' /etc/rc.d/rspamd cat <<'EOF'>> /etc/rc.d/rspamd rc_stop() { pkill -T "${daemon_rtable}" -xf "${pexp}" i=0 while [[ $i -lt 5 ]]; do sleep 1; rc_check || return; i=$((i+1)); done pkill -9 -T "${daemon_rtable}" -xf "${pexp}" } rc_cmd $1 EOF ``` ``` rcctl enable redis rspamd rcctl start redis rspamd ``` ## httpd Basic config at [OpenBSD httpd](/openbsd/httpd) Kopano config: ``` prefork 4 mx="mx.example.com" mail="mail.example.com" mtasts="mta-sts.example.com" server $mx { listen on * port 80 location "/.well-known/acme-challenge/*" { root "/acme" request strip 2 } location "/*" { block return 301 "https://$mail$DOCUMENT_URI" } } server $mail { listen on * port 80 location "/.well-known/acme-challenge/*" { root "/acme" request strip 2 } location "/*" { block return 301 "https://$SERVER_NAME$DOCUMENT_URI" } } server $mail { listen on * tls port 443 tls { certificate "/etc/ssl/mx.example.com.fullchain.pem" key "/etc/ssl/private/mx.example.com.key" ciphers "AEAD-AES256-GCM-SHA384:AEAD-CHACHA20-POLY1305-SHA256:AEAD-AES128-GCM-SHA256:ECDHE+AESGCM+AES256:ECDHE+CHACHA20:ECDHE+AESGCM+AES128" ecdhe "X25519:secp384r1:secp521r1" } connection { max request body 20971520 } hsts { max-age 31536000, subdomains } root "/kopano-webapp" directory index "index.php" location "/robots.txt" { root "/robots" } location "*.php" { fastcgi socket "/run/php-fpm.sock" } location "/Microsoft-Server-ActiveSync" { root "/z-push/index.php" fastcgi socket "/run/php-fpm.sock" } } server $mtasts { listen on * port 80 location "/.well-known/acme-challenge/*" { root "/acme" request strip 2 } location "/*" { block return 301 "https://$SERVER_NAME$DOCUMENT_URI" } } server $mtasts { listen on * tls port 443 tls { certificate "/etc/ssl/mx.example.com.fullchain.pem" key "/etc/ssl/private/mx.example.com.key" ciphers "AEAD-AES256-GCM-SHA384:AEAD-CHACHA20-POLY1305-SHA256:AEAD-AES128-GCM-SHA256:ECDHE+AESGCM+AES256:ECDHE+CHACHA20:ECDHE+AESGCM+AES128" ecdhe "X25519:secp384r1:secp521r1" } hsts { max-age 31536000, subdomains } root "/htdocs" } ``` Add mta-sts file: ``` mkdir /var/www/htdocs/.well-known cat <<EOF> /var/www/htdocs/.well-known/mta-sts.txt version: STSv1 mode: enforce mx: mx.example.com max_age: 604800 EOF ``` ## Lets Encrypt Domains: * mx.example.com * mail.example.com * mta-sts.example.com See: [Lets Encrypt](/openbsd/Lets+Encrypt) Config: ``` domain mx.example.com { alternative names { mail.example.com, mta-sts.example.com } domain key "/etc/ssl/private/mx.example.com.key" domain certificate "/etc/ssl/mx.example.com.pem" domain full chain certificate "/etc/ssl/mx.example.com.fullchain.pem" sign with letsencrypt } ``` ``` cat <<'EOF' >/usr/local/sbin/cron-acme-update #!/bin/sh /usr/sbin/acme-client example.com if [ $? -eq 0 ]; then rcctl restart smtpd spamd httpd kopano_gateway kopano_ical fi EOF ``` ## smtp authentication Install: https://git.giftfish.de/ston1th/smtp_auth_sync ## smtpd Boot startup: ``` rcctl enable smtpd rcctl set smtpd flags -v ``` ### Virtual Domains This file holds all virtual domains: ``` cat /etc/mail/vdomains example.com example.org ``` ### Virtual Users Create the virtual system user: ``` useradd -d /var/empty -s /sbin/nologin vmail ``` This file holds the virtual user mappings: ``` # cat /etc/mail/vusers john@example.com vmail @example.com john@example.com doe@example.org vmail hostmaster@example.org doe@example.org ``` Create secrets file: ``` touch /etc/mail/secrets chmod 600 /etc/mail/secrets ``` ### smtpd.conf Replace `example.com` with your domain in `/etc/mail/smtpd.conf`. ``` smtp max-message-size 20M pki mx.giftfish.de cert "/etc/ssl/mail.example.com.fullchain.pem" pki mx.giftfish.de key "/etc/ssl/private/mail.example.com.key" smtp ciphers AEAD-AES256-GCM-SHA384:AEAD-CHACHA20-POLY1305-SHA256:AEAD-AES128-GCM-SHA256:ECDHE+AESGCM+AES256:ECDHE+CHACHA20:ECDHE+AESGCM+AES128 filter check_dyndns phase connect match rdns regex { '.*\.dyn\..*', '.*\.dsl\..*' } \ disconnect "550 no residential connections" filter check_rdns phase connect match !rdns \ disconnect "550 no rDNS entry" filter check_fcrdns phase connect match !fcrdns \ disconnect "550 no FCrDNS entry" #filter senderscore \ # proc-exec "filter-senderscore -blockBelow 10 -junkBelow 70 -slowFactor 5000" filter rspamd proc-exec "filter-rspamd" filter header proc-exec "filter-header" table aliases "/etc/mail/aliases" table vdomains "/etc/mail/vdomains" table vusers "/etc/mail/vusers" table secrets "/etc/mail/secrets" listen on lo0 mask-src filter { header, rspamd } listen on egress tls pki mail.example.com auth-optional \ filter { check_dyndns, check_rdns, check_fcrdns, rspamd } listen on egress smtps pki mail.example.com auth-optional \ filter { check_dyndns, check_rdns, check_fcrdns, rspamd } listen on egress port submission tls pki mail.example.com \ auth <secrets> mask-src filter { header, rspamd } action "local" maildir junk alias <aliases> action "outbound" relay helo mx.giftfish.de action "kopano" mda "/usr/local/sbin/kopano-dagent -s %{dest:lowercase|strip}" virtual <vusers> user "_kopano" match for local action "local" match from any for domain <vdomains> action "kopano" match from any auth action "outbound" match from local for any action "outbound" match !for domain <vdomains> reject ``` ### Header filter Headers filtered: * User-Agent * X-Mailer Based on: https://github.com/jirutka/opensmtpd-filter-rewrite-from ``` cat <<'EOF'> /usr/local/libexec/smtpd/filter-header #!/usr/bin/awk -f function die(msg) { printf("mail-from: %s\n", msg) > "/dev/stderr" exit 1 } function output(type, v, t, s, msg) { if (v < "0.5") { print(type, t, s, msg) } else { print(type, s, t, msg) } fflush() } BEGIN { ARGC = 0 FS = "|" OFS = FS _ = FS in_body[""] = 0 } "config|ready" == $0 { print("register|filter|smtp-in|data-line") print("register|ready") fflush() next } "config" == $1 { next } "filter" == $1 { if (NF < 7) { die("invalid filter command: expected >6 fields!") } ver = $2 sid = $6 token = $7 line = substr($0, length($1$2$3$4$5$6$7) + 8) } "filter|smtp-in|data-line" == $1_$4_$5 { if (line == "") { in_body[sid] = 1 } if (!in_body[sid] && match(line, /^User-Agent:[\t ]*|^X-Mailer:[\t ]*/)) { next } if (line == ".") { delete in_body[sid] } output("filter-dataline", ver, token, sid, line) } EOF chmod 755 /usr/local/libexec/smtpd/filter-header ``` Restart smtpd: `rcctl restart smtpd` ## spamd Create custom spam files: ``` touch /etc/mail/custom.black ``` Config: ``` cat <<EOF> /etc/mail/spamd.conf all:\ :nixspam:custom_black: nixspam:\\ :black:\\ :msg="Your address %A is in the nixspam list\n\\ See http://www.heise.de/ix/nixspam/dnsbl_en/ for details":\\ :method=http:\\ :file=www.openbsd.org/spamd/nixspam.gz custom_black:\\ :black:\\ :msg="Your address %A has been manually blacklisted":\\ :method=file:\\ :file=/etc/mail/custom.black: EOF ``` Boot startup: Replace `example.com` with your domain. ``` rcctl enable spamd rcctl set spamd flags "-v -G 4:4:864 -K /etc/ssl/private/example.com.key -C /etc/ssl/example.com.fullchain.pem" rcctl enable spamlogd ``` Create `spamd-setup` cronjob: `echo "*/5 * * * * /usr/libexec/spamd-setup -b >/dev/null 2>&1" >> /var/cron/tabs/root` ### pf Rules ``` cat <<EOF>> /etc/pf.conf set limit table-entries 400000 set skip on lo block all smtp_ports="{ smtp, smtps }" in_tcp = "{ ssh, http, https, imaps, 8443 }" pass in on egress proto tcp to any port $in_tcp bgp_addrs = "{ 64.142.121.62, 217.31.80.170, 2a00:15a8:0:100:0:d91f:50aa:1 }" pass in on egress proto tcp from $bgp_addrs to any port bgp pass in on egress proto icmp table <spamd> persist table <bgp_spamd> persist table <bruteforce> persist match in all scrub (no-df random-id max-mss 1440) block in quick log from urpf-failed label uRPF block quick log from <bruteforce> block quick proto {tcp,udp} user www pass in quick log on egress proto tcp from any to any port 587 pass in quick log on egress proto tcp \ from { <bgp_spamd>, <spamd> } \ to egress port $smtp_ports \ rdr-to 127.0.0.1 port spamd pass in quick log on egress proto tcp \ from any to any port $smtp_ports \ flags S/SA modulate state out_tcp = "{ ssh, domain, smtp, smtps, http, https }" pass out log on egress proto tcp to any port $out_tcp pass out log on egress proto tcp to $bgp_addrs port bgp out_udp = "{ domain, ntp }" pass out log on egress proto udp to any port $out_udp pass out log on egress proto { icmp, icmp6 } to any EOF pfctl -f /etc/pf.conf ``` Start spamd and spamlogd: ``` rcctl start spamd rcctl start spamlogd ``` ## MariaDB Basic config at [OpenBSD MariaDB](/openbsd/MariaDB) Create database: ``` mysql -u root -p CREATE DATABASE kopano CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; GRANT ALL ON kopano.* TO 'kopano'@'localhost' IDENTIFIED BY '<password>'; FLUSH PRIVILEGES; ``` ## Kopano Sets required: * xbaseXX.tgz * xfontXX.tgz * xshareXX.tgz Install: `pkg_add kopano-core kopano-mapi py3-bsddb3 py3-magic` Boot startup: `rcctl enable kopano` Make sure mysqld starts before kopano: `rcctl order mysqld` ### Config Change services to start: **Note:** The services `kopano_search` and `kopano_spamd` are broken and do not work. ``` perl -p -i -e 's/^(_pkg_scripts=.*)/#$1\n_pkg_scripts="kopano_server kopano_spooler kopano_monitor kopano_gateway kopano_ical"/' /etc/rc.d/kopano ``` Allow socket in chroot: ``` mkdir /var/www/run/kopano chown _kopano: /var/www/run/kopano ``` ``` /etc/kopano/dagent.cfg spam_header_name = X-Spam ``` ``` /etc/kopano/server.cfg # Replace server_listen = *:236 # with server_listen = # Add: server_pipe_name = /var/run/kopano/server.sock /var/www/run/kopano/server.sock # Set DB Credentials: mysql_user = kopano mysql_password = <password> mysql_socket = /var/run/mysql/mysql.sock disabled_features = pop3 log_file = /var/log/kopano/server.log ``` ``` /etc/kopano/gateway.cfg pop3_listen = pop3s_listen = imap_listen = imaps_listen = *:993 server_socket = file:///var/run/kopano/server.sock disable_plaintext_auth = yes ssl_private_key_file = /etc/ssl/private/example.com.key ssl_certificate_file = /etc/ssl/example.com.fullchain.pem tls_min_proto = tls1.2 ssl_ciphers = AEAD-AES256-GCM-SHA384:AEAD-CHACHA20-POLY1305-SHA256:AEAD-AES128-GCM-SHA256:ECDHE+AESGCM+AES256:ECDHE+CHACHA20:ECDHE+AESGCM+AES128 ssl_prefer_server_ciphers = yes log_file = /var/log/kopano/gateway.log ``` ``` /etc/kopano/ical.cfg ical_listen = icals_listen = *:8443 server_socket = file:///var/run/kopano/server.sock ssl_private_key_file = /etc/ssl/private/example.com.key ssl_certificate_file = /etc/ssl/example.com.fullchain.pem tls_min_proto = tls1.2 ssl_ciphers = AEAD-AES256-GCM-SHA384:AEAD-CHACHA20-POLY1305-SHA256:AEAD-AES128-GCM-SHA256:ECDHE+AESGCM+AES256:ECDHE+CHACHA20:ECDHE+AESGCM+AES128 ssl_prefer_server_ciphers = yes log_file = /var/log/kopano/ical.log ``` ``` /etc/kopano/spooler.cfg log_file = /var/log/kopano/spooler.log ``` ``` /etc/kopano/dagent.cfg log_file = /var/log/kopano/dagent.log ``` Start kopano: `rcctl start kopano` Create a user: `kopano-admin -c "john@example.com" -f "John Doe" -e "john@example.com" -P` Credentials: * Submission (STARTTLS) * Port: 587 * User: john@example.com * IMAPS * Port: 993 * User: john@example.com * CalDav * Port: 8443 * URL: `https://mx.example.com:8443/caldav/john@example.com/` ### DB Upgrade ``` kopano-dbadm k-1216 kopano-dbadm usmp ``` ## Kopano Webapp Install: `pkg_add kopano-webapp` Boot startup: `rcctl enable php74_fpm` ### Config ``` echo "extension=mapi.so" > /etc/php-7.4/kopano.ini sed -i -e 's/^pm.max_children.*/pm.max_children = 20/' \ -e 's/^pm.start_servers.*/pm.start_servers = 4/' \ -e 's/^pm.min_spare_servers.*/pm.min_spare_servers = 2/' \ -e 's/^pm.max_spare_servers.*/pm.max_spare_servers = 6/' /etc/php-fpm.conf sed -i 's/^expose_php.*/expose_php = off/' /etc/php-7.4.ini ``` Change Server Socket: ``` sed -i 's|^.define("DEFAULT_SERVER".*|define("DEFAULT_SERVER", "file:///run/kopano/server.sock");|' /var/www/kopano-webapp/config.php ``` Remove unused plugins: ``` rm -rf /var/www/kopano-webapp/plugins/{filesbackendSMB,filesbackendOwncloud} ``` Start php-fpm: `rcctl start php74_fpm` ## Z-Push Install: `pkg_add z-push` ### Config Change Server Socket: ``` sed -i "s|^define('MAPI_SERVER'.*|define('MAPI_SERVER', 'file:///run/kopano/server.sock');|" /var/www/z-push/backend/kopano/config.php ``` # Optionals ## Kopano Passwd Plugin The password plugin cannot be used anymore. The required functions have been removes from kopano-core in: https://stash.kopano.io/projects/KC/repos/kopanocore/commits/9a003acd34857cf2c27d0d9c581af0e233b48510#php7-ext/main.cpp ## OpenBGPD Project: [bgp-spamd](http://bgp-spamd.net/) Boot startup: `rcctl enable bgpd` ### Config OpenBGPD config: ``` cat <<'EOF'> /etc/bgpd.conf spamdAS="65066" AS 65001 fib-update no # Mandatory, to not update the local routing table nexthop qualify via default # Make sure the nexthop is valid group "spamd-bgp" { remote-as $spamdAS multihop 64 export none # us.bgp-spamd.net neighbor 64.142.121.62 # eu.bgp-spamd.net neighbor 217.31.80.170 # IPv6 eu.bgp-spamd.net #neighbor 2a00:15a8:0:100:0:d91f:50aa:1 } deny to any deny from any allow from group "spamd-bgp" match from group "spamd-bgp" community $spamdAS:666 set pftable "bgp_spamd" EOF chmod 600 /etc/bgpd.conf ``` Start OpenBGPD: `rcctl start bgpd` Add these options to your `/etc/pf.conf`: ``` set limit table-entries 400000 table <bgp_spamd_bypass> persist table <bgp_spamd> persist bgp_addrs = "{ 64.142.121.62, 217.31.80.170, 2a00:15a8:0:100:0:d91f:50aa:1 }" pass in on egress proto tcp from $bgp_addrs to any port bgp ``` Change your pf smtp rules to match these: ``` pass in quick log on egress proto tcp \ from { <bgp_spamd_bypass>, <nospamd>, <spamd-white> } \ to any port $smtp_ports \ flags S/SA modulate state pass in quick proto tcp from { <bgp_spamd>, <spamd> } \ to egress port $smtp_ports \ rdr-to 127.0.0.1 port spamd ``` Reload pf: `pfctl -f /etc/pf.conf` Edit the `/etc/mail/spamd.conf`: ``` # Add bgp-spamd to the all list: all:\ :nixspam:custom_black:bgp-spamd: # Add the bgp-spamd list: bgp-spamd:\ :black:\ :msg="Your address %A has sent mail to a spamtrap\n\ within the last 24 hours":\ :method=file:\ :file=/etc/mail/spamd.black: ``` Create the spamd-reload script: ``` cat <<'EOF'> /usr/local/sbin/spamd-update #!/bin/sh black="/etc/mail/spamd.black" custom="/etc/mail/custom.black" bgpctl show rib community 65066:666|tail -n+5| awk '{sub(/\/.*/, "", $1);print $1}'|uniq >${black} for ip in $(cat ${custom}); do /usr/sbin/spamdb -d ${ip} 2> /dev/null done /usr/libexec/spamd-setup -b EOF chmod 0744 /usr/local/sbin/spamd-update ``` Replace the `spamd-setup` cronjob from above with the new script and run it once manually: `/usr/local/sbin/spamd-update` ## Backup ``` #!/bin/sh db="kopano" db_user="kopano" db_pw="<password>" set -A paths \ /etc/kopano \ /etc/mail \ /etc/rspamd \ /etc/pf.conf \ /etc/httpd.conf \ /etc/acme \ /etc/ssl \ /etc/acme-client.conf \ /etc/bgpd.conf \ /etc/rc.conf.local \ /etc/my.cnf \ /etc/rc.d/kopano \ /etc/php-7.3/kopano.ini \ /etc/php-7.3.ini \ /var/rspamd \ /var/lib/rspamd \ /var/db/spamd \ /var/db/kopano \ /var/www/htdocs/robots.txt \ /var/www/htdocs/.well-known \ /var/www/kopano-webapp/config.php \ /var/www/kopano-webapp/server/includes/core/class.webappauthentication.php \ /var/www/z-push/backend/kopano/config.php \ /usr/local/sbin/spamd-update \ /usr/local/sbin/cron-acme-update \ /var/cron/tabs/root \ /var/redis/dump.rdb \ /tmp/*.pf \ /tmp/${db}.sql for t in $(pfctl -s Tables); do pfctl -t ${t} -T show > /tmp/${t}.pf done mysqldump -u ${db_user} -p${db_pw} ${db} >/tmp/${db}.sql redis-cli <<EOF SAVE EOF tar cfvz /root/mailserver_backup_$(date +%Y%m%d_%H%M%S).tgz ${paths[@]} rm /tmp/${db}.sql /tmp/*.pf ``` Restore backup: ``` pkg_add rspamd opensmtpd-filter-rspamd kopano-core kopano-mapi py3-bsddb3 py3-magic kopano-webapp z-push tar xfpvz mailserver_backup<date>.tgz -C / mkdir /var/www/run/kopano chown _kopano: /var/www/run/kopano useradd -d /var/empty -s /sbin/nologin vmail ``` Restore pf tables: `for t in $(ls /tmp/*.pf); do pfctl -t ${t%.pf} -T add -f ${t}; done` Restore database: `mysql -u root -p kopano < /tmp/kopano.sql` # Bug fixes ## Watchdog ``` cat <<'EOF'>/usr/local/sbin/watchdog #!/bin/sh for p in $(rcctl ls failed); do rcctl start ${p} done chmod 755 /usr/local/sbin/watchdog echo "*/5 * * * * /usr/local/sbin/watchdog >/dev/null 2>&1" >> /var/cron/tabs/root ``` # Manual SPAM and HAM reporting **NOTE:** replace `example.com` with your domain. For the manual spam and ham reporting to work properly, you have to forward the desired mails as attachment to spam@example.com (for spam training - bad mails) and ham@example.com (for ham training - good mails). Attachment parser script: ``` cat <<"EOF"> /usr/local/bin/attachment_report.pl #!/usr/bin/perl use strict; use warnings; use Config; use Email::MIME::Attachment::Stripper; my $rspamc = "/usr/local/bin/rspamc"; my $learn; if ($ARGV[0] eq "spam") { $learn = "${rspamc} learn_spam"; } elsif ($ARGV[0] eq "ham") { $learn = "${rspamc} learn_ham"; } else { die "error: argument must be 'spam' or 'ham'\n"; } if ($Config{osname} eq "openbsd") { eval "use OpenBSD::Pledge"; if ($Config{osvers} >= "6.6") { eval "use OpenBSD::Unveil"; unveil($rspamc, "rx") or die "Unable to unveil: $!"; } pledge("proc exec prot_exec") or die "Unable to pledge: $!"; } local $/; my $mail = <STDIN>; my $s = Email::MIME::Attachment::Stripper->new($mail); my @a = $s->attachments; for my $i (1 .. $#a) { open(IN, "| $learn") or die "$learn failed: $!\n"; print IN $a[$i]{payload}; close(IN); } EOF chmod 755 /usr/local/bin/attachment_report.pl ``` Create spam virtual users: ``` cat <<EOF> /etc/mail/spam spam@example.com vmail ham@example.com vmail EOF ``` Add the following lines to your smtpd.conf: ``` table spam "/etc/mail/spam" action "ham" mda "/usr/local/bin/attachment_report.pl ham" virtual <spam> user "_rspamd" action "spam" mda "/usr/local/bin/attachment_report.pl spam" virtual <spam> user "_rspamd" match from local for domain <vdomains> rcpt-to "ham@example.com" action "ham" match from local for domain <vdomains> rcpt-to "spam@example.com" action "spam" ``` # END