简介

FTP,File Transfer Protocol,文件传输协议
文件服务器,具备存储和共享文件(权限设置合理的情况下)的功能
命令端口:TCP/21
数据端口:TCP/20(主动模式)
明文传输指令与数据,解决办法是SFTP(SSH)和FTPS(SSL/TLS)

实现

服务器端实现

Linux:vsftpd, pureftpd, proftpd
Windows:Filezilla Server, Serv-U, ...

客户端实现

Linux:ftp, lftp, ...
Windows:cuteftp, Filezilla, Flashfxp, ...

连接模式

FTP协议是一种古老的协议,比较特别。一般的协议,客户端和服务器端之间只需要一条连接即可,但是FTP需要两条,一条是命令连接,用于传输FTP命令;另外一条是数据连接,用于传输上传下载的数据。
命令连接为客户端启用一个随机端口连接服务器端的21端口。
数据连接有主动模式和被动模式两种。

主动模式

主动模式,positive,PORT

  1. 客户端启用一个高位随机端口x去连接服务器的21端口建立命令连接。
  2. 命令连接建立完毕后,客户端使用“PORT x+1”命令告诉服务器端客户端在x+1号监听数据连接。
  3. 服务器端使用20号端口去连接客户端的x+1号端口建立数据连接。
  4. 客户端通过数据连接向服务器端发送一个应答,表示数据连接建立成功开始传输数据。
    vsftpd_第1张图片

    缺陷:客户端需要启用一个数据连接监听端口等待服务器端来连接,因此客户端如果有防火墙的话,就需要对此端口进行放行。同时又由于端口号为随机的x+1号端口,因此在客户端防火墙要放行一个随机的端口就有一定的难度了!加之客户端的用户很多都是计算机小白用户,一般不太懂的做这个设置,也不太情愿。

被动模式

被动模式,passive,PASV
由于主动模式缺陷的存在,诞生了被动模式。

  1. 客户端启用一个高位随机端口x去连接服务器的21端口建立命令连接。
  2. 客户端通过命令连接向服务器端发送PASV指令表示要使用被动模式。
  3. 服务器端同意使用PASV模式,并开始监听一个高位随机端口m,并将此端口号告知客户端。
  4. 客户端使用x+1号端口与服务器端口m建立数据连接。
  5. 服务器端响应客户端发起的数据连接请求,数据连接成功建立。
    vsftpd_第2张图片

数据传输模式

参考资料:
https://mingersoft.com/blog/2011/01/ftp-and-the-difference-between-ascii-and-binary-modes/
http://www.jscape.com/blog/ftp-binary-and-ascii-transfer-types-and-the-case-of-corrupt-files

ASCII

数据在计算机中存储或者在计算机间传输,需要以计算机可识别的一种形式而存在。这种形式就是二进制的数据,即0和1。将字母a翻译成二进制数值存储在计算机中,这种翻译过程可理解为编码。不同的计算机,编码可能不同,因此需要统一,应该是ISO给统一的,后来就诞生了ASCII这种编码。ASCII编码将大写字母、小写字母、数字以及控制符号都定义了ASCII值。比如数字0的十进制ASCII值是48,二进制的值是00110000。
一般用于传输纯文本文件(例如批处理脚本、shell脚本、HTML文件、JavaScript文件等)。对于跨平台(例如Windows和Linux)的传输来说,ASCII传输模式可以自动修改行结束符,避免出现换行错乱的情况。
vsftpd_第3张图片

Image / Binary

逐字节传输数据,保留数据本身模样,不会像ASCII那样转换数据,因此不会自动转换文本文件的行结束符,可能出现跨操作系统传输文件后文本换行错乱的情况。
一般用于传输二进制文件,库文件、图片、视频、Linux下的ELF可执行文件等。

纯文本文件区分

可以通过文本编辑器来区分文件是否是文本文件,常见的文本编辑器如下:

  • Windows记事本
  • Sublime Text 3
  • Notepad++
  • Vim

