]> granicus.if.org Git - linux-pam/blobdiff - modules/pam_mkhomedir/pam_mkhomedir.c
Introduce pam_modutil_sanitize_helper_fds
[linux-pam] / modules / pam_mkhomedir / pam_mkhomedir.c
index ec05993dfa1e2be8e11d6326d2392eccd045c95e..c9220897508e821e83fba5ab4c0e1792131b7841 100644 (file)
@@ -4,41 +4,45 @@
    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;
 }