#!/bin/bash
#########################################################################
# Script Name: password_policy_centos7.sh
# Description: CentOS 7.9 密码复杂度和有效期配置脚本（修正版）
# Author: zhangpeng
# Date: 2026-1-12
# Version: 2.1
# Changes: 
#   - 密码复杂度为"至少 3 种字符类型"
#   - 移除强制 4 种字符类型的限制
#########################################################################

# 颜色定义
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
CYAN='\033[0;36m'
NC='\033[0m'

# 日志函数
log_info() {
    echo -e "${GREEN}[INFO]${NC} $1"
}

log_warn() {
    echo -e "${YELLOW}[WARN]${NC} $1"
}

log_error() {
    echo -e "${RED}[ERROR]${NC} $1"
}

log_step() {
    echo -e "${BLUE}[STEP]${NC} $1"
}

# 检查是否为 root 用户
check_root() {
    if [ "$(id -u)" -ne 0 ]; then
        log_error "此脚本必须以 root 用户运行！"
        exit 1
    fi
}

# 检查系统版本
check_system() {
    if [ ! -f /etc/redhat-release ]; then
        log_error "此脚本仅支持 CentOS/RHEL 系统！"
        exit 1
    fi
    
    local version=$(cat /etc/redhat-release | grep -oP '(?<=release )\d+')
    if [ "$version" != "7" ]; then
        log_warn "检测到系统版本为 $version，此脚本专为 CentOS 7.9 设计"
        read -p "是否继续执行？(y/n): " choice
        if [ "$choice" != "y" ]; then
            exit 0
        fi
    fi
}

# 获取普通用户列表
get_normal_users() {
    awk -F: '$3>=1000 && $3!=65534 {print $1":"$3":"$6}' /etc/passwd
}

# 显示普通用户列表
show_normal_users() {
    local users=$(get_normal_users)
    
    if [ -z "$users" ]; then
        log_warn "系统中没有普通用户（UID >= 1000）"
        return 1
    fi
    
    echo ""
    echo -e "${CYAN}=========================================="
    echo -e "  检测到的普通用户列表"
    echo -e "==========================================${NC}"
    printf "%-20s %-10s %-30s\n" "用户名" "UID" "家目录"
    echo "------------------------------------------------------------------"
    
    while IFS=: read -r username uid homedir; do
        printf "%-20s %-10s %-30s\n" "$username" "$uid" "$homedir"
    done <<< "$users"
    
    echo "------------------------------------------------------------------"
    local user_count=$(echo "$users" | wc -l)
    echo -e "总计: ${GREEN}$user_count${NC} 个普通用户"
    echo ""
    
    return 0
}

# 安装必要软件包
install_packages() {
    log_step "步骤 1/5: 检查并安装必要软件包..."
    
    if rpm -qa | grep -q libpwquality; then
        log_info "libpwquality 已安装"
    else
        log_info "正在安装 libpwquality..."
        yum install -y libpwquality
        if [ $? -eq 0 ]; then
            log_info "libpwquality 安装成功"
        else
            log_error "libpwquality 安装失败"
            exit 1
        fi
    fi
}

# 备份配置文件
backup_configs() {
    log_step "步骤 2/5: 备份原配置文件..."
    
    local backup_dir="/root/password_policy_backup_$(date +%Y%m%d_%H%M%S)"
    mkdir -p "$backup_dir"
    
    local files=(
        "/etc/pam.d/system-auth"
        "/etc/pam.d/password-auth"
        "/etc/security/pwquality.conf"
        "/etc/login.defs"
    )
    
    for file in "${files[@]}"; do
        if [ -f "$file" ]; then
            cp "$file" "$backup_dir/"
            log_info "已备份: $file -> $backup_dir/"
        fi
    done
    
    log_info "备份目录: $backup_dir"
}

