#!/usr/bin/env bash
set -euo pipefail
# POS: network scan — Parallel ping sweep of CIDR

usage() {
    cat <<EOF
Usage: pos network scan <cidr> [--full] [--retries N]

Two-phase network scan using nmap.

Phase 1: Fast host discovery (finds alive hosts)
Phase 2: Full metadata scan on alive hosts only (--full only)

Options:
  --full        Detailed scan: OS, ports, services, NSE scripts (slower)
  --retries N   Retries per host in discovery (default: 1)

Examples:
  pos network scan 192.168.1.0/24
  pos network scan 10.0.0.0/28 --full
  pos network scan 172.1.1.104
  pos network scan 192.168.1.0/24 --retries 3
EOF
    exit 0
}

# Deps guard before -h|--help (help also errors without nmap).
if ! command -v nmap &>/dev/null; then
    echo "ERROR: nmap is required. Install with: sudo apt install nmap"
    exit 1
fi

case "${1:-}" in
    -h|--help|"") usage ;;
esac

net=""
full=0
retries=1

while [[ $# -gt 0 ]]; do
    case "$1" in
        --full)    full=1; shift ;;
        --retries)
            if [[ -z "${2:-}" || "$2" == --* ]]; then
                echo "ERROR: --retries requires a number"
                exit 1
            fi
            retries="$2"; shift 2 ;;
        *)         net="$1"; shift ;;
    esac
done

if [[ -z "$net" ]]; then
    echo "ERROR: Missing CIDR (e.g. 192.168.1.0/24)"
    exit 1
fi

# ── Input validation ───────────────────────────────────────────
# Bare IP → /32
if [[ "$net" =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then
    net="${net}/32"
# Valid CIDR
elif [[ "$net" =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}/[0-9]{1,2}$ ]]; then
    : # ok
else
    echo "ERROR: Invalid target '$net'"
    echo "Expected: IP (172.1.1.104) or CIDR (192.168.1.0/24)"
    exit 1
fi

# ── Estimate host count ────────────────────────────────────────
cidr_bits="${net##*/}"
if [[ "$cidr_bits" -ge 24 ]]; then
    host_estimate=$(( 1 << (32 - cidr_bits) ))
elif [[ "$cidr_bits" -ge 16 ]]; then
    host_estimate="$(( 1 << (32 - cidr_bits) ))+"
else
    host_estimate="many"
fi

is_root=0
[[ $EUID -eq 0 ]] && is_root=1

can_sudo=0
if [[ "$is_root" -eq 1 ]]; then
    can_sudo=1
elif sudo -n nmap -V &>/dev/null; then
    can_sudo=1
elif [[ "$full" -eq 1 && -t 0 ]]; then
    can_sudo=1
fi

tmpfile=$(mktemp /tmp/scan-XXXXXX.txt)
trap 'rm -f "$tmpfile"' EXIT

# ── Phase 1: Fast host discovery ───────────────────────────────
nmap_cmd="nmap"
[[ "$can_sudo" -eq 1 ]] && nmap_cmd="sudo nmap"

echo "Discovering hosts in $net (~$host_estimate) ..."
echo

$nmap_cmd -sn -T5 -n \
    --min-rate 1000 \
    --min-parallelism 1024 \
    --min-hostgroup 1024 \
    --max-retries "$retries" \
    --host-timeout 5s \
    "$net" 2>/dev/null | awk '/^Nmap scan report for/ {
    ip = $(NF);
    gsub(/[()]/, "", ip);
    print ip;
}' > "$tmpfile"

host_count=$(wc -l < "$tmpfile")

if [[ "$host_count" -eq 0 ]]; then
    echo "No hosts found."
    exit 0
fi

echo "Found $host_count host(s)."
echo

if [[ "$full" -eq 0 ]]; then
    cat "$tmpfile"
    echo
    echo "Done."
    exit 0
fi

# ── Phase 2: Full metadata scan ────────────────────────────────
echo "Scanning $host_count host(s) (full) ..."
echo

nmap_args="-sV --version-intensity 9 -sC -T4 -n"
nmap_args="$nmap_args --min-parallelism 256 --max-parallelism 512"
nmap_args="$nmap_args --min-hostgroup 256 --max-hostgroup 512"
nmap_args="$nmap_args --min-rate 1000 --max-retries 1"
nmap_args="$nmap_args --host-timeout 60s --max-rtt-timeout 200ms"
nmap_args="$nmap_args --script ssh-hostkey,ssl-cert,http-title,http-server-header,smb-os-discovery,nbstat,rpcinfo"
[[ "$can_sudo" -eq 1 ]] && nmap_args="$nmap_args -O --osscan-guess"

