#!/bin/bash
# Audit complet serveur — applicatif + reseau + correlation
# Compatible RHEL 7/8/9
# Usage : bash server_audit.sh

HOSTNAME=$(hostname -s 2>/dev/null || hostname)
DATE=$(date '+%Y-%m-%d %H:%M:%S')
EXCLUDE_PROC="kworker|ksoftirq|migration|watchdog|kthread|rcu_|irq/|scsi|ata_|writeback|sshd:.*notty|bash /tmp|server_audit|capture_state|PM2.*God Daemon"
EXCLUDE_SVC="auditd|chronyd|crond|dbus|firewalld|getty|irqbalance|kdump|lvm2|lvmetad|NetworkManager|polkit|rsyslog|sshd|sssd|systemd|tuned|qualys|sentinelone|zabbix|commvault|veeam|tina|login|agetty|mingetty|logind|accounts-daemon|udisksd"

echo "########################################################"
echo "# AUDIT COMPLET — $HOSTNAME"
echo "# $DATE"
echo "# OS: $(cat /etc/redhat-release 2>/dev/null || head -1 /etc/os-release 2>/dev/null)"
echo "# Kernel: $(uname -r)"
echo "# Uptime: $(uptime -p 2>/dev/null || uptime)"
echo "########################################################"

##############################################################
# PARTIE 1 — APPLICATIF
##############################################################

echo ""
echo "========================================================"
echo "  PARTIE 1 — ANALYSE APPLICATIVE"
echo "========================================================"

# ── 1.1 Services applicatifs running ──
echo ""
echo "=== 1.1 SERVICES APPLICATIFS RUNNING ==="
echo "SERVICE|ENABLED|MAIN_PID|USER|EXEC_START"
for svc in $(sudo systemctl list-units --type=service --state=running --no-pager --no-legend | awk '{print $1}'); do
    svc_short=$(echo "$svc" | sed 's/.service//')
    echo "$svc_short" | grep -qiE "$EXCLUDE_SVC" && continue
    enabled=$(sudo systemctl is-enabled "$svc" 2>/dev/null)
    pid=$(sudo systemctl show -p MainPID "$svc" 2>/dev/null | awk -F= '{print $2}')
    user=$(sudo systemctl show -p User "$svc" 2>/dev/null | awk -F= '{print $2}')
    execstart=""
    [ -n "$pid" ] && [ "$pid" != "0" ] && [ -f "/proc/$pid/cmdline" ] && \
        execstart=$(tr '\0' ' ' < "/proc/$pid/cmdline" 2>/dev/null | cut -c1-150)
    if [ -z "$execstart" ]; then
        execstart=$(sudo systemctl show -p ExecStart "$svc" 2>/dev/null | grep -oP '(?<=path=)\S+' | head -1)
    fi
    echo "$svc_short|$enabled|$pid|${user:-root}|$execstart"
done

