limit exim to Debian-exim user
[distro-setup] / mail-setup
index ae3aabbe9067365500861c500a77ae08c3f17a66..13f99478e6a7ffb2d85d59a79984522a7dbac57a 100755 (executable)
@@ -4,15 +4,7 @@
 # SPDX-License-Identifier: AGPL-3.0-or-later
 
 # todo: sandbox / harden exim:
-#  1. stop it from running as root. how?
-#  https://www.exim.org/exim-html-current/doc/html/spec_html/ch-security_considerations.html
-#  * avoid using .forward files, remove that router
-#  * set deliver_drop_privilege
-#  * set user to run as Debian-exim in systemd
-#  * set port to something like 2500, and forward 25 to 2500 with iptables. same for 587.
-#  https://superuser.com/questions/710253/allow-non-root-process-to-bind-to-port-80-and-443/1334552#1334552
-#  * consider whether other routers like postmaster need modification / removal.
-#  2. restrict its filesystem access from within systemd
+#  restrict its filesystem access from within systemd
 
 #  todo: harden dovecot. need to do some research. one way is for it to only listen on a wireguard vpn interface, so only clients that are on the vpn can access it.
 #  todo: consider hardening cups listening on 0.0.0.0
@@ -26,8 +18,6 @@
 # todo: emailing info@amnimal.ninja produces a bounce, user doesn't exist
 # instead of a simple rejection like it should.
 
-# todo: auto restart of je on checkrestart
-
 # todo: run mailping test after running, or otherwise
 # clear out terminal alert
 
@@ -378,7 +368,7 @@ esac
 # * Install universal packages
 
 
-# installs epanicclean
+# installs epanicclean iptables-exim ip6tables-exim
 /a/bin/ds/install-my-scripts
 
 if [[ $(debian-codename-compat) == bionic ]]; then
@@ -394,7 +384,7 @@ pi-nostart exim4 exim4-daemon-heavy spamassassin openvpn unbound clamav-daemon w
 
 # note: pyzor debian readme says you need to run some initialization command
 # but its outdated.
-pi spf-tools-perl p0f postgrey pyzor razor jq moreutils certbot
+pi spf-tools-perl p0f postgrey pyzor razor jq moreutils certbot fail2ban
 # bad packages that sometimes get automatically installed
 pu openresolv resolvconf
 
@@ -483,18 +473,28 @@ case $HOSTNAME in
     ;;
 esac
 
-i /etc/systemd/system/wg-quick@wgmail.service.d/override.conf <<EOF
+case $HOSTNAME in
+  li) : ;;
+  *)
+    i /etc/systemd/system/wg-quick@wgmail.service.d/override.conf <<EOF
 [Unit]
 Requires=mailnn.service
 After=network.target mailnn.service
 JoinsNamespaceOf=mailnn.service
 BindsTo=mailnn.service
+StartLimitIntervalSec=0
 
 [Service]
 PrivateNetwork=true
 # i dont think we need any of these, but it doesnt hurt to stay consistent
 BindPaths=$bindpaths
+
+Restart=on-failure
+RestartSec=20
 EOF
+    ;;
+esac
+
 
 # https://selivan.github.io/2017/12/30/systemd-serice-always-restart.html
 i /etc/systemd/system/mailvpn.service <<EOF
@@ -535,7 +535,7 @@ PrivateNetwork=true
 BindPaths=$bindpaths
 Restart=always
 # time to sleep before restarting a service
-RestartSec=1
+RestartSec=20
 
 [Install]
 WantedBy=multi-user.target
@@ -557,7 +557,7 @@ PrivateNetwork=true
 ExecStart=/usr/bin/flock -w 20 /tmp/newns.flock /a/bin/newns/newns -n 10.173.8 start mail
 ExecStop=/usr/bin/flock -w 20 /tmp/newns.flock /a/bin/newns/newns stop mail
 Restart=always
-RestartSec=10
+RestartSec=20
 
 
 [Install]
@@ -592,7 +592,7 @@ Type=simple
 ExecStart=/usr/local/bin/mailbindwatchdog $vpnser ${nn_progs[@]} unbound.service radicale.service
 Restart=always
 # time to sleep before restarting a service
-RestartSec=1
+RestartSec=10
 
 [Install]
 WantedBy=multi-user.target
@@ -669,7 +669,7 @@ PrivateNetwork=true
 BindPaths=$bindpaths
 
 Restart=always
