#!/bin/bash -eu

fatal() { echo "FATAL: $*" >&2; exit 1; }
warn() { echo "WARN: $*"; }
info() { echo "INFO: $*"; }

usage() {
cat<<EOF
Syntax: $0 client-name client-email [private-subnet] [--pass] [--auth-nocache]
Generate keys and configuration for a new client

Arguments:

    client-name         Unique name for client
    client-email        Client email address
    private-subnet      CIDR subnet behind client (optional)

    --pass              Protect client keys with a password

    --auth-nocache      This will force OpenVPN to immediately forget username/password
                        inputs after they are used. As a result, when OpenVPN needs a
                        username/password, it will prompt for input, which may be
                        multiple times during the duration of an OpenVPN session.
                        
    --port=<NUMBER>     Set client to connect to port <NUMBER> (Default: 1194).
                        Note: this does not change the port that OpenVPN listens on,
                        just the port that the client will connect on (e.g. if you
                        forward OpenVPN to alternate port via router/firewall/etc).
EOF
exit 1
}

expand_cidr() {
    addr=$(ipcalc -n "$1" | grep Address | awk '{print $2}')
    mask=$(ipcalc -n "$1" | grep Netmask | awk '{print $2}')
    echo "$addr $mask"
}
which ipcalc >/dev/null || fatal "ipcalc is not installed"

if [[ "$#" -lt "2" ]] || [[ "$#" -gt "5" ]]; then
    usage
fi

REMOTE_PORT='1194'

client_name="$1"
client_email="$2"
shift 2

password='nopass'
auth_nocache=
private_subnet=

for item in "$@"; do
    case "$item" in
        --pass)
            password=''
            ;;
        --auth-nocache)
            auth_nocache="auth-nocache"
            ;;
        --port*)
            REMOTE_PORT="$(echo "$item" | awk -F= '{print $2}')"
            ;;
        *)
            if [[ -z "$private_subnet" ]]; then
                private_subnet="$1"
            else
                usage
            fi
            ;;
    esac
    shift
done

SERVER_CFG='/etc/openvpn/server.conf'
SERVER_CCD='/etc/openvpn/server.ccd'

export EASYRSA='/etc/openvpn/easy-rsa'
export EASYRSA_PKI="$EASYRSA/keys"

mkdir -p "$EASYRSA_PKI"

SERVER_ADDR="$(grep PUBLIC_ADDRESS "$SERVER_CFG" | awk '{print $3}')"
[[ -n "$SERVER_ADDR" ]] || fatal "unable to determine PUBLIC_ADDRESS from $SERVER_CFG"
[[ -e "$EASYRSA_PKI/$client_name.ovpn" ]] && fatal "$EASYRSA_PKI/$client_name.ovpn exists"

EASYRSA_REQ_EMAIL="$client_email" "$EASYRSA/easyrsa" --batch build-client-full "$client_name" "$password"

if [[ -n "$private_subnet" ]]; then
cat >> "$SERVER_CFG" <<EOF
# subnet behind a client: $client_name
route $(expand_cidr "$private_subnet")

EOF

cat > "$SERVER_CCD/$client_name" <<EOF
iroute $(expand_cidr "$private_subnet")
EOF
warn "openvpn needs to be restarted for changes to take effect."
fi

cat > "$EASYRSA_PKI/$client_name.ovpn" <<EOF
client
remote $SERVER_ADDR $REMOTE_PORT
proto udp
remote-cert-tls server
$auth_nocache

cipher AES-256-GCM
auth SHA512

tls-client
dev tun
resolv-retry infinite
keepalive 10 120
nobind
verb 3

;user nobody
;group nogroup

<ca>
$(cat "$EASYRSA_PKI/ca.crt")
</ca>

key-direction 1
<tls-auth>
$(cat "$EASYRSA_PKI/ta.key")
</tls-auth>

<cert>
$(cat "$EASYRSA_PKI/issued/$client_name.crt")
</cert>

<key>
$(cat "$EASYRSA_PKI/private/$client_name.key")
</key>
EOF

echo
info "generated $EASYRSA_PKI/$client_name.ovpn"