文本文件使用文本编辑器打开后是正常显示的,而二进制文件如果使用编辑器打开一般会是此类乱码。
vsftpd_第4张图片

模式选择

  1. FTP客户端程序一般会自动识别,因此我们无需干预。
    vsftpd_第5张图片
  2. FTP命令行工具可通过type命令切换
    ftp> type ascii
    200 Switching to ASCII mode.
    ftp> type binary
    200 Switching to Binary mode.
    ftp> type image
    200 Switching to Binary mode.

用户

匿名用户

登录的用户名为anonymous或者ftp,密码为空。映射到本地用户ftp,家目录为/var/ftp/。

本地用户

登录的用户名和密码与Linux本地用户相同。每个FTP本地用户都会映射到同用户名的Linux用户,家目录为Linux用户的家目录。

虚拟用户

虚拟用户可以有多个,所有的虚拟用户都只会映射到一个本地用户,此本地用户在配置文件中被称为来宾用户(guest_username),来宾用户的家目录即为虚拟用户的家目录,每个虚拟用户都可以拥有独立的用户名和密码。本文中,虚拟用户的用户认证方式目前有两种:

  • 伯克利DB文件
  • MySQL

环境

系统:CentOS 6.9
软件:vsftpd 2.2.2
本实验在iptables和selinux均关闭的情况下进行

帮助

官网:http://vsftpd.beasts.org
查看配置文件中的样例说明

vim /etc/vsftpd/vsftpd.conf

查看man手册

man 5 vsftpd.conf

配置文件

/etc/vsftpd/vsftpd.conf
此配置文件也是主配置文件
每一行就是一句指令(directive)或者注释
指令的格式如下

option=value

在option、=和value的两边都不可以存在空格
指令有默认值,如果没有显式定义指令,则采用默认值,默认值查看man手册。
“#”开头的行表示注释,“#”后接着1个空格表示描述信息

# Example config file /etc/vsftpd/vsftpd.conf
#
# The default compiled in settings are fairly paranoid. This sample file
# loosens things up a bit, to make the ftp daemon more usable.
# Please see vsftpd.conf.5 for all compiled in defaults.

“#”后不接空格表示被注释掉的指令

#chown_uploads=YES
#chown_username=whoever

配置文件修改之后需要重启vsftpd服务才会使修改生效

# service vsftpd restart
Shutting down vsftpd:                                      [  OK  ]
Starting vsftpd for vsftpd:                                [  OK  ]

匿名用户和本地用户的实现

指令说明

vsftpd对权限的设置要求事无巨细,特别是对匿名用户的读(下载)、写(上传文件、创建目录、删除文件和重命名等)操作,除了要求对应的Linux用户权限设置妥当以外,还要有对应的不同指令的正确设置。

首先

write_enable:控制FTP是否可写,无论用户类型。最终用户是否具备相应的权限,一个是看指令上是否允许,另一个就是和Linux用户权限相关。

匿名用户

anonymous_enable:是否启用匿名用户。
anon_upload_enable:是否允许匿名用户上传文件。
anon_mkdir_write_enable:是否允许匿名用户创建目录。
anon_other_write_enable:是否允许匿名用户执行其他写操作,包括删除和重命名。
no_anon_password:是否不向匿名用户询问密码,反正匿名用户也没有密码,因此这项可以设置为YES。

本地用户

local_enable:是否启用本地用户。
local_umask:本地用户创建文件的umask,文件使用666去减,目录使用777去减。

目录消息

dirmessage_enable:第一次进入某个目录的欢迎/描述信息,默认在每个目录下搜索.message文件,是否显示与客户端有关。
message_file:指定目录消息文件的文件名。

数据传输日志

