2

Setup:

  • Amazon Linux EC2
  • vsftpd
  • PAM authentication with pam_userdb.so
  • Usernames/passwords written to Berkeley user db from an external source (lsyncd).

There are many 1000s of virtual users, to date I have manually pre-created home directories for them all under /home/vsftpd

drwx------    2 vsftpd users     4096 Apr 11 15:28 user0123
drwx------    2 vsftpd users     4096 Apr 11 15:28 user0124
...

#%PAM-1.0
auth required pam_userdb.so db=/usr/local/vsftpd_auth/vsftpd_userdb crypt=none
account required pam_userdb.so db=/usr/local/vsftpd_auth/vsftpd_userdb

I want to avoid manually pre-creating the directories so that new entries to the Berkeley DB will work automatically without changing each node.

Searching yields similar questions for LDAP and MySQL authentication using:

  • pam_mkhomedir.so
  • /etc/nsswitch.conf

But I can't seem to put it all together to solve this problem for Berkeley DB.

DanielB6
  • 121
  • 5
  • what kind of problem you have using pam_mkhomedir.so? any error that you can show us? – c4f4t0r May 09 '18 at 13:31
  • Based on other posts, my understanding is that pam_mkhomedir.so relies on /etc/nsswitch.conf to populate a data structure with home directory and permission values. In tern, /etc/nsswitch.conf gets the values typically from information in the passwd file. Since my vsftpd users are virtual, I don't know how to setup nsswitch.conf for the user db case. – DanielB6 May 09 '18 at 16:27
  • https://serverfault.com/questions/320572/vsftpd-pam-mkhomedir/431045#431045 – c4f4t0r May 09 '18 at 22:21
  • I don't think that's related to my case - or at least I don't understand how to make it work with vsftpd/pam_userdb.so and virtual users. – DanielB6 May 10 '18 at 14:53

1 Answers1

0

I ended up implementing a PAM module for vsftpd virtual users, based loosely on pam_mkhomedir.so. I'm sure it can be improved, but below is a working version.

Usage:

pam_mkhomedir_vsftpd_virt.so [debug] vsftpd_user=<vsftpd_user> basedir=<basedir> 
  • vsftpd_user - typically vsftpd
  • basedir - typically /home/vsftpd/

/etc/pam.d/vsftpd:

#%PAM-1.0
auth requisite pam_userdb.so db=/path/to/userdb crypt=none
account requisite pam_userdb.so db=/path/to/userdb
account required pam_mkhomedir_vsftpd_virt.so debug vsftpd_user=vsftpd basedir=/home/vsftpd/
  • I changed auth and account for pam_userdb.so to 'requisite' to avoid creating the home dir if userdb authentication doesn't pass.
  • I implemented the module to act at the account level because sessions aren't used in my vsftpd context.

Compilation:

gcc -fPIC -c pam_mkhomedir_vsftpd_virt.c
gcc -shared -o pam_mkhomedir_vsftpd_virt.so pam_mkhomedir_vsftpd_virt.o -lpam
  • Install pam_mkhomedir_vsftpd_virt.so with the other PAM modules.

Code:

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <pwd.h>
#include <unistd.h>
#include <syslog.h>

/* For now we will use the service function for account management
 */
#define PAM_SM_ACCOUNT
#include <security/pam_modules.h>

#define MAX_HOMEDIR_SIZE 100

typedef struct {
    bool debug;
    const char *vsftpd_user;
    const char *basedir;
    const char *user;
    char homedir[MAX_HOMEDIR_SIZE+1];
} options_t;

