#!/bin/bash
################################################################################
# Script Name: login-attempt-exporter.sh
# Version: 1.1
# Description: Prometheus exporter for SSH/PAM login attempts providing
#              comprehensive metrics for monitoring authentication activity,
#              failed logins, brute force detection, and threat analysis
#
# Author: Phil Connor
# Contact: contact@mylinux.work
# Website: https://mylinux.work
# License: MIT
#
# Prerequisites:
#   - Access to auth logs (/var/log/auth.log, /var/log/secure, or journalctl)
#   - netcat (nc) for HTTP mode
#   - Standard Unix tools (grep, awk, sort, uniq)
#
# Performance:
#   Log lines are cached per period — the log is parsed once per period
#   (1h, 24h, 7d), not once per metric. Timestamps are parsed natively
#   in awk using mktime() with no subprocess spawning per line.
#   Typical run time: a few seconds even on large auth logs.
#
# Usage:
#   # Output to stdout
#   ./login-attempt-exporter.sh
#
#   # HTTP server mode
#   ./login-attempt-exporter.sh --http -p 9198
#
#   # Textfile collector mode
#   ./login-attempt-exporter.sh --textfile
#
# Metrics Exported:
#   Core Status:
#     - login_attempt_up - Exporter status (1=up, 0=down)
#     - login_attempt_exporter_info{version,log_source} - Exporter info
#
#   Attempt Counters:
#     - login_attempt_failed_total{period} - Failed attempts per period
#     - login_attempt_success_total{period} - Successful logins per period
#     - login_attempt_invalid_user_total{period} - Invalid user attempts
#     - login_attempt_preauth_disconnect_total{period} - Pre-auth disconnects
#
#   Authentication Methods:
#     - login_attempt_auth_method_count{method,period} - Attempts by method
#
#   Unique Counts:
#     - login_attempt_unique_source_ips{status,period} - Unique IPs
#     - login_attempt_unique_users{status,period} - Unique usernames
#
#   Top Attackers:
#     - login_attempt_top_failed_ip_count{ip} - Top source IPs (24h)
#     - login_attempt_top_failed_user_count{user} - Top targeted users (24h)
#     - login_attempt_top_success_user_count{user} - Top successful users (24h)
#
#   Threat Detection:
#     - login_attempt_brute_force_score{ip} - Brute force heuristic score
#     - login_attempt_root_attempts{period} - Root login attempts
#
#   Rates:
#     - login_attempt_failed_per_hour - Average failed/hour (24h)
#     - login_attempt_success_rate - Success ratio (24h)
#
#   Log Health:
#     - login_attempt_log_size_bytes - Auth log file size
#     - login_attempt_log_age_seconds - Seconds since last modification
#     - login_attempt_exporter_duration_seconds - Script execution time
#     - login_attempt_exporter_last_run_timestamp - Last run unix timestamp
#
# Configuration:
#   Default HTTP port: 9198
#   Textfile directory: /var/lib/node_exporter
#   Log sources: /var/log/auth.log, /var/log/secure, journalctl
#
################################################################################

# ============================================================================
# CONFIGURATION VARIABLES
# ============================================================================

TEXTFILE_DIR="/var/lib/node_exporter"
OUTPUT_FILE=""
HTTP_MODE=false
HTTP_PORT=9198
AUTH_LOG=""
LOG_SOURCE=""
CACHE_DIR=""

# ============================================================================
# HELPER FUNCTIONS
# ============================================================================

show_usage() {
    cat <<EOF
Usage: $0 [OPTIONS]

Export SSH/PAM login attempt statistics as Prometheus metrics (v1.0).

MODES:
    --textfile      Write to node_exporter textfile collector
    --http          Run HTTP server on port $HTTP_PORT

OPTIONS:
    -p, --port      HTTP port (default: 9198)
    -o, --output    Output file path

EXAMPLES:
    $0 --textfile                    # Write to textfile collector
    $0 --http --port 9198            # Run HTTP server
    $0 -o /tmp/login_attempts.prom   # Write to custom file

METRICS:
    - Failed/successful login attempts (1h, 24h, 7d)
    - Invalid user attempts and pre-auth disconnects
    - Authentication method breakdown (password/publickey)
    - Top attacking IPs and targeted usernames
    - Brute force detection heuristics
    - Root login attempt tracking

EOF
    exit 0
}

