#!/usr/bin/env bash

################################################################################
# Copyright (C) 2021-2026 NICE s.r.l.
# All Rights Reserved
#
# This software is the confidential and proprietary information
# of NICE s.r.l. ("Confidential Information").
# You shall not disclose such Confidential Information
# and shall use it only in accordance with the terms of
# the license agreement you entered into with NICE.
################################################################################

set -u
set -e # Abort in case a command fails. Where we want to handle failures manually, we append "&& :"

declare -r -i DEBUG_ENABLE_TRACING=0

if [[ $DEBUG_ENABLE_TRACING -ne 0 ]]; then
    set -x
fi

# Create temporary log file
readonly TMP_SESSION_LOG="/tmp/dcv-virtualsmartcard.tmp$$.log"
exec >"$TMP_SESSION_LOG" 2>&1

# Configure substitutions
readonly prefix=/usr
readonly exec_prefix=/usr
readonly bindir=/usr/bin
readonly datadir=/usr/share
readonly libdir=/usr/lib/aarch64-linux-gnu
readonly libexecdir=/usr/lib/aarch64-linux-gnu

# Defaults
readonly DEFAULT_LOG_LEVEL="info"
declare -r -i DEFAULT_LOG_ROTATE=10

# Log settings
if [[ ! ${DCV_VIRTUAL_SMARTCARD_LOG_VERBOSITY-} ]]; then
    declare -r -i DCV_VIRTUAL_SMARTCARD_LOG_VERBOSITY=3
fi
declare -r -i LOG_VERBOSITY=$DCV_VIRTUAL_SMARTCARD_LOG_VERBOSITY # 0: silent, 1: error, 2: warning, 3:info, 4:debug
declare LOG_DOMAIN="virtual-smartcard"

# Constants
readonly RUN_DIR=/run/dcv
readonly PIN_PATH="${RUN_DIR}/virtual-smartcard-pin"
readonly SOFTHSM2_MODULE="${libdir}/pkcs11/libsofthsm2.so"

error() { log 1 "ERROR" "$1"; }
warn() { log 2 "WARN " "$1"; }
message() { log 3 "INFO " "$1"; }
debug() { log 4 "DEBUG" "$1"; }
log() {
    if [[ $DEBUG_ENABLE_TRACING -ne 0 ]]; then
        set +x
    fi

    local -r -i verbosity=$1
    local -r level="$2"
    local -r msg="$3"

    if [ $LOG_VERBOSITY -ge "$verbosity" ]; then
         datestring=$(date -u +'%Y-%m-%d %H:%M:%S,%N')
         echo -e "$datestring [$$] $level $LOG_DOMAIN - $msg"
    fi

    if [[ $DEBUG_ENABLE_TRACING -ne 0 ]]; then
        set -x
    fi
}

check_mandatory_env_vars() {
    if [[ ! ${DCV_LOG_DIR-} ]]; then
        error "Missing mandatory environment variable DCV_LOG_DIR"
        die "$UNRECOVERABLE_ERROR"
    fi
    readonly DCV_LOG_DIR

    if [[ ! ${DCV_LOG_MAX_FILE_ROTATIONS-} ]]; then
        error "Missing mandatory environment variable DCV_LOG_MAX_FILE_ROTATIONS"
        die "$UNRECOVERABLE_ERROR"
    fi
    readonly DCV_LOG_MAX_FILE_ROTATIONS

    if [[ ! ${DCV_LOG_LEVEL-} ]]; then
        debug "Use default value '$DEFAULT_LOG_LEVEL' for DCV_LOG_LEVEL"
        DCV_LOG_LEVEL="$DEFAULT_LOG_LEVEL"
    fi
    readonly DCV_LOG_LEVEL
}

rotate_log() {
    local -r -i num=$1
    local -r fname="$2"

    debug "Rotate log file: $fname"

    for ((i = num - 1; i > 0 ; i--)); do
        if [ -r "$fname.$i" ]; then
            mv -f "$fname.$i" "$fname.$((i+1))" 2>/dev/null
        fi
    done

    if [ -r "$fname" ]; then
        if [[ $num -gt 0 ]]; then
            mv -f "$fname" "$fname.1"
        else
            rm -f "$fname"
        fi
    fi

    debug "Done"
}