# ── 1.2 Processus applicatifs ──
echo ""
echo "=== 1.2 PROCESSUS APPLICATIFS ==="
echo "PID|PPID|USER|EXE|CWD|CMDLINE|RESTART_HINT"
for pid in $(sudo ls -d /proc/[0-9]* 2>/dev/null | sed 's|/proc/||' | sort -n); do
    [ ! -f /proc/$pid/cmdline ] && continue
    cmd=$(tr '\0' ' ' < /proc/$pid/cmdline 2>/dev/null)
    [ -z "$cmd" ] && continue
    echo "$cmd" | grep -qE '^\[' && continue
    echo "$cmd" | grep -qiE "$EXCLUDE_PROC" && continue
    user=$(sudo stat -c %U /proc/$pid 2>/dev/null)
    ppid=$(sudo awk '/PPid/{print $2}' /proc/$pid/status 2>/dev/null)
    exe=$(sudo readlink /proc/$pid/exe 2>/dev/null)
    cwd=$(sudo readlink /proc/$pid/cwd 2>/dev/null)
    is_interesting=0
    [ "$ppid" = "1" ] && is_interesting=1
    echo "$cwd" | grep -qi "/applis" && is_interesting=1
    echo "$exe" | grep -qiE "node|java|python|ruby|perl|php|tomcat" && is_interesting=1
    echo "$cmd" | grep -qiE "/applis|\.jar|\.js |\.py |\.rb " && is_interesting=1
    sudo ss -tlnp 2>/dev/null | grep "pid=$pid," >/dev/null && is_interesting=1
    [ "$is_interesting" = "0" ] && continue
    echo "$cmd" | grep -qiE "$EXCLUDE_SVC" && continue
    # Exclure workers enfants (meme exe que le parent)
    if [ "$ppid" != "1" ] && [ -n "$exe" ]; then
        parent_exe=$(sudo readlink /proc/$ppid/exe 2>/dev/null)
        [ "$exe" = "$parent_exe" ] && continue
        # Exclure enfants dont le grandparent est PM2 God Daemon
        grandppid=$(sudo awk '/PPid/{print $2}' /proc/$ppid/status 2>/dev/null)
        if [ -n "$grandppid" ]; then
            grandp_cmd=$(tr '\0' ' ' < /proc/$grandppid/cmdline 2>/dev/null)
            echo "$grandp_cmd" | grep -qiE "PM2.*God Daemon" && continue
        fi
    fi
    # Construire hint de redemarrage
    hint=""
    # Priorite 1: systemd
    svc_match=$(sudo systemctl status $pid 2>/dev/null | head -1 | grep -oP '\S+\.service' | head -1)
    if [ -z "$svc_match" ]; then
        exe_name=$(basename "$exe" 2>/dev/null)
        [ -n "$exe_name" ] && svc_match=$(sudo systemctl list-units --type=service --state=running --no-pager --no-legend 2>/dev/null | grep -i "$exe_name" | awk '{print $1}' | head -1)
    fi
    if [ -n "$svc_match" ]; then
        hint="sudo systemctl restart $svc_match"
    # Priorite 2: PM2
    elif command -v pm2 &>/dev/null; then
        pm2_name=""
        pm2_json=""
        for pm2_user in "$user" root $(sudo ps aux 2>/dev/null | grep -i 'PM2\|pm2' | grep -v grep | awk '{print $1}' | sort -u); do
            pm2_json=$(su - "$pm2_user" -c "pm2 jlist 2>/dev/null" 2>/dev/null)
            [ -z "$pm2_json" ] && continue
            pm2_name=$(echo "$pm2_json" | grep -oP '"pid"\s*:\s*'$pid'\b[^}]*"name"\s*:\s*"\K[^"]+' 2>/dev/null | head -1)
            [ -n "$pm2_name" ] && break
            pm2_name=$(echo "$pm2_json" | grep -oP '"name"\s*:\s*"\K[^"]+(?=[^}]*"pid"\s*:\s*'$pid'\b)' 2>/dev/null | head -1)
            [ -n "$pm2_name" ] && break
        done
        if [ -n "$pm2_name" ]; then
            pm2_bin=$(which pm2 2>/dev/null || echo "/usr/local/bin/pm2")
            pm2_exec=$(echo "$pm2_json" | grep -oP '"pm_exec_path"\s*:\s*"\K[^"]+' 2>/dev/null | head -1)
            pm2_args=$(echo "$pm2_json" | grep -oP '"args"\s*:\s*\[\K[^\]]+' 2>/dev/null | head -1 | sed 's/"//g; s/,/ /g')
            if [ -n "$pm2_exec" ] && [ -n "$pm2_args" ]; then
                hint="su - $pm2_user -c '$pm2_bin start $pm2_exec --name $pm2_name -- $pm2_args'"
            else
                hint="su - $pm2_user -c '$pm2_bin restart $pm2_name'"
            fi
        fi
    fi
    # Priorite 3: start script dans cwd
    if [ -z "$hint" ] && [ -n "$cwd" ] && [ -d "$cwd" ]; then
        start_script=$(find "$cwd" -maxdepth 1 \( -name "start*" -o -name "run*" -o -name "*.sh" \) -executable 2>/dev/null | head -1)
        if [ -n "$start_script" ]; then
            hint="su - $user -c 'cd $cwd && $start_script'"
        else
            hint="su - $user -c 'cd $cwd && $(echo $cmd | cut -c1-120)'"
        fi
    fi
    # Priorite 4: commande directe
    if [ -z "$hint" ]; then
        hint="su - $user -c '$(echo $cmd | cut -c1-120)'"
    fi
    echo "$pid|$ppid|$user|$exe|$cwd|$(echo $cmd | cut -c1-150)|$hint"
