dropbear.init 12 KB

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