diff --git a/modules/email/mail-log-analyzer.sh b/modules/email/mail-log-analyzer.sh index 959dcb9..58124c9 100755 --- a/modules/email/mail-log-analyzer.sh +++ b/modules/email/mail-log-analyzer.sh @@ -22,7 +22,7 @@ source "$SCRIPT_DIR/lib/email-functions.sh" ANALYSIS_HOURS=24 SPAM_THRESHOLD=100 # Emails per hour considered spam TEMP_DIR=$(mktemp -d) || { print_error "Failed to create temp directory"; exit 1; } -REPORT_FILE=""$TEMP_DIR/"mail-analysis-$(date +%Y%m%d-%H%M%S).txt" +REPORT_FILE=$TEMP_DIR/"mail-analysis-$(date +%Y%m%d-%H%M%S).txt" # Cleanup trap - runs on EXIT or SIGINT trap 'rm -rf "$TEMP_DIR" 2>/dev/null' EXIT INT TERM @@ -69,7 +69,7 @@ PANIC_LOG_EXISTS=0 # Detect blacklist rejections detect_blacklist_issues() { local log_file="$1" - local temp_file=""$TEMP_DIR/"blacklist_detections.$$" + local temp_file=$TEMP_DIR/"blacklist_detections.$$" print_info "Scanning for blacklist rejections..." @@ -80,7 +80,7 @@ detect_blacklist_issues() { # 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=""$TEMP_DIR/"blacklist_detections_filtered.$$" + local temp_filtered=$TEMP_DIR/"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 @@ -156,7 +156,7 @@ detect_blacklist_issues() { # Detect spam accounts (high volume senders) detect_spam_accounts() { local log_file="$1" - local temp_file=""$TEMP_DIR/"sender_counts.$$" + local temp_file=$TEMP_DIR/"sender_counts.$$" print_info "Analyzing sender volumes..." @@ -195,7 +195,7 @@ detect_spam_accounts() { # Detect SPF/DKIM/DMARC failures detect_auth_failures() { local log_file="$1" - local temp_file=""$TEMP_DIR/"auth_failures.$$" + local temp_file=$TEMP_DIR/"auth_failures.$$" print_info "Checking email authentication failures..." @@ -243,7 +243,7 @@ detect_auth_failures() { # Analyze bounce reasons analyze_bounces() { local log_file="$1" - local temp_file=""$TEMP_DIR/"bounces.$$" + local temp_file=$TEMP_DIR/"bounces.$$" print_info "Analyzing bounce messages..." @@ -326,7 +326,7 @@ detect_config_issues() { # Detect HELO/EHLO violations detect_helo_violations() { local log_file="$1" - local temp_file=""$TEMP_DIR/"helo_violations.$$" + local temp_file=$TEMP_DIR/"helo_violations.$$" print_info "Checking for HELO/EHLO violations..." @@ -349,7 +349,7 @@ detect_helo_violations() { 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" >> ""$TEMP_DIR/"suspicious_helos.$$" + echo "$helo_name" >> $TEMP_DIR/"suspicious_helos.$$" fi fi done < "$temp_file" @@ -402,7 +402,7 @@ check_panic_log() { ISSUES_FOUND["panic_log"]=$panic_lines # Get recent panic entries - tail -20 "$panic_log" > ""$TEMP_DIR/"recent_panics.$$" + tail -20 "$panic_log" > $TEMP_DIR/"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 @@ -416,7 +416,7 @@ check_panic_log() { # Detect connection flooding detect_connection_flooding() { local log_file="$1" - local temp_file=""$TEMP_DIR/"connection_floods.$$" + local temp_file=$TEMP_DIR/"connection_floods.$$" print_info "Analyzing connection patterns for flooding..." @@ -426,14 +426,14 @@ detect_connection_flooding() { 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 > ""$TEMP_DIR/"flood_ips.$$" + sed 's/\[//;s/\]//' | sort | uniq -c | sort -rn > $TEMP_DIR/"flood_ips.$$" # Flag IPs with >20 rapid disconnects while read count ip; do if [ "$count" -gt 20 ]; then CONNECTION_FLOODS["$ip"]=$count fi - done < ""$TEMP_DIR/"flood_ips.$$" + done < $TEMP_DIR/"flood_ips.$$" if [ ${#CONNECTION_FLOODS[@]} -gt 0 ]; then ISSUES_FOUND["connection_flooding"]=${#CONNECTION_FLOODS[@]} @@ -441,13 +441,13 @@ detect_connection_flooding() { fi fi - rm -f "$temp_file" ""$TEMP_DIR/"flood_ips.$$" + rm -f "$temp_file" $TEMP_DIR/"flood_ips.$$" } # Detect SMTP auth brute force attempts detect_smtp_auth_attacks() { local log_file="$1" - local temp_file=""$TEMP_DIR/"smtp_auth_failures.$$" + local temp_file=$TEMP_DIR/"smtp_auth_failures.$$" print_info "Detecting SMTP authentication failures..." @@ -459,14 +459,14 @@ detect_smtp_auth_attacks() { # 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 > ""$TEMP_DIR/"auth_attack_ips.$$" + sed 's/\[//;s/\]//' | sort | uniq -c | sort -rn > $TEMP_DIR/"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 < ""$TEMP_DIR/"auth_attack_ips.$$" + done < $TEMP_DIR/"auth_attack_ips.$$" if [ ${#AUTH_ATTACK_IPS[@]} -gt 0 ]; then ISSUES_FOUND["auth_attacks"]=${#AUTH_ATTACK_IPS[@]} @@ -477,13 +477,13 @@ detect_smtp_auth_attacks() { fi fi - rm -f "$temp_file" ""$TEMP_DIR/"auth_attack_ips.$$" + rm -f "$temp_file" $TEMP_DIR/"auth_attack_ips.$$" } # Detect deferral loops detect_deferral_loops() { local log_file="$1" - local temp_file=""$TEMP_DIR/"deferrals.$$" + local temp_file=$TEMP_DIR/"deferrals.$$" print_info "Checking for deferral loops..." @@ -495,7 +495,7 @@ detect_deferral_loops() { # 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 > ""$TEMP_DIR/"deferral_domains.$$" + sed 's/@//' | sort | uniq -c | sort -rn | head -10 > $TEMP_DIR/"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." @@ -511,7 +511,7 @@ detect_deferral_loops() { # Detect TLS/SSL issues detect_tls_issues() { local log_file="$1" - local temp_file=""$TEMP_DIR/"tls_issues.$$" + local temp_file=$TEMP_DIR/"tls_issues.$$" print_info "Analyzing TLS/SSL errors..." @@ -523,10 +523,10 @@ detect_tls_issues() { 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") + local ssl_eof=$(grep -c "unexpected eof" -- "$temp_file" 2>/dev/null || echo "0") + local ssl_broken_pipe=$(grep -c "Broken pipe" -- "$temp_file" 2>/dev/null || echo "0") + local ssl_packet_length=$(grep -c "packet length too long" -- "$temp_file" 2>/dev/null || echo "0") + local ssl_reset=$(grep -c "Connection reset" -- "$temp_file" 2>/dev/null || echo "0") # Track IPs with TLS issues declare -A TLS_IPS @@ -541,7 +541,7 @@ detect_tls_issues() { if [ ${#TLS_IPS[@]} -gt 0 ]; then for ip in "${!TLS_IPS[@]}"; do echo "${TLS_IPS[$ip]} $ip" - done | sort -rn | head -10 > ""$TEMP_DIR/"tls_error_ips.$$" + done | sort -rn | head -10 > $TEMP_DIR/"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." @@ -553,7 +553,7 @@ detect_tls_issues() { # Detect message size rejections detect_size_rejections() { local log_file="$1" - local temp_file=""$TEMP_DIR/"size_rejections.$$" + local temp_file=$TEMP_DIR/"size_rejections.$$" print_info "Checking for message size rejections..." @@ -566,7 +566,7 @@ detect_size_rejections() { # 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 > ""$TEMP_DIR/"size_reject_users.$$" + sort | uniq -c | sort -rn | head -10 > $TEMP_DIR/"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 @@ -577,7 +577,7 @@ detect_size_rejections() { # Detect routing/forwarding loops detect_routing_loops() { local log_file="$1" - local temp_file=""$TEMP_DIR/"routing_loops.$$" + local temp_file=$TEMP_DIR/"routing_loops.$$" print_info "Detecting mail routing loops..." @@ -590,7 +590,7 @@ detect_routing_loops() { # 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 > ""$TEMP_DIR/"loop_addresses.$$" + sort | uniq -c | sort -rn | head -10 > $TEMP_DIR/"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 @@ -788,13 +788,13 @@ gather_statistics() { print_info "Gathering statistics..." # Count sent messages - TOTAL_SENT=$(grep -c "<=" -- "$log_file" 2>/dev/null | tr -d '\n' || echo "0") + TOTAL_SENT=$(grep -c "<=" -- "$log_file" 2>/dev/null || echo "0") # Count received messages - TOTAL_RECEIVED=$(grep -c "=>" -- "$log_file" 2>/dev/null | tr -d '\n' || echo "0") + TOTAL_RECEIVED=$(grep -c "=>" -- "$log_file" 2>/dev/null || echo "0") # Count deferrals - TOTAL_DEFERRED=$(grep -c "defer" -- "$log_file" 2>/dev/null | tr -d '\n' || echo "0") + TOTAL_DEFERRED=$(grep -c "defer" -- "$log_file" 2>/dev/null || echo "0") # Count rejections TOTAL_REJECTED=$(grep -cE "(reject|denied)" -- "$log_file" 2>/dev/null | tr -d '\n' || echo "0") @@ -854,9 +854,9 @@ display_issues() { fi # Show timeline - first and last occurrence - if [ -f ""$TEMP_DIR/"blacklist_detections.$$" ]; then - local first_occurrence=$(head -1 ""$TEMP_DIR/"blacklist_detections.$$" | awk '{print $1, $2}') - local last_occurrence=$(tail -1 ""$TEMP_DIR/"blacklist_detections.$$" | awk '{print $1, $2}') + if [ -f $TEMP_DIR/"blacklist_detections.$$" ]; then + local first_occurrence=$(head -1 $TEMP_DIR/"blacklist_detections.$$" | awk '{print $1, $2}') + local last_occurrence=$(tail -1 $TEMP_DIR/"blacklist_detections.$$" | awk '{print $1, $2}') echo " Timeline:" echo " First seen: $first_occurrence" @@ -873,9 +873,9 @@ display_issues() { fi # Show which domains/users triggered it (top 5) - if [ -f ""$TEMP_DIR/"blacklist_detections.$$" ]; then + if [ -f $TEMP_DIR/"blacklist_detections.$$" ]; then echo " Affected senders (top 5):" - grep -oE 'F=<[^>]+>' ""$TEMP_DIR/"blacklist_detections.$$" 2>/dev/null | \ + grep -oE 'F=<[^>]+>' $TEMP_DIR/"blacklist_detections.$$" 2>/dev/null | \ sed 's/F=//' | sort | uniq -c | sort -rn | head -5 | \ while read count sender; do printf " - %-45s %d times\n" "$sender" "$count" @@ -989,7 +989,7 @@ display_issues() { [ "$count" -ge 10 ] && break done fi - if [ -f ""$TEMP_DIR/"suspicious_helos.$$" ]; then + if [ -f $TEMP_DIR/"suspicious_helos.$$" ]; then echo "" echo " Suspicious HELO names detected:" sort "$TEMP_DIR/"suspicious_helos.$$ | uniq -c | sort -rn | head -5 | while read count helo; do @@ -1005,9 +1005,9 @@ display_issues() { if [ -n "${ISSUES_FOUND[panic_log]}" ]; then echo -e "${RED}${BOLD}💥 CRITICAL - PANIC LOG EXISTS (${ISSUES_FOUND[panic_log]} entries)${NC}" echo "" - if [ -f ""$TEMP_DIR/"recent_panics.$$" ]; then + if [ -f $TEMP_DIR/"recent_panics.$$" ]; then echo " Recent panic log entries:" - cat ""$TEMP_DIR/"recent_panics.$$" | head -5 | sed 's/^/ /' + cat $TEMP_DIR/"recent_panics.$$" | head -5 | sed 's/^/ /' echo "" fi echo -e " ${RED}${BOLD}Action Required:${NC} ${RECOMMENDATIONS[panic_log]}" @@ -1062,9 +1062,9 @@ display_issues() { if [ -n "${ISSUES_FOUND[deferral_loops]}" ]; then echo -e "${YELLOW}${BOLD}🔄 DEFERRAL LOOPS (${ISSUES_FOUND[deferral_loops]} messages)${NC}" echo "" - if [ -f ""$TEMP_DIR/"deferral_domains.$$" ]; then + if [ -f $TEMP_DIR/"deferral_domains.$$" ]; then echo " Domains with deferral issues:" - head -5 ""$TEMP_DIR/"deferral_domains.$$" | while read count domain; do + head -5 $TEMP_DIR/"deferral_domains.$$" | while read count domain; do printf " - %-40s %d messages\n" "$domain" "$count" done echo "" @@ -1077,9 +1077,9 @@ display_issues() { if [ -n "${ISSUES_FOUND[tls_errors]}" ]; then echo -e "${YELLOW}${BOLD}🔒 TLS/SSL ERRORS (${ISSUES_FOUND[tls_errors]} occurrences)${NC}" echo "" - if [ -f ""$TEMP_DIR/"tls_error_ips.$$" ]; then + if [ -f $TEMP_DIR/"tls_error_ips.$$" ]; then echo " Top IPs with TLS errors:" - head -10 ""$TEMP_DIR/"tls_error_ips.$$" | while read count ip; do + head -10 $TEMP_DIR/"tls_error_ips.$$" | while read count ip; do printf " - %-40s %d errors\n" "$ip" "$count" done echo "" @@ -1092,9 +1092,9 @@ display_issues() { if [ -n "${ISSUES_FOUND[size_rejections]}" ]; then echo -e "${YELLOW}${BOLD}📦 MESSAGE SIZE REJECTIONS (${ISSUES_FOUND[size_rejections]} occurrences)${NC}" echo "" - if [ -f ""$TEMP_DIR/"size_reject_users.$$" ]; then + if [ -f $TEMP_DIR/"size_reject_users.$$" ]; then echo " Users affected by size limits:" - head -10 ""$TEMP_DIR/"size_reject_users.$$" | while read count user; do + head -10 $TEMP_DIR/"size_reject_users.$$" | while read count user; do printf " - %-40s %d rejections\n" "$user" "$count" done echo "" @@ -1107,9 +1107,9 @@ display_issues() { if [ -n "${ISSUES_FOUND[routing_loops]}" ]; then echo -e "${RED}${BOLD}♻️ ROUTING LOOPS (${ISSUES_FOUND[routing_loops]} detected)${NC}" echo "" - if [ -f ""$TEMP_DIR/"loop_addresses.$$" ]; then + if [ -f $TEMP_DIR/"loop_addresses.$$" ]; then echo " Addresses caught in loops:" - head -10 ""$TEMP_DIR/"loop_addresses.$$" | while read count address; do + head -10 $TEMP_DIR/"loop_addresses.$$" | while read count address; do printf " - %-40s %d times\n" "$address" "$count" done echo "" @@ -1449,7 +1449,7 @@ main() { echo "" # Create temporary log file with time-filtered entries - TEMP_LOG=""$TEMP_DIR/"mail_analysis_$$.log" + TEMP_LOG=$TEMP_DIR/"mail_analysis_$$.log" if [ "$ANALYSIS_HOURS" -eq 999999 ]; then # Use entire log