2611.1
This commit is contained in:
1 parent
c929b299bf
commit
0fdc1facfa
2 files changed
+148
-11
No files matched your search
+131
-11
@@ -5,6 +5,9 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|||||||
LOG_FILE="/var/log/portspoof.log"
|
LOG_FILE="/var/log/portspoof.log"
|
||||||
EVENTS_WINDOW_FILE="$SCRIPT_DIR/.events_window"
|
EVENTS_WINDOW_FILE="$SCRIPT_DIR/.events_window"
|
||||||
EVENTS_HOURS=1
|
EVENTS_HOURS=1
|
||||||
|
WEBHOOK_FILE="$SCRIPT_DIR/.discord_webhook"
|
||||||
|
WEBHOOK_OFFSET_FILE="$SCRIPT_DIR/.webhook_offset"
|
||||||
|
WEBHOOK_PID_FILE="$SCRIPT_DIR/.webhook.pid"
|
||||||
if [ -f "$EVENTS_WINDOW_FILE" ]; then
|
if [ -f "$EVENTS_WINDOW_FILE" ]; then
|
||||||
read -r _loaded < "$EVENTS_WINDOW_FILE" 2>/dev/null
|
read -r _loaded < "$EVENTS_WINDOW_FILE" 2>/dev/null
|
||||||
case "$_loaded" in
|
case "$_loaded" in
|
||||||
@@ -102,7 +105,7 @@ status_box() {
|
|||||||
echo " ╠══════════════════════════════════════════════════════════════════════════╣"
|
echo " ╠══════════════════════════════════════════════════════════════════════════╣"
|
||||||
echo " ║ Network Interface Status ║"
|
echo " ║ Network Interface Status ║"
|
||||||
echo " ╠══════════════════════════════════════════════════════════════════════════╣"
|
echo " ╠══════════════════════════════════════════════════════════════════════════╣"
|
||||||
echo " ║ v0.18 ║"
|
echo " ║ v0.19 ║"
|
||||||
|
|
||||||
local ifaces
|
local ifaces
|
||||||
mapfile -t ifaces < <(get_interfaces)
|
mapfile -t ifaces < <(get_interfaces)
|
||||||
@@ -415,6 +418,118 @@ shell_access() {
|
|||||||
read -rp "Press Enter to continue..."
|
read -rp "Press Enter to continue..."
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# ── discord webhook ───────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
send_discord() {
|
||||||
|
local webhook
|
||||||
|
webhook=$(cat "$WEBHOOK_FILE" 2>/dev/null)
|
||||||
|
[ -n "$webhook" ] || return 1
|
||||||
|
curl -s -o /dev/null --max-time 10 \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d "$(printf '{"content":"%s"}' "$1")" "$webhook"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Builds the Discord message from one raw log line; exits non-zero if unparseable.
|
||||||
|
webhook_msg() {
|
||||||
|
python3 -c '
|
||||||
|
import sys, json
|
||||||
|
from datetime import datetime
|
||||||
|
try:
|
||||||
|
e = json.loads(sys.argv[1])
|
||||||
|
except ValueError:
|
||||||
|
sys.exit(1)
|
||||||
|
raw = str(e.get("timestamp", ""))
|
||||||
|
if raw.endswith("Z"):
|
||||||
|
raw = raw[:-1] + "+00:00"
|
||||||
|
try:
|
||||||
|
ts = datetime.fromisoformat(raw).astimezone().strftime("%Y-%m-%d %H:%M:%S")
|
||||||
|
except ValueError:
|
||||||
|
ts = "?"
|
||||||
|
src = str(e.get("src_ip", "?")).replace("\"", "").replace("\\", "")
|
||||||
|
dst = str(e.get("dst_port", "?"))
|
||||||
|
print(f"⚠️ **PortSpoof alert** — {src} → port {dst} at {ts}")
|
||||||
|
' "$1"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Forwards new log lines to the webhook. Runs in the background; survives the menu.
|
||||||
|
start_webhook_watcher() {
|
||||||
|
[ -f "$WEBHOOK_FILE" ] || return 0
|
||||||
|
if [ -f "$WEBHOOK_PID_FILE" ] && kill -0 "$(cat "$WEBHOOK_PID_FILE")" 2>/dev/null; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
# Seed offset so old events are not replayed.
|
||||||
|
echo "$(wc -l < "$LOG_FILE" 2>/dev/null || echo 0)" > "$WEBHOOK_OFFSET_FILE"
|
||||||
|
(
|
||||||
|
trap 'rm -f "$WEBHOOK_PID_FILE"' EXIT
|
||||||
|
while true; do
|
||||||
|
sleep 2
|
||||||
|
[ -f "$LOG_FILE" ] || continue
|
||||||
|
total=$(wc -l < "$LOG_FILE")
|
||||||
|
offset=$(cat "$WEBHOOK_OFFSET_FILE" 2>/dev/null)
|
||||||
|
[ -n "$offset" ] || offset=0
|
||||||
|
if [ "$total" -le "$offset" ]; then
|
||||||
|
[ "$total" -lt "$offset" ] && echo "$total" > "$WEBHOOK_OFFSET_FILE"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
tail -n +"$((offset + 1))" "$LOG_FILE" | while IFS= read -r line; do
|
||||||
|
msg=$(webhook_msg "$line") || continue
|
||||||
|
send_discord "$msg"
|
||||||
|
done
|
||||||
|
echo "$total" > "$WEBHOOK_OFFSET_FILE"
|
||||||
|
done
|
||||||
|
) &
|
||||||
|
echo $! > "$WEBHOOK_PID_FILE"
|
||||||
|
}
|
||||||
|
|
||||||
|
stop_webhook_watcher() {
|
||||||
|
if [ -f "$WEBHOOK_PID_FILE" ]; then
|
||||||
|
kill "$(cat "$WEBHOOK_PID_FILE")" 2>/dev/null
|
||||||
|
rm -f "$WEBHOOK_PID_FILE"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
set_webhook() {
|
||||||
|
clear
|
||||||
|
echo ""
|
||||||
|
echo "╔══════════════════════════════════════════════════════════════════════════╗"
|
||||||
|
echo "║ Discord Webhook ║"
|
||||||
|
echo "╚══════════════════════════════════════════════════════════════════════════╝"
|
||||||
|
echo ""
|
||||||
|
local current
|
||||||
|
current=$(cat "$WEBHOOK_FILE" 2>/dev/null)
|
||||||
|
echo " Current: ${current:-none}"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
while true; do
|
||||||
|
read -rp " Webhook URL (leave empty to disable): " url
|
||||||
|
if [ -z "$url" ]; then
|
||||||
|
stop_webhook_watcher
|
||||||
|
rm -f "$WEBHOOK_FILE" "$WEBHOOK_OFFSET_FILE"
|
||||||
|
echo ""
|
||||||
|
echo " Webhook disabled."
|
||||||
|
read -rp "Press Enter to continue..."
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
case "$url" in
|
||||||
|
https://discord.com/api/webhooks/*) ;;
|
||||||
|
*) echo " Invalid: must start with https://discord.com/api/webhooks/"; continue ;;
|
||||||
|
esac
|
||||||
|
if curl -s -o /dev/null --max-time 10 -X POST \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"content":"PortSpoof webhook test — configured from menu"}' "$url"; then
|
||||||
|
echo "$url" > "$WEBHOOK_FILE"
|
||||||
|
start_webhook_watcher
|
||||||
|
echo ""
|
||||||
|
echo " Webhook saved — test message sent, watcher started."
|
||||||
|
break
|
||||||
|
else
|
||||||
|
echo " Test failed — check the URL and try again."
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
echo ""
|
||||||
|
read -rp "Press Enter to continue..."
|
||||||
|
}
|
||||||
|
|
||||||
# ── log helpers ───────────────────────────────────────────────────────────────
|
# ── log helpers ───────────────────────────────────────────────────────────────
|
||||||
# Prints the last 5 events within the last $EVENTS_HOURS hours
|
# Prints the last 5 events within the last $EVENTS_HOURS hours
|
||||||
# (always exactly 5 lines): timestamp (local time), src_ip, dst_port.
|
# (always exactly 5 lines): timestamp (local time), src_ip, dst_port.
|
||||||
@@ -584,7 +699,7 @@ main_screen() {
|
|||||||
|
|
||||||
# ── main menu ─────────────────────────────────────────────────────────────────
|
# ── main menu ─────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
MENU_ITEMS=("Refresh" "Timeframe: ${EVENTS_HOURS}h" "Set Static IP" "Set DHCP" "Shell" "Reboot" "Update")
|
MENU_ITEMS=("Refresh" "Timeframe: ${EVENTS_HOURS}h" "Discord Webhook" "Set Static IP" "Set DHCP" "Shell" "Reboot" "Update")
|
||||||
|
|
||||||
toggle_timeframe() {
|
toggle_timeframe() {
|
||||||
if [ "$EVENTS_HOURS" = "1" ]; then
|
if [ "$EVENTS_HOURS" = "1" ]; then
|
||||||
@@ -609,26 +724,29 @@ main_menu() {
|
|||||||
toggle_timeframe
|
toggle_timeframe
|
||||||
;;
|
;;
|
||||||
2)
|
2)
|
||||||
|
set_webhook
|
||||||
|
;;
|
||||||
|
3)
|
||||||
if select_interface; then
|
if select_interface; then
|
||||||
set_static "$SELECTED_IFACE"
|
set_static "$SELECTED_IFACE"
|
||||||
fi
|
fi
|
||||||
echo ""
|
echo ""
|
||||||
read -rp "Press Enter to continue..."
|
read -rp "Press Enter to continue..."
|
||||||
;;
|
;;
|
||||||
3)
|
4)
|
||||||
if select_interface; then
|
if select_interface; then
|
||||||
set_dhcp "$SELECTED_IFACE"
|
set_dhcp "$SELECTED_IFACE"
|
||||||
fi
|
fi
|
||||||
echo ""
|
echo ""
|
||||||
read -rp "Press Enter to continue..."
|
read -rp "Press Enter to continue..."
|
||||||
;;
|
;;
|
||||||
4)
|
5)
|
||||||
shell_access
|
shell_access
|
||||||
;;
|
;;
|
||||||
5)
|
6)
|
||||||
reboot_confirm
|
reboot_confirm
|
||||||
;;
|
;;
|
||||||
6)
|
7)
|
||||||
run_update
|
run_update
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
@@ -637,9 +755,11 @@ main_menu() {
|
|||||||
|
|
||||||
# ── entry point ───────────────────────────────────────────────────────────────
|
# ── entry point ───────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
if [ "$EUID" -ne 0 ]; then
|
if [ "${BASH_SOURCE[0]}" = "$0" ]; then
|
||||||
echo "This script must be run as root (use sudo)."
|
if [ "$EUID" -ne 0 ]; then
|
||||||
exit 1
|
echo "This script must be run as root (use sudo)."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
start_webhook_watcher
|
||||||
|
main_menu
|
||||||
fi
|
fi
|
||||||
|
|
||||||
main_menu
|
|
||||||
Executable
+17
@@ -0,0 +1,17 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Self-check for the Discord webhook helpers: bash test_webhook.sh
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
# shellcheck source=network-config.sh
|
||||||
|
source "$SCRIPT_DIR/network-config.sh"
|
||||||
|
|
||||||
|
line='{"timestamp":"2026-11-05T12:00:00Z","src_ip":"1.2.3.4","dst_port":8080}'
|
||||||
|
msg=$(webhook_msg "$line") || { echo "FAIL: valid line rejected"; exit 1; }
|
||||||
|
echo "$msg" | grep -q "1.2.3.4" || { echo "FAIL: src_ip missing from: $msg"; exit 1; }
|
||||||
|
echo "$msg" | grep -q "8080" || { echo "FAIL: dst_port missing from: $msg"; exit 1; }
|
||||||
|
|
||||||
|
if webhook_msg 'not json' >/dev/null; then
|
||||||
|
echo "FAIL: invalid line accepted"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "OK: $msg"
|
||||||
Reference in new issue
Block a user