fix: update version.txt for 1.26.2 release

This commit is contained in:
Hernâni Marques 2026-09-08 01:39:31 +02:00
parent 6d5adeef9c
commit fb68a1b011
No known key found for this signature in database
8 changed files with 691 additions and 0 deletions

332
check_mail_deliverability.sh Executable file
View file

@ -0,0 +1,332 @@
#!/usr/bin/env bash
# check_mail_deliverability.sh — Taler mail-plane deliverability (ops-checklist)
#
# SUT domains (NOT cyon): taler.net, gnunet.org, firefly.gnunet.org,
# taler-systems.com, anastasis.lu, taler-ops.ch (from mail-catalog.conf)
#
# Based on admin-log/firefly/ops-checklist.md (2026-03 MT/ED scores):
# dig SPF + DMARC + DKIM selector(s); SMTP RCPT smoke via monitord helper;
# optional IMAP sink poll (mon001–003@hacktivism.ch on cyon = sink only).
#
# Explicit phase (not in all/full):
# ./taler-monitoring.sh mail-deliverability
# ./taler-monitoring.sh maildeliv
#
# Env:
# MAIL_CATALOG default mail-catalog.conf
# MAIL_DKIM_SELECTORS default "mail-2026-3 mail" (space-separated)
# MAIL_DELIV_IMAP=1 enable sink IMAP login+INBOX check (needs pass)
# MAIL_DELIV_IMAP_USERS default mon001@hacktivism.ch mon002@… mon003@…
# MAIL_DELIV_IMAP_HOST default mail.cyon.ch (sink host — not SUT)
# MAIL_DELIV_IMAP_PASS shared password for mon* (prefer secrets.env)
# MAIL_DELIV_SEND=0 1 = authenticated send (Freigabe; needs SMTP_*)
# MAIL_DELIV_ALERT=0 1 = on ERROR, send alert via sink SMTP (Freigabe)
# MAIL_DELIV_ALERT_FROM default mon001@hacktivism.ch
# MAIL_DELIV_ALERT_TO default hernani+mon@taler-systems.com
# hernani+mon@vecirex.net vecirex+mon@icloud.com
# fdold+mon@taler.net
# MAIL_DELIV_ALERT_SMTP default mail.cyon.ch:587 (sink submission — not SUT)
# MAIL_DELIV_ALERT_PASS default MAIL_DELIV_IMAP_PASS
# MAIL_DELIV_ALERT_COOLDOWN_S default 21600 (6h); stamp under monitord state
# TALER_MONITORD_DELIVER_* see site-gen smtp_deliverability_probe
#
set -euo pipefail
ROOT=$(cd "$(dirname "$0")" && pwd)
# shellcheck source=lib.sh
source "$ROOT/lib.sh"
set_area maildeliv
section "maildeliv · Taler mail domains (SPF/DMARC/DKIM + SMTP RCPT; cyon=sink only)"
CATALOG="${MAIL_CATALOG:-$ROOT/mail-catalog.conf}"
SELECTORS="${MAIL_DKIM_SELECTORS:-mail-2026-3 mail}"
: "${MAIL_DELIV_IMAP:=0}"
: "${MAIL_DELIV_SEND:=0}"
: "${MAIL_DELIV_ALERT:=0}"
: "${MAIL_DELIV_PROBE:=0}"
IMAP_HOST="${MAIL_DELIV_IMAP_HOST:-mail.cyon.ch}"
IMAP_USERS="${MAIL_DELIV_IMAP_USERS:-mon001@hacktivism.ch mon002@hacktivism.ch mon003@hacktivism.ch}"
ALERT_FROM="${MAIL_DELIV_ALERT_FROM:-mon001@hacktivism.ch}"
ALERT_TO="${MAIL_DELIV_ALERT_TO:-hernani+mon@taler-systems.com hernani+mon@vecirex.net vecirex+mon@icloud.com fdold+mon@taler.net}"
ALERT_SMTP="${MAIL_DELIV_ALERT_SMTP:-mail.cyon.ch:587}"
ALERT_PASS="${MAIL_DELIV_ALERT_PASS:-${MAIL_DELIV_IMAP_PASS:-}}"
ALERT_COOLDOWN_S="${MAIL_DELIV_ALERT_COOLDOWN_S:-21600}"
STATE_DIR="${TALER_MONITORD_STATE_DIR:-${XDG_STATE_HOME:-$HOME/.local/state}/taler-monitord}"
ALERT_STAMP="${STATE_DIR}/mail-deliv-alert.stamp"
if [ ! -f "$CATALOG" ]; then
fail "catalog" "missing $CATALOG"
summary
exit 1
fi
info "scope" "SUT = catalog Taler domains · sink IMAP host=$IMAP_HOST (not under test)"
dns_txt() {
local name="$1"
if command -v dig >/dev/null 2>&1; then
dig +short +time=3 +tries=2 TXT "$name" 2>/dev/null | tr -d '"'
else
host -t TXT "$name" 2>/dev/null | sed 's/.*"\(.*\)"/\1/'
fi
}
dns_mx() {
local d="$1"
dig +short +time=3 +tries=2 MX "$d" 2>/dev/null | awk '{print tolower($2)}' | sed 's/\.$//'
}
# --- per catalog domain ---
while IFS= read -r line || [ -n "$line" ]; do
line=${line%%#*}
line=$(echo "$line" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
[ -z "$line" ] && continue
domain=$(echo "$line" | awk '{print $1}')
expect_mx=$(echo "$line" | awk '{print $2}')
label=$(echo "$line" | awk '{print $6}')
[ -n "$domain" ] || continue
set_group "${label:-$domain}"
# SPF
spf=$(dns_txt "$domain" | tr '\n' ' ' | grep -oE 'v=spf1[^"]*' | head -1 || true)
if [ -n "$spf" ]; then
ok "spf" "$domain has SPF" "$spf"
else
fail "spf" "$domain missing SPF TXT"
fi
# DMARC
dmarc=$(dns_txt "_dmarc.$domain" | tr '\n' ' ' | grep -oE 'v=DMARC1.*' | head -1 || true)
if [ -n "$dmarc" ]; then
case "$dmarc" in
*p=none*)
if [ "$domain" = "taler-ops.ch" ] || [ "$domain" = "firefly.gnunet.org" ]; then
warn "dmarc" "$domain DMARC p=none (known weak · ops-checklist)" "$dmarc"
else
warn "dmarc" "$domain DMARC p=none" "$dmarc"
fi
;;
*)
ok "dmarc" "$domain DMARC present" "$dmarc"
;;
esac
else
fail "dmarc" "$domain missing _dmarc TXT"
fi
# DKIM selectors (ops-checklist / 2026-03)
dkim_ok=0
dkim_hit=""
for sel in $SELECTORS; do
[ -n "$sel" ] || continue
txt=$(dns_txt "${sel}._domainkey.${domain}" | tr '\n' ' ')
if echo "$txt" | grep -qiE 'v=DKIM1|p='; then
dkim_ok=1
dkim_hit="$sel"
break
fi
done
if [ "$dkim_ok" = "1" ]; then
ok "dkim" "$domain DKIM selector $dkim_hit"
else
warn "dkim" "$domain no DKIM TXT for selectors [$SELECTORS] (mail-tester often -1)"
fi
# MX matches catalog expectation (first token)
mx_got=$(dns_mx "$domain" | head -1)
exp_first=${expect_mx%%,*}
exp_first=${exp_first%% *}
if [ -n "$mx_got" ]; then
if [ -z "$exp_first" ] || [ "$exp_first" = "-" ] || [ "$mx_got" = "$exp_first" ]; then
ok "mx" "$domain MX=$mx_got"
else
# allow alternate listed in expect_mx comma list
case ",${expect_mx}," in
*",${mx_got},"*) ok "mx" "$domain MX=$mx_got (listed)" ;;
*) warn "mx" "$domain MX=$mx_got want~$exp_first" ;;
esac
fi
else
fail "mx" "$domain no MX"
continue
fi
# SMTP RCPT smoke (no DATA) — reuse site-gen helper
if [ -f "$ROOT/site-gen/stack_status_lib.py" ]; then
out=$(
ROOT="$ROOT" DOMAIN="$domain" MX="$mx_got" python3 - <<'PY' 2>/dev/null || echo "err=import"
import os, sys
sys.path.insert(0, os.path.join(os.environ["ROOT"], "site-gen"))
from stack_status_lib import smtp_deliverability_probe
r = smtp_deliverability_probe(os.environ["MX"], domain=os.environ["DOMAIN"])
print(f"ok={r.get('ok')} light={r.get('light')} reason={r.get('reason')} rcpt={r.get('rcpt')}")
PY
)
case "$out" in
ok=True*)
ok "smtp-rcpt" "$domain $out"
;;
ok=False*light=yellow*|ok=False*rcpt_reject*|ok=False*reason=rcpt_reject*)
# Many MTAs reject postmaster@ from random clients; still useful signal (ops-checklist uses mail-tester for full score).
warn "smtp-rcpt" "$domain $out"
;;
ok=False*)
fail "smtp-rcpt" "$domain $out"
;;
*)
warn "smtp-rcpt" "$domain probe unavailable · $out"
;;
esac
else
info "smtp-rcpt" "skipped (no site-gen/stack_status_lib.py)"
fi
done <"$CATALOG"
# --- optional IMAP sinks (cyon hosts mon*; not SUT) ---
if [ "$MAIL_DELIV_IMAP" = "1" ]; then
set_group sink-imap
if [ -z "${MAIL_DELIV_IMAP_PASS:-}" ]; then
warn "sink-imap" "MAIL_DELIV_IMAP=1 but MAIL_DELIV_IMAP_PASS unset — skip"
else
info "sink-imap" "probing sinks on $IMAP_HOST (infrastructure only · not Taler MTA)"
for user in $IMAP_USERS; do
res=$(
MAIL_DELIV_IMAP_HOST="$IMAP_HOST" MAIL_DELIV_IMAP_USER="$user" \
MAIL_DELIV_IMAP_PASS="$MAIL_DELIV_IMAP_PASS" python3 - <<'PY' 2>/dev/null || echo FAIL
import imaplib, os, ssl
host=os.environ["MAIL_DELIV_IMAP_HOST"]
user=os.environ["MAIL_DELIV_IMAP_USER"]
pw=os.environ["MAIL_DELIV_IMAP_PASS"]
ctx=ssl.create_default_context()
M=imaplib.IMAP4_SSL(host, 993, ssl_context=ctx)
M.login(user, pw)
typ, data=M.select("INBOX")
n=data[0].decode() if data and data[0] else "?"
# also see junk/spam exists
boxes=[]
t, bl=M.list()
for b in bl or []:
s=b.decode(errors="replace")
if "Junk" in s or "spam" in s.lower():
boxes.append("junk/spam")
M.logout()
print(f"OK inbox={n} extra={','.join(boxes) or 'none'}")
PY
)
case "$res" in
OK*) ok "sink" "$user · $res" ;;
*) fail "sink" "$user · $res" ;;
esac
done
fi
else
info "sink-imap" "skipped (MAIL_DELIV_IMAP=0) · sinks mon001–003 for Freigabe send tests"
fi
if [ "$MAIL_DELIV_SEND" = "1" ]; then
warn "send" "MAIL_DELIV_SEND=1 deprecated here — use MAIL_DELIV_PROBE=1 (inbox vs spam)"
fi
if [ "$MAIL_DELIV_PROBE" = "1" ]; then
set_group probe
if [ -z "${MAIL_DELIV_IMAP_PASS:-}${MAIL_DELIV_ALERT_PASS:-}" ]; then
warn "probe" "MAIL_DELIV_PROBE=1 but no IMAP pass"
else
info "probe" "mon* send + IMAP inbox-vs-spam (regular path · host-agent timer 6h)"
if out=$(python3 "$ROOT/mail_deliv_probe.py" 2>&1); then
ok "probe" "all sinks inbox"
info "probe-detail" "$(echo "$out" | tr '\n' ' ' | head -c 240)"
else
ec=$?
echo "$out" | while IFS= read -r line; do
case "$line" in
*result=spam*|*result=both*)
fail "probe-spam" "$line"
;;
*result=missing*|*IMAP_FAIL*|*SEND_FAIL*)
fail "probe-miss" "$line"
;;
SENT*|ALERT*)
info "probe" "$line"
;;
*)
info "probe" "$line"
;;
esac
done
[ "$ec" -eq 0 ] || true
fi
fi
else
info "probe" "skipped (MAIL_DELIV_PROBE=0) · enable for inbox-vs-spam; timer: taler-monitoring-mail-deliv.timer"
fi
# --- alert on ERROR via sink SMTP (cyon submission; recipients = hernani+mon / icloud) ---
maildeliv_alert_if_needed() {
set_group alert
if [ "$MAIL_DELIV_ALERT" != "1" ]; then
info "alert" "skipped (MAIL_DELIV_ALERT=0) · would To: $ALERT_TO"
return 0
fi
if [ "${#ERRORS[@]}" -eq 0 ]; then
ok "alert" "no ERRORs — no alert mail"
return 0
fi
if [ -z "$ALERT_PASS" ]; then
warn "alert" "MAIL_DELIV_ALERT=1 but no MAIL_DELIV_ALERT_PASS / MAIL_DELIV_IMAP_PASS"
return 0
fi
if ! command -v swaks >/dev/null 2>&1; then
warn "alert" "swaks missing — apt/brew install swaks"
return 0
fi
mkdir -p "$STATE_DIR" 2>/dev/null || true
now=$(date +%s)
if [ -f "$ALERT_STAMP" ]; then
prev=$(cat "$ALERT_STAMP" 2>/dev/null || echo 0)
if [ "$prev" -gt 0 ] 2>/dev/null; then
age=$((now - prev))
if [ "$age" -lt "$ALERT_COOLDOWN_S" ]; then
info "alert" "cooldown ${age}s < ${ALERT_COOLDOWN_S}s — skip send"
return 0
fi
fi
fi
smtp_host=${ALERT_SMTP%%:*}
smtp_port=${ALERT_SMTP##*:}
[ "$smtp_port" = "$ALERT_SMTP" ] && smtp_port=587
body=$(mktemp)
{
echo "taler-monitoring mail-deliverability ALERT"
echo "host=$(hostname 2>/dev/null || echo ?)"
echo "when=$(date -Iseconds 2>/dev/null || date)"
echo "errors=${#ERRORS[@]}"
echo ""
for e in "${ERRORS[@]}"; do
echo "- $e"
done
echo ""
echo "SUT = Taler mail domains (not cyon). From=$ALERT_FROM via $ALERT_SMTP"
} >"$body"
subj="[taler-mon maildeliv] ${#ERRORS[@]} ERROR(s)"
send_fail=0
for to in $ALERT_TO; do
if swaks --server "$smtp_host" --port "$smtp_port" \
--auth LOGIN --auth-user "$ALERT_FROM" --auth-password "$ALERT_PASS" \
--tls --from "$ALERT_FROM" --to "$to" \
--header "Subject: $subj" --body "$body" >/dev/null 2>&1; then
ok "alert" "sent → $to"
else
warn "alert" "send failed → $to"
send_fail=1
fi
done
rm -f "$body"
if [ "$send_fail" = "0" ]; then
echo "$now" >"$ALERT_STAMP" 2>/dev/null || true
fi
}
maildeliv_alert_if_needed
summary