#!/usr/bin/env bash
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements.  See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership.  The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License.  You may obtain a copy of the License at
#
#   http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied.  See the License for the
# specific language governing permissions and limitations
# under the License.

source /root/func.sh

vpnconfdir="/etc/ipsec.d"
vpnoutmark="0x525"
vpninmark="0x524"

usage() {
    printf "Usage: %s: (-A|-D) -l <left-side vpn peer> -n <left-side guest cidr> -g <left-side next hop> -r <right-side vpn peer> -N <right-side private subnets> -e <esp policy> -i <ike policy> -t <ike lifetime> -T <esp lifetime> -s <pre-shared secret> -d <dpd 0 or 1> [ -p <passive or not> -c <check if up on creation> -S <disable vpn ports iptables> -C <ike split connections or not, default not> -v <ike version, default:'ike'> ]\n" $(basename $0) >&2
}

#set -x

start_ipsec() {
  systemctl is-active ipsec > /dev/null
  if [ $? -ne 0 ]
  then
    systemctl start ipsec > /dev/null
    #Wait until ipsec started, 5 seconds at most
    for i in {1..5}
    do
      logger -t cloud "$(basename $0): waiting ipsec start..."
      systemctl is-active ipsec > /dev/null
      result=$?
      if [ $result -eq 0 ]
      then
          break
      fi
      sleep 1
    done
  fi
  systemctl is-active ipsec > /dev/null
  return $?
}

enable_iptables_subnets() {
  for net in $rightnets
  do
    sudo iptables -I FORWARD -t mangle -s $leftnet -d $net -j MARK --set-mark $vpnoutmark
    sudo iptables -A OUTPUT -t mangle -s $leftnet -d $net -j MARK --set-mark $vpnoutmark
    sudo iptables -I FORWARD -t mangle -s $net -d $leftnet -j MARK --set-mark $vpninmark
    sudo iptables -A INPUT -t mangle -s $net -d $leftnet -j MARK --set-mark $vpninmark
  done
  return 0
}

#
# Add the right side here to close the gap, so we're sure no one else comes in
#   also double check the default behaviour of ipsec to drop if wrong....
check_and_enable_iptables() {
  sudo iptables-save | grep "A INPUT -i $outIf -p udp -m udp --dport 500 -j ACCEPT"
  if [ $? -ne 0 ]
  then
      sudo iptables -A INPUT -i $outIf -p udp -m udp --dport 500 $iptables_secure -j ACCEPT
      sudo iptables -A INPUT -i $outIf -p udp -m udp --dport 4500 $iptables_secure -j ACCEPT
      sudo iptables -A INPUT -i $outIf -p 50 $iptables_secure -j ACCEPT
      # Prevent NAT on "marked" VPN traffic, so need to be the first one on POSTROUTING chain
      sudo iptables -t nat -I POSTROUTING -t nat -o $outIf -m mark --mark $vpnoutmark -j ACCEPT
  fi
  return 0
}

disable_iptables_subnets() {
  for net in $rightnets
  do
    sudo iptables -D FORWARD -t mangle -s $leftnet -d $net -j MARK --set-mark $vpnoutmark
    sudo iptables -D OUTPUT -t mangle -s $leftnet -d $net -j MARK --set-mark $vpnoutmark
    sudo iptables -D FORWARD -t mangle -s $net -d $leftnet -j MARK --set-mark $vpninmark
    sudo iptables -D INPUT -t mangle -s $net -d $leftnet -j MARK --set-mark $vpninmark
  done
  return 0
}

check_and_disable_iptables() {
  find $vpnconfdir -name "ipsec.vpn*.conf" | grep ipsec
  if [ $? -ne 0 ]
  then
    #Nobody else use s2s vpn now, so delete the iptables rules
    sudo iptables -D INPUT -i $outIf -p udp -m udp --dport 500 $iptables_secure -j ACCEPT
    sudo iptables -D INPUT -i $outIf -p udp -m udp --dport 4500 $iptables_secure -j ACCEPT
    sudo iptables -D INPUT -i $outIf -p 50 $iptables_secure -j ACCEPT
    sudo iptables -t nat -D POSTROUTING -t nat -o $outIf -m mark --mark $vpnoutmark -j ACCEPT
  fi
  return 0
}