static int parse_input(pam_handle_t *pamh, int flags, int argc, const char **argv, options_t *options) {
    int rc;
    int basedir_len;
    int total_len;
    bool add_slash = false;
    int i;

    /* Retrieve the user name
     */
    rc = pam_get_item(pamh, PAM_USER, (void *)&options->user);

    if (rc != PAM_SUCCESS || options->user == NULL || *(options->user) == '\0') {
        pam_syslog(pamh, LOG_ERR, "cannot retrieve the user name");
        return PAM_USER_UNKNOWN;
    }

    /* Retrieve the module parms
     */
    for (i = 0 ; i < argc; *argv++, i++) {
        if (strcmp(*argv, "debug") == 0) {
            options->debug = true;
        }
        else if (strncmp(*argv, "vsftpd_user=", 12) == 0) {
            options->vsftpd_user = *argv+12;
        }
        else if (strncmp(*argv, "basedir=", 8) == 0) {
            options->basedir = *argv+8;
        }
        else {
            pam_syslog(pamh, LOG_ERR, "unknown option '%s'", *argv);
        }
    }

    /* Validate input
     */
    if (options->vsftpd_user == NULL || *(options->vsftpd_user) == '\0') {
        pam_syslog(pamh, LOG_ERR, "cannot retrieve the vsftpd user");
        return PAM_NO_MODULE_DATA;
    }

    if (options->basedir == NULL || *(options->basedir) == '\0') {
        pam_syslog(pamh, LOG_ERR, "cannot retrieve the base dir");
        return PAM_NO_MODULE_DATA;
    }

    if (options->basedir[0] != '/') {
        pam_syslog(pamh, LOG_ERR, "base dir must start with '/'");
        return PAM_NO_MODULE_DATA;
    }

    /* Check whether we need to add a slash to the path
     */
    basedir_len = (int) strlen(options->basedir);

    if (options->basedir[basedir_len-1] != '/')
        add_slash = true;

    /* Verify we haven't exceeded the max dir length
     */
    total_len = basedir_len + (int) strlen(options->user) + (add_slash?1:0);

    if (total_len > MAX_HOMEDIR_SIZE) {
        pam_syslog(pamh, LOG_ERR, "home directory max length of %d exceeded '%d'", MAX_HOMEDIR_SIZE, total_len);
        return PAM_BUF_ERR;
    }

    /* Create the homedir string
     */
    snprintf(options->homedir, MAX_HOMEDIR_SIZE+1, "%s%s%s", options->basedir, add_slash?"/":"", options->user);

    /* Finished parsing input, log what we got...
     */
    if (options->debug) {
        pam_syslog(pamh, LOG_DEBUG, "vsftpd user '%s'", options->vsftpd_user);
        pam_syslog(pamh, LOG_DEBUG, "base directory '%s'", options->basedir);
        pam_syslog(pamh, LOG_DEBUG, "user '%s'", options->user);
        pam_syslog(pamh, LOG_DEBUG, "home directory '%s'", options->homedir);
    }

    return PAM_SUCCESS;
}

static int create_homedir(pam_handle_t *pamh, options_t *options) {
    struct stat status;
    struct passwd *pwd;
    const char *vsftpd_user = options->vsftpd_user;
    char *homedir = options->homedir;

    /* Retrieve passwd data for the vsftpd user
     */
    pwd = getpwnam(vsftpd_user);

    if (pwd == NULL) {
        pam_syslog(pamh, LOG_ERR, "unable to get user creds for '%s'", vsftpd_user);
        return PAM_CRED_INSUFFICIENT;
    }

    /* Check if home directory already exists
     */
    if (stat(homedir, &status) == 0) {
        if (options->debug)
            pam_syslog(pamh, LOG_DEBUG, "home directory '%s' already exists", homedir);
        return PAM_SUCCESS;
    }

    /* Home directory doesn't exist, create it
     */
    if (options->debug)
        pam_syslog(pamh, LOG_DEBUG, "creating home directory '%s'", homedir);

    if (mkdir(homedir, 0700) != 0) {
        pam_syslog(pamh, LOG_ERR, "unable to create home directory '%s'", homedir);
        return PAM_PERM_DENIED;
    }

    if (chmod(homedir, 0700) != 0 || chown(homedir, pwd->pw_uid, pwd->pw_gid) != 0) {
        pam_syslog(pamh, LOG_ERR, "unable to change perms on directory '%s'", homedir);
        return PAM_PERM_DENIED;
    }

    return PAM_SUCCESS;
}

/* PAM Account Management function
 */
PAM_EXTERN int pam_sm_acct_mgmt(pam_handle_t *pamh, int flags, int argc, const char **argv) {
    options_t options;
    int rc;

    memset(&options, 0, sizeof(options_t));

    rc = parse_input(pamh, flags, argc, argv, &options);

    if (rc != PAM_SUCCESS) {
        return rc;
    }

    rc = create_homedir(pamh, &options);

    if (rc != PAM_SUCCESS) {
        return rc;
    }

    return PAM_SUCCESS;
}
DanielB6
  • 121
  • 5