Introduction
This has been tested on Debian Etch 4.0 and Debian Lenny 5.0, the procedure is the same in both versions. It should be roughly the same on Debian derived systems like Ubuntu.
Here we’ll learn how to set up a FTP server with virtual users, as opposed to real system users. This helps you to both secure your server better and manage multiple users. This is however a pretty basic setup, no user quota and so forth as I didn’t need it when I set up my system. However there are excellent information to be found on places like howtoforge.com if you are looking for a more in depth FTP solution.
Getting everything we need
Well be using VSFTPD and MySQL, we also need libpam-mysql to enable VSFTPD to check usernames and passwords from a MySQL database.
root@server:~# apt-get install vsftpd libpam-mysql mysql-server mysql-client
You’ll be asked to set up a MySQL root password during install, this is the root user for MySQL server and not the same as the system’s root.
Create a database and FTP-users
Log in with MySQL’s root user by typing:
root@server:~# mysql -u root -p
Create a database called vsftpd and a user called vsftpd that will have control over the database. Change somepassword to whatever you like.
mysql> create database vsftpd; mysql> GRANT ALL PRIVILEGES ON vsftpd.* TO 'vsftpd'@'localhost' IDENTIFIED BY 'somepassword'; mysql> GRANT ALL PRIVILEGES ON vsftpd.* TO 'vsftpd'@'localhost.localdomain' IDENTIFIED BY 'somepassword'; mysql> flush privileges;
Create a table called accounts that will hold the FTP users. We set the username to be unique so that we can’t create two users with the same username.
mysql> use vsftpd; mysql> CREATE TABLE accounts (id INT NOT NULL AUTO_INCREMENT PRIMARY KEY, username VARCHAR(20) NOT NULL, password VARCHAR(50) NOT NULL, UNIQUE(username));
Next we’ll add one or several users to the database with the following command:
mysql> INSERT INTO accounts (username,password) VALUES('somevirtualuser', PASSWORD('theuserpassword'));
The MySQL password() function makes sure we store the password as a MD5 hash and not in plain text.
Exit from MySQL by typing:
mysql> quitConfiguring VSFTPD
First we’ll create a system user that will hold all the virtual users home directories and run our FTP server.
root@server:~# useradd -m -s /bin/false vsftpd
You’ll also need to create the FTP home directory of the virtual user(s) you created earlier. There is a option in the VSFTPD config that supposedly should automate this but it doesn’t seem to be working. We also make sure the directory is owned by the system user vsftpd you created in the previous step. Repeat the following for every user you created.
root@server:~# mkdir /home/vsftpd/somevirtualuser;chown vsftpd:vsftpd /home/vsftpd/somevirtualuser
The configuration file is located at /etc/vsftpd.conf and is decently commented, you can back it up by writing:
root@server:~# cp /etc/vsftpd.conf /etc/vsftpd.conf.old
Edit the configuration file /etc/vsftpd.conf. You’ll need to change/uncomment the following:
anonymous_enable=NO local_enable=YES write_enable=YES nopriv_user=vsftpd chroot_local_user=YES local_umask=022
You’ll also need to add the following:
guest_enable=YES guest_username=vsftpd user_sub_token=$USER local_root=/home/vsftpd/$USER virtual_use_local_privs=YES
This might seem counter intuative (for example: why enable_local=YES if we are using virtual users?) so I’ll try to explain briefly.
When we enable local users we normally would enable useraccounts in /etc/passwd to log in to our ftp, however we will in the next section set up VSFTPD to check against our MySQL database instead. We also need to map our virtual users against a existing system user. We do this by classifying all non-anonymous logins as a guest login (by setting guest_enable=YES) and then map all logins to the user specified in guest_username, which in our case is the user vsftpd. We also set our virtual users to have the same rights as the local users (in other words the same umask as we set in local_umask).
Configure VSFTPD to use MySQL for authentication
VSFTPD uses PAM for authentication so we to edit the VSFTPD PAM config file. Back up the file then edit it using your editor of choice.
root@server:~# mv /etc/pam.d/vsftpd /etc/pam.d/vsftpd.old root@server:~# vim /etc/pam.d/vsftpd
Add the following to the new file. Replace “somepassword” with the password you chose for the MySQL user vsftpd when you created the MySQL database earlier. The crypt=2 line checks to makes sure that the password is stored as a MD5 hash. If we accidentally create a user with a plain text password (i.e. we forget to use the password() function in MySQL) they won’t be able to log in.
auth required pam_mysql.so user=vsftpd passwd=somepassword host=localhost db=vsftpd table=accounts usercolumn=username passwdcolumn=password crypt=2
account required pam_mysql.so user=vsftpd passwd=somepassword host=localhost db=vsftpd table=accounts usercolumn=username passwdcolumn=password crypt=2
That’s it. We are now ready to rock and roll. Make sure to restart VSFTPD to reload the changes we made.
root@server:~# /etc/init.d/vsftpd restart
aiq says:
October 12, 2011 at 4:32 am /
i try to follow your tutorial, but it doesn’t work??
can you please, answer on my email?
dazz says:
October 13, 2011 at 9:37 am /
Tried it but i keep getting ‘Login incorrect.’
Alex says:
October 21, 2011 at 6:10 pm /
Thanks for this great tutorial!
And please fix your tutorial, in file /etc/pam.d/vsftpd we have only 2 lines and not 4 as your text was wrapped by parser…
For example:
# cat /etc/pam.d/vsftpd
auth required pam_mysql.so user=vsft…… crypt=2
account required pam_mysql.so user=vsft….. crypt=2
After this everything works as expected!
Regards,
Alex
jstr says:
February 12, 2012 at 2:10 pm /
Thanks for the input guys, I’ve tried to clear the layout out a bit so it’s easier to read.
Pawel says:
February 12, 2012 at 11:06 pm /
Hi,
Below some php script to let you add, remove and/or update users’ passwords to vsftpd database. Any corrections, improvements welcomed. I still don’t know how to implement into it this ‘mkdir /home/vsftpd/somevirtualuser;chown vsftpd:vsftpd /home/vsftpd/somevirtualuser’ piece so it wouldn’t have to be done manually after adding any new user.
User Management — Log In
Enter Password To Access
Username:
Password:
prepare( sprintf(‘DELETE FROM %s WHERE %s = ?’, USER_TABLE, ID_COLUMN) );
if( false !== $query->execute(array($userId)) ) {
echo “Deleted User #{$userId}”;
} else {
echo “Error: Unknwon error Deleting User #{$userId}”;
echo ‘
‘;
}
break;
case ‘Update Password’:
$query = sprintf(‘UPDATE %s SET %s = %s(?,?) WHERE %s = ?’,
USER_TABLE, PASSWORD_COLUMN, ENCRYPTION == ‘ENCRYPT’ ? ‘ENCRYPT’ : ‘AES_ENCRYPT’, ID_COLUMN);
$query = $db->prepare($query);
if( false !== $query->execute(array($password, $userId, ENCRYPTION_SALT)) ) {
echo “Updated password for user #{$userId}”;
} else {
echo “Error: Unknown error updating password for user #{$userId}”;
echo ‘
‘;
}
break;
case ‘Register User’:
if(!$username || !$password) {
echo ‘Error: Invalid username or password’;
} else {
$query = $db->prepare(
sprintf(‘INSERT INTO %s (%s, %s) VALUES ( ?, %s(?,?) )’,
USER_TABLE, USERNAME_COLUMN, PASSWORD_COLUMN, ENCRYPTION == ‘ENCRYPT’ ? ‘ENCRYPT’ : ‘AES_ENCRYPT’)
);
if( false === $query->execute(array($username, $password, ENCRYPTION_SALT)) ) {
$errorInfo = $query->errorInfo();
if( preg_match(“/Duplicate entry \’”.preg_quote($username).”\’/”,$errorInfo[2]) ) {
echo “Error: User ‘{$username}’ already exists.”;
} else {
echo “Error: Unknown error inserting user ‘{$username}’”;
echo ‘
‘;
}
} else {
printf(‘User %s entered into database.’, $username);
}
}
break;
case ‘logout’:
$_SESSION['logged_in'] = false;
break;
}
$users = $db->query(sprintf(‘SELECT %s, %s FROM %s’, ID_COLUMN, USERNAME_COLUMN, USER_TABLE));
$userOptions = “\n”;
while($row = $users->fetch()) {
$userOptions .= sprintf(“\t\t\t%s\n”, $row[0], $row[1]);
}
?>
Manage Users
Add User
Username:
Password:
Edit Password
User:
New Password:
Delete User
User:
Log Out
<?php
echo 'User Management’.ob_get_clean().”;
?>
Michael says:
August 28, 2012 at 6:54 pm /
Thanks very much, great tutorial. Very easy to follow and it works like a charm!
Just in case someone else encounters the same problem (which isn’t really one). Be careful when copy-pasting. When I copied and pasted the block:
guest_enable=YES
guest_username=vsftpd
user_sub_token=$USER
local_root=/home/vsftpd/$USER
virtual_use_local_privs=YES
into my vsftpd.conf I got the following error: “bad bool value in config file for: guest_enable”.
It took me a while to find out, but in the end it was only a space at the end of the line!
Hope this helps others that run into this problem. Anyway, thanks again! Excellent howto.
Mitter Singh Thakur says:
October 18, 2012 at 11:15 am /
Hi, I am using linux mint server :
mitter@mitter-OEM ~ $ ftp -v 127.0.0.1
Connected to 127.0.0.1.
220 (vsFTPd 2.3.0)
Name (127.0.0.1:mitter): test
331 Please specify the password.
Password:
530 Login incorrect.
Login failed.
ftp>
please see tail -f /var/log/auth.log while trying to connect to ftp :
Oct 18 16:42:14 mitter-OEM vsftpd: PAM unable to dlopen(/lib/security/pam_ecryptfs.so): libsmime3.so: failed to map segment from shared object: Cannot allocate memory
Oct 18 16:42:14 mitter-OEM vsftpd: PAM adding faulty module: /lib/security/pam_ecryptfs.so
Oct 18 16:42:14 mitter-OEM vsftpd: PAM unable to dlopen(/lib/security/pam_ck_connector.so): libdbus-1.so.3: failed to map segment from shared object: Cannot allocate memory
Oct 18 16:42:14 mitter-OEM vsftpd: PAM adding faulty module: /lib/security/pam_ck_connector.so
Oct 18 16:42:56 mitter-OEM vsftpd: PAM unable to dlopen(/lib/security/pam_ecryptfs.so): libsmime3.so: failed to map segment from shared object: Cannot allocate memory
Oct 18 16:42:56 mitter-OEM vsftpd: PAM adding faulty module: /lib/security/pam_ecryptfs.so
Oct 18 16:42:56 mitter-OEM vsftpd: PAM unable to dlopen(/lib/security/pam_ck_connector.so): libdbus-1.so.3: failed to map segment from shared object: Cannot allocate memory
Oct 18 16:42:56 mitter-OEM vsftpd: PAM adding faulty module: /lib/security/pam_ck_connector.so