#!/bin/bash

# VPN Client app for YunoHost
# Copyright (C) 2015 Julien Vaubourg <julien@vaubourg.com>
# Contribute at https://github.com/labriqueinternet/vpnclient_ynh
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

###################################################################################
# Logging helpers                                                                 #
###################################################################################

LOGFILE="/var/log/ynh-vpnclient.log"
touch $LOGFILE
chown root:root $LOGFILE
chmod 600 $LOGFILE

function success()
{
  echo "[ OK ] $1" | tee -a $LOGFILE
}

function info()
{
  echo "[INFO] $1" | tee -a $LOGFILE
}

function warn()
{
  echo "[WARN] $1" | tee -a $LOGFILE >&2
}

function error()
{
  echo "[FAIL] $1" | tee -a $LOGFILE >&2
}

function critical()
{
  echo "[CRIT] $1" | tee -a $LOGFILE >&2
  exit 1
}

###################################################################################
# Cleanup                                                                         #
###################################################################################

cleanup() {
  local last_exit_code="$?"
  if [[ "${action}" != "stop" && "${last_exit_code}" -ne 0 ]]; then
    rm -f /tmp/.ynh-vpnclient-started
  fi
}

# Cleanup before exit
trap cleanup 0

###################################################################################
# Time sync                                                                       #
###################################################################################

sync_time() {
  info "Now synchronizing time using ntp..."
  systemctl stop ntp
  timeout 20 ntpd -qg &> /dev/null
  
  # Some networks drop ntp port (udp 123). 
  # Try to get the date with an http request on the internetcube web site
  if [ $? -ne 0 ]; then
    info "ntp synchronization failed, falling back to curl method"
    http_date=$(curl --max-time 5 -sD - yunohost.org | grep -i '^Date:' | cut -d' ' -f2-)
    http_date_seconds=$(date -d "${http_date}" +%s)
    curr_date_seconds=$(date +%s)

    # Set the new date if it's greater than the current date
    # So it does if 1970 year or if old fake-hwclock date is used
    if [ $http_date_seconds -ge $curr_date_seconds ]; then
      date -s "${http_date}"
    fi
  fi 
  systemctl start ntp
}

###################################################################################
# The actual ynh vpnclient management thing                                       #
###################################################################################

check_config() {
  info "Checking if configuration is valid..."

  if [[ ! -e /etc/openvpn/keys/ca-server.crt ]]; then
    critical "You need a CA server (you can add it through the web admin)"
  fi

  if ! openssl x509 -in /etc/openvpn/keys/ca-server.crt -noout -checkend 0 >/dev/null; then
    ca_server_cert_expired_date=$(openssl x509 -in /etc/openvpn/keys/ca-server.crt -noout -enddate | cut -d '=' -f 2)
    critical "The CA server expired on $ca_server_cert_expired_date"
  fi

  if [[ ! -e /etc/openvpn/keys/user.crt || ! -e /etc/openvpn/keys/user.key ]]; then
    if [[ -s /etc/openvpn/keys/credentials ]]; then
      login_user=$(sed -n 1p /etc/openvpn/keys/credentials)
      login_passphrase=$(sed -n 2p /etc/openvpn/keys/credentials)
    else
      login_user=""
      login_passphrase=""
    fi

    if [[ $login_user == "" || $login_passphrase == "" ]]; then
      critical "You need either a client certificate, either a username, or both (you can add one through the web admin)"
    fi
  elif [[ -e /etc/openvpn/keys/user.crt ]] && ! openssl x509 -in /etc/openvpn/keys/user.crt -noout -checkend 0 >/dev/null; then
    user_cert_expired_date=$(openssl x509 -in /etc/openvpn/keys/user.crt -noout -enddate | cut -d '=' -f 2)
    critical "The client certificate expired on $user_cert_expired_date"
  fi
}

action=${1}
if [[ "$action" != restart ]]; then
  # Variables

  info "Retrieving Yunohost settings... "

  ynh_service_enabled=$(yunohost app setting "vpnclient" "service_enabled")

  success "Settings retrieved"
fi

###################################################################################
# Start / stop / restart / status handling                                        #
###################################################################################

case "$action" in

  # ########## #
  #  Starting  #
  # ########## #

  start)
    info "[vpnclient] Starting..."
    
    if [[ -e /tmp/.ynh-vpnclient.started ]] || systemctl -q is-active openvpn@client.service; then
      info "Service is already running"
      exit 0
    elif [[ "${ynh_service_enabled}" -eq 0 ]]; then
      warn "Service is disabled, not starting it"
      exit 0
    fi

    touch /tmp/.ynh-vpnclient-started

    sync_time
    check_config

    info "Now actually starting OpenVPN client..."

    if systemctl start openvpn@client.service; then
      info "OpenVPN client started ... waiting for tun0 interface to show up"
    else
      tail -n 20 /var/log/openvpn-client.log | tee -a $LOGFILE
	    critical "Failed to start OpenVPN :/"
    fi

    has_errors=true
    for attempt in $(seq 0 20); do
      sleep 1
      if ip link show dev tun0 &> /dev/null; then
        success "tun0 interface is up!"
        has_errors=false
        break
      fi
    done
  
    if $has_errors; then
      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"
      tail -n 20 /var/log/openvpn-client.log | tee -a $LOGFILE
      systemctl stop openvpn@client.service
      critical "Failed to start OpenVPN client : tun0 interface did not show up"
    fi

    info "Waiting for VPN client to be ready..."
    if ! timeout 180 tail -n 0 -f /var/log/openvpn-client.log | grep -q "Initialization Sequence Completed"; then
      error "The VPN client didn't complete initiliasation"
      tail -n 20 /var/log/openvpn-client.log | tee -a $LOGFILE
      systemctl stop openvpn@client.service
      critical "Failed to start OpenVPN client"
    fi

    info "Validating that VPN is up and the server is connected to internet..."

    ipv4=$(timeout 5 ping -w3 -c1 ip.yunohost.org  >/dev/null 2>&1 && curl --max-time 5 https://ip.yunohost.org --silent)
    ipv6=$(timeout 5 ping -w3 -c1 ip6.yunohost.org >/dev/null 2>&1 && curl --max-time 5 https://ip6.yunohost.org --silent)

    if ip route get 1.2.3.4 | grep -q tun0; then
      if timeout 5 ping -c1 -w3 debian.org >/dev/null; then
        success "YunoHost VPN client started!"
        info "IPv4 address is $ipv4"
        info "IPv6 address is $ipv6"
      else
        critical "The VPN is up but debian.org cannot be reached, indicating that something is probably misconfigured/blocked."
      fi
    else
      critical "IPv4 routes are misconfigured !?"
    fi
  ;;

  # ########## #
  #  Stopping  #
  # ########## #

  stop)
    info "[vpnclient] Stopping..."
    rm -f /tmp/.ynh-vpnclient-started

    if systemctl is-active -q openvpn@client.service; then
      info "Stopping OpenVPN service"
      systemctl stop openvpn@client.service

      for attempt in $(seq 0 20); do
        if ip link show dev tun0 &> /dev/null; then
          info "(Waiting for tun0 to disappear if it was up)"
          sleep 1
        fi
      done
    fi
  ;;

  # ########## #
  #  Restart   #
  # ########## #

  restart)
    $0 stop
    $0 start
  ;;

  # ########## #
  #    Halp    #
  # ########## #

  *)
    echo "Usage: $0 {start|stop|restart}"
  ;;
esac

exit 0