#!/bin/bash
################################################################################
# Script Name: version/2.1.28/UPDATE.sh
# Description: Check every hosted zone for the delegation fault 2.1.28 can see.
# Company: hitechcloud.org
# Copyright (c) hitechcloud.org
################################################################################
#
# Nothing to migrate. 2.1.28 is a health check that did not exist and a licence
# key format nobody holds a key in yet - no schema, no configuration, no
# service.
#
# What it does instead is run the new check once, now, across every zone this
# server hosts. That is the point of the release: a zone whose nameservers are
# CNAMEs is unreachable from the internet while being perfectly correct here,
# and the panel used to report it as healthy. An operator who has one wants to
# know today, not the next time somebody happens to open the health page for
# that domain.
#
# Read-only. It changes nothing and cannot: the fault is in whichever zone
# holds the nameserver names, which is usually somebody else's, and always
# somewhere this script has no business writing.
################################################################################

set -uo pipefail

GREEN='\033[0;32m'; YELLOW='\033[1;33m'; NC='\033[0m'
ok()   { echo -e "[2.1.28] ${GREEN}✓${NC} $*"; }
warn() { echo -e "[2.1.28] ${YELLOW}!${NC} $*" >&2; }

[[ $EUID -eq 0 ]] || { echo "[2.1.28] This update must run as root." >&2; exit 1; }

# Where the zones are is discovered the same way the CLI discovers it, rather
# than hardcoded: it is /var/named/hitechcloud on RHEL and /etc/bind/hitechcloud
# on Debian, and a wrong guess here would quietly find no zones and report
# everything as fine.
zone_dir=""
for d in /var/named/hitechcloud/zones /etc/bind/hitechcloud/zones /etc/named/hitechcloud/zones; do
    [[ -d "$d" ]] && { zone_dir="$d"; break; }
done

if [[ -z "$zone_dir" ]]; then
    ok "No DNS zones are hosted here, so there is no delegation to check"
    ok "2.1.28 update complete"
    exit 0
fi

if ! command -v dig >/dev/null 2>&1; then
    warn "dig is not installed, so the new nameserver check could not run. Install bind-utils (or dnsutils) and run: hitechcloudcli native-healthcheck <domain>"
    ok "2.1.28 update complete"
    exit 0
fi

# A public resolver, not this machine. This server answers for its own zones
# and would happily report that its nameserver names resolve, which is the
# whole mistake being looked for - what matters is what the internet gets.
resolve() {
    local type="$1" name="$2" r out
    for r in 1.1.1.1 8.8.8.8; do
        out=$(dig +short +time=3 +tries=1 "$type" "$name" "@${r}" 2>/dev/null | grep -v '^$')
        [[ -n "$out" ]] && { echo "$out"; return 0; }
    done
    return 1
}

checked=0 broken=0
for zone_file in "$zone_dir"/db.*; do
    [[ -f "$zone_file" ]] || continue
    domain="${zone_file##*/db.}"
    [[ -n "$domain" ]] || continue
    checked=$((checked + 1))

    # The nameservers the zone itself declares. Its delegation at the
    # registrar should name the same hosts, and check_delegation already
    # compares those; this is about whether those names can be used at all.
    while read -r host; do
        [[ -n "$host" ]] || continue
        if alias=$(resolve CNAME "$host"); then
            alias="${alias%%$'\n'*}"; alias="${alias%.}"
            warn "${domain}: nameserver ${host} is a CNAME for ${alias}. RFC 2181 does not allow that, and a resolver following the rules cannot use it - the zone looks dead from the internet even though this server serves it correctly. Fix it in the zone that holds ${host}: replace the CNAME with an A record."
            broken=$((broken + 1))
        elif ! resolve A "$host" >/dev/null && ! resolve AAAA "$host" >/dev/null; then
            warn "${domain}: nameserver ${host} has no address on public DNS, so nothing can ask it for this zone."
            broken=$((broken + 1))
        fi
    done < <(awk '$3=="NS"{sub(/\.$/,"",$4); print $4}' "$zone_file" 2>/dev/null | sort -u)
done

if (( broken == 0 )); then
    ok "Checked the nameservers of ${checked} zone(s); every one resolves to an address of its own"
else
    warn "${broken} nameserver problem(s) across ${checked} zone(s). Nothing on this server needs changing; the records named above are in whichever zone holds those nameserver names."
fi

ok "2.1.28 update complete"
exit 0
