summaryrefslogtreecommitdiff
path: root/modules/pam_mkhomedir/pam_mkhomedir.c
diff options
context:
space:
mode:
Diffstat (limited to 'modules/pam_mkhomedir/pam_mkhomedir.c')
-rw-r--r--modules/pam_mkhomedir/pam_mkhomedir.c511
1 files changed, 511 insertions, 0 deletions
diff --git a/modules/pam_mkhomedir/pam_mkhomedir.c b/modules/pam_mkhomedir/pam_mkhomedir.c
new file mode 100644
index 00000000..44b092c1
--- /dev/null
+++ b/modules/pam_mkhomedir/pam_mkhomedir.c
@@ -0,0 +1,511 @@
+/* PAM Make Home Dir module
+
+ This module will create a users home directory if it does not exist
+ 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_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_unix.so
+ session required pam_mkhomedir.so skel=/etc/skel/ umask=0022
+ session required pam_unix.so
+ session optional pam_lastlog.so
+ password required pam_unix.so
+
+ Released under the GNU LGPL version 2 or later
+ Originally written by Jason Gunthorpe <jgg@debian.org> Feb 1999
+ Structure taken from pam_lastlogin by Andrew Morgan
+ <morgan@parc.power.net> 1996
+ */
+
+#include "config.h"
+
+#include <stdarg.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.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>
+
+/*
+ * here, we make a definition for the externally accessible function
+ * in this file (this definition is required for static a module
+ * but strongly encouraged generally) it is used to instruct the
+ * modules include file to define the function prototypes.
+ */
+
+#define PAM_SM_SESSION
+
+#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 /* be verbose about things */
+#define MKHOMEDIR_QUIET 040 /* keep quiet about things */
+
+static unsigned int UMask = 0022;
+static char SkelDir[BUFSIZ] = "/etc/skel"; /* THIS MODULE IS NOT THREAD SAFE */
+
+static int
+_pam_parse (const pam_handle_t *pamh, int flags, int argc, const char **argv)
+{
+ int ctrl = 0;
+
+ /* does the appliction require quiet? */
+ if ((flags & PAM_SILENT) == PAM_SILENT)
+ ctrl |= MKHOMEDIR_QUIET;
+
+ /* step through arguments */
+ for (; argc-- > 0; ++argv)
+ {
+ if (!strcmp(*argv, "silent")) {
+ ctrl |= MKHOMEDIR_QUIET;
+ } else if (!strcmp(*argv, "debug")) {
+ ctrl |= MKHOMEDIR_DEBUG;
+ } else if (!strncmp(*argv,"umask=",6)) {
+ UMask = strtol(*argv+6,0,0);
+ } else if (!strncmp(*argv,"skel=",5)) {
+ strncpy(SkelDir,*argv+5,sizeof(SkelDir));
+ SkelDir[sizeof(SkelDir)-1] = '\0';
+ } else {
+ pam_syslog(pamh, LOG_ERR, "unknown option: %s", *argv);
+ }
+ }
+
+ D(("ctrl = %o", ctrl));
+ return ctrl;
+}
+
+static int
+rec_mkdir (const char *dir, mode_t mode)
+{
+ char *cp;
+ char *parent = strdup (dir);
+
+ if (parent == NULL)
+ return 1;
+
+ cp = strrchr (parent, '/');
+
+ if (cp != NULL && cp != parent)
+ {
+ struct stat st;
+
+ *cp++ = '\0';
+ if (stat (parent, &st) == -1 && errno == ENOENT)
+ if (rec_mkdir (parent, mode) != 0)
+ {
+ free (parent);
+ return 1;
+ }
+ }
+
+ free (parent);
+
+ if (mkdir (dir, mode) != 0 && errno != EEXIST)
+ return 1;
+
+ return 0;
+}
+
+/* Do the actual work of creating a home dir */
+static int
+create_homedir (pam_handle_t * pamh, int ctrl,
+ const struct passwd *pwd,
+ const char *source, const char *dest)
+{
+ char remark[BUFSIZ];
+ DIR *D;
+ struct dirent *Dir;
+ int retval = PAM_AUTH_ERR;
+
+ /* Mention what is happening, if the notification fails that is OK */
+ if ((ctrl & MKHOMEDIR_QUIET) != MKHOMEDIR_QUIET)
+ pam_info(pamh, _("Creating directory '%s'."), dest);
+
+ /* Create the new directory */
+ if (rec_mkdir (dest,0755) != 0)
+ {
+ pam_error(pamh, _("Unable to create directory %s: %m"), dest);
+ pam_syslog(pamh, LOG_ERR, "unable to create directory %s: %m", dest);
+ return PAM_PERM_DENIED;
+ }
+
+ /* See if we need to copy the skel dir over. */
+ if ((source == NULL) || (strlen(source) == 0))
+ {
+ retval = PAM_SUCCESS;
+ goto go_out;
+ }
+
+ /* Scan the directory */
+ D = opendir (source);
+ if (D == 0)
+ {
+ pam_syslog(pamh, LOG_DEBUG, "unable to read directory %s: %m", source);
+ retval = PAM_PERM_DENIED;
+ goto go_out;
+ }
+
+ for (Dir = readdir(D); Dir != 0; Dir = readdir(D))
+ {
+ int SrcFd;
+ int DestFd;
+ int Res;
+ struct stat St;
+#ifndef PATH_MAX
+ char *newsource = NULL, *newdest = NULL;
+ /* track length of buffers */
+ int nslen = 0, ndlen = 0;
+ int slen = strlen(source), dlen = strlen(dest);
+#else
+ char newsource[PATH_MAX], newdest[PATH_MAX];
+#endif
+
+ /* Skip some files.. */
+ if (strcmp(Dir->d_name,".") == 0 ||
+ strcmp(Dir->d_name,"..") == 0)
+ continue;
+
+ /* Determine what kind of file it is. */
+#ifndef PATH_MAX
+ nslen = slen + strlen(Dir->d_name) + 2;
+
+ if (nslen <= 0)
+ {
+ retval = PAM_BUF_ERR;
+ goto go_out;
+ }
+
+ if ((newsource = malloc (nslen)) == NULL)
+ {
+ retval = PAM_BUF_ERR;
+ goto go_out;
+ }
+
+ sprintf(newsource, "%s/%s", source, Dir->d_name);
+#else
+ snprintf(newsource,sizeof(newsource),"%s/%s",source,Dir->d_name);
+#endif
+
+ if (lstat(newsource,&St) != 0)
+#ifndef PATH_MAX
+ {
+ free(newsource);
+ newsource = NULL;
+ continue;
+ }
+#else
+ continue;
+#endif
+
+
+ /* We'll need the new file's name. */
+#ifndef PATH_MAX
+ ndlen = dlen + strlen(Dir->d_name)+2;
+
+ if (ndlen <= 0)
+ {
+ retval = PAM_BUF_ERR;
+ goto go_out;
+ }
+
+ if ((newdest = malloc(ndlen)) == NULL)
+ {
+ free (newsource);
+ retval = PAM_BUF_ERR;
+ goto go_out;
+ }
+
+ sprintf (newdest, "%s/%s", dest, Dir->d_name);
+#else
+ snprintf (newdest,sizeof (newdest),"%s/%s",dest,Dir->d_name);
+#endif
+
+ /* If it's a directory, recurse. */
+ if (S_ISDIR(St.st_mode))
+ {
+ retval = create_homedir (pamh, ctrl, pwd, newsource, newdest);
+
+#ifndef PATH_MAX
+ free(newsource); newsource = NULL;
+ free(newdest); newdest = NULL;
+#endif
+
+ if (retval != PAM_SUCCESS)
+ {
+ closedir(D);
+ goto go_out;
+ }
+ continue;
+ }
+
+ /* If it's a symlink, create a new link. */
+ if (S_ISLNK(St.st_mode))
+ {
+ int pointedlen = 0;
+#ifndef PATH_MAX
+ char *pointed = NULL;
+ {
+ int size = 100;
+
+ while (1) {
+ pointed = (char *) malloc(size);
+ if ( ! pointed ) {
+ free(newsource);
+ free(newdest);
+ return PAM_BUF_ERR;
+ }
+ pointedlen = readlink (newsource, pointed, size);
+ if ( pointedlen < 0 ) break;
+ if ( pointedlen < size ) break;
+ free (pointed);
+ size *= 2;
+ }
+ }
+ if ( pointedlen < 0 )
+ free(pointed);
+ else
+ pointed[pointedlen] = 0;
+#else
+ char pointed[PATH_MAX];
+ memset(pointed, 0, sizeof(pointed));
+
+ pointedlen = readlink(newsource, pointed, sizeof(pointed) - 1);
+#endif
+
+ if ( pointedlen >= 0 ) {
+ if(symlink(pointed, newdest) == 0)
+ {
+ if (lchown(newdest,pwd->pw_uid,pwd->pw_gid) != 0)
+ {
+ pam_syslog(pamh, LOG_DEBUG,
+ "unable to change perms on link %s: %m", newdest);
+ closedir(D);
+#ifndef PATH_MAX
+ free(pointed);
+ free(newsource);
+ free(newdest);
+#endif
+ return PAM_PERM_DENIED;
+ }
+ }
+#ifndef PATH_MAX
+ free(pointed);
+#endif
+ }
+#ifndef PATH_MAX
+ free(newsource); newsource = NULL;
+ free(newdest); newdest = NULL;
+#endif
+ continue;
+ }
+
+ /* If it's not a regular file, it's probably not a good idea to create
+ * the new device node, FIFO, or whatever it is. */
+ if (!S_ISREG(St.st_mode))
+ {
+#ifndef PATH_MAX
+ free(newsource); newsource = NULL;
+ free(newdest); newdest = NULL;
+#endif
+ continue;
+ }
+
+ /* Open the source file */
+ if ((SrcFd = open(newsource,O_RDONLY)) < 0 || fstat(SrcFd,&St) != 0)
+ {
+ pam_syslog(pamh, LOG_DEBUG,
+ "unable to open src file %s: %m", newsource);
+ closedir(D);
+
+#ifndef PATH_MAX
+ free(newsource); newsource = NULL;
+ free(newdest); newdest = NULL;
+#endif
+
+ return PAM_PERM_DENIED;
+ }
+ if (stat(newsource,&St) != 0)
+ {
+ pam_syslog(pamh, LOG_DEBUG, "unable to stat src file %s: %m",
+ newsource);
+ close(SrcFd);
+ closedir(D);
+
+#ifndef PATH_MAX
+ free(newsource); newsource = NULL;
+ free(newdest); newdest = NULL;
+#endif
+
+ return PAM_PERM_DENIED;
+ }
+
+ /* Open the dest file */
+ if ((DestFd = open(newdest,O_WRONLY | O_TRUNC | O_CREAT,0600)) < 0)
+ {
+ pam_syslog(pamh, LOG_DEBUG,
+ "unable to open dest file %s: %m", newdest);
+ close(SrcFd);
+ closedir(D);
+
+#ifndef PATH_MAX
+ free(newsource); newsource = NULL;
+ free(newdest); newdest = NULL;
+#endif
+ 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)
+ {
+ pam_syslog(pamh, LOG_DEBUG,
+ "unable to change perms on copy %s: %m", newdest);
+ close(SrcFd);
+ close(DestFd);
+ closedir(D);
+
+#ifndef PATH_MAX
+ free(newsource); newsource = NULL;
+ free(newdest); newdest = NULL;
+#endif
+
+ return PAM_PERM_DENIED;
+ }
+
+ /* Copy the file */
+ do
+ {
+ Res = pam_modutil_read(SrcFd,remark,sizeof(remark));
+
+ if (Res == 0)
+ continue;
+
+ if (Res > 0) {
+ if (pam_modutil_write(DestFd,remark,Res) == Res)
+ continue;
+ }
+
+ /* If we get here, pam_modutil_read returned a -1 or
+ pam_modutil_write returned something unexpected. */
+ pam_syslog(pamh, LOG_DEBUG, "unable to perform IO: %m");
+ close(SrcFd);
+ close(DestFd);
+ closedir(D);
+
+#ifndef PATH_MAX
+ free(newsource); newsource = NULL;
+ free(newdest); newdest = NULL;
+#endif
+
+ return PAM_PERM_DENIED;
+ }
+ while (Res != 0);
+ close(SrcFd);
+ close(DestFd);
+
+#ifndef PATH_MAX
+ free(newsource); newsource = NULL;
+ free(newdest); newdest = NULL;
+#endif
+
+ }
+ closedir(D);
+
+ retval = PAM_SUCCESS;
+
+ go_out:
+
+ if (chmod(dest,0777 & (~UMask)) != 0 ||
+ chown(dest,pwd->pw_uid,pwd->pw_gid) != 0)
+ {
+ pam_syslog(pamh, LOG_DEBUG,
+ "unable to change perms on directory %s: %m", dest);
+ return PAM_PERM_DENIED;
+ }
+
+ return retval;
+}
+
+/* --- authentication management functions (only) --- */
+
+PAM_EXTERN int
+pam_sm_open_session (pam_handle_t *pamh, int flags, int argc,
+ const char **argv)
+{
+ int retval, ctrl;
+ const void *user;
+ const struct passwd *pwd;
+ struct stat St;
+
+ /* Parse the flag values */
+ ctrl = _pam_parse(pamh, flags, argc, argv);
+
+ /* Determine the user name so we can get the home directory */
+ retval = pam_get_item(pamh, PAM_USER, &user);
+ if (retval != PAM_SUCCESS || user == NULL || *(const char *)user == '\0')
+ {
+ pam_syslog(pamh, LOG_NOTICE, "user unknown");
+ return PAM_USER_UNKNOWN;
+ }
+
+ /* Get the password entry */
+ pwd = pam_modutil_getpwnam (pamh, user);
+ if (pwd == NULL)
+ {
+ 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)
+ return PAM_SUCCESS;
+
+ return create_homedir(pamh,ctrl,pwd,SkelDir,pwd->pw_dir);
+}
+
+/* Ignore */
+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;
+}
+
+#ifdef PAM_STATIC
+
+/* static module data */
+struct pam_module _pam_mkhomedir_modstruct =
+{
+ "pam_mkhomedir",
+ NULL,
+ NULL,
+ NULL,
+ pam_sm_open_session,
+ pam_sm_close_session,
+ NULL,
+};
+
+#endif