minor fix
[distro-setup] / mailtest-check
1 #!/bin/bash
2
3 # Usage: mail-test-check [slow] [anything]
4 #
5 # slow: do slow checks, like spamassassin
6 #
7 # anything: consider non-interactive, dont print unless something went
8 # wrong
9
10
11 source /b/errhandle/err
12
13 [[ $EUID == 0 ]] || exec sudo -E "${BASH_SOURCE[0]}" "$@"
14
15 shopt -s nullglob
16
17 e() { $int || return 0; printf "mailtest-check: %s\n" "$*"; }
18
19
20 ## Minutes before we give error.
21 # We run this cronjob along with sending the test email every 5 minutes,
22 # so give it 1 minute to arrive, then if the latest email is older than
23 # 7 minutes, the last 2 haven't arrived in a reasonable amount of time.
24 # However, when machines reboot things can get delayed, so add 10 mins,
25 # not sure if that is a good number or not.
26 min_limit=17
27
28
29 # spamassassin checking takes about 8 seconds. only do that every
30 # once in a while.
31 slow=false
32 if [[ $1 == slow ]]; then
33 slow=true
34 shift
35 fi
36
37 int=false
38 if [[ $SUDO_USER || $SSH_CONNECTION ]]; then
39 int=true
40 fi
41
42 if [[ $1 == int ]]; then
43 int=true
44 fi
45
46 if [[ $1 == nonint ]]; then
47 int=false
48 fi
49
50
51 if ! $int; then
52 sleep 60
53 fi
54
55 # avoid errors like this:
56 # Nov 8 08:16:05.439 [6080] warn: plugin: failed to parse plugin (from @INC): Can't locate Mail/SpamAssassin/Plugin/WLBLEval.pm: lib/Mail/SpamAssassin/Plugin/WLBLEval.pm: Permission denied at (eval 59) line 1.
57 #Nov 8 08:16:05.439 [6080] warn: plugin: failed to parse plugin (from @INC): Can't locate Mail/SpamAssassin/Plugin/VBounce.pm: lib/Mail/SpamAssassin/Plugin/VBounce.pm: Permission denied at (eval 60) line 1.
58 # i dont know why, i just found the solution online
59 cd /m/md
60 # TODO, get je to deliver the local mailbox: /m/md/INBOX
61 # dovecot appears to setup, i can t be sure.
62
63 case $HOSTNAME in
64 bk)
65 folders=(/m/md/{expertpathologyreview.com,amnimal.ninja}/testignore)
66 froms=(ian@iankelling.org z@zroe.org testignore@je.b8.nz iank@gnu.org)
67 ;;
68 je)
69 froms=(ian@iankelling.org z@zroe.org testignore@expertpathologyreview.com testignore@amnimal.ninja)
70 folders=(/m/md/je.b8.nz/testignore)
71 ;;
72 *)
73 folders=(/m/md/l/testignore)
74 froms=(testignore@je.b8.nz testignore@expertpathologyreview.com testignore@amnimal.ninja ian@iankelling.org z@zroe.org iank@gnu.org)
75 if ! $int; then
76 timeout 120 rsync --chown iank:iank -e "ssh -oIdentitiesOnly=yes -F /dev/null -i /root/.ssh/jtuttle" -t --inplace -r 'jtuttle@fencepost.gnu.org:/home/j/jtuttle/Maildir/new/' /m/md/l/testignore/new
77 fi
78 ;;
79 esac
80
81 getspamdpid() {
82 if [[ ! $spamdpid || ! -d /proc/$spamdpid ]]; then
83 # try twice in case we are restarting, it happens.
84 for i in 1 2; do
85 spamdpid=$(systemctl show --property MainPID --value spamassassin | sed 's/^[10]$//' ||:)
86 if [[ $spamdpid ]]; then
87 break
88 fi
89 sleep 30
90 done
91 fi
92 }
93 getspamdpid
94 pr() {
95 if [[ -e /var/lib/prometheus/node-exporter ]]; then
96 cat >>/var/lib/prometheus/node-exporter/mailtest-check.prom.$$
97 fi
98 }
99 # first time we write, overwrite anything existing
100 if [[ -e /var/lib/prometheus/node-exporter ]]; then
101 cat >/var/lib/prometheus/node-exporter/mailtest-check.prom.$$ <<EOF
102 mailtest_check_found_spamd_pid_bool $(( ${spamdpid:-0} > 0 ))
103 EOF
104 fi
105 e spamdpid: $spamdpid
106 if [[ ! $spamdpid ]]; then
107 echo $HOSTNAME mailtest spamd pid not found. systemctl status spamassassin:
108 systemctl status spamassassin
109 fi
110 tmpfile=$(mktemp)
111 declare -i unexpected=0
112 for folder in ${folders[@]}; do
113 for from in ${froms[@]}; do
114 latest=
115 last_sec=0
116
117 if ! grep -rlFx "From: $from" $folder/{new,cur} >$tmpfile; then
118 e "no message found from: $from"
119 continue
120 fi
121 # webmail sends them to cur it seems
122 while read -r file; do
123 if [[ $file -nt $latest ]]; then
124 latest=$file
125 fi
126 done <$tmpfile
127
128 if [[ ! $latest ]]; then
129 # 10 is an arbitrary bad value
130 unexpected+=10
131 else
132 to=$(awk '/^Envelope-to: / {print $2}' $latest)
133 last_sec=$(awk '/^Subject: / {print $4}' $latest)
134
135 if $slow; then
136 if ! $int; then
137 find $folder/new $folder/cur -type f -mmin +1080 -delete
138 fi
139 getspamdpid
140 if [[ $spamdpid ]]; then
141 if [[ $(readlink /proc/$$/ns/net) != "$(readlink /proc/$spamdpid/ns/net)" ]]; then
142 spamcpre="nsenter -t $spamdpid -n -m"
143 fi
144
145 declare -A results
146 # pyzor fails for our test message, so dont put useless load on their
147 # servers.
148 # example line that sed is parsing:
149 # (-0.1 / 5.0 requ) DKIM_SIGNED=0.1,DKIM_VALID=-0.1,DKIM_VALID_AU=-0.1,SPF_HELO_PASS=-0.001,SPF_PASS=-0.001,TVD_SPACE_RATIO=0.001 autolearn=_AUTOLEARN
150 raw_results="$($spamcpre sudo -u Debian-exim spamassassin -t --cf='score PYZOR_CHECK 0' <"$latest" | tail -n2 | head -n1 | sed -r 's/^\([^)]*\) *//;s/=[^, ]*([, ]|$)/ /g')"
151 for r in $raw_results; do
152 case $r in
153 # got this in an update 2022-01. dun care
154 T_SCC_BODY_TEXT_LINE|SCC_BODY_SINGLE_WORD) : ;;
155 # we have a new domain, ignore this.
156 # it seems like some versions of spamassassin do BODY_SINGLE_WORD, others dont, we dun care.
157 # bayes_00 is a new one indicating ham, we dont care if its missing.
158 BAYES_00|BODY_SINGLE_WORD|FROM_FMBLA_NEWDOM*|autolearn) : ;;
159 SPF_HELO_NEUTRAL)
160 # some of my domains use neutral spf, treat them the same.
161 results[SPF_HELO_PASS]=t
162 ;;
163 *)
164 results[$r]=t
165 ;;
166 esac
167 done
168 # debugging
169 # e results = ${!results[@]}
170 missing=()
171
172 keys=(DKIM_SIGNED DKIM_VALID{,_AU,_EF} SPF_HELO_PASS SPF_PASS TVD_SPACE_RATIO)
173 if [[ $to == *@gnu.org && $from == *@gnu.org ]]; then
174 keys=(ALL_TRUSTED TVD_SPACE_RATIO)
175 elif [[ $to == *@gnu.org ]]; then
176 # eggs has RCVD_IN_DNSWL_MED
177 keys+=(RCVD_IN_DNSWL_MED)
178 elif [[ $from == *@gnu.org ]]; then
179 # eggs has these
180 keys+=(RCVD_IN_DNSWL_MED DKIMWL_WL_HIGH)
181 fi
182
183 for t in ${keys[@]}; do
184 if [[ ${results[$t]} ]]; then
185 unset "results[$t]"
186 elif [[ $t == DKIM_VALID_EF && $from == *@[^.]*.[^.]*.[^.]* ]]; then
187 :
188 # third level domains dont hit this. its because
189 # /usr/share/perl5/Mail/SpamAssassin/Plugin/DKIM.pm checks
190 # if its signed with the registryboundaries domain. afaik:
191 # we need the actual domain to sign it, this would result in
192 # a second signature. I only use second level domains for
193 # testing atm, fsf doesnt use them for anything but the
194 # forum and I dont expect that to have any deliverability
195 # problems. So, not bothering atm.
196 else
197 missing+=($t)
198 fi
199 done
200 if (( ${#results[@]} || ${#missing[@]} )); then
201 printf "$HOSTNAME spamtest %s/%s\n" "$latest"
202 if (( ${#results[@]} )); then
203 printf "unexpected %s" "${!results[*]} "
204 fi
205 if (( ${#missing[@]} )); then
206 printf "missing %s" "${missing[*]}"
207 fi
208 echo
209 echo mailtest-check: cat $latest:
210 cat $latest
211 echo mailtest-check: end of cat
212 printf "$(tput setaf 5 2>/dev/null ||:)█$(tput sgr0 2>/dev/null||:)%.0s" $(eval echo "{1..${COLUMNS:-60}}")
213 fi
214 fi # if spamdpid
215 fi # if $slow
216 fi # if [[ $latest ]]
217
218 now=$EPOCHSECONDS
219 limit=$(( now - 60 * min_limit ))
220 age_sec=$(( now - last_sec ))
221 e $((age_sec / 60)):$(( age_sec % 60 )) ago. to:$to from:$from $latest
222
223 if (( last_sec <= limit )); then
224 echo $HOSTNAME mailtest $folder $from $(date -d @$last_sec +'%a %m-%d %H:%M')
225 fi
226 # usec = unix seconds
227 pr <<EOF
228 mailtest_check_last_usec{folder="$folder",from="$from"} $last_sec
229 EOF
230 done
231 done
232 if $slow; then
233 pr <<EOF
234 mailtest_check_unexpected_spamd_results $unexpected
235 EOF
236 fi
237
238 dir=/var/lib/prometheus/node-exporter
239 if [[ -e $dir ]]; then
240 mv $dir/mailtest-check.prom.$$ $dir/mailtest-check.prom
241 fi