# ✅ 修改后的 PAM 配置
configure_pam() {
    log_step "步骤 3/5: 配置 PAM 密码复杂度策略..."
    
    # 配置 system-auth
    log_info "配置 /etc/pam.d/system-auth..."
    
    # 删除旧的 pam_pwquality.so 行
    sed -i '/^password.*pam_pwquality.so/d' /etc/pam.d/system-auth
    
    # ✅ 只使用 minclass=3，不强制每种字符类型
    sed -i '/^password/i\password    requisite     pam_pwquality.so try_first_pass retry=3 authtok_type= minlen=8 minclass=3 enforce_for_root' /etc/pam.d/system-auth
    
    # 删除重复行
    awk '!seen[$0]++' /etc/pam.d/system-auth > /tmp/system-auth.tmp
    mv /tmp/system-auth.tmp /etc/pam.d/system-auth
    
    # 移除 remember 参数（如果存在）
    sed -i 's/\s*remember=[0-9]*//g' /etc/pam.d/system-auth
    
    # 同步到 password-auth
    log_info "同步配置到 /etc/pam.d/password-auth..."
    cp /etc/pam.d/system-auth /etc/pam.d/password-auth
    
    log_info "PAM 配置完成"
}

# ✅ 修改后的 pwquality 配置
configure_pwquality() {
    log_step "步骤 4/5: 配置 /etc/security/pwquality.conf..."
    
    cat > /etc/security/pwquality.conf << 'EOF'
# Configuration for systemwide password quality limits

# Number of characters in the new password that must not be present in the old password.
difok = 3

# Minimum acceptable size for the new password
minlen = 8

# ✅ 不强制要求每种字符类型，只要求至少 3 种即可
# The maximum credit for having digits in the new password.
# dcredit = 1

# The maximum credit for having uppercase characters in the new password.
# ucredit = 1

# The maximum credit for having lowercase characters in the new password.
# lcredit = 1

# The maximum credit for having other characters in the new password.
# ocredit = 1

# The minimum number of required classes of characters for the new password
# ✅ 至少 3 种字符类型（大写、小写、数字、特殊字符）
minclass = 3

# Whether to check for the words from the passwd entry GECOS string of the user
usercheck = 1

# Whether to check for the words from the cracklib dictionary
dictcheck = 1

# Enforces pwquality checks on the root user password.
enforce_for_root
EOF

    log_info "pwquality.conf 配置完成"
}

# 配置密码有效期
configure_password_aging() {
    log_step "步骤 5/5: 配置密码有效期策略..."
    
    log_info "配置 /etc/login.defs..."
    
    sed -i 's/^PASS_MAX_DAYS.*/PASS_MAX_DAYS   90/' /etc/login.defs
    sed -i 's/^PASS_MIN_DAYS.*/PASS_MIN_DAYS   0/' /etc/login.defs
    sed -i 's/^PASS_WARN_AGE.*/PASS_WARN_AGE   7/' /etc/login.defs
    
    if ! grep -q "^PASS_MIN_LEN" /etc/login.defs; then
        echo "PASS_MIN_LEN    8" >> /etc/login.defs
    else
        sed -i 's/^PASS_MIN_LEN.*/PASS_MIN_LEN    8/' /etc/login.defs
    fi
    
    log_info "login.defs 配置完成"
    
    log_info "为 root 用户设置密码有效期..."
    chage -M 90 -m 0 -W 7 root
    log_info "重置 root 密码修改时间为今天..."
    chage -d $(( $(date +%s) / 86400 )) root
    log_success "已对 root 用户应用密码策略并设置有效期90天"

    
    local users=$(get_normal_users)
    
    if [ -z "$users" ]; then
        log_warn "未找到普通用户（UID >= 1000），跳过普通用户配置"
        return
    fi
    
    echo ""
    show_normal_users
    
    echo -e "${YELLOW}是否为以上普通用户配置密码有效期？${NC}"
    echo "  [1] 是 - 为所有普通用户配置密码有效期 90 天"
    echo "  [2] 否 - 跳过普通用户配置（仅配置 root）"
    echo "  [3] 自定义 - 手动选择要配置的用户"
    echo ""
    read -p "请选择 [1-3]: " user_choice
    
    case $user_choice in
        1)
            log_info "为所有普通用户设置密码有效期..."
            local user_count=0
            while IFS=: read -r username uid homedir; do
                chage -M 90 -m 0 -W 7 "$username"
                log_info "  ✓ 已设置用户: $username"
                ((user_count++))
            done <<< "$users"
            log_info "共设置 $user_count 个普通用户的密码有效期"
            ;;
        2)
            log_warn "已跳过普通用户密码有效期配置"
            ;;
        3)
            configure_selected_users "$users"
            ;;
        *)
            log_warn "无效选择，跳过普通用户密码有效期配置"
            ;;
    esac
}

