Sync all scripts from website downloads — 352 scripts total

Includes updated JS challenge scripts with Claude-User whitelist,
same-site referer bypass, Blackbox-Exporter allowed bot, and all
new exporters, cheat sheets, and automation scripts.
This commit is contained in:
2026-05-25 03:31:08 +02:00
parent dbd6bf0324
commit a1a17e81a1
332 changed files with 174509 additions and 1106 deletions
+319 -123
View File
@@ -1,13 +1,23 @@
#!/bin/bash
################################################################################
# Script Name: iptables-blocklists.sh
# Version: 1.0
# Version: 1.04
# Description: Per-feed iptables threat intelligence blocking with ipset
# Author: Phil Connor
# Contact: contact@mylinux.work
# Website: https://mylinux.work
# License: MIT
################################################################################
# Changelog:
# v1.04 - Fix IPv6 parser: reject timestamps (e.g. 14:34:21) misidentified
# as IPv6 addresses by requiring hex letters, 3+ groups, or ::
# v1.03 - Fix remove-feed/disable-feed: delete iptables rules before destroying
# ipsets. Remove dangerous ipset destruction of disabled feeds during
# update. Always swap ipsets even when feed is empty (clears stale
# blocks). Use -I (insert) for IPv6 rules instead of -A (append).
# Fix show-stats to count both IPv4+IPv6 blocks with cached journal.
# Add curl requirement check. Fix ipset member counting.
################################################################################
# Don't use 'set -e' - causes issues with ipset error handling
CONFIG_DIR="/etc/iptables-threats"
@@ -45,6 +55,7 @@ COMMANDS:
whitelist-add IP Add IP/CIDR to whitelist
whitelist-init Initialize whitelist with RFC1918/Docker networks
whitelist-list Show all whitelisted IPs
clean-cache Remove cache files for disabled feeds
OPTIONS:
-h, --help Show this help message
@@ -77,7 +88,53 @@ EOF
}
log_message() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
local msg
msg="[$(date '+%Y-%m-%d %H:%M:%S')] $1"
echo "$msg"
echo "$msg" >> "$LOG_FILE" 2>/dev/null || true
}
# Iterate over enabled feeds in $FEEDS_CONFIG, calling the provided callback
# function with arguments: name url type description
# Usage: for_each_enabled_feed my_callback_function
for_each_enabled_feed() {
local callback="$1"
[ -f "$FEEDS_CONFIG" ] || return 0
local enabled name url type description
while IFS='|' read -r enabled name url type description; do
[[ "$enabled" =~ ^#.*$ ]] && continue
[[ -z "$enabled" ]] && continue
[ "$enabled" != "1" ] && continue
"$callback" "$name" "$url" "$type" "$description"
done < "$FEEDS_CONFIG"
}
# Iterate over ALL feeds (enabled + disabled), calling the provided callback
# function with arguments: enabled name url type description
for_each_feed() {
local callback="$1"
[ -f "$FEEDS_CONFIG" ] || return 0
local enabled name url type description
while IFS='|' read -r enabled name url type description; do
[[ "$enabled" =~ ^#.*$ ]] && continue
[[ -z "$enabled" ]] && continue
"$callback" "$enabled" "$name" "$url" "$type" "$description"
done < "$FEEDS_CONFIG"
}
validate_feed_name() {
local name="$1"
if [ -z "$name" ]; then
echo "ERROR: Feed name cannot be empty"; return 1
fi
if [[ ! "$name" =~ ^[a-zA-Z0-9_-]+$ ]]; then
echo "ERROR: Feed name '$name' contains invalid characters (only a-z, 0-9, _, - allowed)"; return 1
fi
if [ "${#name}" -gt 20 ]; then
echo "ERROR: Feed name '$name' too long (max 20 chars, ipset name limit)"; return 1
fi
}
parse_args() {
@@ -89,7 +146,7 @@ parse_args() {
--no-auto-update) ENABLE_AUTO_UPDATE=false; shift ;;
--no-ipv6) ENABLE_IPV6=false; shift ;;
--update-interval) UPDATE_INTERVAL="$2"; shift 2 ;;
install|update|apply-rules|test-rules|list-feeds|show-stats|whitelist-init|whitelist-list) COMMAND="$1"; shift ;;
install|update|apply-rules|test-rules|list-feeds|show-stats|whitelist-init|whitelist-list|clean-cache) COMMAND="$1"; shift ;;
add-feed) COMMAND="add-feed"; FEED_NAME="$2"; FEED_URL="$3"; shift 3 ;;
remove-feed|enable-feed|disable-feed) COMMAND="$1"; FEED_NAME="$2"; shift 2 ;;
whitelist-add) COMMAND="whitelist-add"; WHITELIST_IP="$2"; shift 2 ;;
@@ -115,6 +172,10 @@ check_requirements() {
exit 1
fi
fi
command -v curl >/dev/null 2>&1 || { echo "ERROR: curl required"; exit 1; }
ensure_ipsets_exist
}
create_directory_structure() {
@@ -137,16 +198,29 @@ cleanup_old_backups() {
}
initialize_feeds_config() {
[ -f "$FEEDS_CONFIG" ] && return
local has_feeds
has_feeds=$(grep -c '^[01]|' "$FEEDS_CONFIG" 2>/dev/null || true)
if [ -f "$FEEDS_CONFIG" ] && [ "$has_feeds" -gt 0 ]; then
log_message "Feeds configuration already exists with $has_feeds feeds"
return
fi
log_message "Creating feeds configuration..."
[ -f "$FEEDS_CONFIG" ] && mv "$FEEDS_CONFIG" "${FEEDS_CONFIG}.old-$(date +%Y%m%d-%H%M%S)"
cat > "$FEEDS_CONFIG" <<'EOF'
# Threat Intelligence Feeds Configuration
# Format: ENABLED|NAME|URL|TYPE|DESCRIPTION
#
# ENABLED: 1 (enabled) or 0 (disabled)
# NAME: Unique feed identifier
# URL: Feed URL
# TYPE: Format type (plain, cidr, commented, custom)
# URL: Feed URL (http/https) or local file (file:///path/to/file)
# TYPE: Format type (plain, cidr, commented)
# plain - One IP/CIDR per line, no comments
# cidr - IP/CIDR with optional inline comments/fields
# commented - Lines starting with # or ; are ignored, IPs extracted
# DESCRIPTION: Feed description
1|cinsarmy|http://cinsscore.com/list/ci-badguys.txt|plain|CINS Army Malicious IPs
@@ -181,95 +255,144 @@ EOF
chmod 600 "$FEEDS_CONFIG"
}
_ensure_feed_ipset() {
local name="$1"
ipset list "${IPSET_PREFIX}-${name}" >/dev/null 2>&1 || \
ipset create "${IPSET_PREFIX}-${name}" hash:net family inet hashsize 4096 maxelem 200000 2>/dev/null || true
if [ "$ENABLE_IPV6" = true ]; then
ipset list "${IPSET_PREFIX}-${name}-v6" >/dev/null 2>&1 || \
ipset create "${IPSET_PREFIX}-${name}-v6" hash:net family inet6 hashsize 4096 maxelem 200000 2>/dev/null || true
fi
}
ensure_ipsets_exist() {
if [ -f /etc/sysconfig/ipset ]; then
ipset restore -f /etc/sysconfig/ipset 2>/dev/null || true
elif [ -f /etc/iptables/ipsets ]; then
ipset restore -f /etc/iptables/ipsets 2>/dev/null || true
fi
ipset list "$WHITELIST_IPSET" >/dev/null 2>&1 || \
ipset create "$WHITELIST_IPSET" hash:net family inet hashsize 1024 maxelem 10000 2>/dev/null || true
if [ "$ENABLE_IPV6" = true ]; then
ipset list "$WHITELIST_IPSET_V6" >/dev/null 2>&1 || \
ipset create "$WHITELIST_IPSET_V6" hash:net family inet6 hashsize 1024 maxelem 10000 2>/dev/null || true
fi
for_each_enabled_feed _ensure_feed_ipset
}
setup_ipsets() {
log_message "Setting up per-feed ipsets..."
# Whitelist
if ! ipset list "$WHITELIST_IPSET" >/dev/null 2>&1; then
ipset create "$WHITELIST_IPSET" hash:net family inet hashsize 1024 maxelem 10000
ipset add "$WHITELIST_IPSET" 127.0.0.1 2>/dev/null || true
fi
if [ "$ENABLE_IPV6" = true ] && ! ipset list "$WHITELIST_IPSET_V6" >/dev/null 2>&1; then
ipset create "$WHITELIST_IPSET_V6" hash:net family inet6 hashsize 1024 maxelem 10000
ipset add "$WHITELIST_IPSET_V6" ::1 2>/dev/null || true
fi
# Create ipset per feed
while IFS='|' read -r enabled name url type description; do
[[ "$enabled" =~ ^#.*$ ]] && continue
[[ -z "$enabled" ]] && continue
[ "$enabled" != "1" ] && continue
if ! ipset list "${IPSET_PREFIX}-${name}" >/dev/null 2>&1; then
ipset create "${IPSET_PREFIX}-${name}" hash:net family inet hashsize 4096 maxelem 200000
fi
if [ "$ENABLE_IPV6" = true ] && ! ipset list "${IPSET_PREFIX}-${name}-v6" >/dev/null 2>&1; then
ipset create "${IPSET_PREFIX}-${name}-v6" hash:net family inet6 hashsize 4096 maxelem 200000
fi
done < "$FEEDS_CONFIG"
for_each_enabled_feed _ensure_feed_ipset
}
download_feed() {
curl -f -s -m 30 -L "$1" -o "$2" 2>/dev/null
local url="$1" output="$2"
# Local file support: file:///path or file://path
if [[ "$url" == file://* ]]; then
local local_path="${url#file://}"
if [ ! -f "$local_path" ]; then
log_message " Local file not found: $local_path"
return 1
fi
cp "$local_path" "$output" 2>/dev/null || return 1
return 0
fi
local http_code
http_code=$(curl -f -s -m 60 --connect-timeout 10 -L \
-A "iptables-threat-feeds-per-feed/1.0" \
-w "%{http_code}" -o "$output" "$url" 2>/dev/null) || true
if [ ! -s "$output" ]; then
log_message " Download failed for $url (HTTP $http_code, empty response)"
return 1
fi
return 0
}
parse_feed() {
local file="$1" type="$2" out_v4="$3" out_v6="$4"
true > "$out_v4"
true > "$out_v6"
: > "$out_v4"
: > "$out_v6"
local cleaned
cleaned=$(mktemp)
tr -d '\r' < "$file" > "$cleaned"
# IPv6 filter: require either a hex letter [a-fA-F], or 3+ colon-separated
# groups, or ::. This excludes timestamps like 14:34:21 which only have
# digits and exactly two colon-separated groups.
local v6_filter='([a-fA-F]|:.*:.*:|::)'
case "$type" in
plain)
grep -E '^[0-9.]+(/[0-9]+)?$' "$file" >> "$out_v4" 2>/dev/null || true
[ "$ENABLE_IPV6" = true ] && grep -E '^[0-9a-fA-F:]+(/[0-9]+)?$' "$file" | grep ':' >> "$out_v6" 2>/dev/null || true
grep -E '^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+(/[0-9]+)?$' "$cleaned" >> "$out_v4" 2>/dev/null || true
if [ "$ENABLE_IPV6" = true ]; then
grep -E '^[0-9a-fA-F:]+(/[0-9]+)?$' "$cleaned" | grep ':' \
| grep -E "$v6_filter" >> "$out_v6" 2>/dev/null || true
fi
;;
cidr)
grep -E '^[0-9.]+' "$file" | cut -d' ' -f1 | cut -d'#' -f1 | grep -v '^$' >> "$out_v4" 2>/dev/null || true
[ "$ENABLE_IPV6" = true ] && grep -E '^[0-9a-fA-F:]+' "$file" | grep ':' | cut -d' ' -f1 | cut -d'#' -f1 >> "$out_v6" 2>/dev/null || true
grep -oE '^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+(/[0-9]+)?' "$cleaned" \
| grep -v '^$' >> "$out_v4" 2>/dev/null || true
if [ "$ENABLE_IPV6" = true ]; then
grep -oE '^[0-9a-fA-F:]+(/[0-9]+)?' "$cleaned" \
| grep ':' | grep -E "$v6_filter" | grep -v '^$' >> "$out_v6" 2>/dev/null || true
fi
;;
commented)
grep -v -E '^[#;]|^$' "$file" | grep -oE '[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+(/[0-9]+)?' >> "$out_v4" 2>/dev/null || true
[ "$ENABLE_IPV6" = true ] && grep -v -E '^[#;]|^$' "$file" | grep -oE '[0-9a-fA-F:]+(/[0-9]+)?' | grep -E '^[0-9a-fA-F]{1,4}:[0-9a-fA-F:]+' >> "$out_v6" 2>/dev/null || true
grep -v -E '^[#;]|^$' "$cleaned" \
| grep -oE '[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+(/[0-9]+)?' >> "$out_v4" 2>/dev/null || true
if [ "$ENABLE_IPV6" = true ]; then
grep -v -E '^[#;]|^$' "$cleaned" \
| grep -oE '[0-9a-fA-F:]+(/[0-9]+)?' \
| grep -E '^[0-9a-fA-F]{1,4}:[0-9a-fA-F:]+' \
| grep -E "$v6_filter" >> "$out_v6" 2>/dev/null || true
fi
;;
esac
rm -f "$cleaned"
}
update_feeds() {
log_message "Starting per-feed update (FAST ipset restore mode)..."
# Auto-cleanup cache and ipsets for disabled feeds
local enabled_feeds=$(grep '^1|' "$FEEDS_CONFIG" 2>/dev/null | cut -d'|' -f2)
local enabled_feeds
enabled_feeds=$(grep '^1|' "$FEEDS_CONFIG" 2>/dev/null | cut -d'|' -f2)
local cleaned_cache=0
local cleaned_ipsets=0
# Clean cache files
for cache_file in "$CACHE_DIR"/*.raw "$CACHE_DIR"/*-v4.parsed "$CACHE_DIR"/*-v6.parsed "$CACHE_DIR"/*-v4.restore "$CACHE_DIR"/*-v6.restore; do
[ -f "$cache_file" ] || continue
local bn=$(basename "$cache_file")
local bn
bn=$(basename "$cache_file")
local fn="${bn%%.raw}"; fn="${fn%%-v4.parsed}"; fn="${fn%%-v6.parsed}"; fn="${fn%%-v4.restore}"; fn="${fn%%-v6.restore}"
if ! echo "$enabled_feeds" | grep -q "^${fn}$"; then
rm -f "$cache_file" && cleaned_cache=$((cleaned_cache + 1))
fi
done
# Clean ipsets for disabled feeds
while IFS='|' read -r enabled name url type description; do
[[ "$enabled" =~ ^#.*$ ]] && continue
[[ -z "$enabled" ]] && continue
[ "$enabled" = "1" ] && continue
if ipset list "${IPSET_PREFIX}-${name}" >/dev/null 2>&1; then
ipset destroy "${IPSET_PREFIX}-${name}" 2>/dev/null && cleaned_ipsets=$((cleaned_ipsets + 1))
fi
if ipset list "${IPSET_PREFIX}-${name}-v6" >/dev/null 2>&1; then
ipset destroy "${IPSET_PREFIX}-${name}-v6" 2>/dev/null && cleaned_ipsets=$((cleaned_ipsets + 1))
fi
done < "$FEEDS_CONFIG"
[ "$cleaned_cache" -gt 0 ] && log_message " Cleaned $cleaned_cache stale cache files"
[ "$cleaned_ipsets" -gt 0 ] && log_message " Destroyed $cleaned_ipsets stale ipsets"
local total=0 failed=0
@@ -290,36 +413,34 @@ update_feeds() {
c4=$(wc -l < "$v4" 2>/dev/null || echo 0)
[ "$ENABLE_IPV6" = true ] && c6=$(wc -l < "$v6" 2>/dev/null || echo 0)
# FAST IPv4: Use ipset restore
if [ "$c4" -gt 0 ]; then
# Ensure target ipset exists for swap
if ! ipset list "${IPSET_PREFIX}-${name}" >/dev/null 2>&1; then
ipset create "${IPSET_PREFIX}-${name}" hash:net family inet hashsize 4096 maxelem 200000
fi
{
echo "create ${IPSET_PREFIX}-${name}-tmp hash:net family inet hashsize 4096 maxelem 200000"
echo "flush ${IPSET_PREFIX}-${name}-tmp"
while IFS= read -r ip; do
[ -z "$ip" ] && continue
echo "add ${IPSET_PREFIX}-${name}-tmp $ip"
done < "$v4"
echo "swap ${IPSET_PREFIX}-${name} ${IPSET_PREFIX}-${name}-tmp"
echo "destroy ${IPSET_PREFIX}-${name}-tmp"
} > "$CACHE_DIR/${name}-v4.restore"
ipset restore < "$CACHE_DIR/${name}-v4.restore" 2>/dev/null || {
log_message " ⚠ Batch load failed for $name IPv4, using fallback"
ipset flush "${IPSET_PREFIX}-${name}" 2>/dev/null || true
while IFS= read -r ip; do
[ -z "$ip" ] && continue
ipset add "${IPSET_PREFIX}-${name}" "$ip" 2>/dev/null || true
done < "$v4"
}
# FAST IPv4: Use ipset restore (always swap, even if empty, to clear stale entries)
# Ensure target ipset exists for swap
if ! ipset list "${IPSET_PREFIX}-${name}" >/dev/null 2>&1; then
ipset create "${IPSET_PREFIX}-${name}" hash:net family inet hashsize 4096 maxelem 200000
fi
# FAST IPv6: Use ipset restore
if [ "$ENABLE_IPV6" = true ] && [ "$c6" -gt 0 ]; then
{
echo "create ${IPSET_PREFIX}-${name}-tmp hash:net family inet hashsize 4096 maxelem 200000"
echo "flush ${IPSET_PREFIX}-${name}-tmp"
while IFS= read -r ip; do
[ -z "$ip" ] && continue
echo "add ${IPSET_PREFIX}-${name}-tmp $ip"
done < "$v4"
echo "swap ${IPSET_PREFIX}-${name} ${IPSET_PREFIX}-${name}-tmp"
echo "destroy ${IPSET_PREFIX}-${name}-tmp"
} > "$CACHE_DIR/${name}-v4.restore"
ipset restore < "$CACHE_DIR/${name}-v4.restore" 2>/dev/null || {
log_message " ⚠ Batch load failed for $name IPv4, using fallback"
ipset flush "${IPSET_PREFIX}-${name}" 2>/dev/null || true
while IFS= read -r ip; do
[ -z "$ip" ] && continue
ipset add "${IPSET_PREFIX}-${name}" "$ip" 2>/dev/null || true
done < "$v4"
}
# FAST IPv6: Use ipset restore (always swap, even if empty, to clear stale entries)
if [ "$ENABLE_IPV6" = true ]; then
# Ensure target ipset exists for swap
if ! ipset list "${IPSET_PREFIX}-${name}-v6" >/dev/null 2>&1; then
ipset create "${IPSET_PREFIX}-${name}-v6" hash:net family inet6 hashsize 4096 maxelem 200000
@@ -391,6 +512,7 @@ apply_iptables_rules() {
# Add per-feed rules
local line=2
local v6_line=2
while IFS='|' read -r enabled name url type description; do
[[ "$enabled" =~ ^#.*$ ]] && continue
[[ -z "$enabled" ]] && continue
@@ -404,8 +526,10 @@ apply_iptables_rules() {
# IPv6
if [ "$ENABLE_IPV6" = true ]; then
ip6tables -A INPUT -m set --match-set "${IPSET_PREFIX}-${name}-v6" src -m limit --limit 5/min -j LOG --log-prefix "[THREAT-v6:${name}] "
ip6tables -A INPUT -m set --match-set "${IPSET_PREFIX}-${name}-v6" src -j DROP
ip6tables -I INPUT $v6_line -m set --match-set "${IPSET_PREFIX}-${name}-v6" src -m limit --limit 5/min -j LOG --log-prefix "[THREAT-v6:${name}] "
v6_line=$((v6_line + 1))
ip6tables -I INPUT $v6_line -m set --match-set "${IPSET_PREFIX}-${name}-v6" src -j DROP
v6_line=$((v6_line + 1))
fi
done < "$FEEDS_CONFIG"
@@ -429,9 +553,11 @@ apply_iptables_rules() {
setup_iptables_persistence() {
log_message "Setting up iptables persistence..."
# Create systemd service for iptables restore
cat > /etc/systemd/system/iptables-restore.service <<'EOF'
local script_path
script_path=$(readlink -f "$0")
cat > /etc/systemd/system/iptables-restore.service <<EOF
[Unit]
Description=Restore iptables rules
Before=network-pre.target
@@ -440,14 +566,14 @@ Wants=network-pre.target
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/bin/bash -c 'ipset restore -f /etc/sysconfig/ipset 2>/dev/null || ipset restore -f /etc/iptables/ipsets 2>/dev/null || true'
ExecStart=/bin/bash -c 'ipset restore -f /etc/sysconfig/ipset 2>/dev/null || ipset restore -f /etc/iptables/ipsets 2>/dev/null || true; ${script_path} _ensure-ipsets 2>/dev/null || true'
ExecStart=/bin/bash -c 'iptables-restore /etc/sysconfig/iptables 2>/dev/null || iptables-restore /etc/iptables/rules.v4 2>/dev/null || true'
ExecStart=/bin/bash -c 'ip6tables-restore /etc/sysconfig/ip6tables 2>/dev/null || ip6tables-restore /etc/iptables/rules.v6 2>/dev/null || true'
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable iptables-restore.service 2>/dev/null || true
log_message "✓ iptables persistence configured"
@@ -456,7 +582,8 @@ EOF
setup_auto_update() {
[ "$ENABLE_AUTO_UPDATE" = false ] && return
local script=$(readlink -f "$0")
local script
script=$(readlink -f "$0")
cat > /etc/systemd/system/iptables-threat-feeds-update.service <<EOF
[Unit]
@@ -485,6 +612,9 @@ EOF
}
cmd_show_stats() {
local journal_1h
journal_1h=$(journalctl -k --since "1 hour ago" 2>/dev/null | grep '\[THREAT' || true)
echo "Per-Feed Blocking Statistics"
printf "%-25s %10s %10s %12s\n" "FEED" "IPv4" "IPv6" "BLOCKS(1h)"
echo "-------------------------------------------------------------------"
@@ -494,35 +624,45 @@ cmd_show_stats() {
[[ -z "$enabled" ]] && continue
[ "$enabled" != "1" ] && continue
local v4 v6=0 blocks
v4=$(ipset list "${IPSET_PREFIX}-${name}" 2>/dev/null | grep -c '^[0-9.]' || echo 0)
[ "$ENABLE_IPV6" = true ] && v6=$(ipset list "${IPSET_PREFIX}-${name}-v6" 2>/dev/null | grep -c '^[0-9a-fA-F:]' || echo 0)
blocks=$(journalctl -k --since "1 hour ago" 2>/dev/null | grep -c "\[THREAT:${name}\]" || echo 0)
local v4 v6=0 blocks_v4=0 blocks_v6=0
v4=$(ipset list "${IPSET_PREFIX}-${name}" 2>/dev/null | sed -n '/^Members:$/,$p' | tail -n +2 | wc -l)
[ "$ENABLE_IPV6" = true ] && v6=$(ipset list "${IPSET_PREFIX}-${name}-v6" 2>/dev/null | sed -n '/^Members:$/,$p' | tail -n +2 | wc -l)
printf "%-25s %10s %10s %12s\n" "$name" "$v4" "$v6" "$blocks"
if [ -n "$journal_1h" ]; then
blocks_v4=$(printf '%s' "$journal_1h" | grep -c "\[THREAT:${name}\]" 2>/dev/null || true)
blocks_v6=$(printf '%s' "$journal_1h" | grep -c "\[THREAT-v6:${name}\]" 2>/dev/null || true)
fi
local blocks=$(( ${blocks_v4:-0} + ${blocks_v6:-0} ))
printf "%-25s %10s %10s %12s\n" "$name" "${v4:-0}" "${v6:-0}" "$blocks"
done < "$FEEDS_CONFIG"
}
_print_feed() {
local enabled="$1" name="$2" _url="$3" _type="$4" description="$5"
printf "%-10s %-25s %s\n" "$([ "$enabled" = "1" ] && echo "ENABLED" || echo "DISABLED")" "$name" "$description"
}
cmd_list_feeds() {
printf "%-10s %-25s %s\n" "STATUS" "NAME" "DESC"
while IFS='|' read -r enabled name url type description; do
[[ "$enabled" =~ ^#.*$ ]] && continue
[[ -z "$enabled" ]] && continue
printf "%-10s %-25s %s\n" "$([ "$enabled" = "1" ] && echo "ENABLED" || echo "DISABLED")" "$name" "$description"
done < "$FEEDS_CONFIG"
for_each_feed _print_feed
}
cmd_whitelist_add() {
[ -z "$WHITELIST_IP" ] && { echo "Usage: $0 whitelist-add <IP|CIDR>"; exit 1; }
if echo "$WHITELIST_IP" | grep -q ':'; then
ipset add "$WHITELIST_IPSET_V6" "$WHITELIST_IP" 2>/dev/null && \
log_message "✓ Added to IPv6 whitelist: $WHITELIST_IP" || \
{ echo "Failed to add $WHITELIST_IP"; exit 1; }
if [[ "$WHITELIST_IP" == *:* ]]; then
if ipset add "$WHITELIST_IPSET_V6" "$WHITELIST_IP" 2>/dev/null; then
log_message "✓ Added to IPv6 whitelist: $WHITELIST_IP"
else
echo "Failed to add $WHITELIST_IP"; exit 1
fi
else
ipset add "$WHITELIST_IPSET" "$WHITELIST_IP" 2>/dev/null && \
log_message "✓ Added to IPv4 whitelist: $WHITELIST_IP" || \
{ echo "Failed to add $WHITELIST_IP"; exit 1; }
if ipset add "$WHITELIST_IPSET" "$WHITELIST_IP" 2>/dev/null; then
log_message "✓ Added to IPv4 whitelist: $WHITELIST_IP"
else
echo "Failed to add $WHITELIST_IP"; exit 1
fi
fi
ipset save > /etc/sysconfig/ipset 2>/dev/null || ipset save > /etc/iptables/ipsets 2>/dev/null || true
@@ -586,6 +726,7 @@ cmd_whitelist_list() {
cmd_add_feed() {
[ -z "$FEED_NAME" ] || [ -z "$FEED_URL" ] && { echo "Usage: $0 add-feed <NAME> <URL>"; exit 1; }
validate_feed_name "$FEED_NAME" || exit 1
grep -q "^[01]|${FEED_NAME}|" "$FEEDS_CONFIG" 2>/dev/null && { echo "Feed exists"; exit 1; }
echo "1|${FEED_NAME}|${FEED_URL}|plain|Custom: ${FEED_NAME}" >> "$FEEDS_CONFIG"
log_message "✓ Added feed: $FEED_NAME"
@@ -593,15 +734,23 @@ cmd_add_feed() {
cmd_remove_feed() {
[ -z "$FEED_NAME" ] && { echo "Usage: $0 remove-feed <NAME>"; exit 1; }
sed -i "/|${FEED_NAME}|/d" "$FEEDS_CONFIG"
# Remove ipsets and rules
# Remove rules for this feed first (while ipsets still exist)
iptables -D INPUT -m set --match-set "${IPSET_PREFIX}-${FEED_NAME}" src -m limit --limit 5/min -j LOG --log-prefix "[THREAT:${FEED_NAME}] " 2>/dev/null || true
iptables -D INPUT -m set --match-set "${IPSET_PREFIX}-${FEED_NAME}" src -j DROP 2>/dev/null || true
if [ "$ENABLE_IPV6" = true ]; then
ip6tables -D INPUT -m set --match-set "${IPSET_PREFIX}-${FEED_NAME}-v6" src -m limit --limit 5/min -j LOG --log-prefix "[THREAT-v6:${FEED_NAME}] " 2>/dev/null || true
ip6tables -D INPUT -m set --match-set "${IPSET_PREFIX}-${FEED_NAME}-v6" src -j DROP 2>/dev/null || true
fi
# Now safe to destroy ipsets
ipset destroy "${IPSET_PREFIX}-${FEED_NAME}" 2>/dev/null || true
ipset destroy "${IPSET_PREFIX}-${FEED_NAME}-v6" 2>/dev/null || true
# Remove from config last
sed -i "/|${FEED_NAME}|/d" "$FEEDS_CONFIG"
log_message "✓ Removed feed: $FEED_NAME"
log_message "Reapplying rules..."
apply_iptables_rules
}
cmd_enable_feed() {
@@ -622,15 +771,23 @@ cmd_enable_feed() {
cmd_disable_feed() {
[ -z "$FEED_NAME" ] && { echo "Usage: $0 disable-feed <NAME>"; exit 1; }
sed -i "s/^1|${FEED_NAME}|/0|${FEED_NAME}|/" "$FEEDS_CONFIG"
# Destroy ipsets to clear metrics
# Remove rules for this feed first (while ipsets still exist)
iptables -D INPUT -m set --match-set "${IPSET_PREFIX}-${FEED_NAME}" src -m limit --limit 5/min -j LOG --log-prefix "[THREAT:${FEED_NAME}] " 2>/dev/null || true
iptables -D INPUT -m set --match-set "${IPSET_PREFIX}-${FEED_NAME}" src -j DROP 2>/dev/null || true
if [ "$ENABLE_IPV6" = true ]; then
ip6tables -D INPUT -m set --match-set "${IPSET_PREFIX}-${FEED_NAME}-v6" src -m limit --limit 5/min -j LOG --log-prefix "[THREAT-v6:${FEED_NAME}] " 2>/dev/null || true
ip6tables -D INPUT -m set --match-set "${IPSET_PREFIX}-${FEED_NAME}-v6" src -j DROP 2>/dev/null || true
fi
# Now safe to destroy ipsets
ipset destroy "${IPSET_PREFIX}-${FEED_NAME}" 2>/dev/null || true
ipset destroy "${IPSET_PREFIX}-${FEED_NAME}-v6" 2>/dev/null || true
# Mark disabled in config
sed -i "s/^1|${FEED_NAME}|/0|${FEED_NAME}|/" "$FEEDS_CONFIG"
log_message "✓ Disabled: $FEED_NAME"
log_message "Reapplying rules..."
apply_iptables_rules
}
cmd_install() {
@@ -704,19 +861,20 @@ cmd_test_rules() {
echo "IPv6 rules that would be created:"
echo " 1. Whitelist bypass: -I INPUT 1 -m set --match-set $WHITELIST_IPSET_V6 src -j ACCEPT"
local v6_count=0
local v6_line=2
while IFS='|' read -r enabled name url type description; do
[[ "$enabled" =~ ^#.*$ ]] && continue
[[ -z "$enabled" ]] && continue
[ "$enabled" != "1" ] && continue
v6_count=$((v6_count + 1))
echo " $((v6_count * 2)). [${name}] LOG: -A INPUT -m set --match-set ${IPSET_PREFIX}-${name}-v6 src -j LOG"
echo " $((v6_count * 2 + 1)). [${name}] DROP: -A INPUT -m set --match-set ${IPSET_PREFIX}-${name}-v6 src -j DROP"
echo " $v6_line. [${name}] LOG: -I INPUT $v6_line -m set --match-set ${IPSET_PREFIX}-${name}-v6 src -j LOG"
v6_line=$((v6_line + 1))
echo " $v6_line. [${name}] DROP: -I INPUT $v6_line -m set --match-set ${IPSET_PREFIX}-${name}-v6 src -j DROP"
v6_line=$((v6_line + 1))
done < "$FEEDS_CONFIG"
echo ""
echo "Total IPv6 rules: $((v6_count * 2 + 1))"
echo "Total IPv6 rules: $((v6_line - 1))"
fi
echo ""
@@ -726,7 +884,44 @@ cmd_test_rules() {
echo "=========================================="
}
cmd_clean_cache() {
log_message "Cleaning cache for disabled feeds..."
local removed=0
local kept=0
local enabled_feeds
enabled_feeds=$(grep '^1|' "$FEEDS_CONFIG" 2>/dev/null | cut -d'|' -f2)
for cache_file in "$CACHE_DIR"/*.raw "$CACHE_DIR"/*-v4.parsed "$CACHE_DIR"/*-v6.parsed "$CACHE_DIR"/*-v4.restore "$CACHE_DIR"/*-v6.restore; do
[ -f "$cache_file" ] || continue
local bn feed_name
bn=$(basename "$cache_file")
feed_name="${bn%%.raw}"
feed_name="${feed_name%%-v4.parsed}"
feed_name="${feed_name%%-v6.parsed}"
feed_name="${feed_name%%-v4.restore}"
feed_name="${feed_name%%-v6.restore}"
if ! grep -q "^${feed_name}$" <<< "$enabled_feeds"; then
rm -f "$cache_file"
removed=$((removed + 1))
else
kept=$((kept + 1))
fi
done
log_message "Removed $removed cache files, kept $kept active feeds"
}
main() {
# Internal command used by iptables-restore.service at boot
if [ "${1:-}" = "_ensure-ipsets" ]; then
ensure_ipsets_exist
exit 0
fi
parse_args "$@"
case "$COMMAND" in
install) cmd_install ;;
@@ -751,6 +946,7 @@ main() {
whitelist-add) cmd_whitelist_add ;;
whitelist-init) cmd_whitelist_init ;;
whitelist-list) cmd_whitelist_list ;;
clean-cache) cmd_clean_cache ;;
esac
}