89ad050222
CRITICAL FIXES (5 issues):
1. email-diagnostics.sh: Fix inverted sender/recipient extraction logic
- Lines 292-303: Corrected pattern matching to properly extract recipients and senders
- Removed inverted grep patterns that were looking for wrong log entry types
2. mail-log-analyzer.sh: Fix string comparison with percent sign
- Line 1184-1186: Properly extract numeric value before '%' character
- Use sed to isolate leading digits for numeric comparison
3. email-diagnostics.sh: Fix malformed grep syntax
- Line 525-527: Corrected grep command structure with -e options
- Changed to -iE with pipe patterns and proper file argument placement
4. mail-log-analyzer.sh: Fix overly broad domain bounce pattern
- Line 749: Changed from "^.*${domain}" to "\b${domain}$"
- Prevents false positives from substring domain matches
5. mail-log-analyzer.sh: Fix undefined TEMP_LOG variable
- Line 860: Changed TEMP_LOG to MAIL_LOG (the actual global variable)
- Added error handling with 2>/dev/null
HIGH SEVERITY FIXES (2 issues):
6. mail-log-analyzer.sh: Fix AWK uninitialized variable
- Lines 1447-1456: Added BEGIN block to initialize print_line = 0
- Prevents first log entries from being incorrectly filtered
7. mail-log-analyzer.sh: Fix overly permissive bounce detection pattern
- Line 247: Changed from "(==|defer)" to more specific pattern
- Prevents false positives from non-bounce defer messages
MODERATE FIXES (3 issues):
8. mail-queue-inspector.sh: Fix queue message count mismatch
- Line 41: Changed head -40 to head -20 to match label
9. deliverability-test.sh: Fix fragile SMTP connection test
- Lines 102-106: Added nc availability check and fallback to bash TCP
- Proper variable quoting and error handling
10. blacklist-check.sh: Replace deprecated host command with dig
- Line 52: Changed from host to dig +short for consistency and timeout control
All scripts pass syntax validation.
Impact: Logic errors fixed, no security issues introduced, all existing functionality preserved.
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
1522 lines
60 KiB
Bash
Executable File
1522 lines
60 KiB
Bash
Executable File
#!/bin/bash
|
|
|
|
################################################################################
|
|
# Mail Log Analyzer - Advanced Email Issue Detection
|
|
################################################################################
|
|
# Purpose: Analyze mail logs for issues and provide actionable recommendations
|
|
# Features:
|
|
# - Spam account detection
|
|
# - Blacklist detection
|
|
# - SPF/DKIM/DMARC failures
|
|
# - Bounce analysis
|
|
# - Rate limiting indicators
|
|
# - Configuration recommendations
|
|
################################################################################
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
|
source "$SCRIPT_DIR/lib/common-functions.sh"
|
|
source "$SCRIPT_DIR/lib/system-detect.sh"
|
|
source "$SCRIPT_DIR/lib/email-functions.sh"
|
|
|
|
# Configuration
|
|
ANALYSIS_HOURS=24
|
|
SPAM_THRESHOLD=100 # Emails per hour considered spam
|
|
REPORT_FILE="/tmp/mail-analysis-$(date +%Y%m%d-%H%M%S).txt"
|
|
|
|
# Issue tracking arrays
|
|
declare -A ISSUES_FOUND
|
|
declare -A RECOMMENDATIONS
|
|
declare -A BLACKLISTED_IPS
|
|
declare -A SPAM_ACCOUNTS
|
|
declare -A AUTH_FAILURES
|
|
declare -A BOUNCE_REASONS
|
|
declare -A HELO_VIOLATIONS
|
|
declare -A AUTH_ATTACK_IPS
|
|
declare -A FROZEN_MESSAGES
|
|
declare -A CONNECTION_FLOODS
|
|
|
|
# NEW: Enhanced tracking arrays
|
|
declare -A DOMAIN_SENT # domain → count of sent messages
|
|
declare -A DOMAIN_DELIVERED # domain → count of delivered messages
|
|
declare -A DOMAIN_BOUNCED # domain → count of bounced messages
|
|
declare -A DOMAIN_ISSUES # domain → list of issues
|
|
declare -A USER_SENT # user@domain → count of sent
|
|
declare -A USER_ISSUES # user@domain → list of issues
|
|
declare -A TOP_RECIPIENTS # recipient@domain → count
|
|
declare -A TOP_SENDERS # sender@domain → count
|
|
declare -A HOURLY_VOLUME # hour → message count
|
|
declare -A ERROR_SAMPLES # error_type → sample log line
|
|
declare -A DELIVERY_TIMES # Track message delivery times
|
|
declare -A REJECTED_REASONS # rejection reason → count
|
|
|
|
# Statistics
|
|
TOTAL_SENT=0
|
|
TOTAL_RECEIVED=0
|
|
TOTAL_BOUNCES=0
|
|
TOTAL_DEFERRED=0
|
|
TOTAL_REJECTED=0
|
|
TOTAL_AUTH_FAILURES=0
|
|
PANIC_LOG_EXISTS=0
|
|
|
|
################################################################################
|
|
# Pattern Detection Functions
|
|
################################################################################
|
|
|
|
# Detect blacklist rejections
|
|
detect_blacklist_issues() {
|
|
local log_file="$1"
|
|
local temp_file="/tmp/blacklist_detections.$$"
|
|
|
|
print_info "Scanning for blacklist rejections..."
|
|
|
|
# Enhanced blacklist detection patterns (from email-diagnostics.sh)
|
|
# Includes explicit RBL keywords, provider-specific patterns, and error codes
|
|
grep -iE "blacklist|block list|RBL|DNSBL|listed in|blocked using|on our block list|S3150|S3140|AS\(48|CS01|local policy|gmail.*(suspicious|reputation|spam|detected).*reputation|gmail.*detected.*suspicious|spamhaus|barracuda|spamcop|sorbs|abuseat|yahoo.*block|yahoo.*reject|aol.*block|aol.*reject|me\.com.*reject|icloud.*reject|mac\.com.*reject|protonmail.*block|protonmail.*reject|pm\.me.*reject|zoho.*block|zoho.*reject|fastmail.*block|fastmail.*reject|outlook.*block|hotmail.*block|live\.com.*block|msn\.com.*block" -- "$log_file" 2>/dev/null > "$temp_file"
|
|
|
|
# ENHANCED: Filter out false positives (same as email-diagnostics.sh)
|
|
# Exclude negation keywords, question contexts, and non-RBL blocks
|
|
if [ -s "$temp_file" ]; then
|
|
local temp_filtered="/tmp/blacklist_detections_filtered.$$"
|
|
grep -vE "not blacklist|not listed|NOT listed|no.*longer|removed from|delisted|successfully delisted|you.*can.*now|check if|if.*server|if your|we block|some.*block|unlike|rarely|are rare|except|not.*block|not.*in|but.*policy|policy.*block|firewall|rate limit|internally|internal.*block|local.*block|rejected.*not.*blacklist|based on sender|blocks are" -- "$temp_file" > "$temp_filtered" 2>/dev/null || true
|
|
|
|
if [ -s "$temp_filtered" ]; then
|
|
mv "$temp_filtered" "$temp_file"
|
|
else
|
|
# All messages were false positives, clear the file
|
|
> "$temp_file"
|
|
fi
|
|
fi
|
|
|
|
if [ -s "$temp_file" ]; then
|
|
local count=$(wc -l < "$temp_file")
|
|
ISSUES_FOUND["blacklist"]=$count
|
|
|
|
# Extract specific blacklists mentioned
|
|
while IFS= read -r line; do
|
|
# Extract recognized blacklist/provider names
|
|
local detected=0
|
|
|
|
if [[ "$line" =~ [Ss]pam[Hh]aus ]]; then
|
|
BLACKLISTED_IPS["Spamhaus"]=$((${BLACKLISTED_IPS["Spamhaus"]:-0} + 1))
|
|
detected=1
|
|
fi
|
|
if [[ "$line" =~ [Ss]pam[Cc]op ]]; then
|
|
BLACKLISTED_IPS["SpamCop"]=$((${BLACKLISTED_IPS["SpamCop"]:-0} + 1))
|
|
detected=1
|
|
fi
|
|
if [[ "$line" =~ [Bb]arracuda ]]; then
|
|
BLACKLISTED_IPS["Barracuda"]=$((${BLACKLISTED_IPS["Barracuda"]:-0} + 1))
|
|
detected=1
|
|
fi
|
|
if [[ "$line" =~ [Gg]mail ]]; then
|
|
BLACKLISTED_IPS["Gmail"]=$((${BLACKLISTED_IPS["Gmail"]:-0} + 1))
|
|
detected=1
|
|
fi
|
|
if [[ "$line" =~ [Mm]icrosoft|[Oo]utlook|[Hh]otmail|[Ll]ive ]]; then
|
|
BLACKLISTED_IPS["Microsoft"]=$((${BLACKLISTED_IPS["Microsoft"]:-0} + 1))
|
|
detected=1
|
|
fi
|
|
if [[ "$line" =~ [Yy]ahoo|[Aa]ol ]]; then
|
|
BLACKLISTED_IPS["Yahoo/AOL"]=$((${BLACKLISTED_IPS["Yahoo/AOL"]:-0} + 1))
|
|
detected=1
|
|
fi
|
|
if [[ "$line" =~ [Ss]orbs ]]; then
|
|
BLACKLISTED_IPS["SORBS"]=$((${BLACKLISTED_IPS["SORBS"]:-0} + 1))
|
|
detected=1
|
|
fi
|
|
if [[ "$line" =~ [Aa]buseat|[Cc]bl ]]; then
|
|
BLACKLISTED_IPS["CBL"]=$((${BLACKLISTED_IPS["CBL"]:-0} + 1))
|
|
detected=1
|
|
fi
|
|
|
|
# Extract IPs being rejected
|
|
if [[ "$line" =~ ([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}) ]]; then
|
|
local ip="${BASH_REMATCH[1]}"
|
|
echo "$line" >> "/tmp/blacklist_ip_${ip//\./_}.log"
|
|
fi
|
|
done < "$temp_file"
|
|
|
|
# Build recommendations based on count
|
|
if [ "$count" -gt 100 ]; then
|
|
RECOMMENDATIONS["blacklist"]="CRITICAL: $count blacklist-related rejections found. Check server IP reputation immediately using 'blacklist-check' tool."
|
|
elif [ "$count" -gt 10 ]; then
|
|
RECOMMENDATIONS["blacklist"]="WARNING: $count blacklist-related rejections. Review using 'email-diagnostics' for detailed analysis."
|
|
else
|
|
RECOMMENDATIONS["blacklist"]="Found $count blacklist-related rejection(s). Use 'blacklist-check' to verify current listing status."
|
|
fi
|
|
fi
|
|
|
|
rm -f "$temp_file"
|
|
}
|
|
|
|
# Detect spam accounts (high volume senders)
|
|
detect_spam_accounts() {
|
|
local log_file="$1"
|
|
local temp_file="/tmp/sender_counts.$$"
|
|
|
|
print_info "Analyzing sender volumes..."
|
|
|
|
# Count messages per sender
|
|
grep "<=" -- "$log_file" 2>/dev/null | \
|
|
grep -oE 'U=[^ ]+' | \
|
|
sort | uniq -c | sort -rn | head -50 > "$temp_file"
|
|
|
|
# Also count by email address
|
|
grep "<=" -- "$log_file" 2>/dev/null | \
|
|
grep -oE '\<[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}\>' | \
|
|
sort | uniq -c | sort -rn | head -50 >> "$temp_file"
|
|
|
|
local hourly_limit=$((SPAM_THRESHOLD * ANALYSIS_HOURS / 24))
|
|
|
|
while read -r count identifier; do
|
|
if [ "$count" -gt "$hourly_limit" ]; then
|
|
# Extract username or email
|
|
if [[ "$identifier" =~ U=([^ ]+) ]]; then
|
|
local user="${BASH_REMATCH[1]}"
|
|
SPAM_ACCOUNTS["$user"]=$count
|
|
elif [[ "$identifier" =~ @ ]]; then
|
|
SPAM_ACCOUNTS["$identifier"]=$count
|
|
fi
|
|
fi
|
|
done < "$temp_file"
|
|
|
|
if [ ${#SPAM_ACCOUNTS[@]} -gt 0 ]; then
|
|
ISSUES_FOUND["spam_accounts"]=${#SPAM_ACCOUNTS[@]}
|
|
RECOMMENDATIONS["spam_accounts"]="Investigate high-volume senders for potential compromise or spam activity."
|
|
fi
|
|
|
|
rm -f "$temp_file"
|
|
}
|
|
|
|
# Detect SPF/DKIM/DMARC failures
|
|
detect_auth_failures() {
|
|
local log_file="$1"
|
|
local temp_file="/tmp/auth_failures.$$"
|
|
|
|
print_info "Checking email authentication failures..."
|
|
|
|
# SPF failures
|
|
grep -iE "(SPF.*fail|sender SPF authorized)" -- "$log_file" 2>/dev/null > "$temp_file"
|
|
if [ -s "$temp_file" ]; then
|
|
AUTH_FAILURES["spf"]=$(wc -l < "$temp_file")
|
|
fi
|
|
|
|
# DKIM failures
|
|
grep -iE "(DKIM.*fail|dkim.*invalid|no DKIM signature)" -- "$log_file" 2>/dev/null >> "$temp_file"
|
|
if grep -q "DKIM" -- "$temp_file"; then
|
|
AUTH_FAILURES["dkim"]=$(grep -c "DKIM" -- "$temp_file")
|
|
fi
|
|
|
|
# DMARC failures
|
|
grep -iE "(DMARC.*fail|dmarc.*reject)" -- "$log_file" 2>/dev/null >> "$temp_file"
|
|
if grep -q "DMARC" -- "$temp_file"; then
|
|
AUTH_FAILURES["dmarc"]=$(grep -c "DMARC" -- "$temp_file")
|
|
fi
|
|
|
|
# Check for recipient servers requesting better authentication
|
|
grep -iE "(requires.*SPF|requires.*DKIM|improve.*authentication|sender verification)" -- "$log_file" 2>/dev/null > /tmp/auth_requests.$$
|
|
if [ -s /tmp/auth_requests.$$ ]; then
|
|
local count=$(wc -l < /tmp/auth_requests.$$)
|
|
AUTH_FAILURES["auth_requested"]=$count
|
|
|
|
# Extract which domains are complaining
|
|
grep -oE '@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}' /tmp/auth_requests.$$ | \
|
|
sed 's/@//' | sort | uniq -c | sort -rn > /tmp/auth_domains.$$
|
|
fi
|
|
|
|
if [ ${#AUTH_FAILURES[@]} -gt 0 ]; then
|
|
ISSUES_FOUND["authentication"]=${#AUTH_FAILURES[@]}
|
|
local rec="Email authentication issues detected: "
|
|
[ -n "${AUTH_FAILURES[spf]}" ] && rec+="SPF failures (${AUTH_FAILURES[spf]}), "
|
|
[ -n "${AUTH_FAILURES[dkim]}" ] && rec+="DKIM failures (${AUTH_FAILURES[dkim]}), "
|
|
[ -n "${AUTH_FAILURES[dmarc]}" ] && rec+="DMARC failures (${AUTH_FAILURES[dmarc]}), "
|
|
RECOMMENDATIONS["authentication"]="${rec%, }. Use SPF/DKIM/DMARC checker tool to verify configuration."
|
|
fi
|
|
|
|
rm -f "$temp_file" /tmp/auth_requests.$$ /tmp/auth_domains.$$
|
|
}
|
|
|
|
# Analyze bounce reasons
|
|
analyze_bounces() {
|
|
local log_file="$1"
|
|
local temp_file="/tmp/bounces.$$"
|
|
|
|
print_info "Analyzing bounce messages..."
|
|
|
|
# Extract bounces (==) and temporary deferrals (defer with reason codes)
|
|
grep -E "==|^[0-9].*defer[ed]*.*reason" -- "$log_file" 2>/dev/null > "$temp_file"
|
|
|
|
if [ -s "$temp_file" ]; then
|
|
# Categorize bounces
|
|
local mailbox_full=$(grep -ciE "(mailbox.*full|quota.*exceed|over quota)" -- "$temp_file")
|
|
local user_unknown=$(grep -ciE "(user.*unknown|no such user|recipient.*reject)" -- "$temp_file")
|
|
local blocked=$(grep -ciE "(blocked|spam|reject.*content)" -- "$temp_file")
|
|
local dns_failure=$(grep -ciE "(DNS|NXDOMAIN|domain.*not.*found)" -- "$temp_file")
|
|
local timeout=$(grep -ciE "(timeout|timed out|connection.*fail)" -- "$temp_file")
|
|
local greylisting=$(grep -ciE "(greylist|grey.*list|try again later|temporarily reject)" -- "$temp_file")
|
|
local tls_failure=$(grep -ciE "(TLS|SSL|certificate)" -- "$temp_file")
|
|
|
|
[ $mailbox_full -gt 0 ] && BOUNCE_REASONS["mailbox_full"]=$mailbox_full
|
|
[ $user_unknown -gt 0 ] && BOUNCE_REASONS["user_unknown"]=$user_unknown
|
|
[ $blocked -gt 0 ] && BOUNCE_REASONS["blocked"]=$blocked
|
|
[ $dns_failure -gt 0 ] && BOUNCE_REASONS["dns_failure"]=$dns_failure
|
|
[ $timeout -gt 0 ] && BOUNCE_REASONS["timeout"]=$timeout
|
|
[ $greylisting -gt 0 ] && BOUNCE_REASONS["greylisting"]=$greylisting
|
|
[ $tls_failure -gt 0 ] && BOUNCE_REASONS["tls_failure"]=$tls_failure
|
|
|
|
TOTAL_BOUNCES=$(wc -l < "$temp_file")
|
|
ISSUES_FOUND["bounces"]=$TOTAL_BOUNCES
|
|
fi
|
|
|
|
rm -f "$temp_file"
|
|
}
|
|
|
|
# Detect rate limiting issues
|
|
detect_rate_limiting() {
|
|
local log_file="$1"
|
|
|
|
print_info "Checking for rate limiting..."
|
|
|
|
# Look for rate limit messages
|
|
local rate_limit_count=$(grep -ciE "(rate limit|too many|throttl|exceed.*limit)" -- "$log_file")
|
|
|
|
if [ $rate_limit_count -gt 0 ]; then
|
|
ISSUES_FOUND["rate_limiting"]=$rate_limit_count
|
|
|
|
# Check which domains are rate limiting
|
|
grep -iE "(rate limit|too many)" -- "$log_file" | \
|
|
grep -oE '@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}' | \
|
|
sed 's/@//' | sort | uniq -c | sort -rn | head -10 > /tmp/rate_limit_domains.$$
|
|
|
|
RECOMMENDATIONS["rate_limiting"]="Server is hitting rate limits. Consider implementing email throttling or spreading out bulk sends."
|
|
fi
|
|
}
|
|
|
|
# Detect configuration issues
|
|
detect_config_issues() {
|
|
local log_file="$1"
|
|
|
|
print_info "Scanning for configuration issues..."
|
|
|
|
# Missing rDNS
|
|
if grep -qiE "(reverse DNS|PTR.*fail|rDNS)" -- "$log_file"; then
|
|
ISSUES_FOUND["rdns"]=1
|
|
RECOMMENDATIONS["rdns"]="Reverse DNS (PTR) issues detected. Ensure PTR record matches server hostname."
|
|
fi
|
|
|
|
# Certificate problems
|
|
local cert_issues=$(grep -ciE "(certificate.*invalid|TLS.*fail|SSL.*error)" -- "$log_file")
|
|
if [ $cert_issues -gt 0 ]; then
|
|
ISSUES_FOUND["certificate"]=$cert_issues
|
|
RECOMMENDATIONS["certificate"]="TLS/SSL certificate issues detected ($cert_issues occurrences). Verify certificate validity."
|
|
fi
|
|
|
|
# Local delivery failures
|
|
local local_fails=$(grep -ciE "(local.*delivery.*fail|unable to deliver locally)" -- "$log_file")
|
|
if [ $local_fails -gt 0 ]; then
|
|
ISSUES_FOUND["local_delivery"]=$local_fails
|
|
RECOMMENDATIONS["local_delivery"]="Local delivery failures detected. Check disk space and mailbox permissions."
|
|
fi
|
|
}
|
|
|
|
# Detect HELO/EHLO violations
|
|
detect_helo_violations() {
|
|
local log_file="$1"
|
|
local temp_file="/tmp/helo_violations.$$"
|
|
|
|
print_info "Checking for HELO/EHLO violations..."
|
|
|
|
# Invalid HELO patterns
|
|
grep -iE "(Invalid HELO|rejected.*HELO|HELO.*reject)" -- "$log_file" 2>/dev/null > "$temp_file"
|
|
|
|
if [ -s "$temp_file" ]; then
|
|
local count=$(wc -l < "$temp_file")
|
|
ISSUES_FOUND["helo_violations"]=$count
|
|
|
|
# Extract IPs with HELO violations
|
|
while IFS= read -r line; do
|
|
if [[ "$line" =~ \[([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})\] ]]; then
|
|
local ip="${BASH_REMATCH[1]}"
|
|
HELO_VIOLATIONS["$ip"]=$((${HELO_VIOLATIONS["$ip"]:-0} + 1))
|
|
fi
|
|
|
|
# Extract HELO names
|
|
if [[ "$line" =~ HELO[[:space:]]+([^[:space:]]+) ]] || [[ "$line" =~ \(([A-Z0-9-]+)\) ]]; then
|
|
local helo_name="${BASH_REMATCH[1]}"
|
|
# Track Windows machine names and other suspicious HELOs
|
|
if [[ "$helo_name" =~ ^WIN- ]] || [[ "$helo_name" =~ ^[0-9.]+$ ]]; then
|
|
echo "$helo_name" >> "/tmp/suspicious_helos.$$"
|
|
fi
|
|
fi
|
|
done < "$temp_file"
|
|
|
|
RECOMMENDATIONS["helo_violations"]="Found $count HELO/EHLO violations. These are often spam bots. Consider tightening HELO checks in Exim configuration."
|
|
fi
|
|
|
|
rm -f "$temp_file"
|
|
}
|
|
|
|
# Detect frozen messages
|
|
detect_frozen_messages() {
|
|
local log_file="$1"
|
|
|
|
print_info "Checking for frozen messages..."
|
|
|
|
# Check for frozen messages in log
|
|
local frozen_count=$(grep -ciE "(frozen|message.*frozen)" -- "$log_file")
|
|
|
|
if [ $frozen_count -gt 0 ]; then
|
|
ISSUES_FOUND["frozen_messages"]=$frozen_count
|
|
|
|
# Try to get actual frozen count from queue
|
|
local mta=$(detect_mta)
|
|
if [ "$mta" = "exim" ]; then
|
|
local queue_frozen=$(exim -bpr 2>/dev/null | grep -c "frozen" || echo "0")
|
|
if [ "$queue_frozen" -gt 0 ]; then
|
|
FROZEN_MESSAGES["current_queue"]=$queue_frozen
|
|
RECOMMENDATIONS["frozen_messages"]="Found $queue_frozen frozen messages in queue. Review with 'exim -bp | grep frozen'. May need manual intervention to delete or retry."
|
|
else
|
|
RECOMMENDATIONS["frozen_messages"]="Found $frozen_count frozen message events in logs. Check mail queue for current frozen messages."
|
|
fi
|
|
else
|
|
RECOMMENDATIONS["frozen_messages"]="Found $frozen_count frozen message events. Check mail queue for stuck messages."
|
|
fi
|
|
fi
|
|
}
|
|
|
|
# Check panic log
|
|
check_panic_log() {
|
|
print_info "Checking for critical errors in panic log..."
|
|
|
|
local panic_log="/var/log/exim_paniclog"
|
|
local alt_panic_log="/var/log/exim/paniclog"
|
|
|
|
# Check if panic log exists and has content
|
|
if [ -f "$panic_log" ] && [ -s "$panic_log" ]; then
|
|
PANIC_LOG_EXISTS=1
|
|
local panic_lines=$(wc -l < "$panic_log")
|
|
ISSUES_FOUND["panic_log"]=$panic_lines
|
|
|
|
# Get recent panic entries
|
|
tail -20 "$panic_log" > "/tmp/recent_panics.$$"
|
|
|
|
RECOMMENDATIONS["panic_log"]="CRITICAL: Panic log exists with $panic_lines entries! Check /var/log/exim_paniclog immediately. This indicates serious mail system problems."
|
|
elif [ -f "$alt_panic_log" ] && [ -s "$alt_panic_log" ]; then
|
|
PANIC_LOG_EXISTS=1
|
|
local panic_lines=$(wc -l < "$alt_panic_log")
|
|
ISSUES_FOUND["panic_log"]=$panic_lines
|
|
RECOMMENDATIONS["panic_log"]="CRITICAL: Panic log exists with $panic_lines entries! Check $alt_panic_log immediately."
|
|
fi
|
|
}
|
|
|
|
# Detect connection flooding
|
|
detect_connection_flooding() {
|
|
local log_file="$1"
|
|
local temp_file="/tmp/connection_floods.$$"
|
|
|
|
print_info "Analyzing connection patterns for flooding..."
|
|
|
|
# Look for rapid connects/disconnects (D=0s or D=1s)
|
|
grep -E "connection.*lost D=[01]s" -- "$log_file" 2>/dev/null > "$temp_file"
|
|
|
|
if [ -s "$temp_file" ]; then
|
|
# Count by IP
|
|
grep -oE '\[([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})\]' -- "$temp_file" | \
|
|
sed 's/\[//;s/\]//' | sort | uniq -c | sort -rn > "/tmp/flood_ips.$$"
|
|
|
|
# Flag IPs with >20 rapid disconnects
|
|
while read count ip; do
|
|
if [ "$count" -gt 20 ]; then
|
|
CONNECTION_FLOODS["$ip"]=$count
|
|
fi
|
|
done < "/tmp/flood_ips.$$"
|
|
|
|
if [ ${#CONNECTION_FLOODS[@]} -gt 0 ]; then
|
|
ISSUES_FOUND["connection_flooding"]=${#CONNECTION_FLOODS[@]}
|
|
RECOMMENDATIONS["connection_flooding"]="Detected connection flooding from ${#CONNECTION_FLOODS[@]} IPs. These IPs are rapidly connecting and disconnecting. Consider rate limiting or blocking."
|
|
fi
|
|
fi
|
|
|
|
rm -f "$temp_file" "/tmp/flood_ips.$$"
|
|
}
|
|
|
|
# Detect SMTP auth brute force attempts
|
|
detect_smtp_auth_attacks() {
|
|
local log_file="$1"
|
|
local temp_file="/tmp/smtp_auth_failures.$$"
|
|
|
|
print_info "Detecting SMTP authentication failures..."
|
|
|
|
# Look for auth failures
|
|
grep -iE "(authenticator.*failed|authentication failed|535.*authentication|failed.*login)" -- "$log_file" 2>/dev/null > "$temp_file"
|
|
|
|
if [ -s "$temp_file" ]; then
|
|
TOTAL_AUTH_FAILURES=$(wc -l < "$temp_file")
|
|
|
|
# Extract IPs with auth failures
|
|
grep -oE '\[([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})\]' -- "$temp_file" | \
|
|
sed 's/\[//;s/\]//' | sort | uniq -c | sort -rn > "/tmp/auth_attack_ips.$$"
|
|
|
|
# Flag IPs with >10 failures (brute force)
|
|
while read count ip; do
|
|
if [ "$count" -gt 10 ]; then
|
|
AUTH_ATTACK_IPS["$ip"]=$count
|
|
fi
|
|
done < "/tmp/auth_attack_ips.$$"
|
|
|
|
if [ ${#AUTH_ATTACK_IPS[@]} -gt 0 ]; then
|
|
ISSUES_FOUND["auth_attacks"]=${#AUTH_ATTACK_IPS[@]}
|
|
RECOMMENDATIONS["auth_attacks"]="SECURITY ALERT: Detected brute force auth attacks from ${#AUTH_ATTACK_IPS[@]} IPs. Total failures: $TOTAL_AUTH_FAILURES. Block these IPs and enable cPHulk or fail2ban."
|
|
elif [ $TOTAL_AUTH_FAILURES -gt 50 ]; then
|
|
ISSUES_FOUND["auth_failures_general"]=$TOTAL_AUTH_FAILURES
|
|
RECOMMENDATIONS["auth_failures_general"]="Detected $TOTAL_AUTH_FAILURES authentication failures. May indicate password issues or attack attempts."
|
|
fi
|
|
fi
|
|
|
|
rm -f "$temp_file" "/tmp/auth_attack_ips.$$"
|
|
}
|
|
|
|
# Detect deferral loops
|
|
detect_deferral_loops() {
|
|
local log_file="$1"
|
|
local temp_file="/tmp/deferrals.$$"
|
|
|
|
print_info "Checking for deferral loops..."
|
|
|
|
# Look for retry timeouts and excessive deferrals
|
|
grep -iE "(retry timeout exceeded|retry time not reached|too many.*defer)" -- "$log_file" 2>/dev/null > "$temp_file"
|
|
|
|
if [ -s "$temp_file" ]; then
|
|
local deferral_loop_count=$(wc -l < "$temp_file")
|
|
|
|
# Extract domains with deferral issues
|
|
grep -oE '@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}' -- "$temp_file" | \
|
|
sed 's/@//' | sort | uniq -c | sort -rn | head -10 > "/tmp/deferral_domains.$$"
|
|
|
|
ISSUES_FOUND["deferral_loops"]=$deferral_loop_count
|
|
RECOMMENDATIONS["deferral_loops"]="Found $deferral_loop_count messages in deferral loops. These will eventually bounce. Check recipient domains and consider manual intervention."
|
|
fi
|
|
|
|
rm -f "$temp_file"
|
|
}
|
|
|
|
#=============================================================================
|
|
# ADDITIONAL DETECTION FUNCTIONS - High Priority
|
|
#=============================================================================
|
|
|
|
# Detect TLS/SSL issues
|
|
detect_tls_issues() {
|
|
local log_file="$1"
|
|
local temp_file="/tmp/tls_issues.$$"
|
|
|
|
print_info "Analyzing TLS/SSL errors..."
|
|
|
|
# Look for TLS errors
|
|
grep -iE "(TLS error|SSL error|SSL_accept|SSL_read|SSL_write|certificate)" -- "$log_file" 2>/dev/null > "$temp_file"
|
|
|
|
if [ -s "$temp_file" ]; then
|
|
local count=$(wc -l < "$temp_file")
|
|
ISSUES_FOUND["tls_errors"]=$count
|
|
|
|
# Categorize TLS errors
|
|
local ssl_eof=$(grep -c "unexpected eof" -- "$temp_file" 2>/dev/null | tr -d '\n' || echo "0")
|
|
local ssl_broken_pipe=$(grep -c "Broken pipe" -- "$temp_file" 2>/dev/null | tr -d '\n' || echo "0")
|
|
local ssl_packet_length=$(grep -c "packet length too long" -- "$temp_file" 2>/dev/null | tr -d '\n' || echo "0")
|
|
local ssl_reset=$(grep -c "Connection reset" -- "$temp_file" 2>/dev/null | tr -d '\n' || echo "0")
|
|
|
|
# Track IPs with TLS issues
|
|
declare -A TLS_IPS
|
|
while IFS= read -r line; do
|
|
if [[ "$line" =~ \[([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})\] ]]; then
|
|
local ip="${BASH_REMATCH[1]}"
|
|
TLS_IPS["$ip"]=$((${TLS_IPS["$ip"]:-0} + 1))
|
|
fi
|
|
done < "$temp_file"
|
|
|
|
# Save top TLS error IPs
|
|
if [ ${#TLS_IPS[@]} -gt 0 ]; then
|
|
for ip in "${!TLS_IPS[@]}"; do
|
|
echo "${TLS_IPS[$ip]} $ip"
|
|
done | sort -rn | head -10 > "/tmp/tls_error_ips.$$"
|
|
fi
|
|
|
|
RECOMMENDATIONS["tls_errors"]="Found $count TLS/SSL errors. Most common: EOF ($ssl_eof), Broken pipe ($ssl_broken_pipe), Packet length ($ssl_packet_length). These are usually scanner/bot probes and can be safely ignored unless affecting legitimate traffic."
|
|
fi
|
|
|
|
rm -f "$temp_file"
|
|
}
|
|
|
|
# Detect message size rejections
|
|
detect_size_rejections() {
|
|
local log_file="$1"
|
|
local temp_file="/tmp/size_rejections.$$"
|
|
|
|
print_info "Checking for message size rejections..."
|
|
|
|
# Look for size-related rejections
|
|
grep -iE "(message too big|size exceed|quota exceed|over.*quota)" -- "$log_file" 2>/dev/null > "$temp_file"
|
|
|
|
if [ -s "$temp_file" ]; then
|
|
local count=$(wc -l < "$temp_file")
|
|
ISSUES_FOUND["size_rejections"]=$count
|
|
|
|
# Extract affected users/domains
|
|
grep -oE '[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}' -- "$temp_file" | \
|
|
sort | uniq -c | sort -rn | head -10 > "/tmp/size_reject_users.$$"
|
|
|
|
RECOMMENDATIONS["size_rejections"]="Found $count message size rejections. Users are trying to send files that exceed size limits. Educate users about limits and suggest file-sharing alternatives (Dropbox, Google Drive, etc.)."
|
|
fi
|
|
|
|
rm -f "$temp_file"
|
|
}
|
|
|
|
# Detect routing/forwarding loops
|
|
detect_routing_loops() {
|
|
local log_file="$1"
|
|
local temp_file="/tmp/routing_loops.$$"
|
|
|
|
print_info "Detecting mail routing loops..."
|
|
|
|
# Look for loop indicators
|
|
grep -iE "(too many.*Received|routing loop|maximum hops|mail loop)" -- "$log_file" 2>/dev/null > "$temp_file"
|
|
|
|
if [ -s "$temp_file" ]; then
|
|
local count=$(wc -l < "$temp_file")
|
|
ISSUES_FOUND["routing_loops"]=$count
|
|
|
|
# Extract affected addresses
|
|
grep -oE '[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}' -- "$temp_file" | \
|
|
sort | uniq -c | sort -rn | head -10 > "/tmp/loop_addresses.$$"
|
|
|
|
RECOMMENDATIONS["routing_loops"]="Found $count routing loops. These are caused by misconfigured email forwards (.forward files, auto-forwards, etc.). Check forwarding rules for affected addresses and break the loops."
|
|
fi
|
|
|
|
rm -f "$temp_file"
|
|
}
|
|
|
|
#=============================================================================
|
|
# COMPREHENSIVE ANALYSIS FUNCTIONS - Domain & User Level
|
|
#=============================================================================
|
|
|
|
# Analyze per-domain activity and issues
|
|
analyze_domain_performance() {
|
|
local log_file="$1"
|
|
|
|
print_info "Analyzing domain-level performance..."
|
|
|
|
# Track sent messages per domain
|
|
grep "<=" -- "$log_file" 2>/dev/null | while IFS= read -r line; do
|
|
# Extract sender domain from F=<user@domain>
|
|
if [[ "$line" =~ F=\<[^@]+@([a-zA-Z0-9.-]+)\> ]]; then
|
|
local domain="${BASH_REMATCH[1]}"
|
|
echo "$domain" >> /tmp/domains_sent.$$
|
|
fi
|
|
done
|
|
|
|
# Track delivered messages per domain
|
|
grep "=>" -- "$log_file" 2>/dev/null | while IFS= read -r line; do
|
|
# Extract recipient domain
|
|
if [[ "$line" =~ @([a-zA-Z0-9.-]+\.[a-zA-Z]{2,}) ]]; then
|
|
local domain="${BASH_REMATCH[1]}"
|
|
echo "$domain" >> /tmp/domains_delivered.$$
|
|
fi
|
|
done
|
|
|
|
# Track bounced messages per domain
|
|
grep "==" -- "$log_file" 2>/dev/null | while IFS= read -r line; do
|
|
if [[ "$line" =~ @([a-zA-Z0-9.-]+\.[a-zA-Z]{2,}) ]]; then
|
|
local domain="${BASH_REMATCH[1]}"
|
|
echo "$domain" >> /tmp/domains_bounced.$$
|
|
|
|
# Capture bounce reason
|
|
if [[ "$line" =~ (550|551|552|553|554)[[:space:]](.{1,80}) ]]; then
|
|
local reason="${BASH_REMATCH[2]}"
|
|
echo "$domain|$reason" >> /tmp/domain_bounce_reasons.$$
|
|
fi
|
|
fi
|
|
done
|
|
|
|
# Summarize domains
|
|
if [ -f /tmp/domains_sent.$$ ]; then
|
|
sort /tmp/domains_sent.$$ | uniq -c | sort -rn | head -20 > /tmp/top_sending_domains.$$
|
|
fi
|
|
|
|
if [ -f /tmp/domains_delivered.$$ ]; then
|
|
sort /tmp/domains_delivered.$$ | uniq -c | sort -rn | head -20 > /tmp/top_recipient_domains.$$
|
|
fi
|
|
|
|
if [ -f /tmp/domains_bounced.$$ ]; then
|
|
sort /tmp/domains_bounced.$$ | uniq -c | sort -rn | head -20 > /tmp/top_bouncing_domains.$$
|
|
fi
|
|
}
|
|
|
|
# Analyze per-user activity
|
|
analyze_user_activity() {
|
|
local log_file="$1"
|
|
|
|
print_info "Analyzing user-level activity..."
|
|
|
|
# Track messages sent per user
|
|
grep "<=" -- "$log_file" 2>/dev/null | while IFS= read -r line; do
|
|
# Extract full email address
|
|
if [[ "$line" =~ F=\<([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})\> ]]; then
|
|
local email="${BASH_REMATCH[1]}"
|
|
echo "$email" >> /tmp/users_sent.$$
|
|
fi
|
|
|
|
# Also track U= (authenticated user)
|
|
if [[ "$line" =~ U=([^[:space:]]+) ]]; then
|
|
local user="${BASH_REMATCH[1]}"
|
|
echo "$user" >> /tmp/authenticated_users.$$
|
|
fi
|
|
done
|
|
|
|
# Summarize top senders
|
|
if [ -f /tmp/users_sent.$$ ]; then
|
|
sort /tmp/users_sent.$$ | uniq -c | sort -rn | head -20 > /tmp/top_senders.$$
|
|
fi
|
|
|
|
if [ -f /tmp/authenticated_users.$$ ]; then
|
|
sort /tmp/authenticated_users.$$ | uniq -c | sort -rn | head -20 > /tmp/top_authenticated_users.$$
|
|
fi
|
|
}
|
|
|
|
# Analyze hourly distribution
|
|
analyze_hourly_patterns() {
|
|
local log_file="$1"
|
|
|
|
print_info "Analyzing hourly distribution..."
|
|
|
|
# Extract hour from each message
|
|
awk '/^[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:/ {
|
|
split($2, time, ":")
|
|
hour = time[1]
|
|
print hour
|
|
}' "$log_file" 2>/dev/null | sort | uniq -c | sort -k2 -n > /tmp/hourly_volume.$$
|
|
}
|
|
|
|
# Analyze rejection reasons in detail
|
|
analyze_rejection_details() {
|
|
local log_file="$1"
|
|
|
|
print_info "Analyzing rejection reasons..."
|
|
|
|
# Extract detailed rejection messages
|
|
grep -iE "(rejected|denied)" -- "$log_file" 2>/dev/null | head -50 > /tmp/rejection_samples.$$
|
|
|
|
# Categorize rejections
|
|
grep -i "rejected" -- "$log_file" 2>/dev/null | while IFS= read -r line; do
|
|
case "$line" in
|
|
*"Relay access denied"*)
|
|
echo "Relay access denied" >> /tmp/rejection_categories.$$
|
|
;;
|
|
*"Sender address rejected"*)
|
|
echo "Invalid sender" >> /tmp/rejection_categories.$$
|
|
;;
|
|
*"Recipient address rejected"*)
|
|
echo "Invalid recipient" >> /tmp/rejection_categories.$$
|
|
;;
|
|
*"Greylisted"*)
|
|
echo "Greylisted" >> /tmp/rejection_categories.$$
|
|
;;
|
|
*"Policy"*)
|
|
echo "Policy violation" >> /tmp/rejection_categories.$$
|
|
;;
|
|
*"Spam"*)
|
|
echo "Spam filter" >> /tmp/rejection_categories.$$
|
|
;;
|
|
*)
|
|
echo "Other rejection" >> /tmp/rejection_categories.$$
|
|
;;
|
|
esac
|
|
done
|
|
|
|
if [ -f /tmp/rejection_categories.$$ ]; then
|
|
sort /tmp/rejection_categories.$$ | uniq -c | sort -rn > /tmp/rejection_summary.$$
|
|
fi
|
|
}
|
|
|
|
# Track message delivery success rate per domain
|
|
calculate_domain_success_rates() {
|
|
local log_file="$1"
|
|
|
|
print_info "Calculating domain success rates..."
|
|
|
|
# For each domain, calculate: (delivered / (delivered + bounced)) * 100
|
|
if [ -f /tmp/top_recipient_domains.$$ ]; then
|
|
while read count domain; do
|
|
local delivered=$count
|
|
# Use word boundary to match exact domain, not substrings
|
|
local bounced=$(grep -c "\b${domain}$" /tmp/domains_bounced.$$ 2>/dev/null || echo "0")
|
|
local total=$((delivered + bounced))
|
|
|
|
if [ $total -gt 0 ]; then
|
|
local success_rate=$(( (delivered * 100) / total ))
|
|
echo "$success_rate%|$domain|$delivered/$total" >> /tmp/domain_success_rates.$$
|
|
fi
|
|
done < /tmp/top_recipient_domains.$$
|
|
|
|
sort -t'|' -k1 -rn /tmp/domain_success_rates.$$ | head -20 > /tmp/domain_success_rates_sorted.$$
|
|
fi
|
|
}
|
|
|
|
# Capture error message samples for troubleshooting
|
|
capture_error_samples() {
|
|
local log_file="$1"
|
|
|
|
print_info "Capturing error message samples..."
|
|
|
|
# Sample of each error type for user troubleshooting
|
|
grep -i "SPF.*fail" -- "$log_file" 2>/dev/null | head -3 > /tmp/sample_spf_failures.$$
|
|
grep -i "DKIM.*fail" -- "$log_file" 2>/dev/null | head -3 > /tmp/sample_dkim_failures.$$
|
|
grep -i "blacklist" -- "$log_file" 2>/dev/null | head -3 > /tmp/sample_blacklist.$$
|
|
grep -i "quota.*exceed" -- "$log_file" 2>/dev/null | head -3 > /tmp/sample_quota.$$
|
|
grep -i "user.*unknown" -- "$log_file" 2>/dev/null | head -3 > /tmp/sample_unknown_user.$$
|
|
grep -i "connection.*timeout" -- "$log_file" 2>/dev/null | head -3 > /tmp/sample_timeout.$$
|
|
}
|
|
|
|
# Gather general statistics
|
|
gather_statistics() {
|
|
local log_file="$1"
|
|
|
|
print_info "Gathering statistics..."
|
|
|
|
# Count sent messages
|
|
TOTAL_SENT=$(grep -c "<=" -- "$log_file" 2>/dev/null | tr -d '\n' || echo "0")
|
|
|
|
# Count received messages
|
|
TOTAL_RECEIVED=$(grep -c "=>" -- "$log_file" 2>/dev/null | tr -d '\n' || echo "0")
|
|
|
|
# Count deferrals
|
|
TOTAL_DEFERRED=$(grep -c "defer" -- "$log_file" 2>/dev/null | tr -d '\n' || echo "0")
|
|
|
|
# Count rejections
|
|
TOTAL_REJECTED=$(grep -cE "(reject|denied)" -- "$log_file" 2>/dev/null | tr -d '\n' || echo "0")
|
|
}
|
|
|
|
################################################################################
|
|
# Reporting Functions
|
|
################################################################################
|
|
|
|
# Display summary header
|
|
display_summary() {
|
|
print_banner "Mail Log Analysis Report"
|
|
|
|
echo -e "${BOLD}Analysis Period:${NC} Last $ANALYSIS_HOURS hours"
|
|
echo -e "${BOLD}Log File:${NC} $MAIL_LOG"
|
|
echo -e "${BOLD}Analysis Time:${NC} $(date)"
|
|
echo ""
|
|
|
|
# Overall statistics
|
|
echo -e "${CYAN}${BOLD}═══════════════════════════════════════════════════${NC}"
|
|
echo -e "${CYAN}${BOLD} OVERALL STATISTICS${NC}"
|
|
echo -e "${CYAN}${BOLD}═══════════════════════════════════════════════════${NC}"
|
|
echo ""
|
|
printf " %-30s %s\n" "Messages Sent:" "$TOTAL_SENT"
|
|
printf " %-30s %s\n" "Messages Delivered:" "$TOTAL_RECEIVED"
|
|
printf " %-30s %s\n" "Bounces/Deferrals:" "$TOTAL_BOUNCES"
|
|
printf " %-30s %s\n" "Deferred:" "$TOTAL_DEFERRED"
|
|
printf " %-30s %s\n" "Rejected:" "$TOTAL_REJECTED"
|
|
echo ""
|
|
}
|
|
|
|
# Display issues found
|
|
display_issues() {
|
|
if [ ${#ISSUES_FOUND[@]} -eq 0 ]; then
|
|
print_success "No significant issues detected!"
|
|
echo ""
|
|
return
|
|
fi
|
|
|
|
echo -e "${RED}${BOLD}═══════════════════════════════════════════════════${NC}"
|
|
echo -e "${RED}${BOLD} ISSUES DETECTED${NC}"
|
|
echo -e "${RED}${BOLD}═══════════════════════════════════════════════════${NC}"
|
|
echo ""
|
|
|
|
# Blacklist issues
|
|
if [ -n "${ISSUES_FOUND[blacklist]}" ]; then
|
|
echo -e "${RED}${BOLD}🚫 BLACKLIST DETECTIONS (${ISSUES_FOUND[blacklist]} occurrences)${NC}"
|
|
echo ""
|
|
|
|
# Show which blacklists
|
|
if [ ${#BLACKLISTED_IPS[@]} -gt 0 ]; then
|
|
echo " Blacklists mentioned:"
|
|
for bl in "${!BLACKLISTED_IPS[@]}"; do
|
|
printf " - %-40s %d times\n" "$bl" "${BLACKLISTED_IPS[$bl]}"
|
|
done
|
|
echo ""
|
|
fi
|
|
|
|
# Show timeline - first and last occurrence
|
|
if [ -f "/tmp/blacklist_detections.$$" ]; then
|
|
local first_occurrence=$(head -1 "/tmp/blacklist_detections.$$" | awk '{print $1, $2}')
|
|
local last_occurrence=$(tail -1 "/tmp/blacklist_detections.$$" | awk '{print $1, $2}')
|
|
|
|
echo " Timeline:"
|
|
echo " First seen: $first_occurrence"
|
|
echo " Last seen: $last_occurrence"
|
|
|
|
# Check if recent (within last hour of log)
|
|
local log_end=$(tail -1 "$MAIL_LOG" 2>/dev/null | awk '{print $1, $2}')
|
|
if [ "$last_occurrence" == "$log_end" ] || [ -z "$last_occurrence" ]; then
|
|
echo -e " ${RED}Status: STILL OCCURRING ⚠️${NC}"
|
|
else
|
|
echo -e " ${GREEN}Status: May have stopped (last seen earlier in log)${NC}"
|
|
fi
|
|
echo ""
|
|
fi
|
|
|
|
# Show which domains/users triggered it (top 5)
|
|
if [ -f "/tmp/blacklist_detections.$$" ]; then
|
|
echo " Affected senders (top 5):"
|
|
grep -oE 'F=<[^>]+>' "/tmp/blacklist_detections.$$" 2>/dev/null | \
|
|
sed 's/F=<//; s/>//' | sort | uniq -c | sort -rn | head -5 | \
|
|
while read count sender; do
|
|
printf " - %-45s %d times\n" "$sender" "$count"
|
|
done
|
|
echo ""
|
|
fi
|
|
|
|
echo -e " ${YELLOW}Action Required:${NC} ${RECOMMENDATIONS[blacklist]}"
|
|
echo ""
|
|
fi
|
|
|
|
# Spam accounts
|
|
if [ -n "${ISSUES_FOUND[spam_accounts]}" ]; then
|
|
echo -e "${RED}${BOLD}⚠️ HIGH-VOLUME SENDERS (${ISSUES_FOUND[spam_accounts]} accounts)${NC}"
|
|
echo ""
|
|
echo " Top senders exceeding threshold:"
|
|
local count=0
|
|
for account in "${!SPAM_ACCOUNTS[@]}"; do
|
|
printf " - %-50s %d messages\n" "$account" "${SPAM_ACCOUNTS[$account]}"
|
|
((count++))
|
|
[ $count -ge 10 ] && break
|
|
done
|
|
echo ""
|
|
echo -e " ${YELLOW}Action Required:${NC} ${RECOMMENDATIONS[spam_accounts]}"
|
|
echo ""
|
|
fi
|
|
|
|
# Authentication failures
|
|
if [ -n "${ISSUES_FOUND[authentication]}" ]; then
|
|
echo -e "${RED}${BOLD}🔐 EMAIL AUTHENTICATION ISSUES${NC}"
|
|
echo ""
|
|
[ -n "${AUTH_FAILURES[spf]}" ] && echo " SPF Failures: ${AUTH_FAILURES[spf]}"
|
|
[ -n "${AUTH_FAILURES[dkim]}" ] && echo " DKIM Failures: ${AUTH_FAILURES[dkim]}"
|
|
[ -n "${AUTH_FAILURES[dmarc]}" ] && echo " DMARC Failures: ${AUTH_FAILURES[dmarc]}"
|
|
echo ""
|
|
if [ -f /tmp/auth_domains.$$ ]; then
|
|
echo " Domains requesting better authentication:"
|
|
head -5 /tmp/auth_domains.$$ | while read count domain; do
|
|
printf " - %-40s %d times\n" "$domain" "$count"
|
|
done
|
|
echo ""
|
|
fi
|
|
echo -e " ${YELLOW}Action Required:${NC} ${RECOMMENDATIONS[authentication]}"
|
|
echo ""
|
|
fi
|
|
|
|
# Bounce analysis
|
|
if [ ${#BOUNCE_REASONS[@]} -gt 0 ]; then
|
|
echo -e "${YELLOW}${BOLD}📊 BOUNCE ANALYSIS${NC}"
|
|
echo ""
|
|
echo " Bounce reasons breakdown:"
|
|
[ -n "${BOUNCE_REASONS[mailbox_full]}" ] && printf " - %-40s %d\n" "Mailbox Full" "${BOUNCE_REASONS[mailbox_full]}"
|
|
[ -n "${BOUNCE_REASONS[user_unknown]}" ] && printf " - %-40s %d\n" "User Unknown" "${BOUNCE_REASONS[user_unknown]}"
|
|
[ -n "${BOUNCE_REASONS[blocked]}" ] && printf " - %-40s %d\n" "Blocked/Spam" "${BOUNCE_REASONS[blocked]}"
|
|
[ -n "${BOUNCE_REASONS[dns_failure]}" ] && printf " - %-40s %d\n" "DNS Failure" "${BOUNCE_REASONS[dns_failure]}"
|
|
[ -n "${BOUNCE_REASONS[timeout]}" ] && printf " - %-40s %d\n" "Timeout" "${BOUNCE_REASONS[timeout]}"
|
|
[ -n "${BOUNCE_REASONS[greylisting]}" ] && printf " - %-40s %d\n" "Greylisting" "${BOUNCE_REASONS[greylisting]}"
|
|
[ -n "${BOUNCE_REASONS[tls_failure]}" ] && printf " - %-40s %d\n" "TLS/SSL Issues" "${BOUNCE_REASONS[tls_failure]}"
|
|
echo ""
|
|
|
|
# Recommendations based on bounce types
|
|
if [ -n "${BOUNCE_REASONS[blocked]}" ] && [ "${BOUNCE_REASONS[blocked]}" -gt 10 ]; then
|
|
echo -e " ${YELLOW}Action Required:${NC} High spam/block rate. Check IP reputation and email authentication."
|
|
fi
|
|
if [ -n "${BOUNCE_REASONS[dns_failure]}" ] && [ "${BOUNCE_REASONS[dns_failure]}" -gt 5 ]; then
|
|
echo -e " ${YELLOW}Action Required:${NC} DNS issues detected. Verify domain DNS records and MX records."
|
|
fi
|
|
echo ""
|
|
fi
|
|
|
|
# Rate limiting
|
|
if [ -n "${ISSUES_FOUND[rate_limiting]}" ]; then
|
|
echo -e "${YELLOW}${BOLD}⏱️ RATE LIMITING (${ISSUES_FOUND[rate_limiting]} occurrences)${NC}"
|
|
echo ""
|
|
if [ -f /tmp/rate_limit_domains.$$ ]; then
|
|
echo " Domains enforcing rate limits:"
|
|
head -5 /tmp/rate_limit_domains.$$ | while read count domain; do
|
|
printf " - %-40s %d times\n" "$domain" "$count"
|
|
done
|
|
echo ""
|
|
fi
|
|
echo -e " ${YELLOW}Action Required:${NC} ${RECOMMENDATIONS[rate_limiting]}"
|
|
echo ""
|
|
fi
|
|
|
|
# Configuration issues
|
|
for issue in rdns certificate local_delivery; do
|
|
if [ -n "${ISSUES_FOUND[$issue]}" ]; then
|
|
local icon="⚙️"
|
|
case $issue in
|
|
rdns) icon="🌐" ;;
|
|
certificate) icon="🔒" ;;
|
|
local_delivery) icon="📬" ;;
|
|
esac
|
|
echo -e "${YELLOW}${BOLD}$icon ${issue^^} ISSUE${NC}"
|
|
echo -e " ${YELLOW}Action Required:${NC} ${RECOMMENDATIONS[$issue]}"
|
|
echo ""
|
|
fi
|
|
done
|
|
|
|
# HELO Violations
|
|
if [ -n "${ISSUES_FOUND[helo_violations]}" ]; then
|
|
echo -e "${RED}${BOLD}🚫 HELO/EHLO VIOLATIONS (${ISSUES_FOUND[helo_violations]} occurrences)${NC}"
|
|
echo ""
|
|
if [ ${#HELO_VIOLATIONS[@]} -gt 0 ]; then
|
|
echo " Top offending IPs:"
|
|
local count=0
|
|
for ip in "${!HELO_VIOLATIONS[@]}"; do
|
|
printf " - %-40s %d violations\n" "$ip" "${HELO_VIOLATIONS[$ip]}"
|
|
((count++))
|
|
[ $count -ge 10 ] && break
|
|
done
|
|
fi
|
|
if [ -f "/tmp/suspicious_helos.$$" ]; then
|
|
echo ""
|
|
echo " Suspicious HELO names detected:"
|
|
sort /tmp/suspicious_helos.$$ | uniq -c | sort -rn | head -5 | while read count helo; do
|
|
printf " - %-40s %d times\n" "$helo" "$count"
|
|
done
|
|
fi
|
|
echo ""
|
|
echo -e " ${YELLOW}Action Required:${NC} ${RECOMMENDATIONS[helo_violations]}"
|
|
echo ""
|
|
fi
|
|
|
|
# Panic Log
|
|
if [ -n "${ISSUES_FOUND[panic_log]}" ]; then
|
|
echo -e "${RED}${BOLD}💥 CRITICAL - PANIC LOG EXISTS (${ISSUES_FOUND[panic_log]} entries)${NC}"
|
|
echo ""
|
|
if [ -f "/tmp/recent_panics.$$" ]; then
|
|
echo " Recent panic log entries:"
|
|
cat "/tmp/recent_panics.$$" | head -5 | sed 's/^/ /'
|
|
echo ""
|
|
fi
|
|
echo -e " ${RED}${BOLD}Action Required:${NC} ${RECOMMENDATIONS[panic_log]}"
|
|
echo ""
|
|
fi
|
|
|
|
# Frozen Messages
|
|
if [ -n "${ISSUES_FOUND[frozen_messages]}" ]; then
|
|
echo -e "${YELLOW}${BOLD}❄️ FROZEN MESSAGES (${ISSUES_FOUND[frozen_messages]} detected)${NC}"
|
|
echo ""
|
|
if [ -n "${FROZEN_MESSAGES[current_queue]}" ]; then
|
|
echo " Currently frozen in queue: ${FROZEN_MESSAGES[current_queue]} messages"
|
|
echo ""
|
|
fi
|
|
echo -e " ${YELLOW}Action Required:${NC} ${RECOMMENDATIONS[frozen_messages]}"
|
|
echo ""
|
|
fi
|
|
|
|
# Connection Flooding
|
|
if [ -n "${ISSUES_FOUND[connection_flooding]}" ]; then
|
|
echo -e "${RED}${BOLD}🌊 CONNECTION FLOODING (${ISSUES_FOUND[connection_flooding]} IPs)${NC}"
|
|
echo ""
|
|
echo " IPs with rapid connect/disconnect:"
|
|
local count=0
|
|
for ip in "${!CONNECTION_FLOODS[@]}"; do
|
|
printf " - %-40s %d rapid connections\n" "$ip" "${CONNECTION_FLOODS[$ip]}"
|
|
((count++))
|
|
[ $count -ge 10 ] && break
|
|
done
|
|
echo ""
|
|
echo -e " ${YELLOW}Action Required:${NC} ${RECOMMENDATIONS[connection_flooding]}"
|
|
echo ""
|
|
fi
|
|
|
|
# Auth Attacks
|
|
if [ -n "${ISSUES_FOUND[auth_attacks]}" ]; then
|
|
echo -e "${RED}${BOLD}🔓 BRUTE FORCE AUTH ATTACKS (${ISSUES_FOUND[auth_attacks]} IPs)${NC}"
|
|
echo ""
|
|
echo " IPs attempting brute force:"
|
|
local count=0
|
|
for ip in "${!AUTH_ATTACK_IPS[@]}"; do
|
|
printf " - %-40s %d failed attempts\n" "$ip" "${AUTH_ATTACK_IPS[$ip]}"
|
|
((count++))
|
|
[ $count -ge 10 ] && break
|
|
done
|
|
echo ""
|
|
echo -e " ${RED}${BOLD}Action Required:${NC} ${RECOMMENDATIONS[auth_attacks]}"
|
|
echo ""
|
|
fi
|
|
|
|
# Deferral Loops
|
|
if [ -n "${ISSUES_FOUND[deferral_loops]}" ]; then
|
|
echo -e "${YELLOW}${BOLD}🔄 DEFERRAL LOOPS (${ISSUES_FOUND[deferral_loops]} messages)${NC}"
|
|
echo ""
|
|
if [ -f "/tmp/deferral_domains.$$" ]; then
|
|
echo " Domains with deferral issues:"
|
|
head -5 "/tmp/deferral_domains.$$" | while read count domain; do
|
|
printf " - %-40s %d messages\n" "$domain" "$count"
|
|
done
|
|
echo ""
|
|
fi
|
|
echo -e " ${YELLOW}Action Required:${NC} ${RECOMMENDATIONS[deferral_loops]}"
|
|
echo ""
|
|
fi
|
|
|
|
# TLS/SSL Issues
|
|
if [ -n "${ISSUES_FOUND[tls_errors]}" ]; then
|
|
echo -e "${YELLOW}${BOLD}🔒 TLS/SSL ERRORS (${ISSUES_FOUND[tls_errors]} occurrences)${NC}"
|
|
echo ""
|
|
if [ -f "/tmp/tls_error_ips.$$" ]; then
|
|
echo " Top IPs with TLS errors:"
|
|
head -10 "/tmp/tls_error_ips.$$" | while read count ip; do
|
|
printf " - %-40s %d errors\n" "$ip" "$count"
|
|
done
|
|
echo ""
|
|
fi
|
|
echo -e " ${YELLOW}Action Required:${NC} ${RECOMMENDATIONS[tls_errors]}"
|
|
echo ""
|
|
fi
|
|
|
|
# Message Size Rejections
|
|
if [ -n "${ISSUES_FOUND[size_rejections]}" ]; then
|
|
echo -e "${YELLOW}${BOLD}📦 MESSAGE SIZE REJECTIONS (${ISSUES_FOUND[size_rejections]} occurrences)${NC}"
|
|
echo ""
|
|
if [ -f "/tmp/size_reject_users.$$" ]; then
|
|
echo " Users affected by size limits:"
|
|
head -10 "/tmp/size_reject_users.$$" | while read count user; do
|
|
printf " - %-40s %d rejections\n" "$user" "$count"
|
|
done
|
|
echo ""
|
|
fi
|
|
echo -e " ${YELLOW}Action Required:${NC} ${RECOMMENDATIONS[size_rejections]}"
|
|
echo ""
|
|
fi
|
|
|
|
# Routing Loops
|
|
if [ -n "${ISSUES_FOUND[routing_loops]}" ]; then
|
|
echo -e "${RED}${BOLD}♻️ ROUTING LOOPS (${ISSUES_FOUND[routing_loops]} detected)${NC}"
|
|
echo ""
|
|
if [ -f "/tmp/loop_addresses.$$" ]; then
|
|
echo " Addresses caught in loops:"
|
|
head -10 "/tmp/loop_addresses.$$" | while read count address; do
|
|
printf " - %-40s %d times\n" "$address" "$count"
|
|
done
|
|
echo ""
|
|
fi
|
|
echo -e " ${RED}Action Required:${NC} ${RECOMMENDATIONS[routing_loops]}"
|
|
echo ""
|
|
fi
|
|
}
|
|
|
|
# Display actionable recommendations
|
|
display_recommendations() {
|
|
if [ ${#RECOMMENDATIONS[@]} -eq 0 ]; then
|
|
return
|
|
fi
|
|
|
|
echo -e "${GREEN}${BOLD}═══════════════════════════════════════════════════${NC}"
|
|
echo -e "${GREEN}${BOLD} RECOMMENDED ACTIONS${NC}"
|
|
echo -e "${GREEN}${BOLD}═══════════════════════════════════════════════════${NC}"
|
|
echo ""
|
|
|
|
local priority=1
|
|
for issue in blacklist spam_accounts authentication rate_limiting rdns certificate local_delivery helo_violations frozen_messages panic_log connection_flooding auth_attacks deferral_loops tls_errors size_rejections routing_loops; do
|
|
if [ -n "${RECOMMENDATIONS[$issue]}" ]; then
|
|
echo -e "${CYAN}$priority)${NC} ${BOLD}$(echo $issue | tr '_' ' ' | awk '{for(i=1;i<=NF;i++)sub(/./,toupper(substr($i,1,1)),$i)}1')${NC}"
|
|
echo " ${RECOMMENDATIONS[$issue]}"
|
|
echo ""
|
|
((priority++))
|
|
fi
|
|
done
|
|
|
|
# General recommendations
|
|
echo -e "${CYAN}${priority})${NC} ${BOLD}Use Email Troubleshooting Tools${NC}"
|
|
echo " - SPF/DKIM/DMARC Check (Menu option 4)"
|
|
echo " - Blacklist Check (Menu option 5)"
|
|
echo " - Mail Queue Inspector (Menu option 2)"
|
|
echo ""
|
|
}
|
|
|
|
#=============================================================================
|
|
# ENHANCED DISPLAY FUNCTIONS - Domain & User Insights
|
|
#=============================================================================
|
|
|
|
# Display detailed domain analysis
|
|
display_domain_analysis() {
|
|
# Only show domains with actual problems (< 80% success rate OR > 10 bounces)
|
|
local has_issues=0
|
|
|
|
# Check if we have problem domains
|
|
if [ -f /tmp/domain_success_rates_sorted.$$ ] && [ -s /tmp/domain_success_rates_sorted.$$ ]; then
|
|
# Check if any domain has < 80% success rate
|
|
if awk -F'|' '$1 < 80 {exit 0} END {exit 1}' /tmp/domain_success_rates_sorted.$$ 2>/dev/null; then
|
|
has_issues=1
|
|
fi
|
|
fi
|
|
|
|
if [ -f /tmp/top_bouncing_domains.$$ ] && [ -s /tmp/top_bouncing_domains.$$ ]; then
|
|
# Check if any domain has > 10 bounces
|
|
if awk '$1 > 10 {exit 0} END {exit 1}' /tmp/top_bouncing_domains.$$ 2>/dev/null; then
|
|
has_issues=1
|
|
fi
|
|
fi
|
|
|
|
# Only display section if there are actual problems
|
|
if [ $has_issues -eq 0 ]; then
|
|
return
|
|
fi
|
|
|
|
echo ""
|
|
echo -e "${CYAN}${BOLD}═══════════════════════════════════════════════════${NC}"
|
|
echo -e "${CYAN}${BOLD} PROBLEM DOMAINS DETECTED${NC}"
|
|
echo -e "${CYAN}${BOLD}═══════════════════════════════════════════════════${NC}"
|
|
echo ""
|
|
|
|
# Show domains with low success rates (< 80%)
|
|
if [ -f /tmp/domain_success_rates_sorted.$$ ] && [ -s /tmp/domain_success_rates_sorted.$$ ]; then
|
|
local shown=0
|
|
while IFS='|' read rate domain stats; do
|
|
# Only show if success rate < 80%
|
|
# Remove percent sign and decimal portion, keep only integer part
|
|
local rate_int=$(echo "$rate" | sed 's/[^0-9].*//')
|
|
if [ -n "$rate_int" ] && [ "$rate_int" -lt 80 ]; then
|
|
if [ $shown -eq 0 ]; then
|
|
echo -e "${RED}${BOLD}⚠️ Domains with Low Delivery Success Rates (<80%):${NC}"
|
|
echo ""
|
|
fi
|
|
printf " %-40s %6s (%s delivered)\n" "$domain" "$rate" "$stats"
|
|
shown=1
|
|
fi
|
|
done < /tmp/domain_success_rates_sorted.$$
|
|
[ $shown -eq 1 ] && echo ""
|
|
fi
|
|
|
|
# Show domains with significant bounces (> 10)
|
|
if [ -f /tmp/top_bouncing_domains.$$ ] && [ -s /tmp/top_bouncing_domains.$$ ]; then
|
|
local shown=0
|
|
local count=0
|
|
while read num domain; do
|
|
# Only show if > 10 bounces
|
|
if [ "$num" -gt 10 ]; then
|
|
if [ $shown -eq 0 ]; then
|
|
echo -e "${RED}${BOLD}⚠️ Domains with High Bounce Counts (>10):${NC}"
|
|
echo ""
|
|
fi
|
|
printf " %-40s %6d bounces\n" "$domain" "$num"
|
|
shown=1
|
|
((count++))
|
|
[ $count -ge 5 ] && break
|
|
fi
|
|
done < /tmp/top_bouncing_domains.$$
|
|
[ $shown -eq 1 ] && echo ""
|
|
fi
|
|
}
|
|
|
|
# Display user activity analysis - ONLY show suspicious/high-volume users
|
|
display_user_analysis() {
|
|
local has_suspicious=0
|
|
local threshold=100 # Show users with > 100 messages (potential spam/compromised)
|
|
|
|
# Check if any users exceed threshold
|
|
if [ -f /tmp/top_senders.$$ ] && [ -s /tmp/top_senders.$$ ]; then
|
|
if awk -v t=$threshold '$1 > t {exit 0} END {exit 1}' /tmp/top_senders.$$ 2>/dev/null; then
|
|
has_suspicious=1
|
|
fi
|
|
fi
|
|
|
|
# Only display if there are suspicious users
|
|
if [ $has_suspicious -eq 0 ]; then
|
|
return
|
|
fi
|
|
|
|
echo ""
|
|
echo -e "${CYAN}${BOLD}═══════════════════════════════════════════════════${NC}"
|
|
echo -e "${CYAN}${BOLD} HIGH-VOLUME SENDERS (Potential Issues)${NC}"
|
|
echo -e "${CYAN}${BOLD}═══════════════════════════════════════════════════${NC}"
|
|
echo ""
|
|
|
|
# Show only high-volume senders (> 100 messages)
|
|
if [ -f /tmp/top_senders.$$ ] && [ -s /tmp/top_senders.$$ ]; then
|
|
local shown=0
|
|
local count=0
|
|
while read num email; do
|
|
if [ "$num" -gt $threshold ]; then
|
|
if [ $shown -eq 0 ]; then
|
|
echo -e "${YELLOW}${BOLD}⚠️ Users Sending >$threshold Messages:${NC}"
|
|
echo ""
|
|
fi
|
|
printf " %-45s %6d messages\n" "$email" "$num"
|
|
shown=1
|
|
((count++))
|
|
[ $count -ge 10 ] && break
|
|
fi
|
|
done < /tmp/top_senders.$$
|
|
|
|
if [ $shown -eq 1 ]; then
|
|
echo ""
|
|
echo -e "${YELLOW} Note: High volume may indicate compromised account or spam bot.${NC}"
|
|
echo ""
|
|
fi
|
|
fi
|
|
}
|
|
|
|
# Display hourly distribution - ONLY if suspicious off-hours activity detected
|
|
display_hourly_distribution() {
|
|
if [ ! -f /tmp/hourly_volume.$$ ] || [ ! -s /tmp/hourly_volume.$$ ]; then
|
|
return
|
|
fi
|
|
|
|
# Calculate average and check for off-hours spikes (00:00-06:00)
|
|
local max_vol=$(awk '{print $1}' /tmp/hourly_volume.$$ | sort -n | tail -1)
|
|
local avg_vol=$(awk '{sum+=$1; count++} END {if(count>0) print int(sum/count); else print 0}' /tmp/hourly_volume.$$)
|
|
|
|
# Check for off-hours activity (midnight-6am) that's > 2x average
|
|
local has_suspicious_hours=0
|
|
while read count hour; do
|
|
if [ "$hour" -lt 6 ] && [ "$count" -gt $((avg_vol * 2)) ]; then
|
|
has_suspicious_hours=1
|
|
break
|
|
fi
|
|
done < /tmp/hourly_volume.$$
|
|
|
|
# Only show if suspicious activity detected
|
|
if [ $has_suspicious_hours -eq 0 ]; then
|
|
return
|
|
fi
|
|
|
|
echo ""
|
|
echo -e "${CYAN}${BOLD}═══════════════════════════════════════════════════${NC}"
|
|
echo -e "${CYAN}${BOLD} ⚠️ SUSPICIOUS HOURLY ACTIVITY DETECTED${NC}"
|
|
echo -e "${CYAN}${BOLD}═══════════════════════════════════════════════════${NC}"
|
|
echo ""
|
|
|
|
echo -e "${YELLOW}${BOLD}Unusual off-hours email activity detected (midnight-6am spike)${NC}"
|
|
echo -e "${YELLOW}This may indicate a compromised account sending spam.${NC}"
|
|
echo ""
|
|
echo -e "${BOLD}📈 Message Volume by Hour:${NC}"
|
|
echo ""
|
|
|
|
while read count hour; do
|
|
# Create simple bar chart
|
|
local bar_length=$((count * 50 / max_vol))
|
|
[ $bar_length -lt 1 ] && bar_length=1
|
|
local bar=$(printf '█%.0s' $(seq 1 $bar_length))
|
|
|
|
# Highlight suspicious hours (00-06) in red
|
|
if [ "$hour" -lt 6 ] && [ "$count" -gt $((avg_vol * 2)) ]; then
|
|
printf " ${RED}%02d:00 %5d %s ← SPIKE${NC}\n" "$hour" "$count" "$bar"
|
|
else
|
|
printf " %02d:00 %5d %s\n" "$hour" "$count" "$bar"
|
|
fi
|
|
done < /tmp/hourly_volume.$$
|
|
echo ""
|
|
}
|
|
|
|
# Display rejection analysis - ONLY if significant rejections (>10)
|
|
display_rejection_analysis() {
|
|
if [ ! -f /tmp/rejection_summary.$$ ] || [ ! -s /tmp/rejection_summary.$$ ]; then
|
|
return
|
|
fi
|
|
|
|
# Check if any rejection type has > 10 occurrences
|
|
local has_significant=0
|
|
if awk '$1 > 10 {exit 0} END {exit 1}' /tmp/rejection_summary.$$ 2>/dev/null; then
|
|
has_significant=1
|
|
fi
|
|
|
|
if [ $has_significant -eq 0 ]; then
|
|
return
|
|
fi
|
|
|
|
echo ""
|
|
echo -e "${CYAN}${BOLD}═══════════════════════════════════════════════════${NC}"
|
|
echo -e "${CYAN}${BOLD} REJECTION ANALYSIS${NC}"
|
|
echo -e "${CYAN}${BOLD}═══════════════════════════════════════════════════${NC}"
|
|
echo ""
|
|
|
|
echo -e "${BOLD}🚫 Top Rejection Reasons (>10 occurrences):${NC}"
|
|
echo ""
|
|
local count=0
|
|
while read num reason; do
|
|
if [ "$num" -gt 10 ]; then
|
|
printf " %-50s %6d\n" "$reason" "$num"
|
|
((count++))
|
|
[ $count -ge 5 ] && break
|
|
fi
|
|
done < /tmp/rejection_summary.$$
|
|
echo ""
|
|
}
|
|
|
|
# Display error samples - DISABLED to avoid information overload
|
|
display_error_samples() {
|
|
# This section is intentionally disabled
|
|
# The existing issue detection already shows relevant error details
|
|
return
|
|
}
|
|
|
|
# Save report to file
|
|
save_report() {
|
|
{
|
|
display_summary
|
|
display_issues
|
|
display_recommendations
|
|
display_domain_analysis
|
|
display_user_analysis
|
|
display_hourly_distribution
|
|
display_rejection_analysis
|
|
display_error_samples
|
|
} | tee "$REPORT_FILE" >/dev/null
|
|
|
|
echo -e "${GREEN}Report saved to: $REPORT_FILE${NC}"
|
|
echo ""
|
|
}
|
|
|
|
################################################################################
|
|
# Main Function
|
|
################################################################################
|
|
|
|
main() {
|
|
print_banner "Mail Log Analyzer"
|
|
|
|
# Detect mail log location
|
|
MAIL_LOG=$(get_mail_log_path)
|
|
|
|
if [ -z "$MAIL_LOG" ] || [ ! -f "$MAIL_LOG" ]; then
|
|
print_error "Mail log not found!"
|
|
echo "Expected locations:"
|
|
echo " - /var/log/exim_mainlog (cPanel)"
|
|
echo " - /var/log/maillog (Plesk/RHEL)"
|
|
echo " - /var/log/mail.log (Debian/Ubuntu)"
|
|
pause_for_user
|
|
return 1
|
|
fi
|
|
|
|
print_info "Found mail log: $MAIL_LOG"
|
|
echo ""
|
|
|
|
# Display time period selection menu
|
|
echo -e "${CYAN}${BOLD}Select Analysis Time Period:${NC}"
|
|
echo ""
|
|
echo " 1) Last 1 hour"
|
|
echo " 2) Last 6 hours"
|
|
echo " 3) Last 12 hours"
|
|
echo " 4) Last 24 hours (recommended)"
|
|
echo " 5) Last 48 hours (2 days)"
|
|
echo " 6) Last 1 week (7 days)"
|
|
echo " 7) Last 1 month (30 days)"
|
|
echo " 8) Entire log file"
|
|
echo ""
|
|
echo -n "Enter choice [4]: "
|
|
read -r choice
|
|
choice=${choice:-4}
|
|
|
|
# Map choice to hours
|
|
case $choice in
|
|
1) ANALYSIS_HOURS=1; ANALYSIS_DESC="1 hour" ;;
|
|
2) ANALYSIS_HOURS=6; ANALYSIS_DESC="6 hours" ;;
|
|
3) ANALYSIS_HOURS=12; ANALYSIS_DESC="12 hours" ;;
|
|
4) ANALYSIS_HOURS=24; ANALYSIS_DESC="24 hours" ;;
|
|
5) ANALYSIS_HOURS=48; ANALYSIS_DESC="48 hours" ;;
|
|
6) ANALYSIS_HOURS=168; ANALYSIS_DESC="1 week" ;;
|
|
7) ANALYSIS_HOURS=720; ANALYSIS_DESC="1 month" ;;
|
|
8) ANALYSIS_HOURS=999999; ANALYSIS_DESC="entire log" ;;
|
|
*) ANALYSIS_HOURS=24; ANALYSIS_DESC="24 hours" ;;
|
|
esac
|
|
|
|
echo ""
|
|
print_info "Analyzing last $ANALYSIS_DESC of mail logs..."
|
|
echo ""
|
|
|
|
# Create temporary log file with time-filtered entries
|
|
TEMP_LOG="/tmp/mail_analysis_$$.log"
|
|
|
|
if [ "$ANALYSIS_HOURS" -eq 999999 ]; then
|
|
# Use entire log
|
|
cp "$MAIL_LOG" "$TEMP_LOG"
|
|
else
|
|
# Calculate cutoff timestamp (works with Exim date format)
|
|
CUTOFF_TIMESTAMP=$(date -d "$ANALYSIS_HOURS hours ago" '+%Y-%m-%d %H:%M:%S' 2>/dev/null)
|
|
|
|
if [ -n "$CUTOFF_TIMESTAMP" ]; then
|
|
# Filter by actual timestamps
|
|
awk -v cutoff="$CUTOFF_TIMESTAMP" '
|
|
BEGIN { print_line = 0 }
|
|
/^[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}/ {
|
|
timestamp = $1 " " $2
|
|
if (timestamp >= cutoff) {
|
|
print_line = 1
|
|
} else {
|
|
print_line = 0
|
|
}
|
|
}
|
|
print_line { print }
|
|
' "$MAIL_LOG" > "$TEMP_LOG"
|
|
|
|
# Fallback to tail if awk filtering produced empty result
|
|
if [ ! -s "$TEMP_LOG" ]; then
|
|
# Estimate lines based on hours (rough estimate: 1000 lines per hour)
|
|
local estimated_lines=$((ANALYSIS_HOURS * 1000))
|
|
tail -n "$estimated_lines" "$MAIL_LOG" > "$TEMP_LOG"
|
|
fi
|
|
else
|
|
# Fallback for systems without GNU date
|
|
local estimated_lines=$((ANALYSIS_HOURS * 1000))
|
|
tail -n "$estimated_lines" "$MAIL_LOG" > "$TEMP_LOG"
|
|
fi
|
|
fi
|
|
|
|
# Run all detection functions
|
|
detect_blacklist_issues "$TEMP_LOG"
|
|
detect_spam_accounts "$TEMP_LOG"
|
|
detect_auth_failures "$TEMP_LOG"
|
|
analyze_bounces "$TEMP_LOG"
|
|
detect_rate_limiting "$TEMP_LOG"
|
|
detect_config_issues "$TEMP_LOG"
|
|
gather_statistics "$TEMP_LOG"
|
|
|
|
# Enhanced detection functions
|
|
detect_helo_violations "$TEMP_LOG"
|
|
detect_frozen_messages "$TEMP_LOG"
|
|
check_panic_log
|
|
detect_connection_flooding "$TEMP_LOG"
|
|
detect_smtp_auth_attacks "$TEMP_LOG"
|
|
detect_deferral_loops "$TEMP_LOG"
|
|
|
|
# Additional high-priority detections
|
|
detect_tls_issues "$TEMP_LOG"
|
|
detect_size_rejections "$TEMP_LOG"
|
|
detect_routing_loops "$TEMP_LOG"
|
|
|
|
# NEW: Comprehensive analysis functions
|
|
analyze_domain_performance "$TEMP_LOG"
|
|
analyze_user_activity "$TEMP_LOG"
|
|
analyze_hourly_patterns "$TEMP_LOG"
|
|
analyze_rejection_details "$TEMP_LOG"
|
|
calculate_domain_success_rates "$TEMP_LOG"
|
|
capture_error_samples "$TEMP_LOG"
|
|
|
|
# Display results
|
|
clear
|
|
display_summary
|
|
display_issues
|
|
display_recommendations
|
|
|
|
# Save report
|
|
save_report
|
|
|
|
# Cleanup
|
|
rm -f "$TEMP_LOG" /tmp/*.$$ 2>/dev/null
|
|
|
|
echo ""
|
|
echo -n "Press Enter to return to menu..."
|
|
read
|
|
}
|
|
|
|
# Run main function
|
|
main
|