Skip to content

Commit

Permalink
openssh: add UCI support
Browse files Browse the repository at this point in the history
* update init script to handle sshd UCI by generating config file
  and passing that config file to the sshd daemon
* add a default sshd config
* add uci-default script that tries to migrate dropbear config
  and authorized_keys file to sshd

Signed-off-by: Mohd Husaam Mehdi <husaam.mehdi@iopsys.eu>
  • Loading branch information
mhusaam committed May 13, 2024
1 parent 42140c6 commit c3ea745
Show file tree
Hide file tree
Showing 4 changed files with 442 additions and 11 deletions.
4 changes: 4 additions & 0 deletions net/openssh/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -232,10 +232,14 @@ define Package/openssh-server/install
sed -r -i 's,^#(HostKey /etc/ssh/ssh_host_(rsa|ed25519)_key)$$$$,\1,' $(1)/etc/ssh/sshd_config
$(INSTALL_DIR) $(1)/etc/init.d
$(INSTALL_BIN) ./files/sshd.init $(1)/etc/init.d/sshd
$(INSTALL_DIR) $(1)/etc/config
$(INSTALL_BIN) ./files/sshd.config $(1)/etc/config/sshd
$(INSTALL_DIR) $(1)/lib/preinit
$(INSTALL_BIN) ./files/sshd.failsafe $(1)/lib/preinit/99_10_failsafe_sshd
$(INSTALL_DIR) $(1)/usr/sbin
$(INSTALL_BIN) $(PKG_INSTALL_DIR)/usr/sbin/sshd $(1)/usr/sbin/
$(INSTALL_DIR) $(1)/etc/uci-defaults
$(INSTALL_DATA) ./files/sshd.ucidefault $(1)/etc/uci-defaults/99-generate-sshd-config
endef

define Package/openssh-server-pam/install
Expand Down
6 changes: 6 additions & 0 deletions net/openssh/files/sshd.config
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
config sshd
option enable '1'
option Port '22'
option RootLogin '1'
option PasswordAuth '1'
option RootPasswordAuth '1'
293 changes: 282 additions & 11 deletions net/openssh/files/sshd.init
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,264 @@ STOP=50

USE_PROCD=1
PROG=/usr/sbin/sshd
NAME=sshd
BASECONFIGFILE="/var/etc/sshd.conf"

