diff --git a/scripts/discover-fortigate.sh b/scripts/discover-fortigate.sh index b8e71df..4dac52c 100755 --- a/scripts/discover-fortigate.sh +++ b/scripts/discover-fortigate.sh @@ -30,7 +30,15 @@ if [ -z "${1:-}" ]; then exit 2 fi -HOST="$1" +ARG="$1" +# Accept either "host" or "user@host" — don't double-prefix the user. +if [[ "$ARG" == *@* ]]; then + TARGET="$ARG" + HOST="${ARG##*@}" +else + TARGET="${FORTIGATE_SSH_USER:-admin}@${ARG}" + HOST="$ARG" +fi USER="${FORTIGATE_SSH_USER:-admin}" # FortiGate CLI command. `execute dhcp lease-list all` dumps every vdom. @@ -41,7 +49,7 @@ USER="${FORTIGATE_SSH_USER:-admin}" raw="" for cmd in 'execute dhcp lease-list all' 'execute dhcp lease-list'; do - raw=$(ssh -o StrictHostKeyChecking=accept-new "${USER}@${HOST}" "$cmd" || true) + raw=$(ssh -o StrictHostKeyChecking=accept-new "$TARGET" "$cmd" || true) if [ -n "$raw" ] && ! grep -qiE 'unknown action|parse error|command fail' <<<"$raw"; then break fi @@ -49,38 +57,44 @@ done if [ -z "$raw" ]; then echo "error: no lease data from $HOST" >&2 - echo " — try running 'ssh ${USER}@${HOST}' interactively and running" >&2 - echo " 'execute dhcp lease-list ?' to discover the right subcommand shape" >&2 + echo " — try: ssh $TARGET then run: execute dhcp lease-list" >&2 exit 1 fi # Parse. # -# FortiGate lease output looks approximately like: +# Real FortiOS output looks like: # -# VDOM: root -# Interface: internal -# IP MAC Hostname Lease-Expiry -# 10.250.50.100 00:1a:2b:3c:4d:5e laptop-01 Mon Apr 21 14:30:00 2026 -# 10.250.50.101 aa:bb:cc:dd:ee:ff printer Mon Apr 21 15:00:00 2026 +# ana-gw # internal +# IP MAC-Address Hostname VCI SSID AP SERVER-ID Expiry +# 10.250.0.105 90:5a:08:98:e2:29 ana-ml2 4 Sun Apr 26 17:02:23 2026 +# mgmt +# IP MAC-Address Hostname … +# 10.250.250.50 7c:c2:55:60:fe:8a ANA-ML2-BMC udhcp 1.32.1 6 Tue Apr 28 18:56:08 2026 # -# Format varies by FortiOS version. The awk below is defensive: -# - only accepts lines where field 1 matches IPv4 -# - assumes MAC is field 2, hostname is field 3-4 (may contain whitespace), -# which is the most common layout. -awk -v src="fortigate:${HOST}" -v vdom="" ' - /^VDOM:/ { vdom=$2; next } - /^Interface:/ { next } - /^IP[[:space:]]/ { next } - /^[[:space:]]*$/ { next } - $1 ~ /^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$/ { - ip = $1 - mac = $2 - # Hostname may be missing (shows "-" or empty) or contain spaces; take - # the rest of the line minus the trailing date fields. - host = "" - for (i=3; i<=NF-6; i++) host = host (host=="" ? "" : " ") $i - if (host == "" || host == "-") host = "-" - printf "%s\t%s\t%s\t%s\t%s\n", ip, mac, host, (vdom==""?"-":vdom), src +# Interfaces are flush-left (no indent); lease rows + column headers are +# indented. First line embeds the shell prompt ("ana-gw # ") followed by +# the first interface name. Hostnames don't contain spaces in practice. + +awk -v src="fortigate:${HOST}" ' + # Flush-left non-blank line = interface name (sometimes preceded by prompt) + /^[^[:space:]]/ && NF >= 1 { + # " # " form — take the last field + if (NF >= 3 && $2 == "#") { iface = $NF; next } + # plain "" form + if (NF == 1) { iface = $1; next } + # prompt-with-trailing-% or similar closing lines — skip + next + } + # Column header row + /^[[:space:]]+IP[[:space:]]/ { next } + # Blank / whitespace-only + /^[[:space:]]*$/ { next } + # Lease row + $1 ~ /^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$/ && $2 ~ /^[0-9a-fA-F]{2}:/ { + ip = $1 + mac = $2 + host = (NF >= 3 && $3 !~ /^[0-9]+$/) ? $3 : "-" + printf "%s\t%s\t%s\t%s\t%s\n", ip, mac, host, (iface==""?"-":iface), src } ' <<<"$raw"