-RestartSec=1
+RestartSec=20
 EOF
 
     # sooo, there are a few ways to get traffic from the mail network
@@ -720,7 +720,7 @@ PrivateNetwork=true
 BindPaths=$bindpaths
 
 Restart=always
-RestartSec=1
+RestartSec=20
 EOF
     done
     ;;
@@ -823,6 +823,121 @@ if $bhost_t && [[ ! -e /etc/exim4/certs/$wghost/privkey.pem ]]; then
           --deploy-hook /a/bin/ds/le-exim-deploy -d $wghost
 fi
 
+# * fail2ban
+
+# todo: test that these configs actually work, eg run
+# s iptables-exim -S
+# and see someone is banned.
+
+sed 's/^ *before *= *iptables-common.conf/before = iptables-common-exim.conf/' \
+    /etc/fail2ban/action.d/iptables-multiport.conf| i /etc/fail2ban/action.d/iptables-exim.conf
+i /etc/fail2ban/action.d/iptables-common-exim.conf <<'EOF'
+# iank: same as iptables-common, except iptables is iptables-exim, ip6tables is ip6tables-exim
+
+# Fail2Ban configuration file
+#
+# Author: Daniel Black
+#
+# This is a included configuration file and includes the definitions for the iptables
+# used in all iptables based actions by default.
+#
+# The user can override the defaults in iptables-common.local
+#
+# Modified: Alexander Koeppe <format_c@online.de>, Serg G. Brester <serg.brester@sebres.de>
+#       made config file IPv6 capable (see new section Init?family=inet6)
+
+[INCLUDES]
+
+after = iptables-blocktype.local
+        iptables-common.local
+# iptables-blocktype.local is obsolete
+
+[Definition]
+
+# Option:  actionflush
+# Notes.:  command executed once to flush IPS, by shutdown (resp. by stop of the jail or this action)
+# Values:  CMD
+#
+actionflush = <iptables> -F f2b-<name>
+
+
+[Init]
+
+# Option:  chain
+# Notes    specifies the iptables chain to which the Fail2Ban rules should be
+#          added
+# Values:  STRING  Default: INPUT
+chain = INPUT
+
+# Default name of the chain
+#
+name = default
+
+# Option:  port
+# Notes.:  specifies port to monitor
+# Values:  [ NUM | STRING ]  Default:
+#
+port = ssh
+
+# Option:  protocol
+# Notes.:  internally used by config reader for interpolations.
+# Values:  [ tcp | udp | icmp | all ] Default: tcp
+#
+protocol = tcp
+
+# Option:  blocktype
+# Note:    This is what the action does with rules. This can be any jump target
+#          as per the iptables man page (section 8). Common values are DROP
+#          REJECT, REJECT --reject-with icmp-port-unreachable
+# Values:  STRING
+blocktype = REJECT --reject-with icmp-port-unreachable
+
+# Option:  returntype
+# Note:    This is the default rule on "actionstart". This should be RETURN
+#          in all (blocking) actions, except REJECT in allowing actions.
+# Values:  STRING
+returntype = RETURN
+
+# Option:  lockingopt
+# Notes.:  Option was introduced to iptables to prevent multiple instances from
+#          running concurrently and causing irratic behavior.  -w was introduced
+#          in iptables 1.4.20, so might be absent on older systems
+#          See https://github.com/fail2ban/fail2ban/issues/1122
+# Values:  STRING
+lockingopt = -w
+
+# Option:  iptables
+# Notes.:  Actual command to be executed, including common to all calls options
+# Values:  STRING
+iptables = /usr/local/bin/iptables-exim <lockingopt>
+
+
+[Init?family=inet6]
+
+# Option:  blocktype (ipv6)
+# Note:    This is what the action does with rules. This can be any jump target
+#          as per the iptables man page (section 8). Common values are DROP
+#          REJECT, REJECT --reject-with icmp6-port-unreachable
+# Values:  STRING
+blocktype = REJECT --reject-with icmp6-port-unreachable
+
+# Option:  iptables (ipv6)
+# Notes.:  Actual command to be executed, including common to all calls options
+# Values:  STRING
+iptables = /usr/local/bin/ip6tables-exim <lockingopt>
+EOF
+
+i /etc/fail2ban/jail.d/exim.local <<'EOF'
+[exim]
+enabled  = true
+port    = 25,587
+filter   = exim
+banaction = iptables-exim
+EOF
+if $ir; then
+  m systemctl restart fail2ban
+fi
+
 # * common exim4 config
 
 
