#!/bin/sh
# Ogni minuto controlla che i salti di Velmo siano i primi di INPUT, FORWARD e
# messo regole prima, o le ha tolte, ricarica le regole di Velmo e lo scrive nel
# registro di sistema. Il controllo si fa solo quando fw3 non sta lavorando:
# durante un riavvio del firewall le catene sono a metà, e fw3 le rimette da sé
# con l'include.

# comune.sh qui in cima, non a metà file: «velmo-guardia verifica» esce alla
# riga subito sotto verifica(), e le funzioni condivise che quella usa -
# impronta_regole - devono essere già definite. Sorge anche router.conf.
. /usr/lib/velmo/comune.sh

#   velmo-guardia verifica   esce con 0 se le regole in vigore sono quelle di
#                            adesso e i salti sono in testa (per le prove)
verifica() {
	P=$VELMO_PONTE
	W=$(lato_casa)
	[ -n "$W" ] || W=$VELMO_WAN
	INPUT_ATTESO="-A INPUT -i $W -j velmo_ingresso_casa"
	[ "$W" = "$VELMO_WAN" ] || INPUT_ATTESO="-A INPUT -i $VELMO_WAN -j velmo_scarta"
	# L'IPv6 ha la stessa forma, e la stessa eccezione: con la casa raggiunta
	# dalla radio, firewall.sh mette in testa lo scarto della WAN dichiarata.
	# Fino al 2 ottobre 2026 qui si aspettava sempre la catena di casa sulla WAN:
	# con la via sul Wi-Fi la verifica falliva a ogni giro, il firewall si
	# ricaricava ogni minuto e le righe IPv6 salivano a 1232 in una notte.
	INPUT6_ATTESO="-A INPUT -i $W -j velmo_ingresso6_wan"
	[ "$W" = "$VELMO_WAN" ] || INPUT6_ATTESO="-A INPUT -i $VELMO_WAN -j DROP"
	ora="$(iptables -w 10 -S FORWARD | sed -n '2,3p' | tr '\n' ' ')|$(iptables -w 10 -S INPUT | sed -n 2p)|$(iptables -w 10 -t nat -S PREROUTING | sed -n 2p)|$(ip6tables -w 10 -S FORWARD | sed -n '2,3p' | tr '\n' ' ')|$(ip6tables -w 10 -S INPUT | sed -n 2p)"
	atteso="-A FORWARD -i $P -j velmo_inoltro -A FORWARD -o $P -j velmo_inoltro |$INPUT_ATTESO|-A PREROUTING -i $P -j velmo_dns|-A FORWARD -i $P -j DROP -A FORWARD -o $P -j DROP |$INPUT6_ATTESO"
	[ "$ora" = "$atteso" ] || return 1
	# Il ponte ricreato da netifd perde nf_call_iptables: senza, il traffico fra le
	# porte dei televisori non passa dal firewall.
	[ ! -d "/sys/class/net/$P/bridge" ] || [ "$(cat /sys/class/net/$P/bridge/nf_call_iptables)" = 1 ] || return 1
	# E le regole in vigore devono essere quelle che **questo** software
	# genererebbe adesso, non quelle di una versione di prima.
	#
	# L'aggiornamento online copia i file e riavvia velmod, il guardiano e la
	# bacheca, e non ricarica mai il firewall: nel kernel resta il ruleset della
	# versione precedente. Il 22 settembre 2026 questo faceva sparire la bacheca
	# dai televisori - la REDIRECT 80->8089 ancora in vigore, e nessuno in ascolto
	# sulla 8089 - finché qualcuno non riavviava l'apparecchio (C1). Non lo
	# vedeva nessuno: i salti erano in testa, e questa funzione guardava solo
	# quelli.
	#
	# Si osserva invece di ricordare, come fa via-cambiata.sh con la via di casa:
	# firewall.sh scrive l'impronta dei file da cui ha generato le regole che ha
	# caricato, e qui si confronta con quella di adesso. Vale per qualunque
	# cambiamento futuro del ruleset, non solo per la deviazione di oggi. Se
	# l'impronta manca - firewall.sh mai riuscito, o /var/run appena svuotato -
	# la risposta è «no», e la ricarica la rimette: una volta, poi combacia.
	[ "$(cat /var/run/velmo/firewall.impronta 2>/dev/null)" = "$(impronta_regole)" ]
}
[ "$1" = verifica ] && { verifica; exit $?; }