xferlog_enable:是否启用传输日志,传输日志记录用户上传下载的细节信息。
vsftpd_log_file:传输日志的位置。当且仅当xferlog_enable为YES且xferlog_std_format为NO的时候有效。默认值是/var/log/vsftpd.log。
xferlog_std_format:是否启用标准格式的传输日志,标准格式的传输日志易读性差,不过可用于传输统计生成器(猜测应该是一种日志统计工具)再利用。
xferlog_file:指定标准格式的传输日志文件存储位置,当且仅当xferlog_enable和xferlog_std_format都被启用的情况下有效。默认值是/var/log/xferlog。

数据传输模式

port_enable:是否启用PORT模式,默认值是YES。
connect_from_port_20:指定FTP的PORT模式是否使用20号端口;
ftp_data_port:当connect_from_port_20为NO的时候,此选项设置PORT模式下FTP服务器使用的数据连接端口,默认值是20。
ascii_download_enable和ascii_upload_enable:上传和下载的时候是否明确使用ASCII数据传输模式。默认情况下服务器假装允许ASCII模式但实际上会忽略ASCII请求。某些FTP如果启用此特性,会被通过FTP命令SIZE一个大文件来达到DoS***的效果。
pasv_enable:是否启用PASV模式,默认值是YES。
pasv_max_port和pasv_min_port:指定PASV模式下的端口范围,设置了好配合防火墙设置,0表示使用任意端口。

修改匿名用户上传文件的属主

chown_uploads:是否修改匿名用户上传文件的属主,不修改的话默认匿名用户上传的文件的属主和属组都是ftp。
chown_username:设置匿名用户上传文件的新属主,不会改变属组。
chown_upload_mode:设置匿名用户上传文件后文件的权限,默认是0600。

设定超时时长

idle_session_timeout:客户端输入两次FTP命令间隔时间的超时时长,默认是300秒。
connect_timeout:PORT模式下,客户端响应数据连接的超时时长,默认是60秒。
data_connection_timeout:数据传输停止的大致超时时长,默认是300秒。

命令连接的监听端口

standalone(独立模式):表示直接运行在后台,自己监听,不依靠类似xinetd的守护进程管理。

listen:启用的话,使vsftpd运行于独立模式。
listen_address:当vsftpd运行于独立模式时,此选项设置vsftpd监听的ipv4地址,默认是0.0.0.0。
listen_port:当vsftpd运行于独立模式时,此选项设置vsftpd监听端口,默认是21。

设定连接及传输速率

local_max_rate:本地用户最大数据传输速率,单位是“字节/秒”,默认是0,表示无限制。
anon_max_rate:匿名用户最大数据传输速率,单位是“字节/秒”,默认是0,表示无限制。
max_clients:当vsftpd运行于独立模式时,此选项设置vsftpd支持的最大客户端连接数,0表示无限制,默认值是2000。
max_per_ip:当vsftpd运行于独立模式时,此选项设置vsftpd支持的最大相同IP地址的客户端连接数,0表示无限制,默认值是50。

禁锢本地用户

chroot可以理解为禁锢的意思,使得用户在自己的家目录,无法切换至其他目录,但是进入自己的子目录除外。
匿名用户始终被禁锢。

chroot_local_user:是否禁锢所有本地用户。
chroot_list_enable:是否启用禁锢列表文件。当chroot_local_user为YES的时候,chroot_list_file表示不被禁锢的本地用户。当chroot_local_user为NO的时候,chroot_list_file表示被禁锢的本地用户。
chroot_list_file:禁锢列表文件位置。
注意:vsftpd从2.3.5版本开始,如果设置了禁锢用户,那么当用户登录的时候,默认是会报错的。

refusing to run with writable root inside chroot ()

解决的办法有两种:

  1. 将用户的家目录设置为只读模式。
  2. 选项allow_writeable_chroot的值设置为YES。

控制用户登录

/etc/vsftpd/ftpusers:这是一个文件,非指令。黑名单文件,文件中的用户将被禁止登录FTP。不过是在输入密码之后才禁止登录的,密码仍然被传输了。
将zwl用户加入ftpusers文件中

