#!/bin/bash

################################################################################
################################################################################
# Copyright 2023-2025 by NI SP Software GmbH, All rights reserved.
# Copyright 1999-2023 by Nice, srl., All rights reserved.
#
# This software includes confidential and proprietary information
# of NI SP Software GmbH ("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 NI SP Software.
################################################################################
################################################################################

# Avoid recursive loops

[ -n "$INTERACTIVE_DO_NOT_RECURSE" ] && exit 0

INTERACTIVE_DO_NOT_RECURSE=true
export INTERACTIVE_DO_NOT_RECURSE

EF_PLUGIN_NAME="EnginFrame Interactive Plug-in"
export EF_PLUGIN_NAME

EF_PLUGIN_FILE="Interactive"
export EF_PLUGIN_FILE

EF_PLUGIN_ROOT="${EF_ROOT}/plugins/interactive"
export EF_PLUGIN_ROOT


# Load common EnginFrame functions and environment
. "${EF_ROOT}/plugins/ef/lib/utils"
. "${EF_ROOT}/plugins/ef/lib/plugins"

# Load environment variables for the session's job
ef_source_conf interactive "interactive.session.job.conf"

# Library functions

interactive_nat() {
    _host="$1"
    _port="$2"

    _natfile="${EF_CONF_ROOT}/plugins/interactive/nat.conf"

    unset _res
    if [ -r "${_natfile}" ]; then

        _res=$(awk -v host="${_host}" -v port="${_port}" '
            BEGIN {
                FS="[ \t:]+";
                lhost = tolower(host);
            }

            # skip comments
            /^[ \t]*#/ {
                next;
            }

            (tolower($1) == lhost) {
                if (NF == 2 || (NF == 3 && $NF == "")) {
                    # Handle cases in which we have only non-empty 2 fields

                    # remove trailing spaces and comments
                    gsub(/[ \t]+(#.*)?$/, "", $2);

                    print $2 ":" port;
                } else {

                    # remove trailing spaces and comments
                    gsub(/[ \t]+(#.*)?$/, "", $4);

                    i = index($2, "-");
                    if (i == 0) {
                        minport = maxport = $2;
                        offset = 0;
                    } else {
                        minport = substr($2, 1, i-1);
                        maxport = substr($2, i+1);
                        offset = port - minport;
                    }
                    i = index($4, "-");
                    if (i == 0) {
                        mminport = mmaxport = $4;
                    } else {
                        mminport = substr($4, 1, i-1);
                        mmaxport = substr($4, i+1);
                    }
                    if (port >= minport && port <= maxport) {
                        mhost = $3;
                        mport = mminport + offset;
                        print mhost ":" mport;
                        exit;
                    }
                }
            }
        ' "${_natfile}")
    fi

    if [ -n "${_res}" ]; then
        echo "${_res}"
    else
        echo "${_host}:${_port}"
    fi
}

# Selects a proxy from the proxy.conf file
# Usage: interactive_proxy host port [type]
interactive_proxy() {
    local _proxyconf
    local _client_host
    local _server_host
    local _proxytype
    local _proxy_list
    local _proxy
    local _exit_code

    _client_host="$1"
    _server_host="$2"
    _proxytype="$3"

    _proxyconf="${EF_CONF_ROOT}/plugins/interactive/proxy.conf"

    if [ ! -r "${_proxyconf}" ]; then
        return 0
    fi

    _proxy_list=$(proxy_filter "${_client_host}" "${_server_host}" "${_proxyconf}" "${_proxytype}")
    if [ -z "${_proxy_list}" ]; then
        proxy_log "ERROR" "No proxy configured for ${_client_host} -> ${_server_host} connection."
        return 1
    else
        _proxy=$(select_proxy "${_proxy_list}")
        _exit_code=$?
        if [ ${_exit_code} -ne 0 ]; then
            proxy_log "ERROR" "No proxy available for ${_client_host} -> ${_server_host} connection."
            return 1
        fi
        echo "${_proxy}"
        return 0
    fi
}

# Filters the proxy.conf file depending on client, server hosts and optionally proxy type
proxy_filter() {
    local _client_host
    local _server_host
    local _proxyconf
    local _proxytype

    _client_host="$1"
    _server_host="$2"
    _proxyconf="$3"
    _proxytype="$4"

    gawk \
        -v client_host="${_client_host}" \
        -v server_host="${_server_host}" \
        -v proxytype="${_proxytype}" \
        -f "${EF_ROOT}/plugins/ef/lib/awk/glob.awk" \
        -f "${EF_ROOT}/plugins/ef/lib/awk/network.awk" \
        -f "${EF_ROOT}/plugins/interactive/bin/proxy.awk" \
        "${_proxyconf}"
}

# Rearranges the list of proxies starting from the selected line
# Usage sort_proxies list start
sort_proxies() {
    local _list
    local _num_of_lines
    local _start

    _list="$1"
    _num_of_lines=$(echo "${_list}" | wc -l)
    _start=$(($RANDOM%${_num_of_lines}+1))

    # Print from _start to EOF
    echo "${_list}" | sed -n -e "${_start},\$ {p;}"
    if [ ${_start} -ne 1 ]; then
        # Print from 1 to _cursor-1
        echo "${_list}" | sed -n -e "1,$((_start-1)) {p;}"
    fi
}

proxy_log() {
    common_log "${INTERACTIVE_PROXY_LOG_DEBUG}" "${EF_LOGDIR}/interactive.proxy.log" "$@"
}

url_mapping_log() {
    common_log "${INTERACTIVE_URL_MAPPING_LOG_DEBUG}" "${EF_LOGDIR}/interactive.url.mapping.log" "$@"
}

write_to_log() {
    local -r _log_file="$1"
    shift
    common_log "${INTERACTIVE_SESSION_HOOK_LOG_DEBUG}" "${_log_file}" "$@"
}

common_log() {
    local -r _debug_var="$1"
    local -r _log_file="$2"
    local -r _level="$3"
    local -r _message="$4"

    local -r _date=$(date "+%Y/%m/%d %H:%M:%S")

    if [ "${_level}" != "DEBUG" -o "${_debug_var}" = "true" ]; then
        printf "[%s] [%s] %s\n" "${_date}" "${_level}" "${_message}" >> "${_log_file}"
    fi
}

check_proxy() {
    local _host="$1"
    local _port="$2"
    local _type="$3"

    if [ -n "${INTERACTIVE_CHECK_PROXY_SCRIPT}" -a -x "${INTERACTIVE_CHECK_PROXY_SCRIPT}" ]; then
        "${INTERACTIVE_CHECK_PROXY_SCRIPT}" "${_host}" "${_port}" "${_type}"
    fi
}

select_proxy() {
    local _full_proxy_list

    local _priority_list
    local _priority

    local _proxy_list

    local _proxy_line

    local _type
    local _host
    local _port

    local _check_result
    local _exit_code

    _full_proxy_list="$1"
    _priority_list=$(echo "${_full_proxy_list}" | cut -d' ' -f1 | sort -n | uniq)

    for _priority in ${_priority_list} ; do
        _proxy_list=$(echo "${_full_proxy_list}" | grep "^${_priority}"'[ \t$]' | cut -d' ' -f2-)
        _proxy_list=$(sort_proxies "${_proxy_list}")

        OLD_IFS="${IFS}"
        IFS=$'\n'

        for _proxy_line in ${_proxy_list} ; do
            _type=$(echo "${_proxy_line}" | cut -d":" -f1)
            if [ "${_type}" = "DIRECT" ]; then
                return 0
            fi
            _host=$(echo "${_proxy_line}" | cut -d":" -f2)
            _port=$(echo "${_proxy_line}" | cut -d":" -f3)
            _check_result=$(check_proxy "${_host}" "${_port}" "${_type}")
            _exit_code=$?
            if [ ${_exit_code} = 0 ]; then
                proxy_log "DEBUG" "Proxy ${_proxy_line} is UP: ${_check_result}"
                echo "${_proxy_line}"
                return 0
            else
                proxy_log "WARN" "Proxy ${_proxy_line} is DOWN: ${_check_result}"
            fi
        done

        IFS="${OLD_IFS}"
    done
    return 1
}

write_session_attribute() {
    local -r _key="${1}"
    local -r _var="${2-${_key}}"
    local -r _val="${!_var}"
    [[ -n ${_key} ]] && [[ -n ${_var} ]] && [[ -n ${_val} ]] || return 1
    printf '<ia:attr id="%s">%s</ia:attr>\n' "$(ef_xml_escape_attribute -i "${_key}")" "$(ef_xml_escape_content -i "${_val}")"
}

interactive_url_mapping() {
    local -r server_host="$1"
    local -r server_port="$2"
    local -r server_web_url_path="$3"
    local -r session_id="$4"
    local -r nat_server_host="$5"
    local -r nat_server_port="$6"
    local -r proxy_host="$7"
    local -r proxy_port="$8"

    if [ -z "${server_host}" ] && [ -z "${session_id}" ]; then
        # if no execution host nor session id is present, it's not worth to go forward
        return 0
    fi

    local -r _url_mappingconf="${EF_CONF_ROOT}/plugins/interactive/url.mapping.conf"

    if [ -r "${_url_mappingconf}" ]; then
        _url_mapping_list=$(filter_url_mapping "${server_host}" "${_url_mappingconf}")
    fi

    if [ -z "${_url_mapping_list}" ]; then
        # Default when no url mapping conf file is present or file is empty or there is no server host match
        _url_mapping="${nat_server_host}:${nat_server_port}:${server_web_url_path}"
        url_mapping_log "INFO" "Use default url mapping (${_url_mapping}) for connection to (${server_host}) for session (${session_id})"
    else
        _url_mapping=$(select_url_mapping "${_url_mapping_list}")
        url_mapping_log "INFO" "Use configured url mapping (${_url_mapping}) for connection to (${server_host}) for session (${session_id})"
    fi

    echo "${_url_mapping}"
}

# Filters the url.mapping.conf file depending on server host
# return N lines in the format
# priority target_host:target_port:target_web_url_path
filter_url_mapping() {
    local -r _server_host="$1"
    local -r _url_mappingconf="$2"

    gawk \
        -v server_host="${_server_host}" \
        -f "${EF_ROOT}/plugins/ef/lib/awk/glob.awk" \
        -f "${EF_ROOT}/plugins/interactive/bin/url.mapping.awk" \
        "${_url_mappingconf}"
}

# select a url mapping from a list of url mapping with priority
#
# input format for $1
# priority1 target_host1:target_port2:target_web_url_path\n
# priority2 target_host2:target_port2:target_web_url_path2
#
# output format
# target_host:target_port:target_web_url_path
select_url_mapping() {
    local -r _full_proxy_list="$1"
    local -r _priority_list=$(echo "${_full_proxy_list}" | cut -d' ' -f1 | sort -n | uniq)  # e.g. 1\n2\n99

    local _priority
    local _url_mapping_list
    local _url_mapping_line

    local _host
    local _port
    local _web_url

    # take only the first line of _priority_list (empty lines are ignored)
    _priority=(${_priority_list[@]})
    _priority="${_priority[0]}" # e.g. 1

    _url_mapping_list=$(echo "${_full_proxy_list}" | grep "^${_priority}"'[ \t$]' | cut -d' ' -f2-)
    _url_mapping_list=$(sort_proxies "${_url_mapping_list}") # e.g. target_host2:target_port2:target_web_url_path2\ntarget_host1:target_port1:target_web_url_path1

    # take only the first line of _url_mapping_line (empty lines are ignored)
    _url_mapping_list=(${_url_mapping_list[@]})
    _url_mapping_line="${_url_mapping_list[0]}" # e.g. target_host2:target_port2:target_web_url_path2

    IFS=':' read -r -a _url_mapping_line_array <<< "${_url_mapping_line}"

    _host="${_url_mapping_line_array[0]}"
    _host=$(. <(echo -e echo "${_host}"))

    _port="${_url_mapping_line_array[1]}"
    _port=$(. <(echo -e echo "${_port}"))

    _web_url="${_url_mapping_line_array[2]}"
    _web_url=$(. <(echo -e echo "${_web_url}"))

    echo "${_host}:${_port}:${_web_url}"
}

# ex:ts=4:sw=4:et:ft=sh:
