]> granicus.if.org Git - linux-pam/blob - modules/pam_mkhomedir/pam_mkhomedir.c
Relevant BUGIDs:
[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 <stdlib.h>
39 #include <stdio.h>
40 #include <string.h>
41 #include <dirent.h>
42
43 /*
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.
48  */
49
50 #define PAM_SM_SESSION
51
52 #include <security/pam_modules.h>
53 #include <security/_pam_macros.h>
54 #include <security/_pam_modutil.h>
55
56
57 /* argument parsing */
58 #define MKHOMEDIR_DEBUG      020        /* keep quiet about things */
59 #define MKHOMEDIR_QUIET      040        /* keep quiet about things */
60
61 static unsigned int UMask = 0022;
62 static char SkelDir[BUFSIZ] = "/etc/skel"; /* THIS MODULE IS NOT THREAD SAFE */
63
64 /* some syslogging */
65 static void _log_err(int err, const char *format, ...)
66 {
67     va_list args;
68
69     va_start(args, format);
70     openlog("PAM-mkhomedir", LOG_CONS|LOG_PID, LOG_AUTH);
71     vsyslog(err, format, args);
72     va_end(args);
73     closelog();
74 }
75
76 static int _pam_parse(int flags, int argc, const char **argv)
77 {
78    int ctrl = 0;
79
80    /* does the appliction require quiet? */
81    if ((flags & PAM_SILENT) == PAM_SILENT)
82       ctrl |= MKHOMEDIR_QUIET;
83
84    /* step through arguments */
85    for (; argc-- > 0; ++argv)
86    {
87       if (!strcmp(*argv, "silent")) {
88          ctrl |= MKHOMEDIR_QUIET;
89       } else if (!strncmp(*argv,"umask=",6)) {
90          UMask = strtol(*argv+6,0,0);
91       } else if (!strncmp(*argv,"skel=",5)) {
92          strncpy(SkelDir,*argv+5,sizeof(SkelDir));
93          SkelDir[sizeof(SkelDir)-1] = '\0';
94       } else {
95          _log_err(LOG_ERR, "unknown option; %s", *argv);
96       }
97    }
98
99    D(("ctrl = %o", ctrl));
100    return ctrl;
101 }
102
103 /* This common function is used to send a message to the applications
104    conversion function. Our only use is to ask the application to print
105    an informative message that we are creating a home directory */
106 static int converse(pam_handle_t * pamh, int ctrl, int nargs
107                     ,struct pam_message **message
108                     ,struct pam_response **response)
109 {
110    int retval;
111    struct pam_conv *conv;
112
113    D(("begin to converse"));
114
115    retval = pam_get_item(pamh, PAM_CONV, (const void **) &conv);
116    if (retval == PAM_SUCCESS && conv)
117    {
118
119       retval = conv->conv(nargs, (const struct pam_message **) message
120                           ,response, conv->appdata_ptr);
121
122       D(("returned from application's conversation function"));
123
124       if (retval != PAM_SUCCESS && (ctrl & MKHOMEDIR_DEBUG))
125       {
126          _log_err(LOG_DEBUG, "conversation failure [%s]"
127                   ,pam_strerror(pamh, retval));
128       }
129
130    }
131    else
132    {
133       _log_err(LOG_ERR, "couldn't obtain coversation function [%s]"
134                ,pam_strerror(pamh, retval));
135      if (retval == PAM_SUCCESS)
136          retval = PAM_BAD_ITEM; /* conv was NULL */
137    }
138
139    D(("ready to return from module conversation"));
140
141    return retval;               /* propagate error status */
142 }
143
144 /* Ask the application to display a short text string for us. */
145 static int make_remark(pam_handle_t * pamh, int ctrl, const char *remark)
146 {
147    int retval;
148
149    if ((ctrl & MKHOMEDIR_QUIET) != MKHOMEDIR_QUIET)
150    {
151       struct pam_message msg[1], *mesg[1];
152       struct pam_response *resp = NULL;
153
154       mesg[0] = &msg[0];
155       msg[0].msg_style = PAM_TEXT_INFO;
156       msg[0].msg = remark;
157
158       retval = converse(pamh, ctrl, 1, mesg, &resp);
159
160       msg[0].msg = NULL;
161       if (resp)
162       {
163          _pam_drop_reply(resp, 1);
164       }
165    }
166    else
167    {
168       D(("keeping quiet"));
169       retval = PAM_SUCCESS;
170    }
171
172    D(("returning %s", pam_strerror(pamh, retval)));
173    return retval;
174 }
175
176 /* Do the actual work of creating a home dir */
177 static int create_homedir(pam_handle_t * pamh, int ctrl,
178                           const struct passwd *pwd,
179                           const char *source, const char *dest)
180 {
181    char remark[BUFSIZ];
182    DIR *D;
183    struct dirent *Dir;
184
185    /* Mention what is happening, if the notification fails that is OK */
186    if (snprintf(remark,sizeof(remark),"Creating directory '%s'.", dest) == -1)
187       return PAM_PERM_DENIED;
188
189    make_remark(pamh, ctrl, remark);
190
191    /* Create the new directory */
192    if (mkdir(dest,0700) != 0)
193    {
194       _log_err(LOG_DEBUG, "unable to create directory %s",dest);
195       return PAM_PERM_DENIED;
196    }
197    if (chmod(dest,0777 & (~UMask)) != 0 ||
198        chown(dest,pwd->pw_uid,pwd->pw_gid) != 0)
199    {
200       _log_err(LOG_DEBUG, "unable to change perms on directory %s",dest);
201       return PAM_PERM_DENIED;
202    }
203
204    /* See if we need to copy the skel dir over. */
205    if ((source == NULL) || (strlen(source) == 0))
206    {
207       return PAM_SUCCESS;
208    }
209
210    /* Scan the directory */
211    D = opendir(source);
212    if (D == 0)
213    {
214       _log_err(LOG_DEBUG, "unable to read directory %s",source);
215       return PAM_PERM_DENIED;
216    }
217
218    for (Dir = readdir(D); Dir != 0; Dir = readdir(D))
219    {
220       int SrcFd;
221       int DestFd;
222       int Res;
223       struct stat St;
224       char newsource[PATH_MAX], newdest[PATH_MAX];
225
226       /* Skip some files.. */
227       if (strcmp(Dir->d_name,".") == 0 ||
228           strcmp(Dir->d_name,"..") == 0)
229          continue;
230
231       /* Determine what kind of file it is. */
232       snprintf(newsource,sizeof(newsource),"%s/%s",source,Dir->d_name);
233       if (lstat(newsource,&St) != 0)
234          continue;
235
236       /* We'll need the new file's name. */
237       snprintf(newdest,sizeof(newdest),"%s/%s",dest,Dir->d_name);
238
239       /* If it's a directory, recurse. */
240       if (S_ISDIR(St.st_mode))
241       {
242          int retval = create_homedir(pamh, ctrl, pwd, newsource, newdest);
243          if (retval != PAM_SUCCESS) {
244             closedir(D);
245             return retval;
246          }
247          continue;
248       }
249
250       /* If it's a symlink, create a new link. */
251       if (S_ISLNK(St.st_mode))
252       {
253          char pointed[PATH_MAX];
254          memset(pointed, 0, sizeof(pointed));
255          if(readlink(newsource, pointed, sizeof(pointed) - 1) != -1)
256          {
257             if(symlink(pointed, newdest) == 0)
258             {
259                if (lchown(newdest,pwd->pw_uid,pwd->pw_gid) != 0)
260                {
261                    closedir(D);
262                    _log_err(LOG_DEBUG, "unable to chang perms on link %s",
263                             newdest);
264                    return PAM_PERM_DENIED;
265                }
266             }
267          }
268          continue;
269       }
270
271       /* If it's not a regular file, it's probably not a good idea to create
272        * the new device node, FIFO, or whatever it is. */
273       if (!S_ISREG(St.st_mode))
274       {
275          continue;
276       }
277
278       /* Open the source file */
279       if ((SrcFd = open(newsource,O_RDONLY)) < 0 || fstat(SrcFd,&St) != 0)
280       {
281          closedir(D);
282          _log_err(LOG_DEBUG, "unable to open src file %s",newsource);
283          return PAM_PERM_DENIED;
284       }
285       stat(newsource,&St);
286
287       /* Open the dest file */
288       if ((DestFd = open(newdest,O_WRONLY | O_TRUNC | O_CREAT,0600)) < 0)
289       {
290          close(SrcFd);
291          closedir(D);
292          _log_err(LOG_DEBUG, "unable to open dest file %s",newdest);
293          return PAM_PERM_DENIED;
294       }
295
296       /* Set the proper ownership and permissions for the module. We make
297          the file a+w and then mask it with the set mask. This preseves
298          execute bits */
299       if (fchmod(DestFd,(St.st_mode | 0222) & (~UMask)) != 0 ||
300           fchown(DestFd,pwd->pw_uid,pwd->pw_gid) != 0)
301       {
302          close(SrcFd);
303          close(DestFd);
304          closedir(D);
305          _log_err(LOG_DEBUG, "unable to chang perms on copy %s",newdest);
306          return PAM_PERM_DENIED;
307       }
308
309       /* Copy the file */
310       do
311       {
312          Res = _pammodutil_read(SrcFd,remark,sizeof(remark));
313
314          if (Res == 0)
315              continue;
316
317          if (Res > 0) {
318              if (_pammodutil_write(DestFd,remark,Res) == Res)
319                 continue;
320          }
321
322          /* If we get here, pammodutil_read returned a -1 or
323             _pammodutil_write returned something unexpected. */
324          close(SrcFd);
325          close(DestFd);
326          closedir(D);
327          _log_err(LOG_DEBUG, "unable to perform IO");
328          return PAM_PERM_DENIED;
329       }
330       while (Res != 0);
331       close(SrcFd);
332       close(DestFd);
333    }
334    closedir(D);
335
336    return PAM_SUCCESS;
337 }
338
339 /* --- authentication management functions (only) --- */
340
341 PAM_EXTERN
342 int pam_sm_open_session(pam_handle_t * pamh, int flags, int argc
343                         ,const char **argv)
344 {
345    int retval, ctrl;
346    const char *user;
347    const struct passwd *pwd;
348    struct stat St;
349
350    /* Parse the flag values */
351    ctrl = _pam_parse(flags, argc, argv);
352
353    /* Determine the user name so we can get the home directory */
354    retval = pam_get_item(pamh, PAM_USER, (const void **) &user);
355    if (retval != PAM_SUCCESS || user == NULL || *user == '\0')
356    {
357       _log_err(LOG_NOTICE, "user unknown");
358       return PAM_USER_UNKNOWN;
359    }
360
361    /* Get the password entry */
362    pwd = _pammodutil_getpwnam (pamh, user);
363    if (pwd == NULL)
364    {
365       D(("couldn't identify user %s", user));
366       return PAM_CRED_INSUFFICIENT;
367    }
368
369    /* Stat the home directory, if something exists then we assume it is
370       correct and return a success*/
371    if (stat(pwd->pw_dir,&St) == 0)
372       return PAM_SUCCESS;
373
374    return create_homedir(pamh,ctrl,pwd,SkelDir,pwd->pw_dir);
375 }
376
377 /* Ignore */
378 PAM_EXTERN
379 int pam_sm_close_session(pam_handle_t * pamh, int flags, int argc
380                          ,const char **argv)
381 {
382    return PAM_SUCCESS;
383 }
384
385 #ifdef PAM_STATIC
386
387 /* static module data */
388 struct pam_module _pam_mkhomedir_modstruct =
389 {
390    "pam_mkhomedir",
391    NULL,
392    NULL,
393    NULL,
394    pam_sm_open_session,
395    pam_sm_close_session,
396    NULL,
397 };
398
399 #endif