# Un tentativo senza velmod vivo non deve bloccare aggiornamenti per sempre.
# Si conserva per 15 minuti il margine del riavvio; solo dopo si registra
# esplicitamente il fallimento e si rimuove il marcatore orfano.
spazza_tentativo() {
	stato=/etc/velmo/aggiornamento
	[ -f "$stato/tentativo" ] || return 0
	quando=$(sed -n 's/^quando //p' "$stato/tentativo" | head -n 1)
	[ -n "$quando" ] || return 0
	partito=$(date -d "$quando" +%s 2>/dev/null) || return 0
	adesso=$(date +%s)
	[ $((adesso - partito)) -ge 900 ] || return 0
	mv "$stato/tentativo" "$stato/tentativo.scaduto" 2>/dev/null || return 0
	printf 'esito scartato\nmotivo aggiornamento interrotto\nquando %s\n' \
		"$(date -u +%Y-%m-%dT%H:%M:%SZ)" > "$stato/ultimo"
	rm -f "$stato/tentativo.scaduto" "$stato/file-scritti" "$stato/avvii"
}

if [ "$1" = controlla ]; then
	spazza_tentativo
	if ! verifica; then
		logger -t velmo-guardia "le regole di Velmo non sono in testa, o non sono quelle di adesso: le ricarico"
		/usr/lib/velmo/firewall.sh
	fi
	exit 0
fi

# Non si prende il lucchetto di fw3: fw3 toglie /var/run/fw3.lock quando ha
# sia. «flock» lo creava e lo lasciava lì, e quegli script restavano fermi per
# sempre (15 settembre 2026). Si aspetta invece, come loro, che non ci sia.
# La memoria: sotto la soglia si scrive nel registro chi la sta usando, per
# trovare picchi come quello di velmod del 15 settembre 2026 (spec §14.7).
memoria() {
	disp=$(awk '/^MemAvailable/ {print $2}' /proc/meminfo)
	[ "$disp" -lt "${VELMO_SOGLIA_MEMORIA_KB:-12000}" ] || return 0
	primi=$(for p in /proc/[0-9]*; do a=$(awk '/^RssAnon/ {print $2}' $p/status 2>/dev/null); [ -n "$a" ] && [ "$a" -gt 1000 ] && echo "$a $(cat $p/comm)"; done | sort -rn | head -4 | tr '\n' ',')
	logger -p daemon.warn -t velmo-guardia "memoria disponibile $disp KB; anonima: $primi"
}

# Il disco dei dati: oltre la soglia si scrive nel registro di sistema chi lo
# occupa. Il 17 settembre 2026 si è riempito in silenzio - una copia
# dell'archivio lasciata lì a mano - e il guardiano ha smesso di scrivere il suo
# registro senza che niente lo dicesse, se non i suoi errori.
disco() {
	. /etc/velmo/router.conf
	pieno=$(df -k "$VELMO_DATI" 2>/dev/null | awk 'NR == 2 {sub("%", "", $5); print $5}')
	[ -n "$pieno" ] && [ "$pieno" -ge "${VELMO_SOGLIA_DISCO:-85}" ] || return 0
	grandi=$(du -sk "$VELMO_DATI"/* "$VELMO_DATI"/*/* 2>/dev/null | sort -rn | head -4 | awk '{printf "%s %s KB, ", $2, $1}')
	logger -p daemon.warn -t velmo-guardia "disco dei dati pieno al $pieno%: $grandi"
}

# Il fuso orario dei servizi Velmo. Nel registro di sistema ogni riga porta due
# orari: quello che ci mette procd, che è l'ora del router, e quello che ci mette
# velmod, che è l'ora che crede lui. Devono dire la stessa cosa. Il 16 settembre
# 2026 non la dicevano - velmod girava in UTC perché su OpenWrt il fuso non sta
# dove Go lo cerca - e i nomi delle richieste finivano nell'archivio due ore
# avanti: il traffico si vedeva, i nomi no.