# grep 'zwl' /etc/vsftpd/ftpusers 
zwl

在客户端上测试登录

# ftp 192.168.17.100
Connected to 192.168.17.100 (192.168.17.100).
220 (vsFTPd 2.2.2)
Name (192.168.17.100:root): zwl
331 Please specify the password.
Password:
530 Login incorrect.
Login failed.

userlist_enable:是否启用用户列表文件。
userlist_file:用户列表文件位置。认值是/etc/vsftpd/user_list。
userlist_deny:用户列表文件中的用户是否被拒绝。如果用户被用户列表给拒绝了,那么连输入账号密码的机会都没有。默认值是YES。
在配置文件中启用用户列表userlist_enable,选项userlist_file和userlist_deny保持默认值即可。

# grep "userlist" /etc/vsftpd/vsftpd.conf
userlist_enable=YES

将用户zwl加入userlist_file

# grep "zwl" /etc/vsftpd/user_list 
zwl

FTP客户端测试,可以看到报错的信息和ftpusers文件是不同的, 重要的是使用用户列表拒绝的话,用户无法输入密码。

# ftp 192.168.17.100
Connected to 192.168.17.100 (192.168.17.100).
220 (vsFTPd 2.2.2)
Name (192.168.17.100:root): zwl
530 Permission denied.
Login failed.

PAM简述

要配置虚拟用户的话,需要理解PAM,不过我个人不太懂PAM,目前只能简单理解。

PAM,Pluggable Authentication Modules,可插拔认证模块。
vsftpd是通过PAM来完成用户认证的,配置文件中通过pam_service_name来指定vsftpd使用的PAM服务文件。

pam_service_name=vsftpd

这是相对路径,相对于/etc/pam.d/,即使用/etc/pam.d/vsftpd这个PAM服务文件,此目录下每一个文件都是一个pam服务。
下面是vsftpd默认使用的PAM服务文件,目前并不详细理解,大概理解就可以了,比如从文件的第三行中的可以看出文件/etc/vsftpd/ftpusers中的每一行表示用户(item=user),每个用户都被拒绝登录(sense=deny)。

# cat /etc/pam.d/vsftpd
#%PAM-1.0
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

PAM服务文件中调用的“.so”文件是相对路径,相对于/lib64/security/,此类文件是动态链接(dynamically linked)的库文件也是PAM的模块文件。

虚拟用户之伯克利DB文件的实现

创建用户信息文本文件

# cat /etc/vsftpd/virtual_user.txt 
tom
123456
jerry
123456

安装db4相关包获取db_load工具,此工具存在于包db4-utils中,理论上安装此包即可。

# rpm -qa | grep 'db4'
db4-devel-4.7.25-22.el6.x86_64
db4-utils-4.7.25-22.el6.x86_64
db4-cxx-4.7.25-22.el6.x86_64
db4-4.7.25-22.el6.x86_64

生成用户信息db文件

# db_load -T -t hash -f /etc/vsftpd/virtual_user.txt /etc/vsftpd/virtual_user.db

db_load的具体用法可以查阅RPM包db4-utils中的HTML文件

# rpm -ql db4-utils | grep 'db_load'
/usr/bin/db_load
/usr/share/doc/db4-utils-4.7.25/utility/db_load.html

pam服务文件替换

# mv /etc/pam.d/vsftpd{,.bak}
# vim /etc/pam.d/vsftpd
auth required pam_userdb.so db=/etc/vsftpd/virtual_user
account required pam_userdb.so db=/etc/vsftpd/virtual_user

关于pam_userdb模块的介绍,请参照官网:http://www.linux-pam.org/Linux-PAM-html/sag-pam_userdb.html
模块的参数db的值不可以是virtual_user.txt或者virtual_user.db,一定得是virtual_user!
创建虚拟用户所映射的本地用户

useradd -d /home/virtual_user_home -s /sbin/nologin virtual_user
chmod 755 /home/virtual_user_home/

