#!/bin/bash
-set -e
+set -e$MODRELAYS_PROBE_SET_X
+
+MODRELAYS=moderators.isc.org
+PROBE_TIMEOUT=$(( 20 * 60 ))
+PROBE_EXPIRE=$(( 32 * 86400 ))
+
+shopt -s nullglob
+
+case "$1" in
+received)
+ mode="$1"
+ cd "$2"
+ shift; shift; set "$mode" "$@"
+ ;;
+esac
+
+. ../global-settings
+. ./settings
id=$(date +%s)_$$
+statedir=probes/probes
+lockfile=$statedir/.lock
fail () {
printf >&2 "%s\n" "modrelays-probe: error: $1"
exit 16
}
-record-probing () {
- # implicitly uses newsgroup, id, domain
- # prints working dir filename
+compute-td () {
+ # implicitly uses GROUP, id, domain
+ # caller must "local td", which will be set
local probeid=$1
- probeid=${probeid//[^=-.,_0-9A-Za-z]/%/}
+ probeid="$domain,${probeid//[^-=:.,_0-9A-Za-z]/%},$id"
case $probeid in
- .*|*/*) fail "yikes, sanitisation bug!" ;;
+ .*|*/*) fail "yikes, sanitisation bug ($probeid) !" ;;
esac
- local newtd="monitoring/modrelays.probes/$probeid"
- mkdir -p $newtd
- printf "%s" $newtd
+ td="$statedir/$probeid"
+}
+
+record-probing () {
+ compute-td "$@"
+ mkdir -p $td
+}
+
+record-probing-start () {
+ record-probing "$@"
+ if ! [ -e "$td/started" ]; then
+ date -R >"$td/started"
+ fi
}
record-outcome () {
local probeid=$1
local outcome=$2
local message=$3
- local td=`record-probing "$probeid"`
- printf "%s\n" >"$td"/"$outcome"
+ local td
+ record-probing "$probeid"
+ printf "%s\n" >"$td"/"$outcome" "$message"
}
-record-success () { record-outcome "$1" ok '' }
-record-tempfail () { record-outcome "$1" tempfail "$2" }
-record-permfail () { record-outcome "$1" permfail "$2" }
+record-success () { record-outcome "$1" ok ''; }
+record-tempfail () { record-outcome "$1" tempfail "$2"; }
+record-permfail () { record-outcome "$1" permfail "$2"; }
probe-addr () {
local mx=$1
local addr=$2
- local td=`record-probing "mx=$mx,addr=$addr"`
+ local td
+ record-probing-start "mx=$mx,addr=$addr"
set +e
- swaks --to "${GROUP//./-/}@$domain" \
+ swaks --to "${GROUP//./-}@$domain" \
--server $addr \
--tls-optional-strict \
--header 'Subject: test modrelays probe test' \
local rhs
local prefix
local expect_no_5xx='initial connection'
- while read <$td/swaks.log prefix rhs; do
+ exec 4<$td/swaks.log
+ while read <&4 prefix rhs; do
case "$prefix" in
'<'*)
case "$rhs" in
5*)
if [ "x$expect_no_5xx" != x ] && \
[ "x$permfail" = x ]; then
- permfail="$rhs ($expect_no_5xx)" ;;
+ permfail="$rhs ($expect_no_5xx)"
fi
;;
esac
esac
;;
*)
+ esac
done
if [ "x$permfail" = x ]; then
probe-domain () {
local domain=$1
- local td=`record-probing dns`
+ local td
+ record-probing-start dns
set +e
adnshost -Fi -Tn +Do +Dt -t mx $domain >$td/dns
case $rc in
0)
# have a list of MX's
- exec <$td/dns
+ exec 3<$td/dns
local pref
local mx
local statustype
local rhs
- while read pref mx statustype rhs; do
- case $statustype in
+ while read <&3 pref mx statustype statustypenum rhs; do
+ case $statustypenum in
0)
# have a list of relays
case $rhs in
record-permfail "mx=$mx" \
"dns format $rhs"
continue
- ;;
- rhs=${rhs%%* (}
- rhs=${rhs# )}
+ ;;
+ esac
+ rhs=${rhs##* (}
+ rhs=${rhs% )}
local addr
for addr in $rhs; do
case $addr in
;;
[123])
# temporary errors
- record-tempfail "mx=$mx" "dns $rc $rhs"
+ record-tempfail "mx=$mx" \
+ "dns $rc $statustype $rhs"
;;
*)
# yikes
- record-permfail "mx=$mx" "dns $rc $rhs"
+ record-permfail "mx=$mx" \
+ "dns $rc $statustype $rhs"
;;
esac
done
return
;;
6)
- permfail, try A
+ # permfail, try A
set +e
- adnshost -Fi -Tn +Do +Dt -t a $domain >$td
+ adnshost -Fi -Tn +Do +Dt -t a $domain >$td/dns
rc=$?
set -e
;;
case $rc in
0)
# have a list of A's (dealt with MXs above)
- exec <$td/dns
+ exec 3<$td/dns
local addr
- while read addr; do
- probe-addr '<no-mx>' $addr
+ while read <&3 addr; do
+ probe-addr 'NONE' $addr
done
record-success dns
return
no_args () {
case $1 in
0) return ;;
- *) fail
+ *) fail "no arguments to $mode allowed" ;;
+ esac
+}
-mode_all () {
+acquire_lock () {
+ local lock_mode="$1"
+ if [ x"$WEBSTUMP_PROBE_LOCK" = x"$lockfile" ]; then return; fi
+ WEBSTUMP_PROBE_LOCK=$lockfile \
+ exec with-lock-ex $lock_mode "$lockfile" "$0" "$mode" "$@"
+}
+
+maybe-report () {
+ local outcome=$1
+
+ if $found_to_report; then return; fi
+ if ! [ -e "$attempt/$outcome" ]; then return; fi
+ found_to_report=true
+
+ read <"$attempt/$outcome" message
+
+ local reported
+ if [ -e "$attempt/reported" ]; then
+ read <"$attempt/reported" reported
+ fi
+ if [ "x$outcome" = "x$reported" ]; then return; fi
+
+ if [ x"$outcome" = x"ok" ] && [ x"$reported" = x ]; then
+ echo ok >"$attempt/reported"
+ return
+ fi
+
+ local info=${attempt##*/}
+ info=${info//,/ }
+
+ delim=`od -N 50 -An -x -w50 </dev/urandom`
+ delim=${delim// /}
+
+ local email="$attempt/.report.$outcome"
+ cat >"$email" <<END
+To: $ADMIN
+Subject: mod relay probe $outcome $info
+Content-Type: multipart/mixed; boundary="$delim"
+MIME-Version: 1.0
+
+--$delim
+Content-Type: text/plain; charset="utf-8"
+Content-Transfer-Encoding: 7bit
+
+The moderation relay probe
+ $info
+END
+
+ if [ -e "$attempt/started" ]; then
+ local started
+ read started <"$attempt/started"
+ cat >>"$email" <<END
+started at
+ $started
+END
+ fi
+
+ cat >>"$email" <<END
+resulted in the outcome
+ $outcome
+END
+ if [ "x$message" != x ]; then
+ cat >>"$email" <<END
+with the message
+ $message
+END
+ fi
+
+ if [ "x$reported" != x ]; then
+ cat >>"$email" <<END
+This is even though previously the outcome seemed to be
+ $reported
+and this was reported previously.
+END
+ fi
+
+ cat >>"$email" <<END
+
+Logs are in
+ $attempt
+and concatenated to this email.
+
+END
+
+ local log
+ for log in "$attempt"/*; do
+ cat >>"$email" <<END
+--$delim
+Content-Type: text/plain; charset="utf-8"
+Content-Disposition: inline; filename="${log##*/}"
+Content-Description: "${log##*/}"
+Content-Transfer-Encoding: 8bit
+
+END
+ cat >>"$email" <"$log"
+ echo >>"$email"
+ done
+
+ cat >>"$email" <<END
+--$delim--
+END
+
+ /usr/sbin/sendmail -odb -oem -oee -t <"$email"
+ echo "$outcome" >"$attempt"/reported
+}
+
+mode_report () {
+ acquire_lock -w "$@"
+
+ local attempt
+ for attempt in $statedir/*; do
+
+ local now=$(date +%s)
+ local age=$(stat -c %Y "$attempt")
+ age=$(( $now - $age ))
+
+ local found_to_report=false
+ maybe-report ok
+ maybe-report permfail
+ maybe-report tempfail
+
+ if ! [ -e $attempt/reported ] && \
+ [ $age -gt $PROBE_TIMEOUT ]; then
+ echo >"$attempt"/timeout \
+ "Message did not arrive after ${PROBE_TIMEOUT}s"
+ fi
+
+ maybe-report timeout
+
+ if [ -e $attempt/reported ] && \
+ [ $age -gt $PROBE_EXPIRE ]; then
+ rm -rf "$attempt"
+ fi
+ done
+}
+
+mode_received () {
no_args $#
- for domain in $MODRELAYS; do
- probe $domain
+
+ local hn group id domain mx addr
+ while read hn group id domain mx addr; do
+ if [ x"$hn" != x"X-WebSTUMP-Relay-Probe:" ]; then continue; fi
+ if [ x"$group" != x"$GROUP" ]; then continue; fi
+ case " $id $domain $mx $addr" in
+ */*|' '.*) fail "bad syntax" ;;
+ esac
+ local td
+ compute-td "mx=$mx,addr=$addr"
+ >"$td/ok" ||:
+ return
done
}
-mode_auto () {
+mode_all () {
no_args $#
- xxx do something to cause sleeping
- mode_all
+ for domain in $MODRELAYS; do
+ probe-domain $domain
+ done
}
-mode_relay () {
- relay="$1"
- probe "$relay"
+mode_domain () {
+ for domain in "$@"; do
+ probe-domain $domain
+ done
}
mode=$1; shift||: