#!/usr/bin/talon-run

# Talon service for the OpenBSD Secure Shell server.
#
# This file is intentionally POSIX /bin/sh. talon-run supplies the lifecycle
# helpers, dependency declarations, status messages, RC_SVCNAME, RC_CMD, and
# RC_RUNLEVEL before sourcing it.

description="OpenBSD Secure Shell server"
description_checkconfig="Verify configuration file"
description_reload="Reload configuration"

extra_commands="checkconfig"
extra_started_commands="reload"

# Preserve OpenSSH's historical environment overrides while preferring the
# shorter variables used by /etc/taloninit/conf.d/sshd.
: "${sshd_disable_keygen:=${SSHD_DISABLE_KEYGEN:-no}}"
: "${cfgfile:=${SSHD_CONFIG:-${SSHD_CONFDIR:-/etc/ssh}/sshd_config}}"
: "${pidfile:=${SSHD_PIDFILE:-/run/sshd.pid}}"
: "${command:=${SSHD_BINARY:-/usr/sbin/sshd}}"
: "${command_args:=${SSHD_OPTS:-}}"

required_files="$cfgfile"

generate_host_key_type() {
	key_type=$1
	bit_size=

	case $key_type in
		ecdsa) bit_size=${ecdsa_bit_size:-} ;;
		rsa) bit_size=${rsa_bit_size:-} ;;
	esac

	if [ ! -f "/etc/ssh/ssh_host_${key_type}_key" ]; then
		einfo "Generating $key_type SSH host key..."
		if [ -n "$bit_size" ]; then
			ssh-keygen -q -f "/etc/ssh/ssh_host_${key_type}_key" -N '' \
				-t "$key_type" -b "$bit_size" || return 1
		else
			ssh-keygen -q -f "/etc/ssh/ssh_host_${key_type}_key" -N '' \
				-t "$key_type" || return 1
		fi
	fi
}

generate_host_keys() {
	if [ -z "${key_types_to_generate:-}" ] &&
	   [ -z "${ecdsa_bit_size:-}" ] && [ -z "${rsa_bit_size:-}" ]; then
		ssh-keygen -A
		return
	fi

	for key_type in ${key_types_to_generate:-dsa ecdsa ed25519 rsa}; do
		generate_host_key_type "$key_type" || return 1
	done
}

print_config() {
	"$command" -f "$cfgfile" -G 2>/dev/null
}

match_config() {
	print_config | grep -iqxE -e "$1"
}

print_listen_addresses() {
	print_config |
		awk '$1 == "listenaddress" && $2 !~ /^(0.0.0.0|\[::\])/ { print $2 }'
}

depend() {
	use logger dns
	after entropy

	if [ -n "${TALON_NEED:-}" ]; then
		# An administrator can set TALON_NEED="net-wired ..." when sshd has
		# explicit interface requirements that Talon cannot infer by name.
		need $TALON_NEED
	else
		listen_addresses=$(print_listen_addresses)
		if [ -n "$listen_addresses" ]; then
			need net
			ewarn "sshd binds explicit ListenAddress entries."
			ewarn "Set TALON_NEED to the service providing these addresses:"
			for listen_address in $listen_addresses; do
				ewarn "  ${listen_address%:*}"
			done
		fi
	fi
}

update_command() {
	if [ -x /usr/sbin/sshd.krb5 ] &&
	   command=/usr/sbin/sshd.krb5 match_config "(Kerberos|GSSAPI)Authentication yes"; then
		command=${SSHD_BINARY:-/usr/sbin/sshd.krb5}
	elif [ -x /usr/sbin/sshd.pam ] &&
	     command=/usr/sbin/sshd.pam match_config "UsePAM yes"; then
		command=${SSHD_BINARY:-/usr/sbin/sshd.pam}
	fi
}

checkconfig() {
	update_command
	warn_deprecated_var SSHD_BINARY
	warn_deprecated_var SSHD_CONFDIR
	warn_deprecated_var SSHD_CONFIG cfgfile
	warn_deprecated_var SSHD_DISABLE_KEYGEN sshd_disable_keygen
	warn_deprecated_var SSHD_OPTS command_args
	warn_deprecated_var SSHD_PIDFILE pidfile

	if ! yesno "$sshd_disable_keygen"; then
		generate_host_keys || return 1
	fi

	case " $command_args " in
		*" -o PidFile="*) ;;
		*) [ "$pidfile" = /run/sshd.pid ] ||
			command_args="$command_args -o PidFile=$pidfile" ;;
	esac
	case " $command_args " in
		*" -f "*) ;;
		*) [ "$cfgfile" = /etc/ssh/sshd_config ] ||
			command_args="$command_args -f $cfgfile" ;;
	esac

	# command_args is a configuration-provided POSIX argument list.
	"$command" -t $command_args
}

start_pre() {
	checkconfig
}

stop_pre() {
	update_command
	if [ "$RC_CMD" = restart ]; then
		checkconfig || return 1
	fi
}

stop_post() {
	if [ "$RC_RUNLEVEL" = shutdown ]; then
		sshd_session_pids=$(pgrep "sshd-session:" 2>/dev/null || :)
		if [ -n "$sshd_session_pids" ]; then
			ebegin "Shutting down SSH connections"
			# The unquoted list is intentionally one signal target per PID.
			kill -TERM $sshd_session_pids >/dev/null 2>&1
			eend $?
		fi
	fi
}

reload() {
	checkconfig || return 1
	if [ ! -s "$pidfile" ]; then
		eerror "sshd is not running: no pidfile at $pidfile"
		return 1
	fi

	ebegin "Reloading $RC_SVCNAME"
	kill -HUP "$(cat "$pidfile")"
	eend $?
}

warn_deprecated_var() {
	varname=$1
	replacement=${2:-}

	eval "test -n \"\${$varname:-}\"" || return 0
	ewarn "Variable \$$varname is deprecated and will be removed in the future!"
	[ -z "$replacement" ] || ewarn "Use \$$replacement instead of \$$varname."
}
