设置:
- 亚马逊 Linux EC2
- vsftpd
- 使用 pam_userdb.so 进行 PAM 身份验证
- 从外部来源(lsyncd)写入 Berkeley 用户数据库的用户名/密码。
有成千上万的虚拟用户,到目前为止,我已经在 /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
我想避免手动预先创建目录,以便 Berkeley DB 的新条目能够自动工作而无需更改每个节点。
搜索使用以下方法进行 LDAP 和 MySQL 身份验证会产生类似的问题:
- pam_mkhomedir 文件
- /etc/nsswitch.conf
但我似乎无法将所有这些结合起来来解决 Berkeley DB 的这个问题。
答案1
我最终为 vsftpd 虚拟用户实现了一个 PAM 模块,大致基于 pam_mkhomedir.so。我确信它可以改进,但下面是一个工作版本。
用法:
pam_mkhomedir_vsftpd_virt.so [debug] vsftpd_user=<vsftpd_user> basedir=<basedir>
- vsftpd_user-通常是 vsftpd
- basedir - 通常是 /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/
- 我将 pam_userdb.so 的身份验证和帐户更改为“requisite”,以避免在 userdb 身份验证未通过时创建主目录。
- 我实现了该模块在帐户级别运行,因为在我的 vsftpd 上下文中不使用会话。
汇编:
gcc -fPIC -c pam_mkhomedir_vsftpd_virt.c
gcc -shared -o pam_mkhomedir_vsftpd_virt.so pam_mkhomedir_vsftpd_virt.o -lpam
- 与其他 PAM 模块一起安装 pam_mkhomedir_vsftpd_virt.so。
代码:
#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;
}