ASR_BASE
Change-Id: Icf3719cc0afe3eeb3edc7fa80a2eb5199ca9dda1
diff --git a/package/network/services/dropbear/files/dropbear.config b/package/network/services/dropbear/files/dropbear.config
new file mode 100644
index 0000000..7eb5975
--- /dev/null
+++ b/package/network/services/dropbear/files/dropbear.config
@@ -0,0 +1,7 @@
+# See https://openwrt.org/docs/guide-user/base-system/dropbear
+config dropbear main
+ option enable '1'
+ option PasswordAuth 'on'
+ option RootPasswordAuth 'on'
+ option Port '22'
+# option BannerFile '/etc/banner'
diff --git a/package/network/services/dropbear/files/dropbear.defaults b/package/network/services/dropbear/files/dropbear.defaults
new file mode 100644
index 0000000..e679bee
--- /dev/null
+++ b/package/network/services/dropbear/files/dropbear.defaults
@@ -0,0 +1,20 @@
+[ ! -s /etc/dropbear/authorized_keys ] || exit 0
+
+. /usr/share/libubox/jshn.sh
+
+json_init
+json_load "$(cat /etc/board.json)"
+json_select credentials
+ json_get_keys keys ssh_authorized_keys
+ [ -z "$keys" ] || {
+ touch /etc/dropbear/authorized_keys
+ uci set dropbear.@dropbear[-1].PasswordAuth='off'
+ uci set dropbear.@dropbear[-1].RootPasswordAuth='off'
+ }
+ json_select ssh_authorized_keys
+ for key in $keys; do
+ json_get_var val "$key"
+ echo "$val" >> /etc/dropbear/authorized_keys
+ done
+ json_select ..
+json_select ..
diff --git a/package/network/services/dropbear/files/dropbear.failsafe b/package/network/services/dropbear/files/dropbear.failsafe
new file mode 100755
index 0000000..417265b
--- /dev/null
+++ b/package/network/services/dropbear/files/dropbear.failsafe
@@ -0,0 +1,61 @@
+#!/bin/sh
+
+_dropbear()
+{
+ /usr/sbin/dropbear "$@" </dev/null >/dev/null 2>&1
+}
+
+_dropbearkey()
+{
+ /usr/bin/dropbearkey "$@" </dev/null >/dev/null 2>&1
+}
+
+_ensurekey()
+{
+ _dropbearkey -y -f "$1" && return
+ rm -f "$1"
+ _dropbearkey -f "$@" || {
+ rm -f "$1"
+ return 1
+ }
+}
+
+ktype_all='ed25519 ecdsa rsa'
+
+failsafe_dropbear () {
+ local kargs kcount ktype tkey
+
+ kargs=
+ kcount=0
+ for ktype in ${ktype_all} ; do
+ tkey="/tmp/dropbear_failsafe_${ktype}_host_key"
+
+ case "${ktype}" in
+ ed25519) _ensurekey "${tkey}" -t ed25519 ;;
+ ecdsa) _ensurekey "${tkey}" -t ecdsa -s 256 ;;
+ rsa) _ensurekey "${tkey}" -t rsa -s 1024 ;;
+ *)
+ echo "unknown key type: ${ktype}" >&2
+ continue
+ ;;
+ esac
+
+ [ -s "${tkey}" ] || {
+ rm -f "${tkey}"
+ continue
+ }
+
+ chmod 0400 "${tkey}"
+ kargs="${kargs}${kargs:+ }-r ${tkey}"
+ kcount=$((kcount+1))
+ done
+
+ [ "${kcount}" != 0 ] || {
+ echo 'DROPBEAR IS BROKEN' >&2
+ return 1
+ }
+
+ _dropbear ${kargs}
+}
+
+boot_hook_add failsafe failsafe_dropbear
diff --git a/package/network/services/dropbear/files/dropbear.init b/package/network/services/dropbear/files/dropbear.init
new file mode 100755
index 0000000..395237f
--- /dev/null
+++ b/package/network/services/dropbear/files/dropbear.init
@@ -0,0 +1,474 @@
+#!/bin/sh /etc/rc.common
+# Copyright (C) 2006-2010 OpenWrt.org
+# Copyright (C) 2006 Carlos Sobrinho
+
+START=19
+STOP=50
+
+USE_PROCD=1
+PROG=/usr/sbin/dropbear
+NAME=dropbear
+PIDCOUNT=0
+
+extra_command "killclients" "Kill ${NAME} processes except servers and yourself"
+
+# most of time real_stat() will be failing
+# due to missing "stat" binary (by default)
+real_stat() { env stat -L "$@" 2>/dev/null ; }
+dumb_stat() { ls -Ldln "$1" | tr -s '\t ' ' ' ; }
+stat_perm() { real_stat -c '%A' "$1" || dumb_stat "$1" | cut -d ' ' -f 1 ; }
+stat_owner() { real_stat -c '%u' "$1" || dumb_stat "$1" | cut -d ' ' -f 3 ; }
+
+_dropbearkey()
+{
+ /usr/bin/dropbearkey "$@" </dev/null >/dev/null 2>&1
+}
+
+# $1 - file name (host key or config)
+file_verify()
+{
+ [ -f "$1" ] || return 1
+ # checking file ownership
+ [ "$(stat_owner "$1")" = "0" ] || {
+ chown 0 "$1"
+ [ "$(stat_owner "$1")" = "0" ] || return 2
+ }
+ # checking file permissions
+ [ "$(stat_perm "$1")" = "-rw-------" ] || {
+ chmod 0600 "$1"
+ [ "$(stat_perm "$1")" = "-rw-------" ] || return 3
+ }
+ # file is host key or not?
+ # if $2 is empty string - file is "host key"
+ # if $2 is non-empty string - file is "config"
+ [ -z "$2" ] || return 0
+ # checking file contents (finally)
+ [ -s "$1" ] || return 4
+ _dropbearkey -y -f "$1" || return 5
+ return 0
+}
+
+# $1 - file_verify() return code
+file_errmsg()
+{
+ case "$1" in
+ 0) ;;
+ 1) echo "file does not exist" ;;
+ 2) echo "file has wrong owner (must be owned by root)" ;;
+ 3) echo "file has wrong permissions (must not have group/other write bit)" ;;
+ 4) echo "file has zero length" ;;
+ 5) echo "file is not valid host key or not supported" ;;
+ *) echo "unknown error" ;;
+ esac
+}
+
+# $1 - config option
+# $2 - host key file name
+hk_config()
+{
+ local x m
+ file_verify "$2" ; x=$?
+ if [ "$x" = 0 ] ; then
+ procd_append_param command -r "$2"
+ return
+ fi
+ m=$(file_errmsg "$x")
+ logger -s -t "${NAME}" -p daemon.warn \
+ "Option '$1', skipping '$2': $m"
+}
+
+# $1 - host key file name
+hk_config__keyfile() { hk_config keyfile "$1" ; }
+
+ktype_all='ed25519 ecdsa rsa'
+
+hk_generate_as_needed()
+{
+ local hk_cfg_dir kgen ktype kfile hk_tmp_dir
+ hk_cfg_dir='/etc/dropbear'
+
+ [ -d "${hk_cfg_dir}" ] || mkdir -p "${hk_cfg_dir}"
+
+ kgen=
+ for ktype in ${ktype_all} ; do
+ kfile="${hk_cfg_dir}/dropbear_${ktype}_host_key"
+
+ if file_verify "${kfile}" ; then continue ; fi
+
+ kgen="${kgen}${kgen:+ }${ktype}"
+ done
+
+ # all keys are sane?
+ [ -n "${kgen}" ] || return 0
+
+ hk_tmp_dir=$(mktemp -d)
+ # system in bad state?
+ [ -n "${hk_tmp_dir}" ] || return 1
+
+ chmod 0700 "${hk_tmp_dir}"
+
+ for ktype in ${kgen} ; do
+ kfile="${hk_tmp_dir}/dropbear_${ktype}_host_key"
+
+ if ! _dropbearkey -t ${ktype} -f "${kfile}" ; then
+ # unsupported key type
+ rm -f "${kfile}"
+ continue
+ fi
+
+ chmod 0600 "${kfile}"
+ done
+
+ kgen=
+ for ktype in ${ktype_all} ; do
+ kfile="${hk_tmp_dir}/dropbear_${ktype}_host_key"
+
+ [ -s "${kfile}" ] || continue
+
+ kgen="${kgen}${kgen:+ }${ktype}"
+ done
+
+ if [ -n "${kgen}" ] ; then
+ for ktype in ${kgen} ; do
+ kfile="${hk_tmp_dir}/dropbear_${ktype}_host_key"
+ [ -s "${kfile}" ] || continue
+ mv -f "${kfile}" "${hk_cfg_dir}/"
+ done
+ fi
+
+ rm -rf "${hk_tmp_dir}"
+
+ # cleanup empty files
+ for ktype in ${ktype_all} ; do
+ kfile="${hk_cfg_dir}/dropbear_${ktype}_host_key"
+
+ [ -s "${kfile}" ] || rm -f "${kfile}"
+ done
+}
+
+# $1 - list with whitespace-separated elements
+normalize_list()
+{
+ printf '%s' "$1" | tr -s ' \r\n\t' ' ' | sed -E 's/^ //;s/ $//'
+}
+
+warn_multiple_interfaces()
+{
+ logger -t "${NAME}" -p daemon.warn \
+ "Option '$1' should specify SINGLE interface but instead it lists interfaces: $2"
+ logger -t "${NAME}" -p daemon.warn \
+ "Consider creating per-interface instances instead!"
+}
+
+validate_section_dropbear()
+{
+ uci_load_validate dropbear dropbear "$1" "$2" \
+ 'PasswordAuth:bool:1' \
+ 'enable:bool:1' \
+ 'DirectInterface:string' \
+ 'Interface:string' \
+ 'GatewayPorts:bool:0' \
+ 'ForceCommand:string' \
+ 'RootPasswordAuth:bool:1' \
+ 'RootLogin:bool:1' \
+ 'rsakeyfile:file' \
+ 'keyfile:list(file)' \
+ 'BannerFile:file' \
+ 'Port:port:22' \
+ 'SSHKeepAlive:uinteger:300' \
+ 'IdleTimeout:uinteger:0' \
+ 'MaxAuthTries:uinteger:3' \
+ 'RecvWindowSize:uinteger:262144' \
+ 'mdns:bool:1'
+}
+
+dropbear_instance()
+{
+ [ "$2" = 0 ] || {
+ echo "validation failed"
+ return 1
+ }
+
+ [ "${enable}" = "1" ] || return 1
+
+ local iface ndev ipaddrs
+
+ # 'DirectInterface' should specify single interface
+ # but end users may misinterpret this setting
+ DirectInterface=$(normalize_list "${DirectInterface}")
+
+ # 'Interface' should specify single interface
+ # but end users are often misinterpret this setting
+ Interface=$(normalize_list "${Interface}")
+
+ if [ -n "${Interface}" ] ; then
+ if [ -n "${DirectInterface}" ] ; then
+ logger -t "${NAME}" -p daemon.warn \
+ "Option 'DirectInterface' takes precedence over 'Interface'"
+ else
+ logger -t "${NAME}" -p daemon.info \
+ "Option 'Interface' binds to address(es) but not to interface"
+ logger -t "${NAME}" -p daemon.info \
+ "Consider using option 'DirectInterface' to bind directly to interface"
+ fi
+ fi
+
+ # handle 'DirectInterface'
+ iface=$(echo "${DirectInterface}" | awk '{print $1}')
+ case "${DirectInterface}" in
+ *\ *)
+ warn_multiple_interfaces DirectInterface "${DirectInterface}"
+ logger -t "${NAME}" -p daemon.warn \
+ "Using network interface '${iface}' for direct binding"
+ ;;
+ esac
+ while [ -n "${iface}" ] ; do
+ # if network is available (even during boot) - proceed
+ if network_is_up "${iface}" ; then break ; fi
+ # skip during boot
+ [ -z "${BOOT}" ] || return 0
+
+ logger -t "${NAME}" -p daemon.crit \
+ "Network interface '${iface}' is not available!"
+ return 1
+ done
+ while [ -n "${iface}" ] ; do
+ # ${iface} is logical (higher level) interface name
+ # ${ndev} is 'real' interface name
+ # e.g.: if ${iface} is 'lan' (default LAN interface) then ${ndev} is 'br-lan'
+ network_get_device ndev "${iface}"
+ [ -z "${ndev}" ] || break
+
+ logger -t "${NAME}" -p daemon.crit \
+ "Missing network device for network interface '${iface}'!"
+ return 1
+ done
+ if [ -n "${iface}" ] ; then
+ logger -t "${NAME}" -p daemon.info \
+ "Using network interface '${iface}' (network device '${ndev}') for direct binding"
+ fi
+ # handle 'Interface'
+ while [ -z "${iface}" ] ; do
+ [ -n "${Interface}" ] || break
+
+ # skip during boot
+ [ -z "${BOOT}" ] || return 0
+
+ case "${Interface}" in
+ *\ *)
+ warn_multiple_interfaces Interface "${Interface}"
+ ;;
+ esac
+
+ local c=0
+ # src/sysoptions.h
+ local DROPBEAR_MAX_PORTS=10
+
+ local a n if_ipaddrs
+ for n in ${Interface} ; do
+ [ -n "$n" ] || continue
+
+ if_ipaddrs=
+ network_get_ipaddrs_all if_ipaddrs "$n"
+ [ -n "${if_ipaddrs}" ] || {
+ logger -s -t "${NAME}" -p daemon.err \
+ "Network interface '$n' has no suitable IP address(es)!"
+ continue
+ }
+
+ [ $c -le ${DROPBEAR_MAX_PORTS} ] || {
+ logger -s -t "${NAME}" -p daemon.err \
+ "Network interface '$n' is NOT listened due to option limit exceed!"
+ continue
+ }
+
+ for a in ${if_ipaddrs} ; do
+ [ -n "$a" ] || continue
+
+ c=$((c+1))
+ if [ $c -le ${DROPBEAR_MAX_PORTS} ] ; then
+ ipaddrs="${ipaddrs} $a"
+ continue
+ fi
+
+ logger -t "${NAME}" -p daemon.err \
+ "Endpoint '$a:${Port}' on network interface '$n' is NOT listened due to option limit exceed!"
+ done
+ done
+ break
+ done
+
+ PIDCOUNT="$(( ${PIDCOUNT} + 1))"
+ local pid_file="/var/run/${NAME}.${PIDCOUNT}.pid"
+
+ procd_open_instance
+ procd_set_param command "$PROG" -F -P "$pid_file"
+ if [ -n "${iface}" ] ; then
+ # if ${iface} is non-empty then ${ndev} is non-empty too
+ procd_append_param command -l "${ndev}" -p "${Port}"
+ else
+ if [ -z "${ipaddrs}" ] ; then
+ procd_append_param command -p "${Port}"
+ else
+ local a
+ for a in ${ipaddrs} ; do
+ [ -n "$a" ] || continue
+ procd_append_param command -p "$a:${Port}"
+ done
+ fi
+ fi
+ [ "${PasswordAuth}" -eq 0 ] && procd_append_param command -s
+ [ "${GatewayPorts}" -eq 1 ] && procd_append_param command -a
+ [ -n "${ForceCommand}" ] && procd_append_param command -c "${ForceCommand}"
+ [ "${RootPasswordAuth}" -eq 0 ] && procd_append_param command -g
+ [ "${RootLogin}" -eq 0 ] && procd_append_param command -w
+ config_list_foreach "$1" 'keyfile' hk_config__keyfile
+ if [ -n "${rsakeyfile}" ]; then
+ logger -s -t "${NAME}" -p daemon.crit \
+ "Option 'rsakeyfile' is considered to be DEPRECATED and will be REMOVED in future releases, use 'keyfile' list instead"
+ sed -i.before-upgrade -E -e 's/option(\s+)rsakeyfile/list keyfile/' \
+ "/etc/config/${NAME}"
+ logger -s -t "${NAME}" -p daemon.crit \
+ "Auto-transition 'option rsakeyfile' => 'list keyfile' in /etc/config/${NAME} is done, please verify your configuration"
+ hk_config 'rsakeyfile' "${rsakeyfile}"
+ fi
+ [ -n "${BannerFile}" ] && procd_append_param command -b "${BannerFile}"
+ [ "${IdleTimeout}" -ne 0 ] && procd_append_param command -I "${IdleTimeout}"
+ [ "${SSHKeepAlive}" -ne 0 ] && procd_append_param command -K "${SSHKeepAlive}"
+ [ "${MaxAuthTries}" -ne 0 ] && procd_append_param command -T "${MaxAuthTries}"
+ [ "${RecvWindowSize}" -gt 0 ] && {
+ # NB: OpenWrt increases receive window size to increase throughput on high latency links
+ # ref: validate_section_dropbear()
+ # default receive window size is 24576 (DEFAULT_RECV_WINDOW in default_options.h)
+
+ # src/sysoptions.h
+ local MAX_RECV_WINDOW=10485760
+ if [ "${RecvWindowSize}" -gt ${MAX_RECV_WINDOW} ] ; then
+ # separate logging is required because syslog misses dropbear's message
+ # Bad recv window '${RecvWindowSize}', using ${MAX_RECV_WINDOW}
+ # it's probably dropbear issue but we should handle this and notify user
+ logger -s -t "${NAME}" -p daemon.warn \
+ "Option 'RecvWindowSize' is too high (${RecvWindowSize}), limiting to ${MAX_RECV_WINDOW}"
+ RecvWindowSize=${MAX_RECV_WINDOW}
+ fi
+ procd_append_param command -W "${RecvWindowSize}"
+ }
+ [ "${mdns}" -ne 0 ] && procd_add_mdns "ssh" "tcp" "$Port" "daemon=dropbear"
+ procd_set_param respawn
+ procd_close_instance
+}
+
+load_interfaces()
+{
+ local enable
+ config_get_bool enable "$1" enable 1
+ [ "${enable}" = "1" ] || return 0
+
+ local direct_iface iface
+ config_get direct_iface "$1" DirectInterface
+ direct_iface=$(normalize_list "${direct_iface}")
+ # 'DirectInterface' takes precedence over 'Interface'
+ if [ -n "${direct_iface}" ] ; then
+ iface=$(echo "${direct_iface}" | awk '{print $1}')
+ else
+ config_get iface "$1" Interface
+ iface=$(normalize_list "${iface}")
+ fi
+ interfaces="${interfaces} ${iface}"
+}
+
+boot()
+{
+ BOOT=1
+ start "$@"
+}
+
+start_service()
+{
+ hk_generate_as_needed
+ file_verify /etc/dropbear/authorized_keys config
+
+ . /lib/functions.sh
+ . /lib/functions/network.sh
+
+ config_load "${NAME}"
+ config_foreach validate_section_dropbear dropbear dropbear_instance
+}
+
+service_triggers()
+{
+ local interfaces
+
+ procd_add_config_trigger "config.change" "${NAME}" /etc/init.d/dropbear reload
+
+ config_load "${NAME}"
+ config_foreach load_interfaces "${NAME}"
+
+ [ -n "${interfaces}" ] && {
+ local n
+ for n in $(printf '%s\n' ${interfaces} | sort -u) ; do
+ procd_add_interface_trigger "interface.*" $n /etc/init.d/dropbear reload
+ done
+ }
+
+ procd_add_validation validate_section_dropbear
+}
+
+shutdown() {
+ # close all open connections
+ killall dropbear
+}
+
+killclients()
+{
+ local ignore=''
+ local server
+ local pid
+
+ # if this script is run from inside a client session, then ignore that session
+ pid="$$"
+ while [ "${pid}" -ne 0 ]
+ do
+ # get parent process id
+ pid=$(cut -d ' ' -f 4 "/proc/${pid}/stat")
+ [ "${pid}" -eq 0 ] && break
+
+ # check if client connection
+ grep -F -q -e "${PROG}" "/proc/${pid}/cmdline" && {
+ append ignore "${pid}"
+ break
+ }
+ done
+
+ # get all server pids that should be ignored
+ for server in $(cat /var/run/${NAME}.*.pid)
+ do
+ append ignore "${server}"
+ done
+
+ # get all running pids and kill client connections
+ local skip
+ for pid in $(pidof "${NAME}")
+ do
+ # check if correct program, otherwise process next pid
+ grep -F -q -e "${PROG}" "/proc/${pid}/cmdline" || {
+ continue
+ }
+
+ # check if pid should be ignored (servers, ourself)
+ skip=0
+ for server in ${ignore}
+ do
+ if [ "${pid}" = "${server}" ]
+ then
+ skip=1
+ break
+ fi
+ done
+ [ "${skip}" -ne 0 ] && continue
+
+ # kill process
+ echo "${initscript}: Killing ${pid}..."
+ kill -KILL ${pid}
+ done
+}