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