ipsec_tunnel_del() {
  disable_iptables_subnets
  sudo ipsec auto --down vpn-$rightpeer
  sudo ipsec auto --delete vpn-$rightpeer
  outIp=$leftpeer
  local vpnconffile=$vpnconfdir/ipsec.vpn-$rightpeer.conf
  local vpnsecretsfile=$vpnconfdir/ipsec.vpn-$rightpeer.secrets
  logger -t cloud "$(basename $0): removing configuration for ipsec tunnel to $rightpeer"
  sudo rm -f $vpnconffile
  sudo rm -f $vpnsecretsfile
  sudo ipsec auto --rereadall
  check_and_disable_iptables
  return 0
}

ipsec_tunnel_add() {
  #need to unify with remote access VPN
  start_ipsec

  if [ $? -ne 0 ]
  then
      logger -t cloud "$(basename $0): Failed to start ipsec service!"
      return 1
  fi

  outIp=$leftpeer
  sudo mkdir -p $vpnconfdir
  local vpnconffile=$vpnconfdir/ipsec.vpn-$rightpeer.conf
  local vpnsecretsfile=$vpnconfdir/ipsec.vpn-$rightpeer.secrets

  logger -t cloud "$(basename $0): creating configuration for ipsec tunnel: left peer=$leftpeer \
    left net=$leftnet left gateway=$leftnexthop right peer=$rightpeer right network=$rightnets phase1 policy=$ikepolicy \
    phase2 policy=$esppolicy secret=$secret"

  [ "$op" == "-A" ] && ipsec_tunnel_del

  check_and_enable_iptables

  rsubnets="  rightsubnets={$rightnets}"
  if [ $splitconnections -eq 1 ]
  then
      rsubnetarr=(${rightnets})
      rsubnets="  rightsubnet=${rsubnetarr[0]}"
  fi

  sudo echo "conn vpn-$rightpeer" > $vpnconffile &&
  sudo echo "  left=$leftpeer" >> $vpnconffile &&
  sudo echo "  leftsubnet=$leftnet" >> $vpnconffile &&
  sudo echo "  right=$rightpeer" >> $vpnconffile &&
  sudo echo $rsubnets >> $vpnconffile &&
  sudo echo "  type=tunnel" >> $vpnconffile &&
  sudo echo "  authby=secret" >> $vpnconffile &&
  sudo echo "  keyexchange=${ikeversion:-ike}" >> $vpnconffile &&
  sudo echo "  ike=$ikepolicy" >> $vpnconffile &&
  sudo echo "  ikelifetime=${ikelifetime}s" >> $vpnconffile &&
  sudo echo "  esp=$esppolicy" >> $vpnconffile &&
  sudo echo "  salifetime=${esplifetime}s" >> $vpnconffile &&
  sudo echo "  keyingtries=2" >> $vpnconffile &&
  sudo echo "  auto=route" >> $vpnconffile &&
  sudo echo "$leftpeer $rightpeer: PSK \"$secret\"" > $vpnsecretsfile &&
  sudo chmod 0400 $vpnsecretsfile

  if [ $dpd -ne 0 ]
  then
      sudo echo "  dpddelay=30" >> $vpnconffile &&
      sudo echo "  dpdtimeout=120" >> $vpnconffile &&
      sudo echo "  dpdaction=restart" >> $vpnconffile
  fi

  if [ $splitconnections -eq 1 ]
  then
      # Split out all but the first right subnet into their own statements
      subnetidx=2
      for rsubnet in ${rsubnetarr[@]:1}; do
          sudo echo "" >> $vpnconffile &&
          sudo echo "conn vpn-$rightpeer-$subnetidx" >> $vpnconffile &&
          sudo echo "  also=vpn-$rightpeer" >> $vpnconffile &&
          sudo echo "  auto=route" >> $vpnconffile &&
          sudo echo "  rightsubnet=$rsubnet" >> $vpnconffile
          ((++subnetidx))
      done
  fi

  enable_iptables_subnets

  sudo ipsec auto --rereadall
  sudo ipsec auto --add vpn-$rightpeer

  logger -t cloud "$(basename $0): done ipsec tunnel entry for right peer=$rightpeer right networks=$rightnets"

  result=0

  if [ $passive -eq 0 ]
  then
      sudo ipsec auto --up vpn-$rightpeer &
  fi
  if [ $checkup -eq 1 ]
  then

    #5 seconds for checking if it's ready
    for i in {1..5}
    do
      logger -t cloud "$(basename $0): checking connection status..."
      /opt/cloud/bin/checks2svpn.sh $rightpeer
      result=$?
      if [ $result -eq 0 ]
      then
          break
      fi
      sleep 1
    done
    if [ $result -eq 0 ]
    then
      logger -t cloud "$(basename $0): connect to remote successful"
    else
      logger -t cloud "$(basename $0): fail to connect to remote, status code: $result"
      logger -t cloud "$(basename $0): would stop site-to-site VPN connection"
      ipsec_tunnel_del
    fi
  fi
  return $result
}

