1 /* PAM Make Home Dir module
3 This module will create a users home directory if it does not exist
4 when the session begins. This allows users to be present in central
5 database (such as nis, kerb or ldap) without using a distributed
6 file system or pre-creating a large number of directories.
8 Here is a sample /etc/pam.d/login file for Debian GNU/Linux
11 auth requisite pam_securetty.so
12 auth sufficient pam_ldap.so
13 auth required pam_pwdb.so
14 auth optional pam_group.so
15 auth optional pam_mail.so
16 account requisite pam_time.so
17 account sufficient pam_ldap.so
18 account required pam_pwdb.so
19 session required pam_mkhomedir.so skel=/etc/skel/ umask=0022
20 session required pam_pwdb.so
21 session optional pam_lastlog.so
22 password required pam_pwdb.so
24 Released under the GNU LGPL version 2 or later
25 Originally written by Jason Gunthorpe <jgg@debian.org> Feb 1999
26 Structure taken from pam_lastlogin by Andrew Morgan
27 <morgan@parc.power.net> 1996
33 #include <sys/types.h>
46 * here, we make a definition for the externally accessible function
47 * in this file (this definition is required for static a module
48 * but strongly encouraged generally) it is used to instruct the
49 * modules include file to define the function prototypes.
52 #define PAM_SM_SESSION
54 #include <security/pam_modules.h>
55 #include <security/_pam_macros.h>
56 #include <security/_pam_modutil.h>
57 #include <security/pam_ext.h>
60 /* argument parsing */
61 #define MKHOMEDIR_DEBUG 020 /* keep quiet about things */
62 #define MKHOMEDIR_QUIET 040 /* keep quiet about things */
64 static unsigned int UMask = 0022;
65 static char SkelDir[BUFSIZ] = "/etc/skel"; /* THIS MODULE IS NOT THREAD SAFE */
68 _pam_parse (const pam_handle_t *pamh, int flags, int argc, const char **argv)
72 /* does the appliction require quiet? */
73 if ((flags & PAM_SILENT) == PAM_SILENT)
74 ctrl |= MKHOMEDIR_QUIET;
76 /* step through arguments */
77 for (; argc-- > 0; ++argv)
79 if (!strcmp(*argv, "silent")) {
80 ctrl |= MKHOMEDIR_QUIET;
81 } else if (!strncmp(*argv,"umask=",6)) {
82 UMask = strtol(*argv+6,0,0);
83 } else if (!strncmp(*argv,"skel=",5)) {
84 strncpy(SkelDir,*argv+5,sizeof(SkelDir));
85 SkelDir[sizeof(SkelDir)-1] = '\0';
87 pam_syslog(pamh,LOG_ERR, "unknown option; %s", *argv);
91 D(("ctrl = %o", ctrl));
95 /* Ask the application to display a short text string for us. */
97 make_remark (pam_handle_t *pamh, int ctrl, const char *remark)
101 if ((ctrl & MKHOMEDIR_QUIET) != MKHOMEDIR_QUIET)
103 pam_info (pamh, "%s", remark);
107 D(("keeping quiet"));
108 retval = PAM_SUCCESS;
111 D(("returning %s", pam_strerror(pamh, retval)));
116 rec_mkdir (const char *dir, mode_t mode)
119 char *parent = strdup (dir);
124 cp = strrchr (parent, '/');
131 if (stat (parent, &st) == -1 && errno == ENOENT)
132 if (rec_mkdir (parent, mode) != 0)
141 if (mkdir (dir, mode) != 0 && errno != EEXIST)
147 /* Do the actual work of creating a home dir */
149 create_homedir (pam_handle_t * pamh, int ctrl,
150 const struct passwd *pwd,
151 const char *source, const char *dest)
156 int retval = PAM_AUTH_ERR;
158 /* Mention what is happening, if the notification fails that is OK */
159 if (snprintf(remark,sizeof(remark),"Creating directory '%s'.", dest) == -1)
160 return PAM_PERM_DENIED;
162 make_remark(pamh, ctrl, remark);
164 /* Create the new directory */
165 if (rec_mkdir (dest,0755) != 0)
167 pam_syslog(pamh,LOG_DEBUG, "unable to create directory %s",dest);
168 return PAM_PERM_DENIED;
171 /* See if we need to copy the skel dir over. */
172 if ((source == NULL) || (strlen(source) == 0))
174 retval = PAM_SUCCESS;
178 /* Scan the directory */
179 D = opendir (source);
182 pam_syslog(pamh,LOG_DEBUG, "unable to read directory %s",source);
183 retval = PAM_PERM_DENIED;
187 for (Dir = readdir(D); Dir != 0; Dir = readdir(D))
194 char *newsource = NULL, *newdest = NULL;
195 /* track length of buffers */
196 int nslen = 0, ndlen = 0;
197 int slen = strlen(source), dlen = strlen(dest);
199 char newsource[PATH_MAX], newdest[PATH_MAX];
202 /* Skip some files.. */
203 if (strcmp(Dir->d_name,".") == 0 ||
204 strcmp(Dir->d_name,"..") == 0)
207 /* Determine what kind of file it is. */
209 nslen = slen + strlen(Dir->d_name) + 2;
213 retval = PAM_BUF_ERR;
217 if ((newsource = malloc (nslen)) == NULL)
219 retval = PAM_BUF_ERR;
223 sprintf(newsource, "%s/%s", source, Dir->d_name);
225 snprintf(newsource,sizeof(newsource),"%s/%s",source,Dir->d_name);
228 if (lstat(newsource,&St) != 0)
240 /* We'll need the new file's name. */
242 ndlen = dlen + strlen(Dir->d_name)+2;
246 retval = PAM_BUF_ERR;
250 if ((newdest = malloc(ndlen)) == NULL)
253 retval = PAM_BUF_ERR;
257 sprintf (newdest, "%s/%s", dest, Dir->d_name);
259 snprintf (newdest,sizeof (newdest),"%s/%s",dest,Dir->d_name);
262 /* If it's a directory, recurse. */
263 if (S_ISDIR(St.st_mode))
265 retval = create_homedir (pamh, ctrl, pwd, newsource, newdest);
268 free(newsource); newsource = NULL;
269 free(newdest); newdest = NULL;
272 if (retval != PAM_SUCCESS)
280 /* If it's a symlink, create a new link. */
281 if (S_ISLNK(St.st_mode))
285 char *pointed = NULL;
290 pointed = (char *) malloc(size);
296 pointedlen = readlink (newsource, pointed, size);
297 if ( pointedlen < 0 ) break;
298 if ( pointedlen < size ) break;
303 if ( pointedlen < 0 )
306 pointed[pointedlen] = 0;
308 char pointed[PATH_MAX];
309 memset(pointed, 0, sizeof(pointed));
311 pointedlen = readlink(newsource, pointed, sizeof(pointed) - 1);
314 if ( pointedlen >= 0 ) {
315 if(symlink(pointed, newdest) == 0)
317 if (lchown(newdest,pwd->pw_uid,pwd->pw_gid) != 0)
320 pam_syslog(pamh,LOG_DEBUG, "unable to change perms on link %s",
327 return PAM_PERM_DENIED;
335 free(newsource); newsource = NULL;
336 free(newdest); newdest = NULL;
341 /* If it's not a regular file, it's probably not a good idea to create
342 * the new device node, FIFO, or whatever it is. */
343 if (!S_ISREG(St.st_mode))
346 free(newsource); newsource = NULL;
347 free(newdest); newdest = NULL;
352 /* Open the source file */
353 if ((SrcFd = open(newsource,O_RDONLY)) < 0 || fstat(SrcFd,&St) != 0)
356 pam_syslog(pamh,LOG_DEBUG, "unable to open src file %s",newsource);
359 free(newsource); newsource = NULL;
360 free(newdest); newdest = NULL;
363 return PAM_PERM_DENIED;
367 /* Open the dest file */
368 if ((DestFd = open(newdest,O_WRONLY | O_TRUNC | O_CREAT,0600)) < 0)
372 pam_syslog(pamh,LOG_DEBUG, "unable to open dest file %s",newdest);
375 free(newsource); newsource = NULL;
376 free(newdest); newdest = NULL;
378 return PAM_PERM_DENIED;
381 /* Set the proper ownership and permissions for the module. We make
382 the file a+w and then mask it with the set mask. This preseves
384 if (fchmod(DestFd,(St.st_mode | 0222) & (~UMask)) != 0 ||
385 fchown(DestFd,pwd->pw_uid,pwd->pw_gid) != 0)
390 pam_syslog(pamh,LOG_DEBUG, "unable to chang perms on copy %s",newdest);
393 free(newsource); newsource = NULL;
394 free(newdest); newdest = NULL;
397 return PAM_PERM_DENIED;
403 Res = _pammodutil_read(SrcFd,remark,sizeof(remark));
409 if (_pammodutil_write(DestFd,remark,Res) == Res)
413 /* If we get here, pammodutil_read returned a -1 or
414 _pammodutil_write returned something unexpected. */
418 pam_syslog(pamh,LOG_DEBUG, "unable to perform IO");
421 free(newsource); newsource = NULL;
422 free(newdest); newdest = NULL;
425 return PAM_PERM_DENIED;
432 free(newsource); newsource = NULL;
433 free(newdest); newdest = NULL;
439 retval = PAM_SUCCESS;
443 if (chmod(dest,0777 & (~UMask)) != 0 ||
444 chown(dest,pwd->pw_uid,pwd->pw_gid) != 0)
446 pam_syslog(pamh,LOG_DEBUG, "unable to change perms on directory %s",dest);
447 return PAM_PERM_DENIED;
453 /* --- authentication management functions (only) --- */
456 pam_sm_open_session (pam_handle_t *pamh, int flags, int argc,
461 const struct passwd *pwd;
464 /* Parse the flag values */
465 ctrl = _pam_parse(pamh, flags, argc, argv);
467 /* Determine the user name so we can get the home directory */
468 retval = pam_get_item(pamh, PAM_USER, &user);
469 if (retval != PAM_SUCCESS || user == NULL || *(const char *)user == '\0')
471 pam_syslog(pamh,LOG_NOTICE, "user unknown");
472 return PAM_USER_UNKNOWN;
475 /* Get the password entry */
476 pwd = _pammodutil_getpwnam (pamh, user);
479 D(("couldn't identify user %s", user));
480 return PAM_CRED_INSUFFICIENT;
483 /* Stat the home directory, if something exists then we assume it is
484 correct and return a success*/
485 if (stat(pwd->pw_dir,&St) == 0)
488 return create_homedir(pamh,ctrl,pwd,SkelDir,pwd->pw_dir);
493 int pam_sm_close_session (pam_handle_t * pamh UNUSED, int flags UNUSED,
494 int argc UNUSED, const char **argv UNUSED)
501 /* static module data */
502 struct pam_module _pam_mkhomedir_modstruct =
509 pam_sm_close_session,