#!/bin/bash
#
# Script to uninstall IPsec VPN
#
# DO NOT RUN THIS SCRIPT ON YOUR PC OR MAC!
#
# The latest version of this script is available at:
# https://github.com/hwdsl2/setup-ipsec-vpn
#
# Copyright (C) 2021-2024 Lin Song <linsongui@gmail.com>
#
# This work is licensed under the Creative Commons Attribution-ShareAlike 3.0
# Unported License: http://creativecommons.org/licenses/by-sa/3.0/
#
# Attribution required: please include my name in any derivative and let me
# know how you have improved it!

export PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
SYS_DT=$(date +%F-%T | tr ':' '_')

exiterr()  { echo "Error: $1" >&2; exit 1; }
conf_bk() { /bin/cp -f "$1" "$1.old-$SYS_DT" 2>/dev/null; }
bigecho() { echo "## $1"; }

check_cidr() {
  CIDR_REGEX='^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(/(3[0-2]|[1-2][0-9]|[0-9]))$'
  printf '%s' "$1" | tr -d '\n' | grep -Eq "$CIDR_REGEX"
}

check_root() {
  if [ "$(id -u)" != 0 ]; then
    exiterr "Script must be run as root. Try 'sudo bash $0'"
  fi
}

check_os() {
  rh_file="/etc/redhat-release"
  if [ -f "$rh_file" ]; then
    os_type=centos
    if grep -q "Red Hat" "$rh_file"; then
      os_type=rhel
    fi
    [ -f /etc/oracle-release ] && os_type=ol
    grep -qi rocky "$rh_file" && os_type=rocky
    grep -qi alma "$rh_file" && os_type=alma
    if ! grep -q -E "release (7|8|9|10)" "$rh_file"; then
      exiterr "This script only supports CentOS/RHEL 7-10."
    fi
  elif grep -qs "Amazon Linux release 2 " /etc/system-release; then
    os_type=amzn
  else
    os_type=$(lsb_release -si 2>/dev/null)
    [ -z "$os_type" ] && [ -f /etc/os-release ] && os_type=$(. /etc/os-release && printf '%s' "$ID")
    case $os_type in
      [Uu]buntu)
        os_type=ubuntu
        ;;
      [Dd]ebian|[Kk]ali|[Rr]aspbian)
        os_type=debian
        ;;
      [Aa]lpine)
        os_type=alpine
        ;;
      *)
cat 1>&2 <<'EOF'
Error: This script only supports one of the following OS:
       Ubuntu, Debian, CentOS/RHEL, Rocky Linux, AlmaLinux,
       Oracle Linux, Amazon Linux 2 or Alpine Linux
EOF
        exit 1
        ;;
    esac
  fi
}

check_libreswan() {
  ipsec_ver=$(ipsec --version 2>/dev/null)
  if ! grep -qs "hwdsl2 VPN script" /etc/sysctl.conf \
    || ! printf '%s' "$ipsec_ver" | grep -qi 'libreswan'; then
    exiterr "Cannot remove IPsec VPN because it has not been set up on this server."
  fi
}

check_iface() {
  def_iface=$(route 2>/dev/null | grep -m 1 '^default' | grep -o '[^ ]*$')
  if [ "$os_type" != "alpine" ]; then
    [ -z "$def_iface" ] && def_iface=$(ip -4 route list 0/0 2>/dev/null | grep -m 1 -Po '(?<=dev )(\S+)')
  fi
  def_state=$(cat "/sys/class/net/$def_iface/operstate" 2>/dev/null)
  if [ -n "$def_state" ] && [ "$def_state" != "down" ]; then
    check_wl=0
    if [ "$os_type" = "ubuntu" ] || [ "$os_type" = "debian" ]; then
      if ! uname -m | grep -qi -e '^arm' -e '^aarch64'; then
        check_wl=1
      fi
    else
      check_wl=1
    fi
    if [ "$check_wl" = 1 ]; then
      case $def_iface in
        wl*)
          exiterr "Wireless interface '$def_iface' detected. DO NOT run this script on your PC or Mac!"
          ;;
      esac
    fi
    NET_IFACE="$def_iface"
  else
    eth0_state=$(cat "/sys/class/net/eth0/operstate" 2>/dev/null)
    if [ -z "$eth0_state" ] || [ "$eth0_state" = "down" ]; then
      exiterr "Could not detect the default network interface."
    fi
    NET_IFACE=eth0
  fi
}

