#!/usr/bin/env bash
# =============================================================================
# onx-system-listen-ports — Enumerate listening TCP/UDP sockets
#
# Input (stdin JSON):  {}
# Output (stdout JSON):
#   {
#     "ports": [
#       { "protocol":"tcp", "port":22, "address":"0.0.0.0",
#         "service":"sshd", "state":"LISTEN", "pid":1234 }
#     ],
#     "count": 12
#   }
#
# Deployed to: /usr/local/onoxsoft/bin/onx-system-listen-ports
# =============================================================================

set -euo pipefail

SCRIPT_DIR="$(dirname "$(readlink -f "$0")")"
# shellcheck source=_lib/common.sh
source "${SCRIPT_DIR}/_lib/common.sh"

require_root
onx_json_input

if [[ "${MOCK_MODE}" == "1" ]] || ! command -v ss >/dev/null 2>&1; then
    jq -nc '{
      ports: [
        {protocol:"tcp", port:22,   address:"0.0.0.0", service:"sshd",      state:"LISTEN", pid:1011},
        {protocol:"tcp", port:25,   address:"0.0.0.0", service:"master",    state:"LISTEN", pid:1233},
        {protocol:"tcp", port:80,   address:"0.0.0.0", service:"httpd",     state:"LISTEN", pid:2154},
        {protocol:"tcp", port:443,  address:"0.0.0.0", service:"httpd",     state:"LISTEN", pid:2154},
        {protocol:"tcp", port:993,  address:"0.0.0.0", service:"dovecot",   state:"LISTEN", pid:3017},
        {protocol:"tcp", port:3306, address:"127.0.0.1", service:"mariadbd",state:"LISTEN", pid:1881},
        {protocol:"udp", port:53,   address:"0.0.0.0", service:"pdns_server", state:"-",   pid:4012}
      ],
      count: 7
    }'
    exit 0
fi

# Use ss -tlnpu for TCP+UDP listening sockets w/ PID
RAW_TCP=$(ss -tlnp 2>/dev/null | tail -n +2 || echo "")
RAW_UDP=$(ss -ulnp 2>/dev/null | tail -n +2 || echo "")

parse_ss_line() {
    local proto="$1" line="$2"
    # ss output columns: State Recv-Q Send-Q Local-Address:Port Peer-Address:Port Process
    local state laddr proc
    state=$(awk '{print $1}' <<<"${line}")
    laddr=$(awk '{print $4}' <<<"${line}")
    proc=$(grep -oE '"[^"]+"' <<<"${line}" | head -1 | tr -d '"' || echo "-")
    local pid
    pid=$(grep -oE 'pid=[0-9]+' <<<"${line}" | head -1 | cut -d= -f2 || echo "0")
    local port addr
    port="${laddr##*:}"
    addr="${laddr%:*}"
    [[ -z "${port}" || ! "${port}" =~ ^[0-9]+$ ]] && return 0
    jq -nc \
        --arg p "${proto}" \
        --argjson pn "${port}" \
        --arg a "${addr}" \
        --arg s "${proc:-unknown}" \
        --arg st "${state}" \
        --argjson pid "${pid:-0}" \
        '{protocol:$p, port:$pn, address:$a, service:$s, state:$st, pid:$pid}'
}

PORTS_JSON="["
SEP=""
while IFS= read -r line; do
    [[ -z "${line}" ]] && continue
    j=$(parse_ss_line "tcp" "${line}")
    [[ -n "${j}" ]] && PORTS_JSON+="${SEP}${j}" && SEP=","
done <<<"${RAW_TCP}"

while IFS= read -r line; do
    [[ -z "${line}" ]] && continue
    j=$(parse_ss_line "udp" "${line}")
    [[ -n "${j}" ]] && PORTS_JSON+="${SEP}${j}" && SEP=","
done <<<"${RAW_UDP}"
PORTS_JSON+="]"

COUNT=$(printf '%s' "${PORTS_JSON}" | jq 'length')

jq -nc --argjson ports "${PORTS_JSON}" --argjson n "${COUNT}" \
    '{ports:$ports, count:$n}'
