#!/bin/bash # PHP-FPM Server Scanner Module # Handles enumeration of accounts/domains across entire server with filtering # Part of PHP Optimizer - Phase 3 Refactoring # Ensures full server-wide scanning and action capability # ============================================================================ # ACCOUNT ENUMERATION FUNCTIONS # ============================================================================ # Enumerate all accounts/users on the server enumerate_all_accounts() { local force_refresh="${1:-false}" local cache_file="/tmp/php-scanner-accounts-cache-$$" # Return cached results if available (unless force_refresh=true) if [ "$force_refresh" != "true" ] && [ -f "$cache_file" ]; then cat "$cache_file" return 0 fi # Delegate to user-manager.sh if available if type list_all_users >/dev/null 2>&1; then local accounts accounts=$(list_all_users) if [ -n "$accounts" ]; then echo "$accounts" | tee "$cache_file" return 0 fi fi # Fallback enumeration if user-manager.sh not available case "${SYS_CONTROL_PANEL:-unknown}" in cpanel) _enumerate_cpanel_accounts | tee "$cache_file" ;; plesk) _enumerate_plesk_accounts | tee "$cache_file" ;; interworx) _enumerate_interworx_accounts | tee "$cache_file" ;; *) _enumerate_system_accounts | tee "$cache_file" ;; esac } # cPanel account enumeration _enumerate_cpanel_accounts() { local cpanel_users_dir="${SYS_CPANEL_USERS_DIR:-/var/cpanel/users}" if [ -d "$cpanel_users_dir" ]; then ls "$cpanel_users_dir" 2>/dev/null | grep -v "^system\|^root\|^\." || true else awk -F: '{print $2}' /etc/trueuserdomains 2>/dev/null | sort -u || true fi } # Plesk account enumeration _enumerate_plesk_accounts() { if command_exists mysql && [ -f /etc/psa/.psa.shadow ]; then mysql -Ns psa -e "SELECT login FROM sys_users WHERE type='user'" 2>/dev/null || true else find /var/www/vhosts -maxdepth 1 -type d -printf "%f\n" 2>/dev/null | \ grep -v "^system$\|^default$\|^chroot$\|^\.skel$\|^fs$\|^fs-passwd$\|^\." || true fi } # InterWorx account enumeration _enumerate_interworx_accounts() { if [ -x "/usr/local/interworx/bin/listaccounts.pex" ]; then /usr/local/interworx/bin/listaccounts.pex --output user 2>/dev/null || true else if [ -d "/etc/httpd/conf.d" ]; then grep -h "^[[:space:]]*SuexecUserGroup" /etc/httpd/conf.d/vhost_*.conf 2>/dev/null | \ awk '{print $2}' | sort -u || true else find /home -maxdepth 1 -type d ! -name "home" ! -name "interworx" -printf "%f\n" 2>/dev/null | sort fi fi } # System-wide account enumeration (fallback) _enumerate_system_accounts() { awk -F: '($3 >= 500) && ($3 != 65534) {print $1}' /etc/passwd 2>/dev/null | \ grep -v "^root\|^nobody\|^ntp\|^mysql\|^www-data\|^apache\|^nginx" | \ sort -u || true } # ============================================================================ # DOMAIN ENUMERATION FUNCTIONS # ============================================================================ # Enumerate all domains for a specific user/account enumerate_user_domains() { [ -z "$1" ] && return 1 local username="$1" local force_refresh="${2:-false}" local cache_file="/tmp/php-scanner-domains-${username}-cache-$$" # Return cached results if available (unless force_refresh=true) if [ "$force_refresh" != "true" ] && [ -f "$cache_file" ]; then cat "$cache_file" return 0 fi # Delegate to user-manager.sh if available if type get_user_domains >/dev/null 2>&1; then local domains domains=$(get_user_domains "$username") if [ -n "$domains" ]; then echo "$domains" | tee "$cache_file" return 0 fi fi # Fallback domain enumeration case "${SYS_CONTROL_PANEL:-unknown}" in cpanel) _enumerate_cpanel_domains "$username" | tee "$cache_file" ;; plesk) _enumerate_plesk_domains "$username" | tee "$cache_file" ;; interworx) _enumerate_interworx_domains "$username" | tee "$cache_file" ;; *) echo "" ;; esac } # cPanel domain enumeration _enumerate_cpanel_domains() { local username="$1" [ -z "$username" ] && return 1 # Primary domain grep ": ${username}$" /etc/trueuserdomains 2>/dev/null | cut -d: -f1 || true # Addon domains if [ -f "/etc/userdatadomains" ]; then grep "==${username}$" /etc/userdatadomains 2>/dev/null | cut -d: -f1 || true fi } # Plesk domain enumeration _enumerate_plesk_domains() { local username="$1" [ -z "$username" ] && return 1 if command_exists mysql && [ -f /etc/psa/.psa.shadow ]; then mysql -Ns psa -e "SELECT d.name FROM domains d JOIN sys_users u ON d.id=u.domain_id WHERE u.login='$username'" 2>/dev/null || true elif [ -x "/usr/local/psa/bin/plesk" ]; then /usr/local/psa/bin/plesk bin site --list 2>/dev/null | grep -i "$username" || true elif [ -d "/var/www/vhosts/$username" ]; then echo "$username" fi } # InterWorx domain enumeration _enumerate_interworx_domains() { local username="$1" [ -z "$username" ] && return 1 if [ -x "/usr/local/interworx/bin/listaccounts.pex" ]; then /usr/local/interworx/bin/listaccounts.pex 2>/dev/null | \ awk -v user="$username" '$1 == user {print $2}' fi if [ -d "/etc/httpd/conf.d" ]; then grep -l "SuexecUserGroup ${username}" /etc/httpd/conf.d/vhost_*.conf 2>/dev/null | \ sed 's|.*/vhost_||; s|\.conf$||' | \ grep -vF "${username}." 2>/dev/null | \ sort -u fi } # Enumerate ALL domains on the server (across all users) enumerate_all_domains() { local force_refresh="${1:-false}" local cache_file="/tmp/php-scanner-all-domains-cache-$$" local progress_file="/tmp/php-scanner-progress-$$" # Return cached results if available (unless force_refresh=true) if [ "$force_refresh" != "true" ] && [ -f "$cache_file" ]; then cat "$cache_file" return 0 fi > "$progress_file" # Clear progress file local users local domain_list="" local user_count=0 local current_user=0 users=$(enumerate_all_accounts) user_count=$(echo "$users" | wc -l) while IFS= read -r username; do [ -z "$username" ] && continue current_user=$((current_user + 1)) echo "$current_user/$user_count: $username" >> "$progress_file" local domains domains=$(enumerate_user_domains "$username") if [ -n "$domains" ]; then domain_list="${domain_list}${domains}"$'\n' fi done <<< "$users" # Deduplicate and sort echo "$domain_list" | sort -u | grep -v "^$" | tee "$cache_file" rm -f "$progress_file" } # ============================================================================ # FILTERING FUNCTIONS # ============================================================================ # Filter accounts by name pattern filter_accounts_by_name() { local pattern="$1" [ -z "$pattern" ] && return 1 local all_accounts all_accounts=$(enumerate_all_accounts) echo "$all_accounts" | grep -i "$pattern" || true } # Filter accounts by resource usage threshold filter_accounts_by_threshold() { local threshold_mb="${1:-1000}" local direction="${2:-above}" # above or below local all_accounts all_accounts=$(enumerate_all_accounts) local filtered="" while IFS= read -r username; do [ -z "$username" ] && continue local usage_mb usage_mb=$(get_account_disk_usage "$username") if [ "$direction" = "above" ] && [ "$usage_mb" -gt "$threshold_mb" ]; then filtered="${filtered}${username}"$'\n' elif [ "$direction" = "below" ] && [ "$usage_mb" -lt "$threshold_mb" ]; then filtered="${filtered}${username}"$'\n' fi done <<< "$all_accounts" echo "$filtered" | grep -v "^$" } # Filter domains by name pattern filter_domains_by_name() { local pattern="$1" [ -z "$pattern" ] && return 1 local all_domains all_domains=$(enumerate_all_domains) echo "$all_domains" | grep -i "$pattern" || true } # Filter domains by traffic level filter_domains_by_traffic() { local min_requests="${1:-100}" # Minimum requests per second local direction="${2:-above}" # above or below local all_domains all_domains=$(enumerate_all_domains) local filtered="" while IFS= read -r domain; do [ -z "$domain" ] && continue local peak_concurrent peak_concurrent=$(get_domain_peak_concurrent "$domain") if [ "$direction" = "above" ] && [ "$peak_concurrent" -gt "$min_requests" ]; then filtered="${filtered}${domain}"$'\n' elif [ "$direction" = "below" ] && [ "$peak_concurrent" -lt "$min_requests" ]; then filtered="${filtered}${domain}"$'\n' fi done <<< "$all_domains" echo "$filtered" | grep -v "^$" } # Filter domains by optimization status filter_domains_by_optimization_status() { local status="${1:-needs_optimization}" # needs_optimization or already_optimized local all_domains all_domains=$(enumerate_all_domains) local filtered="" while IFS= read -r domain; do [ -z "$domain" ] && continue local is_optimized is_optimized=$(is_domain_optimized "$domain") if [ "$status" = "needs_optimization" ] && [ "$is_optimized" = "0" ]; then filtered="${filtered}${domain}"$'\n' elif [ "$status" = "already_optimized" ] && [ "$is_optimized" = "1" ]; then filtered="${filtered}${domain}"$'\n' fi done <<< "$all_domains" echo "$filtered" | grep -v "^$" } # ============================================================================ # DOMAIN INFORMATION FUNCTIONS # ============================================================================ # Get comprehensive PHP-FPM information for a domain get_domain_php_info() { local domain="$1" [ -z "$domain" ] && return 1 local owner username pool_name pool_path # Find domain owner owner=$(find_domain_owner "$domain") [ -z "$owner" ] && return 1 # Find PHP pool pool_name=$(php_detector_get_pool_name "$domain") pool_path=$(php_detector_get_pool_config "$domain") # Return info in structured format cat << EOF domain=$domain owner=$owner pool_name=$pool_name pool_path=$pool_path EOF } # Get disk usage for an account get_account_disk_usage() { local username="$1" [ -z "$username" ] && return 1 case "${SYS_CONTROL_PANEL:-unknown}" in cpanel) _get_cpanel_account_usage "$username" ;; plesk) _get_plesk_account_usage "$username" ;; interworx) _get_interworx_account_usage "$username" ;; *) _get_system_account_usage "$username" ;; esac } _get_cpanel_account_usage() { local username="$1" local home="/home/$username" if [ -d "$home" ]; then du -sb "$home" 2>/dev/null | awk '{printf "%.0f", $1/1048576}' fi } _get_plesk_account_usage() { local username="$1" local vhost_path="/var/www/vhosts/$username" if [ -d "$vhost_path" ]; then du -sb "$vhost_path" 2>/dev/null | awk '{printf "%.0f", $1/1048576}' fi } _get_interworx_account_usage() { local username="$1" local home="/home/$username" if [ -d "$home" ]; then du -sb "$home" 2>/dev/null | awk '{printf "%.0f", $1/1048576}' fi } _get_system_account_usage() { local username="$1" local home home=$(getent passwd "$username" | cut -d: -f6) if [ -n "$home" ] && [ -d "$home" ]; then du -sb "$home" 2>/dev/null | awk '{printf "%.0f", $1/1048576}' fi } # Get peak concurrent requests for a domain get_domain_peak_concurrent() { local domain="$1" [ -z "$domain" ] && return 1 local log_file log_file=$(find_domain_access_log "$domain") if [ -z "$log_file" ] || [ ! -f "$log_file" ]; then echo "0" return 1 fi # Analyze access log for peak concurrent requests (simplified) tail -100000 "$log_file" 2>/dev/null | \ awk '{print $4}' | \ sed 's/\[//' | \ awk -F: '{print $3}' | \ sort | uniq -c | \ sort -rn | head -1 | \ awk '{print $1}' || echo "0" } # Check if a domain is already optimized is_domain_optimized() { local domain="$1" [ -z "$domain" ] && return 1 # Check if pool has been recently optimized (within last 7 days) local pool_path pool_path=$(php_detector_get_pool_config "$domain") if [ -z "$pool_path" ] || [ ! -f "$pool_path" ]; then echo "0" return 0 fi # Check if pm.max_children is set to something other than default (40) local current_max current_max=$(grep -oP 'pm\.max_children\s*=\s*\K\d+' "$pool_path" 2>/dev/null || echo "40") if [ "$current_max" != "40" ]; then echo "1" else echo "0" fi } # Find which user owns a domain find_domain_owner() { local domain="$1" [ -z "$domain" ] && return 1 case "${SYS_CONTROL_PANEL:-unknown}" in cpanel) grep "^${domain}:" /etc/trueuserdomains 2>/dev/null | cut -d: -f2 ;; plesk) if command_exists mysql && [ -f /etc/psa/.psa.shadow ]; then mysql -Ns psa -e "SELECT u.login FROM domains d JOIN sys_users u ON d.id=u.domain_id WHERE d.name='$domain' LIMIT 1" 2>/dev/null fi ;; interworx) grep -l "^${domain}$" /etc/httpd/conf.d/vhost_*.conf 2>/dev/null | \ xargs grep "SuexecUserGroup" 2>/dev/null | \ head -1 | awk '{print $2}' ;; *) echo "" ;; esac } # Find access log for a domain find_domain_access_log() { local domain="$1" [ -z "$domain" ] && return 1 case "${SYS_CONTROL_PANEL:-unknown}" in cpanel) local owner owner=$(find_domain_owner "$domain") if [ -n "$owner" ]; then find "/home/${owner}/public_html" -maxdepth 2 -name "access_log*" -type f 2>/dev/null | head -1 fi ;; plesk) find "/var/www/vhosts/${domain}/statistics/logs" -name "access_log*" -type f 2>/dev/null | head -1 ;; interworx) find "/home/*/public_html/${domain}" -name "access_log*" -type f 2>/dev/null | head -1 ;; *) find /var/log -name "*${domain}*access*log*" -type f 2>/dev/null | head -1 ;; esac } # ============================================================================ # HELPER FUNCTIONS # ============================================================================ # Get count of total accounts get_total_account_count() { enumerate_all_accounts | wc -l } # Get count of total domains get_total_domain_count() { enumerate_all_domains | wc -l } # Clear enumeration cache clear_enumeration_cache() { rm -f /tmp/php-scanner-*-cache-* 2>/dev/null || true } # Display enumeration progress (for use in larger operations) show_enumeration_progress() { local current="$1" local total="$2" if [ -z "$total" ] || [ "$total" -eq 0 ]; then return 0 fi local percent=$((current * 100 / total)) local filled=$((percent / 5)) local empty=$((20 - filled)) printf "Progress: [%-20s] %3d%% (%d/%d)\r" \ "$(printf '#%.0s' $(seq 1 $filled))$(printf ' %.0s' $(seq 1 $empty))" \ "$percent" "$current" "$total" } export -f enumerate_all_accounts export -f enumerate_user_domains export -f enumerate_all_domains export -f filter_accounts_by_name export -f filter_accounts_by_threshold export -f filter_domains_by_name export -f filter_domains_by_traffic export -f filter_domains_by_optimization_status export -f get_domain_php_info export -f get_account_disk_usage export -f get_domain_peak_concurrent export -f is_domain_optimized export -f find_domain_owner export -f find_domain_access_log export -f get_total_account_count export -f get_total_domain_count export -f clear_enumeration_cache export -f show_enumeration_progress