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