Bash script to easily configure your interface to share your internet connection and configure a DHCP and DNS and TFTP boot server to listen on it. IP, DHCP, DNS can be configured, and for WiFi interfaces also wireless mode and encryption.

adhocspot.sh 35KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165
  1. #!/bin/bash
  2. _version=20190514.1
  3. ##### Dependencies. #####
  4. #
  5. # On Arch Linux, this script depends on the following packages (in brackets the bare commands it uses):
  6. # * bash: For running this script. ('bash')
  7. # * dnsmasq: For DHCP and DNS. ('dnsmasq')
  8. # * iptables: For configuring NAT/ masquerading. ('iptables')
  9. # * net-tools: For configuring IP information of the network interface. ('ifconfig')
  10. # * wireless_tools: For configuration of wireless network interface. ('iwconfig')
  11. # * wpa_supplicant: For WPA-encryption. ('wpa_supplicant', 'wpa_passphrase')
  12. # * ... and standard tools like grep, sed, awk ...
  13. #
  14. ##### #####
  15. # set -e
  16. _datetime="$(date +%Y-%m-%d_%H-%M-%S)"
  17. # Default values. Can be overridden by commandline options.
  18. _iface_default="wlan0"
  19. _ip_default="192.168.101.3"
  20. _netmask_default="255.255.255.0"
  21. _out_iface_all_default="true"
  22. _out_iface_default="<all>"
  23. _dhcprange_lower_suffix_default="121"
  24. _dhcprange_upper_suffix_default="199"
  25. _macaddress_change_default="false"
  26. _macaddress_default='<not change>'
  27. _essid_default="adhoc_network"
  28. _channel_default="8"
  29. _encryption_default="off"
  30. _enckey_default="please_specify_a_non_default_key"
  31. _wifimode_default="ad-hoc"
  32. _no_wifi_default="false"
  33. _no_ipconfig_default="false"
  34. _no_nat_default="false"
  35. _tftp_root_default="/tftpboot"
  36. _no_tftp_default="false"
  37. _verbose_default="false"
  38. _debug_default="false"
  39. _rundir_base_default="/var/run/adhocspot"
  40. stdout()
  41. # To write to stdout.
  42. # Options: '-n': Without trailing newline.
  43. {
  44. if [ "$1" == "-n" ]; then
  45. _fmt='%s'
  46. shift
  47. else
  48. _fmt='%s\n'
  49. fi
  50. if [ $# -gt 1 ]; then
  51. errmsg "BUG: Too many arguments passed to internal function 'stdout()'".
  52. return 12
  53. else
  54. printf "${_fmt}" "$1"
  55. fi
  56. }
  57. stderr()
  58. # To write to stderr.
  59. {
  60. stdout "$@" > /dev/stderr
  61. }
  62. msg()
  63. # To print messages for the user.
  64. {
  65. stdout "$@"
  66. }
  67. stdout_prefix()
  68. {
  69. prefix="$1"
  70. while read line; do
  71. stdout "${prefix}${line}"
  72. done
  73. }
  74. verbose() {
  75. if "${_verbose}"; then
  76. msg "$@"
  77. fi
  78. }
  79. debug() {
  80. if "${_debug}"; then
  81. msg "$@"
  82. fi
  83. }
  84. errmsg() {
  85. stderr "$@"
  86. }
  87. exiterror() {
  88. if [ $# -ge 2 ]; then
  89. _exitcode="$2"
  90. else
  91. _exitcode=1
  92. fi
  93. if [ $# -ge 1 ]; then
  94. _msg="$1"
  95. else
  96. _msg="$0: Unspecified Error. Aborting."
  97. fi
  98. errmsg "${_msg}"
  99. exit "${_exitcode}"
  100. }
  101. make_newip_from_suffix() {
  102. # Takes as $1 an IPv4-address, as $2 a last part of an IPv4-address, and replaces the last part of $1 with $2.
  103. if [ $# -lt 2 ]; then
  104. exiterror "$0: In function 'make_newip_from_suffix': Error: Need IP-address and a suffix as options."
  105. fi
  106. _ip="$1"
  107. _sfx="$2"
  108. stdout "$(stdout "${_ip}" | awk -F. '{print $1"."$2"."$3}').${_sfx}"
  109. }
  110. get_macaddress() {
  111. # Returns the MAC-address of the interface specified in $1.
  112. if [ $# -lt 1 ]; then
  113. exiterror "$0: In function 'get_macaddress': Error: Need to specify a network interface as option."
  114. fi
  115. ifconfig "$1" | grep -E '\<ether\>' | sed 's|^.*ether[[:space:]]*\([0-9a-f:]*\)[[:space:]].*$|\1|g'
  116. }
  117. is_interface_up() {
  118. # Returns 0 if interface $1 is up, otherwise 1.
  119. if [ $# -lt 1 ]; then
  120. exiterror "$0: In function 'is_interface_up': Error: Need to specify a network interface as option."
  121. fi
  122. ifconfig "$1" | tr '[[:space:]]' '\n' | grep 'flags=' | grep -q UP
  123. }
  124. set_macaddress() {
  125. # Sets the MAC-address of the network interface $1 to $2.
  126. if [ $# -lt 2 ]; then
  127. exiterror "$0: In function 'set_macaddress': Error: Need interface and MAC-address as options."
  128. fi
  129. verbose "Setting MAC address of $1 to $2."
  130. if is_interface_up "$1"; then
  131. _isup=true
  132. else
  133. _isup=false
  134. fi
  135. # debug "DEBUG: Interface is up, need to be temporarily down to change MAC address."
  136. if "${_isup}"; then
  137. ifconfig "$1" down
  138. fi
  139. # debug "DEBUG: Interface was brought down. Now changing MAC address."
  140. ifconfig "$1" hw ether "$2"
  141. # debug "DEBUG: Bringing interface up again."
  142. if "${_isup}"; then
  143. ifconfig "$1" up
  144. fi
  145. # debug "DEBUG: Interface brought up."
  146. }
  147. get_netmask() {
  148. # Returns the netmask of network interface $1.
  149. if [ $# -lt 1 ]; then
  150. exiterror "$0: In function 'get_netmask': Error: Need to specify a network interface as option."
  151. fi
  152. _nmask="$(ifconfig "$1" | grep -E '\<netmask\>' | sed 's|^.*netmask[[:space:]]*\([0-9a-f\.]*\)[[:space:]].*$|\1|g')"
  153. stdout "${_nmask}"
  154. }
  155. get_ipaddress() {
  156. # Returns the IP address of network interface $1.
  157. if [ $# -lt 1 ]; then
  158. exiterror "$0: In function 'get_ipaddress': Error: Need to specify a network interface as option."
  159. fi
  160. _ipaddr="$(ifconfig "$1" | grep -E '\<inet\>' | sed 's|^.*inet[[:space:]]*\([0-9a-f\.]*\)[[:space:]].*$|\1|g')"
  161. if [ -z "${_ipaddr}" ]; then
  162. _ipaddr='0.0.0.0'
  163. fi
  164. stdout "${_ipaddr}"
  165. }
  166. get_ipconfig() {
  167. # Outputs the ip config state of network interface $1. Output format: 'inet <ip> netmask <netmask> [up/down]' (so we could direktly feed back into ifconfig).
  168. if [ $# -lt 1 ]; then
  169. exiterror "$0: In function 'get_ipconfig': Error: Need to specify a network interface as option."
  170. fi
  171. if is_interface_up "$1"; then
  172. _updown='up'
  173. else
  174. _updown='down'
  175. fi
  176. _ipaddr="$(get_ipaddress "$1")"
  177. _nmask="$(get_netmask "$1")"
  178. if [ -z "${_ipaddr}" ]; then
  179. _ipaddr='0.0.0.0'
  180. fi
  181. if [ -n "${_nmask}" ]; then
  182. _nmtext="netmask ${_nmask} "
  183. else
  184. _nmtext=""
  185. fi
  186. stdout "inet ${_ipaddr} ${_nmtext}${_updown}"
  187. }
  188. get_nat_interfaces() {
  189. # Returns a newline-separated list of interfaces to add masquerading to their postrouting queue. Examines $_out_iface_all: If $_out_iface_all is true, returns all available interfaces whose name does not start with 'lo'. Otherwise, returns ${_out_iface}.
  190. _nat_ifaces=""
  191. if "${_out_iface_all}"; then
  192. _nat_ifaces="$(ifconfig -a | grep -E '^[^[[:space:]]]*' | awk '{print $1}' | sed 's|\:$||g' | grep -vE '^lo')"
  193. else
  194. _nat_ifaces="${_out_iface}"
  195. fi
  196. stdout "${_nat_ifaces}"
  197. }
  198. get_rp_filter_status() {
  199. # Arguments: $1: Space-, tab- or newline-separated list of interfaces to check rp_filter value.
  200. if [ $# -lt 1 ]; then
  201. exiterror "$0: In function 'get_nat_status': Error: Need list of interfaces as option."
  202. fi
  203. for _nat_iface in $1; do
  204. stdout "${_nat_iface} $(cat "/proc/sys/net/ipv4/conf/${_nat_iface}/rp_filter")"
  205. done
  206. }
  207. get_ipv4_forward_status() {
  208. cat "/proc/sys/net/ipv4/ip_forward"
  209. }
  210. configure_nat() {
  211. # Arguments:
  212. # $1: Space-, tab- or newline-separated list of interfaces to add masquerading to their postrouting queue.
  213. if [ $# -lt 1 ]; then
  214. exiterror "$0: In function 'configure_nat': Error: Need list of interfaces as option."
  215. fi
  216. get_rp_filter_status "$1" > "${_rp_filter_statusfile}"
  217. get_ipv4_forward_status > "${_ipv4_forward_statusfile}"
  218. verbose "Configuring IPv4 forwarding"
  219. stdout 1 > /proc/sys/net/ipv4/ip_forward
  220. for _nat_iface in $1; do
  221. verbose "Configuring masquerading and disabling rp_filter on ${_nat_iface}."
  222. iptables -t nat -A POSTROUTING -j MASQUERADE -o "${_nat_iface}"
  223. stdout 0 > "/proc/sys/net/ipv4/conf/${_nat_iface}/rp_filter"
  224. done
  225. }
  226. deconfigure_nat() {
  227. if [ -e "${_rp_filter_statusfile}" ]; then
  228. cat "${_rp_filter_statusfile}" | while read _line; do
  229. _nat_iface="$(stdout "${_line}" | awk '{print $1}')"
  230. _rp_value="$(stdout "${_line}" | awk '{print $2}')"
  231. verbose "Setting rp_filter to ${_rp_value} on ${_nat_iface}."
  232. stdout "${_rp_value}" > "/proc/sys/net/ipv4/conf/${_nat_iface}/rp_filter"
  233. done
  234. rm -f "${_rp_filter_statusfile}"
  235. fi
  236. if [ -e "${_ipv4_forward_statusfile}" ]; then
  237. _fwd="$(cat "${_ipv4_forward_statusfile}")"
  238. verbose "Setting IPv4 forwarding to ${_fwd}."
  239. stdout "${_fwd}" > /proc/sys/net/ipv4/ip_forward
  240. rm -f "${_ipv4_forward_statusfile}"
  241. fi
  242. verbose "Removing all entries from the 'nat' firewall table."
  243. iptables -t nat -F
  244. }
  245. configure_ip() {
  246. # Configures the network interface $1. Expects as argument $2 a string acceptible to ifconfig (e.g. of the format 'inet <IP> netmask <netmask> [up/down]').
  247. if [ $# -lt 2 ]; then
  248. exiterror "$0: In function 'configure_ip': Error: Need interface, and configuration as options."
  249. fi
  250. verbose "Setting network interface $1 to $2."
  251. ifconfig "$1" $2
  252. }
  253. configure_wifi_basics() {
  254. # Configures the WiFi interface $1 for mode $2, ESSID $3, channel $4.
  255. if [ $# -lt 4 ]; then
  256. exiterror "$0: In function 'configure_wifi_basics': Error: Need interface, mode, ESSID and channel as options."
  257. fi
  258. verbose "Configuring $1 for WiFi mode $2, ESSID $3 and channel $4."
  259. iwconfig "$1" mode "$2" essid "$3" channel "$4"
  260. }
  261. configure_wifi_wep() {
  262. # Configures the WiFi interface $1 for WEP-encryption with password $2.
  263. if [ $# -lt 2 ]; then
  264. exiterror "$0: In function 'configure_wifi_wep': Error: Need interface and WEP-password as options."
  265. fi
  266. verbose "Setting encryption of $1 to WEP restricted, with password $2."
  267. iwconfig "$1" key restricted s:"$2"
  268. }
  269. configure_wifi_wpa() {
  270. # Configures the WiFi interface $1 for WPA-encryption with ESSID $2 and password $3. $4 specifies the WiFi-Mode we operate (e.g. ad-hoc or master).
  271. if [ $# -lt 4 ]; then
  272. exiterror "$0: In function 'configure_wifi_wpa': Error: Need interface, ESSID, WEP-password and WiFi-mode as options."
  273. fi
  274. _wpaiface="$1"
  275. _wpaessid="$2"
  276. _wpapasswd="$3"
  277. _wpawifimode="$4"
  278. _wpadriver="wext"
  279. _wpapsk="$(wpa_passphrase "${_wpaessid}" "${_wpapasswd}" | grep -Ev '^[[:space:]]*\#' | grep '^[[:space:]]psk\=' | cut -d'=' -f2)"
  280. _wpa_group="$(id -gn)"
  281. case "${_wpawifimode}" in
  282. "ad-hoc")
  283. _mode=1
  284. _ap_scan=2
  285. _key_mgmt='WPA-NONE'
  286. _pairwise='NONE'
  287. ;;
  288. "master")
  289. exiterror "$0: Error: WPA encryption together with mode 'master' is not implemented yet. Aborting." 99
  290. _mode=1
  291. _ap_scan=1
  292. _key_mgmt='WPA-NONE'
  293. _pairwise='NONE'
  294. ;;
  295. "managed")
  296. _mode=0
  297. _ap_scan=1
  298. _key_mgmt='WPA-PSK'
  299. _pairwise='NONE'
  300. ;;
  301. *)
  302. exiterror "$0: In function 'configure_wifi_wpa': Error: Not supported WiFi-mode "${_wpawifimode}" specified."
  303. ;;
  304. esac
  305. _wpaconf="
  306. ctrl_interface=DIR=${_wpa_ctrl} GROUP=${_wpa_group}
  307. ap_scan=${_ap_scan}
  308. network={
  309. ssid=\"${_wpaessid}\"
  310. mode="${_mode}"
  311. proto=WPA
  312. key_mgmt=${_key_mgmt}
  313. pairwise=${_pairwise}
  314. group=TKIP
  315. psk=${_wpapsk}
  316. }
  317. "
  318. if "${_debug}"; then
  319. _wpadebug="-dd"
  320. elif "${_verbose}"; then
  321. _wpadebug="-d"
  322. else
  323. _wpadebug=""
  324. fi
  325. verbose "Starting wpa_supplicant for interface ${_wpaiface}."
  326. debug ""
  327. debug "=== Using the following configuration file for wpa_supplicant: ==="
  328. debug ""
  329. debug "${_wpaconf}"
  330. debug ""
  331. debug "=== End of wpa_supplicant's config file. ==="
  332. debug ""
  333. stdout "${_wpaconf}" | wpa_supplicant ${_wpadebug} -P "${_wpa_pidfile}" -t -f "${_wpa_logfile}" -B -c"/dev/stdin" -i "${_wpaiface}" -D "${_wpadriver}" || {
  334. _wpa_error="$?"
  335. exiterror "$0: Error: wpa_supplicant failed to start with exitcode ${_wpa_error}. Aborting." "${_wpa_error}"
  336. }
  337. }
  338. configure_wifi() {
  339. # Configures the WiFi interface. Arguments:
  340. # $1: Interface
  341. # $2: Mode
  342. # $3: ESSID
  343. # $4: Channel
  344. # $5: Encryption type
  345. # $6: Encryption password
  346. if [ $# -lt 6 ]; then
  347. exiterror "$0: In function 'configure_wifi': Error: Need interface, mode, ESSID, channel, encryption type and encryption password as options."
  348. fi
  349. _wifi_iface="$1"
  350. _wifi_mode="$2"
  351. _wifi_essid="$3"
  352. _wifi_channel="$4"
  353. _wifi_enctype="$5"
  354. _wifi_encpasswd="$6"
  355. configure_wifi_basics "${_wifi_iface}" "${_wifi_mode}" "${_wifi_essid}" "${_wifi_channel}"
  356. case "${_wifi_enctype}" in
  357. "wep")
  358. configure_wifi_wep "${_wifi_iface}" "${_wifi_encpasswd}"
  359. ;;
  360. "wpa")
  361. configure_wifi_wpa "${_wifi_iface}" "${_wifi_essid}" "${_wifi_encpasswd}" "${_wifi_mode}"
  362. ;;
  363. "off")
  364. true # Do not configure encryption.
  365. ;;
  366. *)
  367. exiterror "$0: In function 'configure_wifi': Selected unsupported encryption type '${_wifi_enctype}'. Aborting."
  368. ;;
  369. esac
  370. }
  371. start_dnsmasq() {
  372. _dnsmasq_iface="${_iface}"
  373. _dnsmasq_tftproot="${_tftp_root}"
  374. if [ "$(grep dnsmasq /etc/passwd | cut -d':' -f1 )" == dnsmasq ]; then
  375. _dnsmasq_user=dnsmasq
  376. else
  377. _dnsmasq_user=nobody
  378. fi
  379. if [ "$(grep dnsmasq /etc/group | cut -d':' -f1 )" == dnsmasq ]; then
  380. _dnsmasq_group=dnsmasq
  381. else
  382. _dnsmasq_group=nobody
  383. fi
  384. if "${_debug}"; then
  385. _dnsmasq_debug_options=(
  386. "--log-queries=extra"
  387. "--log-dhcp"
  388. )
  389. elif "${_verbose}"; then
  390. _dnsmasq_debug_options=(
  391. "--log-dhcp"
  392. )
  393. else
  394. _dnsmasq_debug_options=()
  395. fi
  396. _dnsmasq_general_options=(
  397. "--no-hosts"
  398. "--log-facility=${_dnsmasq_logfile}"
  399. "--pid-file=${_dnsmasq_pidfile}"
  400. "--user=${_dnsmasq_user}"
  401. "--group=${_dnsmasq_group}"
  402. "--interface=${_dnsmasq_iface}"
  403. "--bind-dynamic"
  404. # "--bind-interfaces"
  405. "--dhcp-range=${_dhcprange_lower},${_dhcprange_upper},24h"
  406. "--read-ethers"
  407. "--conf-file=/dev/null"
  408. )
  409. if "${_no_tftp}"; then
  410. _dnsmasq_tftp_options=(
  411. )
  412. else
  413. _dnsmasq_tftp_options=(
  414. "--enable-tftp"
  415. "--tftp-root=${_dnsmasq_tftproot}"
  416. "--tftp-no-fail"
  417. )
  418. fi
  419. verbose "Starting dnsmasq."
  420. dnsmasq "${_dnsmasq_debug_options[@]}" "${_dnsmasq_general_options[@]}" "${_dnsmasq_tftp_options[@]}" || {
  421. _dnsmasq_error="$?"
  422. exiterror "$0: Error: dnsmasq failed to start with exitcode ${_dnsmasq_error}. Aborting." "${_dnsmasq_error}"
  423. }
  424. }
  425. kill_by_pidfile() {
  426. # Kills a process by the PID within the file $1. Optional argument $2: A 'fancy name' of the process to use in error messages.
  427. if [ $# -lt 1 ]; then
  428. exiterror "$0: In function 'kill_by_pidfile': Error: Need a PID-file as option."
  429. fi
  430. if [ $# -ge 2 ]; then
  431. _fancyname="$2"
  432. else
  433. _fancyname="process"
  434. fi
  435. _pid="$(cat "$1")"
  436. verbose "Killing ${_fancyname} (PID ${_pid})."
  437. if [ -d "/proc/${_pid}" ]; then
  438. kill "${_pid}" || {
  439. kill -9 "${_pid}" || {
  440. errmsg "$0: Failed to kill ${_fancyname} (PID "${_pid}")".
  441. }
  442. }
  443. else
  444. errmsg "$0: ${_fancyname}'s PID-file $1 is present, but no process under that PID (${_pid}) is running."
  445. fi
  446. }
  447. string_whitelist() {
  448. # Checks the string $1 against a whitelist: Returns one if the string passes, zero if it contains any character not in the whitelist.
  449. # As optional argument $2 a whitelist to be fed to tr can be used, if not specified a default is used.
  450. if [ $# -lt 1 ]; then
  451. exiterror "$0: In function 'string_whitelist': Error: Need a string to test as option."
  452. fi
  453. if [ $# -ge 2 ]; then
  454. _whitelist="$2"
  455. else
  456. _whitelist='[a-zA-Z0-9\._\-]'
  457. fi
  458. if [ -z "$(stdout "$1" | tr -d "${_whitelist}")" ]; then
  459. return 0
  460. else
  461. return 1
  462. fi
  463. }
  464. _iface="${_iface_default}"
  465. _ip="${_ip_default}"
  466. _netmask="${_netmask_default}"
  467. _out_iface_all="${_out_iface_all_default}"
  468. _out_iface="${_out_iface_default}"
  469. # _dhcprange_lower is set after parsing the options.
  470. # _dhcprange_upper is set after parsing the options.
  471. _macaddress_change="${_macaddress_change_default}"
  472. _macaddress="${_macaddress_default}"
  473. _essid="${_essid_default}"
  474. _channel="${_channel_default}"
  475. _encryption="${_encryption_default}"
  476. _enckey="${_enckey_default}"
  477. _wifimode="${_wifimode_default}"
  478. _no_wifi="${_no_wifi_default}"
  479. _no_ipconfig="${_no_ipconfig_default}"
  480. _no_nat="${_no_nat_default}"
  481. _tftp_root="${_tftp_root_default}"
  482. _no_tftp="${_no_tftp_default}"
  483. _verbose="${_verbose_default}"
  484. _debug="${_debug_default}"
  485. _rundir_base="${_rundir_base_default}"
  486. printusage() {
  487. stdout "Usage:"
  488. stdout " $0 action [arguments ...]"
  489. stdout ""
  490. stdout "Actions (exactly one required):"
  491. stdout " up | start Start the thing."
  492. stdout " down | stop Stop the thing."
  493. stdout " stat | status | state | show Show the status of the thing."
  494. stdout " -h | --help | help Print this message and exit."
  495. stdout " -V | --version Print version number and exit. (Version is: '${_version}'.)"
  496. stdout ""
  497. stdout "Arguments (all optional):"
  498. stdout " -h | --help | help Print this message and exit."
  499. stdout " -V | --version Print version number and exit. (Version is: '${_version}'.)"
  500. stdout " -v | --verbose Print information as we go on/ start daemons verbosely."
  501. stdout " -d | --debug Print debug output/ start daemons with debug output."
  502. stdout " Implies verbose."
  503. stdout " -i | --iface <iface> Interface on which the connection should be made available"
  504. stdout " (default: ${_iface_default})."
  505. stdout " Only characters out of the set [a-zA-Z0-9\._\-] are allowed."
  506. stdout " -ip | --ip <ip> IPv4-address to configure this interface to (default: ${_ip_default})."
  507. stdout " -nm | --netmask <netmask> Netmask to use on this interface (default: ${_netmask_default})."
  508. stdout " -m | --mac <MAC-address> Set the MAC-address of this interface (defaults to the"
  509. stdout " interface's native MAC-address)."
  510. stdout " -ni | --no-ipconfig If specified, do not configure IP information for this"
  511. stdout " interface. Useful e.g. if already configured."
  512. stdout " Specifying this option, the following won't be configured:"
  513. stdout " IP-address, netmask, MAC-address."
  514. stdout " -o | --out-iface <iface> If specified, configure masquerading ('NAT') only for"
  515. stdout " packages leaving on this interface (usually this is your"
  516. stdout " interface which connects to the internet, not the one"
  517. stdout " specified by the option '-i' / '--iface')."
  518. stdout " Only characters out of the set [a-zA-Z0-9\._\-] are allowed."
  519. stdout " If not specified, NAT will be configured on all available non-"
  520. stdout " local interfaces (determined by name starting with 'lo')."
  521. stdout " -nn | --no-nat If specified, do not configure and deconfigure network address"
  522. stdout " translation, forwarding and masquerading."
  523. stdout " -dl | --dhcp-lower <dhcp-ip> Lower end of the range of IP-addresses to assign to"
  524. stdout " clients. The default is the first three numbers of our"
  525. stdout " IP-address, and then ${_dhcprange_lower_suffix_default}, e.g. $(make_newip_from_suffix "${_ip_default}" ${_dhcprange_lower_suffix_default})"
  526. stdout " -du | --dhcp-upper <dhcp-ip> Upper end of the range of IP-addresses to assign to"
  527. stdout " clients. The default is the first three numbers of our"
  528. stdout " IP-address, and then ${_dhcprange_upper_suffix_default}, e.g. $(make_newip_from_suffix "${_ip_default}" ${_dhcprange_upper_suffix_default})"
  529. stdout " -wm | --wifi-mode <mode> The WiFi-mode to set the interface to. Allowed modes:"
  530. stdout " 'ad-hoc', 'master', 'managed'. (Default: ${_wifimode_default}.)"
  531. stdout " -e | --essid <ESSID> Set the WiFi ESSID to use (default: ${_essid_default})."
  532. stdout " -c | --channel <wifi-channel> Set the WiFi channel to use (default: ${_channel_default})."
  533. stdout " -enc | --enc <encryption-type> Set the type of WiFi encryption to use. Possible values:"
  534. stdout " 'off', 'wep', 'wpa'. (Default: ${_encryption_default})."
  535. stdout " -key | --key <enc-password> Set the password to use for the WiFi encryption key"
  536. stdout " (default: ${_enckey_default})."
  537. stdout " NOTE: For WEP, the password has to be 5 or 13 characters long."
  538. stdout " For WPA, it has to be between 8 ans 63 characters long."
  539. stdout " -nw | --no-wifi If specified, do not wifi-configure the interface. Useful"
  540. stdout " e.g. if already configured or it's not a WiFi interface."
  541. stdout " Specifying this option, the following won't be configured:"
  542. stdout " WiFi-mode, ESSID, channel, encryption type, encryption key."
  543. stdout " -tf | --tftp-root <dir> Directory where to serve files for TFTP network boot from"
  544. stdout " (default: ${_tftp_root_default})"
  545. stdout " -nt | --no-tftp If specified, do not provide a TFTP server."
  546. stdout " -r | --rundir <directory> Where to store and look for runtime information"
  547. stdout " (default: ${_rundir_base_default}). It get's created if nonextisting."
  548. stdout ""
  549. stdout "(Info: We were started at ${_datetime}.)"
  550. }
  551. _dhcp_lower_explicitly_set=false
  552. _dhcp_upper_explicitly_set=false
  553. if [ $# -lt 1 ]; then
  554. errmsg "$0: Error: Need at least one action specified."
  555. errmsg ""
  556. errmsg "Try option '-h' for a help."
  557. errmsg ""
  558. exiterror "Aborting." 10
  559. fi
  560. _action="$1"
  561. case "${_action}" in
  562. "up"|"start")
  563. _action="up"
  564. ;;
  565. "down"|"stop")
  566. _action="down"
  567. ;;
  568. "stat"|"status"|"state"|"show")
  569. _action="status"
  570. ;;
  571. "-h"|"--help"|"help")
  572. printusage
  573. exit 0
  574. ;;
  575. "-V"|"--version")
  576. msg "${_version}"
  577. exit 0
  578. ;;
  579. *)
  580. errmsg "$0: Error: Invalid action '${_action}' specified."
  581. errmsg ""
  582. errmsg "$(printusage)"
  583. errmsg ""
  584. exiterror "Aborting." 10
  585. ;;
  586. esac
  587. shift
  588. while [ $# -ge 1 ]; do
  589. case "$1" in
  590. "-h"|"--help"|"help")
  591. shift
  592. printusage
  593. exit 0
  594. ;;
  595. "-V"|"--version")
  596. shift
  597. msg "${_version}"
  598. exit 0
  599. ;;
  600. "-i"|"--iface")
  601. shift
  602. if [ $# -ge 1 ]; then
  603. _iface="$1"
  604. else
  605. errmsg "$0: Error: Too few arguments for option '-i' or '--iface': Need to specify an interface."
  606. errmsg ""
  607. errmsg "$(printusage)"
  608. errmsg ""
  609. exiterror "Aborting." 11
  610. fi
  611. shift
  612. ;;
  613. "-ip"|"--ip")
  614. shift
  615. if [ $# -ge 1 ]; then
  616. _ip="$1"
  617. else
  618. errmsg "$0: Error: Too few arguments for option '-ip' or '--ip': Need to specify an IPv4-address."
  619. errmsg ""
  620. errmsg "$(printusage)"
  621. errmsg ""
  622. exiterror "Aborting." 12
  623. fi
  624. shift
  625. ;;
  626. "-nm"|"--netmask")
  627. shift
  628. if [ $# -ge 1 ]; then
  629. _netmask="$1"
  630. else
  631. errmsg "$0: Error: Too few arguments for option '-nm' or '--netmask': Need to specify a netmask."
  632. errmsg ""
  633. errmsg "$(printusage)"
  634. errmsg ""
  635. exiterror "Aborting." 13
  636. fi
  637. shift
  638. ;;
  639. "-ni"|"--no-ipconfig")
  640. shift
  641. _no_ipconfig='true'
  642. ;;
  643. "-o"|"--out-iface")
  644. shift
  645. if [ $# -ge 1 ]; then
  646. _out_iface_all="false"
  647. _out_iface="$1"
  648. else
  649. errmsg "$0: Error: Too few arguments for option '-o' or '--out-iface': Need to specify an interface."
  650. errmsg ""
  651. errmsg "$(printusage)"
  652. errmsg ""
  653. exiterror "Aborting." 16
  654. fi
  655. shift
  656. ;;
  657. "-nn"|"--no-nat")
  658. shift
  659. _no_nat='true'
  660. ;;
  661. "-dl"|"--dhcp-lower")
  662. shift
  663. if [ $# -ge 1 ]; then
  664. _dhcprange_lower="$1"
  665. _dhcp_lower_explicitly_set=true
  666. else
  667. errmsg "$0: Error: Too few arguments for option '-dl' or '--dhcp-lower': Need to specify an IPv4-address."
  668. errmsg ""
  669. errmsg "$(printusage)"
  670. errmsg ""
  671. exiterror "Aborting." 14
  672. fi
  673. shift
  674. ;;
  675. "-du"|"--dhcp-upper")
  676. shift
  677. if [ $# -ge 1 ]; then
  678. _dhcprange_upper="$1"
  679. _dhcp_upper_explicitly_set=true
  680. else
  681. errmsg "$0: Error: Too few arguments for option '-du' or '--dhcp-upper': Need to specify an IPv4-address."
  682. errmsg ""
  683. errmsg "$(printusage)"
  684. errmsg ""
  685. exiterror "Aborting." 15
  686. fi
  687. shift
  688. ;;
  689. "-m"|"--mac")
  690. shift
  691. if [ $# -ge 1 ]; then
  692. _macaddress_change='true'
  693. _macaddress="$1"
  694. else
  695. errmsg "$0: Error: Too few arguments for option '-m' or '--mac': Need to specify a MAC-address."
  696. errmsg ""
  697. errmsg "$(printusage)"
  698. errmsg ""
  699. exiterror "Aborting." 17
  700. fi
  701. shift
  702. ;;
  703. "-wm"|"--wifi-mode")
  704. shift
  705. if [ $# -ge 1 ]; then
  706. _wifimode="$1"
  707. else
  708. errmsg "$0: Error: Too few arguments for option '-wm' or '--wifi-mode': Need to specify a WiFi mode."
  709. errmsg ""
  710. errmsg "$(printusage)"
  711. errmsg ""
  712. exiterror "Aborting." 18
  713. fi
  714. case "${_wifimode}" in
  715. "ad-hoc"|"master"|"managed")
  716. true # Everything is fine.
  717. ;;
  718. *)
  719. errmsg "$0: Not allowed WiFi-mode '${_wifimode}' specified for the '-wm' or '--wifi-mode'-option."
  720. errmsg ""
  721. errmsg "$(printusage)"
  722. errmsg ""
  723. exiterror "Aborting." 2
  724. ;;
  725. esac
  726. shift
  727. ;;
  728. "-e"|"--essid")
  729. shift
  730. if [ $# -ge 1 ]; then
  731. _essid="$1"
  732. else
  733. errmsg "$0: Error: Too few arguments for option '-e' or '--essid': Need to specify a WiFi ESSID."
  734. errmsg ""
  735. errmsg "$(printusage)"
  736. errmsg ""
  737. exiterror "Aborting." 19
  738. fi
  739. shift
  740. ;;
  741. "-c"|"--channel")
  742. shift
  743. if [ $# -ge 1 ]; then
  744. _channel="$1"
  745. else
  746. errmsg "$0: Error: Too few arguments for option '-c' or '--channel': Need to specify a WiFi channel."
  747. errmsg ""
  748. errmsg "$(printusage)"
  749. errmsg ""
  750. exiterror "Aborting." 20
  751. fi
  752. shift
  753. ;;
  754. "-enc"|"--enc")
  755. shift
  756. if [ $# -ge 1 ]; then
  757. _encryption="$1"
  758. else
  759. errmsg "$0: Error: Too few arguments for option '-enc' or '--enc': Need to specify a WiFi encryption type."
  760. errmsg ""
  761. errmsg "$(printusage)"
  762. errmsg ""
  763. exiterror "Aborting." 21
  764. fi
  765. case "${_encryption}" in
  766. "off"|"wep"|"wpa")
  767. true # Everything is fine.
  768. ;;
  769. *)
  770. errmsg "$0: Not allowed WiFi encryption type '${_encryption}' specified for the '-enc' or '--enc'-option."
  771. errmsg ""
  772. errmsg "$(printusage)"
  773. errmsg ""
  774. exiterror "Aborting." 2
  775. ;;
  776. esac
  777. shift
  778. ;;
  779. "-key"|"--key")
  780. shift
  781. if [ $# -ge 1 ]; then
  782. _enckey="$1"
  783. else
  784. errmsg "$0: Error: Too few arguments for option '-key' or '--key': Need to specify a WiFi encryption password."
  785. errmsg ""
  786. errmsg "$(printusage)"
  787. errmsg ""
  788. exiterror "Aborting." 22
  789. fi
  790. _enckeylength="$(stdout -n "${_enckey}" | wc -c)"
  791. case "${_encryption}" in
  792. "wep")
  793. if [ ${_enckeylength} -ne 5 ] && [ ${_enckeylength} -ne 13 ]; then
  794. errmsg "$0: Error: For WEP-encryption, the length of the password has to be 5 or 13 characters. Yours was ${_enckeylength}."
  795. errmsg ""
  796. exiterror "Aborting." 2
  797. fi
  798. ;;
  799. "wpa")
  800. if [ ${_enckeylength} -lt 8 ] || [ ${_enckeylength} -gt 63 ]; then
  801. errmsg "$0: Error: For WPA-encryption, the length of the password has to be between 8 and 63 characters. Yours was ${_enckeylength}."
  802. errmsg ""
  803. exiterror "Aborting." 2
  804. fi
  805. ;;
  806. *)
  807. true # Everything is fine since there is no encryption, so the password does not matter.
  808. ;;
  809. esac
  810. shift
  811. ;;
  812. "-nw"|"--no-wifi")
  813. shift
  814. _no_wifi='true'
  815. ;;
  816. "-v"|"--verbose")
  817. shift
  818. _verbose='true'
  819. ;;
  820. "-d"|"--debug")
  821. shift
  822. _verbose='true'
  823. _debug='true'
  824. ;;
  825. "-tf"|"--tftp-root")
  826. shift
  827. if [ $# -ge 1 ]; then
  828. _tftp_root="$1"
  829. else
  830. errmsg "$0: Error: Too few arguments for option '-tf' or '--tftp-root': Need to specify a directory."
  831. errmsg ""
  832. errmsg "$(printusage)"
  833. errmsg ""
  834. exiterror "Aborting." 23
  835. fi
  836. shift
  837. ;;
  838. "-nt"|"--no-tftp")
  839. shift
  840. _no_tftp='true'
  841. ;;
  842. "-r"|"--rundir")
  843. shift
  844. if [ $# -ge 1 ]; then
  845. _rundir_base="$1"
  846. else
  847. errmsg "$0: Error: Too few arguments for option '-r' or '--rundir': Need to specify a directory."
  848. errmsg ""
  849. errmsg "$(printusage)"
  850. errmsg ""
  851. exiterror "Aborting." 24
  852. fi
  853. shift
  854. ;;
  855. *)
  856. _unknownarg="$1"
  857. shift
  858. errmsg "$0: Error: Unrecognised argument '${_unknownarg}'."
  859. errmsg ""
  860. errmsg "$(printusage)"
  861. errmsg ""
  862. exiterror "Aborting." 1
  863. ;;
  864. esac
  865. done
  866. if ! "${_dhcp_lower_explicitly_set}"; then
  867. _dhcprange_lower="$(make_newip_from_suffix "${_ip}" ${_dhcprange_lower_suffix_default})"
  868. fi
  869. if ! "${_dhcp_upper_explicitly_set}"; then
  870. _dhcprange_upper="$(make_newip_from_suffix "${_ip}" ${_dhcprange_upper_suffix_default})"
  871. fi
  872. string_whitelist "${_iface}" || {
  873. errmsg "$0: Error: Interface name '${_iface}' (specified via the '-i' or '--iface'-option or set as a default) contains invalid characters."
  874. errmsg "See help."
  875. exiterror "Aborting."
  876. }
  877. _instance="${_iface}"
  878. _rundir="${_rundir_base}/${_instance}"
  879. _wpa_logfilebase='wpa_supplicant.log'
  880. _wpa_ctrlfilebase='wpa_supplicant.ctrl'
  881. _wpa_pidfilebase='wpa_supplicant.pid'
  882. _dnsmasq_logfilebase='dnsmasq.log'
  883. _dnsmasq_pidfilebase='dnsmasq.pid'
  884. _origmacfilebase='macaddress.orig'
  885. _origifconfigfilebase='ifconfig.orig'
  886. _wificonfiguredfilebase='wifi.orig'
  887. _startedatfilebase='datetime.txt'
  888. _rp_filter_statusfilebase='rp_filter.orig'
  889. _ipv4_forward_statusfilebase='ipv4_forward.orig'
  890. _nat_configuredfilebase='nat_configured'
  891. _wpa_ctrl="${_rundir}/${_wpa_ctrlfilebase}"
  892. _wpa_pidfile="${_rundir}/${_wpa_pidfilebase}"
  893. _wpa_logfile="${_rundir}/${_wpa_logfilebase}"
  894. _dnsmasq_logfile="${_rundir}/${_dnsmasq_logfilebase}"
  895. _dnsmasq_pidfile="${_rundir}/${_dnsmasq_pidfilebase}"
  896. _origmacfile="${_rundir}/${_origmacfilebase}"
  897. _origifconfigfile="${_rundir}/${_origifconfigfilebase}"
  898. _wificonfiguredfile="${_rundir}/${_wificonfiguredfilebase}"
  899. _startedatfile="${_rundir}/${_startedatfilebase}"
  900. _rp_filter_statusfile="${_rundir}/${_rp_filter_statusfilebase}"
  901. _ipv4_forward_statusfile="${_rundir}/${_ipv4_forward_statusfilebase}"
  902. _nat_configuredfile="${_rundir}/${_nat_configuredfilebase}"
  903. if "${_verbose}"; then
  904. _mkdirverbose="-v"
  905. _rmverbose="-v"
  906. fi
  907. verbose "Started at ${_datetime}."
  908. case "${_action}" in
  909. "up")
  910. mkdir ${_mkdirverbose} -p "${_rundir}"
  911. cd "${_rundir}"
  912. stdout "${_datetime}" > "${_startedatfile}"
  913. if "${_macaddress_change}"; then
  914. get_macaddress "${_iface}" > "${_origmacfile}"
  915. set_macaddress "${_iface}" "${_macaddress}"
  916. fi
  917. if ! "${_no_wifi}"; then
  918. stdout "We configured WiFi, so we take it down afterwards. (In fact, only the existence of this file matters, not it's content.)" > "${_wificonfiguredfile}"
  919. configure_wifi "${_iface}" "${_wifimode}" "${_essid}" "${_channel}" "${_encryption}" "${_enckey}"
  920. fi
  921. if ! "${_no_ipconfig}"; then
  922. get_ipconfig "${_iface}" > "${_origifconfigfile}"
  923. configure_ip "${_iface}" "inet ${_ip} netmask ${_netmask} up"
  924. fi
  925. if ! "${_no_nat}"; then
  926. _nat_interfaces="$(get_nat_interfaces | tr '\n' ' ')"
  927. configure_nat "${_nat_interfaces}"
  928. stdout "${_nat_interfaces}" > "${_nat_configuredfile}"
  929. fi
  930. start_dnsmasq
  931. ;;
  932. "down")
  933. if [ -d "${_rundir}" ]; then
  934. cd "${_rundir}"
  935. else
  936. errmsg "$0: Cannot bring down, since specified runtime information directory ${_rundir} does not exist."
  937. fi
  938. if [ -e "${_nat_configuredfile}" ]; then
  939. deconfigure_nat
  940. rm -f "${_nat_configuredfile}"
  941. fi
  942. if [ -e "${_wpa_pidfile}" ]; then
  943. kill_by_pidfile "${_wpa_pidfile}" "wpa_supplicant"
  944. rm -f "${_wpa_pidfile}"
  945. fi
  946. if [ -e "${_dnsmasq_pidfile}" ]; then
  947. kill_by_pidfile "${_dnsmasq_pidfile}" "dnsmasq"
  948. rm -f "${_dnsmasq_pidfile}"
  949. fi
  950. if [ -e "${_origifconfigfile}" ]; then
  951. configure_ip "${_iface}" "$(cat "${_origifconfigfile}")"
  952. rm -f "${_origifconfigfile}"
  953. fi
  954. if [ -e "${_wificonfiguredfile}" ]; then
  955. configure_wifi "${_iface}" managed any 0 off "${_enckey_default}"
  956. rm -f "${_wificonfiguredfile}"
  957. fi
  958. if [ -e "${_origmacfile}" ]; then
  959. set_macaddress "${_iface}" "$(cat "${_origmacfile}")"
  960. rm -f "${_origmacfile}"
  961. fi
  962. rm -f "${_startedatfile}"
  963. rm -Rf "${_rundir}"/*
  964. rmdir ${_rmverbose} "${_rundir}"
  965. ;;
  966. "status")
  967. _instances="$(find "${_rundir_base}" -mindepth 1 -maxdepth 1 -type d)"
  968. if [ -z "${_instances}" ]; then
  969. stdout "No runtime information present at '${_rundir_base}/'. Probably nothing running."
  970. else
  971. cd "${_rundir_base}"
  972. for _inst in ${_instances}; do
  973. _inst="$(basename "${_inst}")"
  974. _instdir="${_rundir_base}/${_inst}"
  975. stdout "${_inst}:"
  976. {
  977. [ -e "${_instdir}/${_startedatfilebase}" ] && stdout "Started: $(cat "${_instdir}/${_startedatfilebase}")" || true
  978. stdout -n "$(get_ipconfig "${_inst}")"; [ -e "${_instdir}/${_origifconfigfilebase}" ] && {
  979. stdout -n " (Old: $(cat "${_instdir}/${_origifconfigfilebase}")),"
  980. } || true
  981. stdout -n " "
  982. stdout -n "ether $(get_macaddress "${_inst}")"; [ -e "${_instdir}/${_origmacfilebase}" ] && {
  983. stdout -n " (Old: $(cat "${_instdir}/${_origmacfilebase}"))"
  984. } || true
  985. stdout ""
  986. [ -e "${_instdir}/${_nat_configuredfilebase}" ] && {
  987. stdout -n "NAT: Configured by us for interfaces $(cat "${_instdir}/${_nat_configuredfilebase}")."
  988. } || {
  989. stdout -n "NAT: Not configured by us."
  990. }
  991. stdout ""
  992. [ -e "${_instdir}/${_wpa_pidfilebase}" ] && {
  993. stdout -n "wpa_supplicant: PID: $(cat "${_instdir}/${_wpa_pidfilebase}"), "; [ -d "/proc/$(cat "${_instdir}/${_wpa_pidfilebase}")" ] && stdout -n "Running." || stdout -n "Not running."
  994. } || {
  995. stdout -n "wpa_supplicant: Could not determine PID."
  996. }
  997. [ -e "${_instdir}/${_wpa_logfilebase}" ] && stdout -n " Logfile: '${_instdir}/${_wpa_logfilebase}'." || true
  998. stdout ""
  999. stdout -n "dnsmasq: "; [ -e "${_instdir}/${_dnsmasq_pidfilebase}" ] && {
  1000. stdout -n "PID: $(cat "${_instdir}/${_dnsmasq_pidfilebase}"), "; [ -d "/proc/$(cat "${_instdir}/${_dnsmasq_pidfilebase}")" ] && stdout -n "Running." || stdout -n "Not running."
  1001. } || {
  1002. stdout -n "Could not determine PID."
  1003. }
  1004. [ -e "${_instdir}/${_dnsmasq_logfilebase}" ] && stdout -n " Logfile: '${_instdir}/${_dnsmasq_logfilebase}'." || true
  1005. stdout ""
  1006. } | stdout_prefix " "
  1007. stdout ""
  1008. done
  1009. fi
  1010. ;;
  1011. *)
  1012. exiterror "$0: Error: Unknown action '${_action}'. Aborting."
  1013. ;;
  1014. esac