BastionGuard/data/scripts/BastionGuard-phishing-updater.sh

156 lines
5.8 KiB
Bash
Executable file

#!/bin/bash
set -euo pipefail
umask 077
LOG_DIR="/var/log/BastionGuard"
LOG_FILE="$LOG_DIR/phishing_updater.log"
PHISH_SYS="/usr/share/BastionGuard/data/phishing"
DNSMASQ_CONF="/etc/dnsmasq.d/BastionGuard-blacklist.conf"
DATE=$(date '+%Y-%m-%d %H:%M:%S')
if [[ ${EUID} -ne 0 ]]; then
echo "BastionGuard-phishing-updater: must run as root via pkexec" >&2
exit 77
fi
install -d -o root -g root -m 0755 "$LOG_DIR" "$PHISH_SYS"
echo "[$DATE] Avvio aggiornamento blacklist..." >> "$LOG_FILE"
# The systemd timer may invoke the helper without staged input. Preserve the
# old no-op behaviour, but never consume predictable files from /tmp.
if [[ $# -eq 0 ]]; then
echo "[$DATE] Nessun staging directory fornita; nessun aggiornamento da installare." >> "$LOG_FILE"
exit 0
fi
if [[ $# -ne 1 ]]; then
echo "Uso: $0 <staging-dir>" >&2
exit 64
fi
STAGE="$1"
CALLER_UID="${PKEXEC_UID:-${SUDO_UID:-}}"
if [[ -z "$CALLER_UID" || ! "$CALLER_UID" =~ ^[0-9]+$ || "$CALLER_UID" -eq 0 ]]; then
echo "BastionGuard-phishing-updater: caller UID non affidabile" >&2
exit 65
fi
if [[ -L "$STAGE" || ! -d "$STAGE" ]]; then
echo "BastionGuard-phishing-updater: staging directory non valida" >&2
exit 66
fi
STAGE_REAL=$(realpath -e -- "$STAGE")
case "$STAGE_REAL" in
/tmp/bastionguard-phish.*) ;;
*) echo "BastionGuard-phishing-updater: staging path non autorizzato" >&2; exit 67 ;;
esac
if [[ $(stat -c '%u' -- "$STAGE_REAL") != "$CALLER_UID" || \
$(stat -c '%a' -- "$STAGE_REAL") != "700" ]]; then
echo "BastionGuard-phishing-updater: owner/mode staging non validi" >&2
exit 68
fi
validate_stage_file() {
local f="$1"
[[ -f "$f" && ! -L "$f" ]] || return 1
[[ $(stat -c '%u' -- "$f") == "$CALLER_UID" ]] || return 1
[[ $(stat -c '%a' -- "$f") == "600" ]] || return 1
}
BLACKLIST="$STAGE_REAL/blacklist.txt"
BLACKLIST_REDUCE="$STAGE_REAL/blacklist-reduce.txt"
DNSMASQ_STAGE="$STAGE_REAL/dnsmasq-blacklist.conf"
validate_stage_file "$BLACKLIST" || { echo "blacklist staging non valida" >&2; exit 69; }
validate_stage_file "$BLACKLIST_REDUCE" || { echo "blacklist-reduce staging non valida" >&2; exit 69; }
validate_stage_file "$DNSMASQ_STAGE" || { echo "dnsmasq staging non valida" >&2; exit 69; }
# Snapshot the caller-owned inputs into a root-only directory before parsing.
# The final install is always performed from this immutable snapshot, so a
# caller cannot race validation by swapping the original /tmp files.
install -d -o root -g root -m 0700 /var/lib/BastionGuard
ROOT_STAGE=$(mktemp -d /var/lib/BastionGuard/phishing-stage.XXXXXX)
trap 'rm -rf -- "$ROOT_STAGE"' EXIT
install -m 0600 -- "$BLACKLIST" "$ROOT_STAGE/blacklist.txt"
install -m 0600 -- "$BLACKLIST_REDUCE" "$ROOT_STAGE/blacklist-reduce.txt"
install -m 0600 -- "$DNSMASQ_STAGE" "$ROOT_STAGE/dnsmasq-blacklist.conf"
BLACKLIST="$ROOT_STAGE/blacklist.txt"
BLACKLIST_REDUCE="$ROOT_STAGE/blacklist-reduce.txt"
DNSMASQ_STAGE="$ROOT_STAGE/dnsmasq-blacklist.conf"
validate_blacklist() {
local f="$1" line
while IFS= read -r line || [[ -n "$line" ]]; do
[[ -z "$line" || "$line" == \#* ]] && continue
[[ ${#line} -le 4096 ]] || return 1
# Backend.cpp emits URLs, domains or IPs only. Reject control/whitespace
# and arbitrary file content before it can become world-readable.
if [[ "$line" =~ ^https?://[^[:space:][:cntrl:]]+$ ]]; then
continue
fi
if [[ "$line" =~ ^[A-Za-z0-9._:-]+$ && "$line" == *.* ]]; then
continue
fi
return 1
done < "$f"
}
validate_blacklist "$BLACKLIST" || { echo "blacklist contenuto non valido" >&2; exit 70; }
validate_blacklist "$BLACKLIST_REDUCE" || { echo "blacklist-reduce contenuto non valido" >&2; exit 70; }
# Only the directive generated by Backend.cpp is accepted. This blocks an
# attacker from smuggling dhcp-script= or any other executable dnsmasq option.
while IFS= read -r line || [[ -n "$line" ]]; do
[[ -z "$line" ]] && continue
if [[ ! "$line" =~ ^address=/[A-Za-z0-9.-]+/127\.0\.0\.2$ ]]; then
echo "BastionGuard-phishing-updater: direttiva dnsmasq rifiutata" >&2
exit 71
fi
done < "$DNSMASQ_STAGE"
install -m 0644 -- "$BLACKLIST" "$PHISH_SYS/blacklist.txt"
install -m 0644 -- "$BLACKLIST_REDUCE" "$PHISH_SYS/blacklist-reduce.txt"
echo "[$DATE] blacklist installate" >> "$LOG_FILE"
# Copy the caller's whitelist only if it is a regular, caller-owned file.
REAL_HOME=$(getent passwd "$CALLER_UID" | cut -d: -f6)
if [[ -n "$REAL_HOME" ]]; then
WL_TXT="$REAL_HOME/.local/share/BastionGuard/phishing/whitelist.txt"
if [[ -f "$WL_TXT" && ! -L "$WL_TXT" && $(stat -c '%u' -- "$WL_TXT") == "$CALLER_UID" ]]; then
install -m 0600 -- "$WL_TXT" "$ROOT_STAGE/whitelist.txt"
WL_OK=1
while IFS= read -r line || [[ -n "$line" ]]; do
[[ -z "$line" || "$line" == \#* ]] && continue
if [[ ! "$line" =~ ^[A-Za-z0-9.-]+$ ]]; then WL_OK=0; break; fi
done < "$ROOT_STAGE/whitelist.txt"
if [[ "$WL_OK" -eq 1 ]]; then
install -m 0644 -- "$ROOT_STAGE/whitelist.txt" "$PHISH_SYS/whitelist.txt"
echo "[$DATE] whitelist utente copiata in sys" >> "$LOG_FILE"
fi
fi
fi
install -d -o root -g root -m 0755 /etc/dnsmasq.d
install -m 0644 -- "$DNSMASQ_STAGE" "$DNSMASQ_CONF"
DNSMASQ_OK=0
if dnsmasq --test > "$LOG_DIR/dnsmasq-test.log" 2>&1; then
DNSMASQ_OK=1
else
echo "[$DATE] dnsmasq --test fallito, skip reload" >> "$LOG_FILE"
fi
SCANNER_ACTIVE=0
systemctl is-active --quiet BastionGuard-phishing-scanner.service && SCANNER_ACTIVE=1 || true
if [[ "$DNSMASQ_OK" -eq 1 ]]; then
systemctl restart dnsmasq
echo "[$DATE] dnsmasq ricaricato" >> "$LOG_FILE"
fi
if [[ "$SCANNER_ACTIVE" -eq 1 ]]; then
systemctl restart BastionGuard-phishing-scanner.service
echo "[$DATE] Scanner riavviato" >> "$LOG_FILE"
fi
echo "[$DATE] Aggiornamento completato" >> "$LOG_FILE"
exit 0