#!/bin/bash
#
# Docker script to configure and start an IPsec VPN server
#
# DO NOT RUN THIS SCRIPT ON YOUR PC OR MAC! THIS IS ONLY MEANT TO BE RUN
# IN A CONTAINER!
#
# This file is part of IPsec VPN Docker image, available at:
# https://github.com/hwdsl2/docker-ipsec-vpn-server
#
# Copyright (C) 2016-2025 Lin Song <linsongui@gmail.com>
# Based on the work of Thomas Sarlandie (Copyright 2012)
#
# 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"

exiterr()  { echo "Error: $1" >&2; exit 1; }
nospaces() { printf '%s' "$1" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//'; }
onespace() { printf '%s' "$1" | tr -s ' '; }
noquotes() { printf '%s' "$1" | sed -e 's/^"\(.*\)"$/\1/' -e "s/^'\(.*\)'$/\1/"; }
noquotes2() { printf '%s' "$1" | sed -e 's/" "/ /g' -e "s/' '/ /g"; }

check_ip() {
  IP_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])$'
  printf '%s' "$1" | tr -d '\n' | grep -Eq "$IP_REGEX"
}

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_dns_name() {
  FQDN_REGEX='^([a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$'
  printf '%s' "$1" | tr -d '\n' | grep -Eq "$FQDN_REGEX"
}

check_client_name() {
  ! { [ "${#1}" -gt "64" ] || printf '%s' "$1" | LC_ALL=C grep -q '[^A-Za-z0-9_-]\+' \
    || case $1 in -*) true ;; *) false ;; esac; }
}

if [ ! -f "/.dockerenv" ] && [ ! -f "/run/.containerenv" ] \
  && [ -z "$KUBERNETES_SERVICE_HOST" ] \
  && ! head -n 1 /proc/1/sched 2>/dev/null | grep -q '^run\.sh '; then
  exiterr "This script ONLY runs in a container (e.g. Docker, Podman)."
fi

if ip link add dummy0 type dummy 2>&1 | grep -q "not permitted"; then
cat 1>&2 <<'EOF'
Error: This Docker image should be run in privileged mode.
       See: https://github.com/hwdsl2/docker-ipsec-vpn-server

EOF
  exit 1
fi
ip link delete dummy0 >/dev/null 2>&1

os_type=debian
os_arch=$(uname -m | tr -dc 'A-Za-z0-9_-')
[ -f /etc/os-release ] && os_type=$(. /etc/os-release && printf '%s' "$ID")

if [ ! -e /dev/ppp ]; then
cat <<'EOF'

Warning: /dev/ppp is missing, and IPsec/L2TP mode may not work.
         Please use IKEv2 or IPsec/XAuth mode to connect.
         Debian 11/10 users, see https://vpnsetup.net/debian10
EOF
fi

NET_IFACE=$(route 2>/dev/null | grep -m 1 '^default' | grep -o '[^ ]*$')
[ -z "$NET_IFACE" ] && NET_IFACE=$(ip -4 route list 0/0 2>/dev/null | grep -m 1 -Po '(?<=dev )(\S+)')
[ -z "$NET_IFACE" ] && NET_IFACE=eth0

mkdir -p /opt/src
vpn_env="/opt/src/vpn.env"
vpn_env_dir="/opt/src/env/vpn.env"
if [ -f "$vpn_env_dir" ]; then
  vpn_env="$vpn_env_dir"
fi
vpn_gen_env="/etc/ipsec.d/vpn-gen.env"
if [ -z "$VPN_IPSEC_PSK" ] && [ -z "$VPN_USER" ] && [ -z "$VPN_PASSWORD" ]; then
  if [ -f "$vpn_env" ]; then
    echo
    echo 'Retrieving VPN credentials...'
    . "$vpn_env"
  elif [ -f "$vpn_gen_env" ]; then
    echo
    echo 'Retrieving previously generated VPN credentials...'
    . "$vpn_gen_env"
  else
    echo
    echo 'VPN credentials not set by user. Generating random PSK and password...'
    VPN_IPSEC_PSK=$(LC_CTYPE=C tr -dc 'A-HJ-NPR-Za-km-z2-9' </dev/urandom 2>/dev/null | head -c 20)
    VPN_USER=vpnuser
    VPN_PASSWORD=$(LC_CTYPE=C tr -dc 'A-HJ-NPR-Za-km-z2-9' </dev/urandom 2>/dev/null | head -c 16)
    printf '%s\n' "VPN_IPSEC_PSK='$VPN_IPSEC_PSK'" > "$vpn_gen_env"
    printf '%s\n' "VPN_USER='$VPN_USER'" >> "$vpn_gen_env"
    printf '%s\n' "VPN_PASSWORD='$VPN_PASSWORD'" >> "$vpn_gen_env"
    chmod 600 "$vpn_gen_env"
  fi
fi

# Remove whitespace and quotes around VPN variables, if any
VPN_IPSEC_PSK=$(nospaces "$VPN_IPSEC_PSK")
VPN_IPSEC_PSK=$(noquotes "$VPN_IPSEC_PSK")
VPN_USER=$(nospaces "$VPN_USER")
VPN_USER=$(noquotes "$VPN_USER")
VPN_PASSWORD=$(nospaces "$VPN_PASSWORD")
VPN_PASSWORD=$(noquotes "$VPN_PASSWORD")
if [ -n "$VPN_ADDL_USERS" ] && [ -n "$VPN_ADDL_PASSWORDS" ]; then
  VPN_ADDL_USERS=$(nospaces "$VPN_ADDL_USERS")
  VPN_ADDL_USERS=$(noquotes "$VPN_ADDL_USERS")
  VPN_ADDL_USERS=$(onespace "$VPN_ADDL_USERS")
  VPN_ADDL_USERS=$(noquotes2 "$VPN_ADDL_USERS")
  VPN_ADDL_PASSWORDS=$(nospaces "$VPN_ADDL_PASSWORDS")
  VPN_ADDL_PASSWORDS=$(noquotes "$VPN_ADDL_PASSWORDS")
  VPN_ADDL_PASSWORDS=$(onespace "$VPN_ADDL_PASSWORDS")
  VPN_ADDL_PASSWORDS=$(noquotes2 "$VPN_ADDL_PASSWORDS")
  if [ -n "$VPN_ADDL_IP_ADDRS" ]; then
    VPN_ADDL_IP_ADDRS=$(nospaces "$VPN_ADDL_IP_ADDRS")
    VPN_ADDL_IP_ADDRS=$(noquotes "$VPN_ADDL_IP_ADDRS")
    VPN_ADDL_IP_ADDRS=$(onespace "$VPN_ADDL_IP_ADDRS")
    VPN_ADDL_IP_ADDRS=$(noquotes2 "$VPN_ADDL_IP_ADDRS")
  fi
else
  VPN_ADDL_USERS=""
  VPN_ADDL_PASSWORDS=""
  VPN_ADDL_IP_ADDRS=""
fi
if [ -n "$VPN_DNS_SRV1" ]; then
  VPN_DNS_SRV1=$(nospaces "$VPN_DNS_SRV1")
  VPN_DNS_SRV1=$(noquotes "$VPN_DNS_SRV1")
fi
if [ -n "$VPN_DNS_SRV2" ]; then
  VPN_DNS_SRV2=$(nospaces "$VPN_DNS_SRV2")
  VPN_DNS_SRV2=$(noquotes "$VPN_DNS_SRV2")
fi
if [ -n "$VPN_CLIENT_NAME" ]; then
  VPN_CLIENT_NAME=$(nospaces "$VPN_CLIENT_NAME")
  VPN_CLIENT_NAME=$(noquotes "$VPN_CLIENT_NAME")
fi
if [ -n "$VPN_DNS_NAME" ]; then
  VPN_DNS_NAME=$(nospaces "$VPN_DNS_NAME")
  VPN_DNS_NAME=$(noquotes "$VPN_DNS_NAME")
fi
if [ -n "$VPN_PUBLIC_IP" ]; then
  VPN_PUBLIC_IP=$(nospaces "$VPN_PUBLIC_IP")
  VPN_PUBLIC_IP=$(noquotes "$VPN_PUBLIC_IP")
fi
if [ -n "$VPN_ANDROID_MTU_FIX" ]; then
  VPN_ANDROID_MTU_FIX=$(nospaces "$VPN_ANDROID_MTU_FIX")
  VPN_ANDROID_MTU_FIX=$(noquotes "$VPN_ANDROID_MTU_FIX")
fi
if [ -n "$VPN_SHA2_TRUNCBUG" ]; then
  VPN_SHA2_TRUNCBUG=$(nospaces "$VPN_SHA2_TRUNCBUG")
  VPN_SHA2_TRUNCBUG=$(noquotes "$VPN_SHA2_TRUNCBUG")
fi
if [ -n "$VPN_PROTECT_CONFIG" ]; then
  VPN_PROTECT_CONFIG=$(nospaces "$VPN_PROTECT_CONFIG")
  VPN_PROTECT_CONFIG=$(noquotes "$VPN_PROTECT_CONFIG")
fi
if [ -n "$VPN_SPLIT_IKEV2" ]; then
  VPN_SPLIT_IKEV2=$(nospaces "$VPN_SPLIT_IKEV2")
  VPN_SPLIT_IKEV2=$(noquotes "$VPN_SPLIT_IKEV2")