当来宾用户的家目录的权限不是所有人都可读而且anon_world_readable_only的值为YES的时候,虚拟用户登录后执行ls查看目录会报错

226 Transfer done (but failed to open directory).

目前找到的解决办法有两种
i. 虚拟用户的主目录,确保所有人都有读的权限。
ii. 显式地设置anon_world_readable_only为NO。

anon_world_readable_only
    当启用的时候,匿名用户只能下载那些全局(所有者、所有组和其他人)可读的文件。这是意识到了FTP用户可能会拥有自己的文件,特别是在允许上传的时候。这样可以防止匿名用户下载其他FTP用户上传的自己没有读权限的文件。默认值是YES。

配置文件中虚拟用户相关配置

local_enable=YES
    非匿名用户想要登录的话,必须开启,包括虚拟用户。
guest_enable=YES
    如果启用,所有非匿名(本地和虚拟)登录都会被认为是guest登录。guest登录的用户会被映射成选项guest_username所指定的用户。
guest_username=virtual_user
pam_service_name=vsftpd

配置了虚拟用户之后,匿名用户可以登录,本地用户无法登录(因为pam服务文件已被我们修改)。
虚拟用户在OS中的权限设置是被识别为选项guest_username所指定的用户。
虚拟用户在配置文件中的权限设置,默认是被识别为匿名用户,也就是说如果要确保虚拟用户具备所有权限的话,还需要如下设置。

anon_upload_enable=YES
anon_mkdir_write_enable=YES
anon_other_write_enable=YES

如果想要修改虚拟用户默认映射的用户的话,可以参考下面这个选项。

virtual_use_local_privs
    默认情况下虚拟用户被识别为匿名用户(这么做是为了使得虚拟用户的权限设置更为严格,特别是在写权限上),通过设置此项可以将虚拟用户识别为本地用户。默认值是NO。

虚拟用户和匿名用户一样,默认是被禁锢

ftp> pwd
257 "/"
ftp> cd /etc
550 Failed to change directory.

目前为止,我们已经可以通过伯克利DB来实现虚拟用户了,但是所有的虚拟用户的权限都是一样的,如果想要使不同的虚拟用户具备不同的权限呢?
可以借助配置文件中的选项user_config_dir,通过此选项我们可以配置一个目录:

user_config_dir=/etc/vsftpd/user_conf/

在目录下创建以用户名命名的文本文件,在文本文件中针对不同的虚拟用户设置不同的权限:

# cat /etc/vsftpd/user_conf/tom
anon_upload_enable=YES
anon_mkdir_write_enable=YES
anon_other_write_enable=YES
# cat /etc/vsftpd/user_conf/jerry
anon_upload_enable=NO
anon_mkdir_write_enable=NO
anon_other_write_enable=NO

这种方式称为per-user basis,每个虚拟用户的配置文件的语法和主配置文件是一样的,但不是所有的选项都会生效,比如某些设置在用户的会话启用前就已经被应用了,比如listen_address、banner_file、max_per_ip、max_clients和xferlog_file等。
在此基础上可以在主配置文件中禁用匿名用户

anonymous_enable=NO

这样子实现的结果就是:

  • 匿名用户无法登录。
  • 虚拟用户tom具有所有的权限。
  • 虚拟用户jerry只有下载那些所有人可读的文件的权限。

虚拟用户之MySQL的实现

安装mysql服务器端,我这边是通过下载官网5.7的yum源后安装的

# rpm -qa | grep 'mysql'
mysql-community-libs-compat-5.7.21-1.el6.x86_64
mysql57-community-release-el6-11.noarch
mysql-community-client-5.7.21-1.el6.x86_64
mysql-community-libs-5.7.21-1.el6.x86_64
mysql-community-common-5.7.21-1.el6.x86_64
mysql-community-server-5.7.21-1.el6.x86_64
mysql-community-devel-5.7.21-1.el6.x86_64

