chiark / gitweb /
80109e235b2afca1edf3c6ecd943d5cffc08c0fa
[modbot-ulm.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 do-report-log () {
228         # implicitly uses $outcome, $reported etc. - see maybe-report
229         echo "$outcome" >"$attempt"/reported
230 }
231
232 maybe-report () {
233         local outcome=$1
234
235         if $found_to_report; then return; fi
236         if ! [ -e "$attempt/$outcome" ]; then return; fi
237         found_to_report=true
238
239         message=$(cat "$attempt/$outcome")
240
241         local reported
242         if [ -e "$attempt/reported" ]; then
243                 read <"$attempt/reported" reported
244         fi
245         if [ "x$outcome" = "x$reported" ]; then return; fi
246
247         local info=${attempt##*/}
248         info=${info//,/ }
249
250         if [ x"$outcome" = x"ok" ] && [ x"$reported" = x ]; then
251                 do-report-log
252                 return
253         fi
254
255         delim=`od -N 50 -An -x -w50 </dev/urandom`
256         delim=${delim// /}
257
258         local email="$attempt/.report.$outcome"
259         cat >"$email" <<END
260 To: $ADMIN
261 Subject: mod relay probe $outcome $info
262 Content-Type: multipart/mixed; boundary="$delim"
263 MIME-Version: 1.0
264
265 --$delim
266 Content-Type: text/plain; charset="utf-8"
267 Content-Transfer-Encoding: 7bit
268
269 The moderation relay probe
270   $info
271 END
272
273         if [ -e "$attempt/started" ]; then
274                 local started
275                 read started <"$attempt/started"
276                 cat >>"$email" <<END
277 started at
278   $started
279 END
280         fi
281
282         cat >>"$email" <<END
283 resulted in the outcome
284   $outcome
285 END
286         if [ "x$message" != x ]; then
287                 cat >>"$email" <<END
288 with the message
289   $message
290 END
291         fi
292
293         if [ "x$reported" != x ]; then
294                 cat >>"$email" <<END
295 This is even though previously the outcome seemed to be
296   $reported
297 and this was reported previously.
298 END
299         fi
300
301         cat >>"$email" <<END
302
303 Logs are in
304   $attempt
305 and concatenated to this email.
306
307 END
308
309         local log
310         for log in "$attempt"/*; do
311                 cat >>"$email" <<END
312 --$delim
313 Content-Type: text/plain; charset="utf-8"
314 Content-Disposition: inline; filename="${log##*/}"
315 Content-Description: "${log##*/}"
316 Content-Transfer-Encoding: 8bit
317
318 END
319                 cat >>"$email" <"$log"
320                 echo >>"$email"
321         done
322
323         cat >>"$email" <<END
324 --$delim--
325 END
326
327         /usr/sbin/sendmail -odb -oem -oee -t <"$email"
328         do-report-log
329 }
330
331 mode_report () {
332         acquire_lock -w "$@"
333
334         local attempt
335         for attempt in $statedir/*; do
336
337                 local now=$(date +%s)
338                 local age=$(stat -c %Y "$attempt")
339                 age=$(( $now - $age ))
340
341                 local found_to_report=false
342                 maybe-report ok
343                 maybe-report permfail
344                 maybe-report tempfail
345
346                 if ! [ -e $attempt/reported ] && \
347                      [ $age -gt $PROBE_TIMEOUT ]; then
348                         echo >"$attempt"/timeout \
349         "Message did not arrive after ${PROBE_TIMEOUT}s"
350                 fi
351
352                 maybe-report timeout
353
354                 if [ -e $attempt/reported ] && \
355                    [ $age -gt $PROBE_EXPIRE ]; then
356                         rm -rf "$attempt"
357                 fi
358         done
359 }
360
361 mode_received () {
362         no_args $#
363
364         local hn group id domain mx addr
365         while read hn group id domain mx addr; do
366                 if [ x"$hn" != x"X-WebSTUMP-Relay-Probe:" ]; then continue; fi
367                 if [ x"$group" != x"$GROUP" ]; then continue; fi
368                 case " $id $domain $mx $addr" in
369                 */*|' '.*)      fail "bad syntax" ;;
370                 esac
371                 local td
372                 compute-td "mx=$mx,addr=$addr"
373                 >"$td/ok" ||:
374                 return
375         done
376 }
377
378 mode_all () {
379         no_args $#
380         for domain in $MODRELAYS; do
381                 probe-domain $domain
382         done
383 }
384
385 mode_domain () {
386         for domain in "$@"; do
387                 probe-domain $domain
388         done
389 }
390
391 mode=$1; shift||:
392
393 "mode_$mode" "$@"