#!/bin/bash set -e$MODRELAYS_PROBE_SET_X MODRELAYS=moderators.isc.org PROBE_TIMEOUT=$(( 20 * 60 )) PROBE_EXPIRE=$(( 32 * 86400 )) . ../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 # caller must "local td", which will be set local probeid=$1 probeid="$domain,${probeid//[^-=.,_0-9A-Za-z]/%/},$id" case $probeid in .*|*/*) fail "yikes, sanitisation bug!" ;; esac td="$statedir/$probeid" 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" "$message" } 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-start "mx=$mx,addr=$addr" set +e swaks --to "${GROUP//./-}@$domain" \ --server $addr \ --tls-optional-strict \ --header 'Subject: test modrelays probe test' \ --header \ "X-WebSTUMP-Relay-Probe: $GROUP $id $domain $mx $addr" \ -n >$td/swaks.log 2>$td/swaks.err rc=$? set -e case $rc in 0) return ;; # record-success done by receiver esac local permfail='' local rhs local prefix local expect_no_5xx='initial connection' 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)" fi ;; esac ;; *'>') case "$rhs" in EHLO*|STARTTLS*) expect_no_5xx='' ;; *) expect_no_5xx="after $rhs" ;; esac ;; *) esac done if [ "x$permfail" = x ]; then record-tempfail "mx=$mx,addr=$addr" "see swaks.log / swaks.err" else record-permfail "mx=$mx,addr=$addr" "$permfail" fi } probe-domain () { local domain=$1 local td record-probing-start dns set +e adnshost -Fi -Tn +Do +Dt -t mx $domain >$td/dns rc=$? set -e case $rc in 0) # have a list of MX's exec 3<$td/dns local pref local mx local statustype local rhs 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 ;; esac rhs=${rhs##* (} rhs=${rhs% )} local addr for addr in $rhs; do case $addr in INET|INET6) continue ;; esac probe-addr $mx $addr done ;; [123]) # temporary errors record-tempfail "mx=$mx" \ "dns $rc $statustype $rhs" ;; *) # yikes record-permfail "mx=$mx" \ "dns $rc $statustype $rhs" ;; esac done record-success dns return ;; 6) # permfail, try A set +e adnshost -Fi -Tn +Do +Dt -t a $domain >$td/dns rc=$? set -e ;; esac case $rc in 0) # have a list of A's (dealt with MXs above) exec 3<$td/dns local addr while read <&3 addr; do probe-addr 'NONE' $addr done record-success dns return ;; [123]) local emsg read <$td/dns emsg record-tempfail dns "dns $emsg" ;; *) local emsg read <$td/dns emsg record-permfail dns "dns $emsg" ;; esac } no_args () { case $1 in 0) return ;; *) fail "no arguments to $mode allowed" ;; esac } 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 "$email" <>"$email" <>"$email" <>"$email" <>"$email" <>"$email" <>"$email" <>"$email" <"$log" echo >>"$email" done cat >>"$email" <"$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_all () { no_args $# for domain in $MODRELAYS; do probe-domain $domain done } mode_auto () { no_args $# xxx do something to cause sleeping mode_all } mode_domain () { for domain in "$@"; do probe-domain $domain done } mode=$1; shift||: "mode_$mode" "$@"