ynh-vpnclient 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620
  1. #!/bin/bash
  2. # VPN Client app for YunoHost
  3. # Copyright (C) 2015 Julien Vaubourg <julien@vaubourg.com>
  4. # Contribute at https://github.com/labriqueinternet/vpnclient_ynh
  5. #
  6. # This program is free software: you can redistribute it and/or modify
  7. # it under the terms of the GNU Affero General Public License as published by
  8. # the Free Software Foundation, either version 3 of the License, or
  9. # (at your option) any later version.
  10. #
  11. # This program is distributed in the hope that it will be useful,
  12. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. # GNU Affero General Public License for more details.
  15. #
  16. # You should have received a copy of the GNU Affero General Public License
  17. # along with this program. If not, see <http://www.gnu.org/licenses/>.
  18. ###################################################################################
  19. # Logging helpers #
  20. ###################################################################################
  21. LOGFILE="/var/log/ynh-vpnclient.log"
  22. touch $LOGFILE
  23. chown root:root $LOGFILE
  24. chmod 600 $LOGFILE
  25. function success()
  26. {
  27. echo "[ OK ] $1" | tee -a $LOGFILE
  28. }
  29. function info()
  30. {
  31. echo "[INFO] $1" | tee -a $LOGFILE
  32. }
  33. function warn()
  34. {
  35. echo "[WARN] $1" | tee -a $LOGFILE >&2
  36. }
  37. function error()
  38. {
  39. echo "[FAIL] $1" | tee -a $LOGFILE >&2
  40. }
  41. function critical()
  42. {
  43. echo "[CRIT] $1" | tee -a $LOGFILE >&2
  44. exit 1
  45. }
  46. ###################################################################################
  47. # IPv6 and route config stuff #
  48. ###################################################################################
  49. has_nativeip6() {
  50. ip -6 route | grep -q default\ via
  51. }
  52. has_ip6delegatedprefix() {
  53. [ "${ynh_ip6_addr}" != none ]
  54. }
  55. is_ip6addr_set() {
  56. ip address show dev tun0 2> /dev/null | grep -q "${ynh_ip6_addr}/128"
  57. }
  58. set_ip6addr() {
  59. info "Adding IPv6 from VPN configuration"
  60. ip address add "${ynh_ip6_addr}/128" dev tun0
  61. }
  62. unset_ip6addr() {
  63. info "Removing IPv6 from VPN configuration"
  64. ip address delete "${ynh_ip6_addr}/128" dev tun0
  65. }
  66. #
  67. # Server IPv6 route
  68. #
  69. is_serverip6route_set() {
  70. server_ip6=${1}
  71. if [ -z "${server_ip6}" ]; then
  72. false
  73. else
  74. ip -6 route | grep -q "${server_ip6}/"
  75. fi
  76. }
  77. set_serverip6route() {
  78. server_ip6=${1}
  79. ip6_gw=${2}
  80. wired_device=${3}
  81. info "Adding IPv6 server route"
  82. ip route add "${server_ip6}/128" via "${ip6_gw}" dev "${wired_device}"
  83. }
  84. unset_serverip6route() {
  85. server_ip6=${1}
  86. ip6_gw=${2}
  87. wired_device=${3}
  88. info "Removing IPv6 server route"
  89. ip route delete "${server_ip6}/128" via "${ip6_gw}" dev "${wired_device}"
  90. }
  91. ###################################################################################
  92. # Hotspot app #
  93. ###################################################################################
  94. has_hotspot_app() {
  95. [ -e /tmp/.ynh-hotspot-started ]
  96. }
  97. is_hotspot_knowme() {
  98. hotspot_vpnclient=$(ynh_setting_get hotspot vpnclient)
  99. [ "${hotspot_vpnclient}" == yes ]
  100. }
  101. ###################################################################################
  102. # DNS rules #
  103. ###################################################################################
  104. is_dns_set() {
  105. # FIXME : having the ynh_dns0 in the resolv.dnsmasq.conf is not necessarily good enough
  106. # We want it to be the only one (with ynh_dns1) but nowadays for example ARN's resolver is
  107. # in the default list from yunohost...
  108. [ -e /etc/dhcp/dhclient-exit-hooks.d/ynh-vpnclient ]\
  109. && ( grep -q "${ynh_dns0}" /etc/resolv.conf || grep -q "${ynh_dns0}" /etc/resolv.dnsmasq.conf )
  110. }
  111. set_dns() {
  112. info "Enforcing custom DNS resolvers from vpnclient"
  113. resolvconf=/etc/resolv.conf
  114. [ -e /etc/resolv.dnsmasq.conf ] && resolvconf=/etc/resolv.dnsmasq.conf
  115. cp -fa "${resolvconf}" "${resolvconf}.ynh"
  116. if [ -n "${ynh_dns0}" ] && [ -n "${ynh_dns1}" ]
  117. then
  118. cat << EOF > /etc/dhcp/dhclient-exit-hooks.d/ynh-vpnclient
  119. echo nameserver ${ynh_dns0} > ${resolvconf}
  120. echo nameserver ${ynh_dns1} >> ${resolvconf}
  121. EOF
  122. bash /etc/dhcp/dhclient-exit-hooks.d/ynh-vpnclient
  123. elif [ -n "${ynh_dns0}" ]
  124. then
  125. cat << EOF > /etc/dhcp/dhclient-exit-hooks.d/ynh-vpnclient
  126. echo nameserver ${ynh_dns0} > ${resolvconf}
  127. EOF
  128. bash /etc/dhcp/dhclient-exit-hooks.d/ynh-vpnclient
  129. else
  130. warn "Uhoh, neither dns0 nor dns1 is defined ... You should probably configure these in the vpnclient interface !"
  131. fi
  132. }
  133. unset_dns() {
  134. resolvconf=/etc/resolv.conf
  135. [ -e /etc/resolv.dnsmasq.conf ] && resolvconf=/etc/resolv.dnsmasq.conf
  136. info "Removing custom DNS resolvers from vpnclient"
  137. rm -f /etc/dhcp/dhclient-exit-hooks.d/ynh-vpnclient
  138. mv "${resolvconf}.ynh" "${resolvconf}"
  139. # FIXME : this situation happened to a user ...
  140. # We could try to force regen the dns conf
  141. # (though for now it's tightly coupled to dnsmasq)
  142. grep -q "^nameserver" "${resolvconf}" || error "${resolvconf} does not have any nameserver line !?"
  143. }
  144. ###################################################################################
  145. # Firewall rules management #
  146. ###################################################################################
  147. is_firewall_set() {
  148. wired_device=${1}
  149. ip6tables -w -nvL OUTPUT | grep vpnclient_out | grep -q "${wired_device}"\
  150. && iptables -w -nvL OUTPUT | grep vpnclient_out | grep -q "${wired_device}"
  151. }
  152. set_firewall() {
  153. info "Adding vpnclient custom rules to the firewall"
  154. wired_device=${1}
  155. cp /etc/yunohost/hooks.d/{90-vpnclient.tpl,post_iptable_rules/90-vpnclient}
  156. sed "s|<TPL:SERVER_NAME>|${ynh_server_name}|g" -i /etc/yunohost/hooks.d/post_iptable_rules/90-vpnclient
  157. sed "s|<TPL:SERVER_PORT>|${ynh_server_port}|g" -i /etc/yunohost/hooks.d/post_iptable_rules/90-vpnclient
  158. sed "s|<TPL:PROTO>|${ynh_server_proto}|g" -i /etc/yunohost/hooks.d/post_iptable_rules/90-vpnclient
  159. sed "s|<TPL:WIRED_DEVICE>|${wired_device}|g" -i /etc/yunohost/hooks.d/post_iptable_rules/90-vpnclient
  160. sed "s|<TPL:DNS0>|${ynh_dns0}|g" -i /etc/yunohost/hooks.d/post_iptable_rules/90-vpnclient
  161. sed "s|<TPL:DNS1>|${ynh_dns1}|g" -i /etc/yunohost/hooks.d/post_iptable_rules/90-vpnclient
  162. info "Restarting yunohost firewall..."
  163. yunohost firewall reload && success "Firewall restarted!"
  164. }
  165. unset_firewall() {
  166. info "Cleaning vpnclient custom rules from the firewall"
  167. rm -f /etc/yunohost/hooks.d/post_iptable_rules/90-vpnclient
  168. info "Restarting yunohost firewall..."
  169. yunohost firewall reload && success "Firewall restarted!"
  170. }
  171. ###################################################################################
  172. # Time sync #
  173. ###################################################################################
  174. sync_time() {
  175. info "Now synchronizing time using ntp..."
  176. systemctl stop ntp
  177. timeout 20 ntpd -qg &> /dev/null
  178. # Some networks drop ntp port (udp 123).
  179. # Try to get the date with an http request on the internetcube web site
  180. if [ $? -ne 0 ]; then
  181. info "ntp synchronization failed, falling back to curl method"
  182. http_date=`curl -sD - labriqueinter.net | grep '^Date:' | cut -d' ' -f3-6`
  183. http_date_seconds=`date -d "${http_date}" +%s`
  184. curr_date_seconds=`date +%s`
  185. # Set the new date if it's greater than the current date
  186. # So it does if 1970 year or if old fake-hwclock date is used
  187. if [ $http_date_seconds -ge $curr_date_seconds ]; then
  188. date -s "${http_date}"
  189. fi
  190. fi
  191. systemctl start ntp
  192. }
  193. ###################################################################################
  194. # OpenVPN client start/stop procedures #
  195. ###################################################################################
  196. is_openvpn_running() {
  197. systemctl is-active openvpn@client.service &> /dev/null
  198. }
  199. start_openvpn() {
  200. ip6_gw=${1}
  201. server_ip6=${2}
  202. if [ ! -z "${ip6_gw}" -a ! -z "${server_ip6}" ]; then
  203. proto=udp6
  204. [ "${ynh_server_proto}" == tcp ] && proto=tcp6-client
  205. else
  206. proto=udp
  207. [ "${ynh_server_proto}" == tcp ] && proto=tcp-client
  208. fi
  209. # Unset firewall to let DNS and NTP resolution works
  210. # Firewall is reset after vpn is mounted (more details on #1016)
  211. unset_firewall
  212. sync_time
  213. info "Preparing openvpn configuration..."
  214. cp /etc/openvpn/client.conf{.tpl,}
  215. sed "s|<TPL:SERVER_NAME>|${ynh_server_name}|g" -i /etc/openvpn/client.conf
  216. sed "s|<TPL:SERVER_PORT>|${ynh_server_port}|g" -i /etc/openvpn/client.conf
  217. sed "s|<TPL:PROTO>|${proto}|g" -i /etc/openvpn/client.conf
  218. if [ -e /etc/openvpn/keys/user.key ]; then
  219. sed 's|^<TPL:CERT_COMMENT>||' -i /etc/openvpn/client.conf
  220. else
  221. sed 's|^<TPL:CERT_COMMENT>|;|' -i /etc/openvpn/client.conf
  222. fi
  223. if [ -e /etc/openvpn/keys/user_ta.key ]; then
  224. sed 's|^<TPL:TA_COMMENT>||' -i /etc/openvpn/client.conf
  225. else
  226. sed 's|^<TPL:TA_COMMENT>|;|' -i /etc/openvpn/client.conf
  227. fi
  228. if [[ "${proto}" =~ udp ]]; then
  229. sed 's|^<TPL:UDP_COMMENT>||' -i /etc/openvpn/client.conf
  230. else
  231. sed 's|^<TPL:UDP_COMMENT>|;|' -i /etc/openvpn/client.conf
  232. fi
  233. if [ -z "${ynh_login_user}" ]; then
  234. sed 's|^<TPL:LOGIN_COMMENT>|;|' -i /etc/openvpn/client.conf
  235. else
  236. sed 's|^<TPL:LOGIN_COMMENT>||' -i /etc/openvpn/client.conf
  237. fi
  238. info "Now actually starting OpenVPN client..."
  239. systemctl start openvpn@client.service
  240. if [ ! $? -eq 0 ]
  241. then
  242. tail -n 20 /var/log/openvpn-client.log | tee -a $LOGFILE
  243. critical "Failed to start OpenVPN :/"
  244. else
  245. info "OpenVPN client started ... waiting for tun0 interface to show up"
  246. fi
  247. for attempt in $(seq 0 20)
  248. do
  249. sleep 1
  250. if ip link show dev tun0 &> /dev/null
  251. then
  252. success "tun0 interface is up!"
  253. return 0
  254. fi
  255. done
  256. error "Tun0 interface did not show up ... most likely an issue happening in OpenVPN client ... below is an extract of the log that might be relevant to pinpoint the issue"
  257. tail -n 20 /var/log/openvpn-client.log | tee -a $LOGFILE
  258. stop_openvpn
  259. critical "Failed to start OpenVPN client : tun0 interface did not show up"
  260. }
  261. stop_openvpn() {
  262. # FIXME : isn't openvpn@client ? (idk)
  263. info "Stopping OpenVPN service"
  264. systemctl stop openvpn.service
  265. for attempt in $(seq 0 20)
  266. do
  267. if ip link show dev tun0 &> /dev/null
  268. then
  269. info "(Waiting for tun0 to disappear if it was up)"
  270. sleep 1
  271. fi
  272. done
  273. }
  274. ###################################################################################
  275. # Yunohost settings interface #
  276. ###################################################################################
  277. ynh_setting_get() {
  278. app=${1}
  279. setting=${2}
  280. grep "^${setting}:" "/etc/yunohost/apps/${app}/settings.yml" | sed s/^[^:]\\+:\\s*[\"\']\\?// | sed s/\\s*[\"\']\$//
  281. }
  282. ynh_setting_set() {
  283. app=${1}
  284. setting=${2}
  285. value=${3}
  286. yunohost app setting "${app}" "${setting}" -v "${value}"
  287. }
  288. ###################################################################################
  289. # The actual ynh vpnclient management thing #
  290. ###################################################################################
  291. is_running() {
  292. ((has_nativeip6 && is_serverip6route_set "${new_server_ip6}") || ! has_nativeip6)\
  293. && ((! has_hotspot_app && has_ip6delegatedprefix && is_ip6addr_set) || has_hotspot_app || ! has_ip6delegatedprefix)\
  294. && is_dns_set && is_firewall_set && is_openvpn_running
  295. }
  296. if [ "$1" != restart ]; then
  297. # Check configuration consistency
  298. if [[ ! "${1}" =~ stop ]]; then
  299. if [ ! -e /etc/openvpn/keys/ca-server.crt ]; then
  300. critical "You need a CA server (you can add it through the web admin)"
  301. fi
  302. empty=$(find /etc/openvpn/keys/ -empty -name credentials &> /dev/null | wc -l)
  303. if [ "${empty}" -gt 0 -a ! -e /etc/openvpn/keys/user.key ]; then
  304. critical "You need either a client certificate, either a username, or both (you can add one through the web admin)"
  305. fi
  306. fi
  307. # Variables
  308. info "Retrieving Yunohost settings... "
  309. ynh_service_enabled=$(ynh_setting_get vpnclient service_enabled)
  310. ynh_server_name=$(ynh_setting_get vpnclient server_name)
  311. ynh_server_port=$(ynh_setting_get vpnclient server_port)
  312. ynh_server_proto=$(ynh_setting_get vpnclient server_proto)
  313. ynh_ip6_addr=$(ynh_setting_get vpnclient ip6_addr)
  314. ynh_login_user=$(ynh_setting_get vpnclient login_user)
  315. ynh_dns0=$(ynh_setting_get vpnclient dns0)
  316. ynh_dns1=$(ynh_setting_get vpnclient dns1)
  317. old_ip6_gw=$(ynh_setting_get vpnclient ip6_gw)
  318. old_wired_device=$(ynh_setting_get vpnclient wired_device)
  319. old_server_ip6=$(ynh_setting_get vpnclient server_ip6)
  320. new_ip6_gw=$(ip -6 route | grep default\ via | awk '{ print $3 }')
  321. new_wired_device=$(ip route | awk '/default via/ { print $NF; }')
  322. new_server_ip6=$(host "${ynh_server_name}" 2> /dev/null | awk '/IPv6/ { print $NF; }')
  323. if [ -z "${new_server_ip6}" ]; then
  324. # FIXME wtf is this hardcoded IP ...
  325. new_server_ip6=$(host "${ynh_server_name}" 80.67.188.188 2> /dev/null | awk '/IPv6/ { print $NF; }')
  326. fi
  327. success "Settings retrieved"
  328. fi
  329. ###################################################################################
  330. # Start / stop / restart / status handling #
  331. ###################################################################################
  332. case "${1}" in
  333. # ########## #
  334. # Starting #
  335. # ########## #
  336. start)
  337. if is_running; then
  338. info "Service is already running"
  339. exit 0
  340. elif [ "${ynh_service_enabled}" -eq 0 ]; then
  341. warn "Service is disabled, not starting it"
  342. exit 0
  343. fi
  344. info "[vpnclient] Starting..."
  345. touch /tmp/.ynh-vpnclient-started
  346. # Run openvpn
  347. if is_openvpn_running;
  348. then
  349. info "(openvpn is already running)"
  350. else
  351. start_openvpn "${new_ip6_gw}" "${new_server_ip6}"
  352. fi
  353. # Check old state of the server ipv6 route
  354. if [ ! -z "${old_server_ip6}" -a ! -z "${old_ip6_gw}" -a ! -z "${old_wired_device}"\
  355. -a \( "${new_server_ip6}" != "${old_server_ip6}" -o "${new_ip6_gw}" != "${old_ip6_gw}"\
  356. -o "${new_wired_device}" != "${old_wired_device}" \) ]\
  357. && is_serverip6route_set "${old_server_ip6}"
  358. then
  359. unset_serverip6route "${old_server_ip6}" "${old_ip6_gw}" "${old_wired_device}"
  360. fi
  361. # Set the new server ipv6 route
  362. if has_nativeip6 && ! is_serverip6route_set "${new_server_ip6}"
  363. then
  364. set_serverip6route "${new_server_ip6}" "${new_ip6_gw}" "${new_wired_device}"
  365. fi
  366. # Set the ipv6 address
  367. if ! has_hotspot_app && has_ip6delegatedprefix && ! is_ip6addr_set
  368. then
  369. set_ip6addr
  370. fi
  371. # Set host DNS resolvers
  372. if ! is_dns_set
  373. then
  374. set_dns
  375. fi
  376. # Set ipv6/ipv4 firewall
  377. if ! is_firewall_set "${new_wired_device}"
  378. then
  379. set_firewall "${new_wired_device}"
  380. fi
  381. # Update dynamic settings
  382. info "Saving settings..."
  383. ynh_setting_set vpnclient server_ip6 "${new_server_ip6}"
  384. ynh_setting_set vpnclient ip6_gw "${new_ip6_gw}"
  385. ynh_setting_set vpnclient wired_device "${new_wired_device}"
  386. # Fix configuration
  387. if has_hotspot_app && ! is_hotspot_knowme; then
  388. info "Now starting the hotspot"
  389. ynh-hotspot start
  390. fi
  391. success "YunoHost VPN client started!"
  392. ;;
  393. # ########## #
  394. # Stopping #
  395. # ########## #
  396. stop)
  397. info "[vpnclient] Stopping..."
  398. rm -f /tmp/.ynh-vpnclient-started
  399. if ! has_hotspot_app && has_ip6delegatedprefix && is_ip6addr_set; then
  400. unset_ip6addr
  401. fi
  402. if is_serverip6route_set "${old_server_ip6}"; then
  403. unset_serverip6route "${old_server_ip6}" "${old_ip6_gw}" "${old_wired_device}"
  404. fi
  405. is_firewall_set "${old_wired_device}" && unset_firewall
  406. is_dns_set && unset_dns
  407. is_openvpn_running && stop_openvpn
  408. # Fix configuration
  409. if has_hotspot_app && is_hotspot_knowme; then
  410. info "Now starting the hotspot"
  411. ynh-hotspot start
  412. fi
  413. ;;
  414. # ########## #
  415. # Restart #
  416. # ########## #
  417. restart)
  418. $0 stop
  419. $0 start
  420. ;;
  421. # ########## #
  422. # Status #
  423. # ########## #
  424. status)
  425. exitcode=0
  426. if [ "${ynh_service_enabled}" -eq 0 ]; then
  427. error "VPN Client Service disabled"
  428. exitcode=1
  429. fi
  430. info "Autodetected internet interface: ${new_wired_device} (last start: ${old_wired_device})"
  431. info "Autodetected IPv6 address for the VPN server: ${new_server_ip6} (last start: ${old_server_ip6})"
  432. if has_ip6delegatedprefix; then
  433. info "IPv6 delegated prefix found"
  434. info "IPv6 address computed from the delegated prefix: ${ynh_ip6_addr}"
  435. if ! has_hotspot_app; then
  436. info "No Hotspot app detected"
  437. if is_ip6addr_set; then
  438. success "IPv6 address correctly set"
  439. else
  440. error "No IPv6 address set"
  441. exitcode=1
  442. fi
  443. else
  444. info "Hotspot app detected"
  445. info "No IPv6 address to set"
  446. fi
  447. else
  448. info "No IPv6 delegated prefix found"
  449. fi
  450. if has_nativeip6; then
  451. info "Native IPv6 detected"
  452. info "Autodetected native IPv6 gateway: ${new_ip6_gw} (last start: ${old_ip6_gw})"
  453. if is_serverip6route_set "${new_server_ip6}"; then
  454. success "IPv6 server route correctly set"
  455. else
  456. error "No IPv6 server route set"
  457. exitcode=1
  458. fi
  459. else
  460. info "No native IPv6 detected"
  461. info "No IPv6 server route to set"
  462. fi
  463. if is_firewall_set "${new_wired_device}"; then
  464. success "IPv6/IPv4 firewall set"
  465. else
  466. info "No IPv6/IPv4 firewall set"
  467. exitcode=1
  468. fi
  469. if is_dns_set; then
  470. success "Host DNS correctly set"
  471. else
  472. error "No host DNS set"
  473. exitcode=1
  474. fi
  475. if is_openvpn_running; then
  476. success "Openvpn is running"
  477. else
  478. error "Openvpn is not running"
  479. exitcode=1
  480. fi
  481. exit ${exitcode}
  482. ;;
  483. # ########## #
  484. # Halp #
  485. # ########## #
  486. *)
  487. echo "Usage: $0 {start|stop|restart|status}"
  488. exit 1
  489. ;;
  490. esac
  491. exit 0