#!/bin/sh -f

. cert-sh-functions
. ca-sh-functions

CA_SKO_INDIR="${CA_SKO_INDIR:-$CA_SKO_ROOT/in}"
CA_SKO_OUTDIR="${CA_SKO_OUTDIR:-$CA_SKO_ROOT/out}"

host_csr_dir()
{
	[ -n "$1" -a "$1" != "localhost" ] &&
		printf '%s/%s' "$CA_SKO_INDIR" "$1" ||
		printf '%s' "$SSL_CSRDIR"
}

host_cert_dir()
{
	[ -n "$1" -a "$1" != "localhost" ] &&
		printf '%s/%s' "$CA_SKO_OUTDIR" "$1" ||
		printf '%s' "$SSL_CERTDIR"
}

import_keys()
{
	local csr_dir cert_dir

	[ -n "$1" -a "$1" != "localhost" ] ||
		return 0

	csr_dir="$(host_csr_dir "$1")"
	cert_dir="$(host_cert_dir "$1")"

	rsync -qaH -e trust-ssh --delete-after --include='*.csr' --exclude='*' "$1:$SSL_CSRDIR/" "$csr_dir/" &&
		rsync -qaH -e trust-ssh --delete-after --include='*.cert' --include='*.pem' --exclude='*' "$1:$SSL_CERTDIR/" "$cert_dir/"
}

find_files()
{
	[ -n "$1" -a -n "$2" -a -n "$3" ] ||
		return 1

	find "$1" -mindepth 1 -maxdepth 1 -type "$3" -name "$2" -printf '%P\n'
}

show_keys()
{
	local SSL_CERTDIR="$(host_cert_dir "$@")"

	[ -d "$SSL_CERTDIR" ] ||
		return 1

	find_files "$SSL_CERTDIR" '*.cert' f |
		while IFS= read cert; do
			[ -f "$SSL_CSRDIR/${cert%.cert}.csr" ] ||
				continue
			ca_show_cert "$SSL_CERTDIR/$cert"
		done
}

forcesign_key()
{
	local host="${1:-localhost}"
	local SSL_CSRDIR="$(host_csr_dir "$host")"
	local SSL_CERTDIR="$(host_cert_dir "$host")"
	local ret base req cert status

	[ -d "$SSL_CSRDIR" ] ||
		return 1

	base="$2"
	req="$base.csr"
	cert="$base.cert"

	[ -f "$SSL_CERTDIR/$req" ] ||
		return 1

	[ -z "$CA_VERBOSE" ] ||
		printf '%s:%s: resigned\n' "$host" "$cert"
	ca_sign_req "$base"
}

sign_key()
{
	local host="${1:-localhost}"
	local SSL_CSRDIR="$(host_csr_dir "$host")"
	local SSL_CERTDIR="$(host_cert_dir "$host")"
	local ret base req cert status

	[ -d "$SSL_CSRDIR" ] ||
		return 1

	base="$2"
	req="$base.csr"
	cert="$base.cert"

	[ -f "$SSL_CERTDIR/$req" ] ||
		return 1

	ret=0
	ca_check_cert2 "$SSL_CERTDIR/$cert" || ret=$?

	status=
	if [ "$SSL_CSRDIR/$req" -nt "$SSL_CERTDIR/$cert" ]; then
		status="new sign request found"
	elif ca_needs_resign "$ret"; then
		status="$(ca_status_message "$ret")"
	fi

	if [ -n "$status" ]; then
		[ -z "$CA_VERBOSE" ] ||
			printf '%s:%s: %s, resigned\n' "$host" "$cert" "$status"
		ca_sign_req "$base"
	fi
}

sign_keys()
{
	local host="${1:-localhost}"
	local SSL_CSRDIR="$(host_csr_dir "$host")"
	local SSL_CERTDIR="$(host_cert_dir "$host")"

	[ -d "$SSL_CSRDIR" ] ||
		return 1

	find_files "$SSL_CSRDIR" '*.csr' f |
		while IFS= read req; do
			local base

			base="${req%.csr}"
			sign_key "$host" "$base"
		done
	ca_export_CAcert ca-root
}

