#!/bin/sh

alterator_api_version=1
. alterator-sh-functions
. shell-signal
. shell-quote

cleanup_function()
{
	[ -z "$tmpdir" ] ||
		rm -rf -- "$tmpdir"
}

tmpdir="$(mktemp -dt "${0##*/}.XXXXXXXX")"
set_cleanup_handler cleanup_function

authorizedkeys_file=/etc/openssh/authorized_keys/root
known_hosts_file=/var/lib/alterator-trust/known_hosts
real_hosts_file="$tmpdir/real_hosts"
avail_hosts_file="$tmpdir/avail_hosts"

SSH1_RSA_KEY=/etc/openssh/ssh_host_key
SSH2_RSA_KEY=/etc/openssh/ssh_host_rsa_key
SSH2_DSA_KEY=/etc/openssh/ssh_host_dsa_key

ID_DSA_KEY=/var/lib/alterator-trust/id_dsa

fingerprint()
{
	local line="$1" && shift
	local field="${1:-2}"
	local tempfile="$tmpdir/fingerprint"

	echo "$line">"$tempfile"
	if ssh-keygen -l -f "$tempfile" >/dev/null 2>&1; then
		ssh-keygen -l -f "$tempfile" | cut -d ' ' -f "$field"
	fi
	rm -rf -- "$tempfile"
}

get_host_line()
{
	local host="$1"
	local file="$2"
	local line

	line="$(ssh-keygen -F "$host" -f "$file" | grep -v '^#')"
	[ -n "$line" ] || return 1
	printf '%s' "$line"
}

add_known_host()
{
	local host line

	host="$(hostinfo -1 -n "$1")" || return 1
	line="$(get_host_line "$host" "$real_hosts_file")" || return 3

	ssh-keygen -R "$host" -f "$known_hosts_file" 2>/dev/null ||:
	printf '%s\n' "$line" >>"$known_hosts_file"
	if ! trust-ssh "$host" /bin/true; then
		ssh-keygen -R "$host" -f "$known_hosts_file" 2>/dev/null
		return 4
	fi

	run-parts /usr/lib/alterator/hooks/trust.d "$host" add ||:
}

del_known_host()
{
	local host line

	host="$(hostinfo -1 -n "$1")" || return 1
	get_host_line "$host" "$known_hosts_file" >/dev/null 2>&1 || return 3

	run-parts /usr/lib/alterator/hooks/trust.d "$host" remove ||:

	ssh-keygen -R "$host" -f "$known_hosts_file" 2>/dev/null ||:
}

add_avail_host()
{
	local host

	host="$(hostinfo -1 -n "$1")" || return 1

	grep -qsFx -e "$host" "$avail_hosts_file" 2>/dev/null && return 2 ||:

	printf '%s\n' "$host" >>"$avail_hosts_file"
}

txt_record()
{
	echo "$2" |
		sed -n "s/\(^\|.*[[:space:]]\)\"$(quote_sed_regexp "$1")=\([^\"]*\)\".*/\2/p"
}

refresh_avail_hosts()
{
	local __ prefix ip txt role domain
	local localdomain="$(hostname -d)"

	:>"$avail_hosts_file"
	avahi-browse -prtk _server._tcp |
		while IFS=';' read prefix __ __ __ __ __ __ ip __ txt; do
			[ "$prefix" = "=" ] || continue
			role="$(txt_record role "$txt")"
			domain="$(txt_record domain "$txt")"
			[ "$role" = "slave" ] || continue
			[ "$domain" = "$localdomain" ] || continue
			add_avail_host "$ip"
		done
}

list_hosts()
{
	local __ fp host real_fp status msg newkey realkey

	:>"$real_hosts_file"
	if ssh-keygen -l -f "$known_hosts_file" >/dev/null 2>&1; then
		ssh-keygen -l -f "$known_hosts_file" |
			sort -t ' ' -k 3 |
			while IFS=' ' read -r __ fp host __; do
				status="managed"
				msg=
				newkey=
				realkey="$(ssh-keyscan "$host" 2>/dev/null)"
				real_fp="$(fingerprint "$realkey")"

				if [ -z "$real_fp" ]; then
					status="down"
					msg="$(_ "Host is down!")"
				elif [ "$fp" != "$real_fp" ]; then
					status="forged"
					msg="$(_ "Key does not match! New key:")"
					newkey="$real_fp"
				fi
				write_table_item \
					host "$host" \
					url "https://$host:8080/trust/slave" \
					status "trust/$status" \
					hostkey "$fp" \
					message "$msg" \
					realkey "$newkey"

				ssh-keygen -R "$host" -f "$real_hosts_file" 2>/dev/null
				printf '%s\n' "$realkey" >>"$real_hosts_file"
			done
	fi

	sort <"$avail_hosts_file" |
		while read -r host; do
			get_host_line "$host" "$real_hosts_file" >/dev/null 2>&1 && continue
			get_host_line "$host" "$known_hosts_file" >/dev/null 2>&1 && continue

			status="available"
			msg=
			realkey="$(ssh-keyscan "$host" 2>/dev/null)"
			real_fp="$(fingerprint "$realkey")"

			if [ -z "$real_fp" ]; then
				status="down"
				msg="$(_ "Host is down!")"
			fi
			write_table_item \
				host "$host" \
				url "https://$host:8080/trust/slave" \
				status "trust/$status" \
				hostkey "$real_fp" \
				message "$msg" \
				realkey ""

			ssh-keygen -R "$host" -f "$real_hosts_file" 2>/dev/null
			printf '%s\n' "$realkey" >>"$real_hosts_file"
		done
}

parse_authorized_keys()
{
	local line="$1" && shift
	local opts= keytype= key= comment=

	local tempfile="$tmpdir/parse_authorized_keys"
	printf '%s\n' "$line" >"$tempfile"
	case "$line" in
		ssh-*)
			IFS=' ' read keytype key comment <"$tempfile"
			;;
		*)
			IFS=' ' read opts keytype key comment <"$tempfile"
			;;
	esac

	case "$1" in
		options)
			printf '%s' "$opts"
			;;
		type)
			printf '%s' "$keytype"
			;;
		key)
			printf '%s' "$key"
			;;
		comment)
			printf '%s' "$comment"
			;;
		*) 
			printf '%s\t%s\t%s\t%s' "$opts" "$keytype" "$key" "$comment"
			;;
	esac

	rm -rf -- "$tempfile"
}

