#!/bin/sh
#
# RPCD backend for SecuBox Service Exposure Manager
#

. /usr/share/libubox/jshn.sh
. /lib/functions.sh

case "$1" in
    list)
        json_init
        json_add_object "scan"
        json_close_object
        json_add_object "conflicts"
        json_close_object
        json_add_object "status"
        json_close_object
        json_add_object "tor_list"
        json_close_object
        json_add_object "ssl_list"
        json_close_object
        json_add_object "get_config"
        json_close_object
        json_add_object "fix_port"
            json_add_string "service" "string"
            json_add_int "port" "integer"
        json_close_object
        json_add_object "tor_add"
            json_add_string "service" "string"
            json_add_int "local_port" "integer"
            json_add_int "onion_port" "integer"
        json_close_object
        json_add_object "tor_remove"
            json_add_string "service" "string"
        json_close_object
        json_add_object "ssl_add"
            json_add_string "service" "string"
            json_add_string "domain" "string"
            json_add_int "local_port" "integer"
        json_close_object
        json_add_object "ssl_remove"
            json_add_string "service" "string"
        json_close_object
        json_add_object "vhost_list"
        json_close_object
        json_add_object "emancipate"
            json_add_string "service" "string"
            json_add_int "port" "integer"
            json_add_string "domain" "string"
            json_add_boolean "tor" "boolean"
            json_add_boolean "dns" "boolean"
            json_add_boolean "mesh" "boolean"
        json_close_object
        json_add_object "revoke"
            json_add_string "service" "string"
            json_add_boolean "tor" "boolean"
            json_add_boolean "dns" "boolean"
            json_add_boolean "mesh" "boolean"
        json_close_object
        json_add_object "get_emancipated"
        json_close_object
        json_dump
        ;;

    call)
        case "$2" in
            scan)
                # Scan listening services - use temp file to avoid subshell issues
                TMP_SVC="/tmp/exposure_scan_$$"
                TMP_NAMES="/tmp/exposure_names_$$"
                > "$TMP_NAMES"

                netstat -tlnp 2>/dev/null | grep LISTEN | awk '{
                    split($4, a, ":")
                    port = a[length(a)]
                    if (!seen[port]++) {
                        split($7, p, "/")
                        proc = p[2]
                        if (proc == "") proc = "unknown"
                        print port, $4, proc
                    }
                }' | sort -n > "$TMP_SVC"

                # Build port->name enrichment from component configs

                # uhttpd instances
                for _s in $(uci show uhttpd 2>/dev/null | grep "=uhttpd$" | cut -d'.' -f2 | cut -d'=' -f1); do
                    _listen=$(uci -q get "uhttpd.${_s}.listen_http")
                    [ -z "$_listen" ] && continue
                    _p=$(echo "$_listen" | grep -o '[0-9]*$')
                    case "$_s" in
                        main) echo "$_p|LuCI" >> "$TMP_NAMES" ;;
                        acme) echo "$_p|ACME Challenge" >> "$TMP_NAMES" ;;
                        metablog_site_*) echo "$_p|Metablog: $(echo "$_s" | sed 's/^metablog_site_//')" >> "$TMP_NAMES" ;;
                        p2p_api) echo "$_p|P2P API" >> "$TMP_NAMES" ;;
                        *) echo "$_p|uhttpd: $_s" >> "$TMP_NAMES" ;;
                    esac
                done

                # Streamlit instances
                for _s in $(uci show streamlit 2>/dev/null | grep "\.port=" | cut -d'.' -f2); do
                    _p=$(uci -q get "streamlit.${_s}.port")
                    _n=$(uci -q get "streamlit.${_s}.name")
                    [ -n "$_p" ] && echo "$_p|Streamlit: ${_n:-$_s}" >> "$TMP_NAMES"
                done

                # Docker containers
                docker ps --format '{{.Ports}}|{{.Names}}' 2>/dev/null | while IFS='|' read _ports _cname; do
                    [ -z "$_cname" ] && continue
                    echo "$_ports" | tr ',' '\n' | while read _bind; do
                        _hp=$(echo "$_bind" | sed -n 's/.*:\([0-9]*\)->.*/\1/p')
                        [ -n "$_hp" ] && echo "$_hp|Docker: $_cname" >> "$TMP_NAMES"
                    done
                done

                # Glances
                _gp=$(uci -q get glances.main.web_port)
                [ -n "$_gp" ] && echo "$_gp|Glances" >> "$TMP_NAMES"

                # Known services by port
                echo "9000|Lyrion" >> "$TMP_NAMES"
                echo "3483|Lyrion Discovery" >> "$TMP_NAMES"
                echo "9090|Lyrion CLI" >> "$TMP_NAMES"

                json_init
                json_add_array "services"

                while read port addr proc; do
                    [ -z "$port" ] && continue

                    external=0
                    case "$addr" in
                        *0.0.0.0*|*::*) external=1 ;;
                        *127.0.0.1*|*::1*) external=0 ;;
                        *) external=1 ;;
                    esac

                    # Try enriched name first, fallback to process-based mapping
                    name=$(grep "^${port}|" "$TMP_NAMES" | head -1 | cut -d'|' -f2)
                    if [ -z "$name" ]; then
                        case "$proc" in
                            sshd|dropbear) name="SSH" ;;
                            dnsmasq) name="DNS" ;;
                            haproxy) name="HAProxy" ;;
                            uhttpd) name="LuCI" ;;
                            gitea) name="Gitea" ;;
                            netifyd) name="Netifyd" ;;
                            tor) name="Tor" ;;
                            python*) name="Python App" ;;
                            streamlit) name="Streamlit" ;;
                            hexo|node) name="HexoJS" ;;
                            *) name="$proc" ;;
                        esac
                    fi

                    json_add_object ""
                        json_add_int "port" "$port"
                        json_add_string "address" "$addr"
                        json_add_string "process" "$proc"
                        json_add_string "name" "$name"
                        json_add_boolean "external" "$external"
                    json_close_object
                done < "$TMP_SVC"

                rm -f "$TMP_SVC" "$TMP_NAMES"
                json_close_array
                json_dump
                ;;

            status)
                json_init

                total=$(netstat -tlnp 2>/dev/null | grep LISTEN | awk '{split($4,a,":"); print a[length(a)]}' | sort -u | wc -l)
                external=$(netstat -tlnp 2>/dev/null | grep LISTEN | grep -E "0\.0\.0\.0|::" | awk '{split($4,a,":"); print a[length(a)]}' | sort -u | wc -l)

                json_add_object "services"
                    json_add_int "total" "$total"
                    json_add_int "external" "$external"
                json_close_object

                # Tor hidden services
                TOR_DIR="/var/lib/tor/hidden_services"
                tor_count=0
                [ -d "$TOR_DIR" ] && tor_count=$(ls -1d "$TOR_DIR"/*/ 2>/dev/null | wc -l)

                json_add_object "tor"
                    json_add_int "count" "$tor_count"
                    json_add_array "services"
                    if [ -d "$TOR_DIR" ]; then
                        for dir in "$TOR_DIR"/*/; do
                            [ -d "$dir" ] || continue
                            svc=$(basename "$dir")
                            onion=""
                            [ -f "$dir/hostname" ] && onion=$(cat "$dir/hostname")
                            if [ -n "$onion" ]; then
                                json_add_object ""
                                    json_add_string "service" "$svc"
                                    json_add_string "onion" "$onion"
                                json_close_object
                            fi
                        done
                    fi
                    json_close_array
                json_close_object

                # HAProxy SSL backends - read from UCI config
                TMP_SSL="/tmp/exposure_ssl_$$"
                ssl_count=0

                # Get vhosts from UCI (enabled ones with domains)
                for vhost in $(uci show haproxy 2>/dev/null | grep "=vhost$" | cut -d'.' -f2 | cut -d'=' -f1); do
                    domain=$(uci -q get "haproxy.${vhost}.domain")
                    backend=$(uci -q get "haproxy.${vhost}.backend")
                    enabled=$(uci -q get "haproxy.${vhost}.enabled")
                    [ "$enabled" != "1" ] && continue
                    [ -z "$domain" ] && continue
                    echo "${backend:-$vhost}|${domain}" >> "$TMP_SSL"
                    ssl_count=$((ssl_count + 1))
                done

                json_add_object "ssl"
                    json_add_int "count" "$ssl_count"
                    json_add_array "backends"
                    if [ -f "$TMP_SSL" ]; then
                        while IFS='|' read backend domain; do
                            [ -z "$backend" ] && continue
                            json_add_object ""
                                json_add_string "service" "$backend"
                                json_add_string "domain" "$domain"
                            json_close_object
                        done < "$TMP_SSL"
                        rm -f "$TMP_SSL"
                    fi
                    json_close_array
                json_close_object

                json_dump
                ;;

            tor_list)
                TOR_DIR="/var/lib/tor/hidden_services"
                TOR_CONFIG="/etc/tor/torrc"

                json_init
                json_add_array "services"

                if [ -d "$TOR_DIR" ]; then
                    for dir in "$TOR_DIR"/*/; do
                        [ -d "$dir" ] || continue
                        svc=$(basename "$dir")
                        onion=""
                        [ -f "$dir/hostname" ] && onion=$(cat "$dir/hostname")

                        port=$(grep -A1 "HiddenServiceDir $dir" "$TOR_CONFIG" 2>/dev/null | grep HiddenServicePort | awk '{print $2}')
                        backend=$(grep -A1 "HiddenServiceDir $dir" "$TOR_CONFIG" 2>/dev/null | grep HiddenServicePort | awk '{print $3}')

                        if [ -n "$onion" ]; then
                            json_add_object ""
                                json_add_string "service" "$svc"
                                json_add_string "onion" "$onion"
                                json_add_string "port" "${port:-80}"
                                json_add_string "backend" "${backend:-N/A}"
                            json_close_object
                        fi
                    done
                fi

                json_close_array
                json_dump
                ;;

            ssl_list)
                TMP_SSLLIST="/tmp/exposure_ssllist_$$"
                > "$TMP_SSLLIST"

                # Read from HAProxy UCI config (vhosts with their backends)
                for vhost in $(uci show haproxy 2>/dev/null | grep "=vhost$" | cut -d'.' -f2 | cut -d'=' -f1); do
                    domain=$(uci -q get "haproxy.${vhost}.domain")
                    backend=$(uci -q get "haproxy.${vhost}.backend")
                    enabled=$(uci -q get "haproxy.${vhost}.enabled")

                    [ "$enabled" != "1" ] && continue
                    [ -z "$domain" ] && continue

                    # Get server address from backend config
                    server=""
                    if [ -n "$backend" ]; then
                        server=$(uci -q get "haproxy.${backend}.server" 2>/dev/null | head -1 | awk '{print $2}')
                    fi

                    echo "${backend:-$vhost}|${domain}|${server:-N/A}" >> "$TMP_SSLLIST"
                done

                json_init
                json_add_array "backends"

                if [ -s "$TMP_SSLLIST" ]; then
                    while IFS='|' read service domain server; do
                        [ -z "$service" ] && continue
                        json_add_object ""
                            json_add_string "service" "$service"
                            json_add_string "domain" "$domain"
                            json_add_string "backend" "$server"
                        json_close_object
                    done < "$TMP_SSLLIST"
                fi
                rm -f "$TMP_SSLLIST"

                json_close_array
                json_dump
                ;;

            get_config)
                json_init
                json_add_array "known_services"

                config_load "secubox-exposure"

                get_known() {
                    local section="$1"
                    local default_port config_path category

                    config_get default_port "$section" default_port
                    config_get config_path "$section" config_path
                    config_get category "$section" category "other"

                    actual_port=""
                    if [ -n "$config_path" ]; then
                        actual_port=$(uci -q get "$config_path" 2>/dev/null)
                    fi
                    [ -z "$actual_port" ] && actual_port="$default_port"

                    json_add_object ""
                        json_add_string "id" "$section"
                        json_add_int "default_port" "${default_port:-0}"
                        json_add_int "actual_port" "${actual_port:-0}"
                        json_add_string "config_path" "$config_path"
                        json_add_string "category" "$category"
                    json_close_object
                }
                config_foreach get_known known

                json_close_array
                json_dump
                ;;

            conflicts)
                json_init
                json_add_array "conflicts"
                json_close_array
                json_dump
                ;;

            fix_port)
                read -r input
                service=$(echo "$input" | jsonfilter -e '@.service')
                port=$(echo "$input" | jsonfilter -e '@.port')

                if [ -z "$service" ]; then
                    json_init
                    json_add_boolean "success" 0
                    json_add_string "error" "Service name required"
                    json_dump
                    exit 0
                fi

                result=$(/usr/sbin/secubox-exposure fix-port "$service" "$port" 2>&1)
                json_init
                if [ $? -eq 0 ]; then
                    json_add_boolean "success" 1
                    json_add_string "message" "$result"
                else
                    json_add_boolean "success" 0
                    json_add_string "error" "$result"
                fi
                json_dump
                ;;

            tor_add)
                read -r input
                service=$(echo "$input" | jsonfilter -e '@.service')
                local_port=$(echo "$input" | jsonfilter -e '@.local_port')
                onion_port=$(echo "$input" | jsonfilter -e '@.onion_port')

                if [ -z "$service" ]; then
                    json_init
                    json_add_boolean "success" 0
                    json_add_string "error" "Service name required"
                    json_dump
                    exit 0
                fi

                result=$(/usr/sbin/secubox-exposure tor add "$service" "$local_port" "$onion_port" 2>&1)
                json_init
                if echo "$result" | grep -q "Hidden service created"; then
                    onion=$(echo "$result" | grep "Onion:" | awk '{print $2}')
                    json_add_boolean "success" 1
                    json_add_string "onion" "$onion"
                    json_add_string "message" "Hidden service created"
                else
                    json_add_boolean "success" 0
                    json_add_string "error" "$result"
                fi
                json_dump
                ;;

            tor_remove)
                read -r input
                service=$(echo "$input" | jsonfilter -e '@.service')

                if [ -z "$service" ]; then
                    json_init
                    json_add_boolean "success" 0
                    json_add_string "error" "Service name required"
                    json_dump
                    exit 0
                fi

                result=$(/usr/sbin/secubox-exposure tor remove "$service" 2>&1)
                json_init
                if echo "$result" | grep -q "removed"; then
                    json_add_boolean "success" 1
                    json_add_string "message" "Hidden service removed"
                else
                    json_add_boolean "success" 0
                    json_add_string "error" "$result"
                fi
                json_dump
                ;;

            ssl_add)
                read -r input
                service=$(echo "$input" | jsonfilter -e '@.service')
                domain=$(echo "$input" | jsonfilter -e '@.domain')
                local_port=$(echo "$input" | jsonfilter -e '@.local_port')

                if [ -z "$service" ] || [ -z "$domain" ]; then
                    json_init
                    json_add_boolean "success" 0
                    json_add_string "error" "Service and domain required"
                    json_dump
                    exit 0
                fi

                result=$(/usr/sbin/secubox-exposure ssl add "$service" "$domain" "$local_port" 2>&1)
                json_init
                if echo "$result" | grep -q "configured"; then
                    json_add_boolean "success" 1
                    json_add_string "message" "SSL backend configured"
                else
                    json_add_boolean "success" 0
                    json_add_string "error" "$result"
                fi
                json_dump
                ;;

            ssl_remove)
                read -r input
                service=$(echo "$input" | jsonfilter -e '@.service')

                if [ -z "$service" ]; then
                    json_init
                    json_add_boolean "success" 0
                    json_add_string "error" "Service name required"
                    json_dump
                    exit 0
                fi

                result=$(/usr/sbin/secubox-exposure ssl remove "$service" 2>&1)
                json_init
                if echo "$result" | grep -q "removed"; then
                    json_add_boolean "success" 1
                    json_add_string "message" "SSL backend removed"
                else
                    json_add_boolean "success" 0
                    json_add_string "error" "$result"
                fi
                json_dump
                ;;

            vhost_list)
                json_init

                # HAProxy vhosts (domain -> backend with resolved port)
                json_add_array "haproxy"
                for vhost in $(uci show haproxy 2>/dev/null | grep "=vhost$" | cut -d'.' -f2 | cut -d'=' -f1); do
                    domain=$(uci -q get "haproxy.${vhost}.domain")
                    backend=$(uci -q get "haproxy.${vhost}.backend")
                    enabled=$(uci -q get "haproxy.${vhost}.enabled")
                    ssl=$(uci -q get "haproxy.${vhost}.ssl")
                    acme=$(uci -q get "haproxy.${vhost}.acme")

                    [ -z "$domain" ] && continue

                    # Check for original_backend (when mitmproxy is intercepting)
                    original_backend=$(uci -q get "haproxy.${vhost}.original_backend")
                    resolve_backend="${original_backend:-$backend}"

                    # Resolve backend port from the target backend
                    backend_port=""
                    if [ -n "$resolve_backend" ]; then
                        # Try inline server option: 'name IP:PORT check'
                        server_line=$(uci -q get "haproxy.${resolve_backend}.server" 2>/dev/null)
                        if [ -n "$server_line" ]; then
                            backend_port=$(echo "$server_line" | awk '{print $2}' | grep -o ':[0-9]*' | tr -d ':')
                        fi
                        # Try server sections referencing this backend
                        if [ -z "$backend_port" ]; then
                            for srv in $(uci show haproxy 2>/dev/null | grep "=server$" | cut -d'.' -f2 | cut -d'=' -f1); do
                                srv_backend=$(uci -q get "haproxy.${srv}.backend")
                                if [ "$srv_backend" = "$resolve_backend" ]; then
                                    backend_port=$(uci -q get "haproxy.${srv}.port")
                                    break
                                fi
                            done
                        fi
                    fi

                    json_add_object ""
                        json_add_string "id" "$vhost"
                        json_add_string "domain" "$domain"
                        json_add_string "backend" "${resolve_backend:-${backend:-}}"
                        json_add_int "backend_port" "${backend_port:-0}"
                        json_add_boolean "ssl" "${ssl:-0}"
                        json_add_boolean "acme" "${acme:-0}"
                        json_add_boolean "enabled" "${enabled:-0}"
                    json_close_object
                done
                json_close_array

                # uhttpd vhosts (non-main instances)
                json_add_array "uhttpd"
                for section in $(uci show uhttpd 2>/dev/null | grep "=uhttpd$" | cut -d'.' -f2 | cut -d'=' -f1); do
                    [ "$section" = "main" ] && continue
                    [ "$section" = "acme" ] && continue

                    listen=$(uci -q get "uhttpd.${section}.listen_http")
                    home=$(uci -q get "uhttpd.${section}.home")
                    [ -z "$listen" ] && continue

                    port=$(echo "$listen" | grep -o '[0-9]*$')

                    # Derive friendly name from section id
                    fname=$(echo "$section" | sed 's/^metablog_site_//' | sed 's/_/ /g')

                    json_add_object ""
                        json_add_string "id" "$section"
                        json_add_int "port" "${port:-0}"
                        json_add_string "name" "$fname"
                        json_add_string "home" "${home:-}"
                    json_close_object
                done
                json_close_array

                json_dump
                ;;

            emancipate)
                read -r input
                service=$(echo "$input" | jsonfilter -e '@.service')
                port=$(echo "$input" | jsonfilter -e '@.port')
                domain=$(echo "$input" | jsonfilter -e '@.domain')
                tor=$(echo "$input" | jsonfilter -e '@.tor')
                dns=$(echo "$input" | jsonfilter -e '@.dns')
                mesh=$(echo "$input" | jsonfilter -e '@.mesh')

                if [ -z "$service" ] || [ -z "$port" ]; then
                    json_init
                    json_add_boolean "success" 0
                    json_add_string "error" "Service and port required"
                    json_dump
                    exit 0
                fi

                flags=""
                [ "$tor" = "true" ] || [ "$tor" = "1" ] && flags="$flags --tor"
                [ "$dns" = "true" ] || [ "$dns" = "1" ] && flags="$flags --dns"
                [ "$mesh" = "true" ] || [ "$mesh" = "1" ] && flags="$flags --mesh"
                [ -z "$flags" ] && flags="--all"

                result=$(/usr/sbin/secubox-exposure emancipate "$service" "$port" "$domain" $flags 2>&1)
                rc=$?

                json_init
                if [ $rc -eq 0 ]; then
                    json_add_boolean "success" 1
                    json_add_string "message" "Service emancipated"
                    json_add_string "output" "$result"
                else
                    json_add_boolean "success" 0
                    json_add_string "error" "$result"
                fi
                json_dump
                ;;

            revoke)
                read -r input
                service=$(echo "$input" | jsonfilter -e '@.service')
                tor=$(echo "$input" | jsonfilter -e '@.tor')
                dns=$(echo "$input" | jsonfilter -e '@.dns')
                mesh=$(echo "$input" | jsonfilter -e '@.mesh')

                if [ -z "$service" ]; then
                    json_init
                    json_add_boolean "success" 0
                    json_add_string "error" "Service name required"
                    json_dump
                    exit 0
                fi

                flags=""
                [ "$tor" = "true" ] || [ "$tor" = "1" ] && flags="$flags --tor"
                [ "$dns" = "true" ] || [ "$dns" = "1" ] && flags="$flags --dns"
                [ "$mesh" = "true" ] || [ "$mesh" = "1" ] && flags="$flags --mesh"
                [ -z "$flags" ] && flags="--all"

                result=$(/usr/sbin/secubox-exposure revoke "$service" $flags 2>&1)
                rc=$?

                json_init
                if [ $rc -eq 0 ]; then
                    json_add_boolean "success" 1
                    json_add_string "message" "Service revoked"
                    json_add_string "output" "$result"
                else
                    json_add_boolean "success" 0
                    json_add_string "error" "$result"
                fi
                json_dump
                ;;

            get_emancipated)
                json_init
                json_add_array "services"

                # Read emancipated services from UCI
                for svc in $(uci show secubox-exposure 2>/dev/null | grep "=service$" | cut -d'.' -f2 | cut -d'=' -f1); do
                    emancipated=$(uci -q get "secubox-exposure.$svc.emancipated")
                    [ "$emancipated" != "1" ] && continue

                    port=$(uci -q get "secubox-exposure.$svc.port")
                    domain=$(uci -q get "secubox-exposure.$svc.domain")
                    tor=$(uci -q get "secubox-exposure.$svc.tor")
                    dns=$(uci -q get "secubox-exposure.$svc.dns")
                    mesh=$(uci -q get "secubox-exposure.$svc.mesh")

                    json_add_object ""
                        json_add_string "name" "$svc"
                        json_add_int "port" "${port:-0}"
                        json_add_string "domain" "${domain:-}"
                        json_add_boolean "tor" "${tor:-0}"
                        json_add_boolean "dns" "${dns:-0}"
                        json_add_boolean "mesh" "${mesh:-0}"
                    json_close_object
                done

                json_close_array
                json_dump
                ;;

            *)
                json_init
                json_add_boolean "error" 1
                json_add_string "message" "Unknown method: $2"
                json_dump
                ;;
        esac
        ;;
esac