fi
if [ -n "$VPN_DISABLE_IPSEC_L2TP" ]; then
  VPN_DISABLE_IPSEC_L2TP=$(nospaces "$VPN_DISABLE_IPSEC_L2TP")
  VPN_DISABLE_IPSEC_L2TP=$(noquotes "$VPN_DISABLE_IPSEC_L2TP")
fi
if [ -n "$VPN_DISABLE_IPSEC_XAUTH" ]; then
  VPN_DISABLE_IPSEC_XAUTH=$(nospaces "$VPN_DISABLE_IPSEC_XAUTH")
  VPN_DISABLE_IPSEC_XAUTH=$(noquotes "$VPN_DISABLE_IPSEC_XAUTH")
fi
if [ -n "$VPN_IKEV2_ONLY" ]; then
  VPN_IKEV2_ONLY=$(nospaces "$VPN_IKEV2_ONLY")
  VPN_IKEV2_ONLY=$(noquotes "$VPN_IKEV2_ONLY")
fi
if [ -n "$VPN_ENABLE_MODP1024" ]; then
  VPN_ENABLE_MODP1024=$(nospaces "$VPN_ENABLE_MODP1024")
  VPN_ENABLE_MODP1024=$(noquotes "$VPN_ENABLE_MODP1024")
fi
if [ -n "$VPN_ENABLE_MODP1536" ]; then
  VPN_ENABLE_MODP1536=$(nospaces "$VPN_ENABLE_MODP1536")
  VPN_ENABLE_MODP1536=$(noquotes "$VPN_ENABLE_MODP1536")
fi
if [ -n "$VPN_L2TP_NET" ]; then
  VPN_L2TP_NET=$(nospaces "$VPN_L2TP_NET")
  VPN_L2TP_NET=$(noquotes "$VPN_L2TP_NET")
fi
if [ -n "$VPN_L2TP_LOCAL" ]; then
  VPN_L2TP_LOCAL=$(nospaces "$VPN_L2TP_LOCAL")
  VPN_L2TP_LOCAL=$(noquotes "$VPN_L2TP_LOCAL")
fi
if [ -n "$VPN_L2TP_POOL" ]; then
  VPN_L2TP_POOL=$(nospaces "$VPN_L2TP_POOL")
  VPN_L2TP_POOL=$(noquotes "$VPN_L2TP_POOL")
fi
if [ -n "$VPN_XAUTH_NET" ]; then
  VPN_XAUTH_NET=$(nospaces "$VPN_XAUTH_NET")
  VPN_XAUTH_NET=$(noquotes "$VPN_XAUTH_NET")
fi
if [ -n "$VPN_XAUTH_POOL" ]; then
  VPN_XAUTH_POOL=$(nospaces "$VPN_XAUTH_POOL")
  VPN_XAUTH_POOL=$(noquotes "$VPN_XAUTH_POOL")
fi

if [ -z "$VPN_IPSEC_PSK" ] || [ -z "$VPN_USER" ] || [ -z "$VPN_PASSWORD" ]; then
  exiterr "All VPN credentials must be specified. Edit your 'env' file and re-enter them."
fi
if printf '%s' "$VPN_IPSEC_PSK $VPN_USER $VPN_PASSWORD $VPN_ADDL_USERS $VPN_ADDL_PASSWORDS" | LC_ALL=C grep -q '[^ -~]\+'; then
  exiterr "VPN credentials must not contain non-ASCII characters."