die() {
    local -r -i ret=$1

    if [[ $EXITING -ne 0 ]]; then
        return
    fi

    EXITING=1

    # [Judges 16:30] Samson said, “Let me die with the Philistines!”
    cleanup_processes && :
    message "Exiting (exit value: $ret)"
    exit "$ret"
}

setup_virtual_smartcard() {
    local -r certificate_path=$1
    local -r private_key_path=$2

    if ! command -v softhsm2-util &> /dev/null; then
        message "softhsm2-util could not be found"
        return
    fi

    if ! command -v pkcs11-tool &> /dev/null; then
        message "pkcs11-tool could not be found"
        return
    fi

    if ! command -v openssl &> /dev/null; then
        message "openssl could not be found"
        return
    fi

    local -r pin=$(openssl rand -hex 128 | cut -c1-255)
    local -r so_pin=$(openssl rand -hex 128 | cut -c1-255)
    (umask 077; echo -n "${pin}" > "${PIN_PATH}")

    softhsm2-util  --delete-token --token dcvvirtualsmartcard || true
    softhsm2-util --init-token --label dcvvirtualsmartcard --pin "${pin}" --so-pin "${so_pin}" --free
    pkcs11-tool --module ${SOFTHSM2_MODULE} -l --pin "${pin}" --write-object "${private_key_path}" --type privkey --id 2222 --label dcvvirtualsmartcard
    pkcs11-tool --module ${SOFTHSM2_MODULE} -l --pin "${pin}" --write-object "${certificate_path}" --type cert --id 2222 --label dcvvirtualsmartcard
}

delete_virtual_smartcard() {
    softhsm2-util  --delete-token --token dcvvirtualsmartcard || true
    rm -f "${PIN_PATH}"
}

main() {
    check_mandatory_env_vars
    local -r command=$1
    shift

    # Initial command validation
    case "${command}" in
        setup | delete )
            ;;
        * )
            echo "Unknown option '$1'"
            return 1
            ;;
    esac

    local -r virtual_smartcard_log="$DCV_LOG_DIR/virtualsmartcard-${command}.log"

    if [[ ${DCV_LOG_MAX_FILE_ROTATIONS-} ]]; then
        rotate_log "$DCV_LOG_MAX_FILE_ROTATIONS" "$virtual_smartcard_log"
    fi

    exec >> "$virtual_smartcard_log" 2>&1

    message "Setup virtual smartcard launched (PID: $$)"

    trap '
        debug "Handle SIGINT and SINGTERM"
        die 0
    ' INT TERM

    # Remove temporary log file
    cat "$TMP_SESSION_LOG"
    rm "$TMP_SESSION_LOG"

    if [ $LOG_VERBOSITY -ge 4 ]; then
        debug "Execution environment:"
        echo
        env
        echo "========="
    fi

    case "${command}" in
        setup )
            local certificate_path=""
            local private_key_path=""

            while [ -n "${1-}" ]; do
                case "$1" in
                    --certificate )
                        shift
                        certificate_path=$1
                        ;;
                    --private-key )
                        shift
                        private_key_path=$1
                        ;;
                    * )
                        echo "Invalid parameter '$1'"
                        return 1
                        ;;
                esac
                shift
            done

            if [[ "${certificate_path}" = "" ]]; then
                echo "Missing --certificate parameter"
                return 1
            fi

            if [[ "${private_key_path}" = "" ]]; then
                echo "Missing --private-key parameter"
                return 1
            fi

            setup_virtual_smartcard ${certificate_path} ${private_key_path}
            ;;
        delete )
            delete_virtual_smartcard
            ;;
        * )
            # It should never reach here. The validation has been done before
            echo "Unknown option '$1'"
            return 1
            ;;
    esac

    set -m
    wait && :
    set +m

    debug "No more children"
}

main "$@"

# ex: set ts=4 sw=4 et syntax=sh :
