]> granicus.if.org Git - linux-pam/blob - modules/pam_mkhomedir/pam_mkhomedir.c
Relevant BUGIDs: none
[linux-pam] / modules / pam_mkhomedir / pam_mkhomedir.c
1 /* PAM Make Home Dir module
2
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.
7
8    Here is a sample /etc/pam.d/login file for Debian GNU/Linux
9    2.1:
10
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
23
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
28  */
29
30 /* I want snprintf dammit */
31 #define _GNU_SOURCE 1
32 #include <stdarg.h>
33 #include <sys/types.h>
34 #include <sys/stat.h>
35 #include <fcntl.h>
36 #include <unistd.h>
37 #include <pwd.h>
38 #include <errno.h>
39 #include <stdlib.h>
40 #include <stdio.h>
41 #include <string.h>
42 #include <dirent.h>
43
44 /*
45  * here, we make a definition for the externally accessible function
46  * in this file (this definition is required for static a module
47  * but strongly encouraged generally) it is used to instruct the
48  * modules include file to define the function prototypes.
49  */
50
51 #define PAM_SM_SESSION
52
53 #include <security/pam_modules.h>
54 #include <security/_pam_macros.h>
55 #include <security/_pam_modutil.h>
56
57
58 /* argument parsing */
59 #define MKHOMEDIR_DEBUG      020        /* keep quiet about things */
60 #define MKHOMEDIR_QUIET      040        /* keep quiet about things */
61
62 static unsigned int UMask = 0022;
63 static char SkelDir[BUFSIZ] = "/etc/skel"; /* THIS MODULE IS NOT THREAD SAFE */
64
65 /* some syslogging */
66 static void
67 _log_err (int err, const char *format, ...)
68 {
69     va_list args;
70
71     va_start(args, format);
72     openlog("PAM-mkhomedir", LOG_CONS|LOG_PID, LOG_AUTH);
73     vsyslog(err, format, args);
74     va_end(args);
75     closelog();
76 }
77
78 static int
79 _pam_parse (int flags, int argc, const char **argv)
80 {
81    int ctrl = 0;
82
83    /* does the appliction require quiet? */
84    if ((flags & PAM_SILENT) == PAM_SILENT)
85       ctrl |= MKHOMEDIR_QUIET;
86
87    /* step through arguments */
88    for (; argc-- > 0; ++argv)
89    {
90       if (!strcmp(*argv, "silent")) {
91          ctrl |= MKHOMEDIR_QUIET;
92       } else if (!strncmp(*argv,"umask=",6)) {
93          UMask = strtol(*argv+6,0,0);
94       } else if (!strncmp(*argv,"skel=",5)) {
95          strncpy(SkelDir,*argv+5,sizeof(SkelDir));
96          SkelDir[sizeof(SkelDir)-1] = '\0';
97       } else {
98          _log_err(LOG_ERR, "unknown option; %s", *argv);
99       }
100    }
101
102    D(("ctrl = %o", ctrl));
103    return ctrl;
104 }
105
106 /* This common function is used to send a message to the applications
107    conversion function. Our only use is to ask the application to print
108    an informative message that we are creating a home directory */
109 static int converse(pam_handle_t * pamh, int ctrl, int nargs
110                     ,struct pam_message **message
111                     ,struct pam_response **response)
112 {
113    int retval;
114    const void *void_conv;
115    const struct pam_conv *conv;
116
117    D(("begin to converse"));
118
119    retval = pam_get_item(pamh, PAM_CONV, &void_conv);
120    conv = (const struct pam_conv *)void_conv;
121    if (retval == PAM_SUCCESS && conv)
122    {
123
124       retval = conv->conv(nargs, (const struct pam_message **) message
125                           ,response, conv->appdata_ptr);
126
127       D(("returned from application's conversation function"));
128
129       if (retval != PAM_SUCCESS && (ctrl & MKHOMEDIR_DEBUG))
130       {
131          _log_err(LOG_DEBUG, "conversation failure [%s]"
132                   ,pam_strerror(pamh, retval));
133       }
134
135    }
136    else
137    {
138       _log_err(LOG_ERR, "couldn't obtain coversation function [%s]"
139                ,pam_strerror(pamh, retval));
140      if (retval == PAM_SUCCESS)
141          retval = PAM_BAD_ITEM; /* conv was NULL */
142    }
143
144    D(("ready to return from module conversation"));
145
146    return retval;               /* propagate error status */
147 }
148
149 /* Ask the application to display a short text string for us. */
150 static int
151 make_remark (pam_handle_t *pamh, int ctrl, const char *remark)
152 {
153    int retval;
154
155    if ((ctrl & MKHOMEDIR_QUIET) != MKHOMEDIR_QUIET)
156    {
157       struct pam_message msg[1], *mesg[1];
158       struct pam_response *resp = NULL;
159
160       mesg[0] = &msg[0];
161       msg[0].msg_style = PAM_TEXT_INFO;
162       msg[0].msg = remark;
163
164       retval = converse(pamh, ctrl, 1, mesg, &resp);
165
166       msg[0].msg = NULL;
167       if (resp)
168       {
169          _pam_drop_reply(resp, 1);
170       }
171    }
172    else
173    {
174       D(("keeping quiet"));
175       retval = PAM_SUCCESS;
176    }
177
178    D(("returning %s", pam_strerror(pamh, retval)));
179    return retval;
180 }
181
182 static int
183 rec_mkdir (const char *dir, int mode)
184 {
185   char *cp;
186   char *parent = strdup (dir);
187
188   if (parent == NULL)
189     return 1;
190
191   cp = strrchr (parent, '/');
192
193   if (cp != NULL)
194     {
195       struct stat st;
196
197       *cp++ = '\0';
198       if (stat (parent, &st) == -1 && errno == ENOENT)
199         if (rec_mkdir (parent, mode) != 0)
200           {
201             free (parent);
202             return 1;
203           }
204     }
205
206   free (parent);
207
208   if (mkdir (dir, mode) != 0 && errno != EEXIST)
209     return 1;
210
211   return 0;
212 }
213
214 /* Do the actual work of creating a home dir */
215 static int
216 create_homedir (pam_handle_t * pamh, int ctrl,
217                 const struct passwd *pwd,
218                 const char *source, const char *dest)
219 {
220    char remark[BUFSIZ];
221    DIR *D;
222    struct dirent *Dir;
223    int retval = PAM_AUTH_ERR;
224
225    /* Mention what is happening, if the notification fails that is OK */
226    if (snprintf(remark,sizeof(remark),"Creating directory '%s'.", dest) == -1)
227       return PAM_PERM_DENIED;
228
229    make_remark(pamh, ctrl, remark);
230
231    /* Create the new directory */
232    if (rec_mkdir (dest,0755) != 0)
233    {
234       _log_err(LOG_DEBUG, "unable to create directory %s",dest);
235       return PAM_PERM_DENIED;
236    }
237
238    /* See if we need to copy the skel dir over. */
239    if ((source == NULL) || (strlen(source) == 0))
240    {
241      retval = PAM_SUCCESS;
242      goto go_out;
243    }
244
245    /* Scan the directory */
246    D = opendir (source);
247    if (D == 0)
248    {
249       _log_err(LOG_DEBUG, "unable to read directory %s",source);
250       retval = PAM_PERM_DENIED;
251       goto go_out;
252    }
253
254    for (Dir = readdir(D); Dir != 0; Dir = readdir(D))
255    {
256       int SrcFd;
257       int DestFd;
258       int Res;
259       struct stat St;
260 #ifndef PATH_MAX
261       char *newsource = NULL, *newdest = NULL;
262       /* track length of buffers */
263       int nslen = 0, ndlen = 0;
264       int slen = strlen(source), dlen = strlen(dest);
265 #else
266       char newsource[PATH_MAX], newdest[PATH_MAX];
267 #endif
268
269       /* Skip some files.. */
270       if (strcmp(Dir->d_name,".") == 0 ||
271           strcmp(Dir->d_name,"..") == 0)
272          continue;
273
274       /* Determine what kind of file it is. */
275 #ifndef PATH_MAX
276       nslen = slen + strlen(Dir->d_name) + 2;
277
278       if (nslen <= 0)
279         {
280           retval = PAM_BUF_ERR;
281           goto go_out;
282         }
283
284       if ((newsource = malloc (nslen)) == NULL)
285         {
286           retval = PAM_BUF_ERR;
287           goto go_out;
288         }
289
290       sprintf(newsource, "%s/%s", source, Dir->d_name);
291 #else
292       snprintf(newsource,sizeof(newsource),"%s/%s",source,Dir->d_name);
293 #endif
294
295       if (lstat(newsource,&St) != 0)
296 #ifndef PATH_MAX
297       {
298               free(newsource);
299               newsource = NULL;
300          continue;
301       }
302 #else
303       continue;
304 #endif
305
306
307       /* We'll need the new file's name. */
308 #ifndef PATH_MAX
309       ndlen = dlen + strlen(Dir->d_name)+2;
310
311       if (ndlen <= 0)
312         {
313           retval = PAM_BUF_ERR;
314           goto go_out;
315         }
316
317       if ((newdest = malloc(ndlen)) == NULL)
318         {
319           free (newsource);
320           retval = PAM_BUF_ERR;
321           goto go_out;
322         }
323
324       sprintf (newdest, "%s/%s", dest, Dir->d_name);
325 #else
326       snprintf (newdest,sizeof (newdest),"%s/%s",dest,Dir->d_name);
327 #endif
328
329       /* If it's a directory, recurse. */
330       if (S_ISDIR(St.st_mode))
331       {
332         retval = create_homedir (pamh, ctrl, pwd, newsource, newdest);
333
334 #ifndef PATH_MAX
335          free(newsource); newsource = NULL;
336          free(newdest); newdest = NULL;
337 #endif
338
339          if (retval != PAM_SUCCESS)
340            {
341              closedir(D);
342              goto go_out;
343            }
344          continue;
345       }
346
347       /* If it's a symlink, create a new link. */
348       if (S_ISLNK(St.st_mode))
349       {
350          int pointedlen = 0;
351 #ifndef PATH_MAX
352          char *pointed = NULL;
353            {
354                    int size = 100;
355
356                    while (1) {
357                            pointed = (char *) malloc(size);
358                            if ( ! pointed ) {
359                                    free(newsource);
360                                    free(newdest);
361                                    return PAM_BUF_ERR;
362                            }
363                            pointedlen = readlink (newsource, pointed, size);
364                            if ( pointedlen < 0 ) break;
365                            if ( pointedlen < size ) break;
366                            free (pointed);
367                            size *= 2;
368                    }
369            }
370            if ( pointedlen < 0 )
371                    free(pointed);
372            else
373                    pointed[pointedlen] = 0;
374 #else
375          char pointed[PATH_MAX];
376          memset(pointed, 0, sizeof(pointed));
377
378          pointedlen = readlink(newsource, pointed, sizeof(pointed) - 1);
379 #endif
380
381          if ( pointedlen >= 0 ) {
382             if(symlink(pointed, newdest) == 0)
383             {
384                if (lchown(newdest,pwd->pw_uid,pwd->pw_gid) != 0)
385                {
386                    closedir(D);
387                    _log_err(LOG_DEBUG, "unable to change perms on link %s",
388                             newdest);
389 #ifndef PATH_MAX
390                    free(pointed);
391                    free(newsource);
392                    free(newdest);
393 #endif
394                    return PAM_PERM_DENIED;
395                }
396             }
397 #ifndef PATH_MAX
398             free(pointed);
399 #endif
400          }
401 #ifndef PATH_MAX
402          free(newsource); newsource = NULL;
403          free(newdest); newdest = NULL;
404 #endif
405          continue;
406       }
407
408       /* If it's not a regular file, it's probably not a good idea to create
409        * the new device node, FIFO, or whatever it is. */
410       if (!S_ISREG(St.st_mode))
411       {
412 #ifndef PATH_MAX
413          free(newsource); newsource = NULL;
414          free(newdest); newdest = NULL;
415 #endif
416          continue;
417       }
418
419       /* Open the source file */
420       if ((SrcFd = open(newsource,O_RDONLY)) < 0 || fstat(SrcFd,&St) != 0)
421       {
422          closedir(D);
423          _log_err(LOG_DEBUG, "unable to open src file %s",newsource);
424
425 #ifndef PATH_MAX
426          free(newsource); newsource = NULL;
427          free(newdest); newdest = NULL;
428 #endif
429
430          return PAM_PERM_DENIED;
431       }
432       stat(newsource,&St);
433
434       /* Open the dest file */
435       if ((DestFd = open(newdest,O_WRONLY | O_TRUNC | O_CREAT,0600)) < 0)
436       {
437          close(SrcFd);
438          closedir(D);
439          _log_err(LOG_DEBUG, "unable to open dest file %s",newdest);
440
441 #ifndef PATH_MAX
442          free(newsource); newsource = NULL;
443          free(newdest); newdest = NULL;
444 #endif
445          return PAM_PERM_DENIED;
446       }
447
448       /* Set the proper ownership and permissions for the module. We make
449          the file a+w and then mask it with the set mask. This preseves
450          execute bits */
451       if (fchmod(DestFd,(St.st_mode | 0222) & (~UMask)) != 0 ||
452           fchown(DestFd,pwd->pw_uid,pwd->pw_gid) != 0)
453       {
454          close(SrcFd);
455          close(DestFd);
456          closedir(D);
457          _log_err(LOG_DEBUG, "unable to chang perms on copy %s",newdest);
458
459 #ifndef PATH_MAX
460          free(newsource); newsource = NULL;
461          free(newdest); newdest = NULL;
462 #endif
463
464          return PAM_PERM_DENIED;
465       }
466
467       /* Copy the file */
468       do
469       {
470          Res = _pammodutil_read(SrcFd,remark,sizeof(remark));
471
472          if (Res == 0)
473              continue;
474
475          if (Res > 0) {
476              if (_pammodutil_write(DestFd,remark,Res) == Res)
477                 continue;
478          }
479
480          /* If we get here, pammodutil_read returned a -1 or
481             _pammodutil_write returned something unexpected. */
482          close(SrcFd);
483          close(DestFd);
484          closedir(D);
485          _log_err(LOG_DEBUG, "unable to perform IO");
486
487 #ifndef PATH_MAX
488          free(newsource); newsource = NULL;
489          free(newdest); newdest = NULL;
490 #endif
491
492          return PAM_PERM_DENIED;
493       }
494       while (Res != 0);
495       close(SrcFd);
496       close(DestFd);
497
498 #ifndef PATH_MAX
499          free(newsource); newsource = NULL;
500          free(newdest); newdest = NULL;
501 #endif
502
503    }
504    closedir(D);
505
506    retval = PAM_SUCCESS;
507
508  go_out:
509
510    if (chmod(dest,0777 & (~UMask)) != 0 ||
511        chown(dest,pwd->pw_uid,pwd->pw_gid) != 0)
512    {
513       _log_err(LOG_DEBUG, "unable to change perms on directory %s",dest);
514       return PAM_PERM_DENIED;
515    }
516
517    return retval;
518 }
519
520 /* --- authentication management functions (only) --- */
521
522 PAM_EXTERN
523 int pam_sm_open_session(pam_handle_t * pamh, int flags, int argc
524                         ,const char **argv)
525 {
526    int retval, ctrl;
527    const void *user;
528    const struct passwd *pwd;
529    struct stat St;
530
531    /* Parse the flag values */
532    ctrl = _pam_parse(flags, argc, argv);
533
534    /* Determine the user name so we can get the home directory */
535    retval = pam_get_item(pamh, PAM_USER, &user);
536    if (retval != PAM_SUCCESS || user == NULL || *(const char *)user == '\0')
537    {
538       _log_err(LOG_NOTICE, "user unknown");
539       return PAM_USER_UNKNOWN;
540    }
541
542    /* Get the password entry */
543    pwd = _pammodutil_getpwnam (pamh, user);
544    if (pwd == NULL)
545    {
546       D(("couldn't identify user %s", user));
547       return PAM_CRED_INSUFFICIENT;
548    }
549
550    /* Stat the home directory, if something exists then we assume it is
551       correct and return a success*/
552    if (stat(pwd->pw_dir,&St) == 0)
553       return PAM_SUCCESS;
554
555    return create_homedir(pamh,ctrl,pwd,SkelDir,pwd->pw_dir);
556 }
557
558 /* Ignore */
559 PAM_EXTERN
560 int pam_sm_close_session(pam_handle_t * pamh, int flags, int argc
561                          ,const char **argv)
562 {
563    return PAM_SUCCESS;
564 }
565
566 #ifdef PAM_STATIC
567
568 /* static module data */
569 struct pam_module _pam_mkhomedir_modstruct =
570 {
571    "pam_mkhomedir",
572    NULL,
573    NULL,
574    NULL,
575    pam_sm_open_session,
576    pam_sm_close_session,
577    NULL,
578 };
579
580 #endif