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
30 /* I want snprintf dammit */
33 #include <sys/types.h>
44 * here, we make a definition for the externally accessible function
45 * in this file (this definition is required for static a module
46 * but strongly encouraged generally) it is used to instruct the
47 * modules include file to define the function prototypes.
50 #define PAM_SM_SESSION
52 #include <security/pam_modules.h>
53 #include <security/_pam_macros.h>
55 /* argument parsing */
56 #define MKHOMEDIR_DEBUG 020 /* keep quiet about things */
57 #define MKHOMEDIR_QUIET 040 /* keep quiet about things */
59 static unsigned int UMask = 0022;
60 static char SkelDir[BUFSIZ] = "/etc/skel"; /* THIS MODULE IS NOT THREAD SAFE */
63 static void _log_err(int err, const char *format, ...)
67 va_start(args, format);
68 openlog("PAM-mkhomedir", LOG_CONS|LOG_PID, LOG_AUTH);
69 vsyslog(err, format, args);
74 static int _pam_parse(int flags, int argc, const char **argv)
78 /* does the appliction require quiet? */
79 if ((flags & PAM_SILENT) == PAM_SILENT)
80 ctrl |= MKHOMEDIR_QUIET;
82 /* step through arguments */
83 for (; argc-- > 0; ++argv)
85 if (!strcmp(*argv, "silent")) {
86 ctrl |= MKHOMEDIR_QUIET;
87 } else if (!strncmp(*argv,"umask=",6)) {
88 UMask = strtol(*argv+6,0,0);
89 } else if (!strncmp(*argv,"skel=",5)) {
90 strncpy(SkelDir,*argv+5,sizeof(SkelDir));
91 SkelDir[sizeof(SkelDir)-1] = '\0';
93 _log_err(LOG_ERR, "unknown option; %s", *argv);
97 D(("ctrl = %o", ctrl));
101 /* This common function is used to send a message to the applications
102 conversion function. Our only use is to ask the application to print
103 an informative message that we are creating a home directory */
104 static int converse(pam_handle_t * pamh, int ctrl, int nargs
105 ,struct pam_message **message
106 ,struct pam_response **response)
109 struct pam_conv *conv;
111 D(("begin to converse"));
113 retval = pam_get_item(pamh, PAM_CONV, (const void **) &conv);
114 if (retval == PAM_SUCCESS)
117 retval = conv->conv(nargs, (const struct pam_message **) message
118 ,response, conv->appdata_ptr);
120 D(("returned from application's conversation function"));
122 if (retval != PAM_SUCCESS && (ctrl & MKHOMEDIR_DEBUG))
124 _log_err(LOG_DEBUG, "conversation failure [%s]"
125 ,pam_strerror(pamh, retval));
131 _log_err(LOG_ERR, "couldn't obtain coversation function [%s]"
132 ,pam_strerror(pamh, retval));
135 D(("ready to return from module conversation"));
137 return retval; /* propagate error status */
140 /* Ask the application to display a short text string for us. */
141 static int make_remark(pam_handle_t * pamh, int ctrl, const char *remark)
145 if ((ctrl & MKHOMEDIR_QUIET) != MKHOMEDIR_QUIET)
147 struct pam_message msg[1], *mesg[1];
148 struct pam_response *resp = NULL;
151 msg[0].msg_style = PAM_TEXT_INFO;
154 retval = converse(pamh, ctrl, 1, mesg, &resp);
159 _pam_drop_reply(resp, 1);
164 D(("keeping quiet"));
165 retval = PAM_SUCCESS;
168 D(("returning %s", pam_strerror(pamh, retval)));
172 /* Do the actual work of creating a home dir */
173 static int create_homedir(pam_handle_t * pamh, int ctrl,
174 const struct passwd *pwd,
175 const char *source, const char *dest)
181 /* Mention what is happening, if the notification fails that is OK */
182 if (snprintf(remark,sizeof(remark),"Creating directory '%s'.", dest) == -1)
183 return PAM_PERM_DENIED;
185 make_remark(pamh, ctrl, remark);
187 /* Create the new directory */
188 if (mkdir(dest,0700) != 0)
190 _log_err(LOG_DEBUG, "unable to create directory %s",dest);
191 return PAM_PERM_DENIED;
193 if (chmod(dest,0777 & (~UMask)) != 0 ||
194 chown(dest,pwd->pw_uid,pwd->pw_gid) != 0)
196 _log_err(LOG_DEBUG, "unable to change perms on directory %s",dest);
197 return PAM_PERM_DENIED;
200 /* See if we need to copy the skel dir over. */
201 if ((source == NULL) || (strlen(source) == 0))
206 /* Scan the directory */
210 _log_err(LOG_DEBUG, "unable to read directory %s",source);
211 return PAM_PERM_DENIED;
214 for (Dir = readdir(D); Dir != 0; Dir = readdir(D))
220 char newsource[PATH_MAX], newdest[PATH_MAX];
222 /* Skip some files.. */
223 if (strcmp(Dir->d_name,".") == 0 ||
224 strcmp(Dir->d_name,"..") == 0)
227 /* Determine what kind of file it is. */
228 snprintf(newsource,sizeof(newsource),"%s/%s",source,Dir->d_name);
229 if (lstat(newsource,&St) != 0)
232 /* We'll need the new file's name. */
233 snprintf(newdest,sizeof(newdest),"%s/%s",dest,Dir->d_name);
235 /* If it's a directory, recurse. */
236 if (S_ISDIR(St.st_mode))
238 create_homedir(pamh, ctrl, pwd, newsource, newdest);
242 /* If it's a symlink, create a new link. */
243 if (S_ISLNK(St.st_mode))
245 char pointed[PATH_MAX];
246 memset(pointed, 0, sizeof(pointed));
247 if(readlink(newsource, pointed, sizeof(pointed) - 1) != -1)
249 if(symlink(pointed, newdest) == 0)
251 if (lchown(newdest,pwd->pw_uid,pwd->pw_gid) != 0)
253 _log_err(LOG_DEBUG, "unable to chang perms on link %s",
255 return PAM_PERM_DENIED;
262 /* If it's not a regular file, it's probably not a good idea to create
263 * the new device node, FIFO, or whatever it is. */
264 if (!S_ISREG(St.st_mode))
269 /* Open the source file */
270 if ((SrcFd = open(newsource,O_RDONLY)) < 0 || fstat(SrcFd,&St) != 0)
272 _log_err(LOG_DEBUG, "unable to open src file %s",newsource);
273 return PAM_PERM_DENIED;
277 /* Open the dest file */
278 if ((DestFd = open(newdest,O_WRONLY | O_TRUNC | O_CREAT,0600)) < 0)
281 _log_err(LOG_DEBUG, "unable to open dest file %s",newdest);
282 return PAM_PERM_DENIED;
285 /* Set the proper ownership and permissions for the module. We make
286 the file a+w and then mask it with the set mask. This preseves
288 if (fchmod(DestFd,(St.st_mode | 0222) & (~UMask)) != 0 ||
289 fchown(DestFd,pwd->pw_uid,pwd->pw_gid) != 0)
293 _log_err(LOG_DEBUG, "unable to chang perms on copy %s",newdest);
294 return PAM_PERM_DENIED;
300 Res = read(SrcFd,remark,sizeof(remark));
301 if (Res < 0 || write(DestFd,remark,Res) != Res)
305 _log_err(LOG_DEBUG, "unable to perform IO");
306 return PAM_PERM_DENIED;
317 /* --- authentication management functions (only) --- */
320 int pam_sm_open_session(pam_handle_t * pamh, int flags, int argc
325 const struct passwd *pwd;
328 /* Parse the flag values */
329 ctrl = _pam_parse(flags, argc, argv);
331 /* Determine the user name so we can get the home directory */
332 retval = pam_get_item(pamh, PAM_USER, (const void **) &user);
333 if (retval != PAM_SUCCESS || user == NULL || *user == '\0')
335 _log_err(LOG_NOTICE, "user unknown");
336 return PAM_USER_UNKNOWN;
339 /* Get the password entry */
340 pwd = getpwnam(user);
343 D(("couldn't identify user %s", user));
344 return PAM_CRED_INSUFFICIENT;
347 /* Stat the home directory, if something exists then we assume it is
348 correct and return a success*/
349 if (stat(pwd->pw_dir,&St) == 0)
352 return create_homedir(pamh,ctrl,pwd,SkelDir,pwd->pw_dir);
357 int pam_sm_close_session(pam_handle_t * pamh, int flags, int argc
365 /* static module data */
366 struct pam_module _pam_mkhomedir_modstruct =
373 pam_sm_close_session,