parse_args() {
    while [[ $# -gt 0 ]]; do
        case $1 in
            -h|--help) show_usage ;;
            --textfile) OUTPUT_FILE="$TEXTFILE_DIR/login_attempts.prom"; shift ;;
            --http) HTTP_MODE=true; shift ;;
            -p|--port) HTTP_PORT="$2"; shift 2 ;;
            -o|--output) OUTPUT_FILE="$2"; shift 2 ;;
            *) echo "Unknown option: $1" >&2; exit 1 ;;
        esac
    done
}

# Detect the correct auth log source
# Sets AUTH_LOG and LOG_SOURCE global variables
# Returns: 0 if found, 1 if no log source available
detect_auth_log() {
    if [ -r "/var/log/auth.log" ]; then
        AUTH_LOG="/var/log/auth.log"
        LOG_SOURCE="auth.log"
        return 0
    elif [ -r "/var/log/secure" ]; then
        AUTH_LOG="/var/log/secure"
        LOG_SOURCE="secure"
        return 0
    elif command -v journalctl >/dev/null 2>&1 && journalctl -u sshd -n 1 >/dev/null 2>&1; then
        AUTH_LOG=""
        LOG_SOURCE="journalctl"
        return 0
    fi
    return 1
}

# Set up a temp directory for caching filtered log lines per period
# Called once at the start of generate_metrics
setup_cache() {
    CACHE_DIR=$(mktemp -d /tmp/login-attempt-exporter.XXXXXX)
}

# Clean up cached log data
cleanup_cache() {
    [ -n "$CACHE_DIR" ] && rm -rf "$CACHE_DIR"
    CACHE_DIR=""
}

# Get log lines filtered by time period (cached per period)
# The log is read once per period and cached to a temp file.
# Subsequent calls for the same period return the cached result.
# Args: $1 - period ("1 hour ago", "24 hours ago", "7 days ago")
# Returns: Filtered log lines on stdout
get_log_lines() {
    local period="$1"
    local cache_key="${period// /_}"
    local cache_file="$CACHE_DIR/$cache_key"

    # Return cached result if available
    if [ -f "$cache_file" ]; then
        cat "$cache_file"
        return
    fi

    if [ "$LOG_SOURCE" = "journalctl" ]; then
        journalctl -u sshd --since "$period" --no-pager 2>/dev/null > "$cache_file"
    elif [ -n "$AUTH_LOG" ] && [ -r "$AUTH_LOG" ]; then
        local cutoff_timestamp
        cutoff_timestamp=$(date -d "$period" +%s 2>/dev/null || echo 0)

        # Use awk mktime() for native timestamp parsing — no subprocess per line
        # Supports both traditional syslog (Apr 20 10:08:23) and
        # ISO 8601 / RFC 3339 (2026-04-20T10:08:23.066974+02:00) formats
        awk -v cutoff="$cutoff_timestamp" '
        BEGIN {
            m["Jan"]=1;  m["Feb"]=2;  m["Mar"]=3;  m["Apr"]=4
            m["May"]=5;  m["Jun"]=6;  m["Jul"]=7;  m["Aug"]=8
            m["Sep"]=9;  m["Oct"]=10; m["Nov"]=11; m["Dec"]=12
            "date +%Y" | getline year; close("date +%Y")
        }
        {
            if ($1 in m) {
                # Traditional syslog: Apr 20 10:08:23
                split($3, t, ":")
                ts = mktime(year " " m[$1] " " $2+0 " " t[1] " " t[2] " " t[3])
                if (ts >= cutoff) print
            } else if (match($1, /^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}/)) {
                # ISO 8601: 2026-04-20T10:08:23.066974+02:00
                split($1, dt, "[-T:.+]")
                ts = mktime(dt[1] " " dt[2]+0 " " dt[3]+0 " " dt[4]+0 " " dt[5]+0 " " dt[6]+0)
                if (ts >= cutoff) print
            }
        }' "$AUTH_LOG" > "$cache_file" 2>/dev/null
    fi

    cat "$cache_file"
}