# shellcheck disable=SC2086
$nmap_cmd $nmap_args -iL "$tmpfile" 2>/dev/null | awk '
BEGIN { ip_count = 0; has_os = 0 }

/^Nmap scan report for/ {
    ip = $(NF);
    gsub(/[()]/, "", ip);
    hostname = "";
    if ($(NF) ~ /^\(/) {
        hostname = $(NF-1);
    } else if (NF > 4) {
        if (ip != $(NF-1) && $(NF-1) !~ /^(for|[0-9])/) {
            hostname = $(NF-1);
        }
    }
    if (ip_count > 0) printf "\n";
    ip_count++;
    has_os = 0;
    if (hostname != "" && hostname != ip)
        printf "\033[1;36m%s\033[0m  (%s)\n", ip, hostname;
    else
        printf "\033[1;36m%s\033[0m\n", ip;
}

/^MAC Address/ {
    vendor = $0;
    sub(/.*\(/, "", vendor);
    sub(/\).*/, "", vendor);
    printf "  \033[2m%-10s\033[0m %s  %s\n", "MAC:", $3, vendor;
}

/^Aggressive OS guesses:/ {
    has_os = 1;
    line = $0;
    sub(/.*guesses: /, "", line);
    gsub(/\s*\(.*/, "", line);
    printf "  \033[2m%-10s\033[0m %s\n", "OS:", line;
}

/^OS details:/ {
    has_os = 1;
    sub(/.*OS details: /, "");
    printf "  \033[2m%-10s\033[0m %s\n", "OS:", $0;
}

/^Running:/ {
    sub(/.*Running: /, "");
    printf "  \033[2m%-10s\033[0m %s\n", "OS:", $0;
}

/^Service Info:/ {
    line = $0;
    sub(/.*Service Info:/, "", line);
    gsub(/^ +/, "", line);
    if (has_os == 0)
        printf "  \033[2m%-10s\033[0m %s\n", "Info:", line;
}

/^\| ssh-hostkey:/ {
    line = $0;
    sub(/.*ssh-hostkey:/, "", line);
    gsub(/^ +/, "", line);
    if (line ~ /SHA256/) {
        match(line, /SHA256:[A-Za-z0-9+\/=]+/);
        key = substr(line, RSTART, RLENGTH);
        printf "  \033[2m%-10s\033[0m %s\n", "SSH:", key;
    }
}

/^\| http-title:/ {
    line = $0;
    sub(/.*http-title:/, "", line);
    gsub(/^ +/, "", line);
    gsub(/\s*\[.*$/, "", line);
    if (line != "" && line !~ /^No/)
        printf "  \033[2m%-10s\033[0m %s\n", "HTTP Title:", line;
}

/^\| http-server-header:/ {
    line = $0;
    sub(/.*http-server-header:/, "", line);
    gsub(/^ +/, "", line);
    printf "  \033[2m%-10s\033[0m %s\n", "HTTP Server:", line;
}

/^\|_?NetBIOS name:/ {
    line = $0;
    sub(/.*NetBIOS name:/, "", line);
    sub(/,.*$/, "", line);
    gsub(/^ +/, "", line);
    printf "  \033[2m%-10s\033[0m %s\n", "NetBIOS:", line;
}

/^\|_?SMB OS:/ {
    line = $0;
    sub(/.*SMB OS:/, "", line);
    gsub(/^ +/, "", line);
    printf "  \033[2m%-10s\033[0m %s\n", "SMB:", line;
}

/^\|_?Domain:/ {
    line = $0;
    sub(/.*Domain:/, "", line);
    gsub(/^ +/, "", line);
    if (line != "" && line !~ /^WORKGROUP/)
        printf "  \033[2m%-10s\033[0m %s\n", "Domain:", line;
}

/^\|   [0-9]+\/tcp/ {
    line = $0;
    gsub(/^ *\| */, "", line);
    printf "  \033[2m%-10s\033[0m %s\n", "RPC:", line;
}

/^[0-9]+\/tcp[[:space:]]+open/ {
    port = $1; service = $3;
    version = "";
    for (i = 4; i <= NF; i++) version = version " " $i;
    gsub(/^ +/, "", version);
    if (version != "")
        printf "  \033[2m%-10s\033[0m %s — %s\n", port, service, version;
    else
        printf "  \033[2m%-10s\033[0m %s\n", port, service;
}
'

echo
echo "Done."
