]> 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 #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 #include <security/pam_ext.h>
58
59
60 /* argument parsing */
61 #define MKHOMEDIR_DEBUG      020        /* keep quiet about things */
62 #define MKHOMEDIR_QUIET      040        /* keep quiet about things */
63
64 static unsigned int UMask = 0022;
65 static char SkelDir[BUFSIZ] = "/etc/skel"; /* THIS MODULE IS NOT THREAD SAFE */
66
67 static int
68 _pam_parse (const pam_handle_t *pamh, int flags, int argc, const char **argv)
69 {
70    int ctrl = 0;
71
72    /* does the appliction require quiet? */
73    if ((flags & PAM_SILENT) == PAM_SILENT)
74       ctrl |= MKHOMEDIR_QUIET;
75
76    /* step through arguments */
77    for (; argc-- > 0; ++argv)
78    {
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';
86       } else {
87          pam_syslog(pamh,LOG_ERR, "unknown option; %s", *argv);
88       }
89    }
90
91    D(("ctrl = %o", ctrl));
92    return ctrl;
93 }
94
95 /* Ask the application to display a short text string for us. */
96 static int
97 make_remark (pam_handle_t *pamh, int ctrl, const char *remark)
98 {
99    int retval;
100
101    if ((ctrl & MKHOMEDIR_QUIET) != MKHOMEDIR_QUIET)
102    {
103      pam_info (pamh, "%s", remark);
104    }
105    else
106    {
107       D(("keeping quiet"));
108       retval = PAM_SUCCESS;
109    }
110
111    D(("returning %s", pam_strerror(pamh, retval)));
112    return retval;
113 }
114
115 static int
116 rec_mkdir (const char *dir, mode_t mode)
117 {
118   char *cp;
119   char *parent = strdup (dir);
120
121   if (parent == NULL)
122     return 1;
123
124   cp = strrchr (parent, '/');
125
126   if (cp != NULL)
127     {
128       struct stat st;
129
130       *cp++ = '\0';
131       if (stat (parent, &st) == -1 && errno == ENOENT)
132         if (rec_mkdir (parent, mode) != 0)
133           {
134             free (parent);
135             return 1;
136           }
137     }
138
139   free (parent);
140
141   if (mkdir (dir, mode) != 0 && errno != EEXIST)
142     return 1;
143
144   return 0;
145 }
146
147 /* Do the actual work of creating a home dir */
148 static int
149 create_homedir (pam_handle_t * pamh, int ctrl,
150                 const struct passwd *pwd,
151                 const char *source, const char *dest)
152 {
153    char remark[BUFSIZ];
154    DIR *D;
155    struct dirent *Dir;
156    int retval = PAM_AUTH_ERR;
157
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;
161
162    make_remark(pamh, ctrl, remark);
163
164    /* Create the new directory */
165    if (rec_mkdir (dest,0755) != 0)
166    {
167       pam_syslog(pamh,LOG_DEBUG, "unable to create directory %s",dest);
168       return PAM_PERM_DENIED;
169    }
170
171    /* See if we need to copy the skel dir over. */
172    if ((source == NULL) || (strlen(source) == 0))
173    {
174      retval = PAM_SUCCESS;
175      goto go_out;
176    }
177
178    /* Scan the directory */
179    D = opendir (source);
180    if (D == 0)
181    {
182       pam_syslog(pamh,LOG_DEBUG, "unable to read directory %s",source);
183       retval = PAM_PERM_DENIED;
184       goto go_out;
185    }
186
187    for (Dir = readdir(D); Dir != 0; Dir = readdir(D))
188    {
189       int SrcFd;
190       int DestFd;
191       int Res;
192       struct stat St;
193 #ifndef PATH_MAX
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);
198 #else
199       char newsource[PATH_MAX], newdest[PATH_MAX];
200 #endif
201
202       /* Skip some files.. */
203       if (strcmp(Dir->d_name,".") == 0 ||
204           strcmp(Dir->d_name,"..") == 0)
205          continue;
206
207       /* Determine what kind of file it is. */
208 #ifndef PATH_MAX
209       nslen = slen + strlen(Dir->d_name) + 2;
210
211       if (nslen <= 0)
212         {
213           retval = PAM_BUF_ERR;
214           goto go_out;
215         }
216
217       if ((newsource = malloc (nslen)) == NULL)
218         {
219           retval = PAM_BUF_ERR;
220           goto go_out;
221         }
222
223       sprintf(newsource, "%s/%s", source, Dir->d_name);
224 #else
225       snprintf(newsource,sizeof(newsource),"%s/%s",source,Dir->d_name);
226 #endif
227
228       if (lstat(newsource,&St) != 0)
229 #ifndef PATH_MAX
230       {
231               free(newsource);
232               newsource = NULL;
233          continue;
234       }
235 #else
236       continue;
237 #endif
238
239
240       /* We'll need the new file's name. */
241 #ifndef PATH_MAX
242       ndlen = dlen + strlen(Dir->d_name)+2;
243
244       if (ndlen <= 0)
245         {
246           retval = PAM_BUF_ERR;
247           goto go_out;
248         }
249
250       if ((newdest = malloc(ndlen)) == NULL)
251         {
252           free (newsource);
253           retval = PAM_BUF_ERR;
254           goto go_out;
255         }
256
257       sprintf (newdest, "%s/%s", dest, Dir->d_name);
258 #else
259       snprintf (newdest,sizeof (newdest),"%s/%s",dest,Dir->d_name);
260 #endif
261
262       /* If it's a directory, recurse. */
263       if (S_ISDIR(St.st_mode))
264       {
265         retval = create_homedir (pamh, ctrl, pwd, newsource, newdest);
266
267 #ifndef PATH_MAX
268          free(newsource); newsource = NULL;
269          free(newdest); newdest = NULL;
270 #endif
271
272          if (retval != PAM_SUCCESS)
273            {
274              closedir(D);
275              goto go_out;
276            }
277          continue;
278       }
279
280       /* If it's a symlink, create a new link. */
281       if (S_ISLNK(St.st_mode))
282       {
283          int pointedlen = 0;
284 #ifndef PATH_MAX
285          char *pointed = NULL;
286            {
287                    int size = 100;
288
289                    while (1) {
290                            pointed = (char *) malloc(size);
291                            if ( ! pointed ) {
292                                    free(newsource);
293                                    free(newdest);
294                                    return PAM_BUF_ERR;
295                            }
296                            pointedlen = readlink (newsource, pointed, size);
297                            if ( pointedlen < 0 ) break;
298                            if ( pointedlen < size ) break;
299                            free (pointed);
300                            size *= 2;
301                    }
302            }
303            if ( pointedlen < 0 )
304                    free(pointed);
305            else
306                    pointed[pointedlen] = 0;
307 #else
308          char pointed[PATH_MAX];
309          memset(pointed, 0, sizeof(pointed));
310
311          pointedlen = readlink(newsource, pointed, sizeof(pointed) - 1);
312 #endif
313
314          if ( pointedlen >= 0 ) {
315             if(symlink(pointed, newdest) == 0)
316             {
317                if (lchown(newdest,pwd->pw_uid,pwd->pw_gid) != 0)
318                {
319                    closedir(D);
320                    pam_syslog(pamh,LOG_DEBUG, "unable to change perms on link %s",
321                             newdest);
322 #ifndef PATH_MAX
323                    free(pointed);
324                    free(newsource);
325                    free(newdest);
326 #endif
327                    return PAM_PERM_DENIED;
328                }
329             }
330 #ifndef PATH_MAX
331             free(pointed);
332 #endif
333          }
334 #ifndef PATH_MAX
335          free(newsource); newsource = NULL;
336          free(newdest); newdest = NULL;
337 #endif
338          continue;
339       }
340
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))
344       {
345 #ifndef PATH_MAX
346          free(newsource); newsource = NULL;
347          free(newdest); newdest = NULL;
348 #endif
349          continue;
350       }
351
352       /* Open the source file */
353       if ((SrcFd = open(newsource,O_RDONLY)) < 0 || fstat(SrcFd,&St) != 0)
354       {
355          closedir(D);
356          pam_syslog(pamh,LOG_DEBUG, "unable to open src file %s",newsource);
357
358 #ifndef PATH_MAX
359          free(newsource); newsource = NULL;
360          free(newdest); newdest = NULL;
361 #endif
362
363          return PAM_PERM_DENIED;
364       }
365       stat(newsource,&St);
366
367       /* Open the dest file */
368       if ((DestFd = open(newdest,O_WRONLY | O_TRUNC | O_CREAT,0600)) < 0)
369       {
370          close(SrcFd);
371          closedir(D);
372          pam_syslog(pamh,LOG_DEBUG, "unable to open dest file %s",newdest);
373
374 #ifndef PATH_MAX
375          free(newsource); newsource = NULL;
376          free(newdest); newdest = NULL;
377 #endif
378          return PAM_PERM_DENIED;
379       }
380
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
383          execute bits */
384       if (fchmod(DestFd,(St.st_mode | 0222) & (~UMask)) != 0 ||
385           fchown(DestFd,pwd->pw_uid,pwd->pw_gid) != 0)
386       {
387          close(SrcFd);
388          close(DestFd);
389          closedir(D);
390          pam_syslog(pamh,LOG_DEBUG, "unable to chang perms on copy %s",newdest);
391
392 #ifndef PATH_MAX
393          free(newsource); newsource = NULL;
394          free(newdest); newdest = NULL;
395 #endif
396
397          return PAM_PERM_DENIED;
398       }
399
400       /* Copy the file */
401       do
402       {
403          Res = _pammodutil_read(SrcFd,remark,sizeof(remark));
404
405          if (Res == 0)
406              continue;
407
408          if (Res > 0) {
409              if (_pammodutil_write(DestFd,remark,Res) == Res)
410                 continue;
411          }
412
413          /* If we get here, pammodutil_read returned a -1 or
414             _pammodutil_write returned something unexpected. */
415          close(SrcFd);
416          close(DestFd);
417          closedir(D);
418          pam_syslog(pamh,LOG_DEBUG, "unable to perform IO");
419
420 #ifndef PATH_MAX
421          free(newsource); newsource = NULL;
422          free(newdest); newdest = NULL;
423 #endif
424
425          return PAM_PERM_DENIED;
426       }
427       while (Res != 0);
428       close(SrcFd);
429       close(DestFd);
430
431 #ifndef PATH_MAX
432          free(newsource); newsource = NULL;
433          free(newdest); newdest = NULL;
434 #endif
435
436    }
437    closedir(D);
438
439    retval = PAM_SUCCESS;
440
441  go_out:
442
443    if (chmod(dest,0777 & (~UMask)) != 0 ||
444        chown(dest,pwd->pw_uid,pwd->pw_gid) != 0)
445    {
446       pam_syslog(pamh,LOG_DEBUG, "unable to change perms on directory %s",dest);
447       return PAM_PERM_DENIED;
448    }
449
450    return retval;
451 }
452
453 /* --- authentication management functions (only) --- */
454
455 PAM_EXTERN int
456 pam_sm_open_session (pam_handle_t *pamh, int flags, int argc,
457                      const char **argv)
458 {
459    int retval, ctrl;
460    const void *user;
461    const struct passwd *pwd;
462    struct stat St;
463
464    /* Parse the flag values */
465    ctrl = _pam_parse(pamh, flags, argc, argv);
466
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')
470    {
471       pam_syslog(pamh,LOG_NOTICE, "user unknown");
472       return PAM_USER_UNKNOWN;
473    }
474
475    /* Get the password entry */
476    pwd = _pammodutil_getpwnam (pamh, user);
477    if (pwd == NULL)
478    {
479       D(("couldn't identify user %s", user));
480       return PAM_CRED_INSUFFICIENT;
481    }
482
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)
486       return PAM_SUCCESS;
487
488    return create_homedir(pamh,ctrl,pwd,SkelDir,pwd->pw_dir);
489 }
490
491 /* Ignore */
492 PAM_EXTERN
493 int pam_sm_close_session (pam_handle_t * pamh UNUSED, int flags UNUSED,
494                           int argc UNUSED, const char **argv UNUSED)
495 {
496    return PAM_SUCCESS;
497 }
498
499 #ifdef PAM_STATIC
500
501 /* static module data */
502 struct pam_module _pam_mkhomedir_modstruct =
503 {
504    "pam_mkhomedir",
505    NULL,
506    NULL,
507    NULL,
508    pam_sm_open_session,
509    pam_sm_close_session,
510    NULL,
511 };
512
513 #endif