注意:如果pam-mysql是源码编译安装的话,一定要记得安装mysql的devel包,否则在configure阶段会报错,无法找到头和库文件等。

# ./configure --with-pam-mods-dir=/lib64/security/ --with-mysql=/usr
checking if "/usr" is a mysql_config script... no
checking mysql_config availability in /usr/bin... no
checking mysqlclient availability in /usr/lib... no
checking mysqlclient availability in /usr/lib/mysql... no
checking mysql headers availability in /usr/include... no
checking mysql headers availability in /usr/include/mysql... no
configure: error: Cannot locate mysql client library. Please check your mysql installation.

关于mysql 5.7的初始化密码以及密码要求
一般我们安装并启动完mysql之后,都会运行mysql_secure_installation这个安全初始化脚本,以前的版本默认初始MySQL的root密码为空,因此直接回车即可通过
但是在5.7中,会替用户初始化一个随机密码,并将此密码写在log当中
记得提取此密码来运行mysql_secure_installation

# grep 'temporary password' /var/log/mysqld.log 
2018-04-11T09:38:41.716068Z 1 [Note] A temporary password is generated for root@localhost: H;CPodi6OhDt

降低密码策略以及密码长度要求,方便我们设置简单的密码

mysql> SET GLOBAL validate_password_policy = 'LOW';
mysql> SET GLOBAL validate_password_length = 4;

初始化虚拟用户数据及MySQL用户授权

mysql> CREATE DATABASE vsftpd_auth CHARACTER SET utf8;
Query OK, 1 row affected (0.01 sec)

mysql> USE vsftpd_auth;
Database changed

mysql> CREATE TABLE users (
    -> name VARCHAR(100),
    -> password VARCHAR(100)
    -> );
Query OK, 0 rows affected (0.03 sec)

mysql> INSERT INTO `users` VALUES ('tom',PASSWORD('123456')),('jerry',PASSWORD('123456'));
Query OK, 2 rows affected, 2 warnings (0.01 sec)
Records: 2  Duplicates: 0  Warnings: 2

mysql> SELECT * FROM `users`;
+-------+-------------------------------------------+
| name  | password                                  |
+-------+-------------------------------------------+
| tom   | *6BB4837EB74329105EE4568DDA7DC67ED2CA2AD9 |
| jerry | *6BB4837EB74329105EE4568DDA7DC67ED2CA2AD9 |
+-------+-------------------------------------------+
2 rows in set (0.01 sec)

mysql> CREATE USER 'vsftpd_auth'@'localhost' IDENTIFIED BY '123456';
Query OK, 0 rows affected (0.02 sec)

mysql> GRANT ALL ON `vsftpd_auth`.* TO 'vsftpd_auth'@'localhost';
Query OK, 0 rows affected (0.01 sec)

mysql> FLUSH PRIVILEGES;
Query OK, 0 rows affected (0.01 sec)

在创建表users中设置字段的长度为100,因为我们使用了PASSWORD函数加密,加密后的字符长度为42,长度设置不够的话,在INSERT的时候会报错

ERROR 1406 (22001): Data too long for column 'password' at row 1

来宾账户依然使用之前在伯克利DB文件实验环节中使用的

# grep 'virtual_user' /etc/passwd
virtual_user:x:501:501::/home/virtual_user_home:/sbin/nologin
# ls -ld /home/virtual_user_home/
drwxr--r-- 4 virtual_user virtual_user 4096 Apr 10 11:04 /home/virtual_user_home/
# grep 'guest' /etc/vsftpd/vsftpd.conf
guest_enable=YES
guest_username=virtual_user

下载安装pam-mysql-第三方RPM包
网上许多安装pam-mysql的教程都是通过官网:http://pam-mysql.sourceforge.net/
来源码编译安装,通过源码编译安装的pam-mysql,安装成功后在我的环境下会登录失败
通过/var/log/secure日志,发现频繁出现这两句报错

