FTP 配置

Redhat6.5 Linux Ftp 配置步骤.

1.系统实体账户:这些账户默认登录的家目录是自己的home目录,并且可以任意切换目录,如果要使用系统帐号作为ftp帐号,最好让将这些用户禁锢在自己的家目录中,并且设置这些用户的shell为/sbin/nologin
2.匿名用户:一般就给下载权限即可,默认登录后的主目录在/var/ftp
3.虚拟账户:相对比较安全这些用户都不是系统账户,只具有对ftp的相关操作权限,可以自定义用户主目录。虚拟账号即是系统中不存在的帐号,其目的是实现安全

理论基础

  • 命令端口
  • 数据端口
  • 主动模式(PORT):客户端开端口,服务端连接客户端进行数据通讯
  • 被动模式(PASV):服务端开端口,客户端连接服务端进行数据通讯(ftp实现并发靠的就是开多少个端口。感觉不是很合理,端口不能复用?可以用来限制并发)
  • 命令
    • USER
    • PASS
    • SIZE
    • CWD
    • PASV
    • PORT
    • PETR
    • STOR
    • REST
    • QUIT

FTP服务安装启动

  • 可以使用yum命令直接安装ftp
    yum install vsftpd
  • 开启关闭命令
    service vsftpd start
    service vsftpd stop

方式一:FTP虚拟用户配置

添加账号

增加用户
root用户执行

1
2
3
# useradd -d /data1/ftp  -s /sbin/nologin virtusers //增加用户virtusers,并制定virtusers用户的主目录为/data1/virtusers 。  创建一个用户提供给虚拟用户使用
# passwd test //为virtusers设置密码
# chown -R virtusers.virtusers /data1/ftp

查看用户信息

1
cat /etc/passwd|grep virtusers

修改VSFTPD配置

默认是被动模式:pasv_enable=YES

vi /etc/vsftpd/vsftpd.conf

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
anonymous_enable=NO   //不允许匿名账号访问
local_enable=YES //本地用户可以访问(采用虚拟账号访问时,这个参数也要开启(虚拟账号要寄宿本地账号),虽然开启但是本地账号是不可以登陆的)
write_enable=YES //可写可上传。这个参数是全局配置,否则上传和下载都会报错550!
local_umask=022 // 上传的文件屏蔽权限 777-022=755 666-022=644 新建的目录 权限是755 文件的权限是 644
listen_port=288 //监听的ftp端口
ftp_data_port=208
pasv_min_port=1788 //分配给ftp账号的最小端口。被动模式下的配置
pasv_max_port=1789 //分配给ftp账号的最大端口。每个账号分配一个端口,即最大允许2个ftp账号连接。
anon_upload_enable=NO
anon_mkdir_write_enable=NO
dirmessage_enable=YES
xferlog_enable=YES
connect_from_port_20=YES //通过20端口传输数据
chown_uploads=NO
chroot_local_user=YES
xferlog_file=/var/log/vsftpd.log
xferlog_std_format=YES
nopriv_user=virtusers
async_abor_enable=YES
ascii_upload_enable=YES
ascii_download_enable=YES
ftpd_banner=Welcome to blah FTP service ^_^ //登陆FTP时显示的欢迎信息
chroot_list_enable=NO //限制所有的本地用户在自家目录,即用户登陆系统后锁定在自家目录。虚拟主机配置下,在下面两个chroot配置后,这个参数必须为NO,否则登陆FTP后还可以访问其他目录!
ls_recurse_enable=NO
listen=YES
pam_service_name=vsftpd //指定PAM配置文件,即下面的/etc/pam.d/vsftpd文件要和这里指定的一致。
userlist_enable=YES //打开黑名单服务
tcp_wrappers=YES
guest_enable=YES //启用虚拟用户 若这个值设定为 YES 时,那么任何非 anonymous 登入的账号,均会被假设成为 guest (访客) 至于访客在 vsftpd 当中,预设会取得 ftp 这个使用者的相关权限。但可以透过 guest_username 来修改
guest_username=virtusers //将虚拟用户映射为本地[virtusers]用户(前提是local_enable=YES)
virtual_use_local_privs=YES //这个参数一定要加上,虚拟用户和本地用户有相同的权限;否则ftp连上后不能上传,报错550权限拒绝!
user_config_dir=/etc/vsftpd/vconf //指定不同虚拟用户配置文件的存放路径
#pasv_address=127.0.0.1
reverse_lookup_enable=NO
1
2
vim  ftpusers   最高级别黑名单
vim user_list 低级别黑名单

其他配置项目