done

# ── 1.3 Services failed ──
echo ""
echo "=== 1.3 SERVICES EN ECHEC ==="
failed=$(sudo systemctl list-units --type=service --state=failed --no-pager --no-legend 2>/dev/null)
if [ -n "$failed" ]; then
    echo "$failed"
else
    echo "Aucun service en echec"
fi

# ── 1.4 Needs-restarting ──
echo ""
echo "=== 1.4 NEEDS-RESTARTING ==="
if ! command -v needs-restarting &>/dev/null; then
    major=$(rpm -E %{rhel} 2>/dev/null || cat /etc/redhat-release 2>/dev/null | grep -oP '\d+' | head -1)
    if [ "$major" -ge 9 ] 2>/dev/null; then
        dnf install -y dnf-utils 2>&1 | tail -1
    elif [ "$major" -ge 8 ] 2>/dev/null; then
        dnf install -y yum-utils 2>&1 | tail -1
    else
        yum install -y yum-utils 2>&1 | tail -1
    fi
fi
if command -v needs-restarting &>/dev/null; then
    sudo needs-restarting -r 2>/dev/null
    echo "EXIT_CODE=$?"
    echo "--- Services a redemarrer ---"
    sudo needs-restarting -s 2>/dev/null
else
    echo "sudo needs-restarting non disponible"
fi

# ── 1.5 Espace disque ──
echo ""
echo "=== 1.5 ESPACE DISQUE ==="
df -h --output=target,size,used,avail,pcent 2>/dev/null | grep -vE '^(tmpfs|devtmpfs)' || df -h 2>/dev/null

##############################################################
# PARTIE 2 — RESEAU
##############################################################

echo ""
echo "========================================================"
echo "  PARTIE 2 — ANALYSE RESEAU"
echo "========================================================"

# ── 2.1 Interfaces et IPs ──
echo ""
echo "=== 2.1 INTERFACES RESEAU ==="
echo "INTERFACE|IP|MASK|STATE|MAC"
ip -4 -o addr show 2>/dev/null | while read idx iface scope ip_mask rest; do
    ip=$(echo "$ip_mask" | cut -d/ -f1)
    mask=$(echo "$ip_mask" | cut -d/ -f2)
    state=$(ip link show "$iface" 2>/dev/null | grep -oP '(?<=state )\S+' | head -1)
    mac=$(ip link show "$iface" 2>/dev/null | grep -oP 'link/ether \K\S+' | head -1)
    echo "$iface|$ip|/$mask|${state:-UP}|${mac:--}"
done

# ── 2.2 Routes ──
echo ""
echo "=== 2.2 TABLE DE ROUTAGE ==="
echo "DESTINATION|GATEWAY|INTERFACE|METRIC"
ip route show 2>/dev/null | while read line; do
    dest=$(echo "$line" | awk '{print $1}')
    gw=$(echo "$line" | grep -oP '(?<=via )\S+')
    iface=$(echo "$line" | grep -oP '(?<=dev )\S+')
    metric=$(echo "$line" | grep -oP '(?<=metric )\S+')
    echo "$dest|${gw:-direct}|${iface:--}|${metric:--}"
done

# ── 2.3 Ports en ecoute ──
echo ""
echo "=== 2.3 PORTS EN ECOUTE ==="
echo "PROTO|ADDR:PORT|PID|PROCESS|USER|SERVICE"
sudo ss -tlnp 2>/dev/null | grep LISTEN | while read state recvq sendq local peer info; do
    pid=$(echo "$info" | grep -oP 'pid=\K[0-9]+' | head -1)
    proc=$(echo "$info" | grep -oP '"\K[^"]+' | head -1)
    port=$(echo "$local" | grep -oP ':\K[0-9]+$')
    user=""
    [ -n "$pid" ] && user=$(sudo stat -c %U /proc/$pid 2>/dev/null)
    svc=$(getent services "$port/tcp" 2>/dev/null | awk '{print $1}')
    echo "TCP|$local|${pid:--}|${proc:--}|${user:--}|${svc:--}"
