Linux一键安装vsFTP

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
#!/bin/bash
# vsftpd 虚拟用户一键安装脚本(修复版)
# 修复/改进内容:
# 1. [核心修复] vsftpd >= 2.3.5(CentOS 7 的 3.0.2)自动写入 allow_writeable_chroot=YES,
# 解决"认证成功但登录失败"(500 OOPS: refusing to run with writable root inside chroot())
# CentOS 6 的 vsftpd 2.2.2 没有该检查也没有该选项,自动跳过,避免启动报 unknown option
# 2. 删除重复的 pam_service_name,只保留 vsftpdftp.vu
# 3. 固定被动模式端口段 40000-40010,并自动开放防火墙(firewalld / iptables)
# 4. SELinux 为 Enforcing 时自动设置 ftp_full_access
# 5. 映射用户 virtualftp 改为 /sbin/nologin,禁止登录系统
# 6. 明文密码文件用完即删;密码库权限 600
# 7. 支持多次运行:追加新用户、覆盖同名旧用户,不影响已有其他账号
# (并自动迁移旧脚本留下的 /etc/vsftpd/accuser.txt)

FTP_PORT=56118
PASV_MIN=40000
PASV_MAX=40010
FTP_ROOT=/data/ftp

read -p "创建用户 :" name
read -p "用户密码 :" passwd

# ---------- 输入校验 ----------
if [[ -z "$name" || -z "$passwd" ]]; then
echo "错误:用户名和密码不能为空" >&2; exit 1
fi
if [[ ! "$name" =~ ^[A-Za-z0-9._-]+$ ]]; then
echo "错误:用户名只能包含字母、数字、点、下划线、中划线" >&2; exit 1
fi

# ---------- 安装依赖 ----------
yum -y install vsftpd openssl
yum -y install db4-utils 2>/dev/null || yum -y install db4*
command -v db_load >/dev/null 2>&1 || { echo "错误:db_load 不可用,请安装 db4-utils" >&2; exit 1; }

# ---------- 判断 vsftpd 版本,决定是否需要 allow_writeable_chroot ----------
VSVER=$(vsftpd -v 2>&1 | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1)
CHROOT_OPT=""
if [ -n "$VSVER" ] && [ "$(printf '%s\n2.3.5\n' "$VSVER" | sort -V | head -1)" = "2.3.5" ]; then
CHROOT_OPT="allow_writeable_chroot=YES"
fi

# ---------- 主配置 ----------
cd /etc/vsftpd
[ -f vsftpd.conf ] && cp vsftpd.conf vsftpd.conf.bak

cat > vsftpd.conf <<EOF
anonymous_enable=NO
local_enable=YES
write_enable=YES
local_umask=022
dirmessage_enable=YES
xferlog_enable=YES
connect_from_port_20=YES
xferlog_std_format=YES
listen=YES
listen_port=$FTP_PORT
userlist_enable=NO
tcp_wrappers=YES
accept_timeout=1800
connect_timeout=1800
data_connection_timeout=1800
idle_session_timeout=1800
chroot_local_user=YES
$CHROOT_OPT
ftpd_banner=Welcom to JG FTP server
virtual_use_local_privs=YES
guest_enable=YES
guest_username=virtualftp
pam_service_name=vsftpdftp.vu
user_config_dir=/etc/vsftpd/user_config_dir
local_root=$FTP_ROOT
pasv_enable=YES
pasv_min_port=$PASV_MIN
pasv_max_port=$PASV_MAX
# 如果服务器在 NAT 后面,取消下一行注释并填公网 IP:
#pasv_address=1.2.3.4
EOF

# ---------- 系统映射用户(仅作权限映射,禁止登录) ----------
id virtualftp &>/dev/null || useradd -M -s /sbin/nologin virtualftp

mkdir -p /etc/vsftpd/user_config_dir "$FTP_ROOT"
chown -R virtualftp:virtualftp "$FTP_ROOT"

# ---------- PAM 验证模块 ----------
PAM_SO=/lib64/security/pam_userdb.so
[ -f "$PAM_SO" ] || PAM_SO=/lib/security/pam_userdb.so

cat > /etc/pam.d/vsftpdftp.vu <<EOF
auth required $PAM_SO db=/etc/vsftpd/vsftpdlogin
account required $PAM_SO db=/etc/vsftpd/vsftpdlogin
EOF

# ---------- 虚拟用户库(多用户、可重复运行) ----------
VIRT=/etc/vsftpd/virtusers.txt
touch "$VIRT"; chmod 600 "$VIRT"

# 迁移旧脚本遗留的账号文件(保留 lijiacheng 等已有账号)
if [ -s /etc/vsftpd/accuser.txt ] && [ ! -s "$VIRT" ]; then
mv /etc/vsftpd/accuser.txt "$VIRT"
fi

# 删除同名旧记录(用户名一行 + 密码一行),再追加新记录
awk -v u="$name" '$0==u{getline; next} {print}' "$VIRT" > "$VIRT.tmp" && mv "$VIRT.tmp" "$VIRT"
printf '%s\n%s\n' "$name" "$passwd" >> "$VIRT"
chmod 600 "$VIRT"

db_load -T -t hash -f "$VIRT" /etc/vsftpd/vsftpdlogin.db \
|| { echo "错误:db_load 生成密码库失败" >&2; exit 1; }
chmod 600 /etc/vsftpd/vsftpdlogin.db

# ---------- 用户独立配置 ----------
cat > "/etc/vsftpd/user_config_dir/$name" <<EOF
anon_world_readable_only=NO
write_enable=YES
anon_upload_enable=YES
anon_other_write_enable=YES
anon_mkdir_write_enable=YES
local_root=$FTP_ROOT
EOF

# ---------- SELinux ----------
if command -v getenforce >/dev/null 2>&1 && [ "$(getenforce 2>/dev/null)" = "Enforcing" ]; then
setsebool -P ftp_full_access on 2>/dev/null \
|| setsebool -P allow_ftpd_full_access on 2>/dev/null \
|| echo "警告:SELinux 设置失败,请手动执行 setsebool -P ftp_full_access on"
fi

# ---------- 防火墙 ----------
if systemctl is-active firewalld >/dev/null 2>&1; then
firewall-cmd --permanent --add-port=${FTP_PORT}/tcp
firewall-cmd --permanent --add-port=${PASV_MIN}-${PASV_MAX}/tcp
firewall-cmd --reload
elif service iptables status >/dev/null 2>&1; then
iptables -I INPUT -p tcp --dport ${FTP_PORT} -j ACCEPT
iptables -I INPUT -p tcp --dport ${PASV_MIN}:${PASV_MAX} -j ACCEPT
service iptables save >/dev/null 2>&1
fi

# ---------- 启动服务(CentOS 6 / 7 自适应) ----------
if [ -d /run/systemd/system ] && command -v systemctl >/dev/null 2>&1; then
systemctl restart vsftpd.service
systemctl enable vsftpd.service >/dev/null 2>&1
else
service vsftpd restart
chkconfig vsftpd on
fi

echo "vsFTP搭建成功,账号:密码:端口===$name:$passwd:$FTP_PORT"
echo "被动端口段:$PASV_MIN-$PASV_MAX 数据目录:$FTP_ROOT"
echo "自检命令:journalctl -u vsftpd | grep -i oops(无输出即正常)"