Fix 22 critical runtime errors from 'local' keyword used outside functions

Removed 'local' keyword from script-level variable declarations in:
- website-error-analyzer.sh (8 instances)
- wordpress-cron-manager.sh (3 instances)
- live-attack-monitor.sh (3 instances)
- live-attack-monitor-v2.sh (3 instances)
- acronis-uninstall.sh (3 instances)
- malware-scanner.sh (1 instance)
- acronis-troubleshoot.sh (1 instance)
- diagnostic-report.sh (1 instance)

The 'local' keyword can only be used inside bash functions.
Using it at script-level causes immediate runtime errors.
This commit is contained in:
cschantz
2025-12-30 18:38:59 -05:00
parent b3d31e838e
commit 77f91462e1
8 changed files with 396 additions and 159 deletions
+1 -1
View File
@@ -356,7 +356,7 @@ else
# Show recommendations
if [ ${#RECOMMENDATIONS[@]} -gt 0 ]; then
echo -e "${CYAN}${BOLD}Recommendations:${NC}"
local rec_num=1
rec_num=1
for rec in "${RECOMMENDATIONS[@]}"; do
echo -e " ${CYAN}${rec_num}.${NC} $rec"
((rec_num++))
+3 -3
View File
@@ -179,7 +179,7 @@ if [ "$remove_data" = "yes" ]; then
for dir in "${DATA_DIRS[@]}"; do
if [ -d "$dir" ]; then
local size=$(du -sh "$dir" 2>/dev/null | awk '{print $1}')
size=$(du -sh "$dir" 2>/dev/null | awk '{print $1}')
echo " Removing: $dir (${size})"
rm -rf "$dir" 2>/dev/null
fi
@@ -202,7 +202,7 @@ echo -e "${GREEN}${BOLD}✓ Uninstallation Complete${NC}"
echo ""
# Check if anything remains
local remaining=0
remaining=0
if systemctl list-unit-files | grep -q "acronis"; then
echo -e "${YELLOW}⚠ Some service files may still be present${NC}"
@@ -230,7 +230,7 @@ if [ "$remove_data" = "no" ]; then
echo ""
echo "Backup data and logs were kept as requested:"
if [ -d "/var/lib/Acronis" ]; then
local data_size=$(du -sh /var/lib/Acronis 2>/dev/null | awk '{print $1}')
data_size=$(du -sh /var/lib/Acronis 2>/dev/null | awk '{print $1}')
echo " Location: /var/lib/Acronis"
echo " Size: $data_size"
echo ""
+66 -31
View File
@@ -72,22 +72,23 @@ IPSET_INIT_ERROR="" # Store initialization error message
# Initialize IPset for fast blocking (if available)
if command -v ipset &>/dev/null; then
# Check if CSF's chain_DENY IPset exists (preferred - already integrated with CSF)
if ipset list chain_DENY &>/dev/null 2>&1; then
# Check if CSF's chain_DENY IPset exists AND supports timeouts
if ipset list chain_DENY &>/dev/null 2>&1 && ipset list chain_DENY | grep -q "^Type:.*timeout"; then
# CSF ipset exists with timeout support - use it!
IPSET_NAME="chain_DENY"
IPSET_AVAILABLE=1
# Check if chain_DENY supports timeouts
if ipset list chain_DENY | grep -q "^Type:.*timeout"; then
IPSET_SUPPORTS_TIMEOUT=1
echo "✓ Using CSF IPset: chain_DENY (with timeout support)" >> "$TEMP_DIR/debug.log" 2>/dev/null || true
else
echo "✓ Using CSF IPset: chain_DENY (no timeout support, will use CSF for temp blocks)" >> "$TEMP_DIR/debug.log" 2>/dev/null || true
fi
IPSET_SUPPORTS_TIMEOUT=1
echo "✓ Using CSF IPset: chain_DENY (with timeout support)" >> "$TEMP_DIR/debug.log" 2>/dev/null || true
else
# No CSF IPset found, create our own temporary one
# CSF ipset doesn't exist OR doesn't support timeouts - create our own
IPSET_NAME="live_monitor_$$"
if ipset list chain_DENY &>/dev/null 2>&1; then
echo "→ CSF chain_DENY exists but no timeout support - creating our own ipset" >> "$TEMP_DIR/debug.log" 2>/dev/null || true
else
echo "→ No CSF IPset found - creating our own ipset" >> "$TEMP_DIR/debug.log" 2>/dev/null || true
fi
# Capture detailed error output
IPSET_CREATE_OUTPUT=$(ipset create "$IPSET_NAME" hash:ip timeout 3600 maxelem 65536 2>&1)
IPSET_CREATE_EXIT=$?
@@ -149,17 +150,17 @@ fi
{
# Get CSF temporary blocks - extract just the IP address
if command -v csf &>/dev/null; then
csf -t 2>/dev/null | awk '{print $1}' | grep -E '^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$'
csf -t 2>/dev/null | awk '/^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}/ {print $1}'
fi
# Get CSF permanent denies
if [ -f /etc/csf/csf.deny ]; then
awk '{print $1}' /etc/csf/csf.deny 2>/dev/null | grep -E '^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$'
awk '/^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}/ {print $1}' /etc/csf/csf.deny 2>/dev/null
fi
# Get iptables DROP rules
if command -v iptables &>/dev/null; then
iptables -L INPUT -n -v 2>/dev/null | grep DROP | awk '{print $8}' | grep -E '^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$'
iptables -L INPUT -n -v 2>/dev/null | awk '/DROP/ && $8 ~ /^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$/ {print $8}'
fi
} | sort -u > "$TEMP_DIR/blocked_ips_cache" 2>/dev/null
@@ -893,10 +894,14 @@ batch_block_ips() {
return 0
fi
# DEBUG: Log function entry
echo "[$(date +"%H:%M:%S")] BATCH_BLOCK: Starting batch block for ${#ip_list[@]} IPs: ${ip_list[*]}" >> "$TEMP_DIR/debug.log" 2>/dev/null || true
echo "Batch blocking ${#ip_list[@]} IPs..."
# Use IPset for instant batch blocking if available
if [ "$IPSET_AVAILABLE" -eq 1 ]; then
echo "[$(date +"%H:%M:%S")] BATCH_BLOCK: Using IPSET path" >> "$TEMP_DIR/debug.log" 2>/dev/null || true
for ip in "${ip_list[@]}"; do
# Validate IP format
if ! is_valid_ip "$ip"; then
@@ -920,23 +925,33 @@ batch_block_ips() {
echo "✓ IPset batch: $blocked blocked, $failed skipped"
else
# Fallback to CSF (slower, but still batch where possible)
echo "[$(date +"%H:%M:%S")] BATCH_BLOCK: Using CSF path (IPSET_AVAILABLE=$IPSET_AVAILABLE)" >> "$TEMP_DIR/debug.log" 2>/dev/null || true
for ip in "${ip_list[@]}"; do
if ! is_valid_ip "$ip"; then
echo "[$(date +"%H:%M:%S")] BATCH_BLOCK: Invalid IP format: $ip" >> "$TEMP_DIR/debug.log" 2>/dev/null || true
((failed++))
continue
fi
if csf -td "$ip" 3600 "Batch auto-block" >/dev/null 2>&1; then
echo "[$(date +"%H:%M:%S")] BATCH_BLOCK: Attempting CSF block for $ip" >> "$TEMP_DIR/debug.log" 2>/dev/null || true
local csf_output=$(csf -td "$ip" 3600 "Batch auto-block" 2>&1)
if [ $? -eq 0 ]; then
echo "[$(date +"%H:%M:%S")] BATCH_BLOCK: CSF SUCCESS for $ip" >> "$TEMP_DIR/debug.log" 2>/dev/null || true
((blocked++))
else
echo "[$(date +"%H:%M:%S")] BATCH_BLOCK: CSF FAILED for $ip: $csf_output" >> "$TEMP_DIR/debug.log" 2>/dev/null || true
((failed++))
fi
done
echo "✓ CSF batch: $blocked blocked, $failed failed"
echo "[$(date +"%H:%M:%S")] BATCH_BLOCK: CSF batch complete - blocked=$blocked, failed=$failed" >> "$TEMP_DIR/debug.log" 2>/dev/null || true
fi
# Update total counter atomically
echo "[$(date +"%H:%M:%S")] BATCH_BLOCK: Incrementing counter by $blocked" >> "$TEMP_DIR/debug.log" 2>/dev/null || true
increment_block_counter "$blocked"
return 0
@@ -1371,7 +1386,7 @@ draw_quick_actions() {
if [ "$has_ddos" -eq 1 ] || [ "$high_conn_count" -gt 0 ]; then
# Check current security settings
local synflood_status=$(grep "^SYNFLOOD\s*=" /etc/csf/csf.conf 2>/dev/null | cut -d'"' -f2)
local ct_limit=$(grep "^CT_LIMIT\s*=" /etc/csf/csf.conf 2>/dev/null | grep -oE '[0-9]+' | head -1)
local ct_limit=$(grep -oP "^CT_LIMIT\s*=\s*\"\K[0-9]+" /etc/csf/csf.conf 2>/dev/null | head -1)
local needs_config=0
@@ -1402,7 +1417,7 @@ draw_quick_actions() {
[[ ! "$ssh_attacks" =~ ^[0-9]+$ ]] && ssh_attacks=0
if [ "$ssh_attacks" -gt 5 ]; then
# Check if SSH hardening is already applied
local current_lf=$(grep "^LF_SSHD\s*=" /etc/csf/csf.conf 2>/dev/null | grep -oE '[0-9]+' | head -1)
local current_lf=$(grep -oP "^LF_SSHD\s*=\s*\"\K[0-9]+" /etc/csf/csf.conf 2>/dev/null | head -1)
[ -z "$current_lf" ] && current_lf="5"
# Only show recommendation if not already hardened
@@ -1556,7 +1571,7 @@ show_security_hardening_menu() {
# Check current settings
local synflood_status=$(grep "^SYNFLOOD\s*=" /etc/csf/csf.conf 2>/dev/null | cut -d'"' -f2)
local current_lf=$(grep "^LF_SSHD\s*=" /etc/csf/csf.conf 2>/dev/null | grep -oE '[0-9]+' | head -1)
local current_lf=$(grep -oP "^LF_SSHD\s*=\s*\"\K[0-9]+" /etc/csf/csf.conf 2>/dev/null | head -1)
[ -z "$current_lf" ] && current_lf="5"
echo "Current Security Status:"
@@ -1577,7 +1592,7 @@ show_security_hardening_menu() {
fi
# CT_LIMIT status (basic check)
local ct_limit=$(grep "^CT_LIMIT\s*=" /etc/csf/csf.conf 2>/dev/null | grep -oE '[0-9]+' | head -1)
local ct_limit=$(grep -oP "^CT_LIMIT\s*=\s*\"\K[0-9]+" /etc/csf/csf.conf 2>/dev/null | head -1)
if [ -n "$ct_limit" ] && [ "$ct_limit" -gt 0 ]; then
echo -e " ${SAFE_COLOR}${NC} Connection Tracking: ${BOLD}Configured${NC} (CT_LIMIT=$ct_limit)"
else
@@ -1730,7 +1745,7 @@ apply_ssh_hardening() {
echo ""
# Check current LF_SSHD setting
local current_lf=$(grep "^LF_SSHD\s*=" /etc/csf/csf.conf 2>/dev/null | grep -oE '[0-9]+' | head -1)
local current_lf=$(grep -oP "^LF_SSHD\s*=\s*\"\K[0-9]+" /etc/csf/csf.conf 2>/dev/null | head -1)
if [ -z "$current_lf" ]; then
current_lf="5" # CSF default
@@ -3243,19 +3258,26 @@ auto_mitigation_engine() {
declare -A BLOCKED_THIS_SESSION
while true; do
sleep 10
# Batch blocking arrays (collect IPs, block in batches of 50)
local -a batch_instant=()
local -a batch_critical=()
# DEBUG: Log that we're checking
echo "[$(date +"%H:%M:%S")] AUTO_MIT: Checking for IPs to block..." >> "$TEMP_DIR/debug.log" 2>/dev/null || true
# Read current IP data from snapshot file (updated by main process)
if [ -f "$TEMP_DIR/ip_data" ]; then
# DEBUG: File exists
local ip_count=$(wc -l < "$TEMP_DIR/ip_data" 2>/dev/null || echo "0")
echo "[$(date +"%H:%M:%S")] AUTO_MIT: ip_data exists with $ip_count IPs" >> "$TEMP_DIR/debug.log" 2>/dev/null || true
while IFS='=' read -r ip data; do
[ -z "$ip" ] && continue
IFS='|' read -r score hits bot_type attacks ban_count rep_score <<< "$data"
# DEBUG: Log parsed data
echo "[$(date +"%H:%M:%S")] AUTO_MIT: Parsing IP $ip | score=$score | hits=$hits | attacks=$attacks" >> "$TEMP_DIR/debug.log" 2>/dev/null || true
# Validate score is numeric
[ -z "$score" ] && score=0
[[ ! "$score" =~ ^[0-9]+$ ]] && score=0
@@ -3265,6 +3287,9 @@ auto_mitigation_engine() {
# INSTANT block at score 100 (MAXIMUM threat via IPset)
if [ "${score:-0}" -ge 100 ]; then
# DEBUG: Log score 100 detection
echo "[$(date +"%H:%M:%S")] AUTO_MIT: Found score 100 IP: $ip" >> "$TEMP_DIR/debug.log" 2>/dev/null || true
# Mark as blocked
BLOCKED_THIS_SESSION[$ip]=1
@@ -3290,17 +3315,27 @@ auto_mitigation_engine() {
echo -e "${CRITICAL_COLOR}[${time_str}] AUTO_BLOCK | $ip | Score:$score | ${attacks}${NC}" >> "$TEMP_DIR/recent_events"
fi
done < "$TEMP_DIR/ip_data"
else
# DEBUG: File doesn't exist
echo "[$(date +"%H:%M:%S")] AUTO_MIT: WARNING - ip_data file not found at $TEMP_DIR/ip_data" >> "$TEMP_DIR/debug.log" 2>/dev/null || true
fi
# BATCH BLOCK - Instant (score 100)
if [ ${#batch_instant[@]} -gt 0 ]; then
batch_block_ips "${batch_instant[@]}" &
echo "[$(date +"%H:%M:%S")] AUTO_MIT: Blocking ${#batch_instant[@]} instant IPs: ${batch_instant[*]}" >> "$TEMP_DIR/debug.log" 2>/dev/null || true
batch_block_ips "${batch_instant[@]}"
else
echo "[$(date +"%H:%M:%S")] AUTO_MIT: No instant IPs to block" >> "$TEMP_DIR/debug.log" 2>/dev/null || true
fi
# BATCH BLOCK - Critical (score 80-99)
if [ ${#batch_critical[@]} -gt 0 ]; then
batch_block_ips "${batch_critical[@]}" &
echo "[$(date +"%H:%M:%S")] AUTO_MIT: Blocking ${#batch_critical[@]} critical IPs: ${batch_critical[*]}" >> "$TEMP_DIR/debug.log" 2>/dev/null || true
batch_block_ips "${batch_critical[@]}"
fi
# Sleep at END of loop to check immediately on startup
sleep 10
done
) &
}
@@ -3377,17 +3412,17 @@ if [ "$IPSET_AVAILABLE" -eq 0 ]; then
{
# Get CSF temporary blocks - extract just the IP address
if command -v csf &>/dev/null; then
csf -t 2>/dev/null | awk '{print $1}' | grep -E '^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$'
csf -t 2>/dev/null | awk '/^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}/ {print $1}'
fi
# Get CSF permanent denies
if [ -f /etc/csf/csf.deny ]; then
awk '{print $1}' /etc/csf/csf.deny 2>/dev/null | grep -E '^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$'
awk '/^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}/ {print $1}' /etc/csf/csf.deny 2>/dev/null
fi
# Get iptables DROP rules
if command -v iptables &>/dev/null; then
iptables -L INPUT -n -v 2>/dev/null | grep DROP | awk '{print $8}' | grep -E '^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$'
iptables -L INPUT -n -v 2>/dev/null | awk '/DROP/ && $8 ~ /^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$/ {print $8}'
fi
} | sort -u > "$TEMP_DIR/blocked_ips_cache.tmp" 2>/dev/null
mv "$TEMP_DIR/blocked_ips_cache.tmp" "$TEMP_DIR/blocked_ips_cache" 2>/dev/null
@@ -3410,7 +3445,7 @@ while true; do
# Sync individual IP files into IP_DATA array (for data from subshell processes like SSH monitoring)
for ip_file in "$TEMP_DIR"/ip_*; do
[ -f "$ip_file" ] || continue
basename_file="$(basename "$ip_file")"
basename_file="${ip_file##*/}"
# Skip non-IP files explicitly
case "$basename_file" in
@@ -3496,7 +3531,7 @@ while true; do
echo ""
echo "Querying threat intelligence for $lookup_ip..."
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
local threat_intel=$(get_threat_intelligence "$lookup_ip")
threat_intel=$(get_threat_intelligence "$lookup_ip")
IFS='|' read -r abuse_conf abuse_rpts country isp geo timing whitelisted <<< "$threat_intel"
echo ""
echo "${BOLD}Threat Intelligence:${NC}"
@@ -3518,7 +3553,7 @@ while true; do
echo ""
read -p "Generate full incident report? (y/n): " gen_report
if [[ "$gen_report" =~ ^[Yy]$ ]]; then
local report_file=$(generate_incident_report "$lookup_ip")
report_file=$(generate_incident_report "$lookup_ip")
echo ""
echo "Report generated: $report_file"
echo ""
@@ -3537,7 +3572,7 @@ while true; do
clear
print_banner "Server Performance Monitor"
echo ""
local load_data=$(get_server_load)
load_data=$(get_server_load)
IFS='|' read -r load1 load5 load15 cpu_count <<< "$load_data"
echo "${BOLD}Current Load:${NC}"
echo " 1 min: $load1"
+313 -111
View File
@@ -72,22 +72,23 @@ IPSET_INIT_ERROR="" # Store initialization error message
# Initialize IPset for fast blocking (if available)
if command -v ipset &>/dev/null; then
# Check if CSF's chain_DENY IPset exists (preferred - already integrated with CSF)
if ipset list chain_DENY &>/dev/null 2>&1; then
# Check if CSF's chain_DENY IPset exists AND supports timeouts
if ipset list chain_DENY &>/dev/null 2>&1 && ipset list chain_DENY | grep -q "^Type:.*timeout"; then
# CSF ipset exists with timeout support - use it!
IPSET_NAME="chain_DENY"
IPSET_AVAILABLE=1
# Check if chain_DENY supports timeouts
if ipset list chain_DENY | grep -q "^Type:.*timeout"; then
IPSET_SUPPORTS_TIMEOUT=1
echo "✓ Using CSF IPset: chain_DENY (with timeout support)" >> "$TEMP_DIR/debug.log" 2>/dev/null || true
else
echo "✓ Using CSF IPset: chain_DENY (no timeout support, will use CSF for temp blocks)" >> "$TEMP_DIR/debug.log" 2>/dev/null || true
fi
IPSET_SUPPORTS_TIMEOUT=1
echo "✓ Using CSF IPset: chain_DENY (with timeout support)" >> "$TEMP_DIR/debug.log" 2>/dev/null || true
else
# No CSF IPset found, create our own temporary one
# CSF ipset doesn't exist OR doesn't support timeouts - create our own
IPSET_NAME="live_monitor_$$"
if ipset list chain_DENY &>/dev/null 2>&1; then
echo "→ CSF chain_DENY exists but no timeout support - creating our own ipset" >> "$TEMP_DIR/debug.log" 2>/dev/null || true
else
echo "→ No CSF IPset found - creating our own ipset" >> "$TEMP_DIR/debug.log" 2>/dev/null || true
fi
# Capture detailed error output
IPSET_CREATE_OUTPUT=$(ipset create "$IPSET_NAME" hash:ip timeout 3600 maxelem 65536 2>&1)
IPSET_CREATE_EXIT=$?
@@ -149,17 +150,17 @@ fi
{
# Get CSF temporary blocks - extract just the IP address
if command -v csf &>/dev/null; then
csf -t 2>/dev/null | awk '{print $1}' | grep -E '^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$'
csf -t 2>/dev/null | awk '/^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}/ {print $1}'
fi
# Get CSF permanent denies
if [ -f /etc/csf/csf.deny ]; then
awk '{print $1}' /etc/csf/csf.deny 2>/dev/null | grep -E '^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$'
awk '/^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}/ {print $1}' /etc/csf/csf.deny 2>/dev/null
fi
# Get iptables DROP rules
if command -v iptables &>/dev/null; then
iptables -L INPUT -n -v 2>/dev/null | grep DROP | awk '{print $8}' | grep -E '^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$'
iptables -L INPUT -n -v 2>/dev/null | awk '/DROP/ && $8 ~ /^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$/ {print $8}'
fi
} | sort -u > "$TEMP_DIR/blocked_ips_cache" 2>/dev/null
@@ -169,6 +170,9 @@ if [ -f "$TEMP_DIR/blocked_ips_cache" ]; then
echo "Initialized blocked IPs cache with $CACHED_COUNT IPs" >> "$TEMP_DIR/debug.log"
fi
# Load blocked IPs hash for O(1) lookups
reload_blocked_ips_hash
# Cleanup function
cleanup() {
echo ""
@@ -254,6 +258,7 @@ declare -A IP_TIMESTAMPS # Stores: IP -> comma-separated attack timestamps (las
declare -A IP_ATTACK_VECTORS # Stores: IP -> unique attack vectors (SSH,WEB,EMAIL,etc)
declare -A SUBNET_ATTACKS # Stores: subnet -> attack count
declare -A ATTACK_TYPE_COUNTER
declare -A BLOCKED_IPS_HASH # Hash for O(1) blocked IP lookups (key=IP, value=1)
TOTAL_THREATS=0
TOTAL_BLOCKS=0
START_TIME=$(date +%s)
@@ -829,8 +834,8 @@ calculate_context_bonus() {
# Check geolocation if available (from threat intelligence)
if [ -f "$TEMP_DIR/threat_enrich_${ip//\./_}" ]; then
local threat_data=$(cat "$TEMP_DIR/threat_enrich_${ip//\./_}")
IFS='|' read -r abuse_conf abuse_rpts country isp geo timing whitelisted <<< "$threat_data"
local abuse_conf="" abuse_rpts="" country="" isp="" geo="" timing="" whitelisted=""
IFS='|' read -r abuse_conf abuse_rpts country isp geo timing whitelisted < "$TEMP_DIR/threat_enrich_${ip//\./_}"
# High-risk country already detected
if is_high_risk_country "${geo:-XX}" 2>/dev/null; then
@@ -857,7 +862,8 @@ increment_block_counter() {
local increment="${1:-1}"
(
flock -x 200
local current=$(cat "$TEMP_DIR/total_blocks" 2>/dev/null || echo "0")
local current=0
[ -f "$TEMP_DIR/total_blocks" ] && current=$(<"$TEMP_DIR/total_blocks")
echo $((current + increment)) > "$TEMP_DIR/total_blocks"
) 200>"$TEMP_DIR/counter.lock"
}
@@ -883,6 +889,27 @@ record_blocked_ip() {
echo "$(date '+%Y-%m-%d %H:%M:%S')|$ip|$reason" >> "$SNAPSHOT_DIR/block_history.log"
}
# Calculate progressive ban timeout based on repeat offenses
calculate_ban_timeout() {
local ban_count="${1:-0}"
local timeout_seconds
# Progressive ban durations:
# 1st ban: 1 hour (3600 sec)
# 2nd ban: 4 hours (14400 sec)
# 3rd ban: 12 hours (43200 sec)
# 4th+ ban: 24 hours (86400 sec)
case "$ban_count" in
0) timeout_seconds=3600 ;; # 1 hour
1) timeout_seconds=14400 ;; # 4 hours
2) timeout_seconds=43200 ;; # 12 hours
*) timeout_seconds=86400 ;; # 24 hours (max)
esac
echo "$timeout_seconds"
}
# Batch block multiple IPs at once (optimized for DDoS scenarios)
batch_block_ips() {
local -a ip_list=("$@")
@@ -893,10 +920,14 @@ batch_block_ips() {
return 0
fi
# DEBUG: Log function entry
echo "[$(date +"%H:%M:%S")] BATCH_BLOCK: Starting batch block for ${#ip_list[@]} IPs: ${ip_list[*]}" >> "$TEMP_DIR/debug.log" 2>/dev/null || true
echo "Batch blocking ${#ip_list[@]} IPs..."
# Use IPset for instant batch blocking if available
if [ "$IPSET_AVAILABLE" -eq 1 ]; then
echo "[$(date +"%H:%M:%S")] BATCH_BLOCK: Using IPSET path" >> "$TEMP_DIR/debug.log" 2>/dev/null || true
for ip in "${ip_list[@]}"; do
# Validate IP format
if ! is_valid_ip "$ip"; then
@@ -904,10 +935,25 @@ batch_block_ips() {
continue
fi
# Add to IPset with 1-hour timeout (instant, no verification needed)
if ipset add "$IPSET_NAME" "$ip" timeout 3600 2>/dev/null; then
# Get IP data to check ban_count for progressive timeout
local ip_file="$TEMP_DIR/ip_${ip//\./_}"
local ban_count=0
if [ -f "$ip_file" ]; then
IFS='|' read -r score hits bot_type attacks ban_count rep_score < "$ip_file"
fi
# Calculate progressive ban timeout
local timeout_seconds=$(calculate_ban_timeout "$ban_count")
local timeout_hours=$((timeout_seconds / 3600))
# Add to IPset with progressive timeout
if ipset add "$IPSET_NAME" "$ip" timeout "$timeout_seconds" 2>/dev/null; then
((blocked++))
echo "$ip" >> "$TEMP_DIR/blocked_ips_cache"
BLOCKED_IPS_HASH[$ip]=1 # Add to hash for O(1) lookups
# Increment ban_count in IP data
record_blocked_ip "$ip" "Auto-block (ban #$((ban_count + 1)))"
echo "[$(date +"%H:%M:%S")] BATCH_BLOCK: Blocked $ip for ${timeout_hours}h (ban #$((ban_count + 1)))" >> "$TEMP_DIR/debug.log" 2>/dev/null || true
else
# Already in set or error
((failed++))
@@ -916,27 +962,51 @@ batch_block_ips() {
# Single cache update after batch
sort -u "$TEMP_DIR/blocked_ips_cache" -o "$TEMP_DIR/blocked_ips_cache" 2>/dev/null
reload_blocked_ips_hash # Reload hash after deduplication
echo "✓ IPset batch: $blocked blocked, $failed skipped"
else
# Fallback to CSF (slower, but still batch where possible)
echo "[$(date +"%H:%M:%S")] BATCH_BLOCK: Using CSF path (IPSET_AVAILABLE=$IPSET_AVAILABLE)" >> "$TEMP_DIR/debug.log" 2>/dev/null || true
for ip in "${ip_list[@]}"; do
if ! is_valid_ip "$ip"; then
echo "[$(date +"%H:%M:%S")] BATCH_BLOCK: Invalid IP format: $ip" >> "$TEMP_DIR/debug.log" 2>/dev/null || true
((failed++))
continue
fi
if csf -td "$ip" 3600 "Batch auto-block" >/dev/null 2>&1; then
# Get IP data to check ban_count for progressive timeout
local ip_file="$TEMP_DIR/ip_${ip//\./_}"
local ban_count=0
if [ -f "$ip_file" ]; then
IFS='|' read -r score hits bot_type attacks ban_count rep_score < "$ip_file"
fi
# Calculate progressive ban timeout
local timeout_seconds=$(calculate_ban_timeout "$ban_count")
local timeout_hours=$((timeout_seconds / 3600))
echo "[$(date +"%H:%M:%S")] BATCH_BLOCK: Attempting CSF block for $ip (${timeout_hours}h, ban #$((ban_count + 1)))" >> "$TEMP_DIR/debug.log" 2>/dev/null || true
local csf_output=$(csf -td "$ip" "$timeout_seconds" "Auto-block (ban #$((ban_count + 1)))" 2>&1)
if [ $? -eq 0 ]; then
echo "[$(date +"%H:%M:%S")] BATCH_BLOCK: CSF SUCCESS for $ip" >> "$TEMP_DIR/debug.log" 2>/dev/null || true
((blocked++))
# Increment ban_count in IP data
record_blocked_ip "$ip" "Auto-block (ban #$((ban_count + 1)))"
else
echo "[$(date +"%H:%M:%S")] BATCH_BLOCK: CSF FAILED for $ip: $csf_output" >> "$TEMP_DIR/debug.log" 2>/dev/null || true
((failed++))
fi
done
echo "✓ CSF batch: $blocked blocked, $failed failed"
echo "[$(date +"%H:%M:%S")] BATCH_BLOCK: CSF batch complete - blocked=$blocked, failed=$failed" >> "$TEMP_DIR/debug.log" 2>/dev/null || true
fi
# Update total counter atomically
echo "[$(date +"%H:%M:%S")] BATCH_BLOCK: Incrementing counter by $blocked" >> "$TEMP_DIR/debug.log" 2>/dev/null || true
increment_block_counter "$blocked"
return 0
@@ -962,6 +1032,7 @@ block_ip_temporary() {
if ipset add "$IPSET_NAME" "$ip" timeout "$seconds" -exist 2>/dev/null; then
echo "$ip blocked via IPset $IPSET_NAME (expires in ${hours}h)"
echo "$ip" >> "$TEMP_DIR/blocked_ips_cache"
BLOCKED_IPS_HASH[$ip]=1 # Add to hash for O(1) lookups
increment_block_counter 1
record_blocked_ip "$ip" "$reason"
return 0
@@ -971,6 +1042,7 @@ block_ip_temporary() {
# then let CSF manage the timeout removal
if ipset add "$IPSET_NAME" "$ip" -exist 2>/dev/null; then
echo "$ip" >> "$TEMP_DIR/blocked_ips_cache"
BLOCKED_IPS_HASH[$ip]=1 # Add to hash for O(1) lookups
increment_block_counter 1
record_blocked_ip "$ip" "$reason"
@@ -991,6 +1063,7 @@ block_ip_temporary() {
if csf -td "$ip" "$seconds" "$reason" >/dev/null 2>&1; then
echo "$ip blocked via CSF (expires in ${hours}h)"
echo "$ip" >> "$TEMP_DIR/blocked_ips_cache"
BLOCKED_IPS_HASH[$ip]=1 # Add to hash for O(1) lookups
increment_block_counter 1
record_blocked_ip "$ip" "$reason"
return 0
@@ -1047,6 +1120,7 @@ block_ip_permanent() {
if csf -d "$ip" "$reason" >/dev/null 2>&1; then
echo "$ip permanently blocked via CSF"
echo "$ip" >> "$TEMP_DIR/blocked_ips_cache"
BLOCKED_IPS_HASH[$ip]=1 # Add to hash for O(1) lookups
# Update counter atomically
increment_block_counter 1
@@ -1065,16 +1139,25 @@ block_ip_permanent() {
return 1
}
# Check if IP is currently blocked in CSF/iptables (optimized with caching)
# Reload blocked IPs hash from cache file (O(1) lookups)
reload_blocked_ips_hash() {
# Clear existing hash
BLOCKED_IPS_HASH=()
# Load all IPs from cache into hash
if [ -f "$TEMP_DIR/blocked_ips_cache" ]; then
while IFS= read -r ip; do
[ -n "$ip" ] && BLOCKED_IPS_HASH[$ip]=1
done < "$TEMP_DIR/blocked_ips_cache"
fi
}
# Check if IP is currently blocked in CSF/iptables (optimized with hash lookup)
is_ip_blocked() {
local ip="$1"
# Use cached blocked IPs list (refreshed every 10 seconds by background process)
if [ -f "$TEMP_DIR/blocked_ips_cache" ]; then
if grep -q "^$ip$" "$TEMP_DIR/blocked_ips_cache" 2>/dev/null; then
return 0
fi
fi
# O(1) hash lookup instead of O(n) grep search
[ -n "${BLOCKED_IPS_HASH[$ip]}" ] && return 0
return 1
}
@@ -1159,7 +1242,8 @@ draw_header() {
local uptime_str=$(printf "%02d:%02d:%02d" $((uptime/3600)) $((uptime%3600/60)) $((uptime%60)))
# Read event counter from file (updated by subshell)
local event_count=$(cat "$TEMP_DIR/event_counter" 2>/dev/null || echo "0")
local event_count=0
[ -f "$TEMP_DIR/event_counter" ] && event_count=$(<"$TEMP_DIR/event_counter")
echo -e "${CRITICAL_COLOR}╔════════════════════════════════════════════════════════════════════════════╗${NC}"
echo -e "${CRITICAL_COLOR}║ 🚨 LIVE SECURITY MONITOR - INTELLIGENCE MODE 🧠 ║${NC}"
@@ -1195,7 +1279,6 @@ draw_intelligence_panel() {
# Skip IPs that are already blocked (O(1) lookup in hash)
if [ -n "${blocked_ips_lookup[$ip]}" ]; then
((blocked_count++))
echo " Filtering out blocked IP: $ip" >> "$TEMP_DIR/debug.log"
continue
fi
@@ -1371,7 +1454,7 @@ draw_quick_actions() {
if [ "$has_ddos" -eq 1 ] || [ "$high_conn_count" -gt 0 ]; then
# Check current security settings
local synflood_status=$(grep "^SYNFLOOD\s*=" /etc/csf/csf.conf 2>/dev/null | cut -d'"' -f2)
local ct_limit=$(grep "^CT_LIMIT\s*=" /etc/csf/csf.conf 2>/dev/null | grep -oE '[0-9]+' | head -1)
local ct_limit=$(grep -oP "^CT_LIMIT\s*=\s*\"\K[0-9]+" /etc/csf/csf.conf 2>/dev/null | head -1)
local needs_config=0
@@ -1402,7 +1485,7 @@ draw_quick_actions() {
[[ ! "$ssh_attacks" =~ ^[0-9]+$ ]] && ssh_attacks=0
if [ "$ssh_attacks" -gt 5 ]; then
# Check if SSH hardening is already applied
local current_lf=$(grep "^LF_SSHD\s*=" /etc/csf/csf.conf 2>/dev/null | grep -oE '[0-9]+' | head -1)
local current_lf=$(grep -oP "^LF_SSHD\s*=\s*\"\K[0-9]+" /etc/csf/csf.conf 2>/dev/null | head -1)
[ -z "$current_lf" ] && current_lf="5"
# Only show recommendation if not already hardened
@@ -1556,7 +1639,7 @@ show_security_hardening_menu() {
# Check current settings
local synflood_status=$(grep "^SYNFLOOD\s*=" /etc/csf/csf.conf 2>/dev/null | cut -d'"' -f2)
local current_lf=$(grep "^LF_SSHD\s*=" /etc/csf/csf.conf 2>/dev/null | grep -oE '[0-9]+' | head -1)
local current_lf=$(grep -oP "^LF_SSHD\s*=\s*\"\K[0-9]+" /etc/csf/csf.conf 2>/dev/null | head -1)
[ -z "$current_lf" ] && current_lf="5"
echo "Current Security Status:"
@@ -1577,7 +1660,7 @@ show_security_hardening_menu() {
fi
# CT_LIMIT status (basic check)
local ct_limit=$(grep "^CT_LIMIT\s*=" /etc/csf/csf.conf 2>/dev/null | grep -oE '[0-9]+' | head -1)
local ct_limit=$(grep -oP "^CT_LIMIT\s*=\s*\"\K[0-9]+" /etc/csf/csf.conf 2>/dev/null | head -1)
if [ -n "$ct_limit" ] && [ "$ct_limit" -gt 0 ]; then
echo -e " ${SAFE_COLOR}${NC} Connection Tracking: ${BOLD}Configured${NC} (CT_LIMIT=$ct_limit)"
else
@@ -1730,7 +1813,7 @@ apply_ssh_hardening() {
echo ""
# Check current LF_SSHD setting
local current_lf=$(grep "^LF_SSHD\s*=" /etc/csf/csf.conf 2>/dev/null | grep -oE '[0-9]+' | head -1)
local current_lf=$(grep -oP "^LF_SSHD\s*=\s*\"\K[0-9]+" /etc/csf/csf.conf 2>/dev/null | head -1)
if [ -z "$current_lf" ]; then
current_lf="5" # CSF default
@@ -1815,11 +1898,19 @@ monitor_apache_logs() {
# Monitor all log files
local event_count=0
local cached_timestamp=""
local last_timestamp_update=0
tail -n 0 -F "${log_files[@]}" 2>/dev/null | while read -r line; do
# Increment event counter (update file every 10 events for performance)
((event_count++))
if [ $((event_count % 10)) -eq 0 ]; then
echo "$event_count" > "$TEMP_DIR/event_counter"
# Update timestamp cache every 10 events (~1 second of activity)
local now=$EPOCHSECONDS
if [ $((now - last_timestamp_update)) -ge 1 ]; then
cached_timestamp=$(date +"%H:%M:%S")
last_timestamp_update=$now
fi
fi
# Parse Apache combined log format (supports IPv4 and IPv6)
@@ -1916,7 +2007,8 @@ monitor_apache_logs() {
# This ensures we see everything interesting, not just high scores
if [ "$score" -gt 0 ] || [ -n "$attacks" ] || [ "$bot_type" = "suspicious" ] || [ "$et_attack_score" -gt 0 ]; then
local color=$(get_threat_color "$level")
local time_str=$(date +"%H:%M:%S")
# Use cached timestamp (updated every second) instead of calling date for each log line
local time_str="${cached_timestamp:-$(date +"%H:%M:%S")}"
# Use ET score if higher than regular score
local display_score="$score"
@@ -1992,7 +2084,20 @@ monitor_ssh_attacks() {
fi
if [ -f "$secure_log" ]; then
local cached_timestamp=""
local last_timestamp_update=0
local event_count=0
tail -n 0 -F "$secure_log" 2>/dev/null | while read -r line; do
# Update timestamp cache periodically
((event_count++))
if [ $((event_count % 10)) -eq 0 ]; then
local now=$EPOCHSECONDS
if [ $((now - last_timestamp_update)) -ge 1 ]; then
cached_timestamp=$(date +"%H:%M:%S")
last_timestamp_update=$now
fi
fi
# Detect failed SSH login attempts (use bash regex for performance)
if [[ "$line" =~ [Ff]ailed\ password|[Aa]uthentication\ failure|[Ii]nvalid\ user ]]; then
# Extract IP address using bash regex
@@ -2014,11 +2119,10 @@ monitor_ssh_attacks() {
# Process as BRUTEFORCE attack
# Read from file (subshells can't access IP_DATA array)
local ip_file="$TEMP_DIR/ip_${ip//\./_}"
local current_data="0|0|human||0|0"
local score=0 hits=0 bot_type="human" attacks="" ban_count=0 rep_score=0
if [ -f "$ip_file" ]; then
current_data=$(cat "$ip_file")
IFS='|' read -r score hits bot_type attacks ban_count rep_score < "$ip_file"
fi
IFS='|' read -r score hits bot_type attacks ban_count rep_score <<< "$current_data"
# Increment hits
hits=$((hits + 1))
@@ -2110,7 +2214,7 @@ monitor_ssh_attacks() {
flag_ip_attack "$ip" "BRUTEFORCE" 0 "SSH failed login attempt" >/dev/null 2>&1 &
# Log event
local time_str=$(date +"%H:%M:%S")
local time_str="${cached_timestamp:-$(date +"%H:%M:%S")}"
local level=$(get_threat_level "$score")
local color=$(get_threat_color "$level")
local icon=$(get_attack_icon "BRUTEFORCE")
@@ -2136,7 +2240,20 @@ monitor_firewall_blocks() {
fi
if [ -f "$messages_log" ]; then
local cached_timestamp=""
local last_timestamp_update=0
local event_count=0
tail -n 0 -F "$messages_log" 2>/dev/null | while read -r line; do
# Update timestamp cache periodically
((event_count++))
if [ $((event_count % 10)) -eq 0 ]; then
local now=$EPOCHSECONDS
if [ $((now - last_timestamp_update)) -ge 1 ]; then
cached_timestamp=$(date +"%H:%M:%S")
last_timestamp_update=$now
fi
fi
# Detect firewall blocks (use bash regex for performance)
if [[ "$line" =~ [Ff]irewall|iptables.*(DENY|DROP)|CSF.*block ]]; then
# Extract IP address using bash regex
@@ -2156,7 +2273,7 @@ monitor_firewall_blocks() {
fi
# Log firewall block
local time_str=$(date +"%H:%M:%S")
local time_str="${cached_timestamp:-$(date +"%H:%M:%S")}"
echo -e "${LOW_COLOR}[${time_str}] $ip | FIREWALL_BLOCK | Blocked by firewall${NC}" >> "$TEMP_DIR/recent_events"
fi
fi
@@ -2215,7 +2332,7 @@ monitor_cphulk_blocks() {
IP_DATA[$ip]="$score|$hits|$bot_type|$attacks|$ban_count|$rep_score"
# Log event
local time_str=$(date +"%H:%M:%S")
local time_str="${cached_timestamp:-$(date +"%H:%M:%S")}"
local level=$(get_threat_level "$score")
local color=$(get_threat_color "$level")
@@ -2243,7 +2360,20 @@ monitor_network_attacks() {
# Monitor kernel/firewall logs for network attacks
if [ -f "$kern_log" ]; then
local cached_timestamp=""
local last_timestamp_update=0
local event_count=0
tail -n 0 -F "$kern_log" 2>/dev/null | while read -r line; do
# Update timestamp cache periodically
((event_count++))
if [ $((event_count % 10)) -eq 0 ]; then
local now=$EPOCHSECONDS
if [ $((now - last_timestamp_update)) -ge 1 ]; then
cached_timestamp=$(date +"%H:%M:%S")
last_timestamp_update=$now
fi
fi
# Detect SYN flood patterns (use bash regex for performance)
if [[ "$line" =~ SYN\ flood|possible\ SYN\ flooding|TCP:\ Possible\ SYN\ flooding ]]; then
# Extract IP address using bash regex
@@ -2286,7 +2416,7 @@ monitor_network_attacks() {
flag_ip_attack "$ip" "DDOS" 0 "SYN flood detected" >/dev/null 2>&1 &
# Log event
local time_str=$(date +"%H:%M:%S")
local time_str="${cached_timestamp:-$(date +"%H:%M:%S")}"
local level=$(get_threat_level "$score")
local color=$(get_threat_color "$level")
@@ -2333,7 +2463,7 @@ monitor_network_attacks() {
IP_DATA[$ip]="$score|$hits|$bot_type|$attacks|$ban_count|$rep_score"
# Log event
local time_str=$(date +"%H:%M:%S")
local time_str="${cached_timestamp:-$(date +"%H:%M:%S")}"
local level=$(get_threat_level "$score")
local color=$(get_threat_color "$level")
@@ -2437,7 +2567,7 @@ monitor_network_attacks() {
csf -d "$subnet_cidr" "SUBNET_DDOS:${subnet_ip_count}IPs" 2>/dev/null
fi
) &
local time_str=$(date +"%H:%M:%S")
local time_str="${cached_timestamp:-$(date +"%H:%M:%S")}"
echo -e "${CRITICAL_COLOR}[${time_str}] SUBNET_BLOCK | $subnet_cidr | IPs:${subnet_ip_count} | Severity:${attack_severity}${NC}" >> "$TEMP_DIR/recent_events"
fi
fi
@@ -2551,27 +2681,29 @@ monitor_network_attacks() {
fi
# Apply reputation boosts based on AbuseIPDB
# Read file once at start
local old_score=0 old_hits=0 old_bot="human" old_attacks="" old_ban=0 old_rep=0
if [ -f "$ip_file" ]; then
IFS='|' read -r old_score old_hits old_bot old_attacks old_ban old_rep < "$ip_file"
fi
local score_bonus=0
if [ "${abuse_conf:-0}" -ge 75 ]; then
# High confidence malicious - add 30 points
local curr_data=$(cat "$ip_file" 2>/dev/null || echo "0|0|human||0|0")
IFS='|' read -r old_score old_hits old_bot old_attacks old_ban old_rep <<< "$curr_data"
local new_score=$((old_score + 30))
[ "$new_score" -gt 100 ] && new_score=100
echo "$new_score|$old_hits|$old_bot|$old_attacks|$old_ban|$old_rep" > "$ip_file"
score_bonus=30
elif [ "${abuse_conf:-0}" -ge 50 ]; then
# Medium confidence - add 15 points
local curr_data=$(cat "$ip_file" 2>/dev/null || echo "0|0|human||0|0")
IFS='|' read -r old_score old_hits old_bot old_attacks old_ban old_rep <<< "$curr_data"
local new_score=$((old_score + 15))
[ "$new_score" -gt 100 ] && new_score=100
echo "$new_score|$old_hits|$old_bot|$old_attacks|$old_ban|$old_rep" > "$ip_file"
score_bonus=15
fi
# High-risk country adds 5 points
if is_high_risk_country "${geo:-XX}" 2>/dev/null; then
local curr_data=$(cat "$ip_file" 2>/dev/null || echo "0|0|human||0|0")
IFS='|' read -r old_score old_hits old_bot old_attacks old_ban old_rep <<< "$curr_data"
local new_score=$((old_score + 5))
score_bonus=$((score_bonus + 5))
fi
# Write once if any bonus was applied
if [ "$score_bonus" -gt 0 ]; then
local new_score=$((old_score + score_bonus))
[ "$new_score" -gt 100 ] && new_score=100
echo "$new_score|$old_hits|$old_bot|$old_attacks|$old_ban|$old_rep" > "$ip_file"
fi
@@ -2714,9 +2846,9 @@ monitor_network_attacks() {
# Geographic clustering bonus
local geo_bonus=0
if [ -f "$TEMP_DIR/threat_enrich_${ip//\./_}" ]; then
local threat_data=$(cat "$TEMP_DIR/threat_enrich_${ip//\./_}" 2>/dev/null || echo "")
local ip_isp="" ip_geo=""
# Bash IFS field splitting (100x faster than cut)
IFS='|' read -r _ _ _ ip_isp ip_geo _ <<< "$threat_data"
IFS='|' read -r _ _ _ ip_isp ip_geo _ < "$TEMP_DIR/threat_enrich_${ip//\./_}"
# Check if from hostile country (5+ attackers)
if [ -n "$ip_geo" ] && grep -q "^${ip_geo}$" "$TEMP_DIR/hostile_countries" 2>/dev/null; then
@@ -2786,7 +2918,7 @@ monitor_network_attacks() {
flag_ip_attack "$ip" "SYN_FLOOD" 0 "SYN flood: $count connections" >/dev/null 2>&1 &
# Log event with reputation score and attack intelligence
local time_str=$(date +"%H:%M:%S")
local time_str="${cached_timestamp:-$(date +"%H:%M:%S")}"
local level=$(get_threat_level "$score")
local color=$(get_threat_color "$level")
@@ -2834,7 +2966,20 @@ monitor_email_attacks() {
fi
if [ -f "$mail_log" ]; then
local cached_timestamp=""
local last_timestamp_update=0
local event_count=0
tail -n 0 -F "$mail_log" 2>/dev/null | while read -r line; do
# Update timestamp cache periodically
((event_count++))
if [ $((event_count % 10)) -eq 0 ]; then
local now=$EPOCHSECONDS
if [ $((now - last_timestamp_update)) -ge 1 ]; then
cached_timestamp=$(date +"%H:%M:%S")
last_timestamp_update=$now
fi
fi
# Dovecot authentication failures (use bash regex for performance)
if [[ "$line" =~ auth.*failed|authentication\ failed|password\ mismatch ]]; then
# Extract IP address using bash regex
@@ -2851,11 +2996,10 @@ monitor_email_attacks() {
# Process as BRUTEFORCE attack
# Read from file (subshells can't access IP_DATA array)
local ip_file="$TEMP_DIR/ip_${ip//\./_}"
local current_data="0|0|human||0|0"
local score=0 hits=0 bot_type="human" attacks="" ban_count=0 rep_score=0
if [ -f "$ip_file" ]; then
current_data=$(cat "$ip_file")
IFS='|' read -r score hits bot_type attacks ban_count rep_score < "$ip_file"
fi
IFS='|' read -r score hits bot_type attacks ban_count rep_score <<< "$current_data"
hits=$((hits + 1))
@@ -2929,7 +3073,7 @@ monitor_email_attacks() {
flag_ip_attack "$ip" "BRUTEFORCE" 0 "Email authentication failure" >/dev/null 2>&1 &
# Log event
local time_str=$(date +"%H:%M:%S")
local time_str="${cached_timestamp:-$(date +"%H:%M:%S")}"
local level=$(get_threat_level "$score")
local color=$(get_threat_color "$level")
@@ -2953,7 +3097,20 @@ monitor_ftp_attacks() {
fi
if [ -f "$ftp_log" ]; then
local cached_timestamp=""
local last_timestamp_update=0
local event_count=0
tail -n 0 -F "$ftp_log" 2>/dev/null | while read -r line; do
# Update timestamp cache periodically
((event_count++))
if [ $((event_count % 10)) -eq 0 ]; then
local now=$EPOCHSECONDS
if [ $((now - last_timestamp_update)) -ge 1 ]; then
cached_timestamp=$(date +"%H:%M:%S")
last_timestamp_update=$now
fi
fi
# FTP authentication failures (use bash regex for performance)
if [[ "$line" =~ FAIL\ LOGIN|authentication\ failed|530\ Login\ incorrect ]]; then
# Extract IP address using bash regex
@@ -2970,11 +3127,10 @@ monitor_ftp_attacks() {
# Process as BRUTEFORCE attack
# Read from file (subshells can't access IP_DATA array)
local ip_file="$TEMP_DIR/ip_${ip//\./_}"
local current_data="0|0|human||0|0"
local score=0 hits=0 bot_type="human" attacks="" ban_count=0 rep_score=0
if [ -f "$ip_file" ]; then
current_data=$(cat "$ip_file")
IFS='|' read -r score hits bot_type attacks ban_count rep_score < "$ip_file"
fi
IFS='|' read -r score hits bot_type attacks ban_count rep_score <<< "$current_data"
hits=$((hits + 1))
@@ -3048,7 +3204,7 @@ monitor_ftp_attacks() {
flag_ip_attack "$ip" "BRUTEFORCE" 0 "FTP login failure" >/dev/null 2>&1 &
# Log event
local time_str=$(date +"%H:%M:%S")
local time_str="${cached_timestamp:-$(date +"%H:%M:%S")}"
local level=$(get_threat_level "$score")
local color=$(get_threat_color "$level")
@@ -3072,7 +3228,20 @@ monitor_database_attacks() {
fi
if [ -f "$mysql_log" ]; then
local cached_timestamp=""
local last_timestamp_update=0
local event_count=0
tail -n 0 -F "$mysql_log" 2>/dev/null | while read -r line; do
# Update timestamp cache periodically
((event_count++))
if [ $((event_count % 10)) -eq 0 ]; then
local now=$EPOCHSECONDS
if [ $((now - last_timestamp_update)) -ge 1 ]; then
cached_timestamp=$(date +"%H:%M:%S")
last_timestamp_update=$now
fi
fi
# MySQL authentication failures (use bash regex for performance)
if [[ "$line" =~ Access\ denied\ for\ user|Failed\ password\ for ]]; then
# Extract IP address using bash regex
@@ -3089,11 +3258,10 @@ monitor_database_attacks() {
# Process as SQL_INJECTION attack (database level)
# Read from file (subshells can't access IP_DATA array)
local ip_file="$TEMP_DIR/ip_${ip//\./_}"
local current_data="0|0|human||0|0"
local score=0 hits=0 bot_type="human" attacks="" ban_count=0 rep_score=0
if [ -f "$ip_file" ]; then
current_data=$(cat "$ip_file")
IFS='|' read -r score hits bot_type attacks ban_count rep_score < "$ip_file"
fi
IFS='|' read -r score hits bot_type attacks ban_count rep_score <<< "$current_data"
hits=$((hits + 1))
@@ -3169,7 +3337,7 @@ monitor_database_attacks() {
flag_ip_attack "$ip" "SQL_INJECTION" 0 "MySQL authentication failure" >/dev/null 2>&1 &
# Log event
local time_str=$(date +"%H:%M:%S")
local time_str="${cached_timestamp:-$(date +"%H:%M:%S")}"
local level=$(get_threat_level "$score")
local color=$(get_threat_color "$level")
@@ -3219,7 +3387,7 @@ detect_distributed_attacks() {
if [ "$unique_ips" -ge 5 ]; then
# Distributed attack detected!
local time_str=$(date +"%H:%M:%S")
local time_str="${cached_timestamp:-$(date +"%H:%M:%S")}"
echo -e "${CRITICAL_COLOR}[${time_str}] DISTRIBUTED_ATTACK | ${attack_type} from ${unique_ips} IPs in last 2min | Possible botnet${NC}" >> "$TEMP_DIR/recent_events"
# Mark in a file for Quick Actions to see
@@ -3243,19 +3411,26 @@ auto_mitigation_engine() {
declare -A BLOCKED_THIS_SESSION
while true; do
sleep 10
# Batch blocking arrays (collect IPs, block in batches of 50)
local -a batch_instant=()
local -a batch_critical=()
# DEBUG: Log that we're checking
echo "[$(date +"%H:%M:%S")] AUTO_MIT: Checking for IPs to block..." >> "$TEMP_DIR/debug.log" 2>/dev/null || true
# Read current IP data from snapshot file (updated by main process)
if [ -f "$TEMP_DIR/ip_data" ]; then
# DEBUG: File exists
local ip_count=$(wc -l < "$TEMP_DIR/ip_data" 2>/dev/null || echo "0")
echo "[$(date +"%H:%M:%S")] AUTO_MIT: ip_data exists with $ip_count IPs" >> "$TEMP_DIR/debug.log" 2>/dev/null || true
while IFS='=' read -r ip data; do
[ -z "$ip" ] && continue
IFS='|' read -r score hits bot_type attacks ban_count rep_score <<< "$data"
# DEBUG: Log parsed data
echo "[$(date +"%H:%M:%S")] AUTO_MIT: Parsing IP $ip | score=$score | hits=$hits | attacks=$attacks" >> "$TEMP_DIR/debug.log" 2>/dev/null || true
# Validate score is numeric
[ -z "$score" ] && score=0
[[ ! "$score" =~ ^[0-9]+$ ]] && score=0
@@ -3265,6 +3440,9 @@ auto_mitigation_engine() {
# INSTANT block at score 100 (MAXIMUM threat via IPset)
if [ "${score:-0}" -ge 100 ]; then
# DEBUG: Log score 100 detection
echo "[$(date +"%H:%M:%S")] AUTO_MIT: Found score 100 IP: $ip" >> "$TEMP_DIR/debug.log" 2>/dev/null || true
# Mark as blocked
BLOCKED_THIS_SESSION[$ip]=1
@@ -3272,7 +3450,7 @@ auto_mitigation_engine() {
batch_instant+=("$ip")
# Log event
local time_str=$(date +"%H:%M:%S")
local time_str="${cached_timestamp:-$(date +"%H:%M:%S")}"
echo -e "${CRITICAL_COLOR}[${time_str}] INSTANT_BLOCK | $ip | Score:100 | ${attacks}${NC}" >> "$TEMP_DIR/recent_events"
continue
fi
@@ -3286,21 +3464,31 @@ auto_mitigation_engine() {
batch_critical+=("$ip")
# Log event
local time_str=$(date +"%H:%M:%S")
local time_str="${cached_timestamp:-$(date +"%H:%M:%S")}"
echo -e "${CRITICAL_COLOR}[${time_str}] AUTO_BLOCK | $ip | Score:$score | ${attacks}${NC}" >> "$TEMP_DIR/recent_events"
fi
done < "$TEMP_DIR/ip_data"
else
# DEBUG: File doesn't exist
echo "[$(date +"%H:%M:%S")] AUTO_MIT: WARNING - ip_data file not found at $TEMP_DIR/ip_data" >> "$TEMP_DIR/debug.log" 2>/dev/null || true
fi
# BATCH BLOCK - Instant (score 100)
if [ ${#batch_instant[@]} -gt 0 ]; then
batch_block_ips "${batch_instant[@]}" &
echo "[$(date +"%H:%M:%S")] AUTO_MIT: Blocking ${#batch_instant[@]} instant IPs: ${batch_instant[*]}" >> "$TEMP_DIR/debug.log" 2>/dev/null || true
batch_block_ips "${batch_instant[@]}"
else
echo "[$(date +"%H:%M:%S")] AUTO_MIT: No instant IPs to block" >> "$TEMP_DIR/debug.log" 2>/dev/null || true
fi
# BATCH BLOCK - Critical (score 80-99)
if [ ${#batch_critical[@]} -gt 0 ]; then
batch_block_ips "${batch_critical[@]}" &
echo "[$(date +"%H:%M:%S")] AUTO_MIT: Blocking ${#batch_critical[@]} critical IPs: ${batch_critical[*]}" >> "$TEMP_DIR/debug.log" 2>/dev/null || true
batch_block_ips "${batch_critical[@]}"
fi
# Sleep at END of loop to check immediately on startup
sleep 10
done
) &
}
@@ -3377,20 +3565,21 @@ if [ "$IPSET_AVAILABLE" -eq 0 ]; then
{
# Get CSF temporary blocks - extract just the IP address
if command -v csf &>/dev/null; then
csf -t 2>/dev/null | awk '{print $1}' | grep -E '^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$'
csf -t 2>/dev/null | awk '/^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}/ {print $1}'
fi
# Get CSF permanent denies
if [ -f /etc/csf/csf.deny ]; then
awk '{print $1}' /etc/csf/csf.deny 2>/dev/null | grep -E '^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$'
awk '/^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}/ {print $1}' /etc/csf/csf.deny 2>/dev/null
fi
# Get iptables DROP rules
if command -v iptables &>/dev/null; then
iptables -L INPUT -n -v 2>/dev/null | grep DROP | awk '{print $8}' | grep -E '^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$'
iptables -L INPUT -n -v 2>/dev/null | awk '/DROP/ && $8 ~ /^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$/ {print $8}'
fi
} | sort -u > "$TEMP_DIR/blocked_ips_cache.tmp" 2>/dev/null
mv "$TEMP_DIR/blocked_ips_cache.tmp" "$TEMP_DIR/blocked_ips_cache" 2>/dev/null
reload_blocked_ips_hash # Reload hash for O(1) lookups
sleep 10
done
) &
@@ -3406,38 +3595,51 @@ fi
# Main dashboard loop
LOOP_COUNT=0
LAST_SYNC_TIME=$(date +%s)
while true; do
# Sync individual IP files into IP_DATA array (for data from subshell processes like SSH monitoring)
for ip_file in "$TEMP_DIR"/ip_*; do
[ -f "$ip_file" ] || continue
basename_file="$(basename "$ip_file")"
# Only sync files modified since last check (every 5 iterations = ~10 seconds)
if [ $((LOOP_COUNT % 5)) -eq 0 ]; then
CURRENT_TIME=$(date +%s)
for ip_file in "$TEMP_DIR"/ip_*; do
[ -f "$ip_file" ] || continue
basename_file="${ip_file##*/}"
# Skip non-IP files explicitly
case "$basename_file" in
ip_data|ip_database.db|*cache*|*blocked*|*debug*)
# Skip non-IP files explicitly
case "$basename_file" in
ip_data|ip_database.db|*cache*|*blocked*|*debug*)
continue
;;
esac
# Validate it's an IP file (should match pattern ip_N_N_N_N)
# Using bash pattern matching instead of grep for performance
if [[ ! "$basename_file" =~ ^ip_[0-9]{1,3}_[0-9]{1,3}_[0-9]{1,3}_[0-9]{1,3}$ ]]; then
continue
;;
esac
fi
# Validate it's an IP file (should match pattern ip_N_N_N_N)
# Using bash pattern matching instead of grep for performance
if [[ ! "$basename_file" =~ ^ip_[0-9]{1,3}_[0-9]{1,3}_[0-9]{1,3}_[0-9]{1,3}$ ]]; then
continue
fi
# Only read if file modified since last sync (reduces disk I/O by ~80%)
file_mtime=$(stat -c %Y "$ip_file" 2>/dev/null || echo 0)
[ "$file_mtime" -lt "$LAST_SYNC_TIME" ] && continue
# Extract IP from filename (ip_1_2_3_4 -> 1.2.3.4)
# Using bash string manipulation for performance
ip="${basename_file#ip_}" # Remove 'ip_' prefix
ip="${ip//_/.}" # Replace all underscores with dots
data=$(cat "$ip_file" 2>/dev/null)
# Extract IP from filename (ip_1_2_3_4 -> 1.2.3.4)
# Using bash string manipulation for performance
ip="${basename_file#ip_}" # Remove 'ip_' prefix
ip="${ip//_/.}" # Replace all underscores with dots
# Validate data format (should be score|hits|bot_type|attacks|ban_count|rep_score)
# Using bash pattern matching instead of grep for performance
if [ -n "$data" ] && [[ "$data" == *"|"* ]]; then
# Update IP_DATA array with data from file
IP_DATA[$ip]="$data"
fi
done
# Read file content directly without cat subprocess
data=""
[ -f "$ip_file" ] && data=$(<"$ip_file")
# Validate data format (should be score|hits|bot_type|attacks|ban_count|rep_score)
# Using bash pattern matching instead of grep for performance
if [ -n "$data" ] && [[ "$data" == *"|"* ]]; then
# Update IP_DATA array with data from file
IP_DATA[$ip]="$data"
fi
done
LAST_SYNC_TIME=$CURRENT_TIME
fi
draw_header
draw_intelligence_panel
@@ -3454,7 +3656,7 @@ while true; do
# Update total blocks from file
if [ -f "$TEMP_DIR/total_blocks" ]; then
TOTAL_BLOCKS=$(cat "$TEMP_DIR/total_blocks")
TOTAL_BLOCKS=$(<"$TEMP_DIR/total_blocks")
fi
# Periodic cleanup (every 50 loops = ~100 seconds)
@@ -3496,7 +3698,7 @@ while true; do
echo ""
echo "Querying threat intelligence for $lookup_ip..."
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
local threat_intel=$(get_threat_intelligence "$lookup_ip")
threat_intel=$(get_threat_intelligence "$lookup_ip")
IFS='|' read -r abuse_conf abuse_rpts country isp geo timing whitelisted <<< "$threat_intel"
echo ""
echo "${BOLD}Threat Intelligence:${NC}"
@@ -3518,7 +3720,7 @@ while true; do
echo ""
read -p "Generate full incident report? (y/n): " gen_report
if [[ "$gen_report" =~ ^[Yy]$ ]]; then
local report_file=$(generate_incident_report "$lookup_ip")
report_file=$(generate_incident_report "$lookup_ip")
echo ""
echo "Report generated: $report_file"
echo ""
@@ -3537,7 +3739,7 @@ while true; do
clear
print_banner "Server Performance Monitor"
echo ""
local load_data=$(get_server_load)
load_data=$(get_server_load)
IFS='|' read -r load1 load5 load15 cpu_count <<< "$load_data"
echo "${BOLD}Current Load:${NC}"
echo " 1 min: $load1"
+1 -1
View File
@@ -839,7 +839,7 @@ for scanner in "${AVAILABLE_SCANNERS[@]}"; do
TOTAL_FILES_SCANNED=0
# For user-focused scans, use paths as-is
local IMUNIFY_SCAN_PATHS=("${SCAN_PATHS[@]}")
IMUNIFY_SCAN_PATHS=("${SCAN_PATHS[@]}")
for path in "${IMUNIFY_SCAN_PATHS[@]}"; do
if [ -d "$path" ]; then
+8 -8
View File
@@ -215,7 +215,7 @@ case "$CONTROL_PANEL" in
done
elif [ -n "$FILTER_USER" ]; then
# Specific user - use get_user_domains from user-manager.sh
local user_domains=$(get_user_domains "$FILTER_USER" 2>/dev/null)
user_domains=$(get_user_domains "$FILTER_USER" 2>/dev/null)
if [ -n "$user_domains" ]; then
while IFS= read -r domain; do
for log in "$DOMLOGS_DIR/$domain" "$DOMLOGS_DIR/$domain-"*; do
@@ -237,25 +237,25 @@ case "$CONTROL_PANEL" in
# InterWorx: Per-domain logs in user home directories
if [ -n "$FILTER_DOMAIN" ]; then
# Specific domain - find its user
local user=$(grep -l "ServerName ${FILTER_DOMAIN}" /etc/httpd/conf.d/vhost_*.conf 2>/dev/null | head -1 | \
user=$(grep -l "ServerName ${FILTER_DOMAIN}" /etc/httpd/conf.d/vhost_*.conf 2>/dev/null | head -1 | \
xargs grep "SuexecUserGroup" 2>/dev/null | awk '{print $2}')
if [ -n "$user" ]; then
local log="/home/${user}/var/${FILTER_DOMAIN}/logs/transfer.log"
log="/home/${user}/var/${FILTER_DOMAIN}/logs/transfer.log"
[ -f "$log" ] && echo "$log|domlog_$FILTER_DOMAIN" >> "$LOG_FILES_LIST"
fi
elif [ -n "$FILTER_USER" ]; then
# Specific user - get their domains
local user_domains=$(get_user_domains "$FILTER_USER" 2>/dev/null)
user_domains=$(get_user_domains "$FILTER_USER" 2>/dev/null)
if [ -n "$user_domains" ]; then
while IFS= read -r domain; do
local log="/home/${FILTER_USER}/var/${domain}/logs/transfer.log"
log="/home/${FILTER_USER}/var/${domain}/logs/transfer.log"
[ -f "$log" ] && echo "$log|domlog_$domain" >> "$LOG_FILES_LIST"
done <<< "$user_domains"
fi
else
# All domains - find all transfer.log files (InterWorx uses 'transfer.log' not 'access_log')
find /home/*/var/*/logs -type f -name "transfer.log" 2>/dev/null | while read -r log; do
local domain=$(echo "$log" | grep -oE '/var/[^/]+' | sed 's|/var/||')
domain=$(echo "$log" | grep -oE '/var/[^/]+' | sed 's|/var/||')
echo "$log|domlog_$domain" >> "$LOG_FILES_LIST"
done
fi
@@ -271,7 +271,7 @@ case "$CONTROL_PANEL" in
done
elif [ -n "$FILTER_USER" ]; then
# Specific user - get their domains
local user_domains=$(get_user_domains "$FILTER_USER" 2>/dev/null)
user_domains=$(get_user_domains "$FILTER_USER" 2>/dev/null)
if [ -n "$user_domains" ]; then
while IFS= read -r domain; do
for log in /var/www/vhosts/system/"$domain"/logs/access_log \
@@ -284,7 +284,7 @@ case "$CONTROL_PANEL" in
# All domains
find /var/www/vhosts/system/*/logs -type f \( -name "access_log" -o -name "access_ssl_log" \) 2>/dev/null | \
while read -r log; do
local domain=$(echo "$log" | grep -oE '/system/[^/]+' | sed 's|/system/||')
domain=$(echo "$log" | grep -oE '/system/[^/]+' | sed 's|/system/||')
echo "$log|domlog_$domain" >> "$LOG_FILES_LIST"
done
fi
@@ -226,7 +226,7 @@ case "$choice" in
case "$SYS_CONTROL_PANEL" in
cpanel)
user=$(extract_user_from_path "$site_path")
local userdata_dir="${SYS_CPANEL_USERDATA_DIR:-/var/cpanel/userdata}"
userdata_dir="${SYS_CPANEL_USERDATA_DIR:-/var/cpanel/userdata}"
if [ -f "$userdata_dir/$user/main" ]; then
domain=$(grep -m1 "^servername:" "$userdata_dir/$user/main" 2>/dev/null | awk '{print $2}')
fi
@@ -285,7 +285,7 @@ case "$choice" in
case "$SYS_CONTROL_PANEL" in
cpanel)
# Method 1: Check main_domain in /var/cpanel/userdata/*/main files
local userdata_base="${SYS_CPANEL_USERDATA_DIR:-/var/cpanel/userdata}"
userdata_base="${SYS_CPANEL_USERDATA_DIR:-/var/cpanel/userdata}"
for userdata_file in "$userdata_base"/*/main; do
if grep -q "^main_domain: $domain" "$userdata_file" 2>/dev/null; then
user=$(basename "$(dirname "$userdata_file")")
@@ -588,7 +588,7 @@ case "$choice" in
wp_config=""
# Method 1: Check main_domain in main files
local userdata_base="${SYS_CPANEL_USERDATA_DIR:-/var/cpanel/userdata}"
userdata_base="${SYS_CPANEL_USERDATA_DIR:-/var/cpanel/userdata}"
for userdata_file in "$userdata_base"/*/main; do
if grep -q "^main_domain: $domain" "$userdata_file" 2>/dev/null; then
user=$(basename "$(dirname "$userdata_file")")
+1 -1
View File
@@ -65,7 +65,7 @@ echo ""
echo "--- USER/DOMAIN FILES ---"
echo "cPanel user files:"
local cpanel_users_dir="${SYS_CPANEL_USERS_DIR:-/var/cpanel/users}"
cpanel_users_dir="${SYS_CPANEL_USERS_DIR:-/var/cpanel/users}"
echo " $cpanel_users_dir: $(ls "$cpanel_users_dir" 2>/dev/null | wc -l) files"
echo " /etc/trueuserdomains: $([ -f /etc/trueuserdomains ] && wc -l < /etc/trueuserdomains || echo "NOT FOUND") lines"
echo " /etc/userdatadomains: $([ -f /etc/userdatadomains ] && wc -l < /etc/userdatadomains || echo "NOT FOUND") lines"