#!/bin/sh
# Whether an address is one a trunk talks to. fail2ban's SIP jails ask this
# before they ban (ignorecommand), and exit 0 means leave it alone.
#
#   nuxpbx-trunk-address 217.10.79.9
#
# A refused SIP request over UDP carries whatever source address its sender
# wrote into it, so five forged with the provider's address banned the
# provider, and the company took no calls. The trunks are asked of Asterisk
# rather than written down once: a trunk added or moved is known without
# anything being rendered again.
#
# A trunk is what nuxpbx made one, never what a phone says about itself: the
# server of a registration, and the contacts of an endpoint with an outbound
# auth, which only a trunk has. Telling them apart by the shape of the contact
# (no user part, sip:sipgate.de) let a phone in: the phone writes its own
# Contact, the registrar takes sip:203.0.113.9:5060 as readily as
# sip:105@203.0.113.9, and whoever held one extension's password could then
# guess every other one from that address with no ban ever coming.
#
# Not asked per call. fail2ban asks this for every refused line, and a forged
# flood carries a new source on every line, so fail2ban's own cache, keyed by
# address, never hits: each line forked two Asterisk consoles and a DNS lookup
# per trunk, the jail fell behind, and a real guesser's lines aged past
# findtime unread. The addresses are worked out at most every five minutes
# into one file, which is all a line costs after that. fail2ban's own cache
# keeps an answer per address for another five minutes on top, so a trunk
# added is known within about ten minutes at worst, where it was five.
#
# ponytail: the host's A and AAAA records only. A provider that sends from
# addresses its name does not resolve to (an SRV target elsewhere) is not
# known here, and loses SIP for the ban, ssh and the interface kept.
[ -n "${1:-}" ] || exit 1

cache=${NUXPBX_TRUNK_CACHE:-/run/nuxpbx-trunk-addresses}

refresh() {
	registrations=$(asterisk -rx 'pjsip show registrations' 2>/dev/null) || return 0
	endpoints=$(asterisk -rx 'pjsip show endpoints' 2>/dev/null) || return 0
	part=$(mktemp "$cache.XXXXXX") || return 0
	for host in $(
		{
			printf '%s\n' "$registrations" | awk '$1 ~ /^[^<]+\/sips?:/ { print $1 }'
			# The header names an Endpoint and an I/OAuth too, never an
			# OutAuth, so it opens no trunk.
			printf '%s\n' "$endpoints" | awk '$1 == "Endpoint:" { trunk = 0 }
				$1 == "OutAuth:" { trunk = 1 }
				trunk && $1 == "Contact:" { print $2 }'
		} | sed -E 's#^[^/]*/sips?:##; s#^[^@]*@##; s#;.*##; s#^\[([^]]*)\].*#\1#; s#^([^:]+):[0-9]+$#\1#' \
			| sort -u
	); do
		getent ahosts "$host" 2>/dev/null | awk '{ print $1 }'
	done | sort -u >"$part"
	mv -f "$part" "$cache"
}

# An Asterisk that does not answer leaves the list as it was: it is not
# writing any refusals either, and the trunks it had are the trunks it has.
[ -n "$(find "$cache" -mmin -5 2>/dev/null)" ] || refresh
grep -Fqx -- "$1" "$cache" 2>/dev/null