start_service() {
. /lib/functions.sh
. /lib/functions/network.sh

validate_section_sshd()
{
uci_load_validate sshd sshd "$1" "$2" \
'AllowUsers:list(string)' \
'BannerFile:file' \
'Ciphers:list(string)' \
'ClientAliveCountMax:uinteger' \
'ClientAliveInterval:uinteger' \
'enable:bool:1' \
'enabled:bool:1' \
'GatewayPorts:bool:0' \
'HostKeyAlgorithms:list(string)' \
'HostKeyFiles:list(file)' \
'IdleTimeout:uinteger:0' \
'Interface:string' \
'KexAlgorithms:list(string)' \
'MacAlgorithms:list(string)' \
'MaxAuthTries:uinteger:6' \
'mdns:bool:1' \
'PasswordAuth:bool:1' \
'Port:port:22' \
'RootLogin:bool:1' \
'RootPasswordAuth:bool:1'
}

append_config()
{
local Param="${1}"
local Value="${2}"

if [ -n "${Param}" ]; then
echo "${Param} ${Value}" >> "${TEMPCONF}"
fi
}

set_option_list_comma()
{
local OptionName="${1}"
local Values="${2}"
local ValueList=""

for Value in $Values; do
# if this is the first iteration, then ValueList
# should be empty, and we only want to add a
# comma if there are multiple values,
# this is to avoid extra comma in the end
if [ -z "${ValueList}" ]; then
ValueList="${Value}"
else
ValueList="${Value},${ValueList}"
fi
done

append_config "${OptionName}" "${ValueList}"
}

set_option_list_space()
{
local OptionName="${1}"
local Values="${2}"
local ValueList=""

for Value in $Values; do
ValueList="${Value} ${ValueList}"
done

append_config "${OptionName}" "${ValueList}"
}

set_gateway_ports()
{
local GatewayPorts="${1}"

if [ -n "${GatewayPorts}" ]; then
if [ "${GatewayPorts}" -eq 0 ]; then
append_config "GatewayPorts" "no"
else
append_config "GatewayPorts" "yes"
fi
fi
}

# because sshd does not have an option for specifying an interface
# but only for specifying listen address
# we get the addresses of interface and add them
set_listen_addresses()
{
local Port="${1}"
local IpAddrs="${2}"

[ "${Port}" -gt 0 ] && append_config "Port" "${Port}"

for Addr in $IpAddrs; do
append_config "ListenAddress" "${Addr}"
done
}

set_idle_timeout()
{
local IdleTimeout="${1}"

# from https://www.man7.org/linux/man-pages/man5/sshd_config.5.html
# ClientAliveCountMax:
# The default value is 3. If ClientAliveInterval is set to
# 15, and ClientAliveCountMax is left at the default,
# unresponsive SSH clients will be disconnected after
# approximately 45 seconds. Setting a zero
# ClientAliveCountMax disables connection termination.
#
# Therefore, to mimic IdleTimeout from dropbear, we set
# ClientAliveCountMax to 1 and ClientAliveInterval to
# the time we want to wait
if [ "${IdleTimeout}" -ne 0 ]; then
append_config "ClientAliveCountMax" "1"
append_config "ClientAliveInterval" "${IdleTimeout}"
fi
}

publish_ssh_service_over_mdns()
{
local mdns="${1}"
local Port="${2}"

if [ "${mdns}" -ne 0 ] && [ "${Port}" -gt 0 ]; then
procd_add_mdns "ssh" "tcp" "${Port}" "daemon=sshd"
fi
}

set_root_login()
{
local RootLogin="${1}"
local RootPasswordAuth="${2}"

if [ "${RootLogin}" -eq 0 ]; then
append_config "PermitRootLogin" "no"
else
if [ "${RootPasswordAuth}" -eq 0 ]; then
append_config "PermitRootLogin" "prohibit-password"
else
append_config "PermitRootLogin" "yes"
fi
fi
}

set_password_auth()
{
local PasswordAuth="${1}"

if [ "${PasswordAuth}" -eq 0 ]; then
append_config "PasswordAuthentication" "no"
else
append_config "PasswordAuthentication" "yes"
fi
}

set_params()
{
local ConfigFile="${1}"

# start fresh
rm -rf "${TEMPCONF}"
touch "${TEMPCONF}"

[ -n "${AllowUsers}" ] && set_option_list_space "AllowUsers" "${AllowUsers}"
[ -n "${BannerFile}" ] && append_config "Banner" "${BannerFile}"
[ -n "${Ciphers}" ] && set_option_list_comma "Ciphers" "${Ciphers}"
[ -n "${ClientAliveCountMax}" ] && append_config "ClientAliveCountMax" "${ClientAliveCountMax}"
[ -n "${ClientAliveInterval}" ] && append_config "ClientAliveInterval" "${ClientAliveInterval}"
[ -n "${HostKeyAlgorithms}" ] && set_option_list_comma "HostKeyAlgorithms" "${HostKeyAlgorithms}"
[ -n "${HostKeyFiles}" ] && set_option_list_comma "HostKey" "${HostKeyFiles}"
[ -n "${KexAlgorithms}" ] && set_option_list_comma "KexAlgorithms" "${KexAlgorithms}"
[ -n "${MacAlgorithms}" ] && set_option_list_comma "MACs" "${MacAlgorithms}"
[ "${MaxAuthTries}" -gt 0 ] && append_config "MaxAuthTries" "${MaxAuthTries}"

# these require a little handling
set_gateway_ports "${GatewayPorts}"
set_listen_addresses "${Port}" "${IpAddrs}"
set_password_auth "${PasswordAuth}"
set_root_login "${RootLogin}" "${RootPasswordAuth}"

# if ClientAliveCountMax or ClientAliveInterval are set explicitly
# then IdleTimeout cannot be used because IdleTimeout also uses them
# IdleTimeout is present in dropbear and not in sshd anyways
# so user can go with dropbear style (IdleTimeout) or sshd style (explicit)
[ -z "${ClientAliveCountMax}" ] && [ -z "${ClientAliveInterval}" ] && set_idle_timeout "${IdleTimeout}"

# finalize
mv -f "${TEMPCONF}" "${ConfigFile}"
}

sshd_instance()
{
local IpAddrs
local Cfg="$1"
local ValidationResult="${2}"

[ "${ValidationResult}" = 0 ] || {
echo "validation failed"
return 1
}

# user can use either of enable or enabled
[ "${enable}" -eq 0 ] || [ "${enabled}" -eq 0 ] && return 0

[ -n "${Interface}" ] && {
network_get_ipaddrs_all IpAddrs "${Interface}" || {
echo "interface ${Interface} has no physdev or physdev has no suitable ip"
return 1
}
}

local PidFile="/var/run/${NAME}.${Cfg}.pid"
# ConfigFile is BASECONFIGFILE + uci section name
local ConfigFile="${BASECONFIGFILE}.${Cfg}"
# global
TEMPCONF="${ConfigFile}.tmp"

# create directory if not present
mkdir -p $(dirname $ConfigFile)

# create config file
set_params "$ConfigFile"

# set up procd instance
procd_open_instance $Cfg
procd_set_param command $PROG -D
procd_set_param file "$ConfigFile"
procd_append_param command -o "PidFile $PidFile"

# pass config to daemon
procd_append_param command -f "$ConfigFile"

# announce mdns service
publish_ssh_service_over_mdns "${mdns}" "${Port}"

procd_set_param respawn
procd_close_instance
}

# for adding trigger
load_interfaces()
{
config_get Interface "$1" Interface
config_get_bool enable "$1" enable 1
config_get_bool enabled "$1" enabled 1

# user can use either of enable or enabled
[ "${enable}" = "1" ] && [ "${enabled}" = "1" ] && Interfaces=" ${Interface} ${Interfaces}"
}

start_service()
{
for type in rsa ed25519
do
# check for keys
Expand All @@ -20,19 +276,34 @@ start_service() {
}
done
mkdir -m 0700 -p /var/empty
mkdir -m 0700 -p /root/.ssh

local lport=$(awk '/^Port / { print $2; exit }' /etc/ssh/sshd_config)
[ -z "$lport" ] && lport=22
config_load "${NAME}"
config_foreach validate_section_sshd sshd sshd_instance
}

procd_open_instance
procd_add_mdns "ssh" "tcp" "$lport"
procd_set_param command $PROG -D
procd_set_param respawn
procd_close_instance
reload_service()
{
rc_procd start_service "$@"
procd_send_signal sshd "$@"
}

reload_service() {
procd_send_signal sshd
service_triggers()
{
local Interfaces

procd_add_config_trigger "config.change" "sshd" /etc/init.d/sshd reload

config_load "${NAME}"
config_foreach load_interfaces sshd

[ -n "${Interfaces}" ] && {
for n in $Interfaces ; do
procd_add_interface_trigger "interface.*" $n /etc/init.d/sshd reload
done
}

procd_add_validation validate_section_sshd
}

shutdown() {
Expand All @@ -44,6 +315,6 @@ shutdown() {
for pid in $(pidof sshd)
do
[ "$pid" = "$$" ] && continue
[ -e "/proc/$pid/stat" ] && kill $pid
[ -e "/proc/$pid/stat" ] && kill "$pid"
done
}
Loading

0 comments on commit c3ea745

Please sign in to comment.