rflag=
pflag=
eflag=
Eflag=
lflag=
iflag=
Iflag=
sflag=
passive=0
op=""
checkup=0
secure=1
ikeversion="ike"
splitconnections=0

while getopts 'ACDSpcl:n:g:r:N:e:i:t:T:s:d:v:' OPTION
do
  case $OPTION in
  A)    opflag=1
        op="-A"
        ;;
  D)    opflag=2
        op="-D"
        ;;
  l)    lflag=1
        leftpeer="$OPTARG"
        ;;
  n)    nflag=1
        leftnet="$OPTARG"
        ;;
  g)    gflag=1
        leftnexthop="$OPTARG"
        ;;
  r)    rflag=1
        rightpeer="$OPTARG"
        ;;
  N)    Nflag=1
        rightnets="$OPTARG"
        ;;
  e)    eflag=1
        esppolicy="$OPTARG"
        ;;
  v)    ikeversion="$OPTARG"
        ;;
  i)    iflag=1
        ikepolicy="$OPTARG"
        ;;
  t)    tflag=1
        ikelifetime="$OPTARG"
        ;;
  T)    Tflag=1
        esplifetime="$OPTARG"
        ;;
  s)    sflag=1
        secret="$OPTARG"
        ;;
  d)    dflag=1
        dpd="$OPTARG"
        ;;
  p)    passive=1
        ;;
  c)    checkup=1
        ;;
  S)    secure=0
        ;;
  C)    splitconnections=1
        ;;
  ?)    usage
        exit 2
        ;;
  esac
done

logger -t cloud "$(basename $0): parameters $*"
if [ $secure -eq 1 ]
then
   iptables_secure=" -s $rightpeer -d $leftpeer "
fi

# get interface for public ip
ip link|grep BROADCAST|grep -v eth0|cut -d ":" -f 2 > /tmp/iflist
while read i
do
    ip addr show $i|grep "$leftpeer"
    if [ $? -eq 0 ]
    then
        outIf=$i
        break
    fi
done < /tmp/iflist

rightnets=${rightnets//,/ }

ret=0
#Firewall ports for one-to-one/static NAT
if [ "$opflag" == "1" ]
then
    ipsec_tunnel_add
    ret=$?
elif [ "$opflag" == "2" ]
then
    ipsec_tunnel_del
    ret=$?
else
    printf "Invalid action specified, must choose -A or -D to add/del tunnels\n" >&2
    exit 5
fi

exit $ret
