#!/bin/sh
#
# Aktualisierung der lokalen CRL-Dateien
# Da die CRL-Dateien typischerweise eine Verfallszeit von 30 Tagen haben, sollte
# dieses Skript ca. täglich als cronjob ausgeführt werden.
#
# ACHTUNG: dieses Skript muss versionsunabhängig sein - es darf keine Dateien außerhalb dieses Pakets laden
#

set -eu


CRL_LIST="opennet-root.crl opennet-vpn-ugw.crl opennet-vpn-user.crl opennet-client.crl opennet-server.crl"
BASE_URLS="https://ca.opennet-initiative.de"
CA_DIR=/etc/ssl/certs/opennet-initiative.de
CURL_OPTIONS="-q --silent --fail"


verify_crl() {
	local hash="$1"
	local cert_file
	cert_file=$(find "$CA_DIR" -mindepth 1 -maxdepth 1 -name "$hash.*" -print0 \
		| grep "/$hash"'\.[0-9]$' | sort -n | tail -1 | xargs -0 basename)
	[ -z "$cert_file" ] && echo >&2 "Failed to find certificate belonging to CRL '$hash'" && return 1
	# Ausgabe des CRL-Resultats ("verify OK" oder aehnliches) umlenken - bei Erfolg sofort beenden
	openssl crl -CAfile "$CA_DIR/$cert_file" -noout 2>/dev/null && return 0
	echo >&2 "CRL validation failed for '$hash'" && return 2
}


safely_replace_file() {
	local target_filename="$1"
	local content
	content=$(cat -)
	# Datei mit identischem Inhalt existiert bereits - wir sind fertig
	[ -e "$target_filename" ] && echo "$content" | cmp -s "$target_filename" - && return 0
	# vorsichtiges Ersetzen: in dasselbe Verzeichnis schreiben und anschließend als atomare Operation umbenennen
	local tmp_filename="${target_filename}_new"
	# bei Abbruch die Loeschung der temporaeren Datei sicherstellen
	trap 'rm -f "$tmp_filename"' EXIT
	echo "$content" >"$tmp_filename"
	mv "$tmp_filename" "$target_filename"
	trap "" EXIT
}


update_crl() {
	local crl_name="$1"
	local base_url
	local content=
	for base_url in $BASE_URLS; do
		[ -n "$content" ] && break
		# shellcheck disable=SC2086
		content=$(curl $CURL_OPTIONS "$base_url/$crl_name")
	done
	[ -z "$content" ] && echo >&2 "Failed to download CRL '$crl_name'" && return 1
	local hash
	hash=$(echo "$content" | openssl crl -hash -noout)
	[ -z "$hash" ] && echo >&2 "Failed to parse hash of CRL '$crl_name'" && return 2
	echo "$content" | verify_crl "$hash" || return 1
	# die CRL-Daten sind korrekt - wir koennen sie schreiben
	# wir gehen hier davon aus, dass es keine Hash-Duplikate in unseren Zertifikaten gibt ("r0")
	echo "$content" | safely_replace_file "$CA_DIR/${hash}.r0"
}


is_interactive() {
	# pruefe ob stdin ein Terminal ist
	[ -t 0 ]
}


# alle CRLs aktualisieren
for crl_file in $CRL_LIST; do
	is_interactive && echo "Starting update of CRL '$crl_file'"
	# falls die Aktualisierung einer CRL fehlschlaegt, wollen wir trotzdem die anderen CRLs aktualisieren
	update_crl "$crl_file" || true
done
