ynh-vpnclient 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609
  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 $LOFILE
  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. cat << EOF > /etc/dhcp/dhclient-exit-hooks.d/ynh-vpnclient
  117. echo nameserver ${ynh_dns0} > ${resolvconf}
  118. echo nameserver ${ynh_dns1} >> ${resolvconf}
  119. EOF
  120. bash /etc/dhcp/dhclient-exit-hooks.d/ynh-vpnclient
  121. }
  122. unset_dns() {
  123. resolvconf=/etc/resolv.conf
  124. [ -e /etc/resolv.dnsmasq.conf ] && resolvconf=/etc/resolv.dnsmasq.conf
  125. info "Removing custom DNS resolvers from vpnclient"
  126. rm -f /etc/dhcp/dhclient-exit-hooks.d/ynh-vpnclient
  127. mv "${resolvconf}.ynh" "${resolvconf}"
  128. # FIXME : this situation happened to a user ...
  129. # We could try to force regen the dns conf
  130. # (though for now it's tightly coupled to dnsmasq)
  131. grep -q "^nameserver" "${resolvconf}" || error "${resolvconf} does not have any nameserver line !?"
  132. }
  133. ###################################################################################
  134. # Firewall rules management #
  135. ###################################################################################
  136. is_firewall_set() {
  137. wired_device=${1}
  138. ip6tables -w -nvL OUTPUT | grep vpnclient_out | grep -q "${wired_device}"\
  139. && iptables -w -nvL OUTPUT | grep vpnclient_out | grep -q "${wired_device}"
  140. }
  141. set_firewall() {
  142. info "Adding vpnclient custom rules to the firewall"
  143. wired_device=${1}
  144. cp /etc/yunohost/hooks.d/{90-vpnclient.tpl,post_iptable_rules/90-vpnclient}
  145. sed "s|<TPL:SERVER_NAME>|${ynh_server_name}|g" -i /etc/yunohost/hooks.d/post_iptable_rules/90-vpnclient
  146. sed "s|<TPL:SERVER_PORT>|${ynh_server_port}|g" -i /etc/yunohost/hooks.d/post_iptable_rules/90-vpnclient
  147. sed "s|<TPL:PROTO>|${ynh_server_proto}|g" -i /etc/yunohost/hooks.d/post_iptable_rules/90-vpnclient
  148. sed "s|<TPL:WIRED_DEVICE>|${wired_device}|g" -i /etc/yunohost/hooks.d/post_iptable_rules/90-vpnclient
  149. sed "s|<TPL:DNS0>|${ynh_dns0}|g" -i /etc/yunohost/hooks.d/post_iptable_rules/90-vpnclient
  150. sed "s|<TPL:DNS1>|${ynh_dns1}|g" -i /etc/yunohost/hooks.d/post_iptable_rules/90-vpnclient
  151. yunohost firewall reload
  152. }
  153. unset_firewall() {
  154. info "Cleaning vpnclient custom rules from the firewall"
  155. rm -f /etc/yunohost/hooks.d/post_iptable_rules/90-vpnclient
  156. yunohost firewall reload
  157. }
  158. ###################################################################################
  159. # Time sync #
  160. ###################################################################################
  161. sync_time() {
  162. info "Now synchronizing time using ntp..."
  163. systemctl stop ntp
  164. timeout 20 ntpd -qg &> /dev/null
  165. # Some networks drop ntp port (udp 123).
  166. # Try to get the date with an http request on the internetcube web site
  167. if [ $? -ne 0 ]; then
  168. info "ntp synchronization failed, falling back to curl method"
  169. http_date=`curl -sD - labriqueinter.net | grep '^Date:' | cut -d' ' -f3-6`
  170. http_date_seconds=`date -d "${http_date}" +%s`
  171. curr_date_seconds=`date +%s`
  172. # Set the new date if it's greater than the current date
  173. # So it does if 1970 year or if old fake-hwclock date is used
  174. if [ $http_date_seconds -ge $curr_date_seconds ]; then
  175. date -s "${http_date}"
  176. fi
  177. fi
  178. systemctl start ntp
  179. }
  180. ###################################################################################
  181. # OpenVPN client start/stop procedures #
  182. ###################################################################################
  183. is_openvpn_running() {
  184. systemctl is-active openvpn@client.service &> /dev/null
  185. }
  186. start_openvpn() {
  187. info "Preparing openvpn configuration..."
  188. ip6_gw=${1}
  189. server_ip6=${2}
  190. if [ ! -z "${ip6_gw}" -a ! -z "${server_ip6}" ]; then
  191. proto=udp6
  192. [ "${ynh_server_proto}" == tcp ] && proto=tcp6-client
  193. else
  194. proto=udp
  195. [ "${ynh_server_proto}" == tcp ] && proto=tcp-client
  196. fi
  197. # Unset firewall to let DNS and NTP resolution works
  198. # Firewall is reset after vpn is mounted (more details on #1016)
  199. unset_firewall
  200. sync_time
  201. info "Preparing openvpn configuration..."
  202. cp /etc/openvpn/client.conf{.tpl,}
  203. sed "s|<TPL:SERVER_NAME>|${ynh_server_name}|g" -i /etc/openvpn/client.conf
  204. sed "s|<TPL:SERVER_PORT>|${ynh_server_port}|g" -i /etc/openvpn/client.conf
  205. sed "s|<TPL:PROTO>|${proto}|g" -i /etc/openvpn/client.conf
  206. if [ -e /etc/openvpn/keys/user.key ]; then
  207. sed 's|^<TPL:CERT_COMMENT>||' -i /etc/openvpn/client.conf
  208. else
  209. sed 's|^<TPL:CERT_COMMENT>|;|' -i /etc/openvpn/client.conf
  210. fi
  211. if [ -e /etc/openvpn/keys/user_ta.key ]; then
  212. sed 's|^<TPL:TA_COMMENT>||' -i /etc/openvpn/client.conf
  213. else
  214. sed 's|^<TPL:TA_COMMENT>|;|' -i /etc/openvpn/client.conf
  215. fi
  216. if [[ "${proto}" =~ udp ]]; then
  217. sed 's|^<TPL:UDP_COMMENT>||' -i /etc/openvpn/client.conf
  218. else
  219. sed 's|^<TPL:UDP_COMMENT>|;|' -i /etc/openvpn/client.conf
  220. fi
  221. if [ -z "${ynh_login_user}" ]; then
  222. sed 's|^<TPL:LOGIN_COMMENT>|;|' -i /etc/openvpn/client.conf
  223. else
  224. sed 's|^<TPL:LOGIN_COMMENT>||' -i /etc/openvpn/client.conf
  225. fi
  226. info "Now actually starting OpenVPN client..."
  227. systemctl start openvpn@client.service
  228. if [ ! $? -eq 0 ]
  229. then
  230. tail -n 20 /var/log/openvpn-client.log
  231. critical "Failed to start OpenVPN :/"
  232. else
  233. info "OpenVPN client started ... waiting for tun0 interface to show up"
  234. fi
  235. for attempt in $(seq 0 20)
  236. do
  237. sleep 1
  238. if ip link show dev tun0 &> /dev/null
  239. then
  240. success "tun0 interface is up!"
  241. return 0
  242. fi
  243. done
  244. tail -n 20 /var/log/openvpn-client.log
  245. error "Tun0 interface did not show up ... possibly an issue with OpenVPN client"
  246. stop_openvpn && exit 1
  247. }
  248. stop_openvpn() {
  249. # FIXME : isn't openvpn@client ? (idk)
  250. info "Stopping OpenVPN service"
  251. systemctl stop openvpn.service
  252. info "(Waiting for tun0 to disappear if it was up)"
  253. for attempt in $(seq 0 20)
  254. do
  255. if ip link show dev tun0 &> /dev/null
  256. then
  257. sleep 1
  258. fi
  259. done
  260. }
  261. ###################################################################################
  262. # Yunohost settings interface #
  263. ###################################################################################
  264. ynh_setting_get() {
  265. app=${1}
  266. setting=${2}
  267. grep "^${setting}:" "/etc/yunohost/apps/${app}/settings.yml" | sed s/^[^:]\\+:\\s*[\"\']\\?// | sed s/\\s*[\"\']\$//
  268. }
  269. ynh_setting_set() {
  270. app=${1}
  271. setting=${2}
  272. value=${3}
  273. yunohost app setting "${app}" "${setting}" -v "${value}"
  274. }
  275. ###################################################################################
  276. # The actual ynh vpnclient management thing #
  277. ###################################################################################
  278. is_running() {
  279. ((has_nativeip6 && is_serverip6route_set "${new_server_ip6}") || ! has_nativeip6)\
  280. && ((! has_hotspot_app && has_ip6delegatedprefix && is_ip6addr_set) || has_hotspot_app || ! has_ip6delegatedprefix)\
  281. && is_dns_set && is_firewall_set && is_openvpn_running
  282. }
  283. if [ "$1" != restart ]; then
  284. # Restart php-fpm at the first start (it needs to be restarted after the slapd start)
  285. if [ ! -e /tmp/.ynh-vpnclient-boot ]; then
  286. touch /tmp/.ynh-vpnclient-boot
  287. systemctl restart php7.0-fpm
  288. fi
  289. # Check configuration consistency
  290. if [[ ! "${1}" =~ stop ]]; then
  291. if [ ! -e /etc/openvpn/keys/ca-server.crt ]; then
  292. critical "You need a CA server (you can add it through the web admin)"
  293. fi
  294. empty=$(find /etc/openvpn/keys/ -empty -name credentials &> /dev/null | wc -l)
  295. if [ "${empty}" -gt 0 -a ! -e /etc/openvpn/keys/user.key ]; then
  296. critical "You need either a client certificate, either a username, or both (you can add one through the web admin)"
  297. fi
  298. fi
  299. # Variables
  300. info "Retrieving Yunohost settings... "
  301. ynh_service_enabled=$(ynh_setting_get vpnclient service_enabled)
  302. ynh_server_name=$(ynh_setting_get vpnclient server_name)
  303. ynh_server_port=$(ynh_setting_get vpnclient server_port)
  304. ynh_server_proto=$(ynh_setting_get vpnclient server_proto)
  305. ynh_ip6_addr=$(ynh_setting_get vpnclient ip6_addr)
  306. ynh_login_user=$(ynh_setting_get vpnclient login_user)
  307. ynh_dns0=$(ynh_setting_get vpnclient dns0)
  308. ynh_dns1=$(ynh_setting_get vpnclient dns1)
  309. old_ip6_gw=$(ynh_setting_get vpnclient ip6_gw)
  310. old_wired_device=$(ynh_setting_get vpnclient wired_device)
  311. old_server_ip6=$(ynh_setting_get vpnclient server_ip6)
  312. new_ip6_gw=$(ip -6 route | grep default\ via | awk '{ print $3 }')
  313. new_wired_device=$(ip route | awk '/default via/ { print $NF; }')
  314. new_server_ip6=$(host "${ynh_server_name}" 2> /dev/null | awk '/IPv6/ { print $NF; }')
  315. if [ -z "${new_server_ip6}" ]; then
  316. # FIXME wtf is this hardcoded IP ...
  317. new_server_ip6=$(host "${ynh_server_name}" 80.67.188.188 2> /dev/null | awk '/IPv6/ { print $NF; }')
  318. fi
  319. success "Settings retrieved"
  320. fi
  321. ###################################################################################
  322. # Start / stop / restart / status handling #
  323. ###################################################################################
  324. case "${1}" in
  325. # ########## #
  326. # Starting #
  327. # ########## #
  328. start)
  329. if is_running; then
  330. info "Service is already running"
  331. exit 0
  332. elif [ "${ynh_service_enabled}" -eq 0 ]; then
  333. warn "Service is disabled, not starting it"
  334. exit 0
  335. fi
  336. info "[vpnclient] Starting..."
  337. touch /tmp/.ynh-vpnclient-started
  338. # Run openvpn
  339. if is_openvpn_running;
  340. then
  341. info "(openvpn is already running)"
  342. else
  343. start_openvpn "${new_ip6_gw}" "${new_server_ip6}"
  344. fi
  345. # Check old state of the server ipv6 route
  346. if [ ! -z "${old_server_ip6}" -a ! -z "${old_ip6_gw}" -a ! -z "${old_wired_device}"\
  347. -a \( "${new_server_ip6}" != "${old_server_ip6}" -o "${new_ip6_gw}" != "${old_ip6_gw}"\
  348. -o "${new_wired_device}" != "${old_wired_device}" \) ]\
  349. && is_serverip6route_set "${old_server_ip6}"
  350. then
  351. unset_serverip6route "${old_server_ip6}" "${old_ip6_gw}" "${old_wired_device}"
  352. fi
  353. # Set the new server ipv6 route
  354. if has_nativeip6 && ! is_serverip6route_set "${new_server_ip6}"
  355. then
  356. set_serverip6route "${new_server_ip6}" "${new_ip6_gw}" "${new_wired_device}"
  357. fi
  358. # Set the ipv6 address
  359. if ! has_hotspot_app && has_ip6delegatedprefix && ! is_ip6addr_set
  360. then
  361. set_ip6addr
  362. fi
  363. # Set host DNS resolvers
  364. if ! is_dns_set
  365. then
  366. set_dns
  367. fi
  368. # Set ipv6/ipv4 firewall
  369. if ! is_firewall_set "${new_wired_device}"
  370. then
  371. set_firewall "${new_wired_device}"
  372. fi
  373. # Update dynamic settings
  374. ynh_setting_set vpnclient server_ip6 "${new_server_ip6}"
  375. ynh_setting_set vpnclient ip6_gw "${new_ip6_gw}"
  376. ynh_setting_set vpnclient wired_device "${new_wired_device}"
  377. # Fix configuration
  378. if has_hotspot_app && ! is_hotspot_knowme; then
  379. ynh-hotspot start
  380. fi
  381. ;;
  382. # ########## #
  383. # Stopping #
  384. # ########## #
  385. stop)
  386. info "[vpnclient] Stopping..."
  387. rm -f /tmp/.ynh-vpnclient-started
  388. if ! has_hotspot_app && has_ip6delegatedprefix && is_ip6addr_set; then
  389. unset_ip6addr
  390. fi
  391. if is_serverip6route_set "${old_server_ip6}"; then
  392. unset_serverip6route "${old_server_ip6}" "${old_ip6_gw}" "${old_wired_device}"
  393. fi
  394. is_firewall_set "${old_wired_device}" && unset_firewall
  395. is_dns_set && unset_dns
  396. is_openvpn_running && stop_openvpn
  397. # Fix configuration
  398. if has_hotspot_app && is_hotspot_knowme; then
  399. ynh-hotspot start
  400. fi
  401. ;;
  402. # ########## #
  403. # Restart #
  404. # ########## #
  405. restart)
  406. $0 stop
  407. $0 start
  408. ;;
  409. # ########## #
  410. # Status #
  411. # ########## #
  412. status)
  413. exitcode=0
  414. if [ "${ynh_service_enabled}" -eq 0 ]; then
  415. error "VPN Client Service disabled"
  416. exitcode=1
  417. fi
  418. info "Autodetected internet interface: ${new_wired_device} (last start: ${old_wired_device})"
  419. info "Autodetected IPv6 address for the VPN server: ${new_server_ip6} (last start: ${old_server_ip6})"
  420. if has_ip6delegatedprefix; then
  421. info "IPv6 delegated prefix found"
  422. info "IPv6 address computed from the delegated prefix: ${ynh_ip6_addr}"
  423. if ! has_hotspot_app; then
  424. info "No Hotspot app detected"
  425. if is_ip6addr_set; then
  426. success "IPv6 address correctly set"
  427. else
  428. error "No IPv6 address set"
  429. exitcode=1
  430. fi
  431. else
  432. info "Hotspot app detected"
  433. info "No IPv6 address to set"
  434. fi
  435. else
  436. info "No IPv6 delegated prefix found"
  437. fi
  438. if has_nativeip6; then
  439. info "Native IPv6 detected"
  440. info "Autodetected native IPv6 gateway: ${new_ip6_gw} (last start: ${old_ip6_gw})"
  441. if is_serverip6route_set "${new_server_ip6}"; then
  442. success "IPv6 server route correctly set"
  443. else
  444. error "No IPv6 server route set"
  445. exitcode=1
  446. fi
  447. else
  448. info "No native IPv6 detected"
  449. info "No IPv6 server route to set"
  450. fi
  451. if is_firewall_set "${new_wired_device}"; then
  452. success "IPv6/IPv4 firewall set"
  453. else
  454. info "No IPv6/IPv4 firewall set"
  455. exitcode=1
  456. fi
  457. if is_dns_set; then
  458. success "Host DNS correctly set"
  459. else
  460. error "No host DNS set"
  461. exitcode=1
  462. fi
  463. if is_openvpn_running; then
  464. success "Openvpn is running"
  465. else
  466. error "Openvpn is not running"
  467. exitcode=1
  468. fi
  469. exit ${exitcode}
  470. ;;
  471. # ########## #
  472. # Halp #
  473. # ########## #
  474. *)
  475. echo "Usage: $0 {start|stop|restart|status}"
  476. exit 1
  477. ;;
  478. esac
  479. exit 0