2025-11-04 10:04:57 +00:00
|
|
|
#!/bin/bash
|
|
|
|
|
|
|
|
|
|
# ==============================================================================
|
|
|
|
|
# SFTP User Diagnostic Script
|
|
|
|
|
# Use this script to diagnose SFTP/SSH account creation issues
|
|
|
|
|
# ==============================================================================
|
|
|
|
|
|
2025-11-04 10:09:11 +00:00
|
|
|
# Get username from argument or prompt
|
|
|
|
|
if [ -z "$1" ]; then
|
|
|
|
|
echo "Usage: $0 <username>"
|
|
|
|
|
echo "Example: $0 mightyuser1"
|
|
|
|
|
exit 1
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
USERNAME="$1"
|
2025-11-04 10:04:57 +00:00
|
|
|
|
|
|
|
|
echo "=============================================================================="
|
|
|
|
|
echo "SFTP/SSH User Diagnostic Script"
|
|
|
|
|
echo "Checking user: $USERNAME"
|
|
|
|
|
echo "Timestamp: $(date)"
|
|
|
|
|
echo "=============================================================================="
|
|
|
|
|
echo
|
|
|
|
|
|
|
|
|
|
# Colors for output
|
|
|
|
|
RED='\033[0;31m'
|
|
|
|
|
GREEN='\033[0;32m'
|
|
|
|
|
YELLOW='\033[1;33m'
|
|
|
|
|
NC='\033[0m' # No Color
|
|
|
|
|
|
|
|
|
|
# Function to print colored output
|
|
|
|
|
print_status() {
|
|
|
|
|
local status=$1
|
|
|
|
|
local message=$2
|
|
|
|
|
if [ "$status" = "OK" ]; then
|
|
|
|
|
echo -e "${GREEN}[OK]${NC} $message"
|
|
|
|
|
elif [ "$status" = "ERROR" ]; then
|
|
|
|
|
echo -e "${RED}[ERROR]${NC} $message"
|
|
|
|
|
elif [ "$status" = "WARNING" ]; then
|
|
|
|
|
echo -e "${YELLOW}[WARNING]${NC} $message"
|
|
|
|
|
else
|
|
|
|
|
echo "[INFO] $message"
|
|
|
|
|
fi
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
echo "=== 1. USER ACCOUNT CHECK ==="
|
|
|
|
|
if id "$USERNAME" &>/dev/null; then
|
|
|
|
|
print_status "OK" "User account exists: $USERNAME"
|
|
|
|
|
echo " User ID: $(id -u $USERNAME)"
|
|
|
|
|
echo " Group ID: $(id -g $USERNAME)"
|
|
|
|
|
echo " Groups: $(id -Gn $USERNAME)"
|
|
|
|
|
echo " Home Directory: $(getent passwd $USERNAME | cut -d: -f6)"
|
|
|
|
|
echo " Shell: $(getent passwd $USERNAME | cut -d: -f7)"
|
|
|
|
|
else
|
|
|
|
|
print_status "ERROR" "User account does NOT exist: $USERNAME"
|
|
|
|
|
echo " Run: id $USERNAME"
|
|
|
|
|
fi
|
|
|
|
|
echo
|
|
|
|
|
|
|
|
|
|
echo "=== 2. USER GROUPS CHECK ==="
|
|
|
|
|
if id "$USERNAME" &>/dev/null; then
|
|
|
|
|
GROUPS=$(id -Gn "$USERNAME")
|
|
|
|
|
if echo "$GROUPS" | grep -q "sftpusers"; then
|
|
|
|
|
print_status "OK" "User is in sftpusers group (SFTP-only access)"
|
|
|
|
|
elif echo "$GROUPS" | grep -q "sshusers"; then
|
|
|
|
|
print_status "OK" "User is in sshusers group (SSH+SFTP access)"
|
|
|
|
|
else
|
|
|
|
|
print_status "ERROR" "User is NOT in sftpusers or sshusers group!"
|
|
|
|
|
echo " Current groups: $GROUPS"
|
|
|
|
|
echo " Fix: usermod -aG sftpusers $USERNAME (for SFTP-only)"
|
|
|
|
|
echo " Fix: usermod -aG sshusers $USERNAME (for SSH+SFTP)"
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
if echo "$GROUPS" | grep -q "litespeed"; then
|
|
|
|
|
print_status "OK" "User is in litespeed group"
|
|
|
|
|
else
|
|
|
|
|
print_status "WARNING" "User is NOT in litespeed group (may affect file access)"
|
|
|
|
|
fi
|
|
|
|
|
fi
|
|
|
|
|
echo
|
|
|
|
|
|
|
|
|
|
echo "=== 3. HOME DIRECTORY CHECK ==="
|
|
|
|
|
if id "$USERNAME" &>/dev/null; then
|
|
|
|
|
USER_HOME=$(getent passwd $USERNAME | cut -d: -f6)
|
|
|
|
|
if [ -d "$USER_HOME" ]; then
|
|
|
|
|
print_status "OK" "Home directory exists: $USER_HOME"
|
|
|
|
|
echo " Ownership: $(stat -c '%U:%G' $USER_HOME)"
|
|
|
|
|
echo " Permissions: $(stat -c '%a' $USER_HOME)"
|
|
|
|
|
|
|
|
|
|
# Check if home directory is owned by root (required for chroot)
|
|
|
|
|
if [ "$(stat -c '%U' $USER_HOME)" = "root" ]; then
|
|
|
|
|
print_status "OK" "Home directory is owned by root (required for chroot)"
|
|
|
|
|
else
|
|
|
|
|
print_status "ERROR" "Home directory must be owned by root for chroot!"
|
|
|
|
|
echo " Fix: chown root:root $USER_HOME"
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# Check permissions
|
|
|
|
|
PERMS=$(stat -c '%a' $USER_HOME)
|
|
|
|
|
if [ "$PERMS" = "755" ] || [ "$PERMS" = "751" ]; then
|
|
|
|
|
print_status "OK" "Home directory permissions are correct: $PERMS"
|
|
|
|
|
else
|
|
|
|
|
print_status "WARNING" "Home directory permissions should be 755 or 751, current: $PERMS"
|
|
|
|
|
echo " Fix: chmod 755 $USER_HOME"
|
|
|
|
|
fi
|
|
|
|
|
else
|
|
|
|
|
print_status "ERROR" "Home directory does NOT exist: $USER_HOME"
|
|
|
|
|
fi
|
|
|
|
|
fi
|
|
|
|
|
echo
|
|
|
|
|
|
|
|
|
|
echo "=== 4. DIRECTORY STRUCTURE CHECK ==="
|
|
|
|
|
if id "$USERNAME" &>/dev/null; then
|
|
|
|
|
USER_HOME=$(getent passwd $USERNAME | cut -d: -f6)
|
|
|
|
|
if [ -d "$USER_HOME/data" ]; then
|
|
|
|
|
print_status "OK" "Data directory exists: $USER_HOME/data"
|
2025-11-04 10:09:11 +00:00
|
|
|
echo " Ownership: $(stat -c '%U:%G' $USER_HOME/data 2>/dev/null || echo 'N/A')"
|
|
|
|
|
echo " Permissions: $(stat -c '%a' $USER_HOME/data 2>/dev/null || echo 'N/A')"
|
2025-11-04 10:04:57 +00:00
|
|
|
|
|
|
|
|
if [ -d "$USER_HOME/data/ROOT" ]; then
|
|
|
|
|
print_status "OK" "ROOT directory exists: $USER_HOME/data/ROOT"
|
|
|
|
|
# Check if it's a mount point
|
2025-11-04 10:11:18 +00:00
|
|
|
# mountpoint command is part of util-linux (installed by default on AlmaLinux 9.6)
|
|
|
|
|
# Fallback to checking /proc/mounts if mountpoint is not available
|
|
|
|
|
IS_MOUNTED=false
|
|
|
|
|
if command -v mountpoint >/dev/null 2>&1; then
|
|
|
|
|
if mountpoint -q "$USER_HOME/data/ROOT" 2>/dev/null; then
|
|
|
|
|
IS_MOUNTED=true
|
|
|
|
|
fi
|
|
|
|
|
else
|
|
|
|
|
# Fallback: check /proc/mounts
|
|
|
|
|
if grep -q " $USER_HOME/data/ROOT " /proc/mounts 2>/dev/null; then
|
|
|
|
|
IS_MOUNTED=true
|
|
|
|
|
fi
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
if [ "$IS_MOUNTED" = true ]; then
|
2025-11-04 10:04:57 +00:00
|
|
|
print_status "OK" "ROOT is properly mounted (bind mount)"
|
2025-11-04 10:09:11 +00:00
|
|
|
MOUNT_INFO=$(mount | grep "$USER_HOME/data/ROOT" 2>/dev/null || echo "No mount info found")
|
|
|
|
|
echo " Mount info: $MOUNT_INFO"
|
|
|
|
|
|
|
|
|
|
# Check webroot permissions
|
|
|
|
|
WEBROOT_DIR="/var/www/webroot/ROOT"
|
|
|
|
|
if [ -d "$WEBROOT_DIR" ]; then
|
|
|
|
|
WEBROOT_PERMS=$(stat -c '%a' "$WEBROOT_DIR" 2>/dev/null)
|
|
|
|
|
WEBROOT_GROUP=$(stat -c '%G' "$WEBROOT_DIR" 2>/dev/null)
|
|
|
|
|
WEBROOT_OWNER=$(stat -c '%U:%G' "$WEBROOT_DIR" 2>/dev/null)
|
|
|
|
|
echo " Webroot permissions: $WEBROOT_PERMS ($WEBROOT_OWNER)"
|
|
|
|
|
|
|
|
|
|
# Check if group has write permission
|
|
|
|
|
GROUP_WRITE_BIT=$(echo "$WEBROOT_PERMS" | cut -c2)
|
|
|
|
|
if [ "$GROUP_WRITE_BIT" = "4" ] || [ "$GROUP_WRITE_BIT" = "5" ] || [ "$GROUP_WRITE_BIT" = "1" ] || [ "$GROUP_WRITE_BIT" = "0" ]; then
|
|
|
|
|
print_status "WARNING" "Webroot does NOT have group write permissions (current: $WEBROOT_PERMS)"
|
|
|
|
|
echo " Fix: chmod -R g+w $WEBROOT_DIR"
|
|
|
|
|
else
|
|
|
|
|
print_status "OK" "Webroot has group write permissions"
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
if [ "$WEBROOT_GROUP" != "litespeed" ]; then
|
|
|
|
|
print_status "WARNING" "Webroot group is not litespeed (current: $WEBROOT_GROUP)"
|
|
|
|
|
echo " Fix: chgrp -R litespeed $WEBROOT_DIR"
|
|
|
|
|
fi
|
|
|
|
|
fi
|
2025-11-04 10:04:57 +00:00
|
|
|
else
|
|
|
|
|
print_status "WARNING" "ROOT directory exists but is not mounted"
|
|
|
|
|
echo " Fix: mount --bind /var/www/webroot/ROOT $USER_HOME/data/ROOT"
|
|
|
|
|
fi
|
|
|
|
|
else
|
|
|
|
|
print_status "ERROR" "ROOT directory does NOT exist: $USER_HOME/data/ROOT"
|
|
|
|
|
fi
|
|
|
|
|
else
|
|
|
|
|
print_status "ERROR" "Data directory does NOT exist: $USER_HOME/data"
|
|
|
|
|
fi
|
|
|
|
|
fi
|
|
|
|
|
echo
|
|
|
|
|
|
|
|
|
|
echo "=== 5. PASSWORD CHECK ==="
|
|
|
|
|
if id "$USERNAME" &>/dev/null; then
|
|
|
|
|
# Check if password is set (this is tricky - we can only check if shadow entry exists)
|
|
|
|
|
if grep -q "^$USERNAME:" /etc/shadow; then
|
|
|
|
|
print_status "OK" "User has shadow entry (password record exists)"
|
|
|
|
|
# Check if password field is empty or has '!' (locked)
|
|
|
|
|
PWD_FIELD=$(grep "^$USERNAME:" /etc/shadow | cut -d: -f2)
|
|
|
|
|
if [ -z "$PWD_FIELD" ] || [ "$PWD_FIELD" = "*" ] || [ "$PWD_FIELD" = "!" ]; then
|
|
|
|
|
print_status "ERROR" "User password appears to be LOCKED or NOT SET!"
|
|
|
|
|
echo " Password field: $PWD_FIELD"
|
|
|
|
|
echo " Fix: echo '$USERNAME:NEW_PASSWORD' | chpasswd"
|
|
|
|
|
else
|
|
|
|
|
print_status "OK" "User password appears to be set (hashed)"
|
|
|
|
|
fi
|
|
|
|
|
else
|
|
|
|
|
print_status "ERROR" "User does NOT have shadow entry!"
|
|
|
|
|
fi
|
|
|
|
|
fi
|
|
|
|
|
echo
|
|
|
|
|
|
|
|
|
|
echo "=== 6. SSH CONFIGURATION CHECK ==="
|
|
|
|
|
echo "Checking SSH daemon configuration..."
|
|
|
|
|
|
|
|
|
|
# Check if sshd_config.d directory exists and is included
|
|
|
|
|
if [ -d "/etc/ssh/sshd_config.d" ]; then
|
|
|
|
|
print_status "OK" "SSH config.d directory exists"
|
|
|
|
|
if grep -q "^Include" /etc/ssh/sshd_config | grep -q "sshd_config.d"; then
|
|
|
|
|
print_status "OK" "sshd_config.d is included in main config"
|
|
|
|
|
else
|
|
|
|
|
print_status "WARNING" "sshd_config.d may not be included (check Include directive)"
|
|
|
|
|
fi
|
|
|
|
|
else
|
|
|
|
|
print_status "WARNING" "sshd_config.d directory does not exist"
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# Check addon config file
|
|
|
|
|
ADDON_CONFIG="/etc/ssh/sshd_config.d/99-sftp-addon.conf"
|
|
|
|
|
if [ -f "$ADDON_CONFIG" ]; then
|
|
|
|
|
print_status "OK" "Addon config file exists: $ADDON_CONFIG"
|
|
|
|
|
echo " Contents:"
|
|
|
|
|
cat "$ADDON_CONFIG" | sed 's/^/ /'
|
|
|
|
|
else
|
|
|
|
|
print_status "WARNING" "Addon config file does NOT exist: $ADDON_CONFIG"
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# Check main sshd_config
|
|
|
|
|
if grep -qE "^PasswordAuthentication\s+yes" /etc/ssh/sshd_config; then
|
|
|
|
|
print_status "OK" "PasswordAuthentication is enabled in main config"
|
|
|
|
|
elif grep -qE "^PasswordAuthentication\s+no" /etc/ssh/sshd_config; then
|
|
|
|
|
print_status "ERROR" "PasswordAuthentication is DISABLED in main config!"
|
|
|
|
|
echo " Fix: sed -i 's/^PasswordAuthentication no/PasswordAuthentication yes/' /etc/ssh/sshd_config"
|
|
|
|
|
else
|
|
|
|
|
print_status "WARNING" "PasswordAuthentication setting not found in main config (may use default)"
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# Check Match Group configuration
|
|
|
|
|
if grep -q "Match Group sftpusers" /etc/ssh/sshd_config; then
|
|
|
|
|
print_status "OK" "Match Group sftpusers found in main config"
|
|
|
|
|
elif [ -f "$ADDON_CONFIG" ] && grep -q "Match Group sftpusers" "$ADDON_CONFIG"; then
|
|
|
|
|
print_status "OK" "Match Group sftpusers found in addon config"
|
|
|
|
|
else
|
|
|
|
|
print_status "ERROR" "Match Group sftpusers NOT found in SSH config!"
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
if grep -q "Match Group sshusers" /etc/ssh/sshd_config; then
|
|
|
|
|
print_status "OK" "Match Group sshusers found in main config"
|
|
|
|
|
elif [ -f "$ADDON_CONFIG" ] && grep -q "Match Group sshusers" "$ADDON_CONFIG"; then
|
|
|
|
|
print_status "OK" "Match Group sshusers found in addon config"
|
|
|
|
|
else
|
|
|
|
|
print_status "WARNING" "Match Group sshusers NOT found (needed for SSH access)"
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# Test SSH config syntax
|
|
|
|
|
if sshd -t 2>/dev/null; then
|
|
|
|
|
print_status "OK" "SSH configuration syntax is valid"
|
|
|
|
|
else
|
|
|
|
|
print_status "ERROR" "SSH configuration syntax is INVALID!"
|
|
|
|
|
echo " Run: sshd -t (as root) to see errors"
|
|
|
|
|
fi
|
|
|
|
|
echo
|
|
|
|
|
|
|
|
|
|
echo "=== 7. SSH SERVICE STATUS ==="
|
|
|
|
|
if systemctl is-active --quiet sshd; then
|
|
|
|
|
print_status "OK" "SSH service is running"
|
|
|
|
|
else
|
|
|
|
|
print_status "ERROR" "SSH service is NOT running!"
|
|
|
|
|
echo " Fix: systemctl start sshd"
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
if systemctl is-enabled --quiet sshd; then
|
|
|
|
|
print_status "OK" "SSH service is enabled (will start on boot)"
|
|
|
|
|
else
|
|
|
|
|
print_status "WARNING" "SSH service is NOT enabled"
|
|
|
|
|
fi
|
|
|
|
|
echo
|
|
|
|
|
|
|
|
|
|
echo "=== 8. RECENT LOGS CHECK ==="
|
|
|
|
|
LOG_DIR="/opt/add-sftp-user-addon/logs"
|
|
|
|
|
if [ -d "$LOG_DIR" ]; then
|
|
|
|
|
print_status "OK" "Log directory exists: $LOG_DIR"
|
|
|
|
|
|
|
|
|
|
# Find user creation log
|
|
|
|
|
USER_LOG=$(ls -t $LOG_DIR/user_creation-*.log 2>/dev/null | head -1)
|
|
|
|
|
if [ -n "$USER_LOG" ]; then
|
|
|
|
|
echo " Most recent user creation log: $USER_LOG"
|
|
|
|
|
echo " Last 10 lines:"
|
|
|
|
|
tail -10 "$USER_LOG" | sed 's/^/ /'
|
|
|
|
|
else
|
|
|
|
|
print_status "WARNING" "No user creation logs found"
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# Check error log
|
|
|
|
|
if [ -f "$LOG_DIR/errors.log" ]; then
|
|
|
|
|
ERROR_COUNT=$(wc -l < "$LOG_DIR/errors.log")
|
|
|
|
|
if [ "$ERROR_COUNT" -gt 0 ]; then
|
|
|
|
|
print_status "WARNING" "Found $ERROR_COUNT lines in error log"
|
|
|
|
|
echo " Last 5 errors:"
|
|
|
|
|
tail -5 "$LOG_DIR/errors.log" | sed 's/^/ /'
|
|
|
|
|
fi
|
|
|
|
|
fi
|
|
|
|
|
else
|
|
|
|
|
print_status "WARNING" "Log directory does not exist: $LOG_DIR"
|
|
|
|
|
fi
|
|
|
|
|
echo
|
|
|
|
|
|
|
|
|
|
echo "=== 9. AUTHENTICATION TEST (SIMULATED) ==="
|
|
|
|
|
if id "$USERNAME" &>/dev/null; then
|
|
|
|
|
USER_SHELL=$(getent passwd $USERNAME | cut -d: -f7)
|
|
|
|
|
if [ "$USER_SHELL" = "/sbin/nologin" ]; then
|
|
|
|
|
GROUPS=$(id -Gn "$USERNAME")
|
|
|
|
|
if echo "$GROUPS" | grep -q "sftpusers"; then
|
|
|
|
|
print_status "OK" "User configured for SFTP-only (nologin shell + sftpusers group)"
|
|
|
|
|
echo " Note: This user can ONLY use SFTP, not SSH shell access"
|
|
|
|
|
else
|
|
|
|
|
print_status "ERROR" "User has nologin shell but NOT in sftpusers group!"
|
|
|
|
|
fi
|
|
|
|
|
elif [ "$USER_SHELL" = "/bin/bash" ] || [ "$USER_SHELL" = "/bin/sh" ]; then
|
|
|
|
|
GROUPS=$(id -Gn "$USERNAME")
|
|
|
|
|
if echo "$GROUPS" | grep -q "sshusers"; then
|
|
|
|
|
print_status "OK" "User configured for SSH+SFTP (bash shell + sshusers group)"
|
|
|
|
|
else
|
|
|
|
|
print_status "ERROR" "User has bash shell but NOT in sshusers group!"
|
|
|
|
|
echo " Fix: usermod -aG sshusers $USERNAME"
|
|
|
|
|
fi
|
|
|
|
|
else
|
|
|
|
|
print_status "WARNING" "Unexpected shell: $USER_SHELL"
|
|
|
|
|
fi
|
|
|
|
|
fi
|
|
|
|
|
echo
|
|
|
|
|
|
|
|
|
|
echo "=== 10. QUICK FIXES ==="
|
|
|
|
|
echo "If user exists but login fails, try these commands (as root):"
|
|
|
|
|
echo
|
|
|
|
|
if id "$USERNAME" &>/dev/null; then
|
|
|
|
|
echo "1. Reset password:"
|
|
|
|
|
echo " echo '$USERNAME:NEW_PASSWORD' | chpasswd"
|
|
|
|
|
echo
|
|
|
|
|
echo "2. Fix group membership (choose one based on desired access):"
|
|
|
|
|
echo " # For SFTP-only:"
|
|
|
|
|
echo " usermod -aG sftpusers,litespeed $USERNAME"
|
|
|
|
|
echo " usermod -s /sbin/nologin $USERNAME"
|
|
|
|
|
echo " # For SSH+SFTP:"
|
|
|
|
|
echo " usermod -aG sshusers,litespeed $USERNAME"
|
|
|
|
|
echo " usermod -s /bin/bash $USERNAME"
|
|
|
|
|
echo
|
|
|
|
|
echo "3. Fix home directory permissions:"
|
|
|
|
|
USER_HOME=$(getent passwd $USERNAME | cut -d: -f6)
|
|
|
|
|
echo " chown root:root $USER_HOME"
|
|
|
|
|
echo " chmod 755 $USER_HOME"
|
|
|
|
|
echo
|
|
|
|
|
echo "4. Remount ROOT directory:"
|
|
|
|
|
echo " mount --bind /var/www/webroot/ROOT $USER_HOME/data/ROOT"
|
|
|
|
|
echo
|
|
|
|
|
echo "5. Restart SSH service:"
|
|
|
|
|
echo " systemctl restart sshd"
|
|
|
|
|
else
|
|
|
|
|
echo "User does not exist. Cannot provide fixes."
|
|
|
|
|
fi
|
|
|
|
|
echo
|
|
|
|
|
|
|
|
|
|
echo "=============================================================================="
|
|
|
|
|
echo "Diagnostic complete!"
|
|
|
|
|
echo "=============================================================================="
|
|
|
|
|
|
2025-11-04 10:09:11 +00:00
|
|
|
# Exit with success status
|
|
|
|
|
exit 0
|