#!/usr/bin/env bash
#
# create_tunnel_user.sh
# 创建/管理仅用于 SSH 端口转发(隧道)的受限账号。
# 该账号：
#   - 只能通过密码登录
#   - 无法获取交互式 shell / 执行远程命令
#   - 仅允许 TCP 端口转发（-L / -R / -D），禁止 X11、Agent 转发
#
# 用法: sudo bash create_tunnel_user.sh
#
set -euo pipefail

SSHD_CONFIG="/etc/ssh/sshd_config"
SSHD_CONFIG_D="/etc/ssh/sshd_config.d"
MARK_BEGIN="# >>> ssh-tunnel-only-user BEGIN"
MARK_END="# >>> ssh-tunnel-only-user END"
# 标记中携带用户名，格式固定为: ssh-tunnel-only-user BEGIN (user: xxx)
MARK_BEGIN_PREFIX="# >>> ssh-tunnel-only-user BEGIN (user: "

# ---------- 基础检查 ----------

if [[ "$(id -u)" -ne 0 ]]; then
    echo "错误: 请使用 root 权限运行此脚本 (例如: sudo bash $0)" >&2
    exit 1
fi

if ! command -v sshd >/dev/null 2>&1; then
    echo "错误: 未检测到 sshd，请确认本机已安装并运行 OpenSSH 服务端。" >&2
    exit 1
fi

# ============================================================
# 工具函数: 识别本脚本创建的隧道账号
# ============================================================
#
# 判定标准（两者都满足才算"本脚本创建的隧道账号"）：
#   1) sshd 配置中存在带标记的 Match User 块，标记里写明了该用户名
#   2) 该系统用户确实存在，且登录 shell 是 nologin/false 一类
#
# 这样可以避免误删手动创建的、恰好也是 nologin 的其它账号。

# 找出所有"带标记"的配置文件，输出: 文件路径
find_marked_config_files() {
    local files=()
    if [[ -f "${SSHD_CONFIG}" ]] && grep -qF "${MARK_BEGIN_PREFIX}" "${SSHD_CONFIG}" 2>/dev/null; then
        files+=("${SSHD_CONFIG}")
    fi
    if [[ -d "${SSHD_CONFIG_D}" ]]; then
        while IFS= read -r -d '' f; do
            if grep -qF "${MARK_BEGIN_PREFIX}" "${f}" 2>/dev/null; then
                files+=("${f}")
            fi
        done < <(find "${SSHD_CONFIG_D}" -maxdepth 1 -type f -name "*.conf" -print0 2>/dev/null)
    fi
    printf '%s\n' "${files[@]}"
}

# 从标记中解析出用户名列表（去重），每行一个用户名
list_marked_usernames() {
    local files
    mapfile -t files < <(find_marked_config_files)
    local f
    for f in "${files[@]}"; do
        [[ -z "${f}" ]] && continue
        grep -oP "(?<=\(user: )[^)]+(?=\))" "${f}" 2>/dev/null || true
    done | sort -u
}

# 判断某用户名是否同时满足"有标记配置"且"系统账号存在且 shell 为 nologin 类"
is_managed_tunnel_user() {
    local uname="$1"
    local files
    mapfile -t files < <(find_marked_config_files)
    local has_mark=0
    local f
    for f in "${files[@]}"; do
        [[ -z "${f}" ]] && continue
        if grep -qF "${MARK_BEGIN_PREFIX}${uname})" "${f}" 2>/dev/null; then
            has_mark=1
            break
        fi
    done
    [[ "${has_mark}" -eq 1 ]] || return 1

    id "${uname}" >/dev/null 2>&1 || return 1
    local shell
    shell="$(getent passwd "${uname}" | cut -d: -f7)"
    case "${shell}" in
        */nologin|*/false)
            return 0
            ;;
        *)
            return 1
            ;;
    esac
}