# 为选定的用户配置密码有效期
configure_selected_users() {
    local users="$1"
    
    echo ""
    log_info "请输入要配置密码有效期的用户名，多个用户用空格分隔"
    log_info "可用用户: $(echo "$users" | cut -d: -f1 | tr '\n' ' ')"
    echo ""
    read -p "用户列表: " selected_users
    
    if [ -z "$selected_users" ]; then
        log_warn "未选择任何用户，跳过配置"
        return
    fi
    
    local configured_count=0
    local failed_users=""
    
    for username in $selected_users; do
        if echo "$users" | grep -q "^${username}:"; then
            chage -M 90 -m 0 -W 7 "$username"
            log_info "  ✓ 已设置用户: $username"
            ((configured_count++))
        else
            log_warn "  ✗ 用户 $username 不存在或不是普通用户，跳过"
            failed_users="$failed_users $username"
        fi
    done
    
    echo ""
    log_info "成功配置 $configured_count 个用户的密码有效期"
    
    if [ -n "$failed_users" ]; then
        log_warn "以下用户配置失败:$failed_users"
    fi
}

# 验证配置

verify_configuration() {
    echo ""
    log_step "===== 配置验证 ====="
    echo ""
    
    echo -e "${BLUE}[1] PAM 配置:${NC}"
    grep pam_pwquality.so /etc/pam.d/system-auth
    echo ""
    
    echo -e "${BLUE}[2] pwquality 配置:${NC}"
    cat /etc/security/pwquality.conf | grep -v '^#' | grep -v '^$'
    echo ""
    
    echo -e "${BLUE}[3] 密码有效期配置:${NC}"
    grep PASS_ /etc/login.defs | grep -v '^#'
    echo ""
    
    echo -e "${BLUE}[4] root 用户密码策略:${NC}"
    chage -l root || chage -l root | grep -E "(Last password change|Password expires|Maximum number|Minimum number|Number of days of warning)"
    echo ""
    
    echo -e "${BLUE}[5] 已配置密码有效期的普通用户:${NC}"
    local users=$(get_normal_users)
    if [ -n "$users" ]; then
        echo ""
        printf "%-20s %-15s %-15s %-15s\n" "用户名" "最大有效期" "最小间隔" "警告天数"
        echo "---------------------------------------------------------------"
        while IFS=: read -r username uid homedir; do
            local max_days=$(chage -l "$username" | grep "Maximum" | awk -F: '{print $2}' | xargs)
            local min_days=$(chage -l "$username" | grep "Minimum" | awk -F: '{print $2}' | xargs)
            local warn_days=$(chage -l "$username" | grep "warning" | awk -F: '{print $2}' | xargs)
            
            if [ "$max_days" = "90" ]; then
                printf "%-20s %-15s %-15s %-15s\n" "$username" "$max_days" "$min_days" "$warn_days"
            fi
        done <<< "$users"
        echo ""
    else
        log_info "无普通用户"
        echo ""
    fi
}