done
sudo ss -ulnp 2>/dev/null | grep -v 'State' | while read state recvq sendq local peer info; do
    [ -z "$local" ] && continue
    pid=$(echo "$info" | grep -oP 'pid=\K[0-9]+' | head -1)
    proc=$(echo "$info" | grep -oP '"\K[^"]+' | head -1)
    port=$(echo "$local" | grep -oP ':\K[0-9]+$')
    user=""
    [ -n "$pid" ] && user=$(sudo stat -c %U /proc/$pid 2>/dev/null)
    svc=$(getent services "$port/udp" 2>/dev/null | awk '{print $1}')
    echo "UDP|$local|${pid:--}|${proc:--}|${user:--}|${svc:--}"
done

# ── 2.4 Connexions etablies ──
echo ""
echo "=== 2.4 CONNEXIONS ETABLIES ==="
echo "DIRECTION|PROTO|LOCAL|REMOTE|PID|PROCESS|USER|STATE"
listen_ports=$(sudo ss -tlnp 2>/dev/null | grep LISTEN | grep -oP '\S+:(\d+)\s' | grep -oP ':\K\d+' | sort -u)
sudo ss -tnp 2>/dev/null | grep -v 'State' | while read state recvq sendq local remote info; do
    [ "$state" = "State" ] && continue
    [ -z "$local" ] && continue
    pid=$(echo "$info" | grep -oP 'pid=\K[0-9]+' | head -1)
    proc=$(echo "$info" | grep -oP '"\K[^"]+' | head -1)
    user=""
    [ -n "$pid" ] && user=$(sudo stat -c %U /proc/$pid 2>/dev/null)
    local_port=$(echo "$local" | grep -oP ':\K[0-9]+$')
    direction="OUT"
    echo "$listen_ports" | grep -qx "$local_port" && direction="IN"
    echo "$direction|TCP|$local|$remote|${pid:--}|${proc:--}|${user:--}|$state"
done

# ── 2.5 Resume flux entrants ──
echo ""
echo "=== 2.5 RESUME FLUX ENTRANTS (par port) ==="
echo "PORT|SERVICE|PROCESS|NB_CONNEXIONS|SOURCES"
for port in $(sudo ss -tnp 2>/dev/null | grep ESTAB | awk '{print $4}' | grep -oP ':\K\d+$' | sort -n | uniq); do
    echo "$listen_ports" | grep -qx "$port" || continue
    svc=$(getent services "$port/tcp" 2>/dev/null | awk '{print $1}')
    proc=$(sudo ss -tlnp 2>/dev/null | grep ":$port " | grep -oP '"\K[^"]+' | head -1)
    count=$(sudo ss -tnp 2>/dev/null | grep ESTAB | awk '{print $4}' | grep -P ":${port}$" | wc -l)
    sources=$(sudo ss -tnp 2>/dev/null | grep ESTAB | awk -v p=":${port}$" '$4 ~ p {print $5}' | grep -oP '^[^:]+' | sort -u | head -10 | tr '\n' ',' | sed 's/,$//')
    echo "$port|${svc:--}|${proc:--}|$count|${sources:--}"
done