max_per_ip默认值是50 测试结果:max_per_ip默认值并不是0(无限制),而是50

修改防火墙

cat /etc/sysconfig/iptables

1
2
A INPUT -s 10.68.250.13 -m state --state NEW -m tcp -p tcp --dport 2021 -j ACCEPT
-A INPUT -s 10.68.250.13 -m state --state NEW -m tcp -p tcp --dport 40001:40100 -j ACCEPT

设置虚拟FTP账号

  1. 虚拟账号它不是真实存在于系统中的,即/etc/passwd文件里没有的,它是借助于宿主账号virtusers。 奇数行是账号,偶数行是密码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    [root@infotech vsftpd]# cat virtusers
    lteinfotech
    lteinfotech1
    ftpuser
    1234888
    docuser
    1234888
    docadmin
    doc88888
  2. 生成虚拟用户口令的db文件(设置600权限)

    1
    2
    [root@infotech vsftpd]# db_load -T -t hash -f /etc/vsftpd/virtusers /etc/vsftpd/virtusers.db
    [root@infotech vsftpd]# chmod 600 /etc/vsftpd/virtusers.db
  3. 锁定目录(非必要)

    1
    2
    [root@infotech vsftpd]# cat /etc/vsftpd/chroot_list     //将虚拟用户放在这个列表文件里,说明这些用户登陆后都只能锁定到对应主目录内
    docuser
  4. 配置虚拟配置文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    [root@infotech vsftpd]# mkdir vconf      //此目录名是在vsftpd.conf配置中指定的,里面是虚拟用户配置文件(文件名是虚拟用户名)
    [root@infotech vconf]# pwd
    /etc/vsftpd/vconf
    [root@infotech vconf]# cat lteinfotech
    local_root=/data1/document //指定虚拟账号登陆后的主目录,目录权限要是宿主账号virtusers,这样就可以实现账号映射。chown -R virtusers.virtusers /data1/ftp key是其他目录,但是要授权。可以为不同ftp账号分配不同目录
    anonymous_enable=NO
    write_enable=YES //写权限
    local_umask=022
    anon_upload_enable=NO //上传权限
    anon_mkdir_write_enable=NO //创建目录权限
    anon_other_write_enable=NO //删除和重命名权限
    idle_session_timeout=300
    data_connection_timeout=90
    max_clients=1
    max_per_ip=1
    local_max_rate=0
    pam_service_name=vsftpd
    chroot_local_user=YES
    virtual_use_local_privs=NO // 如果是NO使用anon权限,否则使用主账号权限
  5. 系统其他配置

    1
    2
    3
    4
    5
    [root@infotech vsftpd]#  ll /lib64/security/pam_userdb.so
    [root@infotech vsftpd]# cat /etc/pam.d/vsftpd //注释原先内容修改如下
    #%PAM-1.0
    auth required /lib64/security/pam_userdb.so db=/etc/vsftpd/virtusers
    account required /lib64/security/pam_userdb.so db=/etc/vsftpd/virtusers
    1
    2
    [root@infotech vsftpd]# cat /etc/passwd|grep virtusers
    virtusers:x:501:501::/data1/ftp:/sbin/nologin
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    [root@infotech vsftpd]# chown -R virtusers.virtusers /data1/document     //设置虚拟账号指定的主目录的权限为virtusers,这样就可以映射到宿主账号
    [root@infotech data1]# ll
    鎬荤敤閲24
    drwxr-xr-x 3 root root 4096 11鏈27 17:07 backup
    drwxr-xr-x 2 root root 4096 3鏈 9 2018 blank
    drwxr-xr-x 3 virtusers virtusers 4096 1鏈 3 10:53 document
    drwxr-xr-x. 15 virtusers virtusers 4096 4鏈 13 2018 ftp
    drwxr-xr-x 7 virtusers virtusers 4096 12鏈 3 11:12 north
    drwxr-xr-x 9 root root 4096 4鏈 13 2018 transit
    [root@infotech vsftpd]# chmod -R 700 /data1/document

    [root@infotech vsftpd]# /etc/init.d/vsftpd start

    [root@infotech vsftpd]# ll /data1/ftp/

方式二:建立一个新的FTP账号

root用户执行

1
2
# useradd -d /data1/document docadmin //增加用户docadmin,并制定test用户的主目录为/data1/document
# passwd docadmin //为docadmin设置密码
1
2
setfacl -R -m u:admin:rwx /data1/document        --赋权admin账号对于document目录的权限   rwx   只读  只写   执行权限
vi /etc/vsftpd/user_list //添加新增的用户名