# 测试密码复杂度
test_password_policy() {
    echo ""
    log_step "===== 密码复杂度测试 ====="
    echo ""
    
    read -p "是否进行密码复杂度测试？(y/n): " do_test
    if [ "$do_test" != "y" ]; then
        log_info "跳过测试"
        return
    fi
    
    local test_user="testpwd_$(date +%s)"
    
    log_info "创建测试用户: $test_user"
    useradd "$test_user"
    
    echo ""
    log_info "请为测试用户设置密码，建议测试以下密码："
    echo "  ✅ Abcd1234      (3种: 大写+小写+数字)"
    echo "  ✅ [O_GtbyX      (3种: 大写+小写+特殊字符)"
    echo "  ✅ test@123      (3种: 小写+数字+特殊字符)"
    echo "  ✅ Test@123      (4种: 大写+小写+数字+特殊字符)"
    echo "  ❌ 123456        (1种: 只有数字)"
    echo "  ❌ abcdefgh      (1种: 只有小写)"
    echo "  ❌ Test123       (2种: 大写+小写+数字，但只有2种)"
    echo ""
    
    passwd "$test_user"
    
    echo ""
    log_info "查看测试用户密码策略:"
    chage -l "$test_user"
    
    echo ""
    read -p "是否删除测试用户 $test_user？(y/n): " do_delete
    if [ "$do_delete" = "y" ]; then
        userdel -r "$test_user" 2>/dev/null
        log_info "已删除测试用户: $test_user"
    else
        log_warn "保留测试用户: $test_user"
        log_warn "手动删除命令: userdel -r $test_user"
    fi
}

# 显示配置摘要
show_summary() {
    echo ""
    echo "=========================================="
    log_info "配置完成摘要"
    echo "=========================================="
    echo ""
    echo "✅ 密码复杂度要求:"
    echo "   - 最小长度: 8 位"
    echo "   - 字符类型: 至少 3 种（大写、小写、数字、特殊字符）"
    echo "   - 示例密码:"
    echo "     • Abcd1234       (大写+小写+数字)"
    echo "     • [O_GtbyX       (大写+小写+特殊字符)"
    echo "     • test@123       (小写+数字+特殊字符)"
    echo "     • Test@123       (大写+小写+数字+特殊字符)"
    echo "   - 禁止使用: 用户名、字典单词"
    echo "   - 对 root 生效: 是"
    echo "   - 历史密码限制: 无"
    echo ""
    echo "✅ 密码有效期策略:"
    echo "   - 最大有效期: 90 天"
    echo "   - 最小修改间隔: 0 天 (随时可改)"
    echo "   - 过期前警告: 7 天"
    echo ""
    echo "✅ 已生效用户:"
    echo "   - root 用户: 已设置"
    
    local users=$(get_normal_users)
    local configured_count=0
    if [ -n "$users" ]; then
        while IFS=: read -r username uid homedir; do
            local max_days=$(chage -l "$username" | grep "Maximum" | awk -F: '{print $2}' | xargs)
            if [ "$max_days" = "90" ]; then
                ((configured_count++))
            fi
        done <<< "$users"
    fi
    
    local total_users=$(echo "$users" | wc -l)
    [ -z "$users" ] && total_users=0
    
    echo "   - 普通用户: $configured_count/$total_users 个已配置"
    echo ""
    echo "=========================================="
}

# 显示未配置的用户
show_unconfigured_users() {
    local users=$(get_normal_users)
    
    if [ -z "$users" ]; then
        return
    fi
    
    local unconfigured_users=""
    
    while IFS=: read -r username uid homedir; do
        local max_days=$(chage -l "$username" | grep "Maximum" | awk -F: '{print $2}' | xargs)
        if [ "$max_days" != "90" ]; then
            unconfigured_users="$unconfigured_users $username"
        fi
    done <<< "$users"
    
    if [ -n "$unconfigured_users" ]; then
        echo ""
        log_warn "以下普通用户未配置密码有效期:"
        echo "   $unconfigured_users"
        echo ""
        log_info "如需配置，可手动执行:"
        for user in $unconfigured_users; do
            echo "   chage -M 90 -m 0 -W 7 $user"
        done
    fi
}

# 主函数
main() {
    echo "=========================================="
    echo "  CentOS 7.9 密码复杂度配置脚本 v2.1"
    echo "=========================================="
    echo ""
    
    check_root
    check_system
    
    echo ""
    read -p "即将开始配置，是否继续？(y/n): " confirm
    if [ "$confirm" != "y" ]; then
        log_info "用户取消操作"
        exit 0
    fi
    
    echo ""
    
    install_packages
    backup_configs
    configure_pam
    configure_pwquality
    configure_password_aging
    verify_configuration
    test_password_policy
    show_summary
    show_unconfigured_users
    
    log_info "所有配置已完成！"
    log_warn "建议重启系统后再次验证配置"
}

main "$@"