list_ssh_keys()
{
	local line comment comment_field

	while IFS=' ' read -r line; do
		comment="$(parse_authorized_keys "$line" comment)"
		case "$comment" in
			alterator-trust@*)
				write_string_param master_fp "$(fingerprint "$line")"
				write_string_param master_comment "$comment"
				return
				;;
		esac
	done <"$authorizedkeys_file"

	write_string_param master_fp "$(_ "not set")"
	write_string_param master_comment "$(_ "not set")"
}

del_ssh_key()
{
	local tempfile="$tmpdir/new_authorized_keys"
	local comment

	while IFS=' ' read -r line; do
		comment="$(parse_authorized_keys "$line" comment)"
		[ -n "$comment" -a -z "${comment##alterator-trust@*}" ] ||
			printf '%s\n' "$line"
	done <"$authorizedkeys_file" >"$tempfile"

	mv -f -- "$tempfile" "$authorizedkeys_file"
}

add_ssh_key()
{
	local new_line="$1" && shift
	local new_fp="$(fingerprint "$new_line")"
	local keytype key comment

	keytype="$(parse_authorized_keys "$new_line" type)"
	key="$(parse_authorized_keys "$new_line" key)"
	comment="$(parse_authorized_keys "$new_line" comment)"

	if [ -z "$new_fp" -o -z "$comment" -o -n "${comment##alterator-trust@*}" ];then
		write_error "$(_ "Invalid ssh key")"
		return
	fi

	while IFS=' ' read -r line; do
		local fp="$(fingerprint "$line")"
		if [ "$fp" = "$new_fp" ];then
			write_error "$(_ "Same ssh key already exists")"
			return
		fi
	done <"$authorizedkeys_file"

	del_ssh_key

	printf 'no-pty %s %s %s\n' "$keytype" "$key" "$comment" >>"$authorizedkeys_file"
}