Apr 13 16:47:52 server vsftpd[50238]: PAM unable to dlopen(/lib64/security/pam_mysql.so): /lib64/security/pam_mysql.so: undefined symbol: make_scrambled_password
Apr 13 16:47:52 server vsftpd[50238]: PAM adding faulty module: /lib64/security/pam_mysql.so

Google了一番发现这应该是pam-mysql自身的bug,网上有针对此问题而制作的RPM包,如下
RPM包参考网址:https://centos.pkgs.org/6/epel-x86_64/pam_mysql-0.7-0.12.rc1.el6.x86_64.rpm.html
此RPM的changelog刚好解决了此问题

2011-06-10 - Paul P. Komkoff Jr  1:0.7-0.12.rc1
- make_scrambled_password fix (bz#709534)

下载安装过程

# wget http://dl.fedoraproject.org/pub/epel/6/x86_64/Packages/p/pam_mysql-0.7-0.12.rc1.el6.x86_64.rpm
# rpm -ivh pam_mysql-0.7-0.12.rc1.el6.x86_64.rpm
# rpm -ql pam_mysql
/lib64/security/pam_mysql.so
/usr/share/doc/pam_mysql-0.7
/usr/share/doc/pam_mysql-0.7/COPYING
/usr/share/doc/pam_mysql-0.7/CREDITS
/usr/share/doc/pam_mysql-0.7/ChangeLog
/usr/share/doc/pam_mysql-0.7/NEWS
/usr/share/doc/pam_mysql-0.7/README

如果上面那个RPM包安装的pam-mysql模块,使用失败的,可以考虑官网的源码安装,如下两步
安装开发环境包组用于源码编译安装

# yum groupinstall "Development Tools" "Server Platform Development"

下载安装pam-mysql-官网源码编译安装

# cd /usr/local/src/
# wget http://prdownloads.sourceforge.net/pam-mysql/pam_mysql-0.7RC1.tar.gz
# tar -xzf pam_mysql-0.7RC1.tar.gz
# cd pam_mysql-0.7RC1/
# ./configure --with-pam-mods-dir=/lib64/security/ --with-mysql=/usr
# make
# make install

关于pam-mysql的参数说明,记得阅读一下README

RPM包安装的位于:/usr/share/doc/pam_mysql-0.7/README
源码编译安装的位于:/usr/local/src/pam_mysql-0.7RC1/README

创建PAM服务文件
参数verbose:设置后可用于排错,应该也是设置了我才看到了/var/log/secure中的报错
参数crypt:设置用户密码的加密方法,0或者plain表示纯文本,2或者mysql表示使用MySQL的PASSWORD()函数加密

# cat /etc/pam.d/vsftpd.mysql 
auth required pam_mysql.so user=vsftpd_auth passwd=123456 host=localhost db=vsftpd_auth table=users usercolumn=name passwdcolumn=password crypt=2 verbose=1 sqllog=1
account required pam_mysql.so user=vsftpd_auth passwd=123456 host=localhost db=vsftpd_auth table=users usercolumn=name passwdcolumn=password crypt=2 verbose=1 sqllog=1

设置vsftpd调用PAM服务文件

# grep 'pam_service' /etc/vsftpd/vsftpd.conf
pam_service_name=vsftpd.mysql

vsftpd配置文件有修改的话,记得重启服务

# service vsftpd restart
Shutting down vsftpd:                                      [  OK  ]
Starting vsftpd for vsftpd:                                [  OK  ]

我自己测试登录是OK的,就不贴上来了。
基于pam-mysql的虚拟用户做per-user basis的权限设置,应该是和基于伯克利DB文件实现是一样的,这里没测试。

总结

vsftpd的使用感觉还是有点麻烦,以前尝试过pure-ftpd,相对简单很多。
vsftpd使用到这个程度的话,我觉得已经很足够了,毕竟这个服务目前已经不是很多人在使用了。