ynh-vpnclient 16 KB

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