export_keys()
{
	local csr_dir cert_dir

	if [ -z "$1" -o "$1" = "localhost" ]; then
		c_rehash "$SSL_CERTDIR" &>/dev/null
		return 0
	fi

	csr_dir="$(host_csr_dir "$1")"
	cert_dir="$(host_cert_dir "$1")"

	rsync -qaH -e trust-ssh --include='*.csr' --exclude='*' "$csr_dir/" "$1:$SSL_CSRDIR/" &&
		rsync -qaH -e trust-ssh --include='*.cert' --include='*.pem' --exclude='*' "$cert_dir/" "$1:$SSL_CSRDIR/" &&
		trust-ssh "$1" c_rehash "$SSL_CERTDIR" >/dev/null
}

update_host()
{
	import_keys "$@" &&
		sign_keys "$@" &&
		export_keys "$@"
}

each_host()
{
	local function list

	function="$1" && shift
	list=

	if [ "$#" -gt 0 ]; then
		while [ "$#" -gt 0 ]; do
			list="${list:+$list
}$1" && shift
		done
	else
		list="localhost
$(find_files "$CA_SKO_INDIR" '*' d)"
	fi

	printf '%s\n' "$list" |
		while IFS= read host; do
			"$function" "$host" ||
				printf '%s: Update failed.\n' "$host"
		done
}

cmd="$1" && shift

case "$cmd" in
	init)
		ca_make_CAdir
		ca_make_CAkey
		ca_make_CA
		ca_export_CAcert ca-root
		exit $?
		;;
	status)
		ca_check_CA ||
			exit $?
		cert="$CA_SKO_CADIR/cacert.pem"
		ca_show_cert "$cert"
		ca_check_cert2 "$cert"
		exit $?
		;;
	*)
		ca_check_CAdir >/dev/null 2>&1 &&
			ca_check_CAkey >/dev/null 2>&1 &&
			ca_check_CA >/dev/null 2>&1 ||
			ssl_fatal "CA not initialized"

		ret=0
		ca_check_cert2 "$CA_SKO_CADIR/cacert.pem" >/dev/null 2>&1 ||
			ret=$?

		if [ $ret != 0 ]; then
			if ca_needs_resign "$ret"; then
				ca_make_CA
			else
				ssl_fatal "$(ca_status_message "$ret")"
			fi
		fi
		;;
esac

ca_update_db

rc=0
case "$cmd" in
	drop)
		rm -rf "$CA_SKO_CADIR" ||: 2>/dev/null
		;;
	addhost)
		[ -n "$1" -a "$1" != "localhost" ] ||
			ssl_fatal "lolwhut?"

		host="$1"

		[ -d "$CA_SKO_INDIR/$host" ] || mkdir -p "$CA_SKO_INDIR/$host"
		[ -d "$CA_SKO_OUTDIR/$host" ] || mkdir -p "$CA_SKO_OUTDIR/$host"

		printf 'Added host:\n%s\n' "$hostkey"

		rc=$?
		;;
	delhost)
		[ -n "$1" -a "$1" != "localhost" ] ||
			ssl_fatal "lolwhut?"

		host="$1"

		rm -rf "$CA_SKO_INDIR/$host"
		rm -rf "$CA_SKO_OUTDIR/$host"
		rc=$?
		;;
	update)
		each_host update_host "$@"
		rc=$?
		;;
	import)
		each_host import_keys "$@"
		rc=$?
		;;
	sign)
		each_host sign_keys "$@"
		rc=$?
		;;
	export)
		each_host export_keys "$@"
		rc=$?
		;;
	show)
		show_keys "$@"
		rc=$?
		;;
	hosts)
		find_files "$CA_SKO_INDIR" '*' d
		rc=$?
		;;
	signkey)
		case "$#" in
			2)
				host="$1"
				key="$2"
				;;
			1)
				host="localhost"
				key="$1"
				;;
			*)
				ssl_fatal "Usage: ${0##*/} sign [HOST] NAME"
				;;
		esac
		sign_key "$host" "$key"
		rc=$?
		;;
	signfile)
		case "$#" in
			2)
				ca_sign_req2 "$1" "$2"
				rc=$?
				;;
			*)
				ssl_fatal "Usage: ${0##*/} sign REQUEST CERTIFICATE"
				;;
		esac
		;;
	check)
		[ -n "$1" ] ||
			ssl_fatal "Usage: ${0##*/} check CERTIFICATE"

		ca_check_cert "$1"
		rc=$?
		;;
	*)
		ssl_fatal "Invalid command: $cmd"
		;;
esac

exit $rc
# vim: set ts=4:
