ynh-vpnclient 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324
  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. # We need to remove firewall rules in order to sync time
  177. unset_firewall
  178. sync_time
  179. check_config
  180. set_firewall
  181. info "Now actually starting OpenVPN client..."
  182. if systemctl start openvpn@client.service; then
  183. info "OpenVPN client started ... waiting for tun0 interface to show up"
  184. else
  185. tail -n 20 /var/log/openvpn-client.log | tee -a $LOGFILE
  186. critical "Failed to start OpenVPN :/"
  187. fi
  188. has_errors=true
  189. for attempt in $(seq 0 20); do
  190. sleep 1
  191. if ip link show dev tun0 &> /dev/null; then
  192. success "tun0 interface is up!"
  193. has_errors=false
  194. break
  195. fi
  196. done
  197. if $has_errors; then
  198. 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"
  199. tail -n 20 /var/log/openvpn-client.log | tee -a $LOGFILE
  200. systemctl stop openvpn@client.service
  201. critical "Failed to start OpenVPN client : tun0 interface did not show up"
  202. fi
  203. info "Waiting for VPN client to be ready..."
  204. if ! timeout 180 tail -n 0 -f /var/log/openvpn-client.log | grep -q "Initialization Sequence Completed"; then
  205. error "The VPN client didn't complete initiliasation"
  206. tail -n 20 /var/log/openvpn-client.log | tee -a $LOGFILE
  207. systemctl stop openvpn@client.service
  208. critical "Failed to start OpenVPN client"
  209. fi
  210. info "Validating that VPN is up and the server is connected to internet..."
  211. ipv4=$(timeout 5 ping -w3 -c1 ip.yunohost.org >/dev/null 2>&1 && curl --max-time 5 https://ip.yunohost.org --silent)
  212. ipv6=$(timeout 5 ping -w3 -c1 ip6.yunohost.org >/dev/null 2>&1 && curl --max-time 5 https://ip6.yunohost.org --silent)
  213. if ip route get 1.2.3.4 | grep -q tun0; then
  214. if timeout 5 ping -c1 -w3 debian.org >/dev/null; then
  215. success "YunoHost VPN client started!"
  216. info "IPv4 address is $ipv4"
  217. info "IPv6 address is $ipv6"
  218. else
  219. critical "The VPN is up but debian.org cannot be reached, indicating that something is probably misconfigured/blocked."
  220. fi
  221. else
  222. critical "IPv4 routes are misconfigured !?"
  223. fi
  224. ;;
  225. # ########## #
  226. # Stopping #
  227. # ########## #
  228. stop)
  229. info "[vpnclient] Stopping..."
  230. rm -f /tmp/.ynh-vpnclient-started
  231. if systemctl is-active -q openvpn@client.service; then
  232. info "Stopping OpenVPN service"
  233. systemctl stop openvpn@client.service
  234. for attempt in $(seq 0 20); do
  235. if ip link show dev tun0 &> /dev/null; then
  236. info "(Waiting for tun0 to disappear if it was up)"
  237. sleep 1
  238. fi
  239. done
  240. fi
  241. unset_firewall
  242. ;;
  243. # ########## #
  244. # Restart #
  245. # ########## #
  246. restart)
  247. $0 stop
  248. $0 start
  249. ;;
  250. # ########## #
  251. # Halp #
  252. # ########## #
  253. *)
  254. echo "Usage: $0 {start|stop|restart}"
  255. ;;
  256. esac
  257. exit 0