# Count failed password attempts in a time period
# Args: $1 - period (e.g., "1 hour ago")
# Returns: Number of failed attempts
count_failed_attempts() {
    local period="$1"
    local count
    count=$(get_log_lines "$period" | grep -c "sshd.*Failed password" 2>/dev/null)
    echo "${count:-0}"
}

# Count successful logins (password + publickey) in a time period
# Args: $1 - period (e.g., "1 hour ago")
# Returns: Number of successful logins
count_successful_logins() {
    local period="$1"
    local count
    count=$(get_log_lines "$period" | grep -c "sshd.*Accepted" 2>/dev/null)
    echo "${count:-0}"
}

# Count invalid user attempts in a time period
# Args: $1 - period (e.g., "1 hour ago")
# Returns: Number of invalid user attempts
count_invalid_users() {
    local period="$1"
    local count
    count=$(get_log_lines "$period" | grep -c "sshd.*Invalid user" 2>/dev/null)
    echo "${count:-0}"
}

# Count pre-auth disconnects in a time period
# Args: $1 - period (e.g., "1 hour ago")
# Returns: Number of pre-auth disconnect events
count_preauth_disconnects() {
    local period="$1"
    local count
    count=$(get_log_lines "$period" | grep -cE "sshd.*(Connection closed by.*\[preauth\]|Disconnected from.*\[preauth\])" 2>/dev/null)
    echo "${count:-0}"
}

# Get authentication method counts for a time period
# Args: $1 - period (e.g., "24 hours ago")
# Returns: Lines of "method count" format (password, publickey, keyboard-interactive)
get_auth_method_counts() {
    local period="$1"
    local lines
    lines=$(get_log_lines "$period")

    local password_count publickey_count kbdint_count
    password_count=$(echo "$lines" | grep -c "sshd.*Accepted password" 2>/dev/null)
    publickey_count=$(echo "$lines" | grep -c "sshd.*Accepted publickey" 2>/dev/null)
    kbdint_count=$(echo "$lines" | grep -c "sshd.*Accepted keyboard-interactive" 2>/dev/null)

    echo "password ${password_count:-0}"
    echo "publickey ${publickey_count:-0}"
    echo "keyboard-interactive ${kbdint_count:-0}"
}

# Get top source IPs from failed attempts (24h)
# Args: $1 - limit (default: 10)
# Returns: Lines with "count IP" format, sorted by count descending
get_top_failed_ips() {
    local limit="${1:-10}"
    get_log_lines "24 hours ago" | grep "sshd.*Failed password" | \
        grep -oE '[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+' | \
        sort | uniq -c | sort -rn | head -n "$limit"
}

# Get most targeted usernames from failed attempts (24h)
# Args: $1 - limit (default: 10)
# Returns: Lines with "count username" format, sorted by count descending
get_top_failed_users() {
    local limit="${1:-10}"
    get_log_lines "24 hours ago" | grep "sshd.*Failed password" | \
        sed -n 's/.*Failed password for invalid user \([^ ]*\).*/\1/p; s/.*Failed password for \([^ ]*\).*/\1/p' | \
        sort | uniq -c | sort -rn | head -n "$limit"
}

# Get users with most successful logins (24h)
# Args: $1 - limit (default: 10)
# Returns: Lines with "count username" format, sorted by count descending
get_top_successful_users() {
    local limit="${1:-10}"
    get_log_lines "24 hours ago" | grep "sshd.*Accepted" | \
        awk '{for(i=1;i<=NF;i++) if($i=="for") {print $(i+1); break}}' | \
        sort | uniq -c | sort -rn | head -n "$limit"
}

