]> granicus.if.org Git - linux-pam/commitdiff
Relevant BUGIDs: rhbz#476784
authorTomas Mraz <tm@t8m.info>
Mon, 19 Jan 2009 09:09:15 +0000 (09:09 +0000)
committerTomas Mraz <tm@t8m.info>
Mon, 19 Jan 2009 09:09:15 +0000 (09:09 +0000)
Purpose of commit: new feature

Commit summary:
---------------
2009-01-19  Tomas Mraz <t8m@centrum.cz>

        * modules/pam_mkhomedir/Makefile.am: Add mkhomedir_helper.
        * modules/pam_mkhomedir/mkhomedir_helper.8.xml: New file. Manual page
        for mkhomedir_helper.
        * modules/pam_mkhomedir/mkhomedir_helper.c: New file. Source
        for mkhomedir_helper. Most of the code moved from pam_mkhomedir.c.
        * modules/pam_mkhomedir/pam_mkhomedir.c (_pam_parse): Do not convert umask
        to integer.
        (rec_mkdir): Moved to mkhomedir_helper.c.
        (create_homedir): Just exec the helper.
        (pam_sm_open_session): Improve logging.

ChangeLog
modules/pam_mkhomedir/Makefile.am
modules/pam_mkhomedir/mkhomedir_helper.8.xml [new file with mode: 0644]
modules/pam_mkhomedir/mkhomedir_helper.c [new file with mode: 0644]
modules/pam_mkhomedir/pam_mkhomedir.c

index c083f3410ee978da41bb5ee1681b780084c49c56..a00752cd59380fa5250ede653124e96bbe818a9a 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,16 @@
+2009-01-19  Tomas Mraz <t8m@centrum.cz>
+
+       * modules/pam_mkhomedir/Makefile.am: Add mkhomedir_helper.
+       * modules/pam_mkhomedir/mkhomedir_helper.8.xml: New file. Manual page
+       for mkhomedir_helper.
+       * modules/pam_mkhomedir/mkhomedir_helper.c: New file. Source
+       for mkhomedir_helper. Most of the code moved from pam_mkhomedir.c.
+       * modules/pam_mkhomedir/pam_mkhomedir.c (_pam_parse): Do not convert umask
+       to integer.
+       (rec_mkdir): Moved to mkhomedir_helper.c.
+       (create_homedir): Just exec the helper.
+       (pam_sm_open_session): Improve logging.
+
 2009-01-19  Daniel Cabrera <h.daniel.cabrera@gmail.com>
 
        * po/es.po: Updated translations.
index 7ed3a9f05e499dea7355f00f410d61e78ff30cdd..420314723b1ff300f44a9e9375f10ce35ffad84c 100644 (file)
@@ -1,21 +1,23 @@
 #
 # Copyright (c) 2005, 2006 Thorsten Kukuk <kukuk@suse.de>
+# Copyright (c) 2008 Red Hat, Inc.
 #
 
 CLEANFILES = *~
 
 EXTRA_DIST = README $(MANS) $(XMLS) tst-pam_mkhomedir
 
-man_MANS = pam_mkhomedir.8
+man_MANS = pam_mkhomedir.8 mkhomedir_helper.8
 
-XMLS = README.xml pam_mkhomedir.8.xml
+XMLS = README.xml pam_mkhomedir.8.xml mkhomedir_helper.8.xml
 
 TESTS = tst-pam_mkhomedir
 
 securelibdir = $(SECUREDIR)
 secureconfdir = $(SCONFIGDIR)
 
-AM_CFLAGS = -I$(top_srcdir)/libpam/include -I$(top_srcdir)/libpamc/include
+AM_CFLAGS = -I$(top_srcdir)/libpam/include -I$(top_srcdir)/libpamc/include \
+           -DMKHOMEDIR_HELPER=\"$(sbindir)/mkhomedir_helper\"
 AM_LDFLAGS = -no-undefined -avoid-version -module
 if HAVE_VERSIONING
   AM_LDFLAGS += -Wl,--version-script=$(srcdir)/../modules.map
