Files
cschantz dea6f27b4d Fix ESCAPE issues in multiple library files
- lib/domain-discovery.sh: Added -- to grep command (1 fix)
- lib/reference-db.sh: Added -- to grep command (1 fix)
- lib/user-manager.sh: Added -- to grep command (1 fix)
- lib/email-functions.sh: Added -- to awk and grep commands (2 fixes)
- lib/php-config-manager.sh: Added -- to grep commands (3 fixes)
- lib/php-detector.sh: Added -- to grep command (1 fix)
Total: 9 ESCAPE fixes

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-09 16:38:55 -05:00

771 lines
26 KiB
Bash
Executable File

#!/bin/bash
#############################################################################
# User Manager Library
# Dynamic user listing and management for cPanel/Plesk/InterWorx
#############################################################################
# Source dependencies
if [ -z "$TOOLKIT_BASE_DIR" ]; then
_LIB_SRCDIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
[ -f "$_LIB_SRCDIR/common-functions.sh" ] && source "$_LIB_SRCDIR/common-functions.sh" || { echo "ERROR: common-functions.sh not found" >&2; return 1; }
[ -f "$_LIB_SRCDIR/system-detect.sh" ] && source "$_LIB_SRCDIR/system-detect.sh" || { echo "ERROR: system-detect.sh not found" >&2; return 1; }
fi
# Initialize temp session directory if not set
if [ -z "$TEMP_SESSION_DIR" ]; then
TEMP_SESSION_DIR="/tmp/server-toolkit-$$"
mkdir -p "$TEMP_SESSION_DIR" 2>/dev/null
fi
#############################################################################
# USER LISTING (Control Panel Specific)
#############################################################################
list_all_users() {
case "$SYS_CONTROL_PANEL" in
cpanel)
list_cpanel_users
;;
plesk)
list_plesk_users
;;
interworx)
list_interworx_users
;;
*)
list_system_users
;;
esac
}
# cPanel user listing
list_cpanel_users() {
local cpanel_users_dir="${SYS_CPANEL_USERS_DIR:-/var/cpanel/users}"
if [ -d "$cpanel_users_dir" ]; then
ls "$cpanel_users_dir" 2>/dev/null || true
else
# Fallback: parse /etc/trueuserdomains
awk -F: '{print $2}' /etc/trueuserdomains 2>/dev/null | sort -u || true
fi
}
# Plesk user listing
list_plesk_users() {
# Use plesk_list_users() if available (from plesk-helpers.sh)
if type plesk_list_users >/dev/null 2>&1; then
plesk_list_users
elif command_exists mysql && [ -f /etc/psa/.psa.shadow ]; then
# Fallback: Try MySQL query
mysql -Ns psa -e "SELECT login FROM sys_users WHERE type='user'" 2>/dev/null
else
# Last resort: list directories
find /var/www/vhosts -maxdepth 1 -type d -printf "%f\n" 2>/dev/null | \
grep -v "^system$\|^default$\|^chroot$\|^\.skel$\|^fs$\|^fs-passwd$" | \
grep -v "^\."
fi
}
# InterWorx user listing
list_interworx_users() {
if [ -x "/usr/local/interworx/bin/listaccounts.pex" ]; then
/usr/local/interworx/bin/listaccounts.pex --output user 2>/dev/null
else
# Fallback: Parse Apache vhost configs for SuexecUserGroup directives
# Each InterWorx account has vhost files in /etc/httpd/conf.d/
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
else
# Last resort: list /home directories (may include non-InterWorx users)
find /home -maxdepth 1 -type d ! -name "home" ! -name "interworx" -printf "%f\n" 2>/dev/null | sort
fi
fi
}
# System users (no control panel)
list_system_users() {
# List users with home directories in /home and valid shells
awk -F: '$6 ~ /^\/home\// && $7 !~ /nologin|false/ {print $1}' /etc/passwd
}
#############################################################################
# USER INFORMATION
#############################################################################
get_user_info() {
local username="$1"
case "$SYS_CONTROL_PANEL" in
cpanel)
get_cpanel_user_info "$username"
;;
plesk)
get_plesk_user_info "$username"
;;
interworx)
get_interworx_user_info "$username"
;;
*)
get_system_user_info "$username"
;;
esac
}
# cPanel user info
get_cpanel_user_info() {
local username="$1"
local user_file="${SYS_CPANEL_USERS_DIR:-/var/cpanel/users}/${username}"
if [ ! -f "$user_file" ]; then
echo "USER_EXISTS=no"
return 1
fi
# Parse cPanel user file
local primary_domain=$(grep "^DNS=" -- "$user_file" | cut -d= -f2)
local email=$(grep "^CONTACTEMAIL=" -- "$user_file" | cut -d= -f2)
# cPanel doesn't store HOMEDIR in user file - it's always /home/username
local home_dir="/home/${username}"
# Get addon/parked domains
local all_domains=$(grep "^DNS" -- "$user_file" | cut -d= -f2 | tr '\n' ' ')
# Get disk usage
local disk_used=$(du -sh "$home_dir" 2>/dev/null | awk '{print $1}')
echo "USER_EXISTS=yes"
echo "USERNAME=$username"
echo "PRIMARY_DOMAIN=$primary_domain"
echo "ALL_DOMAINS=$all_domains"
echo "EMAIL=$email"
echo "HOME_DIR=$home_dir"
echo "DISK_USED=$disk_used"
}
# Plesk user info
get_plesk_user_info() {
local username="$1"
if ! command_exists mysql || [ ! -f /etc/psa/.psa.shadow ]; then
echo "USER_EXISTS=no"
return 1
fi
local home_dir="/var/www/vhosts/${username}"
local primary_domain=$(mysql -Ns psa -e "SELECT name FROM domains WHERE id IN (SELECT domain_id FROM sys_users WHERE login='$username')" 2>/dev/null | head -1)
local disk_used=$(du -sh "$home_dir" 2>/dev/null | awk '{print $1}')
echo "USER_EXISTS=yes"
echo "USERNAME=$username"
echo "PRIMARY_DOMAIN=$primary_domain"
echo "HOME_DIR=$home_dir"
echo "DISK_USED=$disk_used"
}
# InterWorx user info
get_interworx_user_info() {
local username="$1"
local home_dir="/home/${username}"
if [ ! -d "$home_dir" ]; then
echo "USER_EXISTS=no"
return 1
fi
# Try to get primary domain from listaccounts.pex first
local primary_domain=""
if [ -x "/usr/local/interworx/bin/listaccounts.pex" ]; then
primary_domain=$(/usr/local/interworx/bin/listaccounts.pex 2>/dev/null | \
awk -v user="$username" '$1 == user {print $2; exit}')
fi
# Fallback: Parse vhost configs to find primary domain
if [ -z "$primary_domain" ]; then
primary_domain=$(grep -l "SuexecUserGroup ${username}" /etc/httpd/conf.d/vhost_*.conf 2>/dev/null | \
head -1 | sed 's|.*/vhost_||; s|\.conf$||')
fi
# Get all domains for this user from vhost configs
local all_domains=$(grep -l "SuexecUserGroup ${username}" /etc/httpd/conf.d/vhost_*.conf 2>/dev/null | \
sed 's|.*/vhost_||; s|\.conf$||' | tr '\n' ' ' | sed 's/[[:space:]]*$//')
# Get disk usage
local disk_used=$(du -sh "$home_dir" 2>/dev/null | awk '{print $1}')
# Try to get email from NodeWorx API (if available)
# Note: This requires nodeworx CLI which may need authentication
local email=""
if [ -x "/usr/local/interworx/bin/nodeworx.pex" ] && [ -n "$primary_domain" ]; then
email=$(nodeworx -u -n -c Siteworx -a listAccounts 2>/dev/null | \
grep "\"domain\" => \"$primary_domain\"" 2>/dev/null | head -1 | \
grep "\"email\"" 2>/dev/null | head -1 | sed 's/.*=> "\(.*\)".*/\1/')
fi
echo "USER_EXISTS=yes"
echo "USERNAME=$username"
echo "PRIMARY_DOMAIN=$primary_domain"
echo "ALL_DOMAINS=$all_domains"
echo "EMAIL=${email:-unknown}"
echo "HOME_DIR=$home_dir"
echo "DISK_USED=$disk_used"
}
# System user info (no control panel)
get_system_user_info() {
local username="$1"
local user_entry=$(getent passwd "$username")
if [ -z "$user_entry" ]; then
echo "USER_EXISTS=no"
return 1
fi
local home_dir=$(echo "$user_entry" | cut -d: -f6)
local disk_used=$(du -sh "$home_dir" 2>/dev/null | awk '{print $1}')
echo "USER_EXISTS=yes"
echo "USERNAME=$username"
echo "HOME_DIR=$home_dir"
echo "DISK_USED=$disk_used"
}
#############################################################################
# USER DOMAINS
#############################################################################
get_user_domains() {
[ -z "$1" ] && return 1
local username="$1"
case "$SYS_CONTROL_PANEL" in
cpanel)
get_cpanel_user_domains "$username"
;;
plesk)
get_plesk_user_domains "$username"
;;
interworx)
get_interworx_user_domains "$username"
;;
*)
echo ""
;;
esac
}
get_cpanel_user_domains() {
[ -z "$1" ] && return 1
local username="$1"
# Primary domain (format: domain: user)
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
}
get_plesk_user_domains() {
[ -z "$1" ] && return 1
local username="$1"
# Try MySQL query first
if command_exists mysql && [ -f /etc/psa/.psa.shadow ]; then
local domains=$(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)
if [ -n "$domains" ]; then
echo "$domains"
return 0
fi
fi
# Fallback: Use Plesk CLI if available
if [ -x "/usr/local/psa/bin/plesk" ]; then
/usr/local/psa/bin/plesk bin site --list 2>/dev/null | grep -i "$username" || true
fi
# Last resort: Check if vhosts directory exists for this user
if [ -d "/var/www/vhosts/$username" ]; then
echo "$username"
fi
}
get_interworx_user_domains() {
[ -z "$1" ] && return 1
local username="$1"
# Method 1: Use listaccounts.pex to get primary domain
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
# Method 2: Parse vhost configs to get ALL domains (primary + secondary/addon)
# InterWorx creates vhost_domain.conf for each domain, with SuexecUserGroup directive
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
}
#############################################################################
# USER DATABASES
#############################################################################
get_user_databases() {
local username="$1"
case "$SYS_CONTROL_PANEL" in
cpanel)
get_cpanel_user_databases "$username"
;;
plesk)
get_plesk_user_databases "$username"
;;
interworx)
get_interworx_user_databases "$username"
;;
*)
# Try to find databases matching username pattern
mysql -e "SHOW DATABASES" 2>/dev/null | grep "^${username}_"
;;
esac
}
get_cpanel_user_databases() {
local username="$1"
# cPanel databases typically follow pattern: username_dbname
mysql -e "SHOW DATABASES" 2>/dev/null | grep "^${username}_" 2>/dev/null || true
}
get_plesk_user_databases() {
local username="$1"
if command_exists mysql && [ -f /etc/psa/.psa.shadow ]; then
mysql -Ns psa -e "SELECT db.name FROM data_bases db JOIN domains d ON db.dom_id=d.id JOIN sys_users u ON d.id=u.domain_id WHERE u.login='$username'" 2>/dev/null
fi
}
get_interworx_user_databases() {
local username="$1"
# InterWorx uses the first 8 characters of the PRIMARY DOMAIN as database prefix
# NOT the username! (e.g., domain example.com → prefix: examplec_)
# Get primary domain for this user
local primary_domain=""
if [ -x "/usr/local/interworx/bin/listaccounts.pex" ]; then
primary_domain=$(/usr/local/interworx/bin/listaccounts.pex 2>/dev/null | \
awk -v user="$username" '$1 == user {print $2; exit}')
fi
# Fallback: try to find from vhost configs
if [ -z "$primary_domain" ]; then
primary_domain=$(grep -l "SuexecUserGroup ${username}" /etc/httpd/conf.d/vhost_*.conf 2>/dev/null | \
head -1 | sed 's|.*/vhost_||; s|\.conf$||')
fi
if [ -z "$primary_domain" ]; then
# No domain found, try username pattern as last resort
mysql -e "SHOW DATABASES" 2>/dev/null | grep "^${username}_" || true
return
fi
# Get first 8 characters of domain (removing dots) as database prefix
local db_prefix=$(echo "$primary_domain" | sed 's/\.//g' | cut -c1-8)
# Query MySQL for databases with this prefix
mysql -e "SHOW DATABASES" 2>/dev/null | grep "^${db_prefix}_" || true
}
#############################################################################
# USER LOG FILES
#############################################################################
get_user_log_files() {
local username="$1"
local domains=$(get_user_domains "$username")
case "$SYS_CONTROL_PANEL" in
cpanel)
# Iterate safely over domains (handles spaces in domain names)
echo "$domains" | while IFS= read -r domain; do
[ -z "$domain" ] && continue
echo "${SYS_LOG_DIR}/${domain}"
echo "${SYS_LOG_DIR}/${domain}-ssl_log"
done
;;
plesk)
echo "/var/www/vhosts/${username}/statistics/logs/access_log"
echo "/var/www/vhosts/${username}/statistics/logs/error_log"
# Iterate safely over domains (handles spaces in domain names)
echo "$domains" | while IFS= read -r domain; do
[ -z "$domain" ] && continue
echo "/var/www/vhosts/${domain}/statistics/logs/access_log"
echo "/var/www/vhosts/${domain}/statistics/logs/error_log"
done
;;
interworx)
# Iterate safely over domains (handles spaces in domain names)
echo "$domains" | while IFS= read -r domain; do
[ -z "$domain" ] && continue
echo "/home/${username}/var/${domain}/logs/access_log"
echo "/home/${username}/var/${domain}/logs/error_log"
done
;;
esac | grep -v "^$" | sort -u
}
#############################################################################
# USER SELECTION MENU
#############################################################################
select_user_interactive() {
local prompt="${1:-Select a user}"
local users=($(list_all_users))
local total_users=${#users[@]}
if [ "${total_users:-0}" -eq 0 ]; then
print_error "No users found" >&2
return 1
fi
# Build user info cache to avoid repeated get_user_domains calls
declare -A user_primary_domain
declare -A user_domain_count
for user in "${users[@]}"; do
local domains=$(get_user_domains "$user" 2>/dev/null | grep -v "^$")
if [ -n "$domains" ]; then
user_domain_count["$user"]=$(echo "$domains" | wc -l)
user_primary_domain["$user"]=$(echo "$domains" | head -1)
else
user_domain_count["$user"]=0
user_primary_domain["$user"]="(no domains)"
fi
done
# Send all display output to stderr so it shows on screen (not captured by $(...))
{
echo ""
print_section "$prompt"
echo ""
echo "Found $total_users user(s) on this server"
echo "───────────────────────────────────────────────────────────────────────────────"
# Auto-show list if 10 or fewer users
if [ "${total_users:-0}" -le 10 ]; then
echo ""
for user in "${users[@]}"; do
echo -e " ${GREEN}$user${NC} - ${user_primary_domain[$user]} (${user_domain_count[$user]} domains)"
done
fi
echo ""
echo "───────────────────────────────────────────────────────────────────────────────"
echo ""
echo "Options:"
if [ "${total_users:-0}" -gt 10 ]; then
echo " L - List all $total_users users"
fi
echo " S [text] - Search/filter users (e.g., 's pick' or 's example.com')"
echo " [username] - Enter exact username"
echo " A - All users (system-wide scan)"
echo " 0 - Cancel/Go back"
echo ""
} >&2
read -p "Enter choice> " choice
case "$choice" in
[Aa]|ALL|all)
echo "ALL"
return 0
;;
[Ss]\ *|[Ss])
# Search mode
local search_term="${choice#[Ss] }" # Remove 'S ' prefix
search_term="${search_term#[Ss]}" # Remove 'S' if no space
if [ -z "$search_term" ]; then
# No search term provided, ask for it
read -p "Enter search term (username or domain)> " search_term >&2
fi
if [ -z "$search_term" ]; then
print_error "No search term provided" >&2
return 1
fi
# Search and build match array
local -a matched_users
local -a menu_items
for user in "${users[@]}"; do
# Case-insensitive partial match in username or domain
if [[ "${user,,}" == *"${search_term,,}"* ]] || [[ "${user_primary_domain[$user],,}" == *"${search_term,,}"* ]]; then
matched_users+=("$user")
menu_items+=("$user - ${user_primary_domain[$user]} (${user_domain_count[$user]} domains)")
fi
done
if [ ${#matched_users[@]} -eq 0 ]; then
print_error "No users found matching '$search_term'" >&2
return 1
elif [ ${#matched_users[@]} -eq 1 ]; then
# Single match - ask for confirmation
{
echo ""
echo "Found 1 match: ${matched_users[0]} - ${user_primary_domain[${matched_users[0]}]} (${user_domain_count[${matched_users[0]}]} domains)"
echo ""
} >&2
read -p "Use this user? (Y/n)> " confirm >&2
if [[ ! $confirm =~ ^[Nn]$ ]]; then
echo "${matched_users[0]}"
return 0
else
return 1
fi
else
# Multiple matches - show interactive arrow-key menu
{
echo ""
echo "Found ${#matched_users[@]} matches for '$search_term'"
echo "Use arrow keys (↑/↓) to navigate, Enter to select:"
echo ""
} >&2
PS3="Select user> "
select option in "${menu_items[@]}" "Cancel"; do
if [ "$option" = "Cancel" ]; then
return 1
elif [ -n "$option" ]; then
# Extract username from selected menu item (before the ' - ')
local selected_user="${matched_users[$((REPLY-1))]}"
echo "$selected_user"
return 0
fi
done >&2
fi
;;
[Ll]|LIST|list)
# Show full list
{
echo ""
echo "Complete user list ($total_users users):"
echo "───────────────────────────────────────────────────────────────────────────────"
for user in "${users[@]}"; do
echo -e " ${GREEN}$user${NC} - ${user_primary_domain[$user]} (${user_domain_count[$user]} domains)"
done
echo "───────────────────────────────────────────────────────────────────────────────"
echo ""
} >&2
# Ask again after showing list
read -p "Enter username (or A for all, 0 to cancel)> " choice
# Re-evaluate choice
if [[ " ${users[@]} " =~ " ${choice} " ]]; then
echo "$choice"
return 0
elif [ "$choice" = "A" ] || [ "$choice" = "a" ]; then
echo "ALL"
return 0
elif [ "$choice" = "0" ]; then
return 1
else
print_error "User '$choice' not found" >&2
return 1
fi
;;
0|cancel|back)
return 1
;;
"")
print_error "No input provided" >&2
return 1
;;
*)
# Check if it's an exact username match
if [[ " ${users[@]} " =~ " ${choice} " ]]; then
echo "$choice"
return 0
fi
# Not exact match
print_error "User '$choice' not found" >&2
if [ "${total_users:-0}" -gt 10 ]; then
echo " Tip: Type 'L' to list all users" >&2
fi
return 1
;;
esac
}
#############################################################################
# USER PROCESSES
#############################################################################
get_user_processes() {
local username="$1"
ps aux | grep "$username" 2>/dev/null | grep -v grep
}
get_user_top_processes() {
local username="$1"
local limit="${2:-10}"
ps aux | grep "$username" 2>/dev/null | grep -v grep | sort -k3 -rn | head -n "$limit"
}
#############################################################################
# DATABASE HELPER FUNCTIONS
#############################################################################
get_database_owner() {
local db_name="$1"
# Try to determine owner from database name prefix (common cPanel convention)
# Database names are typically: username_dbname
local prefix=$(echo "$db_name" | cut -d_ -f1)
# Check if this prefix matches a user (iterate safely over usernames)
list_all_users | while IFS= read -r user; do
[ -z "$user" ] && continue
if [ "$user" = "$prefix" ]; then
echo "$user"
return 0
fi
done
# If no match, return unknown
echo "unknown"
}
get_database_domain() {
local db_name="$1"
local owner=$(get_database_owner "$db_name")
# Try to get primary domain for the owner
if [ "$owner" != "unknown" ]; then
get_user_domains "$owner" 2>/dev/null | head -1
else
echo ""
fi
}
#############################################################################
# WORDPRESS DETECTION
#############################################################################
find_user_wordpress_sites() {
local username="$1"
local home_dir=$(get_user_info "$username" | grep "^HOME_DIR=" | cut -d= -f2)
if [ -z "$home_dir" ] || [ ! -d "$home_dir" ]; then
return 1
fi
# Find wp-config.php files
find "$home_dir" -name "wp-config.php" -type f 2>/dev/null | while read wp_config; do
local wp_dir=$(dirname "$wp_config")
local domain=$(basename "$(dirname "$wp_dir")" 2>/dev/null)
# Try to get actual domain from wp-config
local site_url=$(grep "WP_SITEURL\|WP_HOME" "$wp_config" | head -1 | grep -oP "https?://\K[^/'\"]+" 2>/dev/null || true)
if [ -n "$site_url" ]; then
echo "${site_url}|${wp_dir}"
else
echo "${domain}|${wp_dir}"
fi
done
}
#############################################################################
# DISPLAY FUNCTIONS
#############################################################################
show_user_summary() {
local username="$1"
print_section "User Summary: $username"
# Get user info
local user_info=$(get_user_info "$username")
if echo "$user_info" | grep -q "USER_EXISTS=no"; then
print_error "User does not exist"
return 1
fi
# Parse info
local primary_domain=$(echo "$user_info" | grep "^PRIMARY_DOMAIN=" | cut -d= -f2)
local home_dir=$(echo "$user_info" | grep "^HOME_DIR=" | cut -d= -f2)
local disk_used=$(echo "$user_info" | grep "^DISK_USED=" | cut -d= -f2)
# Display
echo " Username: $username"
[ -n "$primary_domain" ] && echo " Primary Domain: $primary_domain"
echo " Home Directory: $home_dir"
echo " Disk Usage: $disk_used"
echo ""
# Domains
local domains=$(get_user_domains "$username")
local domain_count=$(echo "$domains" | grep -v "^$" | wc -l)
echo " Domains ($domain_count):"
echo "$domains" | sed 's/^/ - /'
echo ""
# Databases
local databases=$(get_user_databases "$username")
local db_count=$(echo "$databases" | grep -v "^$" | wc -l)
echo " Databases ($db_count):"
echo "$databases" | sed 's/^/ - /'
echo ""
}
show_all_users_summary() {
print_section "All Users Summary"
local users=($(list_all_users))
local total_users=${#users[@]}
echo " Total Users: $total_users"
echo ""
printf " ${BOLD}%-20s %-30s %10s %10s${NC}\n" "Username" "Primary Domain" "Domains" "Databases"
echo " ────────────────────────────────────────────────────────────────────"
for user in "${users[@]}"; do
local primary=$(get_user_domains "$user" | head -1)
local domain_count=$(get_user_domains "$user" | grep -v "^$" | wc -l)
local db_count=$(get_user_databases "$user" | grep -v "^$" | wc -l)
printf " %-20s %-30s %10s %10s\n" "$user" "$primary" "$domain_count" "$db_count"
done
echo ""
}
# Export all functions for use in other scripts
export -f list_all_users
export -f list_cpanel_users
export -f list_plesk_users
export -f list_interworx_users
export -f list_system_users
export -f get_user_info
export -f get_user_domains
export -f get_cpanel_user_domains
export -f get_plesk_user_domains
export -f get_interworx_user_domains
export -f get_user_databases
export -f get_user_log_files
export -f select_user_interactive