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