# Count unique source IPs in a time period
# Args: $1 - period, $2 - status (failed/success)
# Returns: Number of unique IPs
get_unique_source_ips() {
    local period="$1"
    local status="$2"
    local pattern count

    if [ "$status" = "failed" ]; then
        pattern="sshd.*Failed password"
    else
        pattern="sshd.*Accepted"
    fi

    count=$(get_log_lines "$period" | grep "$pattern" | \
        grep -oE '[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+' | \
        sort -u | wc -l 2>/dev/null)
    echo "${count:-0}"
}

# Count unique usernames in a time period
# Args: $1 - period, $2 - status (failed/success)
# Returns: Number of unique usernames
get_unique_users() {
    local period="$1"
    local status="$2"
    local count

    if [ "$status" = "failed" ]; then
        count=$(get_log_lines "$period" | grep "sshd.*Failed password" | \
            sed -n 's/.*Failed password for invalid user \([^ ]*\).*/\1/p; s/.*Failed password for \([^ ]*\).*/\1/p' | \
            sort -u | wc -l 2>/dev/null)
    else
        count=$(get_log_lines "$period" | grep "sshd.*Accepted" | \
            awk '{for(i=1;i<=NF;i++) if($i=="for") {print $(i+1); break}}' | \
            sort -u | wc -l 2>/dev/null)
    fi
    echo "${count:-0}"
}

# Detect brute force candidates: IPs with 10+ failed attempts in 1h hitting 3+ unique users
# Returns: Lines of "ip score" where score = attempts × unique_users
get_brute_force_candidates() {
    local lines
    lines=$(get_log_lines "1 hour ago" | grep "sshd.*Failed password")

    [ -z "$lines" ] && return

    # Extract IP and username pairs, then compute per-IP stats
    echo "$lines" | \
        awk '{
            ip = ""; user = ""
            for (i=1; i<=NF; i++) {
                if ($i ~ /^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$/) ip = $i
                if ($i == "for" && $(i+1) == "invalid" && $(i+2) == "user") { user = $(i+3); i+=3 }
                else if ($i == "for") { user = $(i+1) }
            }
            if (ip != "" && user != "") {
                attempts[ip]++
                users[ip, user] = 1
            }
        }
        END {
            for (ip in attempts) {
                unique = 0
                for (key in users) {
                    split(key, parts, SUBSEP)
                    if (parts[1] == ip) unique++
                }
                if (attempts[ip] >= 10 && unique >= 3) {
                    score = attempts[ip] * unique
                    print ip, score
                }
            }
        }'
}

# Count login attempts targeting root
# Args: $1 - period (e.g., "1 hour ago")
# Returns: Number of root login attempts
get_root_login_attempts() {
    local period="$1"
    local count
    count=$(get_log_lines "$period" | grep -c "sshd.*Failed password for root" 2>/dev/null)
    echo "${count:-0}"
}

# Calculate average failed attempts per hour over the last 24h
# Returns: Floating point average
get_hourly_attempt_rate() {
    local total
    total=$(count_failed_attempts "24 hours ago")
    total=${total:-0}

    if [ "$total" -gt 0 ] 2>/dev/null; then
        awk "BEGIN {printf \"%.2f\", $total / 24}" 2>/dev/null || echo "0.00"
    else
        echo "0.00"
    fi
}

# ============================================================================
# METRIC GENERATION
# ============================================================================

