#!/bin/bash

#
# Generate OpenVPN configs
#

# Convert 1.2.3.4/24 -> 255.255.255.0
cidr2mask()
{
    local i
    local subnetmask=""
    local cidr=${1#*/}
    local full_octets=$(($cidr/8))
    local partial_octet=$(($cidr%8))

    for ((i=0;i<4;i+=1)); do
        if [ $i -lt $full_octets ]; then
            subnetmask+=255
        elif [ $i -eq $full_octets ]; then
            subnetmask+=$((256 - 2**(8-$partial_octet)))
        else
            subnetmask+=0
        fi
        [ $i -lt 3 ] && subnetmask+=.
    done
    echo $subnetmask
}

# Used often enough to justify a function
getroute() {
    echo ${1%/*} $(cidr2mask $1)
}

usage() {
    echo "usage: $0 [-d]"
    echo "                  -u SERVER_PUBLIC_URL"
    echo "                 [-s SERVER_SUBNET]"
    echo "                 [-r ROUTE ...]"
    echo "                 [-p PUSH ...]"
    echo
    echo "optional arguments:"
    echo " -d    Disable NAT routing and default route"
    echo " -c    Enable client-to-client option"
}

set -ex

OVPN_ENV=$OPENVPN/ovpn_env.sh
OVPN_SERVER=192.168.255.0/24
OVPN_DEFROUTE=1
OVPN_ROUTES=()
OVPN_PUSH=()

# Import defaults if present
[ -r "$OVPN_ENV" ] && source "$OVPN_ENV"

# Parse arguments
while getopts ":r:s:du:cp:" opt; do
    case $opt in
        r)
            OVPN_ROUTES+=("$OPTARG")
            ;;
        s)
            OVPN_SERVER=$OPTARG
            ;;
        d)
            OVPN_DEFROUTE=0
            ;;
        u)
            OVPN_SERVER_URL=$OPTARG
            ;;
        c)
            OVPN_CLIENT_TO_CLIENT=1
            ;;
        p)
            OVPN_PUSH+=("$OPTARG")
            ;;
        \?)
            set +x
            echo "Invalid option: -$OPTARG" >&2
            usage
            exit 1
            ;;
        :)
            set +x
            echo "Option -$OPTARG requires an argument." >&2
            usage
            exit 1
            ;;
    esac
done


# Server name is in the form "udp://vpn.example.com:1194"
if [[ "$OVPN_SERVER_URL" =~ ^((udp|tcp)://)?([0-9a-zA-Z\.\-]+)(:([0-9]+))?$ ]]; then
    OVPN_PROTO=${BASH_REMATCH[2]};
    OVPN_CN=${BASH_REMATCH[3]};
    OVPN_PORT=${BASH_REMATCH[5]};
else
    set +x
    echo "Common name not specified, see '-u'"
    usage
    exit 1
fi

# Apply defaults
[ -z "$OVPN_PROTO" ] && OVPN_PROTO=udp
[ -z "$OVPN_PORT" ] && OVPN_PORT=1194
[ ${#OVPN_ROUTES[@]} -eq 0 ] && OVPN_ROUTES=("192.168.254.0/24")

export OVPN_SERVER OVPN_ROUTES OVPN_DEFROUTE
export OVPN_SERVER_URL OVPN_ENV OVPN_PROTO OVPN_CN OVPN_PORT
export OVPN_CLIENT_TO_CLIENT OVPN_PUSH

# Preserve config
if [ -f "$OVPN_ENV" ]; then
    bak_env=$OVPN_ENV.$(date +%s).bak
    echo "Backing up $OVPN_ENV -> $bak_env"
    mv "$OVPN_ENV" "$bak_env"
fi
export | grep OVPN_ > "$OVPN_ENV"

conf=$OPENVPN/openvpn.conf
if [ -f "$conf" ]; then
    bak=$conf.$(date +%s).bak
    echo "Backing up $conf -> $bak"
    mv "$conf" "$bak"
fi

cat > "$conf" <<EOF
server $(getroute $OVPN_SERVER)
verb 3
#duplicate-cn
key $EASYRSA_PKI/private/${OVPN_CN}.key
ca $EASYRSA_PKI/ca.crt
cert $EASYRSA_PKI/issued/${OVPN_CN}.crt
dh $EASYRSA_PKI/dh.pem
tls-auth $EASYRSA_PKI/ta.key
key-direction 0
keepalive 10 60
persist-key
persist-tun
push "dhcp-option DNS 8.8.4.4"
push "dhcp-option DNS 8.8.8.8"

proto $OVPN_PROTO
# Rely on Docker to do port mapping, internally always 1194
port 1194
dev tun0
status /tmp/openvpn-status.log

client-config-dir $OPENVPN/ccd
EOF

[ -n "$OVPN_CLIENT_TO_CLIENT" ] && echo "client-to-client" >> "$conf"

# Append Routes
for i in "${OVPN_ROUTES[@]}"; do
    # If user passed "0" skip this, assume no extra routes
    [ "$i" = "0" ] && break;
    echo route $(getroute "$i") >> "$conf"
done

# Append push commands
for i in "${OVPN_PUSH[@]}"; do
    echo push \"$i\" >> "$conf"
done

# Clean-up duplicate configs (always return success)
diff -q "$bak_env" "$OVPN_ENV" 2> /dev/null && rm "$bak_env" || true
diff -q "$bak" "$conf" 2> /dev/null && rm "$bak" || true

echo "Successfully generated config"