@@ -25,6 +27,10 @@ securelib_LTLIBRARIES = pam_mkhomedir.la
 pam_mkhomedir_la_SOURCES = pam_mkhomedir.c
 pam_mkhomedir_la_LIBADD = -L$(top_builddir)/libpam -lpam
 
+sbin_PROGRAMS = mkhomedir_helper
+mkhomedir_helper_SOURCES = mkhomedir_helper.c
+mkhomedir_helper_LDADD = -L$(top_builddir)/libpam -lpam
+
 if ENABLE_REGENERATE_MAN
 noinst_DATA = README
 README: pam_mkhomedir.8.xml
diff --git a/modules/pam_mkhomedir/mkhomedir_helper.8.xml b/modules/pam_mkhomedir/mkhomedir_helper.8.xml
new file mode 100644 (file)
index 0000000..c834edd
--- /dev/null
@@ -0,0 +1,78 @@
+<?xml version="1.0" encoding='UTF-8'?>
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.3//EN"
+       "http://www.oasis-open.org/docbook/xml/4.3/docbookx.dtd">
+
+<refentry id="mkhomedir_helper">
+
+  <refmeta>
+    <refentrytitle>mkhomedir_helper</refentrytitle>
+    <manvolnum>8</manvolnum>
+    <refmiscinfo class="sectdesc">Linux-PAM Manual</refmiscinfo>
+  </refmeta>
+
+  <refnamediv id="mkhomedir_helper-name">
+    <refname>mkhomedir_helper</refname>
+    <refpurpose>Helper binary that creates home directories</refpurpose>
+  </refnamediv>
+
+  <refsynopsisdiv>
+    <cmdsynopsis id="mkhomedir_helper-cmdsynopsis">
+      <command>mkhomedir_helper</command>
+      <arg choice="req">
+        <replaceable>user</replaceable>
+      </arg>
+      <arg choice="opt">
+        <replaceable>umask</replaceable>
+      <arg choice="opt">
+        <replaceable>path-to-skel</replaceable>
+      </arg>
+      </arg>
+    </cmdsynopsis>
+  </refsynopsisdiv>
+
+  <refsect1 id="mkhomedir_helper-description">
+
+    <title>DESCRIPTION</title>
+
+    <para>
+      <emphasis>mkhomedir_helper</emphasis> is a helper program for the
+      <emphasis>pam_mkhomedir</emphasis> module that creates home directories
+      and populates them with contents of the specified skel directory.
+    </para>
+
+    <para>
+      The default value of <replaceable>umask</replaceable> is 0022 and the
+      default value of <replaceable>path-to-skel</replaceable> is
+      <emphasis>/etc/skel</emphasis>.
+    </para>
+
+    <para>
+      The helper is separated from the module to not require direct access from
+      login SELinux domains to the contents of user home directories. The
+      SELinux domain transition happens when the module is executing the
+      <emphasis>mkhomedir_helper</emphasis>.
+    </para>
+
+    <para>
+      The helper never touches home directories if they already exist.
+    </para>
+  </refsect1>
+
+  <refsect1 id='mkhomedir_helper-see_also'>
+    <title>SEE ALSO</title>
+    <para>
+      <citerefentry>
+       <refentrytitle>pam_mkhomedir</refentrytitle><manvolnum>8</manvolnum>
+      </citerefentry>
+    </para>
+  </refsect1>
+
+  <refsect1 id='mkhomedir_helper-author'>
+    <title>AUTHOR</title>
+      <para>
+        Written by Tomas Mraz based on the code originally in
+        <emphasis>pam_mkhomedir</emphasis> module.
+      </para>
+  </refsect1>
+
+</refentry>
diff --git a/modules/pam_mkhomedir/mkhomedir_helper.c b/modules/pam_mkhomedir/mkhomedir_helper.c
new file mode 100644 (file)
index 0000000..550a135
--- /dev/null
@@ -0,0 +1,422 @@
+/* mkhomedir_helper - helper for pam_mkhomedir module
+
+   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
+     <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>
+
+#include <security/pam_ext.h>
+#include <security/pam_modutil.h>
+
+static unsigned long u_mask = 0022;
+static char skeldir[BUFSIZ] = "/etc/skel";
+
+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(const struct passwd *pwd,
+              const char *source, const char *dest)
+{
+   char remark[BUFSIZ];
+   DIR *d;
+   struct dirent *dent;
+   int retval = PAM_SESSION_ERR;
+
+   /* Create the new directory */
+   if (rec_mkdir(dest, 0755) != 0)
+   {
+      pam_syslog(NULL, 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 == NULL)
+   {
+      pam_syslog(NULL, LOG_DEBUG, "unable to read directory %s: %m", source);
+      retval = PAM_PERM_DENIED;
+      goto go_out;
+   }
+
+   for (dent = readdir(d); dent != NULL; dent = 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(dent->d_name,".") == 0 ||
+         strcmp(dent->d_name,"..") == 0)
+        continue;
+
+      /* Determine what kind of file it is. */
+#ifndef PATH_MAX
+      nslen = slen + strlen(dent->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, dent->d_name);
+#else
+      snprintf(newsource, sizeof(newsource), "%s/%s", source, dent->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(dent->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, dent->d_name);
+#else
+      snprintf (newdest, sizeof (newdest), "%s/%s", dest, dent->d_name);
+#endif
+
+      /* If it's a directory, recurse. */
+      if (S_ISDIR(st.st_mode))
+      {
+         retval = create_homedir(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 = malloc(size);
+                          if (pointed == NULL) {
+                                  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(NULL, 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(NULL, 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(NULL, 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(NULL, 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) & (~u_mask)) != 0 ||
+         fchown(destfd, pwd->pw_uid, pwd->pw_gid) != 0)
+      {
+         pam_syslog(NULL, 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(NULL, 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 & (~u_mask)) != 0 ||
+       chown(dest, pwd->pw_uid, pwd->pw_gid) != 0)
+   {
+      pam_syslog(NULL, LOG_DEBUG,
+                "unable to change perms on directory %s: %m", dest);
+      return PAM_PERM_DENIED;
+   }
+
+   return retval;
+}
+
+int
+main(int argc, char *argv[])
+{
+   const struct passwd *pwd;
+   struct stat st;
+
+   if (argc < 2) {
+       fprintf(stderr, "Usage: %s <username> [<umask> [<skeldir>]]\n", argv[0]);
+       return PAM_SESSION_ERR;
+   }
+
+   pwd = getpwnam(argv[1]);
+   if (pwd == NULL) {
+       pam_syslog(NULL, LOG_ERR, "User unknown.");
+       return PAM_CRED_INSUFFICIENT;
+   }
+
+   if (argc >= 3) {
+       char *eptr;
+       errno = 0;
+       u_mask = strtoul(argv[2], &eptr, 0);
+       if (errno != 0 || *eptr != '\0') {
+               pam_syslog(NULL, LOG_ERR, "Bogus umask value %s", argv[2]);
+               return PAM_SESSION_ERR;
+       }
+   }
+
+   if (argc >= 4) {
+       if (strlen(argv[3]) >= sizeof(skeldir)) {
+               pam_syslog(NULL, LOG_ERR, "Too long skeldir path.");
+               return PAM_SESSION_ERR;
+       }
+       strcpy(skeldir, argv[3]);
+   }
+
+   /* 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(pwd, skeldir, pwd->pw_dir);
+}
+
index 44b092c1e00bab85a65a51bdfa6b7bd95d1dcf98..a0c389c55f162c1a7e9e21fb8780a7ccd5937410 100644 (file)
@@ -22,6 +22,7 @@
    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
      <morgan@parc.power.net> 1996
 
 #include "config.h"
 
-#include <stdarg.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_modutil.h>
 #include <security/pam_ext.h>
 
+#define MAX_FD_NO 10000
 
 /* 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 UMask[16] = "0022";
 static char SkelDir[BUFSIZ] = "/etc/skel"; /* THIS MODULE IS NOT THREAD SAFE */
 
 static int
@@ -81,7 +84,8 @@ _pam_parse (const pam_handle_t *pamh, int flags, int argc, const char **argv)
       } else if (!strcmp(*argv, "debug")) {
          ctrl |= MKHOMEDIR_DEBUG;
       } else if (!strncmp(*argv,"umask=",6)) {
-        UMask = strtol(*argv+6,0,0);
+        strncpy(SkelDir,*argv+6,sizeof(UMask));
+        UMask[sizeof(UMask)-1] = '\0';
       } else if (!strncmp(*argv,"skel=",5)) {
         strncpy(SkelDir,*argv+5,sizeof(SkelDir));
         SkelDir[sizeof(SkelDir)-1] = '\0';
@@ -94,357 +98,88 @@ _pam_parse (const pam_handle_t *pamh, int flags, int argc, const char **argv)
    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)
+create_homedir (pam_handle_t *pamh, int ctrl,
+               const struct passwd *pwd)
 {
-   char remark[BUFSIZ];
-   DIR *D;
-   struct dirent *Dir;
-   int retval = PAM_AUTH_ERR;
+   int retval, child;
+   void (*sighandler)(int) = NULL;
 
    /* 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;
-       }
+   if (!(ctrl & MKHOMEDIR_QUIET))
+      pam_info(pamh, _("Creating directory '%s'."), pwd->pw_dir);
 
-      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
+   D(("called."));
 
+   /*
+    * 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.
+    */
+   sighandler = signal(SIGCHLD, SIG_DFL);
 
-      /* We'll need the new file's name. */
-#ifndef PATH_MAX
-      ndlen = dlen + strlen(Dir->d_name)+2;
+   if (ctrl & MKHOMEDIR_DEBUG) {
+        pam_syslog(pamh, LOG_DEBUG, "Executing mkhomedir_helper.");
+   }
 
-      if (ndlen <= 0)
-       {
-         retval = PAM_BUF_ERR;
-         goto go_out;
+   /* fork */
+   child = fork();
+   if (child == 0) {
+        int i;
+        struct rlimit rlim;
+       static char *envp[] = { NULL };
+       char *args[] = { NULL, NULL, NULL, NULL, NULL };
+
+       if (getrlimit(RLIMIT_NOFILE, &rlim)==0) {
+          if (rlim.rlim_max >= MAX_FD_NO)
+                rlim.rlim_max = MAX_FD_NO;
+         for (i=0; i < (int)rlim.rlim_max; i++) {
+               close(i);
+         }
        }
 
-      if ((newdest = malloc(ndlen)) == NULL)
-       {
-         free (newsource);
-         retval = PAM_BUF_ERR;
-         goto go_out;
+       /* exec the mkhomedir helper */
+       args[0] = x_strdup(MKHOMEDIR_HELPER);
+       args[1] = pwd->pw_name;
+       args[2] = UMask;
+       args[3] = SkelDir;
+
+       execve(MKHOMEDIR_HELPER, 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 {
+         retval = WEXITSTATUS(retval);
        }
-
-      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
-
+   } else {
+       D(("fork failed"));
+       pam_syslog(pamh, LOG_ERR, "fork failed: %m");
+       retval = PAM_SYSTEM_ERR;
    }
-   closedir(D);
 
-   retval = PAM_SUCCESS;
+   if (sighandler != SIG_ERR) {
+        (void) signal(SIGCHLD, sighandler);   /* restore old signal handler */
+   }
 
- go_out:
+   if (ctrl & MKHOMEDIR_DEBUG) {
+        pam_syslog(pamh, LOG_DEBUG, "mkhomedir_helper returned %d", retval);
+   }
 
-   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;
+   if (retval != PAM_SUCCESS && !(ctrl & MKHOMEDIR_QUIET)) {
+       pam_error(pamh, _("Unable to create and initialize directory '%s'."),
+           pwd->pw_dir);
    }
 
+   D(("returning %d", retval));
    return retval;
 }
 
@@ -466,7 +201,7 @@ pam_sm_open_session (pam_handle_t *pamh, int flags, int argc,
    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");
+      pam_syslog(pamh, LOG_NOTICE, "Cannot obtain the user name.");
       return PAM_USER_UNKNOWN;
    }
 
@@ -474,16 +209,22 @@ pam_sm_open_session (pam_handle_t *pamh, int flags, int argc,
    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 (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,SkelDir,pwd->pw_dir);
+   return create_homedir(pamh, ctrl, pwd);
 }
 
 /* Ignore */