X-Git-Url: https://iankelling.org/git/?a=blobdiff_plain;f=mail-setup;h=3ac3476c337be913ba03f3cee8a73bcafd51cef6;hb=HEAD;hp=3c1ad8160784494b96b3ab4f8582370864e584dd;hpb=8d33c68549c02c45ed78a05f7de703a08ec245c6;p=distro-setup diff --git a/mail-setup b/mail-setup index 3c1ad81..d19fba0 100755 --- a/mail-setup +++ b/mail-setup @@ -1,7 +1,23 @@ #!/bin/bash # * intro -# Copyright (C) 2019 Ian Kelling -# SPDX-License-Identifier: AGPL-3.0-or-later + +# Program to install and configure Ian's email related programs +# Copyright (C) 2024 Ian Kelling + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +# SPDX-License-Identifier: GPL-3.0-or-later # todo: # on bk (and fsf servers that run multiple exim4 daemons, eg eximfsf2 and eximfsf3), @@ -91,7 +107,7 @@ # todo: run mailping test after running, or otherwise # clear out terminal alert -# todo: disable postgrey +# todo: disable postgrey. (why did we have it?) # todo: in testforward-check, we should also look @@ -183,10 +199,10 @@ if [ -z "$BASH_VERSION" ]; then echo "error: shell is not bash" >&2; exit 1; fi shopt -s nullglob -if [[ -s /usr/local/lib/err ]]; then - source /usr/local/lib/err -elif [[ -s /a/bin/errhandle/err ]]; then - source /a/bin/errhandle/err +if [[ -s /usr/local/lib/bash-bear ]]; then + source /usr/local/lib/bash-bear +elif [[ -s /a/bin/bash-bear-trap/bash-bear ]]; then + source /a/bin/bash-bear-trap/bash-bear else echo "no err tracing script found" exit 1 @@ -345,7 +361,10 @@ reload=false if [[ -e /var/local/mail-setup-reload ]]; then reload=true fi -u() { # update file. note: duplicated in brc +# update file. +# if the file changed, ur=true, else false. +# note: duplicated in brc +u() { local tmp tmpdir dest="$1" local base="${dest##*/}" local dir="${dest%/*}" @@ -399,7 +418,6 @@ sre() { mailhost() { [[ $HOSTNAME == "$MAIL_HOST" ]] } -e() { printf "%s\n" "$*"; } reifactive() { for service; do if systemctl is-active $service >/dev/null; then @@ -438,7 +456,7 @@ fi bhost_t=false case $HOSTNAME in $MAIL_HOST) : ;; - kd|frodo|x2|x3|kw|sy|bo) + kd|x2|x3|kw|sy|bo|so) bhost_t=true ;; esac @@ -458,13 +476,30 @@ Pin-Priority: 500 EOF fi +# name change in t12, and now timer instead of cron option in /etc/default +first_spamd_run=false +if ! systemctl cat spamassassin-maintenance.timer &>/dev/null; then + first_spamd_run=true +fi + + # light version of exim does not have sasl auth support. # note: for bitfolk hosts, unbound has important config with conflink. pi-nostart exim4 exim4-daemon-heavy spamassassin unbound clamav-daemon wireguard +spamd_ser=spamd +if systemctl cat spamassassin &>/dev/null; then + spamd_ser=spamassassin +elif $first_spamd_run; then + systemctl start spamassassin-maintenance +fi + +systemctl enable --now spamassassin-maintenance.timer + # 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 fail2ban +pi spf-tools-perl p0f pyzor razor jq moreutils certbot fail2ban +pu postgrey case $HOSTNAME in je) : ;; # not included due to using wireguard: openvpn @@ -493,6 +528,77 @@ fi # our nostart pi fails to avoid enabling + +# * initial dns config & daemon setup +# +# use systemd-resolved for glibc resolutions, setup symlinks + +pi libnss-resolve + +# if this link gets replaced with a normal file we will get exim log +# errors on MAIL_HOST like so: +# +# R=fsfsmarthost defer (-36) DT=0s: host lookup for mail.fsf.org did not complete (DNS timeout?) + +if [[ ! -L /etc/nsswitch.conf ]]; then + sudo mkdir -p /etc/resolved-nsswitch + sudo mv /etc/nsswitch.conf /etc/resolved-nsswitch + sudo ln -sf /etc/resolved-nsswitch/nsswitch.conf /etc +fi + +f=/etc/basic-nsswitch/nsswitch.conf +if [[ ! -e $f ]]; then + sudo mkdir -p ${f%/*} + sudo cp /etc/nsswitch.conf $f + sudo sed -i --follow-symlinks 's/^ *hosts:.*/hosts: files dns myhostname/' $f +fi +case $HOSTNAME in + bk|je) + # je should be able to get along systemd-resolved, but ive had some odd + # very intermittent dns failures with spamassassin, it seems it might only + # be happening with systemd-resolved, so just use unbound + # to make it consistent with the other hosts. + sudo sed -i --follow-symlinks 's/^ *hosts:.*/hosts: files dns myhostname/' /etc/nsswitch.conf + soff systemd-resolved + sudo ln -sf 127.0.0.1-resolv/stub-resolv.conf /etc/resolv.conf + sgo unbound + # cautious measure to make sure resolution is working + sleep 1 + ;; + *) + # default is + # files mdns4_minimal [NOTFOUND=return] dns myhostname + # mdns4 is needed for my printer and for bbb webrtc, not sure exactly why. + # https://www.freedesktop.org/software/systemd/man/nss-resolve.html# + # seems more important than some potential use case. + # Interestingly, t9/t10 man page says use files before resolve, debian 10 says the opposite. + # removing files makes hostname -f not actually give the fully qualified domain name. + sudo sed -i --follow-symlinks 's/^ *hosts:.*/hosts: files resolve [!UNAVAIL=return] mdns4_minimal [NOTFOUND=return] myhostname/' /etc/resolved-nsswitch/nsswitch.conf + ;; +esac + +case $HOSTNAME in + bk) + sgo named + ;; +esac + + +u /etc/apparmor.d/abstractions/nameservice.d/iank <<'EOF' +/etc/resolved-nsswitch/nsswitch.conf r, +/etc/basic-nsswitch/nsswitch.conf r, +# Aug 06 23:09:11 kd audit[3995]: AVC apparmor="DENIED" operation="connect" profile="/usr/bin/freshclam" name="/run/systemd/resolve/io.systemd.Resolve" pid=3995 comm="freshclam" requested_mask="wr" denied_mask="wr" fsuid=109 ouid=101 +# I dont know if this is quite the right fix, but I saw other sockets +# in the nameservice files that were rw, so figured it was ok to add this and it worked. +/run/systemd/resolve/io.systemd.Resolve rw, +EOF + +if $ur && systemctl is-active apparmor; then + m systemctl reload apparmor +fi + + + # * Mail clean cronjob u /etc/systemd/system/mailclean.timer <<'EOF' @@ -759,25 +865,26 @@ EOF # this is just a bug fix for trisquel. f=/etc/apparmor.d/usr.sbin.unbound -line="/usr/sbin/unbound flags=(attach_disconnected) {" -if ! grep -qFx "$line" $f; then - badline="/usr/sbin/unbound {" - if ! grep -qFx "$badline" $f; then +good_string="/usr/sbin/unbound flags=(attach_disconnected) {" +if ! grep -qF "$good_string" $f; then + bad_string="/usr/sbin/unbound {" + if ! grep -qF "$bad_string" $f; then err expected line in $f not found fi - sed -i "s,^$badline$,$line," $f + sed -i "s,$bad_string$,$good_string," $f if systemctl is-active apparmor &>/dev/null; then m systemctl reload apparmor fi fi + # note: anything added to nn_progs needs corresponding rm # down below in the host switch nn_progs=(exim4) if mailhost; then # Note dovecots lmtp doesnt need to be in the same nn to accept delivery. # Its in the nn so remote clients can connect to it. - nn_progs+=(spamassassin dovecot) + nn_progs+=($spamd_ser dovecot) fi case $HOSTNAME in @@ -857,7 +964,7 @@ EOF done ;; *) - for unit in exim4 spamassassin dovecot unbound; do + for unit in exim4 $spamd_ser dovecot unbound; do f=/etc/systemd/system/$unit.service.d/nn.conf if [[ -s $f ]]; then rm -fv $f @@ -909,12 +1016,14 @@ EOF # 2020-10-19 remove old file. remove this when all hosts updated rm -fv /etc/systemd/system/spamddnsfix.{timer,service} -u /etc/default/spamassassin <<'EOF' +u /etc/default/$spamd_ser <<'EOF' # defaults plus debugging flags for an issue im having OPTIONS="--create-prefs --max-children 5 --helper-home-dir" -PIDFILE="/var/run/spamd.pid" +PIDFILE="/run/spamd.pid" # my additions NICE="--nicelevel 15" +# not used in t12+, that uses +# /usr/lib/systemd/system/spamassassin-maintenance.timer CRON=1 EOF @@ -1127,6 +1236,10 @@ banaction = iptables-exim ignoreip = 209.51.188.13 2001:470:142::13 209.51.188.92 2001:470:142:3::10 72.14.176.105 2600:3c00:e000:280::2 10.173.8.1 EOF if $ur; then + # Ensure the log file monitored by fail2ban exists, or else fail2ban can't start. + if [[ ! -e /var/log/exim4/mainlog ]]; then + install -m 640 -o Debian-exim -g adm /dev/null /var/log/exim4/mainlog + fi m systemctl restart fail2ban fi @@ -1864,8 +1977,8 @@ if mailhost; then # plus debug does not help. # sudo -u radicale radicale -D - # created password file with: - # htpasswd -c /p/c/machine_specific/li/filesystem/etc/caldav-htpasswd + # created radicale password file with: + # htpasswd -c /p/c/machine_specific/li/filesystem/etc/caldav-htpasswd ian # chmod 640 /p/c/machine_specific/li/filesystem/etc/caldav-htpasswd # # setup chgrp www-data in ./conflink @@ -2037,7 +2150,7 @@ EOF ssl = required # this is the same as the certbot list, i check changes in /a/bin/ds/filesystem/usr/local/bin/check-lets-encrypt-ssl-settings ssl_cipher_list = ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384 -ssl_protocols = TLSv1.2 +ssl_min_protocol = TLSv1.2 ssl_prefer_server_ciphers = no protocol lmtp { @@ -2045,6 +2158,23 @@ protocol lmtp { # default is just $mail_plugins mail_plugins = $mail_plugins sieve } + +# /etc/dovecot/conf.d/10-master.conf says the default is 256M. +# but I started getting oom errors in the syslog +# Mar 27 15:10:04 sy dovecot[330088]: lmtp(iank)<3839880>: Fatal: master: service(lmtp): child 3839880 returned error 83 (Out of memory (service lmtp { vsz_limit=256 MB }, you may need to increase it) - set CORE_OUTOFMEM=1 environment to get core dump) +# exim would just queue mail until it eventually succeeded. +# Deciding what to increase it to, I found this +# https://dovecot.org/list/dovecot/2011-December/080056.html +# which suggests 3x the largest dovecot.index.cache file +# and then I found that +# md/l/testignore/dovecot.index.cache is 429M, my largest cache file, +# but that folder only has 2k messages. +# next biggest is md/l/qemu-devel/dovecot.index.cache 236M +# which lead to me a search https://doc.dovecot.org/admin_manual/known_issues/large_cache/ +# which suggests 1.5x the maximum cache file size 1G, and +# that I can safely rm the index. +default_vsz_limit = 1500M + EOF if dpkg --compare-versions "$(dpkg-query -f='${Version}\n' --show dovecot-core)" ge 1:2.3; then cat <>/etc/dovecot/local.conf < Options Indexes SymLinksIfOwnerMatch MultiViews @@ -2928,10 +3074,11 @@ case $HOSTNAME in # which will overwrite any existing file u /etc/default/exim4 <<'EOF' QUEUERUNNER='combined' -# note: this is duplicated in brc2, 10m here is -q10m there. QUEUEINTERVAL='10m' COMMONOPTIONS='-C /etc/exim4/my.conf' UPEX4OPTS='-o /etc/exim4/my.conf' +# in t12 exim, this replaces all the above options +EXIMSERVICE='-bdf -q10m -C /etc/exim4/my.conf' # i use epanic-clean for alerting if there are bad paniclog entries E4BCD_WATCH_PANICLOG='no' EOF @@ -3128,6 +3275,11 @@ bounce_debbugs: domains = DEBBUGS_DOMAIN EOF + install -m=0775 -d -g Debian-exim -o iank /var/spool/exim4/gw + f=/var/spool/exim4/gw/.no-delay-eximids + if [[ ! -e $f ]]; then + install -g Debian-exim -o iank /dev/null $f + fi u /etc/exim4/conf.d/router/155_delay <<'EOF' # By default, delay sending email by 30-40 minutes in case I # change my mind. @@ -3147,14 +3299,14 @@ delay_iank: condition = ${if and { \ {< {$tod_epoch} {${eval10:$received_time + 60*30}}} \ {!def:h_i:} \ -{!bool{${lookup{$message_exim_id}lsearch{/etc/exim4/no-delay-eximids}{true}}}} \ -{!bool{${lookup{all}lsearch{/etc/exim4/no-delay-eximids}{true}}}} \ +{!bool{${lookup{$message_exim_id}lsearch{/var/spool/exim4/gw/.no-delay-eximids}{true}}}} \ +{!bool{${lookup{all}lsearch{/var/spool/exim4/gw/.no-delay-eximids}{true}}}} \ } {true}{false}} headers_remove = <; i: domains = ! +local_domains # uncomment for testing delays to jtuttle # local_parts = ! root : ! testignore : ! alerts : ! ian-pager : ! daylert - local_parts = ! root : ! testignore : ! alerts : ! jtuttle : ! ian-pager : ! daylert + local_parts = ! root : ! testignore : ! alerts : ! jtuttle : ! ian-pager : ! daylert : ! r2e ignore_target_hosts = ROUTER_DNSLOOKUP_IGNORE_TARGET_HOSTS EOF @@ -3227,16 +3379,27 @@ MAILDIR_HOME_MAILDIR_LOCATION = /m/md/Sent EOF + # ian: save a copy of sent mail. i thought of other ways to do this, + # for example, to only save sent mail that is not sent from my mail + # client which saves a copy by default, but in the end, it seems + # simplest to turn that off. We want to save external mail sent by + # smarthosts. However, there is one complication: encrypted + # mail. Saving it here just gets us an encrypted copy that we can't + # read. Soo, we could bcc ourselves: then we still have the + # annoyance that it is encrypted so we can't grep it. Or, we could + # hack emacs so that it sends us an unencrypted copy. Turns out that + # the emacs function which saves sent email can also send us a + # copy. But, then we have 3 copies: the encrypted copy exim saves, + # the unencrypted copy exim saves, and the copy emacs saves. Soo, + # we can emacs send a copy directly to the sent alias but only when + # it is not mail_host, and have the exim condition for redirecting a + # copy to the sent alias avoid doing it if it has an emacs user + # agent header. u /etc/exim4/conf.d/router/186_sentarchive_nn <<'EOF' -# ian: save a copy of sent mail. i thought of other ways to -# do this, for example, to only save sent mail that is not sent -# from my mail client which saves a copy by default, but in the -# end, it seems simplest to turn that off. We want to save -# external mail sent by smarthosts. sentarchive_nn: driver = redirect domains = ! +local_domains - condition = ${if !bool{${lookup{$local_part@$domain}lsearch{/etc/exim4/ignore-sent}{true}}}} + condition = ${if and {{!bool{${lookup{$local_part@$domain}lsearch{/etc/exim4/ignore-sent}{true}}}} {!match {$h_user-agent:}{emacs}}}} data = vojdedIdNejyebni@b8.nz unseen EOF @@ -3284,6 +3447,12 @@ EOF # This name won\'t appear on From: lines of outgoing messages if rewriting is enabled. echo iankelling.org > /etc/mailname + # mail default domain. + u /etc/mailutils.conf <<'EOF' +address { + email-domain iankelling.org; +}; +EOF # mail.iankelling.org so local imap clients can connect with tls and # when they happen to not be local. @@ -3498,11 +3667,13 @@ backup_local: EOF # Bind to wghole to receive mailbackup. - wgholeip=$(sed -rn 's/^ *Address *= *([^/]+).*/\1/p' /etc/wireguard/wghole.conf) - cat >>/etc/exim4/update-exim4.conf.conf <>/etc/exim4/update-exim4.conf.conf </dev/null; then - m systemctl --now enable clamav-daemon - out=$(rsync -aiSAX --chown=root:root --chmod=g-s /a/bin/ds/filesystem/etc/systemd/system/epanicclean.service /etc/systemd/system) - if [[ $out ]]; then - reload=true - fi - - # note, this will cause paniclog entries because it takes like 45 - # seconds for clamav to start, i use ./epanic-clean to remove - # them. - fi - ;;& - $MAIL_HOST|bk|je) - # start spamassassin/dovecot before exim. - sre dovecot spamassassin - # Wait a bit before restarting exim, else I get a paniclog entry - # like: spam acl condition: all spamd servers failed. But I'm tired - # of waiting. I'll deal with this some other way. - # - # sleep 3 - m systemctl --now enable mailclean.timer - ;;& - $MAIL_HOST) - # < 2.1 (eg: in t9), uses a different data format which required manual - # migration. dont start if we are running an old version. - if dpkg --compare-versions "$(dpkg -s radicale | awk '$1 == "Version:" { print $2 }')" ge 2.1; then - m systemctl --now enable radicale - fi - ;;& -esac - -# for debugging dns issues -case $HOSTNAME in - je|bk) - systemctl enable --now logrotate-fast.timer - ;; -esac - -# last use of $reload happens in previous block -rm -f /var/local/mail-setup-reload - - -case $HOSTNAME in - $MAIL_HOST|bk|je|li) - # on li, these are never started, except $vpnser - : - ;; - *) - soff radicale mailclean.timer dovecot spamassassin $vpnser mailnn clamav-daemon - ;; -esac - -sre exim4 - -case $HOSTNAME in - $MAIL_HOST) - m systemctl --now enable mailbindwatchdog - ;; - *) - soff mailbindwatchdog - ;; -esac - - -case $HOSTNAME in - bk) sre exim4in ;; -esac - # * mail monitoring / testing # note, to test clamav, send an email with body that only contains @@ -3870,6 +3913,8 @@ After=local-fs.target StartLimitIntervalSec=0 [Service] +# avoid fans spinning up +CPUQuota=22% Type=simple ExecStart=/usr/local/bin/mailtest-check slow Restart=always @@ -3879,7 +3924,6 @@ RestartSec=60 WantedBy=graphical.target EOF sysd-prom-fail-install mailtest-check - sre mailtest-check ;;& $MAIL_HOST) test_froms=(ian@iankelling.org z@zroe.org iank@gnu.org) @@ -3964,6 +4008,135 @@ EOFOUTER esac +# * start / stop services + +reifactive dnsmasq nscd + +if $reload; then + m systemctl daemon-reload +fi + +# optimization, this only needs to run once. +if [[ ! -e /sys/class/net/wghole ]]; then + # checking bhost_t is redundant, but could help us catch errors. + if $bhost_t || [[ -e /etc/wireguard/wghole.conf ]]; then + # todo: in mail-setup, we have a static list of backup hosts, not *y + m systemctl --now enable wg-quick@wghole + fi +fi + +# optimization, this only needs to be run once +if [[ ! -e /var/lib/prometheus/node-exporter/exim_paniclog.prom ]]; then + sysd-prom-fail-install epanicclean + m systemctl --now enable epanicclean +fi + +case $HOSTNAME in + je) + /a/exe/web-conf apache2 je.b8.nz + ;; + bk) + /a/exe/web-conf apache2 mail2.iankelling.org + ;; +esac + +# optimization, this only needs to run once. But, if we move to a +# computer we haven't used much, we need to fetch a fresh cert. +# Existence check is just to avoid ugly error message from openssl. +if [[ ! -e /etc/exim4/fullchain.pem ]] || ! openssl x509 -checkend $(( 60 * 60 * 24 * 3 )) -noout -in /etc/exim4/fullchain.pem; then + m /a/bin/ds/mail-cert-cron -1 -i + m systemctl --now enable mailcert.timer +fi + +case $HOSTNAME in + $MAIL_HOST|bk) + m systemctl --now enable mailnn mailnnroute + ;;& + $MAIL_HOST) + # we use dns to start wg + if $reload; then + sre unbound + else + m systemctl --now enable unbound + fi + ;;& + $MAIL_HOST|bk) + # If these have changes, id rather manually restart it, id rather + # not restart and cause temporary errors + if $reload; then + sre $vpnser + else + m systemctl --now enable $vpnser + fi + ;;& + bk) + if ! systemctl is-active clamav-daemon >/dev/null; then + m systemctl --now enable clamav-daemon + out=$(rsync -aiSAX --chown=root:root --chmod=g-s /a/bin/ds/filesystem/etc/systemd/system/epanicclean.service /etc/systemd/system) + if [[ $out ]]; then + reload=true + fi + + # note, this will cause paniclog entries because it takes like 45 + # seconds for clamav to start, i use ./epanic-clean to remove + # them. + fi + ;;& + $MAIL_HOST|bk|je) + # start spamassassin/dovecot before exim. + sre dovecot $spamd_ser mailtest-check + # Wait a bit before restarting exim, else I get a paniclog entry + # like: spam acl condition: all spamd servers failed. But I'm tired + # of waiting. I'll deal with this some other way. + # + # sleep 3 + m systemctl --now enable mailclean.timer + ;;& + $MAIL_HOST) + # < 2.1 (eg: in t9), uses a different data format which required manual + # migration. dont start if we are running an old version. + if dpkg --compare-versions "$(dpkg -s radicale | awk '$1 == "Version:" { print $2 }')" ge 2.1; then + m systemctl --now enable radicale + fi + ;;& +esac + +# for debugging dns issues +case $HOSTNAME in + je|bk) + systemctl enable --now logrotate-fast.timer + ;; +esac + +# last use of $reload happens in previous block +rm -f /var/local/mail-setup-reload + + +case $HOSTNAME in + $MAIL_HOST|bk|je|li) + # on li, these are never started, except $vpnser + : + ;; + *) + soff radicale mailclean.timer dovecot $spamd_ser $vpnser mailnn clamav-daemon + ;; +esac + +sre exim4 + +case $HOSTNAME in + $MAIL_HOST) + m systemctl --now enable mailbindwatchdog + ;; + *) + soff mailbindwatchdog + ;; +esac + + +case $HOSTNAME in + bk) sre exim4in ;; +esac # * misc m sudo -u $u mkdir -p /home/$u/.cache