fi
case "$VPN_IPSEC_PSK $VPN_USER $VPN_PASSWORD $VPN_ADDL_USERS $VPN_ADDL_PASSWORDS" in
  *[\\\"\']*)
    exiterr "VPN credentials must not contain these special characters: \\ \" '"
    ;;
esac
if printf '%s' "$VPN_USER $VPN_ADDL_USERS" | tr ' ' '\n' | sort | uniq -c | grep -qv '^ *1 '; then
  exiterr "VPN usernames must not contain duplicates."
fi

# Check DNS servers and try to resolve hostnames to IPs
if [ -n "$VPN_DNS_SRV1" ]; then
  check_ip "$VPN_DNS_SRV1" || VPN_DNS_SRV1=$(dig -t A -4 +short "$VPN_DNS_SRV1")
  if ! check_ip "$VPN_DNS_SRV1"; then
cat <<'EOF'

Warning: Invalid DNS server. Check VPN_DNS_SRV1 in your 'env' file.
EOF
    VPN_DNS_SRV1=""
  fi
fi
if [ -n "$VPN_DNS_SRV2" ]; then
  check_ip "$VPN_DNS_SRV2" || VPN_DNS_SRV2=$(dig -t A -4 +short "$VPN_DNS_SRV2")
  if ! check_ip "$VPN_DNS_SRV2"; then
cat <<'EOF'

Warning: Invalid DNS server. Check VPN_DNS_SRV2 in your 'env' file.
EOF
    VPN_DNS_SRV2=""
  fi
fi
if [ -n "$VPN_CLIENT_NAME" ]; then
  if ! check_client_name "$VPN_CLIENT_NAME"; then
cat <<'EOF'

Warning: Invalid client name. Use one word only, no special characters except '-' and '_'.
         Falling back to default client name 'vpnclient'.
EOF
    VPN_CLIENT_NAME=""
  fi
fi
if [ -n "$VPN_DNS_NAME" ]; then
  if ! check_dns_name "$VPN_DNS_NAME"; then
cat <<'EOF'

Warning: Invalid DNS name. 'VPN_DNS_NAME' must be a fully qualified domain name (FQDN).
         Falling back to using this server's IP address.
EOF
    VPN_DNS_NAME=""
  fi
fi
if [ -n "$VPN_SPLIT_IKEV2" ]; then
  if ! check_cidr "$VPN_SPLIT_IKEV2"; then
cat <<'EOF'

Warning: Invalid split VPN subnet. Check VPN_SPLIT_IKEV2 in your 'env' file.
EOF
    VPN_SPLIT_IKEV2=""
  fi
fi

echo
echo 'Trying to auto discover IP of this server...'
# In case auto IP discovery fails, manually define the public IP
# of this server in your 'env' file, as variable 'VPN_PUBLIC_IP'.
public_ip=${VPN_PUBLIC_IP:-''}
check_ip "$public_ip" || public_ip=$(dig @resolver1.opendns.com -t A -4 myip.opendns.com +short)
check_ip "$public_ip" || public_ip=$(wget -t 2 -T 10 -qO- http://ipv4.icanhazip.com)
check_ip "$public_ip" || public_ip=$(wget -t 2 -T 10 -qO- http://ip1.dynupdate.no-ip.com)
check_ip "$public_ip" || exiterr "Cannot detect this server's public IP. Define it in your 'env' file as 'VPN_PUBLIC_IP'."

if [ -n "$VPN_DNS_NAME" ]; then
  server_addr="$VPN_DNS_NAME"
else
  server_addr="$public_ip"
fi

L2TP_NET=${VPN_L2TP_NET:-'192.168.42.0/24'}
L2TP_LOCAL=${VPN_L2TP_LOCAL:-'192.168.42.1'}
L2TP_POOL=${VPN_L2TP_POOL:-'192.168.42.10-192.168.42.250'}
XAUTH_NET=${VPN_XAUTH_NET:-'192.168.43.0/24'}
XAUTH_POOL=${VPN_XAUTH_POOL:-'192.168.43.10-192.168.43.250'}
DNS_SRV1=${VPN_DNS_SRV1:-'8.8.8.8'}
DNS_SRV2=${VPN_DNS_SRV2:-'8.8.4.4'}
DNS_SRVS="\"$DNS_SRV1 $DNS_SRV2\""
[ -n "$VPN_DNS_SRV1" ] && [ -z "$VPN_DNS_SRV2" ] && DNS_SRVS="$DNS_SRV1"

if [ -n "$VPN_DNS_SRV1" ] && [ -n "$VPN_DNS_SRV2" ]; then
  echo
  echo "Setting DNS servers to $VPN_DNS_SRV1 and $VPN_DNS_SRV2..."
elif [ -n "$VPN_DNS_SRV1" ]; then
  echo
  echo "Setting DNS server to $VPN_DNS_SRV1..."
fi

sha2_truncbug=no
case $VPN_SHA2_TRUNCBUG in
  [yY][eE][sS])
    echo
    echo "Setting sha2-truncbug to yes in ipsec.conf..."
    sha2_truncbug=yes
    ;;
esac
disable_ipsec_l2tp=no
case $VPN_DISABLE_IPSEC_L2TP in
  [yY][eE][sS])
    disable_ipsec_l2tp=yes
    ;;
esac
disable_ipsec_xauth=no
case $VPN_DISABLE_IPSEC_XAUTH in
  [yY][eE][sS])
    disable_ipsec_xauth=yes
    ;;
esac
case $VPN_IKEV2_ONLY in
  [yY][eE][sS])
    disable_ipsec_l2tp=yes
    disable_ipsec_xauth=yes
    ;;
esac
ike_algs="aes256-sha2;modp2048,aes128-sha2;modp2048,aes256-sha1;modp2048,aes128-sha1;modp2048"
ike_algs_addl_1=",aes256-sha2;modp1024,aes128-sha1;modp1024"
ike_algs_addl_2=",aes256-sha2;modp1536,aes128-sha1;modp1536"
case $VPN_ENABLE_MODP1024 in
  [yY][eE][sS])
    echo
    echo "Enabling modp1024 in ipsec.conf..."
    ike_algs="$ike_algs$ike_algs_addl_1"
    ;;
