#!/bin/sh
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH
#
# This is a Shell script for configure and start L2TP/IPSec VPN server with Docker image
# 
# Copyright (C) 2018 - 2019 Teddysun <i@teddysun.com>
#
# Reference URL:
# https://github.com/libreswan/libreswan
# https://github.com/xelerance/xl2tpd

if [ ! -f "/.dockerenv" ]; then
    echo "Error: This script must be run in a Docker container." >&2
    exit 1
fi

if ip link add dummy0 type dummy 2>&1 | grep -q "not permitted"; then
    echo "Error: This Docker image must be run in privileged mode." >&2
    exit 1
fi

ip link delete dummy0 >/dev/null 2>&1

rand(){
    str=$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 10 | head -n 1)
    echo ${str}
}

is_64bit(){
    if [ "$(getconf WORD_BIT)" = "32" ] && [ "$(getconf LONG_BIT)" = "64" ]; then
        return 0
    else
        return 1
    fi
}

# Environment file name
l2tp_env_file="/etc/l2tp.env"
# Auto generated
if [ -z "${VPN_IPSEC_PSK}" ] && [ -z "${VPN_USER}" ] && [ -z "${VPN_PASSWORD}" ]; then
    if [ -f "${l2tp_env_file}" ]; then
        echo "Loading previously generated environment variables for L2TP/IPSec VPN Server..."
        . "${l2tp_env_file}"
    else
        echo "L2TP/IPSec VPN Server environment variables is not set. Use default environment variables..."
        VPN_IPSEC_PSK="teddysun.com"
        VPN_USER="vpnuser"
        VPN_PASSWORD="$(rand)"
        echo "VPN_IPSEC_PSK=${VPN_IPSEC_PSK}" > ${l2tp_env_file}
        echo "VPN_USER=${VPN_USER}" >> ${l2tp_env_file}
        echo "VPN_PASSWORD=${VPN_PASSWORD}" >> ${l2tp_env_file}
        chmod 600 ${l2tp_env_file}
    fi
fi

# Environment variables:
# VPN_IPSEC_PSK
# VPN_USER
# VPN_PASSWORD
if [ -z "${VPN_IPSEC_PSK}" ] || [ -z "${VPN_USER}" ] || [ -z "${VPN_PASSWORD}" ]; then
    echo "Error: Environment variables must be specified. please edit your environment file and retry again." >&2
    exit 1
fi

if printf '%s' "${VPN_IPSEC_PSK} ${VPN_USER} ${VPN_PASSWORD}" | LC_ALL=C grep -q '[^ -~]\+'; then
    echo "Error: Environment variables must not contain non-ASCII characters." >&2
    exit 1
fi

case "${VPN_IPSEC_PSK} ${VPN_USER} ${VPN_PASSWORD}" in
    *[\\\"\']*)
    echo "Error: Environment variables must not contain these special characters like: \\ \" '"
    exit 1
    ;;
esac

# Environment variables:
# VPN_PUBLIC_IP
PUBLIC_IP=${VPN_PUBLIC_IP:-''}

[ -z "${PUBLIC_IP}" ] && PUBLIC_IP=$( wget -qO- -t1 -T2 ipv4.icanhazip.com )
[ -z "${PUBLIC_IP}" ] && PUBLIC_IP=$( wget -qO- -t1 -T2 ipinfo.io/ip )

# Environment variables:
# VPN_L2TP_NET
# VPN_L2TP_LOCAL
# VPN_L2TP_REMOTE
# VPN_XAUTH_NET
# VPN_XAUTH_REMOTE
# VPN_DNS1
# VPN_DNS2
# VPN_SHA2_TRUNCBUG
L2TP_NET=${VPN_L2TP_NET:-'192.168.18.0/24'}
L2TP_LOCAL=${VPN_L2TP_LOCAL:-'192.168.18.1'}
L2TP_REMOTE=${VPN_L2TP_REMOTE:-'192.168.18.10-192.168.18.250'}
XAUTH_NET=${VPN_XAUTH_NET:-'192.168.20.0/24'}
XAUTH_REMOTE=${VPN_XAUTH_REMOTE:-'192.168.20.10-192.168.20.250'}
DNS1=${VPN_DNS1:-'8.8.8.8'}
DNS2=${VPN_DNS2:-'8.8.4.4'}

case ${VPN_SHA2_TRUNCBUG} in
  [yY][eE][sS])
    SHA2_TRUNCBUG=yes
    ;;
  *)
    SHA2_TRUNCBUG=no
    ;;
esac

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

config setup
    protostack=netkey
    uniqueids=no
    interfaces=%defaultroute
    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}

