dropbear.init 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472
  1. #!/bin/sh /etc/rc.common
  2. # Copyright (C) 2006-2010 OpenWrt.org
  3. # Copyright (C) 2006 Carlos Sobrinho
  4. START=19
  5. STOP=50
  6. USE_PROCD=1
  7. PROG=/usr/sbin/dropbear
  8. NAME=dropbear
  9. extra_command "killclients" "Kill ${NAME} processes except servers and yourself"
  10. # most of time real_stat() will be failing
  11. # due to missing "stat" binary (by default)
  12. real_stat() { env stat -L "$@" 2>/dev/null ; }
  13. dumb_stat() { ls -Ldln "$1" | tr -s '\t ' ' ' ; }
  14. stat_perm() { real_stat -c '%A' "$1" || dumb_stat "$1" | cut -d ' ' -f 1 ; }
  15. stat_owner() { real_stat -c '%u' "$1" || dumb_stat "$1" | cut -d ' ' -f 3 ; }
  16. _dropbearkey()
  17. {
  18. /usr/bin/dropbearkey "$@" </dev/null >/dev/null 2>&1
  19. }
  20. # $1 - file name (host key or config)
  21. file_verify()
  22. {
  23. [ -f "$1" ] || return 1
  24. # checking file ownership
  25. [ "$(stat_owner "$1")" = "0" ] || {
  26. chown 0 "$1"
  27. [ "$(stat_owner "$1")" = "0" ] || return 2
  28. }
  29. # checking file permissions
  30. [ "$(stat_perm "$1")" = "-rw-------" ] || {
  31. chmod 0600 "$1"
  32. [ "$(stat_perm "$1")" = "-rw-------" ] || return 3
  33. }
  34. # file is host key or not?
  35. # if $2 is empty string - file is "host key"
  36. # if $2 is non-empty string - file is "config"
  37. [ -z "$2" ] || return 0
  38. # checking file contents (finally)
  39. [ -s "$1" ] || return 4
  40. _dropbearkey -y -f "$1" || return 5
  41. return 0
  42. }
  43. # $1 - file_verify() return code
  44. file_errmsg()
  45. {
  46. case "$1" in
  47. 0) ;;
  48. 1) echo "file does not exist" ;;
  49. 2) echo "file has wrong owner (must be owned by root)" ;;
  50. 3) echo "file has wrong permissions (must not have group/other write bit)" ;;
  51. 4) echo "file has zero length" ;;
  52. 5) echo "file is not valid host key or not supported" ;;
  53. *) echo "unknown error" ;;
  54. esac
  55. }
  56. # $1 - config option
  57. # $2 - host key file name
  58. hk_config()
  59. {
  60. local x m
  61. file_verify "$2" ; x=$?
  62. if [ "$x" = 0 ] ; then
  63. procd_append_param command -r "$2"
  64. return
  65. fi
  66. m=$(file_errmsg "$x")
  67. logger -s -t "${NAME}" -p daemon.warn \
  68. "Option '$1', skipping '$2': $m"
  69. }
  70. # $1 - host key file name
  71. hk_config__keyfile() { hk_config keyfile "$1" ; }
  72. ktype_all='ed25519 ecdsa rsa'
  73. hk_generate_as_needed()
  74. {
  75. local hk_cfg_dir kgen ktype kfile hk_tmp_dir
  76. hk_cfg_dir='/etc/dropbear'
  77. [ -d "${hk_cfg_dir}" ] || mkdir -p "${hk_cfg_dir}"
  78. kgen=
  79. for ktype in ${ktype_all} ; do
  80. kfile="${hk_cfg_dir}/dropbear_${ktype}_host_key"
  81. if file_verify "${kfile}" ; then continue ; fi
  82. kgen="${kgen}${kgen:+ }${ktype}"
  83. done
  84. # all keys are sane?
  85. [ -n "${kgen}" ] || return 0
  86. hk_tmp_dir=$(mktemp -d)
  87. # system in bad state?
  88. [ -n "${hk_tmp_dir}" ] || return 1
  89. chmod 0700 "${hk_tmp_dir}"
  90. for ktype in ${kgen} ; do
  91. kfile="${hk_tmp_dir}/dropbear_${ktype}_host_key"
  92. if ! _dropbearkey -t ${ktype} -f "${kfile}" ; then
  93. # unsupported key type
  94. rm -f "${kfile}"
  95. continue
  96. fi
  97. chmod 0600 "${kfile}"
  98. done
  99. kgen=
  100. for ktype in ${ktype_all} ; do
  101. kfile="${hk_tmp_dir}/dropbear_${ktype}_host_key"
  102. [ -s "${kfile}" ] || continue
  103. kgen="${kgen}${kgen:+ }${ktype}"
  104. done
  105. if [ -n "${kgen}" ] ; then
  106. for ktype in ${kgen} ; do
  107. kfile="${hk_tmp_dir}/dropbear_${ktype}_host_key"
  108. [ -s "${kfile}" ] || continue
  109. mv -f "${kfile}" "${hk_cfg_dir}/"
  110. done
  111. fi
  112. rm -rf "${hk_tmp_dir}"
  113. # cleanup empty files
  114. for ktype in ${ktype_all} ; do
  115. kfile="${hk_cfg_dir}/dropbear_${ktype}_host_key"
  116. [ -s "${kfile}" ] || rm -f "${kfile}"
  117. done
  118. }
  119. # $1 - list with whitespace-separated elements
  120. normalize_list()
  121. {
  122. printf '%s' "$1" | tr -s ' \r\n\t' ' ' | sed -E 's/^ //;s/ $//'
  123. }
  124. warn_multiple_interfaces()
  125. {
  126. logger -t "${NAME}" -p daemon.warn \
  127. "Option '$1' should specify SINGLE interface but instead it lists interfaces: $2"
  128. logger -t "${NAME}" -p daemon.warn \
  129. "Consider creating per-interface instances instead!"
  130. }
  131. validate_section_dropbear()
  132. {
  133. uci_load_validate dropbear dropbear "$1" "$2" \
  134. 'PasswordAuth:bool:1' \
  135. 'enable:bool:1' \
  136. 'DirectInterface:string' \
  137. 'Interface:string' \
  138. 'GatewayPorts:bool:0' \
  139. 'ForceCommand:string' \
  140. 'RootPasswordAuth:bool:1' \
  141. 'RootLogin:bool:1' \
  142. 'rsakeyfile:file' \
  143. 'keyfile:list(file)' \
  144. 'BannerFile:file' \
  145. 'Port:port:22' \
  146. 'SSHKeepAlive:uinteger:300' \
  147. 'IdleTimeout:uinteger:0' \
  148. 'MaxAuthTries:uinteger:3' \
  149. 'RecvWindowSize:uinteger:0' \
  150. 'mdns:bool:1'
  151. }
  152. dropbear_instance()
  153. {
  154. [ "$2" = 0 ] || {
  155. echo "validation failed"
  156. return 1
  157. }
  158. [ "${enable}" = "1" ] || return 1
  159. local iface ndev ipaddrs
  160. # 'DirectInterface' should specify single interface
  161. # but end users may misinterpret this setting
  162. DirectInterface=$(normalize_list "${DirectInterface}")
  163. # 'Interface' should specify single interface
  164. # but end users are often misinterpret this setting
  165. Interface=$(normalize_list "${Interface}")
  166. if [ -n "${Interface}" ] ; then
  167. if [ -n "${DirectInterface}" ] ; then
  168. logger -t "${NAME}" -p daemon.warn \
  169. "Option 'DirectInterface' takes precedence over 'Interface'"
  170. else
  171. logger -t "${NAME}" -p daemon.info \
  172. "Option 'Interface' binds to address(es) but not to interface"
  173. logger -t "${NAME}" -p daemon.info \
  174. "Consider using option 'DirectInterface' to bind directly to interface"
  175. fi
  176. fi
  177. # handle 'DirectInterface'
  178. iface=$(echo "${DirectInterface}" | awk '{print $1}')
  179. case "${DirectInterface}" in
  180. *\ *)
  181. warn_multiple_interfaces DirectInterface "${DirectInterface}"
  182. logger -t "${NAME}" -p daemon.warn \
  183. "Using network interface '${iface}' for direct binding"
  184. ;;
  185. esac
  186. while [ -n "${iface}" ] ; do
  187. # if network is available (even during boot) - proceed
  188. if network_is_up "${iface}" ; then break ; fi
  189. # skip during boot
  190. [ -z "${BOOT}" ] || return 0
  191. logger -t "${NAME}" -p daemon.crit \
  192. "Network interface '${iface}' is not available!"
  193. return 1
  194. done
  195. while [ -n "${iface}" ] ; do
  196. # ${iface} is logical (higher level) interface name
  197. # ${ndev} is 'real' interface name
  198. # e.g.: if ${iface} is 'lan' (default LAN interface) then ${ndev} is 'br-lan'
  199. network_get_device ndev "${iface}"
  200. [ -z "${ndev}" ] || break
  201. logger -t "${NAME}" -p daemon.crit \
  202. "Missing network device for network interface '${iface}'!"
  203. return 1
  204. done
  205. if [ -n "${iface}" ] ; then
  206. logger -t "${NAME}" -p daemon.info \
  207. "Using network interface '${iface}' (network device '${ndev}') for direct binding"
  208. fi
  209. # handle 'Interface'
  210. while [ -z "${iface}" ] ; do
  211. [ -n "${Interface}" ] || break
  212. # skip during boot
  213. [ -z "${BOOT}" ] || return 0
  214. case "${Interface}" in
  215. *\ *)
  216. warn_multiple_interfaces Interface "${Interface}"
  217. ;;
  218. esac
  219. local c=0
  220. # src/sysoptions.h
  221. local DROPBEAR_MAX_PORTS=10
  222. local a n if_ipaddrs
  223. for n in ${Interface} ; do
  224. [ -n "$n" ] || continue
  225. if_ipaddrs=
  226. network_get_ipaddrs_all if_ipaddrs "$n"
  227. [ -n "${if_ipaddrs}" ] || {
  228. logger -s -t "${NAME}" -p daemon.err \
  229. "Network interface '$n' has no suitable IP address(es)!"
  230. continue
  231. }
  232. [ $c -le ${DROPBEAR_MAX_PORTS} ] || {
  233. logger -s -t "${NAME}" -p daemon.err \
  234. "Network interface '$n' is NOT listened due to option limit exceed!"
  235. continue
  236. }
  237. for a in ${if_ipaddrs} ; do
  238. [ -n "$a" ] || continue
  239. c=$((c+1))
  240. if [ $c -le ${DROPBEAR_MAX_PORTS} ] ; then
  241. ipaddrs="${ipaddrs} $a"
  242. continue
  243. fi
  244. logger -t "${NAME}" -p daemon.err \
  245. "Endpoint '$a:${Port}' on network interface '$n' is NOT listened due to option limit exceed!"
  246. done
  247. done
  248. break
  249. done
  250. local pid_file="/var/run/${NAME}.${1}.pid"
  251. procd_open_instance
  252. procd_set_param command "$PROG" -F -P "$pid_file"
  253. if [ -n "${iface}" ] ; then
  254. # if ${iface} is non-empty then ${ndev} is non-empty too
  255. procd_append_param command -l "${ndev}" -p "${Port}"
  256. else
  257. if [ -z "${ipaddrs}" ] ; then
  258. procd_append_param command -p "${Port}"
  259. else
  260. local a
  261. for a in ${ipaddrs} ; do
  262. [ -n "$a" ] || continue
  263. procd_append_param command -p "$a:${Port}"
  264. done
  265. fi
  266. fi
  267. [ "${PasswordAuth}" -eq 0 ] && procd_append_param command -s
  268. [ "${GatewayPorts}" -eq 1 ] && procd_append_param command -a
  269. [ -n "${ForceCommand}" ] && procd_append_param command -c "${ForceCommand}"
  270. [ "${RootPasswordAuth}" -eq 0 ] && procd_append_param command -g
  271. [ "${RootLogin}" -eq 0 ] && procd_append_param command -w
  272. config_list_foreach "$1" 'keyfile' hk_config__keyfile
  273. if [ -n "${rsakeyfile}" ]; then
  274. logger -s -t "${NAME}" -p daemon.crit \
  275. "Option 'rsakeyfile' is considered to be DEPRECATED and will be REMOVED in future releases, use 'keyfile' list instead"
  276. sed -i.before-upgrade -E -e 's/option(\s+)rsakeyfile/list keyfile/' \
  277. "/etc/config/${NAME}"
  278. logger -s -t "${NAME}" -p daemon.crit \
  279. "Auto-transition 'option rsakeyfile' => 'list keyfile' in /etc/config/${NAME} is done, please verify your configuration"
  280. hk_config 'rsakeyfile' "${rsakeyfile}"
  281. fi
  282. [ -n "${BannerFile}" ] && procd_append_param command -b "${BannerFile}"
  283. [ "${IdleTimeout}" -ne 0 ] && procd_append_param command -I "${IdleTimeout}"
  284. [ "${SSHKeepAlive}" -ne 0 ] && procd_append_param command -K "${SSHKeepAlive}"
  285. [ "${MaxAuthTries}" -ne 0 ] && procd_append_param command -T "${MaxAuthTries}"
  286. [ "${RecvWindowSize}" -gt 0 ] && {
  287. # NB: OpenWrt increases receive window size to increase throughput on high latency links
  288. # ref: validate_section_dropbear()
  289. # default receive window size is 24576 (DEFAULT_RECV_WINDOW in default_options.h)
  290. # src/sysoptions.h
  291. local MAX_RECV_WINDOW=10485760
  292. if [ "${RecvWindowSize}" -gt ${MAX_RECV_WINDOW} ] ; then
  293. # separate logging is required because syslog misses dropbear's message
  294. # Bad recv window '${RecvWindowSize}', using ${MAX_RECV_WINDOW}
  295. # it's probably dropbear issue but we should handle this and notify user
  296. logger -s -t "${NAME}" -p daemon.warn \
  297. "Option 'RecvWindowSize' is too high (${RecvWindowSize}), limiting to ${MAX_RECV_WINDOW}"
  298. RecvWindowSize=${MAX_RECV_WINDOW}
  299. fi
  300. procd_append_param command -W "${RecvWindowSize}"
  301. }
  302. [ "${mdns}" -ne 0 ] && procd_add_mdns "ssh" "tcp" "$Port" "daemon=dropbear"
  303. procd_set_param respawn
  304. procd_close_instance
  305. }
  306. load_interfaces()
  307. {
  308. local enable
  309. config_get_bool enable "$1" enable 1
  310. [ "${enable}" = "1" ] || return 0
  311. local direct_iface iface
  312. config_get direct_iface "$1" DirectInterface
  313. direct_iface=$(normalize_list "${direct_iface}")
  314. # 'DirectInterface' takes precedence over 'Interface'
  315. if [ -n "${direct_iface}" ] ; then
  316. iface=$(echo "${direct_iface}" | awk '{print $1}')
  317. else
  318. config_get iface "$1" Interface
  319. iface=$(normalize_list "${iface}")
  320. fi
  321. interfaces="${interfaces} ${iface}"
  322. }
  323. boot()
  324. {
  325. BOOT=1
  326. start "$@"
  327. }
  328. start_service()
  329. {
  330. hk_generate_as_needed
  331. file_verify /etc/dropbear/authorized_keys config
  332. . /lib/functions.sh
  333. . /lib/functions/network.sh
  334. config_load "${NAME}"
  335. config_foreach validate_section_dropbear dropbear dropbear_instance
  336. }
  337. service_triggers()
  338. {
  339. local interfaces
  340. procd_add_config_trigger "config.change" "${NAME}" /etc/init.d/dropbear reload
  341. config_load "${NAME}"
  342. config_foreach load_interfaces "${NAME}"
  343. [ -n "${interfaces}" ] && {
  344. local n
  345. for n in $(printf '%s\n' ${interfaces} | sort -u) ; do
  346. procd_add_interface_trigger "interface.*" $n /etc/init.d/dropbear reload
  347. done
  348. }
  349. procd_add_validation validate_section_dropbear
  350. }
  351. shutdown() {
  352. # close all open connections
  353. killall dropbear
  354. }
  355. killclients()
  356. {
  357. local ignore=''
  358. local server
  359. local pid
  360. # if this script is run from inside a client session, then ignore that session
  361. pid="$$"
  362. while [ "${pid}" -ne 0 ]
  363. do
  364. # get parent process id
  365. pid=$(cut -d ' ' -f 4 "/proc/${pid}/stat")
  366. [ "${pid}" -eq 0 ] && break
  367. # check if client connection
  368. grep -F -q -e "${PROG}" "/proc/${pid}/cmdline" && {
  369. append ignore "${pid}"
  370. break
  371. }
  372. done
  373. # get all server pids that should be ignored
  374. for server in $(cat /var/run/${NAME}.*.pid)
  375. do
  376. append ignore "${server}"
  377. done
  378. # get all running pids and kill client connections
  379. local skip
  380. for pid in $(pidof "${NAME}")
  381. do
  382. # check if correct program, otherwise process next pid
  383. grep -F -q -e "${PROG}" "/proc/${pid}/cmdline" || {
  384. continue
  385. }
  386. # check if pid should be ignored (servers, ourself)
  387. skip=0
  388. for server in ${ignore}
  389. do
  390. if [ "${pid}" = "${server}" ]
  391. then
  392. skip=1
  393. break
  394. fi
  395. done
  396. [ "${skip}" -ne 0 ] && continue
  397. # kill process
  398. echo "${initscript}: Killing ${pid}..."
  399. kill -KILL ${pid}
  400. done
  401. }