# lato_casa_al_minuto accende e spegne la rete di configurazione, e tiene velmod
# legato all'interfaccia giusta.
#
# VELMO-ONE non è uno stato di fabbrica: è lo stato «non so da dove uscire»
# (spec 2026-09-20 §3). Ci si arriva accendendo un apparecchio nuovo, e ci si
# torna il giorno che la via verso casa sparisce e non ce n'è un'altra da
# provare. Chi non la accendesse lascerebbe un apparecchio muto; chi non la
# spegnesse lascerebbe in onda, in una casa già configurata, una rete senza fili
# con la parola stampata sulla scatola.
#
# **Il riavvio di velmod è la metà che si dimentica.** velmod sceglie
# l'interfaccia di gestione quando l'istanza parte (init.d), e procd che la
# respawna non rifà quella scelta: senza un riavvio del servizio, l'apparecchio
# che ritrova il cavo continuerebbe ad ascoltare sulla rete di configurazione, e
# quello che l'ha persa continuerebbe ad ascoltare su un'interfaccia spenta. Si
# riavvia solo quando l'interfaccia giusta è cambiata davvero, non a ogni giro:
# un riavvio al minuto sarebbe una console che non risponde mai per intero.
lato_casa_al_minuto() {
	if via_verso_casa; then
		/usr/lib/velmo/velmo-one.sh spegni
	elif [ -z "$(uci -q get wireless.velmo_casa.ssid)" ]; then
		# Nessuna via, e nessuna rete di casa da provare: è il primo avvio, o un
		# ritorno a quello stato. Con una rete di casa configurata invece si
		# aspetta che la stazione agganci - la rete di configurazione tornerebbe
		# in onda a ogni riavvio del router di casa.
		/usr/lib/velmo/velmo-one.sh accendi
	fi
	# E poi si guarda **quello che velmod sta facendo davvero**, non quello che
	# si era deciso l'ultima volta.
	#
	# Il 20 settembre 2026 qui c'era una variabile con «l'ultimo lato casa che ho
	# visto», e velmod si riavviava solo quando cambiava. Staccando il cavo la
	# rete di configurazione si è alzata, il DHCP dava indirizzi, e velmod è
	# rimasto a ripetere «interfaccia di gestione eth0.2: nessun indirizzo» ogni
	# cinque secondi per quattro minuti: la scelta sbagliata si era congelata -
	# procd respawna il comando senza rifare la scelta dell'init.d - e la guardia
	# credeva di aver già fatto il suo lavoro. Uno stato ricordato non si
	# corregge; uno stato osservato sì, a ogni giro, finché non torna giusto.
	casa_giusta=$(interfaccia_di_gestione)
	casa_ora=$(velmod_gestione) || return 0
	[ -n "$casa_giusta" ] && [ -n "$casa_ora" ] || return 0
	[ "$casa_ora" = "$casa_giusta" ] && return 0
	logger -t velmo-guardia "velmod ascolta su $casa_ora e dovrebbe ascoltare su $casa_giusta: lo riavvio"
	/etc/init.d/velmod restart >/dev/null 2>&1
}

# La via verso casa, al minuto. Il lavoro vero lo fa via-cambiata.sh, che è lo
# stesso che l'hotplug di netifd chiama quando un'interfaccia va su o giù: lì
# arriva in un secondo, qui al minuto. Questa è la rete di sicurezza, per quando
# l'evento non arriva - un'interfaccia che cambia indirizzo senza andare giù, un
# hotplug perso - e non una seconda copia della stessa logica: due posti che
# rimettono le regole divergono, e il giorno che divergono nessuno capisce quale
# delle due ha parlato.
regole_della_via() {
	/usr/lib/velmo/via-cambiata.sh
}

