diff --git a/modules/email/email-diagnostics.sh b/modules/email/email-diagnostics.sh index 48ee439..2338773 100755 --- a/modules/email/email-diagnostics.sh +++ b/modules/email/email-diagnostics.sh @@ -419,7 +419,8 @@ if [ "$sent" -gt 0 ] || [ "$received" -gt 0 ]; then # Top recipients (delivery recipients from emails in TEMP_MATCHES) if [ "$sent" -gt 0 ] || [ "$delivered" -gt 0 ]; then print_info "Top 5 recipients (emails delivered TO):" - grep -F "$search_pattern" "$TEMP_MATCHES" 2>/dev/null | grep -oE "=> [^@]+@[^ ]+" | sed 's/=> //' | sort | uniq -c | sort -rn | head -5 | while read count recipient; do + sed -n "/^$search_pattern/p" "$TEMP_MATCHES" 2>/dev/null | \ + sed -n 's/.*=> \([^@]*@[^ ]*\).*/\1/p' | sort | uniq -c | sort -rn | head -5 | while read count recipient; do [ -n "$count" ] && echo " $recipient - $count emails" done echo "" @@ -428,7 +429,8 @@ if [ "$sent" -gt 0 ] || [ "$received" -gt 0 ]; then # Top senders (who is sending emails in TEMP_MATCHES) if [ "$sent" -gt 0 ]; then print_info "Top 5 senders (emails sent FROM):" - grep -F "$search_pattern" "$TEMP_MATCHES" 2>/dev/null | grep -oE "<= [^@]+@[^ ]+" | sed 's/<= //' | sort | uniq -c | sort -rn | head -5 | while read count sender; do + sed -n "/^$search_pattern/p" "$TEMP_MATCHES" 2>/dev/null | \ + sed -n 's/.*<= \([^@]*@[^ ]*\).*/\1/p' | sort | uniq -c | sort -rn | head -5 | while read count sender; do [ -n "$count" ] && echo " $sender - $count emails" done echo "" @@ -546,7 +548,7 @@ if [ "$check_type" != "2" ]; then # cPanel forwarders (in /etc/valiases) if [ -f "/etc/valiases/$domain_part" ]; then - forwarder=$(grep -F "^$local_part:" "/etc/valiases/$domain_part" 2>/dev/null) + forwarder=$(grep "^${local_part}:" "/etc/valiases/$domain_part" 2>/dev/null || echo "") if [ -n "$forwarder" ]; then echo "" print_info "Forwarder configured:" @@ -650,7 +652,7 @@ if [ "$delivered" -gt 0 ]; then else echo " $line" fi - done < <(grep -F "$search_pattern" "$TEMP_MATCHES" | grep -iE "=>|delivered" | tail -5) + done < <(sed -n "/^$search_pattern/p" "$TEMP_MATCHES" 2>/dev/null | sed -n '/=>\|[Dd]elivered/p' | tail -5) echo "" fi @@ -660,7 +662,7 @@ if [ "$bounced" -gt 0 ]; then # Get all bounce lines (Issue 4.1: add -- after grep flags) TEMP_BOUNCES="/tmp/email_bounces_$$.txt" - grep -F -- "$search_pattern" "$TEMP_MATCHES" 2>/dev/null | \ + sed -n "/^$search_pattern/p" "$TEMP_MATCHES" 2>/dev/null | \ grep -Ev "authenticator failed|Authentication failed|saved mail to|=>" | \ grep -iE "550|551|552|553|554|bounced|Mail delivery failed|\\*\\* " > "$TEMP_BOUNCES" 2>/dev/null diff --git a/modules/email/mail-queue-inspector.sh b/modules/email/mail-queue-inspector.sh index 1d0a086..67aa1d8 100755 --- a/modules/email/mail-queue-inspector.sh +++ b/modules/email/mail-queue-inspector.sh @@ -40,14 +40,14 @@ if [ "$MTA" = "exim" ]; then print_header "Queue Summary" # Exim: exim -bpc returns just the number - queue_count=$(eval "$SYS_MAIL_CMD_QUEUE_COUNT") + queue_count=$(bash -c "$SYS_MAIL_CMD_QUEUE_COUNT" 2>/dev/null || echo "0") if [ "$queue_count" -gt 0 ] 2>/dev/null; then print_warning "$queue_count messages in queue" echo "" # Cache queue list - single execution for all operations - queue_list=$(eval "$SYS_MAIL_CMD_QUEUE_LIST") + queue_list=$(bash -c "$SYS_MAIL_CMD_QUEUE_LIST" 2>/dev/null || echo "") print_header "Recent Queue Messages (last 20)" echo "$queue_list" | head -20 @@ -74,7 +74,7 @@ elif [ "$MTA" = "postfix" ]; then print_header "Queue Summary" # Postfix: mailq | tail -1 returns "-- N Kbytes in M Requests." - queue_summary=$(eval "$SYS_MAIL_CMD_QUEUE_COUNT") + queue_summary=$(bash -c "$SYS_MAIL_CMD_QUEUE_COUNT" 2>/dev/null || echo "") print_info "$queue_summary" # Extract message count from summary line (last number is always message count) @@ -89,7 +89,7 @@ elif [ "$MTA" = "postfix" ]; then echo "" # Cache queue list - single execution for all operations - queue_list=$(eval "$SYS_MAIL_CMD_QUEUE_LIST") + queue_list=$(bash -c "$SYS_MAIL_CMD_QUEUE_LIST" 2>/dev/null || echo "") print_header "Queue Details (first 50)" echo "$queue_list" | head -50 @@ -116,7 +116,7 @@ elif [ "$MTA" = "sendmail" ]; then print_header "Queue Summary" # Sendmail: mailq | tail -1 returns "-- N Kbytes in M Requests." - queue_summary=$(eval "$SYS_MAIL_CMD_QUEUE_COUNT") + queue_summary=$(bash -c "$SYS_MAIL_CMD_QUEUE_COUNT" 2>/dev/null || echo "") print_info "$queue_summary" # Extract message count from summary line (last number is always message count) @@ -131,7 +131,7 @@ elif [ "$MTA" = "sendmail" ]; then echo "" # Cache queue list - single execution for all operations - queue_list=$(eval "$SYS_MAIL_CMD_QUEUE_LIST") + queue_list=$(bash -c "$SYS_MAIL_CMD_QUEUE_LIST" 2>/dev/null || echo "") print_header "Queue Details (first 50)" echo "$queue_list" | head -50 diff --git a/modules/maintenance/disk-space-analyzer.sh b/modules/maintenance/disk-space-analyzer.sh index bd1d57e..a9bf8fc 100755 --- a/modules/maintenance/disk-space-analyzer.sh +++ b/modules/maintenance/disk-space-analyzer.sh @@ -1,4 +1,5 @@ #!/bin/bash +set -eo pipefail ################################################################################ # Disk Space Analyzer (WinDirStat for Linux) @@ -17,6 +18,7 @@ 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/reference-db.sh" # Require root if [ "$EUID" -ne 0 ]; then @@ -24,6 +26,9 @@ if [ "$EUID" -ne 0 ]; then exit 1 fi +# Ensure cache is fresh (only rebuilds if > 1 hour old) +db_ensure_fresh 2>/dev/null || true + # Temp file for results TEMP_DIR="/tmp/disk-analysis-$$" mkdir -p "$TEMP_DIR" @@ -619,55 +624,51 @@ analyze_wordpress() { print_banner "WordPress Storage Analysis" echo "" - # Find WordPress installations + # Find WordPress installations from cache (instant lookup, no filesystem scan) show_progress "Finding WordPress installations" - local wp_paths=() + local wp_count=0 + local wp_data="" - # Common locations - if [ -d "/home" ]; then - while IFS= read -r wp_config; do - wp_dir=$(dirname "$wp_config") - wp_paths+=("$wp_dir") - done < <(find /home -name "wp-config.php" -type f 2>/dev/null) + # Get WordPress data from cache + if command -v db_get_all_wordpress &>/dev/null; then + wp_data=$(db_get_all_wordpress 2>/dev/null || true) fi - if [ -d "/var/www" ]; then - while IFS= read -r wp_config; do - wp_dir=$(dirname "$wp_config") - wp_paths+=("$wp_dir") - done < <(find /var/www -name "wp-config.php" -type f 2>/dev/null) + # Count WP installations + if [ -n "$wp_data" ]; then + wp_count=$(echo "$wp_data" | grep -c "^WP|" || echo 0) fi - if [ ${#wp_paths[@]} -eq 0 ]; then + if [ "$wp_count" -eq 0 ]; then echo -e "\r${DIM}No WordPress installations found${NC} " echo "" press_enter return fi - echo -e "\r${GREEN}✓${NC} Found ${#wp_paths[@]} WordPress installations " + echo -e "\r${GREEN}✓${NC} Found ${wp_count} WordPress installations " echo "" echo -e "${BOLD}WordPress Space Usage:${NC}" echo "───────────────────────────────────────────────────────────────" - for wp_dir in "${wp_paths[@]}"; do - # Get domain/user from path - domain=$(echo "$wp_dir" | awk -F'/' '{for(i=1;i<=NF;i++) if($i~/public_html|httpdocs|www/) print $(i-1)}' | tail -1) + # Process cached WordPress data + while IFS='|' read -r type domain path db_name db_user version plugins themes; do + if [ "$type" = "WP" ] && [ -d "$path" ]; then + # Calculate sizes + total_size=$(du -sh "$path" 2>/dev/null | awk '{print $1}') + uploads_size=$(du -sh "$path/wp-content/uploads" 2>/dev/null | awk '{print $1}') + plugins_size=$(du -sh "$path/wp-content/plugins" 2>/dev/null | awk '{print $1}') + cache_size=$(du -sh "$path/wp-content/cache" 2>/dev/null | awk '{print $1}') - # Calculate sizes - total_size=$(du -sh "$wp_dir" 2>/dev/null | awk '{print $1}') - uploads_size=$(du -sh "$wp_dir/wp-content/uploads" 2>/dev/null | awk '{print $1}') - plugins_size=$(du -sh "$wp_dir/wp-content/plugins" 2>/dev/null | awk '{print $1}') - cache_size=$(du -sh "$wp_dir/wp-content/cache" 2>/dev/null | awk '{print $1}') - - echo -e "${BOLD}$domain${NC} ($total_size)" - echo -e " Uploads: ${CYAN}${uploads_size:-0}${NC}" - echo -e " Plugins: ${CYAN}${plugins_size:-0}${NC}" - echo -e " Cache: ${CYAN}${cache_size:-0}${NC}" - echo "" - done + echo -e "${BOLD}$domain${NC} ($total_size)" + echo -e " Uploads: ${CYAN}${uploads_size:-0}${NC}" + echo -e " Plugins: ${CYAN}${plugins_size:-0}${NC}" + echo -e " Cache: ${CYAN}${cache_size:-0}${NC}" + echo "" + fi + done <<< "$wp_data" echo -e "${BOLD}Cleanup Suggestions:${NC}" echo " • Delete old revisions: wp post delete \$(wp post list --post_type=revision --format=ids)" diff --git a/modules/performance/hardware-health-check.sh b/modules/performance/hardware-health-check.sh index 8fd5eec..0aa3f01 100755 --- a/modules/performance/hardware-health-check.sh +++ b/modules/performance/hardware-health-check.sh @@ -15,6 +15,9 @@ source "$TOOLKIT_ROOT/lib/reference-db.sh" # Initialize system detection detect_system +# Ensure reference database is fresh (only rebuild if > 1 hour old) +db_ensure_fresh 2>/dev/null || true + # Load system info from reference database if [ -f "$TOOLKIT_ROOT/.sysref" ]; then SYS_HOSTNAME=$(grep "^SYS|HOSTNAME|" "$TOOLKIT_ROOT/.sysref" 2>/dev/null | cut -d'|' -f3) diff --git a/modules/performance/network-bandwidth-analyzer.sh b/modules/performance/network-bandwidth-analyzer.sh index 5d980a3..09e69ec 100755 --- a/modules/performance/network-bandwidth-analyzer.sh +++ b/modules/performance/network-bandwidth-analyzer.sh @@ -15,6 +15,9 @@ source "$TOOLKIT_ROOT/lib/reference-db.sh" # Initialize system detection detect_system +# Ensure reference database is fresh (only rebuild if > 1 hour old) +db_ensure_fresh 2>/dev/null || true + # Load system info from reference database if [ -f "$TOOLKIT_ROOT/.sysref" ]; then SYS_HOSTNAME=$(grep "^SYS|HOSTNAME|" "$TOOLKIT_ROOT/.sysref" 2>/dev/null | cut -d'|' -f3) diff --git a/modules/performance/nginx-varnish-manager.sh b/modules/performance/nginx-varnish-manager.sh index 6b73cd2..c6ec8a9 100755 --- a/modules/performance/nginx-varnish-manager.sh +++ b/modules/performance/nginx-varnish-manager.sh @@ -31,6 +31,9 @@ if [ "$EUID" -ne 0 ]; then exit 1 fi +# Ensure reference database is fresh (only rebuild if > 1 hour old) +db_ensure_fresh 2>/dev/null || true + # Configuration BACKUP_DIR="/root/nginx-varnish-backups" VARNISH_VCL="/etc/varnish/default.vcl" @@ -149,11 +152,28 @@ create_backup() { echo "$backup_path" } -# Get list of cPanel domains +# Get list of cPanel domains (from launcher cache, not filesystem) get_cpanel_domains() { + # Use launcher's cached domain list (instant lookup, already filtered by launcher) + # Fallback to filesystem scan only if cache unavailable + + if command -v db_get_all_domains &>/dev/null; then + # Use cached data from launcher (built on startup, instant O(n) lookup) + db_get_all_domains 2>/dev/null || { + # Fallback if cache fails (shouldn't happen if db_ensure_fresh was called) + get_cpanel_domains_fallback + } + else + # Library not available, use filesystem fallback + get_cpanel_domains_fallback + fi +} + +# Fallback domain discovery (only used if cache unavailable) +get_cpanel_domains_fallback() { local domains=() - # Get domains from cPanel user data + # Fallback: Get domains from cPanel user data if [ -d /var/cpanel/userdata ]; then while IFS= read -r domain_file; do local domain=$(basename "$domain_file") diff --git a/modules/security/bot-analyzer.sh b/modules/security/bot-analyzer.sh index 6257045..4d9d9ad 100755 --- a/modules/security/bot-analyzer.sh +++ b/modules/security/bot-analyzer.sh @@ -1311,7 +1311,7 @@ calculate_bot_fingerprint() { } close(tmpdir "/bot_fingerprints.txt") } - ' < "$TEMP_DIR/parsed_logs.txt" + ' < "$TEMP_DIR/parsed_logs.txt" 2>/dev/null || true # Create file if empty touch "$TEMP_DIR/bot_fingerprints.txt" @@ -1997,10 +1997,10 @@ generate_statistics() { close(tmpdir "/top_urls_raw.txt") }' - # Sort and limit results - sort -rn "$TEMP_DIR/top_sites_raw.txt" | head -5 > "$TEMP_DIR/top_sites.txt" - sort -rn "$TEMP_DIR/top_ips_raw.txt" | head -5 > "$TEMP_DIR/top_ips.txt" - sort -rn "$TEMP_DIR/top_urls_raw.txt" | head -5 > "$TEMP_DIR/top_urls.txt" + # Sort and limit results (files may not exist if no data) + [ -f "$TEMP_DIR/top_sites_raw.txt" ] && sort -rn "$TEMP_DIR/top_sites_raw.txt" | head -5 > "$TEMP_DIR/top_sites.txt" || touch "$TEMP_DIR/top_sites.txt" + [ -f "$TEMP_DIR/top_ips_raw.txt" ] && sort -rn "$TEMP_DIR/top_ips_raw.txt" | head -5 > "$TEMP_DIR/top_ips.txt" || touch "$TEMP_DIR/top_ips.txt" + [ -f "$TEMP_DIR/top_urls_raw.txt" ] && sort -rn "$TEMP_DIR/top_urls_raw.txt" | head -5 > "$TEMP_DIR/top_urls.txt" || touch "$TEMP_DIR/top_urls.txt" # Top 5 bots by request count (single decompression) cat "$TEMP_DIR/classified_bots.txt" 2>/dev/null | awk -F'|' '$9 != "unknown" {print $10}' | \ @@ -2070,7 +2070,7 @@ generate_comparison_report() { echo " Baseline (7-day avg): $baseline_requests requests" echo " Today: $total_requests requests" elif [ "$request_pct" -lt 50 ]; then - echo "🟢 LOW: Requests are $(($((100 - $request_pct))))% below baseline" + echo "🟢 LOW: Requests are $((100 - $request_pct))% below baseline" else echo "🟡 NORMAL: Requests within expected range" fi diff --git a/modules/security/malware-scanner.sh b/modules/security/malware-scanner.sh index 6efc4b3..38436eb 100755 --- a/modules/security/malware-scanner.sh +++ b/modules/security/malware-scanner.sh @@ -2156,7 +2156,7 @@ for scanner in "${available_scanners[@]}"; do # Extract scan results from event log (more reliable than parsing output) # Maldet logs to /usr/local/maldetect/logs/event_log # Use dynamic path search for portability across all platforms (FIXED Issue 2: comprehensive path discovery) - local event_log="" + event_log="" # Search standard locations in order of likelihood for search_path in \ @@ -2556,7 +2556,7 @@ STANDALONE_EOF fi # Inject MALDET_ONLY flag for Maldet-dedicated scans - local maldet_flag="${MALDET_ONLY:-0}" + maldet_flag="${MALDET_ONLY:-0}" if ! sed -i "s|PLACEHOLDER_MALDET_ONLY|$maldet_flag|" "$session_dir/scan.sh"; then echo -e "${RED}ERROR: Failed to inject MALDET_ONLY flag${NC}" return 1 diff --git a/modules/security/optimize-ct-limit.sh b/modules/security/optimize-ct-limit.sh index f244294..a31c37e 100755 --- a/modules/security/optimize-ct-limit.sh +++ b/modules/security/optimize-ct-limit.sh @@ -826,11 +826,8 @@ main() { echo "" fi - # Check if sysref database exists, build if needed - if [ ! -f "$SYSREF_DB" ] || [ ! -s "$SYSREF_DB" ]; then - print_status "Building system reference database (first run)..." - build_reference_database >/dev/null 2>&1 - fi + # Ensure reference database is fresh (only rebuild if > 1 hour old) + db_ensure_fresh >/dev/null 2>&1 # Run analysis check_server_resources diff --git a/modules/security/suspicious-login-monitor.sh b/modules/security/suspicious-login-monitor.sh index acf9f23..e9bcf30 100755 --- a/modules/security/suspicious-login-monitor.sh +++ b/modules/security/suspicious-login-monitor.sh @@ -1,4 +1,5 @@ #!/bin/bash +set -eo pipefail # # Suspicious Login Monitor - Integrated Security Analysis & Compromise Detection @@ -11,6 +12,9 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" TOOLKIT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" +# Source reference-db for cache support (avoid redundant /etc/passwd parsing) +source "$TOOLKIT_ROOT/lib/reference-db.sh" 2>/dev/null || true + # Configuration SUSPICIOUS_LOGIN_AUTO_BLOCK="${SUSPICIOUS_LOGIN_AUTO_BLOCK:-yes}" SUSPICIOUS_LOGIN_AUTO_SCAN="${SUSPICIOUS_LOGIN_AUTO_SCAN:-yes}" @@ -1673,7 +1677,7 @@ check_maintenance_mode() { fi if [ -n "$indicators" ]; then - echo "maintenance-mode:$(echo $indicators | sed 's/ $//')" + echo "maintenance-mode:$(sed 's/ $//' <<< "$indicators")" return 0 fi @@ -1823,6 +1827,10 @@ check_recent_password_changes() { fi # Check for locked accounts that were recently unlocked + # OPTIMIZATION: Read /etc/passwd ONCE, build nologin list, then check against it + # (avoiding redundant grep for each user in the loop) + local nologin_users=$(awk -F: '/\/sbin\/nologin|\/bin\/false/ {print $1}' /etc/passwd 2>/dev/null | tr '\n' '|') + local recently_unlocked=$(awk -F: -v cutoff=$(( $(date +%s) / 86400 - 7 )) ' # Field 2 starts with ! or !! = locked # If field 3 (last change) is recent and field 2 does NOT start with !, might have been unlocked @@ -1830,8 +1838,8 @@ check_recent_password_changes() { print $1 } ' /etc/shadow 2>/dev/null | while read user; do - # Check if account was previously locked (this is imperfect without history) - if grep "^$user:" /etc/passwd | grep -q "/sbin/nologin\|/bin/false"; then + # Check if account has nologin shell (from pre-built list) + if [[ "|$nologin_users" =~ \|$user\| ]]; then echo "$user" fi done) @@ -2947,6 +2955,11 @@ main() { echo -e "${CYAN}Starting Suspicious Login Monitor...${NC}" echo "" + # Ensure cache is fresh (only rebuilds if > 1 hour old) + if command -v db_ensure_fresh &>/dev/null; then + db_ensure_fresh 2>/dev/null || true + fi + # Detect panel local panel=$(detect_panel) echo "Detected panel: $panel" diff --git a/modules/website/website-slowness-diagnostics.sh b/modules/website/website-slowness-diagnostics.sh index f0a4d27..cc04e1b 100755 --- a/modules/website/website-slowness-diagnostics.sh +++ b/modules/website/website-slowness-diagnostics.sh @@ -1977,18 +1977,18 @@ calculate_performance_score() { # Calculate score (100 - issues) local score=$((100 - (critical_count * 10) - (warning_count * 2))) - [ $score -lt 0 ] && score=0 - [ $score -gt 100 ] && score=100 + [ "$score" -lt 0 ] && score=0 + [ "$score" -gt 100 ] && score=100 # Determine grade local grade - if [ $score -ge 90 ]; then + if [ "$score" -ge 90 ]; then grade="A - EXCELLENT" - elif [ $score -ge 80 ]; then + elif [ "$score" -ge 80 ]; then grade="B - GOOD" - elif [ $score -ge 70 ]; then + elif [ "$score" -ge 70 ]; then grade="C - FAIR" - elif [ $score -ge 60 ]; then + elif [ "$score" -ge 60 ]; then grade="D - POOR" else grade="F - CRITICAL" diff --git a/run.sh b/run.sh index 5ea3243..9af56cd 100755 --- a/run.sh +++ b/run.sh @@ -10,14 +10,22 @@ set -o pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" CLEANUP_FLAG="/tmp/.cleanup_requested" -# Save original history setting to restore even if interrupted -HISTORY_SETTING=$(set +o | grep history) +# Save original history state to restore even if interrupted +HISTORY_STATE="off" +if set -o | grep -q "^set +o history" 2>/dev/null; then + HISTORY_STATE="on" +fi RESTORE_HISTORY=false # Cleanup function: restore history even on error/interrupt cleanup_on_exit() { - if [ "$RESTORE_HISTORY" = true ]; then - eval "$HISTORY_SETTING" 2>/dev/null || true + if [ "$RESTORE_HISTORY" = true ] && [ -n "$HISTORY_STATE" ]; then + set +H # Disable history expansion temporarily + if [ "$HISTORY_STATE" = "on" ]; then + set -o history + else + set +o history + fi fi } @@ -59,7 +67,11 @@ source "$SCRIPT_DIR/launcher.sh" LAUNCHER_EXIT=$? # Re-enable history (trap will also do this) -eval "$HISTORY_SETTING" 2>/dev/null || true +if [ "$HISTORY_STATE" = "on" ]; then + set -o history 2>/dev/null || true +else + set +o history 2>/dev/null || true +fi RESTORE_HISTORY=false # Handle cleanup request (if user selected "Clean and remove traces")