@@ -846,7 +961,7 @@ awk 'BEGIN { FS = ":" } ; $6 ~ /^\/home/ && $7 !~ /\/nologin$/ { print $1 }' /et
   case $HOSTNAME in
     $MAIL_HOST)
       sed -i "/^user:/d" /etc/aliases
-    ;;
+      ;;
     *)
       if ! grep -q "^$user:" /etc/aliases; then
         echo "$user: root" |m tee -a /etc/aliases
@@ -895,12 +1010,14 @@ fi
 m sed -ri 's/^(\s*rotate\s).*/\11000/' /etc/logrotate.d/exim4-base
 
 
-## https://blog.dhampir.no/content/make-exim4-on-debian-respect-forward-and-etcaliases-when-using-a-smarthost
-# i only need .forwards, so just doing that one.
-cd /etc/exim4/conf.d/router
-b=userforward_higher_priority
-# replace the router name so it is unique
-sed -r s/^\\S+:/$b:/ 600_exim4-config_userforward >175_$b
+## disabled. not using .forward files, but this is still interesting
+## for reference.
+# ## https://blog.dhampir.no/content/make-exim4-on-debian-respect-forward-and-etcaliases-when-using-a-smarthost
+# # i only need .forwards, so just doing that one.
+# cd /etc/exim4/conf.d/router
+# b=userforward_higher_priority
+# # replace the router name so it is unique
+# sed -r s/^\\S+:/$b:/ 600_exim4-config_userforward >175_$b
 
 # todo, consider 'separate' in etc/exim4.conf, could it help on busy systems?
 
@@ -1271,7 +1388,7 @@ PrivateNetwork=true
 BindPaths=$bindpaths
 Restart=always
 # time to sleep before restarting a service
-RestartSec=1000
+RestartSec=20
 
 [Install]
 # for openvpn
@@ -1384,8 +1501,8 @@ case $HOSTNAME in
         break
       fi
     done
-    for f in /p/c/subdir_files/sieve/*sieve /a/c/subdir_files/sieve/*sieve; do
-      m sudo -u $u /a/exe/lnf -T $f $uhome/sieve/${f##*/}
+    for f in /p/c/subdir_files/sieve/*sieve /a/bin/ds/subdir_files/sieve/*sieve; do
+      m sudo -u $u /a/exe/lnf -v -T $f $uhome/sieve/${f##*/}
     done
 
     # https://wiki.dovecot.org/SSL/DovecotConfiguration
@@ -1861,7 +1978,6 @@ EOF
     m chown -R www-data.www-data $rctmpdir /m/rc
     m chmod 750 $rctmpdir
     # Ensure the log file monitored by fail2ban exists, or else fail2ban can't start.
-    # todo: setup fail2ban
     # todo: check for other mailinabox things
     m sudo -u www-data touch $rclogdir/errors.log
 
@@ -2162,7 +2278,7 @@ if (( ${#files[@]} )); then
 fi
 
 
-# ** auth
+# ** exim: auth
 
 case $HOSTNAME in
   bk|je)
@@ -2198,7 +2314,7 @@ server_advertise_condition = ${if eq{$tls_in_cipher}{}{}{*}}
 EOF
 fi
 
-# ** main daemon use non-default config file
+# ** exim: main daemon use non-default config file
 case $HOSTNAME in
   bk|$MAIL_HOST)
     # to see the default comments in /etc/default/exim4:
@@ -2209,8 +2325,11 @@ QUEUERUNNER='combined'
 QUEUEINTERVAL='30m'
 COMMONOPTIONS='-C /etc/exim4/my.conf'
 UPEX4OPTS='-o /etc/exim4/my.conf'
-#E4BCD_PANICLOG_NOISE='malware acl condition: clamd /var/run/clamav/clamd\.ctl : unable to connect to UNIX socket'
+#E4BCD_PANICLOG_NOISE='exim user lost privilege for using -C option'
 EOF
+    chown Debian-exim:Debian-exim /usr/sbin/exim4
+    # needs guid set in order to become Debian-exim
+    chmod g+s,u+s /usr/sbin/exim4
     i /etc/exim4/trusted_configs <<'EOF'
 /etc/exim4/my.conf
 EOF
@@ -2224,7 +2343,32 @@ EOF
     ;;
 esac
 
+# ** exim non-root
+
 case $HOSTNAME in
+  bk|je|li)
+    # no reason to expect it to ever be there.
+    rm -fv /etc/systemd/system/exim4.service.d/nonroot.conf
+    ;;
+  *)
+    i /etc/systemd/system/exim4.service.d/nonroot.conf <<'EOF'
+[Service]
+# see 56.2 Root privilege in exim spec
+AmbientCapabilities=CAP_NET_BIND_SERVICE
+EOF
+    i /etc/exim4/conf.d/main/000_local-noroot <<'EOF'
+# see 56.2 Root privilege in exim spec
+deliver_drop_privilege = true
+EOF
+    # Note: there are other routers that would also fail due to not running as root,
+    # but afaik, the main router will catch all mail. If not, we will see
+    # something in the queue.
+    rm -fv /etc/exim4/conf.d/router/600_exim4-config_userforward
+    ;;
+esac
+
+case $HOSTNAME in
+
   # ** $MAIL_HOST|bk|je)
   $MAIL_HOST|bk|je)
 