# Generate all Prometheus metrics
# Returns: Prometheus text format metrics on stdout
generate_metrics() {
    local script_start
    script_start=$(date +%s)

    # Detect auth log source
    if ! detect_auth_log; then
        cat <<EOF
# HELP login_attempt_up Login attempt exporter status
# TYPE login_attempt_up gauge
login_attempt_up 0
EOF
        return
    fi

    # Set up per-period cache (log is parsed once per period, not per metric)
    setup_cache
    trap cleanup_cache EXIT

    cat <<EOF
# HELP login_attempt_up Login attempt exporter status
# TYPE login_attempt_up gauge
login_attempt_up 1

# HELP login_attempt_exporter_info Login attempt exporter information
# TYPE login_attempt_exporter_info gauge
login_attempt_exporter_info{version="1.0",log_source="$LOG_SOURCE"} 1
EOF

    echo ""

    # ========================================================================
    # Attempt Counters
    # ========================================================================
    cat <<EOF
# HELP login_attempt_failed_total Failed login attempts per period
# TYPE login_attempt_failed_total gauge
EOF

    for period_label in "1h:1 hour ago" "24h:24 hours ago" "7d:7 days ago"; do
        local label="${period_label%%:*}"
        local since="${period_label#*:}"
        local val
        val=$(count_failed_attempts "$since")
        echo "login_attempt_failed_total{period=\"$label\"} ${val:-0}"
    done

    echo ""

    cat <<EOF
# HELP login_attempt_success_total Successful logins per period
# TYPE login_attempt_success_total gauge
EOF

    for period_label in "1h:1 hour ago" "24h:24 hours ago" "7d:7 days ago"; do
        local label="${period_label%%:*}"
        local since="${period_label#*:}"
        local val
        val=$(count_successful_logins "$since")
        echo "login_attempt_success_total{period=\"$label\"} ${val:-0}"
    done

    echo ""

    cat <<EOF
# HELP login_attempt_invalid_user_total Invalid user attempts per period
# TYPE login_attempt_invalid_user_total gauge
EOF

    for period_label in "1h:1 hour ago" "24h:24 hours ago" "7d:7 days ago"; do
        local label="${period_label%%:*}"
        local since="${period_label#*:}"
        local val
        val=$(count_invalid_users "$since")
        echo "login_attempt_invalid_user_total{period=\"$label\"} ${val:-0}"
    done

    echo ""

    cat <<EOF
# HELP login_attempt_preauth_disconnect_total Pre-auth disconnects per period
# TYPE login_attempt_preauth_disconnect_total gauge
EOF

    for period_label in "1h:1 hour ago" "24h:24 hours ago" "7d:7 days ago"; do
        local label="${period_label%%:*}"
        local since="${period_label#*:}"
        local val
        val=$(count_preauth_disconnects "$since")
        echo "login_attempt_preauth_disconnect_total{period=\"$label\"} ${val:-0}"
    done

    echo ""

    # ========================================================================
    # Authentication Methods
    # ========================================================================
    cat <<EOF
# HELP login_attempt_auth_method_count Attempts by authentication method (24h)
# TYPE login_attempt_auth_method_count gauge
EOF

    while read -r method count; do
        [ -z "$method" ] && continue
        echo "login_attempt_auth_method_count{method=\"$method\",period=\"24h\"} ${count:-0}"
    done < <(get_auth_method_counts "24 hours ago")

    echo ""

    # ========================================================================
    # Unique Counts
    # ========================================================================
    cat <<EOF
# HELP login_attempt_unique_source_ips Unique source IPs per status and period
# TYPE login_attempt_unique_source_ips gauge
EOF

    for status in "failed" "success"; do
        for period_label in "1h:1 hour ago" "24h:24 hours ago"; do
            local label="${period_label%%:*}"
            local since="${period_label#*:}"
            local val
            val=$(get_unique_source_ips "$since" "$status")
            echo "login_attempt_unique_source_ips{status=\"$status\",period=\"$label\"} ${val:-0}"
        done
    done

    echo ""

    cat <<EOF
# HELP login_attempt_unique_users Unique usernames per status and period
# TYPE login_attempt_unique_users gauge
EOF

    for status in "failed" "success"; do
        for period_label in "1h:1 hour ago" "24h:24 hours ago"; do
            local label="${period_label%%:*}"
            local since="${period_label#*:}"
            local val
            val=$(get_unique_users "$since" "$status")
            echo "login_attempt_unique_users{status=\"$status\",period=\"$label\"} ${val:-0}"
        done
    done

    echo ""

    # ========================================================================
    # Top Attackers
    # ========================================================================
    cat <<EOF
# HELP login_attempt_top_failed_ip_count Top source IPs by failed attempts (24h)
# TYPE login_attempt_top_failed_ip_count gauge
EOF

    while read -r count ip; do
        [ -z "$ip" ] && continue
        echo "login_attempt_top_failed_ip_count{ip=\"$ip\"} $count"
    done < <(get_top_failed_ips 10)

    echo ""

    cat <<EOF
# HELP login_attempt_top_failed_user_count Top targeted usernames by failed attempts (24h)
# TYPE login_attempt_top_failed_user_count gauge
EOF

    while read -r count user; do
        [ -z "$user" ] && continue
        echo "login_attempt_top_failed_user_count{user=\"$user\"} $count"
    done < <(get_top_failed_users 10)

    echo ""

    cat <<EOF
# HELP login_attempt_top_success_user_count Top users by successful logins (24h)
# TYPE login_attempt_top_success_user_count gauge
EOF

    while read -r count user; do
        [ -z "$user" ] && continue
        echo "login_attempt_top_success_user_count{user=\"$user\"} $count"
    done < <(get_top_successful_users 10)

    echo ""

    # ========================================================================
    # Threat Detection
    # ========================================================================
    cat <<EOF
# HELP login_attempt_brute_force_score Brute force heuristic score per IP (attempts x unique_users)
# TYPE login_attempt_brute_force_score gauge
EOF

    while read -r ip score; do
        [ -z "$ip" ] && continue
        echo "login_attempt_brute_force_score{ip=\"$ip\"} $score"
    done < <(get_brute_force_candidates)

    echo ""

    cat <<EOF
# HELP login_attempt_root_attempts Root login attempts per period
# TYPE login_attempt_root_attempts gauge
EOF

    for period_label in "1h:1 hour ago" "24h:24 hours ago"; do
        local label="${period_label%%:*}"
        local since="${period_label#*:}"
        local val
        val=$(get_root_login_attempts "$since")
        echo "login_attempt_root_attempts{period=\"$label\"} ${val:-0}"
    done

    echo ""

    # ========================================================================
    # Rates
    # ========================================================================
    local failed_per_hour
    failed_per_hour=$(get_hourly_attempt_rate)

    local failed_24h success_24h success_rate
    failed_24h=$(count_failed_attempts "24 hours ago")
    success_24h=$(count_successful_logins "24 hours ago")
    failed_24h=${failed_24h:-0}
    success_24h=${success_24h:-0}

    local total_24h=$((failed_24h + success_24h))
    if [ "$total_24h" -gt 0 ] 2>/dev/null; then
        success_rate=$(awk "BEGIN {printf \"%.4f\", $success_24h / $total_24h}" 2>/dev/null || echo "0")
    else
        success_rate="0"
    fi

    cat <<EOF
# HELP login_attempt_failed_per_hour Average failed attempts per hour (24h)
# TYPE login_attempt_failed_per_hour gauge
login_attempt_failed_per_hour $failed_per_hour

# HELP login_attempt_success_rate Success ratio: success/(success+failed) over 24h
# TYPE login_attempt_success_rate gauge
login_attempt_success_rate $success_rate
EOF

    echo ""

    # ========================================================================
    # Log Health
    # ========================================================================
    local log_size=0
    local log_age=0

    if [ -n "$AUTH_LOG" ] && [ -f "$AUTH_LOG" ]; then
        log_size=$(stat -c %s "$AUTH_LOG" 2>/dev/null || echo "0")
        log_age=$(($(date +%s) - $(stat -c %Y "$AUTH_LOG" 2>/dev/null || echo 0)))
    fi

    local script_end script_duration
    script_end=$(date +%s)
    script_duration=$((script_end - script_start))

    cat <<EOF
# HELP login_attempt_log_size_bytes Size of auth log file
# TYPE login_attempt_log_size_bytes gauge
login_attempt_log_size_bytes $log_size

# HELP login_attempt_log_age_seconds Time since last auth log modification
# TYPE login_attempt_log_age_seconds gauge
login_attempt_log_age_seconds $log_age

# HELP login_attempt_exporter_duration_seconds Time to generate all metrics
# TYPE login_attempt_exporter_duration_seconds gauge
login_attempt_exporter_duration_seconds $script_duration

# HELP login_attempt_exporter_last_run_timestamp Unix timestamp of last successful run
# TYPE login_attempt_exporter_last_run_timestamp gauge
login_attempt_exporter_last_run_timestamp $script_end
EOF

    echo ""
}

