2

I've installed vsftpd using the following instructions

http://www.camcloud.com/blog/setup-vsftpd-with-virtual-users-using-mysql

I'm trying to automatically/dynamically create directories for each user as they log into the ftp server.

I have the following /etc/pam.d/vsftpd which does not automatically create the directory.

Static Directory Configuration Code

session    optional     pam_keyinit.so    force revoke 
auth       required     pam_mysql.so      verbose=1 user=mydbusername passwd=mydbpass host=localhost db=mydb table=mydb_table usercolumn=username passwdcolumn=password crypt=0
account    required     pam_mysql.so      verbose=1 user=mydbusername passwd=mydbpass host=localhost db=mydb table=mydb_table usercolumn=username passwdcolumn=password crypt=0

My question is how to properly use pam_mkhomedir to create the directory with the username from mysql. I found this documentation https://www.dalemacartney.com/2012/07/30/auto-creation-of-user-home-directories-in-linux/

I'm wondering if this is the proper configuration, I'd also like to know how he username is set for the directory. I do not see anywhere's in the code where username is applied, or is that something that just happens behind the scenes?

Dynamic Directory Configuration Code

session    optional     pam_keyinit.so    force revoke 
auth       required     pam_mysql.so      verbose=1 user=mydbusername passwd=mydbpass host=localhost db=mydb table=mydb_table usercolumn=username passwdcolumn=password crypt=0
account    required     pam_mysql.so      verbose=1 user=mydbusername passwd=mydbpass host=localhost db=mydb table=mydb_table usercolumn=username passwdcolumn=password crypt=0
session    required     pam_mkhomedir.so  skel=/etc/ftp/ umask=0022
3
session    required     pam_mkhomedir.so  skel=/etc/ftp/ umask=0022

As you can see, pam_mkhomedir.so acts at the session level. This means that it acts after the user has been authenticated (by the auth facility). To do so, it defines the following PAM-specific function :

PAM_EXTERN
int pam_sm_open_session(pam_handle_t * pamh, int flags, 
                        int argc, const char **argv);

As I said, this function is called when the user has been authenticated. Therefore, when it retrieves the PAM user handle...

/* Determine the user name so we can get the home directory */
retval = pam_get_item(pamh, PAM_USER, (const void **)&user);

... it gets access to the name of an authenticated, existing user. With this information, the module will be able to retrieve what it needs (the path to a home directory) through a simple getpwnam(3) library call:

/* Get the password entry */
pwd = getpwnam(user);

This will return the following structure, filled with information about the user:

struct passwd {
    char   *pw_name;       /* username */
    char   *pw_passwd;     /* user password */
    uid_t   pw_uid;        /* user ID */
    gid_t   pw_gid;        /* group ID */
    char   *pw_gecos;      /* user information */
    char   *pw_dir;        /* home directory */
    char   *pw_shell;      /* shell program */
};

Now, if you wonder where this information is coming from, have a look at /etc/nsswitch.conf. The Name Service Switch (NSS) is here to provide information regarding names in general (which can be usernames, aliases, groups, hosts, ... see nsswitch.conf(5) for more details). If the guide you followed is complete enough, it should have had you configure both PAM and NSS: the first handles authentication and account/session management, while the other is merely here to provide name information. In other words, the first one can perform read/write operations, while the second one is read-only.

Once pam_mkhomedir.so has retrieved the path to the user's home directory, all it has to do is check whether or not it exists, and create it if it does not:

/* Stat the home directory, if something exists then we 
 * assume it is correct and return a success. */
if(stat(pwd->pw_dir, &st) == 0) return PAM_SUCCESS;

return create_homedir(..., pwd, ...);

The create_homedir function is defined by the module itself, and it is a bit too long for me to paste it here. Just see it as a big mkdir(2) call, which handles specific cases and makes sure everything goes smoothly. If you're interested in more details, have a look at pam_mkhomedir.so's source code. I used this version of it for my answer (it was written by Jason Gunthorpe, whose name is also on my pam_mkhomedir.so manpage).

Now, as far as I'm concerned, I think the guide you followed is a little incomplete, or at least, the MySQL table it has you create is:

+----------+-------------+------+-----+---------+----------------+
| Field    | Type        | Null | Key | Default | Extra          |
+----------+-------------+------+-----+---------+----------------+
| id       | int(11)     | NO   | PRI | NULL    | auto_increment |
| username | varchar(30) | NO   | UNI | NULL    |                |
| pass     | varchar(50) | NO   |     | NULL    |                |
+----------+-------------+------+-----+---------+----------------+

This table is probably detailed enough for VSFTPd to work. Since it is configured with a home root, it can easily determine a user's home directory: $ftp_root/username. This is actually set with the local_root parameter:

local_root=/home/vsftpd/$USER

However, pam_mkhomedir.so is not VSFTPd. It doesn't use this mechanism, but relies on NSS (through getpwnam(3)). The problem is: your MySQL table does not provide enough information for NSS to fill the struct passwd we saw earlier. In order to have NSS use MySQL as a source, you'll need to install nss-mysql, which requires a larger MySQL table. This will require adjustments in your VSFTPd configuration, but you should have no problem in having VSFTPd and nss-mysql cohabitate.

This directory gives an example for a minimal nss-mysql setup. The MySQL table for users is defined as follows:

+-----------------------+--------------+------+-----+---------+----------------+
| Field                 | Type         | Null | Key | Default | Extra          |
+-----------------------+--------------+------+-----+---------+----------------+
| id                    | int(11)      | NO   | PRI | NULL    | auto_increment |
| username              | varchar(255) | NO   |     | NULL    |                |
| username_canonical    | varchar(255) | NO   | UNI | NULL    |                |
| email                 | varchar(255) | NO   |     | NULL    |                |
| email_canonical       | varchar(255) | NO   | UNI | NULL    |                |
| enabled               | tinyint(1)   | NO   |     | NULL    |                |
| salt                  | varchar(255) | NO   |     | NULL    |                |
| password              | varchar(255) | NO   |     | NULL    |                |
| last_login            | datetime     | YES  |     | NULL    |                |
| locked                | tinyint(1)   | NO   |     | NULL    |                |
| expired               | tinyint(1)   | NO   |     | NULL    |                |
| expires_at            | datetime     | YES  |     | NULL    |                |
| confirmation_token    | varchar(255) | YES  |     | NULL    |                |
| password_requested_at | datetime     | YES  |     | NULL    |                |
| roles                 | longtext     | NO   |     | NULL    |                |
| credentials_expired   | tinyint(1)   | NO   |     | NULL    |                |
| credentials_expire_at | datetime     | YES  |     | NULL    |                |
+-----------------------+--------------+------+-----+---------+----------------+

Now, you might not need all these fields yourself, but keep in mind that struct passwd structure we saw earlier, and make sure nss-mysql has enough data to fill it :) Besides, the nss-mysql configuration files are flexible enough for you to use default values for anything.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.