@@ -2423,10 +2567,10 @@ EOF
 
     # this avoids some error. i cant remember what. todo:
     # test it out and document why/if its needed.
-#     i /etc/exim4/host_local_deny_exceptions <<'EOF'
-# mail.fsf.org
-# *.posteo.de
-# EOF
+    #     i /etc/exim4/host_local_deny_exceptions <<'EOF'
+    # mail.fsf.org
+    # *.posteo.de
+    # EOF
 
     # cron email from smarthost hosts will automatically be to
     # USER@FQDN. I redirect that to alerts@, on the smarthosts, but in
@@ -2556,7 +2700,7 @@ StartLimitIntervalSec=0
 [Service]
 Restart=always
 # time to sleep before restarting a service
-RestartSec=1
+RestartSec=20
 EOF
 
     i /etc/default/exim4in <<'EOF'
@@ -2658,14 +2802,22 @@ backup_local:
 EOF
 
       # Bind to wghole to receive mailbackup.
-      # todo: will wghole fail to start without internet connectivity?
-      # if so, we need to set it automatically restart infinitely,
-      # and same with exim.
       wgholeip=$(sed -rn 's/^ *Address *= *([^/]+).*/\1/p' /etc/wireguard/wghole.conf)
       cat >>/etc/exim4/update-exim4.conf.conf <<EOF
 dc_other_hostnames='eximbackup.b8.nz'
 dc_local_interfaces='127.0.0.1;::1;$wgholeip'
 EOF
+
+      # wghole & thus exim will fail to start without internet connectivity.
+      i /etc/systemd/system/exim4.service.d/backup.conf <<'EOF'
+[Unit]
+StartLimitIntervalSec=0
+
+[Service]
+Restart=always
+RestartSec=20
+EOF
+
     else
       cat >>/etc/exim4/update-exim4.conf.conf <<EOF
 # Note: If theres like a temporary problem where mail gets sent to
@@ -2673,6 +2825,7 @@ EOF
 # instead of a permanent 5xx.
 dc_local_interfaces='127.0.0.1;::1'
 EOF
+      rm -fv /etc/systemd/system/exim4.service.d/backup.conf
     fi
     cat >>/etc/exim4/update-exim4.conf.conf <<EOF
 dc_eximconfig_configtype='smarthost'
@@ -2700,9 +2853,9 @@ case $HOSTNAME in
     m rsync -ra --delete /etc/exim4/ /etc/myexim4
     # If we ever wanted to have a separate spool,
     # we could do it like this.
-#     cat >>/etc/exim4/conf.d/main/000_local-nn <<'EOF'
-# spool_directory = /var/spool/myexim4
-# EOF
+    #     cat >>/etc/exim4/conf.d/main/000_local-nn <<'EOF'
+    # spool_directory = /var/spool/myexim4
+    # EOF
     cat >>/etc/myexim4/update-exim4.conf.conf <<'EOF'
 dc_eximconfig_configtype='smarthost'
 dc_smarthost='nn.b8.nz'
@@ -2874,7 +3027,7 @@ sre exim4
 case $HOSTNAME in
   $MAIL_HOST)
     m systemctl --now enable mailbindwatchdog
-  ;;
+    ;;
   *)
     soff mailbindwatchdog
     ;;