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