esac
case $VPN_ENABLE_MODP1536 in
  [yY][eE][sS])
    echo
    echo "Enabling modp1536 in ipsec.conf..."
    ike_algs="$ike_algs$ike_algs_addl_2"
    ;;
esac

if [ "$disable_ipsec_l2tp" = yes ] && [ "$disable_ipsec_xauth" = yes ]; then
cat <<'EOF'

Note: Running in IKEv2-only mode via env file option.
      IPsec/L2TP and IPsec/XAuth ("Cisco IPsec") modes are disabled.
EOF
  if ! grep -q " /etc/ipsec.d " /proc/mounts; then
cat <<'EOF'

Warning: /etc/ipsec.d not mounted. IKEv2 setup requires a Docker volume
         mounted at /etc/ipsec.d.
EOF
  fi
elif [ "$disable_ipsec_l2tp" = yes ]; then
cat <<'EOF'

Note: IPsec/L2TP mode is disabled via env file option.
EOF
elif [ "$disable_ipsec_xauth" = yes ]; then
cat <<'EOF'

Note: IPsec/XAuth ("Cisco IPsec") mode is disabled via env file option.
EOF
fi

# Create IPsec config
cat > /etc/ipsec.conf <<EOF
version 2.0

config setup
  ikev1-policy=accept
  virtual-private=%v4:10.0.0.0/8,%v4:192.168.0.0/16,%v4:172.16.0.0/12,%v4:!$L2TP_NET,%v4:!$XAUTH_NET
  uniqueids=no

conn shared
  left=%defaultroute
  leftid=$public_ip
  right=%any
  encapsulation=yes
  authby=secret
  pfs=no
  rekey=no
  dpddelay=30
  dpdtimeout=300
  ikev2=never
  ike=$ike_algs
  phase2alg=aes_gcm-null,aes128-sha1,aes256-sha1,aes256-sha2_512,aes128-sha2,aes256-sha2
  ikelifetime=24h
  salifetime=24h
  sha2-truncbug=$sha2_truncbug

EOF

if [ "$disable_ipsec_l2tp" != yes ]; then
cat >> /etc/ipsec.conf <<'EOF'
conn l2tp-psk
  auto=add
  leftprotoport=17/1701
  rightprotoport=17/%any
  type=transport
  also=shared

EOF
fi
if [ "$disable_ipsec_xauth" != yes ]; then
cat >> /etc/ipsec.conf <<EOF
conn xauth-psk
  auto=add
  leftsubnet=0.0.0.0/0
  rightaddresspool=$XAUTH_POOL
  modecfgdns=$DNS_SRVS
  leftxauthserver=yes
  rightxauthclient=yes
  leftmodecfgserver=yes
  rightmodecfgclient=yes
  modecfgpull=yes
  cisco-unity=yes
  also=shared

EOF
fi