abort_and_exit() {
  echo "Abort. No changes were made." >&2
  exit 1
}

confirm_or_abort() {
  printf '%s' "$1"
  read -r response
  case $response in
    [yY][eE][sS]|[yY])
      echo
      ;;
    *)
      abort_and_exit
      ;;
  esac
}

confirm_remove() {
cat <<'EOF'

WARNING: This script will remove IPsec VPN from this server. All VPN configuration
         will be *permanently deleted*, and Libreswan and xl2tpd will be removed.
         This *cannot* be undone!

EOF
  confirm_or_abort "Are you sure you want to remove the VPN? [y/N] "
}

stop_services() {
  bigecho "Stopping services..."
  service ipsec stop
  service xl2tpd stop
}

remove_ipsec() {
  bigecho "Removing IPsec..."
  /bin/rm -rf /usr/local/sbin/ipsec /usr/local/libexec/ipsec /usr/local/share/doc/libreswan
  /bin/rm -f /etc/init/ipsec.conf /lib/systemd/system/ipsec.service /etc/init.d/ipsec \
    /usr/lib/systemd/system/ipsec.service /etc/logrotate.d/libreswan \
    /usr/lib/tmpfiles.d/libreswan.conf
}

remove_xl2tpd() {
  bigecho "Removing xl2tpd..."
  if [ "$os_type" = "ubuntu" ] || [ "$os_type" = "debian" ]; then
    export DEBIAN_FRONTEND=noninteractive
    apt-get -yqq purge xl2tpd >/dev/null
  elif [ "$os_type" = "alpine" ]; then
    apk del -q xl2tpd
  else
    yum -y -q remove xl2tpd >/dev/null
  fi
}

remove_helper_scripts() {
  bigecho "Removing helper scripts..."
  for sc in ikev2.sh addvpnuser.sh delvpnuser.sh; do
    if [ "$(readlink -f "/usr/bin/$sc" 2>/dev/null)" = "/opt/src/$sc" ]; then
      /bin/rm -f "/usr/bin/$sc" "/opt/src/$sc"
    fi
  done
}

update_sysctl() {
  if grep -qs "hwdsl2 VPN script" /etc/sysctl.conf; then
    bigecho "Updating sysctl settings..."
    conf_bk "/etc/sysctl.conf"
    count=17
    line1=$(grep -A 18 "hwdsl2 VPN script" /etc/sysctl.conf | tail -n 1)
    line2=$(grep -A 19 "hwdsl2 VPN script" /etc/sysctl.conf | tail -n 1)
    line3=$(grep -A 20 "hwdsl2 VPN script" /etc/sysctl.conf | tail -n 1)
    if [ "$line1" = "net.core.default_qdisc = fq" ] \
      && [ "$line2" = "net.ipv4.tcp_congestion_control = bbr" ]; then
      count=19
      if [ "$line3" = "net.ipv6.conf.all.forwarding = 1" ]; then
        count=20
      fi
    elif [ "$line1" = "net.ipv6.conf.all.forwarding = 1" ]; then
      count=18
    fi
    if [ "$os_type" = "alpine" ]; then
      sed -i "/# Added by hwdsl2 VPN script/,+${count}d" /etc/sysctl.conf
    else
      sed --follow-symlinks -i "/# Added by hwdsl2 VPN script/,+${count}d" /etc/sysctl.conf
    fi
    if [ ! -f /usr/bin/wg-quick ] && [ ! -f /usr/sbin/openvpn ]; then
      echo 0 > /proc/sys/net/ipv4/ip_forward
    fi
    echo 1 > /proc/sys/net/ipv4/conf/all/rp_filter
  fi
}