minuti() { echo "$1" | awk -F: '{print $1 * 60 + $2}'; }
fuso() {
	riga=$(logread | grep -E "velmod\[[0-9]+\]: [0-9]{4}/[0-9]{2}/[0-9]{2} " | tail -1)
	[ -n "$riga" ] || return 0
	sistema=$(echo "$riga" | awk '{print $4}' | cut -d: -f1,2)
	suo=$(echo "$riga" | sed -n 's/.*: [0-9]\{4\}\/[0-9]\{2\}\/[0-9]\{2\} \([0-9]\{2\}:[0-9]\{2\}\).*/\1/p')
	[ -n "$suo" ] && [ -n "$sistema" ] || return 0
	d=$(( $(minuti "$sistema") - $(minuti "$suo") ))
	[ $d -lt 0 ] && d=$(( 0 - d ))
	# Un minuto di scarto è il cambio di minuto fra i due orari; oltre, è il fuso.
	[ $d -le 1 ] || [ $d -ge 1439 ] && return 0
	logger -p daemon.warn -t velmo-guardia \
		"velmod crede che siano le $suo e il router dice le $sistema: fuso orario sbagliato, i nomi finiscono nell'ora sbagliata"
}

# ssh_quando_la_console_c_e: la politica di SSH - dropbear chiuso, senza
# condizioni, dal 23 settembre 2026 che l'apparecchio e' chiuso - si applica
# **dopo** aver visto la console rispondere dove deve, non prima.
#
# Stava in lega_ascolti, cioe' dentro start_service di velmod, prima di
# procd_open_instance: un aggiornamento online riavvia velmod, e la porta si
# chiudeva li'. Fra la copia dei file e lo spegnimento non passava nessun
# comando umano, e se velmod non riusciva a legare l'indirizzo di gestione -
# la finestra che l'init stesso documenta, «se il DHCP non ha ancora dato
# l'indirizzo esce, e procd lo riavvia» - l'apparecchio restava senza SSH e
# senza console insieme (C1, revisione del 22 settembre 2026).
#
# Qui invece si guarda, e si guarda la cosa vera: non «velmod e' partito», ma
# **c'e' qualcuno in ascolto sulla porta della console, sull'indirizzo
# dell'interfaccia di gestione di adesso**. Finche' non c'e', SSH resta com'e'.
# Si chiude una porta solo dopo che l'altra e' aperta, e si riprova al minuto.
ssh_quando_la_console_c_e() {
	[ -n "$VELMOD_PORTA_CASA" ] || return 0
	sq_if=$(interfaccia_di_gestione)
	[ -n "$sq_if" ] || return 0
	sq_ip=$(ip -4 addr show "$sq_if" 2>/dev/null | sed -n 's/.*inet \([0-9.]*\).*/\1/p' | head -1)
	[ -n "$sq_ip" ] || return 0
	netstat -ltn 2>/dev/null |
		awk -v a="$sq_ip:$VELMOD_PORTA_CASA" '$4 == a { t = 1 } END { exit !t }' || return 0
	# E l'altra meta': che da casa ci si arrivi davvero. Un ascolto non e' una
	# via - se firewall.sh e' uscito 1 dentro una ricarica di fw3 (matrice
	# assente), fw3 ha gia' rifatto le tabelle senza le catene di Velmo e dal
	# lato casa decide la zona wan, input REJECT: velmod ascolta e nessuno lo
	# raggiunge. Li' chiudere anche SSH vuol dire perdere tutt'e due le vie
	# (trovato in revisione, 23 settembre 2026). «verifica» dice esattamente
	# questo: le regole in vigore sono quelle che i file di adesso generano.
	verifica || return 0
	lega_ssh_a_casa
}

while :; do
	memoria
	disco
	fuso
	lato_casa_al_minuto
	regole_della_via
	ssh_quando_la_console_c_e
	# La domanda vera alle tre istanze del DNS, ogni giro: su main è un timer a
	# parte, qui sta nello stesso ciclo perché il router ha un processo in meno
	# da tenere. Il programma è lo stesso di main nell'interfaccia e nelle soglie.
	[ "${VELMO_GUARDIA_DNS:-1}" = 1 ] && /usr/sbin/velmo-guardia-dns >/dev/null 2>&1
	n=0
	while [ -e /var/run/fw3.lock ] && [ $n -lt 30 ]; do sleep 1; n=$((n + 1)); done
	[ -e /var/run/fw3.lock ] || "$0" controlla
	sleep 60
done
