scripts/discover-fortigate: handle real FortiOS output + user@host arg
First live run against ana-fw.phasefinal.com surfaced two bugs: 1. Script double-prefixed user@ when the arg already contained it (e.g. `admin@10.250.250.1` became `admin@admin@10.250.250.1` → auth prompt loop). Accept either "host" or "user@host" and only prepend the default user if missing. 2. Parser assumed the wrong output format. Real FortiOS (tested on 7.x) emits: <prompt> # <iface> IP MAC-Address Hostname VCI SSID AP SERVER-ID Expiry 10.x.x.x ... <next-iface> IP MAC-Address ... - Interface names are flush-left (no "Interface:" prefix) - First line has the shell prompt embedded before the iface - Hostnames don't contain spaces in practice - 8 columns, not 4; VCI can contain "udhcp 1.32.1" etc. Rewrote awk to detect interfaces via indentation (flush-left = iface, indented = header or lease) and extract IP/MAC/Hostname from the first three tokens of each lease row. Verified against a captured sample; emits clean TSV.
This commit is contained in:
@@ -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 {
|
||||
# "<prompt> # <iface>" form — take the last field
|
||||
if (NF >= 3 && $2 == "#") { iface = $NF; next }
|
||||
# plain "<iface>" 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"
|
||||
|
||||
Reference in New Issue
Block a user