when the session begins. This allows users to be present in central
database (such as nis, kerb or ldap) without using a distributed
file system or pre-creating a large number of directories.
-
+
Here is a sample /etc/pam.d/login file for Debian GNU/Linux
2.1:
-
+
auth requisite pam_securetty.so
auth sufficient pam_ldap.so
- auth required pam_pwdb.so
+ auth required pam_unix.so
auth optional pam_group.so
auth optional pam_mail.so
account requisite pam_time.so
account sufficient pam_ldap.so
- account required pam_pwdb.so
+ account required pam_unix.so
session required pam_mkhomedir.so skel=/etc/skel/ umask=0022
- session required pam_pwdb.so
+ session required pam_unix.so
session optional pam_lastlog.so
- password required pam_pwdb.so
-
+ password required pam_unix.so
+
Released under the GNU LGPL version 2 or later
+ Copyright (c) Red Hat, Inc. 2009
Originally written by Jason Gunthorpe <jgg@debian.org> Feb 1999
- Structure taken from pam_lastlogin by Andrew Morgan
+ Structure taken from pam_lastlogin by Andrew Morgan
<morgan@parc.power.net> 1996
*/
-/* I want snprintf dammit */
-#define _GNU_SOURCE 1
-#include <stdarg.h>
+#include "config.h"
+
#include <sys/types.h>
#include <sys/stat.h>
-#include <fcntl.h>
+#include <sys/time.h>
+#include <sys/resource.h>
+#include <sys/wait.h>
#include <unistd.h>
#include <pwd.h>
+#include <errno.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
-#include <dirent.h>
+#include <syslog.h>
+#include <signal.h>
/*
* here, we make a definition for the externally accessible function
#include <security/pam_modules.h>
#include <security/_pam_macros.h>
+#include <security/pam_modutil.h>
+#include <security/pam_ext.h>
/* argument parsing */
-#define MKHOMEDIR_DEBUG 020 /* keep quiet about things */
+#define MKHOMEDIR_DEBUG 020 /* be verbose about things */
#define MKHOMEDIR_QUIET 040 /* keep quiet about things */
-static unsigned int UMask = 0022;
-static char SkelDir[BUFSIZ] = "/etc/skel";
-
-/* some syslogging */
-static void _log_err(int err, const char *format, ...)
-{
- va_list args;
-
- va_start(args, format);
- openlog("PAM-mkhomedir", LOG_CONS|LOG_PID, LOG_AUTH);
- vsyslog(err, format, args);
- va_end(args);
- closelog();
-}
+struct options_t {
+ int ctrl;
+ const char *umask;
+ const char *skeldir;
+};
+typedef struct options_t options_t;
-static int _pam_parse(int flags, int argc, const char **argv)
+static void
+_pam_parse (const pam_handle_t *pamh, int flags, int argc, const char **argv,
+ options_t *opt)
{
- int ctrl = 0;
+ opt->ctrl = 0;
+ opt->umask = "0022";
+ opt->skeldir = "/etc/skel";
/* does the appliction require quiet? */
if ((flags & PAM_SILENT) == PAM_SILENT)
- ctrl |= MKHOMEDIR_QUIET;
+ opt->ctrl |= MKHOMEDIR_QUIET;
/* step through arguments */
for (; argc-- > 0; ++argv)
{
- if (!strcmp(*argv, "silent"))
- {
- ctrl |= MKHOMEDIR_QUIET;
- }
- else if (!strncmp(*argv,"umask=",6))
- UMask = strtol(*argv+6,0,0);
- else if (!strncmp(*argv,"skel=",5))
- strcpy(SkelDir,*argv+5);
- else
- {
- _log_err(LOG_ERR, "unknown option; %s", *argv);
+ if (!strcmp(*argv, "silent")) {
+ opt->ctrl |= MKHOMEDIR_QUIET;
+ } else if (!strcmp(*argv, "debug")) {
+ opt->ctrl |= MKHOMEDIR_DEBUG;
+ } else if (!strncmp(*argv,"umask=",6)) {
+ opt->umask = *argv+6;
+ } else if (!strncmp(*argv,"skel=",5)) {
+ opt->skeldir = *argv+5;
+ } else {
+ pam_syslog(pamh, LOG_ERR, "unknown option: %s", *argv);
}
}
-
- D(("ctrl = %o", ctrl));
- return ctrl;
}
-/* This common function is used to send a message to the applications
- conversion function. Our only use is to ask the application to print
- an informative message that we are creating a home directory */
-static int converse(pam_handle_t * pamh, int ctrl, int nargs
- ,struct pam_message **message
- ,struct pam_response **response)
+/* Do the actual work of creating a home dir */
+static int
+create_homedir (pam_handle_t *pamh, options_t *opt,
+ const char *user, const char *dir)
{
- int retval;
- struct pam_conv *conv;
+ int retval, child;
+ struct sigaction newsa, oldsa;
- D(("begin to converse"));
-
- retval = pam_get_item(pamh, PAM_CONV, (const void **) &conv);
- if (retval == PAM_SUCCESS)
- {
+ /* Mention what is happening, if the notification fails that is OK */
+ if (!(opt->ctrl & MKHOMEDIR_QUIET))
+ pam_info(pamh, _("Creating directory '%s'."), dir);
- retval = conv->conv(nargs, (const struct pam_message **) message
- ,response, conv->appdata_ptr);
- D(("returned from application's conversation function"));
+ D(("called."));
- if (retval != PAM_SUCCESS && (ctrl & MKHOMEDIR_DEBUG))
- {
- _log_err(LOG_DEBUG, "conversation failure [%s]"
- ,pam_strerror(pamh, retval));
- }
+ /*
+ * This code arranges that the demise of the child does not cause
+ * the application to receive a signal it is not expecting - which
+ * may kill the application or worse.
+ */
+ memset(&newsa, '\0', sizeof(newsa));
+ newsa.sa_handler = SIG_DFL;
+ sigaction(SIGCHLD, &newsa, &oldsa);
+ if (opt->ctrl & MKHOMEDIR_DEBUG) {
+ pam_syslog(pamh, LOG_DEBUG, "Executing mkhomedir_helper.");
}
- else
- {
- _log_err(LOG_ERR, "couldn't obtain coversation function [%s]"
- ,pam_strerror(pamh, retval));
- }
-
- D(("ready to return from module conversation"));
- return retval; /* propagate error status */
-}
-
-/* Ask the application to display a short text string for us. */
-static int make_remark(pam_handle_t * pamh, int ctrl, const char *remark)
-{
- int retval;
-
- if ((ctrl & MKHOMEDIR_QUIET) != MKHOMEDIR_QUIET)
- {
- struct pam_message msg[1], *mesg[1];
- struct pam_response *resp = NULL;
-
- mesg[0] = &msg[0];
- msg[0].msg_style = PAM_TEXT_INFO;
- msg[0].msg = remark;
-
- retval = converse(pamh, ctrl, 1, mesg, &resp);
-
- msg[0].msg = NULL;
- if (resp)
- {
- _pam_drop_reply(resp, 1);
- }
- }
- else
- {
- D(("keeping quiet"));
- retval = PAM_SUCCESS;
+ /* fork */
+ child = fork();
+ if (child == 0) {
+ static char *envp[] = { NULL };
+ const char *args[] = { NULL, NULL, NULL, NULL, NULL };
+
+ if (pam_modutil_sanitize_helper_fds(pamh, PAM_MODUTIL_PIPE_FD,
+ PAM_MODUTIL_PIPE_FD,
+ PAM_MODUTIL_PIPE_FD) < 0)
+ _exit(PAM_SYSTEM_ERR);
+
+ /* exec the mkhomedir helper */
+ args[0] = MKHOMEDIR_HELPER;
+ args[1] = user;
+ args[2] = opt->umask;
+ args[3] = opt->skeldir;
+
+ execve(MKHOMEDIR_HELPER, (char *const *) args, envp);
+
+ /* should not get here: exit with error */
+ D(("helper binary is not available"));
+ _exit(PAM_SYSTEM_ERR);
+ } else if (child > 0) {
+ int rc;
+ while ((rc=waitpid(child, &retval, 0)) < 0 && errno == EINTR);
+ if (rc < 0) {
+ pam_syslog(pamh, LOG_ERR, "waitpid failed: %m");
+ retval = PAM_SYSTEM_ERR;
+ } else if (!WIFEXITED(retval)) {
+ pam_syslog(pamh, LOG_ERR, "mkhomedir_helper abnormal exit: %d", retval);
+ retval = PAM_SYSTEM_ERR;
+ } else {
+ retval = WEXITSTATUS(retval);
+ }
+ } else {
+ D(("fork failed"));
+ pam_syslog(pamh, LOG_ERR, "fork failed: %m");
+ retval = PAM_SYSTEM_ERR;
}
- D(("returning %s", pam_strerror(pamh, retval)));
- return retval;
-}
+ sigaction(SIGCHLD, &oldsa, NULL); /* restore old signal handler */
-/* Do the actual work of creating a home dir */
-static int create_homedir(pam_handle_t * pamh, int ctrl,
- const struct passwd *pwd)
-{
- char *remark;
- DIR *D;
- struct dirent *Dir;
-
- /* Some scratch space */
- remark = malloc(BUFSIZ);
- if (remark == NULL)
- {
- D(("no memory for last login remark"));
- return PAM_BUF_ERR;
+ if (opt->ctrl & MKHOMEDIR_DEBUG) {
+ pam_syslog(pamh, LOG_DEBUG, "mkhomedir_helper returned %d", retval);
}
- /* Mention what is happening, if the notification fails that is OK */
- if (snprintf(remark,BUFSIZ,"Creating home directory '%s'.",
- pwd->pw_dir) == -1)
- return PAM_PERM_DENIED;
-
- make_remark(pamh, ctrl, remark);
-
- /* Crete the home directory */
- if (mkdir(pwd->pw_dir,0700) != 0)
- {
- free(remark);
- _log_err(LOG_DEBUG, "unable to create home directory %s",pwd->pw_dir);
- return PAM_PERM_DENIED;
- }
- if (chmod(pwd->pw_dir,0777 & (~UMask)) != 0 ||
- chown(pwd->pw_dir,pwd->pw_uid,pwd->pw_gid) != 0)
- {
- free(remark);
- _log_err(LOG_DEBUG, "unable to chance perms on home directory %s",pwd->pw_dir);
- return PAM_PERM_DENIED;
- }
-
- /* See if we need to copy the skel dir over. */
- if (SkelDir[0] == 0)
- {
- free(remark);
- return PAM_SUCCESS;
- }
-
- /* Scan the directory */
- D = opendir(SkelDir);
- if (D == 0)
- {
- free(remark);
- _log_err(LOG_DEBUG, "unable to read directory %s",SkelDir);
- return PAM_PERM_DENIED;
+ if (retval != PAM_SUCCESS && !(opt->ctrl & MKHOMEDIR_QUIET)) {
+ pam_error(pamh, _("Unable to create and initialize directory '%s'."),
+ dir);
}
-
- for (Dir = readdir(D); Dir != 0; Dir = readdir(D))
- {
- int SrcFd;
- int DestFd;
- int Res;
- struct stat St;
-
- /* Skip some files.. */
- if (strcmp(Dir->d_name,".") == 0 ||
- strcmp(Dir->d_name,"..") == 0)
- continue;
-
- /* Check if it is a directory */
- snprintf(remark,BUFSIZ,"%s/%s",SkelDir,Dir->d_name);
- if (stat(remark,&St) != 0)
- continue;
- if (S_ISDIR(St.st_mode))
- {
- snprintf(remark,BUFSIZ,"%s/%s",pwd->pw_dir,Dir->d_name);
- if (mkdir(remark,(St.st_mode | 0222) & (~UMask)) != 0 ||
- chmod(remark,(St.st_mode | 0222) & (~UMask)) != 0 ||
- chown(remark,pwd->pw_uid,pwd->pw_gid) != 0)
- {
- free(remark);
- _log_err(LOG_DEBUG, "unable to change perms on copy %s",remark);
- return PAM_PERM_DENIED;
- }
- continue;
- }
-
- /* Open the source file */
- if ((SrcFd = open(remark,O_RDONLY)) < 0 || fstat(SrcFd,&St) != 0)
- {
- free(remark);
- _log_err(LOG_DEBUG, "unable to open src file %s",remark);
- return PAM_PERM_DENIED;
- }
- stat(remark,&St);
-
- /* Open the dest file */
- snprintf(remark,BUFSIZ,"%s/%s",pwd->pw_dir,Dir->d_name);
- if ((DestFd = open(remark,O_WRONLY | O_TRUNC | O_CREAT,0600)) < 0)
- {
- close(SrcFd);
- free(remark);
- _log_err(LOG_DEBUG, "unable to open dest file %s",remark);
- return PAM_PERM_DENIED;
- }
- /* Set the proper ownership and permissions for the module. We make
- the file a+w and then mask it with the set mask. This preseves
- execute bits */
- if (fchmod(DestFd,(St.st_mode | 0222) & (~UMask)) != 0 ||
- fchown(DestFd,pwd->pw_uid,pwd->pw_gid) != 0)
- {
- free(remark);
- _log_err(LOG_DEBUG, "unable to chang perms on copy %s",remark);
- return PAM_PERM_DENIED;
- }
-
- /* Copy the file */
- do
- {
- Res = read(SrcFd,remark,BUFSIZ);
- if (Res < 0 || write(DestFd,remark,Res) != Res)
- {
- close(SrcFd);
- close(DestFd);
- free(remark);
- _log_err(LOG_DEBUG, "unable to perform IO");
- return PAM_PERM_DENIED;
- }
- }
- while (Res != 0);
- close(SrcFd);
- close(DestFd);
- }
-
- free(remark);
- return PAM_SUCCESS;
+ D(("returning %d", retval));
+ return retval;
}
/* --- authentication management functions (only) --- */
-PAM_EXTERN
-int pam_sm_open_session(pam_handle_t * pamh, int flags, int argc
- ,const char **argv)
+PAM_EXTERN int
+pam_sm_open_session (pam_handle_t *pamh, int flags, int argc,
+ const char **argv)
{
- int retval, ctrl;
- const char *user;
+ int retval;
+ options_t opt;
+ const void *user;
const struct passwd *pwd;
struct stat St;
-
+
/* Parse the flag values */
- ctrl = _pam_parse(flags, argc, argv);
+ _pam_parse(pamh, flags, argc, argv, &opt);
/* Determine the user name so we can get the home directory */
- retval = pam_get_item(pamh, PAM_USER, (const void **) &user);
- if (retval != PAM_SUCCESS || user == NULL || *user == '\0')
+ retval = pam_get_item(pamh, PAM_USER, &user);
+ if (retval != PAM_SUCCESS || user == NULL || *(const char *)user == '\0')
{
- _log_err(LOG_NOTICE, "user unknown");
+ pam_syslog(pamh, LOG_NOTICE, "Cannot obtain the user name.");
return PAM_USER_UNKNOWN;
}
/* Get the password entry */
- pwd = getpwnam(user);
+ pwd = pam_modutil_getpwnam (pamh, user);
if (pwd == NULL)
{
+ pam_syslog(pamh, LOG_NOTICE, "User unknown.");
D(("couldn't identify user %s", user));
return PAM_CRED_INSUFFICIENT;
}
/* Stat the home directory, if something exists then we assume it is
correct and return a success*/
- if (stat(pwd->pw_dir,&St) == 0)
+ if (stat(pwd->pw_dir, &St) == 0) {
+ if (opt.ctrl & MKHOMEDIR_DEBUG) {
+ pam_syslog(pamh, LOG_DEBUG, "Home directory %s already exists.",
+ pwd->pw_dir);
+ }
return PAM_SUCCESS;
+ }
- return create_homedir(pamh,ctrl,pwd);
+ return create_homedir(pamh, &opt, user, pwd->pw_dir);
}
/* Ignore */
-PAM_EXTERN
-int pam_sm_close_session(pam_handle_t * pamh, int flags, int argc
- ,const char **argv)
+PAM_EXTERN
+int pam_sm_close_session (pam_handle_t * pamh UNUSED, int flags UNUSED,
+ int argc UNUSED, const char **argv UNUSED)
{
return PAM_SUCCESS;
}