ynh-vpnclient 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321
  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. # Cleanup #
  48. ###################################################################################
  49. cleanup() {
  50. local last_exit_code="$?"
  51. if [[ "${action}" != "stop" && "${last_exit_code}" -ne 0 ]]; then
  52. rm -f /tmp/.ynh-vpnclient-started
  53. fi
  54. }
  55. # Cleanup before exit
  56. trap cleanup 0
  57. ###################################################################################
  58. # Time sync #
  59. ###################################################################################
  60. sync_time() {
  61. info "Now synchronizing time using ntp..."
  62. systemctl stop ntp
  63. timeout 20 ntpd -qg &> /dev/null
  64. # Some networks drop ntp port (udp 123).
  65. # Try to get the date with an http request on the internetcube web site
  66. if [ $? -ne 0 ]; then
  67. info "ntp synchronization failed, falling back to curl method"
  68. http_date=$(curl --max-time 5 -sD - yunohost.org | grep -i '^Date:' | cut -d' ' -f2-)
  69. http_date_seconds=$(date -d "${http_date}" +%s)
  70. curr_date_seconds=$(date +%s)
  71. # Set the new date if it's greater than the current date
  72. # So it does if 1970 year or if old fake-hwclock date is used
  73. if [ $http_date_seconds -ge $curr_date_seconds ]; then
  74. date -s "${http_date}"
  75. fi
  76. fi
  77. systemctl start ntp
  78. }
  79. ###################################################################################
  80. # The actual ynh vpnclient management thing #
  81. ###################################################################################
  82. check_config() {
  83. info "Checking if configuration is valid..."
  84. if [[ ! -e /etc/openvpn/keys/ca-server.crt ]]; then
  85. critical "You need a CA server (you can add it through the web admin)"
  86. fi
  87. if ! openssl x509 -in /etc/openvpn/keys/ca-server.crt -noout -checkend 0 >/dev/null; then
  88. ca_server_cert_expired_date=$(openssl x509 -in /etc/openvpn/keys/ca-server.crt -noout -enddate | cut -d '=' -f 2)
  89. critical "The CA server expired on $ca_server_cert_expired_date"
  90. fi
  91. if [[ ! -e /etc/openvpn/keys/user.crt || ! -e /etc/openvpn/keys/user.key ]]; then
  92. if [[ -s /etc/openvpn/keys/credentials ]]; then
  93. login_user=$(sed -n 1p /etc/openvpn/keys/credentials)
  94. login_passphrase=$(sed -n 2p /etc/openvpn/keys/credentials)
  95. else
  96. login_user=""
  97. login_passphrase=""
  98. fi
  99. if [[ $login_user == "" || $login_passphrase == "" ]]; then
  100. critical "You need either a client certificate, either a username, or both (you can add one through the web admin)"
  101. fi
  102. elif [[ -e /etc/openvpn/keys/user.crt ]] && ! openssl x509 -in /etc/openvpn/keys/user.crt -noout -checkend 0 >/dev/null; then
  103. user_cert_expired_date=$(openssl x509 -in /etc/openvpn/keys/user.crt -noout -enddate | cut -d '=' -f 2)
  104. critical "The client certificate expired on $user_cert_expired_date"
  105. fi
  106. }
  107. is_firewall_set() {
  108. local wired_device="$1"
  109. ip6tables -w -nvL OUTPUT | grep vpnclient_out | grep -q "${wired_device}" \
  110. && iptables -w -nvL OUTPUT | grep vpnclient_out | grep -q "${wired_device}"
  111. }
  112. set_firewall() {
  113. info "Applying firewall rules..."
  114. local wired_device=$(ip route | awk '/default via/ { print $5; }')
  115. if ! is_firewall_set "${wired_device}"; then
  116. bash /etc/yunohost/apps/vpnclient/conf/hook_post-iptable-rules
  117. fi
  118. cp /etc/yunohost/apps/vpnclient/conf/hook_post-iptable-rules /etc/yunohost/hooks.d/post_iptable_rules/90-vpnclient
  119. if is_firewall_set "${wired_device}"; then
  120. success "IPv6/IPv4 firewall set"
  121. else
  122. error "No IPv6/IPv4 firewall set" >&2
  123. fi
  124. }
  125. unset_firewall() {
  126. info "Removing firewall rules..."
  127. local wired_device=$(ip route | awk '/default via/ { print $5; }')
  128. rm -f /etc/yunohost/hooks.d/post_iptable_rules/90-vpnclient
  129. if is_firewall_set "${wired_device}"; then
  130. # IPv4
  131. iptables -w -D INPUT -i "${wired_device}" -j vpnclient_in
  132. iptables -w -D OUTPUT -o "${wired_device}" -j vpnclient_out
  133. iptables -w -D FORWARD -o "${wired_device}" -j vpnclient_fwd
  134. iptables -w -F vpnclient_in
  135. iptables -w -F vpnclient_out
  136. iptables -w -F vpnclient_fwd
  137. iptables -w -X vpnclient_in
  138. iptables -w -X vpnclient_out
  139. iptables -w -X vpnclient_fwd
  140. # IPv6
  141. ip6tables -w -D INPUT -i "${wired_device}" -j vpnclient_in
  142. ip6tables -w -D OUTPUT -o "${wired_device}" -j vpnclient_out
  143. ip6tables -w -D FORWARD -o "${wired_device}" -j vpnclient_fwd
  144. ip6tables -w -F vpnclient_in
  145. ip6tables -w -F vpnclient_out
  146. ip6tables -w -F vpnclient_fwd
  147. ip6tables -w -X vpnclient_in
  148. ip6tables -w -X vpnclient_out
  149. ip6tables -w -X vpnclient_fwd
  150. fi
  151. }
  152. action=${1}
  153. if [[ "$action" != restart ]]; then
  154. # Variables
  155. info "Retrieving Yunohost settings... "
  156. ynh_service_enabled=$(yunohost app setting "vpnclient" "service_enabled")
  157. success "Settings retrieved"
  158. fi
  159. ###################################################################################
  160. # Start / stop / restart / status handling #
  161. ###################################################################################
  162. case "$action" in
  163. # ########## #
  164. # Starting #
  165. # ########## #
  166. start)
  167. info "[vpnclient] Starting..."
  168. if [[ -e /tmp/.ynh-vpnclient.started ]] || systemctl -q is-active openvpn@client.service; then
  169. info "Service is already running"
  170. exit 0
  171. elif [[ "${ynh_service_enabled}" -eq 0 ]]; then
  172. warn "Service is disabled, not starting it"
  173. exit 0
  174. fi
  175. touch /tmp/.ynh-vpnclient-started
  176. sync_time
  177. check_config
  178. set_firewall
  179. info "Now actually starting OpenVPN client..."
  180. if systemctl start openvpn@client.service; then
  181. info "OpenVPN client started ... waiting for tun0 interface to show up"
  182. else
  183. tail -n 20 /var/log/openvpn-client.log | tee -a $LOGFILE
  184. critical "Failed to start OpenVPN :/"
  185. fi
  186. has_errors=true
  187. for attempt in $(seq 0 20); do
  188. sleep 1
  189. if ip link show dev tun0 &> /dev/null; then
  190. success "tun0 interface is up!"
  191. has_errors=false
  192. break
  193. fi
  194. done
  195. if $has_errors; then
  196. 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"
  197. tail -n 20 /var/log/openvpn-client.log | tee -a $LOGFILE
  198. systemctl stop openvpn@client.service
  199. critical "Failed to start OpenVPN client : tun0 interface did not show up"
  200. fi
  201. info "Waiting for VPN client to be ready..."
  202. if ! timeout 180 tail -n 0 -f /var/log/openvpn-client.log | grep -q "Initialization Sequence Completed"; then
  203. error "The VPN client didn't complete initiliasation"
  204. tail -n 20 /var/log/openvpn-client.log | tee -a $LOGFILE
  205. systemctl stop openvpn@client.service
  206. critical "Failed to start OpenVPN client"
  207. fi
  208. info "Validating that VPN is up and the server is connected to internet..."
  209. ipv4=$(timeout 5 ping -w3 -c1 ip.yunohost.org >/dev/null 2>&1 && curl --max-time 5 https://ip.yunohost.org --silent)
  210. ipv6=$(timeout 5 ping -w3 -c1 ip6.yunohost.org >/dev/null 2>&1 && curl --max-time 5 https://ip6.yunohost.org --silent)
  211. if ip route get 1.2.3.4 | grep -q tun0; then
  212. if timeout 5 ping -c1 -w3 debian.org >/dev/null; then
  213. success "YunoHost VPN client started!"
  214. info "IPv4 address is $ipv4"
  215. info "IPv6 address is $ipv6"
  216. else
  217. critical "The VPN is up but debian.org cannot be reached, indicating that something is probably misconfigured/blocked."
  218. fi
  219. else
  220. critical "IPv4 routes are misconfigured !?"
  221. fi
  222. ;;
  223. # ########## #
  224. # Stopping #
  225. # ########## #
  226. stop)
  227. info "[vpnclient] Stopping..."
  228. rm -f /tmp/.ynh-vpnclient-started
  229. if systemctl is-active -q openvpn@client.service; then
  230. info "Stopping OpenVPN service"
  231. systemctl stop openvpn@client.service
  232. for attempt in $(seq 0 20); do
  233. if ip link show dev tun0 &> /dev/null; then
  234. info "(Waiting for tun0 to disappear if it was up)"
  235. sleep 1
  236. fi
  237. done
  238. fi
  239. unset_firewall
  240. ;;
  241. # ########## #
  242. # Restart #
  243. # ########## #
  244. restart)
  245. $0 stop
  246. $0 start
  247. ;;
  248. # ########## #
  249. # Halp #
  250. # ########## #
  251. *)
  252. echo "Usage: $0 {start|stop|restart}"
  253. ;;
  254. esac
  255. exit 0