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> quit

Configuring 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

Post filed under Linux, Server Administration.

11 Comments

  1. i try to follow your tutorial, but it doesn’t work??
    can you please, answer on my email?

  2. Tried it but i keep getting ‘Login incorrect.’

  3. 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

  4. Thanks for the input guys, I’ve tried to clear the layout out a bit so it’s easier to read.

  5. 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 ‘

    '.print_r($query->errorInfo(), true).'

    ‘;
    }
    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 ‘

    '.print_r($query->errorInfo(), true).'

    ‘;
    }
    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 ‘

    '.print_r($query->errorInfo(), true).'

    ‘;
    }
    } 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().”;
    ?>

  6. 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.

  7. Mitter Singh Thakur says:

    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

  8. Hi All,

    I want to auto create directory “/home/vsftpd/$USER “,

    How It would be possible?

    Please help me!

  9. So what is dancing in a rueda, because this custom of dancing in
    a rueda, because this custom of dancing in a rueda?
    Here you’ll see there are lots of games to choose from.
    Deuces Wild follows the same rules as regular roulette, only that it allows players to
    split any card he wants up to four times and double on splits.

Leave a Reply

Your email address will not be published. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre lang="" line="" escaped="" highlight="">