cat >> /etc/ipsec.conf <<'EOF'
include /etc/ipsec.d/*.conf
EOF

if uname -r | grep -qi 'coreos'; then
  sed -i '/phase2alg/s/,aes256-sha2_512//' /etc/ipsec.conf
fi
if grep -qs ike-frag /etc/ipsec.d/ikev2.conf; then
  sed -i 's/^[[:space:]]\+ike-frag=/  fragmentation=/' /etc/ipsec.d/ikev2.conf
fi

# Specify IPsec PSK
cat > /etc/ipsec.secrets <<EOF
%any  %any  : PSK "$VPN_IPSEC_PSK"
EOF

# Create xl2tpd config
cat > /etc/xl2tpd/xl2tpd.conf <<EOF
[global]
port = 1701

[lns default]
ip range = $L2TP_POOL
local ip = $L2TP_LOCAL
require chap = yes
refuse pap = yes
require authentication = yes
name = l2tpd
pppoptfile = /etc/ppp/options.xl2tpd
length bit = yes
EOF

# Set xl2tpd options
cat > /etc/ppp/options.xl2tpd <<EOF
+mschap-v2
ipcp-accept-local
ipcp-accept-remote
noccp
auth
mtu 1280
mru 1280
proxyarp
lcp-echo-failure 4
lcp-echo-interval 30
connect-delay 5000
ms-dns $DNS_SRV1
EOF

if [ -z "$VPN_DNS_SRV1" ] || [ -n "$VPN_DNS_SRV2" ]; then
cat >> /etc/ppp/options.xl2tpd <<EOF
ms-dns $DNS_SRV2
EOF
fi

# Create VPN credentials
cat > /etc/ppp/chap-secrets <<EOF
"$VPN_USER" l2tpd "$VPN_PASSWORD" *
EOF

VPN_PASSWORD_ENC=$(openssl passwd -1 "$VPN_PASSWORD")
cat > /etc/ipsec.d/passwd <<EOF
$VPN_USER:$VPN_PASSWORD_ENC:xauth-psk
EOF

if [ -n "$VPN_ADDL_USERS" ] && [ -n "$VPN_ADDL_PASSWORDS" ]; then
  count=1
  addl_user=$(printf '%s' "$VPN_ADDL_USERS" | cut -d ' ' -f 1)
  addl_password=$(printf '%s' "$VPN_ADDL_PASSWORDS" | cut -d ' ' -f 1)
  addl_ip=$(printf '%s' "$VPN_ADDL_IP_ADDRS" | cut -d ' ' -f 1)
  while [ -n "$addl_user" ] && [ -n "$addl_password" ]; do
    addl_ip_l2tp="$addl_ip"
    addl_ip_xauth="$addl_ip"
    if [ "$addl_ip" = "*" ] || ! check_ip "$addl_ip"; then
      addl_ip_l2tp=""
      addl_ip_xauth=""
    elif [ "$L2TP_NET" = "192.168.42.0/24" ] && [ "$XAUTH_NET" = "192.168.43.0/24" ]; then
      addl_ip_part=$(printf '%s' "$addl_ip" | cut -f 1-3 -d '.')
      if [ "$addl_ip_part" = "192.168.42" ]; then
        addl_ip_xauth=""
      elif [ "$addl_ip_part" = "192.168.43" ]; then
        addl_ip_l2tp=""
      else
        addl_ip_l2tp=""
        addl_ip_xauth=""
      fi
    fi
cat >> /etc/ppp/chap-secrets <<EOF
"$addl_user" l2tpd "$addl_password" ${addl_ip_l2tp:-*}
EOF
    [ -n "$addl_ip_xauth" ] && addl_ip_xauth=$(printf '%s' ":$addl_ip_xauth")
    addl_password_enc=$(openssl passwd -1 "$addl_password")
cat >> /etc/ipsec.d/passwd <<EOF
$addl_user:$addl_password_enc:xauth-psk${addl_ip_xauth}
EOF
    count=$((count+1))
    addl_user=$(printf '%s' "$VPN_ADDL_USERS" | cut -s -d ' ' -f "$count")
    addl_password=$(printf '%s' "$VPN_ADDL_PASSWORDS" | cut -s -d ' ' -f "$count")
    addl_ip=$(printf '%s' "$VPN_ADDL_IP_ADDRS" | cut -s -d ' ' -f "$count")
  done
fi

# Update sysctl settings
syt='/sbin/sysctl -e -q -w'
$syt kernel.msgmnb=65536 2>/dev/null
$syt kernel.msgmax=65536 2>/dev/null
$syt net.ipv4.ip_forward=1 2>/dev/null
$syt net.ipv4.conf.all.accept_redirects=0 2>/dev/null
$syt net.ipv4.conf.all.send_redirects=0 2>/dev/null
$syt net.ipv4.conf.all.rp_filter=0 2>/dev/null
$syt net.ipv4.conf.default.accept_redirects=0 2>/dev/null
$syt net.ipv4.conf.default.send_redirects=0 2>/dev/null
$syt net.ipv4.conf.default.rp_filter=0 2>/dev/null
$syt "net.ipv4.conf.$NET_IFACE.send_redirects=0" 2>/dev/null
$syt "net.ipv4.conf.$NET_IFACE.rp_filter=0" 2>/dev/null
$syt net.ipv4.tcp_rmem="4096 87380 16777216" 2>/dev/null
$syt net.ipv4.tcp_wmem="4096 87380 16777216" 2>/dev/null
if modprobe -q tcp_bbr 2>/dev/null \
  && printf '%s\n%s' "4.20" "$(uname -r)" | sort -C -V; then
  $syt net.ipv4.tcp_congestion_control=bbr 2>/dev/null
fi

# Create IPTables rules
ipi='iptables -I INPUT'
ipf='iptables -I FORWARD'
ipp='iptables -t nat -I POSTROUTING'
res='RELATED,ESTABLISHED'
modprobe -q ip_tables 2>/dev/null
if ! iptables -t nat -C POSTROUTING -s "$L2TP_NET" -o "$NET_IFACE" -j MASQUERADE 2>/dev/null; then
  $ipi 1 -p udp --dport 1701 -m policy --dir in --pol none -j DROP
  $ipi 2 -m conntrack --ctstate INVALID -j DROP
  $ipi 3 -m conntrack --ctstate "$res" -j ACCEPT
  $ipi 4 -p udp -m multiport --dports 500,4500 -j ACCEPT
  $ipi 5 -p udp --dport 1701 -m policy --dir in --pol ipsec -j ACCEPT
  $ipi 6 -p udp --dport 1701 -j DROP
  $ipf 1 -m conntrack --ctstate INVALID -j DROP
  $ipf 2 -i "$NET_IFACE" -o ppp+ -m conntrack --ctstate "$res" -j ACCEPT
  $ipf 3 -i ppp+ -o "$NET_IFACE" -j ACCEPT
  $ipf 4 -i ppp+ -o ppp+ -j ACCEPT
  $ipf 5 -i "$NET_IFACE" -d "$XAUTH_NET" -m conntrack --ctstate "$res" -j ACCEPT
  $ipf 6 -s "$XAUTH_NET" -o "$NET_IFACE" -j ACCEPT
  $ipf 7 -s "$XAUTH_NET" -o ppp+ -j ACCEPT
  # Client-to-client traffic is allowed by default. To *disallow* such traffic,
  # uncomment below and restart the Docker container.
  # $ipf 2 -i ppp+ -o ppp+ -s "$L2TP_NET" -d "$L2TP_NET" -j DROP
  # $ipf 3 -s "$XAUTH_NET" -d "$XAUTH_NET" -j DROP
  # $ipf 4 -i ppp+ -d "$XAUTH_NET" -j DROP
  # $ipf 5 -s "$XAUTH_NET" -o ppp+ -j DROP
  iptables -A FORWARD -j DROP
  if ! $ipp -s "$XAUTH_NET" -o "$NET_IFACE" -m policy --dir out --pol none -j MASQUERADE; then
    $ipp -s "$XAUTH_NET" -o "$NET_IFACE" ! -d "$XAUTH_NET" -j MASQUERADE
  fi
  $ipp -s "$L2TP_NET" -o "$NET_IFACE" -j MASQUERADE
fi

case $VPN_ANDROID_MTU_FIX in
  [yY][eE][sS])
    echo
    echo "Applying fix for Android MTU/MSS issues..."
    iptables -t mangle -A FORWARD -m policy --pol ipsec --dir in \
      -p tcp -m tcp --tcp-flags SYN,RST SYN -m tcpmss --mss 1361:1536 \
      -j TCPMSS --set-mss 1360
    iptables -t mangle -A FORWARD -m policy --pol ipsec --dir out \
      -p tcp -m tcp --tcp-flags SYN,RST SYN -m tcpmss --mss 1361:1536 \
      -j TCPMSS --set-mss 1360
    echo 1 > /proc/sys/net/ipv4/ip_no_pmtu_disc
    ;;
esac

# Update file attributes
chmod 600 /etc/ipsec.secrets /etc/ppp/chap-secrets /etc/ipsec.d/passwd

echo
echo "Starting IPsec service..."
mkdir -p /run/pluto /var/run/pluto
rm -f /run/pluto/pluto.pid /var/run/pluto/pluto.pid
if [ "$os_type" = "alpine" ]; then
  sed -i '1c\#!/sbin/openrc-run' /etc/init.d/ipsec
  rc-status >/dev/null 2>&1
  rc-service ipsec zap >/dev/null
  rc-service -D ipsec start >/dev/null 2>&1
  mkdir -p /etc/crontabs
  cron_cmd="rc-service -c -D ipsec zap start"
if ! grep -qs "$cron_cmd" /etc/crontabs/root; then
cat >> /etc/crontabs/root <<EOF
* * * * * $cron_cmd
* * * * * sleep 15; $cron_cmd
* * * * * sleep 30; $cron_cmd
* * * * * sleep 45; $cron_cmd
EOF
fi
  /usr/sbin/crond -L /dev/null
else
  service ipsec start >/dev/null 2>&1
fi

if [ -n "$VPN_DNS_NAME" ]; then
  server_text="Server"
else
  server_text="Server IP"
fi

if [ "$disable_ipsec_l2tp" != yes ] || [ "$disable_ipsec_xauth" != yes ]; then
cat <<EOF

================================================

IPsec VPN server is now ready for use!

Connect to your new VPN with these details:

$server_text: $server_addr
IPsec PSK: $VPN_IPSEC_PSK
Username: $VPN_USER
Password: $VPN_PASSWORD
EOF
  if [ -n "$VPN_ADDL_USERS" ] && [ -n "$VPN_ADDL_PASSWORDS" ]; then
    count=1
    addl_user=$(printf '%s' "$VPN_ADDL_USERS" | cut -d ' ' -f 1)
    addl_password=$(printf '%s' "$VPN_ADDL_PASSWORDS" | cut -d ' ' -f 1)
cat <<'EOF'

Additional VPN users (username | password):
EOF
    while [ -n "$addl_user" ] && [ -n "$addl_password" ]; do
cat <<EOF
$addl_user | $addl_password
EOF
      count=$((count+1))
      addl_user=$(printf '%s' "$VPN_ADDL_USERS" | cut -s -d ' ' -f "$count")
      addl_password=$(printf '%s' "$VPN_ADDL_PASSWORDS" | cut -s -d ' ' -f "$count")
    done
  fi
cat <<'EOF'

Write these down. You'll need them to connect!

VPN client setup: https://vpnsetup.net/clients2

================================================
EOF
fi

# Set up IKEv2
status=0
ikev2_sh="/opt/src/ikev2.sh"
ikev2_conf="/etc/ipsec.d/ikev2.conf"
ikev2_log="/etc/ipsec.d/ikev2setup.log"
if grep -q " /etc/ipsec.d " /proc/mounts && [ -s "$ikev2_sh" ] && [ ! -f "$ikev2_conf" ]; then
  echo
  echo "Setting up IKEv2. This may take a few moments..."
  if [ -n "$VPN_SPLIT_IKEV2" ]; then
    sed -i "s|^  leftsubnet=0\.0\.0\.0/0$|  leftsubnet=$VPN_SPLIT_IKEV2|g" "$ikev2_sh"
  fi
  if VPN_DNS_NAME="$VPN_DNS_NAME" VPN_PUBLIC_IP="$public_ip" \
    VPN_CLIENT_NAME="$VPN_CLIENT_NAME" VPN_XAUTH_POOL="$VPN_XAUTH_POOL" \
    VPN_DNS_SRV1="$VPN_DNS_SRV1" VPN_DNS_SRV2="$VPN_DNS_SRV2" \
    VPN_PROTECT_CONFIG="$VPN_PROTECT_CONFIG" \
    /bin/bash "$ikev2_sh" --auto >"$ikev2_log" 2>&1; then
    status=1
    status_text="IKEv2 setup successful."
  else
    status=4
    rm -f "$ikev2_conf"
    echo "IKEv2 setup failed."
  fi
  chmod 600 "$ikev2_log"
fi
if [ "$status" = 0 ] && [ -f "$ikev2_conf" ] && [ -s "$ikev2_log" ]; then
  status=2
  status_text="IKEv2 is already set up."
fi
if [ "$status" = 1 ] || [ "$status" = 2 ]; then
cat <<EOF

================================================

$status_text Details for IKEv2 mode:

EOF
  sed -n '/VPN server address:/,/Next steps:/p' "$ikev2_log"
cat <<'EOF'
https://vpnsetup.net/clients2

================================================

EOF
else
  echo
fi

if [ "$status" = 2 ] && [ -n "$VPN_DNS_NAME" ]; then
  server_addr_cur=$(grep -s "leftcert=" /etc/ipsec.d/ikev2.conf | cut -f2 -d= | head -n 1)
  if [ "$VPN_DNS_NAME" != "$server_addr_cur" ]; then
cat <<'EOF'
Warning: The VPN_DNS_NAME variable you specified has no effect
         for IKEv2 mode, because IKEv2 is already set up in this
         container. To change the IKEv2 server address, see:
         https://vpnsetup.net/ikev2docker

EOF
  fi
fi

# Check for new Libreswan version
ts_file="/opt/src/swanver"
if [ ! -f "$ts_file" ] || [ "$(find "$ts_file" -mmin +10080)" ]; then
  touch "$ts_file"
  ipsec_ver=$(ipsec --version 2>/dev/null)
  swan_ver=$(printf '%s' "$ipsec_ver" | sed -e 's/.*Libreswan U\?//' -e 's/\( (\|\/K\).*//')
  base_url="https://github.com/hwdsl2/vpn-extras/releases/download/v1.0.0"
  swan_ver_url="$base_url/upg-docker-$os_type-$os_arch-swanver"
  swan_ver_latest=$(wget -t 2 -T 10 -qO- "$swan_ver_url" | head -n 1)
  if printf '%s' "$swan_ver_latest" | grep -Eq '^([3-9]|[1-9][0-9]{1,2})(\.([0-9]|[1-9][0-9]{1,2})){1,2}$' \
    && [ -n "$swan_ver" ] && [ "$swan_ver" != "$swan_ver_latest" ] \
    && printf '%s\n%s' "$swan_ver" "$swan_ver_latest" | sort -C -V; then
cat <<EOF
Note: A newer version of Libreswan ($swan_ver_latest) is available.
To update this Docker image, see: https://vpnsetup.net/dockerupdate

EOF
  fi
fi

# Start xl2tpd
mkdir -p /var/run/xl2tpd
rm -f /var/run/xl2tpd.pid
exec /usr/sbin/xl2tpd -D -c /etc/xl2tpd/xl2tpd.conf