# ============================================================================
# HTTP SERVER MODE
# ============================================================================

# Run simple HTTP server using netcat
# Serves metrics on /metrics endpoint
run_http_server() {
    echo "Starting login attempt exporter on port $HTTP_PORT..." >&2

    if ! command -v nc >/dev/null 2>&1; then
        echo "ERROR: netcat (nc) required for HTTP mode" >&2
        exit 1
    fi

    # Infinite loop accepting HTTP requests
    while true; do
        {
            read -r request
            # Check if request is for /metrics endpoint
            if [[ "$request" =~ ^GET\ /metrics ]]; then
                echo -e "HTTP/1.1 200 OK\r\nContent-Type: text/plain; version=0.0.4\r\n\r"
                generate_metrics
            else  # Serve HTML landing page for other requests
                echo -e "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r"
                cat <<EOF
<!DOCTYPE html>
<html>
<head><title>Login Attempt Exporter v1.0</title></head>
<body>
<h1>Login Attempt Exporter v1.0</h1>
<p><a href="/metrics">Metrics</a></p>
<h2>Metric Categories</h2>
<ul>
<li>Core Status: exporter up/down, version info</li>
<li>Attempt Counters: failed, successful, invalid user, pre-auth disconnects</li>
<li>Authentication Methods: password, publickey, keyboard-interactive</li>
<li>Unique Counts: unique source IPs and usernames</li>
<li>Top Attackers: top failed IPs, targeted users, successful users</li>
<li>Threat Detection: brute force scores, root login attempts</li>
<li>Rates: failed attempts per hour, success ratio</li>
<li>Log Health: log size, age, exporter runtime</li>
</ul>
</body>
</html>
EOF
            fi
        } | nc -l -p "$HTTP_PORT" -q 1 2>/dev/null  # -q 1: wait 1 second after EOF before closing
    done
}