主配置文件

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
anonymous_enable=NO
local_enable=YES
write_enable=YES
local_umask=022
listen_port=288
ftp_data_port=208
pasv_min_port=1788
pasv_max_port=1789
anon_upload_enable=NO
anon_mkdir_write_enable=NO
dirmessage_enable=YES
xferlog_enable=YES
connect_from_port_20=YES
chown_uploads=NO
chroot_local_user=YES
chroot_list_enable=YES
chroot_list_file=/etc/vsftpd/chroot_list
xferlog_file=/var/log/vsftpd.log
xferlog_std_format=YES
nopriv_user=virtusers
async_abor_enable=YES
ascii_upload_enable=YES
ascii_download_enable=YES
ftpd_banner=Welcome to blah FTP service ^_^
chroot_list_enable=NO
ls_recurse_enable=NO
listen=YES
pam_service_name=vsftpd
userlist_enable=YES
userlist_deny=NO
userlist_file=/etc/vsftpd/user_list
tcp_wrappers=YES
guest_enable=YES
guest_username=virtusers
virtual_use_local_privs=YES
user_config_dir=/etc/vsftpd/vconf
#pasv_address=127.0.0.1 # 外网映射进来的情况可能需要修改成外网IP
reverse_lookup_enable=NO

重启vsftpd服务后发现还是无法使用,通过如下修改可以使用

1
2
3
4
5
6
7
8
9
10
11
[root@infotech vsftpd]# vi /etc/pam.d/vsftpd
#%PAM-1.0
#auth required /lib64/security/pam_userdb.so db=/etc/vsftpd/virtusers
#account required /lib64/security/pam_userdb.so db=/etc/vsftpd/virtusers
session optional pam_keyinit.so force revoke
auth required pam_listfile.so item=user sense=deny file=/etc/vsftpd/ftpusers onerr=succeed
auth required pam_shells.so
auth include password-auth
account include password-auth
session required pam_loginuid.so
session include password-auth

得出结论vsftpd虚拟用户和本地用户不能共存。有可以共存的策略把required改成sufficient

1
2
3
4
5
6
7
8
9
auth sufficient pam_userdb.so db=/etc/vsftpd/vuser_passwd
account sufficient pam_userdb.so db=/etc/vsftpd/vuser_passwd
session optional pam_keyinit.so force revoke
auth required pam_listfile.so item=user sense=deny file=/etc/vsftpd/ftpusers onerr=succeed
auth required pam_shells.so
auth include password-auth
account include password-auth
session required pam_loginuid.so
session include password-auth

Linux 客户端命令

在本机访问可以使用127.0.0.1或者本机ip。使用外网映射的ip是无法访问的。所以对于有的系统需要连接ftp服务器最好是提供配置像可以配置使用内部IP还是外部IP,有些组网情况下不可用。

1
2
3
ftp 192.168.33.224 288 # ftp ip port
# 输入用户名/密码
put a.log # 上传本地文件

FTP用户配置

不同网络环境下的应用

  • 主动模式,客户端在虚拟机中需要使用桥接模式才能与服务端进行数据通信,被动模式的话不会有问题
  • 主动模式,如果客户端在阿里云的话可能需要独立IP,阿里云默认使用的估计是NAT所以会报错500 Illegal PORT command
  • 主动模式,Client通过路由器访问外网,外网的Server增么范围内网开的数据端口的?

FTP Client → NAT → internet → NAT → FTP Server 这种情况,设备以主动模式访问FTP服务器时,由于NAT设备不会聪明的变更FTP包中的IP地址,从而导致无法访问服务器,防火墙也类似。NAT要对端口进行映射估计可以使用。