update_rclocal() {
  if grep -qs "hwdsl2 VPN script" /etc/rc.local; then
    bigecho "Updating rc.local..."
    conf_bk "/etc/rc.local"
    if [ "$os_type" = "alpine" ]; then
      sed -i '/# Added by hwdsl2 VPN script/,/)&$/d' /etc/rc.local
    else
      sed --follow-symlinks -i '/# Added by hwdsl2 VPN script/,/)&$/d' /etc/rc.local
    fi
  fi
}

get_vpn_subnets() {
  L2TP_NET=192.168.42.0/24
  XAUTH_NET=192.168.43.0/24
  if [ -s /etc/ipsec.conf ]; then
    if ! grep -q "$L2TP_NET" /etc/ipsec.conf \
      || ! grep -q "$XAUTH_NET" /etc/ipsec.conf; then
      vipr=$(grep "virtual-private=" /etc/ipsec.conf)
      l2tpnet=$(printf '%s' "$vipr" | cut -f2 -d '!' | cut -f1 -d ',')
      xauthnet=$(printf '%s' "$vipr" | cut -f3 -d '!' | cut -f1 -d ',')
      check_cidr "$l2tpnet" && L2TP_NET="$l2tpnet"
      check_cidr "$xauthnet" && XAUTH_NET="$xauthnet"
    fi
  fi
}

update_iptables_rules() {
  use_nft=0
  if [ "$os_type" = "ubuntu" ] || [ "$os_type" = "debian" ] \
    || [ "$os_type" = "alpine" ]; then
    IPT_FILE=/etc/iptables.rules
    IPT_FILE2=/etc/iptables/rules.v4
  else
    IPT_FILE=/etc/sysconfig/iptables
    if grep -qs "hwdsl2 VPN script" /etc/sysconfig/nftables.conf; then
      use_nft=1
      IPT_FILE=/etc/sysconfig/nftables.conf
    fi
  fi
  ipt_flag=0
  if grep -qs "hwdsl2 VPN script" "$IPT_FILE"; then
    ipt_flag=1
  fi
  ipi='iptables -D INPUT'
  ipf='iptables -D FORWARD'
  ipp='iptables -t nat -D POSTROUTING'
  res='RELATED,ESTABLISHED'
  if [ "$ipt_flag" = 1 ]; then
    if [ "$use_nft" = 0 ]; then
      bigecho "Updating IPTables rules..."
      get_vpn_subnets
      iptables-save > "$IPT_FILE.old-$SYS_DT"
      $ipi -p udp --dport 1701 -m policy --dir in --pol none -j DROP
      $ipi -m conntrack --ctstate INVALID -j DROP
      $ipi -m conntrack --ctstate "$res" -j ACCEPT
      $ipi -p udp -m multiport --dports 500,4500 -j ACCEPT
      $ipi -p udp --dport 1701 -m policy --dir in --pol ipsec -j ACCEPT
      $ipi -p udp --dport 1701 -j DROP
      $ipf -m conntrack --ctstate INVALID -j DROP
      $ipf -i "$NET_IFACE" -o ppp+ -m conntrack --ctstate "$res" -j ACCEPT
      $ipf -i ppp+ -o "$NET_IFACE" -j ACCEPT
      $ipf -i ppp+ -o ppp+ -j ACCEPT
      $ipf -i "$NET_IFACE" -d "$XAUTH_NET" -m conntrack --ctstate "$res" -j ACCEPT
      $ipf -s "$XAUTH_NET" -o "$NET_IFACE" -j ACCEPT
      $ipf -s "$XAUTH_NET" -o ppp+ -j ACCEPT
      iptables -D FORWARD -j DROP
      $ipp -s "$XAUTH_NET" -o "$NET_IFACE" -m policy --dir out --pol none -j MASQUERADE
      $ipp -s "$L2TP_NET" -o "$NET_IFACE" -j MASQUERADE
      iptables-save > "$IPT_FILE"
      if [ "$os_type" = "ubuntu" ] || [ "$os_type" = "debian" ]; then
        if [ -f "$IPT_FILE2" ]; then
          conf_bk "$IPT_FILE2"
          /bin/cp -f "$IPT_FILE" "$IPT_FILE2"
        fi
      fi
      if [ "$os_type" = "ubuntu" ] || [ "$os_type" = "debian" ] \
        || [ "$os_type" = "alpine" ]; then
        IPT6_FILE=/etc/ip6tables.rules
        IPT6_FILE2=/etc/iptables/rules.v6
      else
        IPT6_FILE=/etc/sysconfig/ip6tables
        IPT6_FILE2=""
      fi
      if grep -qs "hwdsl2 VPN script" "$IPT6_FILE" 2>/dev/null; then
        IP6_NET=$(grep 'FORWARD.*-d ' "$IPT6_FILE" \
          | sed -n 's/.* -d \([^ ]*\).*/\1/p' | head -n 1)
        [ -z "$IP6_NET" ] && IP6_NET='fddd:500:500:500::/64'
        ip6tables -D INPUT -m conntrack --ctstate INVALID -j DROP 2>/dev/null
        ip6tables -D INPUT -m conntrack --ctstate "$res" -j ACCEPT 2>/dev/null
        ip6tables -D INPUT -p udp -m multiport --dports 500,4500 -j ACCEPT 2>/dev/null
        ip6tables -D FORWARD -m conntrack --ctstate INVALID -j DROP 2>/dev/null
        ip6tables -D FORWARD -i "$NET_IFACE" -d "$IP6_NET" \
          -m conntrack --ctstate "$res" -j ACCEPT 2>/dev/null
        ip6tables -D FORWARD -s "$IP6_NET" -o "$NET_IFACE" -j ACCEPT 2>/dev/null
        ip6tables -t nat -D POSTROUTING -s "$IP6_NET" -o "$NET_IFACE" \
          -m policy --dir out --pol none -j MASQUERADE 2>/dev/null
        /bin/rm -f "$IPT6_FILE"
        [ -n "$IPT6_FILE2" ] && /bin/rm -f "$IPT6_FILE2"
      fi
    else
      nft_bk=$(find /etc/sysconfig -maxdepth 1 -name 'nftables.conf.old-*-*-*-*_*_*' -print0 \
        | xargs -r -0 ls -1 -t | head -1)
      diff_count=24
      if grep -qs "release 9" /etc/redhat-release; then
        diff_count=38
      fi
      if [ -f "$nft_bk" ] \
        && [ "$(diff -y --suppress-common-lines "$IPT_FILE" "$nft_bk" | wc -l)" = "$diff_count" ]; then
        bigecho "Restoring nftables rules..."
        conf_bk "$IPT_FILE"
        /bin/cp -f "$nft_bk" "$IPT_FILE" && /bin/rm -f "$nft_bk"
        nft flush ruleset
        systemctl restart nftables
      else
cat <<'EOF'

Note: This script cannot automatically remove nftables rules for the VPN.
      To manually clean them up, edit /etc/sysconfig/nftables.conf
      and remove unneeded rules. Your original rules are backed up as file
      /etc/sysconfig/nftables.conf.old-date-time.

EOF
      fi
    fi
  fi
}

