chiark / gitweb /
79e8ab7f5f4b0d10f13795ec888bb0ec11e0b3c9
[modbot-mtm.git] / probes / modrelays-probe
1 #!/bin/bash
2
3 set -e$MODRELAYS_PROBE_SET_X
4
5 MODRELAYS=moderators.isc.org
6 PROBE_TIMEOUT=$(( 20 * 60 ))
7 PROBE_EXPIRE=$(( 32 * 86400 ))
8
9 case "$1" in
10 received)
11         mode="$1"
12         cd "$2"
13         shift; shift; set "$mode" "$@"
14         ;;
15 esac
16
17 . ../global-settings
18 . ./settings
19
20 id=$(date +%s)_$$
21 statedir=probes/probes
22 lockfile=$statedir/.lock
23
24 fail () {
25         printf >&2 "%s\n" "modrelays-probe: error: $1"
26         exit 16
27 }
28
29 compute-td () {
30         # implicitly uses GROUP, id, domain
31         # caller must "local td", which will be set
32         local probeid=$1
33
34         probeid="$domain,${probeid//[^-=.,_0-9A-Za-z]/%/},$id"
35         case $probeid in
36         .*|*/*) fail "yikes, sanitisation bug!" ;;
37         esac
38
39         td="$statedir/$probeid"
40 }
41
42 record-probing () {
43         compute-td "$@"
44         mkdir -p $td
45 }
46
47 record-probing-start () {
48         record-probing "$@"
49         if ! [ -e "$td/started" ]; then
50                 date -R >"$td/started"
51         fi
52 }
53
54 record-outcome () {
55         local probeid=$1
56         local outcome=$2
57         local message=$3
58         local td
59         record-probing "$probeid"
60         printf "%s\n" >"$td"/"$outcome" "$message"
61 }
62
63 record-success () { record-outcome "$1" ok ''; }
64 record-tempfail () { record-outcome "$1" tempfail "$2"; }
65 record-permfail () { record-outcome "$1" permfail "$2"; }
66
67 probe-addr () {
68         local mx=$1
69         local addr=$2
70
71         local td
72         record-probing-start "mx=$mx,addr=$addr"
73
74         set +e
75         swaks   --to "${GROUP//./-}@$domain" \
76                 --server $addr \
77                 --tls-optional-strict \
78                 --header 'Subject: test modrelays probe test' \
79                 --header \
80         "X-WebSTUMP-Relay-Probe: $GROUP $id $domain $mx $addr" \
81                 -n >$td/swaks.log 2>$td/swaks.err
82         rc=$?
83         set -e
84
85         case $rc in
86         0) return ;; # record-success done by receiver
87         esac
88         local permfail=''
89
90         local rhs
91         local prefix
92         local expect_no_5xx='initial connection'
93         exec 4<$td/swaks.log
94         while read <&4 prefix rhs; do
95                 case "$prefix" in
96                 '<'*)
97                         case "$rhs" in
98                         5*)
99                                 if [ "x$expect_no_5xx" != x ] && \
100                                    [ "x$permfail" = x ]; then
101                                         permfail="$rhs ($expect_no_5xx)"
102                                 fi
103                                 ;;
104                         esac
105                         ;;
106                 *'>')
107                         case "$rhs" in
108                         EHLO*|STARTTLS*) expect_no_5xx='' ;;
109                         *) expect_no_5xx="after $rhs" ;;
110                         esac
111                         ;;
112                 *)
113                 esac
114         done
115
116         if [ "x$permfail" = x ]; then
117                 record-tempfail "mx=$mx,addr=$addr" "see swaks.log / swaks.err"
118         else
119                 record-permfail "mx=$mx,addr=$addr" "$permfail"
120         fi
121 }
122
123 probe-domain () {
124         local domain=$1
125         local td
126         record-probing-start dns
127         
128         set +e
129         adnshost -Fi -Tn +Do +Dt -t mx $domain >$td/dns
130         rc=$?
131         set -e
132
133         case $rc in
134         0)
135                 # have a list of MX's
136                 exec 3<$td/dns
137                 local pref
138                 local mx
139                 local statustype
140                 local rhs
141                 while read <&3 pref mx statustype statustypenum rhs; do
142                         case $statustypenum in
143                         0)
144                                 # have a list of relays
145                                 case $rhs in
146                                 *" ( "*")") ;;
147                                 *)
148                                         record-permfail "mx=$mx" \
149                                                 "dns format $rhs"
150                                         continue
151                                         ;;
152                                 esac
153                                 rhs=${rhs##* (}
154                                 rhs=${rhs% )}
155                                 local addr
156                                 for addr in $rhs; do
157                                         case $addr in
158                                         INET|INET6) continue ;;
159                                         esac
160                                         probe-addr $mx $addr
161                                 done
162                                 ;;
163                         [123])
164                                 # temporary errors
165                                 record-tempfail "mx=$mx" \
166                                         "dns $rc $statustype $rhs"
167                                 ;;
168                         *)
169                                 # yikes
170                                 record-permfail "mx=$mx" \
171                                         "dns $rc $statustype $rhs"
172                                 ;;
173                         esac
174                 done
175                 record-success dns
176                 return
177                 ;;
178         6)
179                 # permfail, try A
180                 set +e
181                 adnshost -Fi -Tn +Do +Dt -t a $domain >$td/dns
182                 rc=$?
183                 set -e
184                 ;;
185         esac
186
187         case $rc in
188         0)
189                 # have a list of A's (dealt with MXs above)
190                 exec 3<$td/dns
191                 local addr
192                 while read <&3 addr; do
193                         probe-addr 'NONE' $addr
194                 done
195                 record-success dns
196                 return
197                 ;;
198         [123])
199                 local emsg
200                 read <$td/dns emsg
201                 record-tempfail dns "dns <no-mx> $emsg"
202                 ;;
203         *)
204                 local emsg
205                 read <$td/dns emsg
206                 record-permfail dns "dns <no-mx> $emsg"
207                 ;;
208         esac
209 }
210
211 no_args () {
212         case $1 in
213         0) return ;;
214         *) fail "no arguments to $mode allowed" ;;
215         esac
216 }
217
218 acquire_lock () {
219         local lock_mode="$1"
220         if [ x"$WEBSTUMP_PROBE_LOCK" = x"$lockfile" ]; then return; fi
221         WEBSTUMP_PROBE_LOCK=$lockfile \
222         exec with-lock-ex $lock_mode "$lockfile" "$0" "$mode" "$@"
223 }
224
225 maybe-report () {
226         local outcome=$1
227
228         if $found_to_report; then return; fi
229         if ! [ -e "$attempt/$outcome" ]; then return; fi
230         found_to_report=true
231
232         read <"$attempt/$outcome" message
233
234         local reported
235         if [ -e "$attempt/reported" ]; then
236                 read <"$attempt/reported" reported
237         fi
238         if [ "x$outcome" = "x$reported" ]; then return; fi
239
240         if [ x"$outcome" = x"ok" ] && [ x"$reported" = x ]; then
241                 echo ok >"$attempt/reported"
242                 return
243         fi
244
245         local info=${attempt##*/}
246         info=${info//,/ }
247
248         delim=`od -N 50 -An -x -w50 </dev/urandom`
249         delim=${delim// /}
250
251         local email="$attempt/.report.$outcome"
252         cat >"$email" <<END
253 To: $ADMIN
254 Subject: mod relay probe $outcome $info
255 Content-Type: multipart/mixed; boundary="$delim"
256 MIME-Version: 1.0
257
258 --$delim
259 Content-Type: text/plain; charset="utf-8"
260 Content-Transfer-Encoding: 7bit
261
262 The moderation relay probe
263   $info
264 END
265
266         if [ -e "$attempt/started" ]; then
267                 local started
268                 read started <"$attempt/started"
269                 cat >>"$email" <<END
270 started at
271   $started
272 END
273         fi
274
275         cat >>"$email" <<END
276 resulted in the outcome
277   $outcome
278 END
279         if [ "x$message" != x ]; then
280                 cat >>"$email" <<END
281 with the message
282   $message
283 END
284         fi
285
286         if [ "x$reported" != x ]; then
287                 cat >>"$email" <<END
288 This is even though previously the outcome seemed to be
289   $reported
290 and this was reported previously.
291 END
292         fi
293
294         cat >>"$email" <<END
295
296 Logs are in
297   $attempt
298 and concatenated to this email.
299
300 END
301
302         local log
303         for log in "$attempt"/*; do
304                 cat >>"$email" <<END
305 --$delim
306 Content-Type: text/plain; charset="utf-8"
307 Content-Disposition: inline; filename="${log##*/}"
308 Content-Description: "${log##*/}"
309 Content-Transfer-Encoding: 8bit
310
311 END
312                 cat >>"$email" <"$log"
313                 echo >>"$email"
314         done
315
316         cat >>"$email" <<END
317 --$delim--
318 END
319
320         /usr/sbin/sendmail -odb -oem -oee -t <"$email"
321         echo "$outcome" >"$attempt"/reported
322 }
323
324 mode_report () {
325         acquire_lock -w "$@"
326
327         local attempt
328         for attempt in $statedir/*; do
329
330                 local now=$(date +%s)
331                 local age=$(stat -c %Y "$attempt")
332                 age=$(( $now - $age ))
333
334                 local found_to_report=false
335                 maybe-report ok
336                 maybe-report permfail
337                 maybe-report tempfail
338
339                 if ! [ -e $attempt/reported ] && \
340                      [ $age -gt $PROBE_TIMEOUT ]; then
341                         echo >"$attempt"/timeout \
342         "Message did not arrive after ${PROBE_TIMEOUT}s"
343                 fi
344
345                 maybe-report timeout
346
347                 if [ -e $attempt/reported ] && \
348                    [ $age -gt $PROBE_EXPIRE ]; then
349                         rm -rf "$attempt"
350                 fi
351         done
352 }
353
354 mode_received () {
355         no_args $#
356
357         local hn group id domain mx addr
358         while read hn group id domain mx addr; do
359                 if [ x"$hn" != x"X-WebSTUMP-Relay-Probe:" ]; then continue; fi
360                 if [ x"$group" != x"$GROUP" ]; then continue; fi
361                 local td
362                 compute-td "mx=$mx,addr=$addr"
363                 >"$td/ok" ||:
364                 return
365         done
366 }
367
368 mode_all () {
369         no_args $#
370         for domain in $MODRELAYS; do
371                 probe-domain $domain
372         done
373 }
374
375 mode_domain () {
376         for domain in "$@"; do
377                 probe-domain $domain
378         done
379 }
380
381 mode=$1; shift||:
382
383 "mode_$mode" "$@"