Port模式的FTP步骤如下:

  1. 客户端发送一个TCP
    SYN(TCP同步)包给服务器段众所周知的FTP控制端口21,客户端使用暂时的端口作为它的源
    端口;
  2. 服务器端发送SYN ACK(同步确认)包给客户端,源端口为21,目的端口为客户端上使用的暂时端口;
  3. 客户端发送一个ACK(确认)包;客户端使用这个连接来发送FTP命令,服务器端使用这个连接来发送FTP应答;
  4. 当用户请求一个列表(List)请求或者发起一个要求发送或者接受文件的请求,客户端软件使用PORT命令,这个命令包含了一个暂时的端口,客户端希望服务器在打开一个数据连接时候使用这个暂时端口;PORT命令也包含了一个IP地址,这个IP地址通常是客户自己的IP地址,而且FTP也支持第三方(third-party)模式,第三方模式是客户端告诉服务器端打开与另台主机的连接;
  5. 服务器端发送一个SYN包给客户端的暂时端口,源端口为20,暂时端口为客户端在PORT命令中发送给服务器端的暂时端口号;
  6. 客户端以源端口为暂时端口,目的端口为20发送一个SYN ACK包;
  7. 服务器端发送一个ACK包;
  8. 发送数据的主机以这个连接来发送数据,数据以TCP段(注:segment,第4层的PDU)形式发送(一些命令,如STOR表示客户端要发送数据,RETR表示服务器段发送数据),这些TCP段都需要对方进行ACK确认(注:因为TCP协议是一个面向连接的协议)
  9. 当数据传输完成以后,发送数据的主机以一个FIN命令来结束数据连接,这个FIN命令需要另一台主机以ACK确认,另一台主机也发送一个FIN命令,这个FIN命令同样需要发送数据的主机以ACK确认;
  10. 客户端能在控制连接上发送更多的命令,这可以打开和关闭另外的数据连接;有时候客户端结束后,客户端以FIN命令来关闭一个控制连接,服务器端以ACK包来确认客户端的FIN,服务器同样也发送它的FIN,客户端用ACK来确认。

当使用FTP时候,网络中的防火墙必须要声明相应的端口,防火墙必须要跟踪FTP对话然后检查PORT命令,防火墙必须要参与从服务器端到客户端在PORT命令中指定的端口连接的建立过程。如果网络中使用了NAT(注:网络地址翻译),那么NAT的网关同样也需要声明相应的端口,网关需要把在PORT命令中指定的IP地址翻译成分配给客户的地址,然后重新计算TCP的Checksum;如果网关没有正确地执行这个操作,FTP就失败了。

黑客可能会利用FTP支持第三方特性这一特点,在PORT命令中设置IP地址和端口号参数来指定一台目标主机的地址和端口号(有时候称这种攻击为FTP反弹攻击),例如黑客可以让一台FTP服务器不断地从它的源端口20发送TCP SYN包给一系列目的端口,让FTP服务器看起来正在进行端口扫描,目的主机不知道攻击来自黑客的主机,看起来攻击象是来自FTP服务器。一些常用的FTP应用在PORT命令中设置地址为0.0.0.0,这样做的意图是让FTP服务器只需要与打开控制连接的相同客户进行数据连接,设置地址为0.0.0.0可能会让防火墙不知所措。

被动模式

  • 设置端口范围
  • 还要设置外网的IP

需要将命令端口和数据端口在路由器上映射到公网里,其中命令端口是21和22,数据端口是35001-35005。

还有一种称之为DMZ主机的把所有端口都映射到外网,不是很好的做法。

如果服务器在阿里云需要配置放行策略 云服务器ECS-网络和安全-安全组-配置规则-入方向

很多防火墙在设置的时候都是不允许接受外部发起的连接的,所以许多位于防火墙后或内网的FTP服务器不支持PASV模式,因为客户端无法穿过防火墙打开FTP服务器的高端端口;而许多内网的客户端不能用PORT模式登陆FTP服务器,因为从服务器的TCP 20无法和内部网络的客户端建立一个新的连接,造成无法工作。

设置5个PASV端口一般意味着单个客户端支持5个并发数据传输,其它的要排队等待。连接的组成是客户端IP+Port+服务端IP+Port。由于TCP连接断开可能出现TIME_WAIT一段时间不可用,所以可以通过使用多个端口或者其他模式保持可用状态。

防火墙需要放开配置的被动端口范围或者数据端口,还有控制端口。

不同网络环境可能还需要配置pasv_address指定被动模式使用的ip也就是外网IP。(Default: (none - the address is taken from the incoming connected socket) ) pasv模式中服务器传回的ip地址。【十分重要。这个地址是你服务器的公网地址。PASV模式,服务器会返回一个6位的地址和端口。如果不设置上述两项,返回的是私网ip,远程连接无法找到你的地址。】要同时支持内外网可能就需要部署多套VSFTP服务了,或者内网用主动外网用被动模式。

集群多台FTP服务器

根据端口范围段进行端口映射转发提高性能。比如5000-5005到服务器A,5006-5010到服务器B,服务器之间再做数据同步。