update_crontabs() {
  if [ "$os_type" = "alpine" ]; then
    cron_cmd="rc-service -c ipsec zap start"
    if grep -qs "$cron_cmd" /etc/crontabs/root; then
      bigecho "Updating crontabs..."
      sed -i "/$cron_cmd/d" /etc/crontabs/root
      touch /etc/crontabs/cron.update
    fi
  fi
}

remove_config_files() {
  bigecho "Removing VPN configuration..."
  /bin/rm -f /etc/ipsec.conf* /etc/ipsec.secrets* /etc/ppp/chap-secrets* /etc/ppp/options.xl2tpd* \
      /etc/pam.d/pluto /etc/sysconfig/pluto /etc/default/pluto
  /bin/rm -rf /etc/ipsec.d /etc/xl2tpd
}

remove_vpn() {
  stop_services
  remove_ipsec
  remove_xl2tpd
  remove_helper_scripts
  update_sysctl
  update_rclocal
  update_iptables_rules
  update_crontabs
  remove_config_files
}

print_vpn_removed() {
  echo
  echo "IPsec VPN removed!"
}

vpnuninstall() {
  check_root
  check_os
  check_libreswan
  check_iface
  confirm_remove
  remove_vpn
  print_vpn_removed
}

## Defer until we have the complete script
vpnuninstall "$@"

exit 0