# 删除单个隧道账号：移除系统用户 + 移除其对应的 sshd 配置块/文件
remove_tunnel_user() {
    local uname="$1"

    echo "正在删除隧道账号: ${uname}"

    # 1) 删除系统账号（含家目录）
    if id "${uname}" >/dev/null 2>&1; then
        userdel --remove "${uname}" 2>/dev/null || userdel "${uname}" 2>/dev/null || true
        echo "  - 已删除系统账号及家目录"
    else
        echo "  - 系统账号不存在，跳过"
    fi

    # 2) 清理 sshd 配置：独立文件直接删除；写在主配置里的需要摘除对应块
    local files
    mapfile -t files < <(find_marked_config_files)
    local f
    for f in "${files[@]}"; do
        [[ -z "${f}" ]] && continue
        if ! grep -qF "${MARK_BEGIN_PREFIX}${uname})" "${f}" 2>/dev/null; then
            continue
        fi

        if [[ "${f}" == "${SSHD_CONFIG_D}"/*"-${uname}.conf" ]]; then
            # 独立配置文件，且文件名就是这个用户专属的，直接删除整个文件
            rm -f "${f}"
            echo "  - 已删除独立配置文件: ${f}"
        else
            # 在共享文件（一般是主 sshd_config）中，精确摘除对应的 BEGIN..END 块
            local tmp
            tmp="$(mktemp)"
            awk -v begin="${MARK_BEGIN_PREFIX}${uname})" -v endmark="${MARK_END}" '
                $0 == begin {skip=1; next}
                skip && $0 == endmark {skip=0; next}
                !skip {print}
            ' "${f}" > "${tmp}"
            mv "${tmp}" "${f}"
            echo "  - 已从 ${f} 中摘除对应配置块"
        fi
    done
}

# ============================================================
# 主菜单
# ============================================================

echo "============================================"
echo " SSH 隧道专用账号管理"
echo "============================================"
echo "  1) 创建新的隧道账号"
echo "  2) 查看已存在的隧道账号"
echo "  3) 删除一个或全部隧道账号"
echo "  0) 退出"
echo "============================================"
read -r -p "请选择操作 [1/2/3/0]: " MENU_CHOICE

case "${MENU_CHOICE}" in
    2)
        mapfile -t CANDIDATES < <(list_marked_usernames)
        FOUND=0
        for u in "${CANDIDATES[@]}"; do
            [[ -z "${u}" ]] && continue
            if is_managed_tunnel_user "${u}"; then
                echo "  - ${u}"
                FOUND=1
            fi
        done
        if [[ "${FOUND}" -eq 0 ]]; then
            echo "未检测到由本脚本创建的隧道账号。"
        fi
        exit 0
        ;;
    3)
        mapfile -t CANDIDATES < <(list_marked_usernames)
        VALID_USERS=()
        for u in "${CANDIDATES[@]}"; do
            [[ -z "${u}" ]] && continue
            if is_managed_tunnel_user "${u}"; then
                VALID_USERS+=("${u}")
            fi
        done

        if [[ "${#VALID_USERS[@]}" -eq 0 ]]; then
            echo "未检测到由本脚本创建的隧道账号，无需删除。"
            exit 0
        fi

        echo "检测到以下隧道账号:"
        idx_i=1
        for u in "${VALID_USERS[@]}"; do
            echo "  ${idx_i}) ${u}"
            idx_i=$((idx_i + 1))
        done
        echo "  a) 全部删除"
        echo "  0) 取消"
        read -r -p "请输入要删除的编号（多个用空格分隔，或输入 a 全部删除）: " DEL_CHOICE

        if [[ "${DEL_CHOICE}" == "0" ]]; then
            echo "已取消。"
            exit 0
        fi

        TARGETS=()
        if [[ "${DEL_CHOICE}" == "a" || "${DEL_CHOICE}" == "A" ]]; then
            TARGETS=("${VALID_USERS[@]}")
        else
            for idx in ${DEL_CHOICE}; do
                if [[ "${idx}" =~ ^[0-9]+$ ]] && [[ "${idx}" -ge 1 ]] && [[ "${idx}" -le "${#VALID_USERS[@]}" ]]; then
                    TARGETS+=("${VALID_USERS[$((idx - 1))]}")
                fi
            done
        fi

        if [[ "${#TARGETS[@]}" -eq 0 ]]; then
            echo "未选择任何有效账号，已取消。"
            exit 0
        fi

        echo "即将删除以下账号: ${TARGETS[*]}"
        read -r -p "确认删除？此操作不可恢复 [y/N]: " CONFIRM
        if [[ "${CONFIRM}" != "y" && "${CONFIRM}" != "Y" ]]; then
            echo "已取消。"
            exit 0
        fi

        for u in "${TARGETS[@]}"; do
            remove_tunnel_user "${u}"
        done

        echo "校验 sshd 配置语法..."
        if ! sshd -t; then
            echo "警告: sshd 配置校验失败，请手动检查 ${SSHD_CONFIG} 及 ${SSHD_CONFIG_D}/ 下的文件。" >&2
            exit 1
        fi
        echo "重新加载 sshd 服务..."
        if command -v systemctl >/dev/null 2>&1; then
            systemctl reload sshd 2>/dev/null || systemctl reload ssh 2>/dev/null || systemctl restart sshd 2>/dev/null || systemctl restart ssh
        else
            service sshd reload 2>/dev/null || service ssh reload 2>/dev/null || true
        fi
        echo "删除完成。"
        exit 0
        ;;
    0)
        exit 0
        ;;
    1)
        : # 继续往下走创建流程
        ;;
    *)
        echo "无效选择。"
        exit 1
        ;;
esac

# ---------- 交互式输入用户名 ----------

while true; do
    read -r -p "请输入要创建的隧道账号用户名: " TUNNEL_USER
    if [[ -z "${TUNNEL_USER}" ]]; then
        echo "用户名不能为空，请重新输入。"
        continue
    fi
    if [[ ! "${TUNNEL_USER}" =~ ^[a-z_][a-z0-9_-]{0,31}$ ]]; then
        echo "用户名格式不合法（建议小写字母开头，只含小写字母/数字/下划线/连字符）。"
        continue
    fi
    if id "${TUNNEL_USER}" >/dev/null 2>&1; then
        echo "用户 ${TUNNEL_USER} 已存在，请换一个用户名，或先手动删除该用户后重试。"
        continue
    fi
    break
done

# ---------- 交互式输入密码（隐藏输入，二次确认） ----------

while true; do
    read -r -s -p "请输入 ${TUNNEL_USER} 的密码: " TUNNEL_PASS
    echo
    read -r -s -p "请再次输入密码以确认: " TUNNEL_PASS_CONFIRM
    echo
    if [[ "${TUNNEL_PASS}" != "${TUNNEL_PASS_CONFIRM}" ]]; then
        echo "两次输入的密码不一致，请重新输入。"
        continue
    fi
    if [[ ${#TUNNEL_PASS} -lt 8 ]]; then
        echo "密码长度过短（建议至少 8 位），请重新输入。"
        continue
    fi
    break
done
unset TUNNEL_PASS_CONFIRM

# ---------- 创建用户 ----------
# 登录 shell 设为 /usr/sbin/nologin 仅作为兜底防护；
# 真正阻断 shell/命令执行是靠下面 sshd_config 的 ForceCommand + PermitTTY no。
NOLOGIN_BIN="$(command -v nologin || echo /usr/sbin/nologin)"
if [[ ! -x "${NOLOGIN_BIN}" ]]; then
    NOLOGIN_BIN="/sbin/nologin"
fi

echo "正在创建系统账号 ${TUNNEL_USER} ..."
useradd --create-home --shell "${NOLOGIN_BIN}" "${TUNNEL_USER}"

echo "${TUNNEL_USER}:${TUNNEL_PASS}" | chpasswd
unset TUNNEL_PASS

# 锁定家目录权限，避免该账号被用于存放/中转敏感文件
chmod 700 "/home/${TUNNEL_USER}"

# ---------- 配置 sshd：仅允许该用户做端口转发 ----------

# 优先使用 /etc/ssh/sshd_config.d/ 下的独立文件（新版 OpenSSH 支持 Include），
# 若系统不支持该机制，则回退为直接追加到 sshd_config 主文件。
USE_CONFIG_D=0
if [[ -d "${SSHD_CONFIG_D}" ]] && grep -Eq "^[[:space:]]*Include[[:space:]]+.*sshd_config\.d" "${SSHD_CONFIG}"; then
    USE_CONFIG_D=1
fi

TUNNEL_BLOCK=$(cat <<EOF
${MARK_BEGIN} (user: ${TUNNEL_USER})
Match User ${TUNNEL_USER}
    PasswordAuthentication yes
    PubkeyAuthentication no
    AuthenticationMethods password
    PermitTTY no
    X11Forwarding no
    AllowAgentForwarding no
    AllowTcpForwarding yes
    AllowStreamLocalForwarding no
    GatewayPorts no
    PermitOpen any
    ForceCommand /usr/sbin/nologin
${MARK_END}
EOF
)

if [[ "${USE_CONFIG_D}" -eq 1 ]]; then
    TARGET_FILE="${SSHD_CONFIG_D}/99-tunnel-${TUNNEL_USER}.conf"
    echo "检测到 sshd_config.d 机制，写入独立配置文件: ${TARGET_FILE}"
    printf '%s\n' "${TUNNEL_BLOCK}" > "${TARGET_FILE}"
    chmod 600 "${TARGET_FILE}"
else
    echo "写入配置到 ${SSHD_CONFIG} 末尾"
    {
        echo ""
        printf '%s\n' "${TUNNEL_BLOCK}"
    } >> "${SSHD_CONFIG}"
fi

# ---------- 校验配置并重启 sshd ----------

echo "校验 sshd 配置语法..."
if ! sshd -t; then
    echo "错误: sshd 配置校验失败，请检查上面写入的配置块，回滚后再处理。" >&2
    exit 1
fi

echo "重新加载 sshd 服务..."
if command -v systemctl >/dev/null 2>&1; then
    systemctl reload sshd 2>/dev/null || systemctl reload ssh 2>/dev/null || systemctl restart sshd 2>/dev/null || systemctl restart ssh
else
    service sshd reload 2>/dev/null || service ssh reload 2>/dev/null || true
fi

echo
echo "============================================"
echo "隧道账号创建完成"
echo "  用户名: ${TUNNEL_USER}"
echo "  登录方式: 仅密码登录"
echo "  权限: 仅允许 SSH 端口转发(-L/-R/-D)，无法获取 shell 或执行命令"
echo
echo "客户端连接示例（建议加 -N 不打开远程 shell）:"
echo "  ssh -N -L 本地端口:目标内网IP:目标端口 ${TUNNEL_USER}@本机地址"
echo
echo "如需删除该账号，再次运行本脚本并选择「3) 删除一个或全部隧道账号」即可。"
echo "============================================"