开发注意事项

  1. 完整性:为了避免文件在传输过程走被对端程序处理,可以在传输过程中使用.tmp后缀标识,传输成功后去掉.tmp,对端程序监控非.tmp文件就可以处理到完整数据。The inotify interface does have limitations—it can’t monitor remote, network-mounted filesystems (that is, NFS); it does not report the userid involved in the event; it does not work with /proc or other pseudo-filesystems; and mmap() operations do not trigger it, among other concerns. Even with these limitations, it is a tremendously useful feature.
  • 上传的时候增加.tmp后缀,上传结束后才改为正确的后缀比如.csv,这样可以避免同步全量数据的时候上传半半网络段了,部分数据被对方程序解析走,特别是同步全量数据会导致对方做同步删除操作。 对于其他方式的同步比如NAS等可以写到一个tmp目录中,通过mv命令移动到共享目录
  1. 文件传输一般使用inotify监控linux系统提供的写完成事件。当然通过定时扫描处理也不是不可以,避免扫描文件太多处理完成后需要移动到其他目录。当然如果其他程序也是通过FTP获取文件的话这个方法是用不了的。
  2. 对于java写的客户端程序,还有FTP超时断开连接没有重连问题需要考虑。还有就算文件格式错误,具体业务需要处理的各种异常情况了。可能有问题的其实是主动、被动模式、服务器连接数限制、DNS等等。

可能出现问题的地方

421 Too many connections (10) from this IP

修改连接数:只需修改FTP配置,MaxClientsPerIP ,默认只有10,改成1000

421 Data timeout. Reconnect. Sorry

ftp传输文件错误“421 Data timeout. Reconnect”,代表数据连接超时,连接被服务器强制断开了,只要我们找到vsftpd.conf文件,将文件里面的data_connection_timeout参数值改大一点的就可以解决问题了,特别是当我们网络环境不好的时候特别容易出现这个问题。

客户端连接池也可以设置超时时间,超时重连。

227 Entering Passive Mode

可能IP地址没有设置对,可能防火墙没放开。

响应: 500 OOPS: vsftpd: refusing to run with writable root inside chroot()

增加配置项目:
allow_writeable_chroot=yes

警告信息… 状态: 服务器发回了不可路由的地址。使用服务器地址代替。

FileZilla可以正常上传,但是有些FTP客户端可能没那么智能不懂替换无法路由的IP地址。
需要设置pasv_address,有些FTP客户端可能遇到这个问题没做处理,所以对于有多个IP的,在外网使用,就必须配置上这个外网IP地址。

登录报错

1
2
3
响应:	331 Please specify the password.
命令: PASS ********
响应: 500 OOPS: cannot change directory:/ffff/xxxx

查看/ffff目录权限是否正确,可能上层目录没有权限导致问题。

运维

查看日志tail -f /var/log/vsftpd.log
注意防火墙被动端口要打开,不然就会出现登录成功,但是无法获取目录,无法上传下载。

支持特殊情况

可以内网用主动,外网用被动模式。 也可以安装多个vsftpd服务提供给不同网络使用。

客户端

大多数操作系统都提供了FTP客户端功能。 windows可以在文件资源管理器输入ftp://userame:password@8.8.8.2:2132

判断是否写完成

方案1.上传文件的客户端先创建一个临时文件名称,然后上传完毕再重命名。而重命名是修改注册表所以不会产生过程中数据不一致的问题

方案2.在ftp客户端上传文件不由我们控制的时候,不能实施第一方案怎么办呢。打开文件在文件尾追加一个标识内容,关闭打开文件,然后在重新打开文件判断追加写入的内容时候成功,如果不成功,说明文件还在被占用,如果成功了就说明ftp上传结束了。还可以用是否重命名成功来试。
还有一种就是判断如果文件不增长了就当成写完成了。都是不是很好,最好还是用inotify通知来实现。

多次写文件到同一个文件中,之前写入文件内容丢失,可以使用追加写入的模式,不要覆盖之前文件就可以解决这个问题。
或者使用多线程读取操作文件,每个线程读取文件之前循环3次间隔半秒,md5判断文件是否变化,如果都不变化当作文件写完成了?还可以循环等的时候使用累加0.5秒,1秒,2秒加上去。

监控文件是否创建并且移走不能用apache io的方法必须用inotify,apache io文件没有写完成也会被移动走。

老外的回答:
This is a very old and well-known problem.

There is no way to be absolutely certain a file being written by the FTP daemon is complete. It’s even possible that the file transfer failed and then gets restarted and completed. You must poll the file’s size and set a time limit, say 5 minutes. If the size does not change during that time you assume the file is complete.

If possible, the program that processes the file should be able to deal with partial files.

A much better alternative is rsync, which is much more robust and deterministic. It can even be configured (via command-line option) to write the data initially to a temporary location and move it to its final destination path upon successful completion. If the file exists where you expect it, then it is by definition complete.

参考