# ── 2.6 Resume flux sortants ──
echo ""
echo "=== 2.6 RESUME FLUX SORTANTS (par destination:port) ==="
echo "DEST_IP|DEST_PORT|SERVICE|PROCESS|NB_CONNEXIONS"
sudo ss -tnp 2>/dev/null | grep ESTAB | while read state recvq sendq local remote info; do
    local_port=$(echo "$local" | grep -oP ':\K[0-9]+$')
    echo "$listen_ports" | grep -qx "$local_port" && continue
    echo "$remote|$(echo "$info" | grep -oP '"\K[^"]+' | head -1)"
done | sort | uniq -c | sort -rn | head -50 | while read count dest_info; do
    dest_ip=$(echo "$dest_info" | grep -oP '^\S+(?=:\d+)')
    dest_port=$(echo "$dest_info" | grep -oP ':\K\d+(?=\|)')
    proc=$(echo "$dest_info" | awk -F'|' '{print $2}')
    svc=$(getent services "$dest_port/tcp" 2>/dev/null | awk '{print $1}')
    dns=$(timeout 2 host "$dest_ip" 2>/dev/null | grep -oP 'pointer \K\S+' | sed 's/\.$//' | head -1)
    if [ -n "$dns" ]; then
        echo "$dest_ip ($dns)|$dest_port|${svc:--}|${proc:--}|$count"
    else
        echo "$dest_ip|$dest_port|${svc:--}|${proc:--}|$count"
    fi
done

# ── 2.7 Connexions en attente ──
echo ""
echo "=== 2.7 CONNEXIONS EN ATTENTE ==="
echo "STATE|COUNT"
for st in TIME-WAIT CLOSE-WAIT FIN-WAIT-1 FIN-WAIT-2 SYN-SENT SYN-RECV LAST-ACK CLOSING; do
    cnt=$(sudo ss -tn state "$st" 2>/dev/null | grep -c -v 'State')
    [ "$cnt" -gt 0 ] && echo "$st|$cnt"
done

# ── 2.8 Stats reseau ──
echo ""
echo "=== 2.8 STATISTIQUES RESEAU ==="
echo "METRIC|VALUE"
echo "TCP ESTABLISHED|$(sudo ss -tn state established 2>/dev/null | grep -c -v 'State')"
echo "TCP LISTEN|$(sudo ss -tln 2>/dev/null | grep -c LISTEN)"
echo "UDP LISTEN|$(sudo ss -uln 2>/dev/null | grep -c -v 'State')"

# ── 2.9 Trafic par interface ──
echo ""
echo "=== 2.9 TRAFIC PAR INTERFACE ==="
echo "INTERFACE|RX_BYTES|RX_PACKETS|RX_ERRORS|TX_BYTES|TX_PACKETS|TX_ERRORS"
sudo cat /proc/net/dev 2>/dev/null | tail -n+3 | while read line; do
    iface=$(echo "$line" | awk -F: '{print $1}' | tr -d ' ')
    stats=$(echo "$line" | awk -F: '{print $2}')
    rx_bytes=$(echo "$stats" | awk '{print $1}')
    rx_pkt=$(echo "$stats" | awk '{print $2}')
    rx_err=$(echo "$stats" | awk '{print $3}')
    tx_bytes=$(echo "$stats" | awk '{print $9}')
    tx_pkt=$(echo "$stats" | awk '{print $10}')
    tx_err=$(echo "$stats" | awk '{print $11}')
    [ "$iface" = "lo" ] && continue
    rx_h=$(numfmt --to=iec "$rx_bytes" 2>/dev/null || echo "${rx_bytes}B")
    tx_h=$(numfmt --to=iec "$tx_bytes" 2>/dev/null || echo "${tx_bytes}B")
    echo "$iface|$rx_h|$rx_pkt|$rx_err|$tx_h|$tx_pkt|$tx_err"
done

# ── 2.10 Firewall ──
echo ""
echo "=== 2.10 FIREWALL ==="
if command -v iptables &>/dev/null; then
    echo "--- POLICY ---"
    for chain in INPUT OUTPUT FORWARD; do
        policy=$(iptables -L "$chain" -n 2>/dev/null | head -1 | grep -oP 'policy \K\w+')
        echo "$chain|$policy"
    done
    echo "--- INPUT ---"
    echo "NUM|TARGET|PROTO|SOURCE|DEST|PORT|INFO"
    iptables -L INPUT -n -v --line-numbers 2>/dev/null | tail -n+3 | head -30 | while read num pkts bytes target prot opt in out source dest rest; do
        port=$(echo "$rest" | grep -oP '(dpt|dpts):\K\S+' | head -1)
        echo "$num|$target|$prot|$source|$dest|${port:--}|$rest"
    done
    echo "--- OUTPUT ---"
    echo "NUM|TARGET|PROTO|SOURCE|DEST|PORT|INFO"
    iptables -L OUTPUT -n -v --line-numbers 2>/dev/null | tail -n+3 | head -30 | while read num pkts bytes target prot opt in out source dest rest; do
        port=$(echo "$rest" | grep -oP '(dpt|dpts):\K\S+' | head -1)
        echo "$num|$target|$prot|$source|$dest|${port:--}|$rest"
    done
else
    echo "iptables non disponible"
fi
if sudo systemctl is-active firewalld &>/dev/null 2>&1; then
    echo "--- FIREWALLD ---"
    echo "ZONE|SERVICES|PORTS"
    for zone in $(firewall-cmd --get-active-zones 2>/dev/null | grep -v '^\s' | grep -v '^$'); do
        svcs=$(firewall-cmd --zone="$zone" --list-services 2>/dev/null | tr ' ' ',')
        ports=$(firewall-cmd --zone="$zone" --list-ports 2>/dev/null | tr ' ' ',')
        echo "$zone|${svcs:--}|${ports:--}"
    done
fi

##############################################################
# PARTIE 3 — CORRELATION
##############################################################

echo ""
echo "========================================================"
echo "  PARTIE 3 — CORRELATION PROCESS ↔ PORTS ↔ FLUX"
echo "========================================================"

echo ""
echo "=== 3.1 MATRICE PROCESS → PORTS → CONNEXIONS ==="
echo "PROCESS|USER|PID|LISTEN_PORTS|CONN_IN|CONN_OUT|REMOTE_DESTINATIONS"

# Pour chaque process qui ecoute sur un port
sudo ss -tlnp 2>/dev/null | grep LISTEN | while read state recvq sendq local peer info; do
    pid=$(echo "$info" | grep -oP 'pid=\K[0-9]+' | head -1)
    proc=$(echo "$info" | grep -oP '"\K[^"]+' | head -1)
    [ -z "$pid" ] && continue
    port=$(echo "$local" | grep -oP ':\K[0-9]+$')
    echo "$pid|$proc|$port"
done | sort -t'|' -k1,1n -u | while IFS='|' read pid proc port; do
    user=$(sudo stat -c %U /proc/$pid 2>/dev/null)
    # Tous les ports en ecoute pour ce PID
    ports=$(sudo ss -tlnp 2>/dev/null | grep "pid=$pid," | grep -oP '\S+:\K\d+(?=\s)' | sort -un | tr '\n' ',' | sed 's/,$//')
    # Connexions entrantes sur ces ports
    conn_in=0
    for p in $(echo "$ports" | tr ',' ' '); do
        c=$(sudo ss -tnp 2>/dev/null | grep ESTAB | awk '{print $4}' | grep -c ":${p}$")
        conn_in=$((conn_in + c))
    done
    # Connexions sortantes de ce PID
    conn_out=$(sudo ss -tnp 2>/dev/null | grep ESTAB | grep "pid=$pid," | while read st rq sq lc rm inf; do
        lp=$(echo "$lc" | grep -oP ':\K\d+$')
        echo "$listen_ports" | grep -qx "$lp" || echo OUT
    done | wc -l)
    # Destinations sortantes
    dests=$(sudo ss -tnp 2>/dev/null | grep ESTAB | grep "pid=$pid," | awk '{print $5}' | sort -u | head -5 | tr '\n' ',' | sed 's/,$//')
    echo "$proc|${user:--}|$pid|$ports|$conn_in|$conn_out|${dests:--}"
done

# Processus avec connexions sortantes mais sans port en ecoute
echo ""
echo "=== 3.2 PROCESS SORTANTS UNIQUEMENT (pas de port en ecoute) ==="
echo "PROCESS|USER|PID|DESTINATIONS"
sudo ss -tnp 2>/dev/null | grep ESTAB | while read state recvq sendq local remote info; do
    pid=$(echo "$info" | grep -oP 'pid=\K[0-9]+' | head -1)
    proc=$(echo "$info" | grep -oP '"\K[^"]+' | head -1)
    [ -z "$pid" ] && continue
    local_port=$(echo "$local" | grep -oP ':\K[0-9]+$')
    # Verifier si ce process n'ecoute PAS
    sudo ss -tlnp 2>/dev/null | grep "pid=$pid," >/dev/null && continue
    echo "$pid|$proc|$remote"
done | sort -t'|' -k1,1n -k3 | awk -F'|' '{
    if ($1 != prev_pid) {
        if (prev_pid != "") print prev_pid"|"prev_proc"|"dests
        prev_pid=$1; prev_proc=$2; dests=$3
    } else {
        dests=dests","$3
    }
} END { if (prev_pid != "") print prev_pid"|"prev_proc"|"dests }' | while IFS='|' read pid proc dests; do
    user=$(sudo stat -c %U /proc/$pid 2>/dev/null)
    echo "$proc|${user:--}|$pid|$dests"
done

echo ""
echo "########################################################"
echo "# FIN AUDIT — $HOSTNAME — $(date '+%H:%M:%S')"
echo "########################################################"