#!/bin/bash
# Readiness of the agent: agent.ping over 127.0.0.1 must answer "1" -- the
# question the Zabbix server asks, not just "port open".
#
# The way of asking follows TLSAccept= of the agent (main config plus its
# Include= files, last value wins):
#   * "unencrypted" allowed (the default): plain ZBXD request over bash
#     /dev/tcp. Measured on rt-1.tick1 (agent 7.0.22): only requests WITH the
#     ZBXD header are answered; a plain-text key is silently dropped.
#   * only "psk": the agent refuses unencrypted connections ("unencrypted
#     connections are not allowed", one log line per attempt) and the unit
#     would never become ready (0.1.5, PSK stage B, 23.09.2026). Then
#     zabbix_get with --tls-connect psk if it is installed, otherwise
#     data/psk-ping (python3 ssl with a PSK callback). The key is read from
#     TLSPSKFile by the client itself; it never appears on a command line or
#     in any output. This probe runs as root, so a 0400 key file owned by
#     zabbix is readable.
#   * anything else (e.g. only "cert"): not ready, with the reason.
# python only runs in the psk case; the check interval is unchanged.
#
# 127.0.0.1 has to be in Server= (Debian default and Puppet set it);
# otherwise the agent closes the connection without an answer, and this
# probe says so.
set -u
conf=/etc/zabbix/zabbix_agentd.conf

# Config files in the agent's order: the main file, then every Include=
# (a file, a directory, or a glob).
conf_files() {
  echo "$conf"
  local inc f
  while IFS= read -r inc; do
    if [ -d "$inc" ]; then
      for f in "$inc"/*; do [ -f "$f" ] && echo "$f"; done
    else
      for f in $inc; do [ -f "$f" ] && echo "$f"; done
    fi
  done < <(sed -n 's/^[[:space:]]*Include[[:space:]]*=[[:space:]]*//p' "$conf" 2>/dev/null)
}

# Last value of a parameter over all config files.
conf_get() {
  local k="$1"
  conf_files | while IFS= read -r f; do
    sed -n "s/^[[:space:]]*$k[[:space:]]*=[[:space:]]*//p" "$f" 2>/dev/null
  done | tail -n 1 | sed 's/[[:space:]]*$//'
}

port=$(conf_get ListenPort); port=${port:-10050}
accept=$(conf_get TLSAccept); accept=${accept:-unencrypted}

case ",${accept// /}," in
  *,unencrypted,*)
    # bash's own connect error goes to /dev/null: check-report logs the
    # FIRST line of the output, and that should be ours.
    { exec 3<>"/dev/tcp/127.0.0.1/$port"; } 2>/dev/null || { echo "port $port not reachable"; exit 1; }
    # "agent.ping" has 10 characters: length 10 as 8 bytes little endian.
    printf 'ZBXD\001\012\000\000\000\000\000\000\000agent.ping' >&3
    r=$(timeout 3 cat <&3 | tail -c 1)
    exec 3<&-
    [ "$r" = 1 ] && exit 0
    echo "agent.ping answers '${r}' instead of 1 (127.0.0.1 in Server=?)"
    exit 1
    ;;
  *,psk,*)
    identity=$(conf_get TLSPSKIdentity)
    keyfile=$(conf_get TLSPSKFile)
    if [ -z "$identity" ] || [ -z "$keyfile" ]; then
      echo "TLSAccept=$accept but TLSPSKIdentity or TLSPSKFile is not set"
      exit 1
    fi
    if [ ! -r "$keyfile" ]; then
      echo "TLSAccept=$accept but the PSK file $keyfile is not readable"
      exit 1
    fi
    if command -v zabbix_get >/dev/null 2>&1; then
      out=$(timeout 5 zabbix_get -s 127.0.0.1 -p "$port" -k agent.ping \
              --tls-connect psk --tls-psk-identity "$identity" --tls-psk-file "$keyfile" 2>&1)
      [ "$out" = 1 ] && exit 0
      # the identity is not the secret, but keep it out of the log anyway
      echo "agent.ping via zabbix_get (psk) failed: $(printf '%s\n' "$out" | head -n 1 | sed "s|$identity|<identity>|g")"
      exit 1
    fi
    if command -v python3 >/dev/null 2>&1; then
      PSK_IDENTITY="$identity" PSK_FILE="$keyfile" PSK_PORT="$port" exec timeout 5 python3 ./data/psk-ping
    fi
    echo "not ready (no PSK client: zabbix_get or python3 >= 3.13)"
    exit 1
    ;;
  *)
    echo "TLSAccept=$accept: this probe supports unencrypted and psk"
    exit 1
    ;;
esac