generate_ssh_host_keys()
{
	rm -rf -- \
		"$SSH2_RSA_KEY" "$SSH2_RSA_KEY.pub" \
		"$SSH2_DSA_KEY" "$SSH2_DSA_KEY.pub" \
		"$SSH1_RSA_KEY" "$SSH1_RSA_KEY.pub"
	ssh-keygen -q -t rsa  -f "$SSH2_RSA_KEY" -C '' -N ''
	ssh-keygen -q -t dsa  -f "$SSH2_DSA_KEY" -C '' -N ''
	ssh-keygen -q -t rsa1 -f "$SSH1_RSA_KEY" -C '' -N ''

	service sshd condreload >/dev/null 2>&1
	refresh_avail_hosts
}

generate_ssh_key()
{
	rm -rf -- \
		"$ID_DSA_KEY" "$ID_DSA_KEY.pub"
	ssh-keygen -q -t dsa -f "$ID_DSA_KEY" -C "alterator-trust@$HOSTNAME" -N ''
}

trust_strerror()
{
	case $1 in
		1) printf "%s" "$(_ "unknown host")" ;;
		2) printf "%s" "$(_ "already exists")" ;;
		3) printf "%s" "$(_ "unable to get host key")" ;;
		4) printf "%s" "$(_ "can't connect (missing key?)")" ;;
	esac
}

touch "$known_hosts_file"
touch "$avail_hosts_file"

[ -s "$ID_DSA_KEY" ] ||
	generate_ssh_key

[ -s "$SSH1_RSA_KEY" -a -s "$SSH2_RSA_KEY" -a -s "$SSH2_DSA_KEY" ] ||
	generate_ssh_host_keys

refresh_avail_hosts

on_message()
{
	case "$in_action" in
		list)
			case "$in__objects" in
				hosts) list_hosts;;
			esac
			;;
		read)
			case "$in__objects" in
				master/trust-key.pub)
					[ -s "$ID_DSA_KEY.pub" ] &&
						write_string_param key "$ID_DSA_KEY.pub"
					;;
				master)
					[ -s "$ID_DSA_KEY.pub" ] &&
						write_string_param key_fp "$(ssh-keygen -l -f "$ID_DSA_KEY.pub" | cut -d ' ' -f 2)"
					;;
				slave)
					[ -s "$SSH2_RSA_KEY.pub" ] &&
						write_string_param hostkey_fp "$(ssh-keygen -l -f "$SSH2_RSA_KEY.pub" | cut -d ' ' -f 2)"
					list_ssh_keys
					;;
			esac
			;;
		write)
			if [ -n "$in_add_host_to_avail" ]; then
				add_avail_host "$in_new_host"
				err=$?
				[ $err = 0 ] ||
					write_error "$(printf "$(_ "Unable to add host %s: %s")" "$in_new_host" "$(trust_strerror $err)")"
			elif [ -n "$in_refresh_avail" ]; then
				refresh_avail_hosts
			elif [ -n "$in_pwn_host" ]; then
				(IFS=';'
				err_fmt="$(_ "Error adding host(s):%s")"
				err_msg=
				for i in $in_host; do
					[ -n "$i" ] &&
						add_known_host "$i"
					err=$?
					[ $err = 0 ] ||
						err_msg="$(printf "%s\n%s - %s" "$err_msg" "$i" "$(trust_strerror $err)")"
				done
				[ -z "$err_msg" ] ||
					write_error "$(printf "$err_fmt" "$err_msg")"
				)
			elif [ -n "$in_create_ssh_key" ]; then
				generate_ssh_key
			elif [ -n "$in_create_ssh_hostkey" ]; then
				generate_ssh_host_keys
			elif [ -n "$in_del_known_host" ]; then
				(IFS=';'
				err_fmt="$(_ "Error removing host(s):%s")"
				err_msg=
				for i in $in_host; do
					[ -n "$i" ] &&
						del_known_host "$i"
					err=$?
					[ $err = 0 ] ||
						err_msg="$(printf "%s\n%s - %s" "$err_msg" "$i" "$(trust_strerror $err)")"
				done
				[ -z "$err_msg" ] ||
					write_error "$(printf "$err_fmt" "$err_msg")"
				)
			elif [ -n "$in_add_ssh_key" ]; then
				add_ssh_key "$in_key_file"
			elif [ -n "$in_del_ssh_key" ]; then
				del_ssh_key
			fi
			;;
	esac
}

message_loop