# ============================================================================
# MAIN EXECUTION
# ============================================================================

# Main entry point - routes to appropriate output mode
main() {
    parse_args "$@"

    if [ "$HTTP_MODE" = true ]; then
        # Run HTTP server (blocks until killed)
        run_http_server
    elif [ -n "$OUTPUT_FILE" ]; then
        # Textfile collector mode: write atomically using temp file
        local output_dir
        output_dir="$(dirname "$OUTPUT_FILE")"
        mkdir -p "$output_dir"

        # Create temp file in SAME directory for atomic rename (same filesystem)
        local temp_file
        temp_file=$(mktemp "${output_dir}/.login_attempt_metrics.XXXXXX")

        # Generate metrics to temp file
        if ! generate_metrics > "$temp_file" 2>/dev/null; then
            rm -f "$temp_file"
            echo "ERROR: Failed to generate metrics" >&2
            exit 1
        fi

        # Validate: file must exist, have content, and contain enough metric lines
        local file_lines
        file_lines=$(wc -l < "$temp_file" 2>/dev/null || echo 0)

        if [ "$file_lines" -lt 10 ]; then
            rm -f "$temp_file"
            echo "ERROR: Metrics file too small ($file_lines lines), keeping previous" >&2
            exit 1
        fi

        # Set permissions before move
        chmod 644 "$temp_file"

        # Atomic rename - no gap where file is missing
        mv -f "$temp_file" "$OUTPUT_FILE"

        echo "Metrics written to $OUTPUT_FILE ($file_lines lines)" >&2
    else
        # Default: output to stdout
        generate_metrics
    fi
}

# Execute main function with all script arguments
main "$@"
