| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472 |
- #!/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
- 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:0' \
- '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
- local pid_file="/var/run/${NAME}.${1}.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
- }
|