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