conn shared
    left=%defaultroute
    leftid=${PUBLIC_IP}
    right=%any
    encapsulation=yes
    authby=secret
    pfs=no
    rekey=no
    keyingtries=5
    dpddelay=30
    dpdtimeout=120
    dpdaction=clear
    ikev2=never
    ike=aes256-sha2,aes128-sha2,aes256-sha1,aes128-sha1,aes256-sha2
    phase2alg=aes_gcm-null,aes128-sha1,aes256-sha1,aes256-sha2_512,aes128-sha2,aes256-sha2
    sha2-truncbug=${SHA2_TRUNCBUG}

conn l2tp-psk
    auto=add
    leftprotoport=17/1701
    rightprotoport=17/%any
    type=transport
    phase2=esp
    also=shared

conn xauth-psk
    auto=add
    leftsubnet=0.0.0.0/0
    rightaddresspool=${XAUTH_REMOTE}
    modecfgdns=${DNS1},${DNS2}
    leftxauthserver=yes
    rightxauthclient=yes
    leftmodecfgserver=yes
    rightmodecfgclient=yes
    modecfgpull=yes
    xauthby=file
    ike-frag=yes
    cisco-unity=yes
    also=shared
EOF

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

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

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

cat > /etc/ipsec.secrets <<EOF
%any  %any  : PSK "${VPN_IPSEC_PSK}"
EOF

if ! grep -qw "${VPN_USER}" /etc/ppp/chap-secrets 2>/dev/null; then
    cat > /etc/ppp/chap-secrets <<EOF
${VPN_USER} l2tpd ${VPN_PASSWORD} *
EOF
fi

VPN_PASSWORD_ENC=$(openssl passwd -1 "${VPN_PASSWORD}")
if ! grep -qw "${VPN_USER}" /etc/ipsec.d/passwd 2>/dev/null; then
    cat > /etc/ipsec.d/passwd <<EOF
${VPN_USER}:${VPN_PASSWORD_ENC}:xauth-psk
EOF
fi

chmod 600 /etc/ipsec.secrets /etc/ppp/chap-secrets /etc/ipsec.d/passwd

# Update sysctl settings
if is_64bit; then
    SHM_MAX=68719476736
    SHM_ALL=4294967296
else
    SHM_MAX=4294967295
    SHM_ALL=268435456
fi

sysctl -eqw kernel.msgmnb=65536
sysctl -eqw kernel.msgmax=65536
sysctl -eqw kernel.shmmax=${SHM_MAX}
sysctl -eqw kernel.shmall=${SHM_ALL}
sysctl -eqw net.ipv4.ip_forward=1
sysctl -eqw net.ipv4.conf.all.accept_source_route=0
sysctl -eqw net.ipv4.conf.all.accept_redirects=0
sysctl -eqw net.ipv4.conf.all.send_redirects=0
sysctl -eqw net.ipv4.conf.all.rp_filter=0
sysctl -eqw net.ipv4.conf.default.accept_source_route=0
sysctl -eqw net.ipv4.conf.default.accept_redirects=0
sysctl -eqw net.ipv4.conf.default.send_redirects=0
sysctl -eqw net.ipv4.conf.default.rp_filter=0
sysctl -eqw net.ipv4.conf.eth0.send_redirects=0
sysctl -eqw net.ipv4.conf.eth0.rp_filter=0

# Create iptables rules
iptables -I INPUT 1 -p udp --dport 1701 -m policy --dir in --pol none -j DROP
iptables -I INPUT 2 -m conntrack --ctstate INVALID -j DROP
iptables -I INPUT 3 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
iptables -I INPUT 4 -p udp -m multiport --dports 500,4500 -j ACCEPT
iptables -I INPUT 5 -p udp --dport 1701 -m policy --dir in --pol ipsec -j ACCEPT
iptables -I INPUT 6 -p udp --dport 1701 -j DROP
iptables -I FORWARD 1 -m conntrack --ctstate INVALID -j DROP
iptables -I FORWARD 2 -i eth+ -o ppp+ -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
iptables -I FORWARD 3 -i ppp+ -o eth+ -j ACCEPT
iptables -I FORWARD 4 -i ppp+ -o ppp+ -s "${L2TP_NET}" -d "${L2TP_NET}" -j ACCEPT
iptables -I FORWARD 5 -i eth+ -d "${XAUTH_NET}" -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
iptables -I FORWARD 6 -s "${XAUTH_NET}" -o eth+ -j ACCEPT
iptables -A FORWARD -j DROP
iptables -t nat -I POSTROUTING -s "${XAUTH_NET}" -o eth+ -m policy --dir out --pol none -j MASQUERADE
iptables -t nat -I POSTROUTING -s "${L2TP_NET}" -o eth+ -j MASQUERADE

cat <<EOF

L2TP/IPsec VPN Server with the Username and Password is below:

Server IP: ${PUBLIC_IP}
IPSec PSK: ${VPN_IPSEC_PSK}
Username : ${VPN_USER}
Password : ${VPN_PASSWORD}

EOF

# Load IPsec kernel module
modprobe af_key

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