#!/bin/bash

set -e

default_ldap_host=ldap://127.0.0.1
default_ldap_base_dn="o=ls"
default_ldap_filter="(objectClass=lsOpenvpnClient)"
default_ldap_cert_attr="lsOpenvpnCertificate"
default_ca_cert=/etc/openvpn/ldap/ca.crt

debug=0
config_path=""
ldap_host=""
ldap_bind_dn=""
ldap_bind_password=""
ldap_base_dn=""
ldap_filter=""
ldap_cert_attr=""
ca_cert=""
certificate_depth=""
subject=""

usage() {
    [[ "$#" -ge 1 ]] && echo "$*" >&2
    cat << EOF
usage: $(basename "$0") [-d] [-H ldap_uri] [-D bind_dn] [-w bind_password] [-b base_dn] [-f ldap_filter] [-c cacert]
  -d|--debug                Enable debug mode
  -x|--trace                Enable Bash tracing mode
  -c|--config               Configuration file path (sourced as BASH file)
  -H|--ldap-host            LDAP host URI (default: $default_ldap_host)
  -D|--ldap-bind-dn         LDAP bind DN (default: anonymous)
  -w|--ldap-bind-password   LDAP bind password
  -b|--ldap-base-dn         LDAP base DN (default: $default_ldap_base_dn)
  -f|--ldap-filter          LDAP filter (default: $default_ldap_filter)
  -a|--ldap-cert-attr       LDAP client certificate attribute name (default: $default_ldap_cert_attr)
  -C|--ca-cert              CA certificate path (default: $default_ca_cert)
EOF
    [[ "$#" -ge 1 ]] && exit 1
    exit 0
}

debug() { [[ "$debug" -eq 1 ]] || return 0; echo "[DEBUG] $*" >&2; }
error() { echo "[ERROR] $*" >&2; exit 1; }

idx=1
while [[ $idx -le $# ]]; do
    opt=${!idx}
    case $opt in
        -d|--debug)
            debug=1
        ;;
        -x|--trace)
            set -x
        ;;
        -c|--config)
            ((idx++))
            config_path=${!idx}
            [[ ! -e "$config_path" ]] && usage "Configuration file '$config_path' not found"
            source "$config_path"
        ;;
        -H|--ldap-host)
            ((idx++))
            ldap_host=${!idx}
        ;;
        -D|--ldap-bind-dn)
            ((idx++))
            ldap_bind_dn=${!idx}
        ;;
        -w|--ldap-bind-password)
            ((idx++))
            ldap_bind_password=${!idx}
        ;;
        -b|--ldap-base-dn)
            ((idx++))
            ldap_base_dn=${!idx}
        ;;
        -f|--ldap-filter)
            ((idx++))
            ldap_filter=${!idx}
        ;;
        -a|--ldap-cert-attr)
            ((idx++))
            ldap_cert_attr=${!idx}
        ;;
        -c|--ca-cert)
            ((idx++))
            ca_cert=${!idx}
        ;;
        -h|--help)
            usage
        ;;
        *)
            if [[ -z "$certificate_depth" ]]; then
                certificate_depth="$opt"
            elif [[ -z "$subject" ]]; then
                subject="$opt"
            else
                usage "Invalid argument '$opt'"
            fi
    esac
    ((idx++))
done

[[ -z "$peer_cert" ]] && usage "Environment variable 'peer_cert' is not defined!"
[[ -z "$ldap_host" ]] && ldap_host=$default_ldap_host
[[ -z "$ldap_base_dn" ]] && ldap_base_dn=$default_ldap_base_dn
[[ -z "$ldap_filter" ]] && ldap_filter=$default_ldap_filter
[[ -z "$ldap_cert_attr" ]] && ldap_cert_attr=$default_ldap_cert_attr
[[ -z "$ca_cert" ]] && ca_cert=$default_ca_cert

debug "certificate_depth=$certificate_depth"
debug "subject=$subject"
debug "peer_cert=$peer_cert"
debug "common_name=$common_name"
debug "$(cat "$peer_cert")"
debug "$(openssl x509 --text -in "$peer_cert" -noout )"

fingerprint=$(
    openssl x509 -in "$peer_cert" -noout -fingerprint -sha256 | \
    cut -d'=' -f2 | \
    tr -d ':'
)
debug "fingerprint=$fingerprint"

if [[ -z "$common_name" ]]; then
    ca_cert_fingerprint=$(
        openssl x509 -in "$ca_cert" -noout -fingerprint -sha256 | \
        cut -d'=' -f2 | \
        tr -d ':'
    )
    debug "CA cert fingerprint=$fingerprint"

    if [[ "$ca_cert_fingerprint" == "$fingerprint" ]]; then
        debug "CA cert detected => access granted"
        exit 0
    else
        error "No common name specified and fingerprint differ with CA cert ($fingerprint != $ca_cert_fingerprint)"
    fi
fi

ldapsearch_args=()
[[ -n "$ldap_bind_dn" ]] && [[ -n "$ldap_bind_password" ]] && \
    ldapsearch_args+=( -D "$ldap_bind_dn" -w "$ldap_bind_password" )

mapfile -t certs < <(
    ldapsearch -x -LLL -o ldif-wrap=no \
        -H "$ldap_host" \
        -b "$ldap_base_dn" \
        "${ldapsearch_args[@]}" \
        "(&${ldap_filter}($ldap_cert_attr={$fingerprint}*))" \
        "$ldap_cert_attr" | \
            grep -E "^$ldap_cert_attr:: " | \
            sed "s/^$ldap_cert_attr:: //"
)
debug "${#certs[@]} certificate(s) found in LDAP with fingerprint $fingerprint"
for cert in "${certs[@]}"; do
    cert=$( base64 -d <<< "$cert" )
    debug "cert=$cert"
    if [[ "$cert" == "{$fingerprint}$(cat "$peer_cert")" ]]; then
        debug "Match found for $common_name => access granted"
        exit 0
    fi
done
error "No match found for certificate $common_name with fingerprint $fingerprint => access denied"
