#!/bin/sh /etc/rc.common

START=20
USE_PROCD=1
run_dir=/var/run/acme
export CHALLENGE_DIR=$run_dir/challenge
export CERT_DIR=/etc/ssl/acme
LAST_LISTEN_PORT=
NFT_HANDLE=
HOOK=/usr/lib/acme/hook
LOG_TAG=acme

# shellcheck source=net/acme/files/functions.sh
. "$IPKG_INSTROOT/usr/lib/acme/functions.sh"

extra_command "abort" "Abort running certificate issuances/renewals"
extra_command "renew" "Run certificate issuances/renewals"

delete_nft_rule() {
	if [ "$NFT_HANDLE" ]; then
		# $NFT_HANDLE contains the string 'handle XX' so pass it unquoted to nft
		nft delete rule inet fw4 input $NFT_HANDLE
		NFT_HANDLE=
	fi
}

cleanup() {
	log debug "cleaning up"
	delete_nft_rule
}

load_options() {
	section=$1

	config_get staging "$section" staging
	# compatibility for old option name
	if [ -z "$staging" ]; then
		config_get_bool staging "$section" use_staging 0
	fi
	procd_append_param env staging="$staging"
	config_get calias "$section" calias
	procd_append_param env calias="$calias"
	config_get dalias "$section" dalias
	procd_append_param env dalias="$dalias"
	config_get domains "$section" domains
	procd_append_param env domains="$domains"
	main_domain="$(first_arg $domains)"
	procd_append_param env main_domain="$main_domain"
	config_get keylength "$section" keylength
	if [ "$keylength" ]; then
		log warn "Option \"keylength\" is deprecated, please use key_type (e.g., ec256, rsa2048) instead."
		case $keylength in
		ec-*) key_type=${keylength/-/} ;;
		*) key_type=rsa$keylength ;;
		esac
	else
		config_get key_type "$section" key_type ec256
	fi
	procd_append_param env key_type="$key_type"
	config_get acme_server "$section" acme_server
	procd_append_param env acme_server="$acme_server"
	config_get days "$section" days
	procd_append_param env days="$days"
	config_get cert_profile "$section" cert_profile
	procd_append_param env cert_profile="$cert_profile"
	config_get dns_wait "$section" dns_wait
	procd_append_param env dns_wait="$dns_wait"
	config_get webroot "$section" webroot
	if [ "$webroot" ]; then
		log warn "Option \"webroot\" is deprecated, please remove it and change your web server's config so it serves ACME challenge requests from $CHALLENGE_DIR."
		CHALLENGE_DIR=$webroot
	fi
}

first_arg() {
	echo "$1"
}

get_cert() {
	section=$1

	config_get_bool enabled "$section" enabled 1
	[ "$enabled" = 1 ] || return

	# load `listen_port` here rather than in `load_options` so we can
	# return early without leaving a dangling `procd_open_instance`; the
	# check requires loading `validation_method` as well, which in turn
	# requires loading `dns` and `standalone`
	config_get validation_method "$section" validation_method
	config_get dns "$section" dns
	config_get standalone "$section" standalone
	[ -n "$standalone" ] && log warn "Option \"standalone\" is deprecated."
	# if validation_method isn't set then guess it
	if [ -z "$validation_method" ]; then
		if [ -n "$dns" ]; then
			validation_method="dns"
		elif [ "$standalone" = 1 ]; then
			validation_method="standalone"
		else
			validation_method="webroot"
		fi
		log warn "Please set \"option validation_method $validation_method\"."
	fi
	if [ "$validation_method" = "webroot" ]; then
		mkdir -p "$CHALLENGE_DIR"
	fi
	case "$validation_method" in
	standalone)
		config_get listen_port "$section" listen_port 80
		;;
	alpn)
		config_get listen_port "$section" listen_port 443
		;;
	*)
		config_get listen_port "$section" listen_port
		;;
	esac
	if [ "$listen_port" != "$LAST_LISTEN_PORT" ]; then
		delete_nft_rule

		if [ "$listen_port" ]; then
			if ! NFT_HANDLE=$(nft -a -e insert rule inet fw4 input tcp dport "$listen_port" counter accept comment ACME | grep -o 'handle [0-9]\+'); then
				return 1
			fi
			log debug "added nft rule: $NFT_HANDLE"
		fi

		LAST_LISTEN_PORT="$listen_port"
	fi

	procd_open_instance "$section"
	procd_set_param command "$HOOK" get
	procd_set_param stdout 1
	procd_set_param stderr 1
	procd_set_param env CHALLENGE_DIR="$CHALLENGE_DIR" CERT_DIR="$CERT_DIR"
	procd_append_param env account_email="$account_email" state_dir="$state_dir" debug="$debug"
	procd_append_param env dns="$dns" validation_method="$validation_method" listen_port="$listen_port"

	load_options "$section"

	load_credentials() {
		# use `eval` to correctly strip quotes around credential values
		eval procd_append_param env "$1"
	}
	config_list_foreach "$section" credentials load_credentials

	procd_close_instance
}

load_globals() {
	[ -z "$account_email" ] || return 1  # only read the first acme section

	section=$1

	config_get account_email "$section" account_email
	if [ -z "$account_email" ]; then
		log err "account_email option is required"
		exit 1
	fi
	export account_email

	config_get state_dir "$section" state_dir
	if [ "$state_dir" ]; then
		log warn "Option \"state_dir\" is deprecated, please remove it. Certificates now exist in $CERT_DIR."
		mkdir -p "$state_dir"
	else
		state_dir=/etc/acme
	fi
	export state_dir

	config_get_bool debug "$section" debug 0
	export debug
}

start_service() {
	grep -q '/etc/init.d/acme' /etc/crontabs/root 2>/dev/null || {
		echo "0 0 * * * /etc/init.d/acme renew" >>/etc/crontabs/root
	}
}

service_started() {
	echo 'Nightly certificate renewal enabled. To renew now, run `service acme renew`.'
}

stop_service() {
	sed -i '\|/etc/init.d/acme|d' /etc/crontabs/root
	running && stop_aborted="Running certificate renewal(s) aborted and a"
}

service_stopped() {
	if enabled; then
		untilboot=' until next boot. To disable permanently, run `service acme disable`'
	fi
	echo "${stop_aborted:-A}utomatic nightly renewal disabled$untilboot."
	echo 'To re-enable nightly renewal, run `service acme start`. To issue/renew now, run `service acme renew`.'
}

service_triggers() {
	procd_add_config_trigger config.change acme \
		/etc/init.d/acme renew
}

load_and_run() {
	trap cleanup EXIT

	config_load acme
	config_foreach load_globals acme

	config_foreach get_cert cert
}

renew() {
	echo "Starting certificate issuance/renewal in the background; see system log for progress."
	echo 'Issuances/renewals can be aborted with `service acme abort`.'
	rc_procd load_and_run
}

abort() {
	procd_lock
	if running "$@"; then
		procd_kill "$(basename ${basescript:-$initscript})" "$1"
		echo "Aborting certificate issuance(s)/renewal(s); see system log for confirmation."
	elif [ -z "$1" ]; then
		echo "No certificate issuances/renewals running to abort!"
		exit 1
	else
		echo "No certificate issuance/renewal \"$1\" running to abort!"
		exit 1
	fi
}
