#!/bin/bash -eu

fatal() { echo "FATAL: $@" 1>&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 [[ "$#" < "2" ]] || [[ "$#" > "5" ]]; then
    usage
fi

REMOTE_PORT='1194'

client_name=$1
client_email=$2
shift; shift # remove processed arguments

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

EASY_RSA=/etc/openvpn/easy-rsa
SERVER_CFG=/etc/openvpn/server.conf
SERVER_CCD=/etc/openvpn/server.ccd

export EASYRSA_VARS_FILE=$EASY_RSA/vars

read_var() {
    name=$1
    sed -n 's/set_var '"$name"' "\(.*\)"/\1/p' $EASY_RSA/vars
}

KEY_DIR=$(read_var EASYRSA_PKI)
SERVER_ADDR=$(grep PUBLIC_ADDRESS $SERVER_CFG | awk '{print $3}')
[ "$SERVER_ADDR" ] || fatal "unable to determine PUBLIC_ADDRESS from $SERVER_CFG"

[ -d $KEY_DIR ] || mkdir "$KEY_DIR"
[ -e $KEY_DIR/$client_name.ovpn ] && fatal "$KEY_DIR/$client_name.ovpn exists"

export EASYRSA_PKI=$EASY_RSA/keys
KEY_CN=$client_name KEY_EMAIL=$client_email $EASY_RSA/easyrsa build-client-full $client_name $password

if [ "$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 > $KEY_DIR/$client_name.ovpn <<EOF
client
remote $SERVER_ADDR $REMOTE_PORT
proto udp
remote-cert-tls server
$auth_nocache

cipher AES-256-CBC
auth SHA256

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

;user nobody
;group nogroup

<ca>
$(cat $KEY_DIR/ca.crt)
</ca>

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

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

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

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

