]> granicus.if.org Git - linux-pam/blob - modules/pam_mkhomedir/mkhomedir_helper.c
Relevant BUGIDs:
[linux-pam] / modules / pam_mkhomedir / mkhomedir_helper.c
1 /* mkhomedir_helper - helper for pam_mkhomedir module
2
3    Released under the GNU LGPL version 2 or later
4
5    Copyright (c) Red Hat, Inc., 2009
6    Originally written by Jason Gunthorpe <jgg@debian.org> Feb 1999
7    Structure taken from pam_lastlogin by Andrew Morgan
8      <morgan@parc.power.net> 1996
9  */
10
11 #include "config.h"
12
13 #include <stdarg.h>
14 #include <sys/types.h>
15 #include <sys/stat.h>
16 #include <fcntl.h>
17 #include <unistd.h>
18 #include <pwd.h>
19 #include <errno.h>
20 #include <stdlib.h>
21 #include <stdio.h>
22 #include <string.h>
23 #include <dirent.h>
24 #include <syslog.h>
25
26 #include <security/pam_ext.h>
27 #include <security/pam_modutil.h>
28
29 static unsigned long u_mask = 0022;
30 static char skeldir[BUFSIZ] = "/etc/skel";
31
32 static int
33 rec_mkdir(const char *dir, mode_t mode)
34 {
35   char *cp;
36   char *parent = strdup(dir);
37
38   if (parent == NULL)
39     return 1;
40
41   cp = strrchr(parent, '/');
42
43   if (cp != NULL && cp != parent)
44     {
45       struct stat st;
46
47       *cp++ = '\0';
48       if (stat(parent, &st) == -1 && errno == ENOENT)
49         if (rec_mkdir(parent, 0755) != 0)
50           {
51             free(parent);
52             return 1;
53           }
54     }
55
56   free(parent);
57
58   if (mkdir(dir, mode) != 0 && errno != EEXIST)
59     return 1;
60
61   return 0;
62 }
63
64 /* Do the actual work of creating a home dir */
65 static int
66 create_homedir(const struct passwd *pwd,
67                const char *source, const char *dest)
68 {
69    char remark[BUFSIZ];
70    DIR *d;
71    struct dirent *dent;
72    int retval = PAM_SESSION_ERR;
73
74    /* Create the new directory */
75    if (rec_mkdir(dest, 0700) != 0)
76    {
77       pam_syslog(NULL, LOG_ERR, "unable to create directory %s: %m", dest);
78       return PAM_PERM_DENIED;
79    }
80
81    /* See if we need to copy the skel dir over. */
82    if ((source == NULL) || (strlen(source) == 0))
83    {
84       retval = PAM_SUCCESS;
85       goto go_out;
86    }
87
88    /* Scan the directory */
89    d = opendir(source);
90    if (d == NULL)
91    {
92       pam_syslog(NULL, LOG_DEBUG, "unable to read directory %s: %m", source);
93       retval = PAM_PERM_DENIED;
94       goto go_out;
95    }
96
97    for (dent = readdir(d); dent != NULL; dent = readdir(d))
98    {
99       int srcfd;
100       int destfd;
101       int res;
102       struct stat st;
103 #ifndef PATH_MAX
104       char *newsource = NULL, *newdest = NULL;
105       /* track length of buffers */
106       int nslen = 0, ndlen = 0;
107       int slen = strlen(source), dlen = strlen(dest);
108 #else
109       char newsource[PATH_MAX], newdest[PATH_MAX];
110 #endif
111
112       /* Skip some files.. */
113       if (strcmp(dent->d_name,".") == 0 ||
114           strcmp(dent->d_name,"..") == 0)
115          continue;
116
117       /* Determine what kind of file it is. */
118 #ifndef PATH_MAX
119       nslen = slen + strlen(dent->d_name) + 2;
120
121       if (nslen <= 0)
122         {
123           retval = PAM_BUF_ERR;
124           goto go_out;
125         }
126
127       if ((newsource = malloc(nslen)) == NULL)
128         {
129           retval = PAM_BUF_ERR;
130           goto go_out;
131         }
132
133       sprintf(newsource, "%s/%s", source, dent->d_name);
134 #else
135       snprintf(newsource, sizeof(newsource), "%s/%s", source, dent->d_name);
136 #endif
137
138       if (lstat(newsource, &st) != 0)
139 #ifndef PATH_MAX
140       {
141               free(newsource);
142               newsource = NULL;
143          continue;
144       }
145 #else
146       continue;
147 #endif
148
149
150       /* We'll need the new file's name. */
151 #ifndef PATH_MAX
152       ndlen = dlen + strlen(dent->d_name)+2;
153
154       if (ndlen <= 0)
155         {
156           retval = PAM_BUF_ERR;
157           goto go_out;
158         }
159
160       if ((newdest = malloc(ndlen)) == NULL)
161         {
162           free (newsource);
163           retval = PAM_BUF_ERR;
164           goto go_out;
165         }
166
167       sprintf (newdest, "%s/%s", dest, dent->d_name);
168 #else
169       snprintf (newdest, sizeof (newdest), "%s/%s", dest, dent->d_name);
170 #endif
171
172       /* If it's a directory, recurse. */
173       if (S_ISDIR(st.st_mode))
174       {
175          retval = create_homedir(pwd, newsource, newdest);
176
177 #ifndef PATH_MAX
178          free(newsource); newsource = NULL;
179          free(newdest); newdest = NULL;
180 #endif
181
182          if (retval != PAM_SUCCESS)
183            {
184              closedir(d);
185              goto go_out;
186            }
187          continue;
188       }
189
190       /* If it's a symlink, create a new link. */
191       if (S_ISLNK(st.st_mode))
192       {
193          int pointedlen = 0;
194 #ifndef PATH_MAX
195          char *pointed = NULL;
196            {
197                    int size = 100;
198
199                    while (1) {
200                            pointed = malloc(size);
201                            if (pointed == NULL) {
202                                    free(newsource);
203                                    free(newdest);
204                                    return PAM_BUF_ERR;
205                            }
206                            pointedlen = readlink(newsource, pointed, size);
207                            if (pointedlen < 0) break;
208                            if (pointedlen < size) break;
209                            free(pointed);
210                            size *= 2;
211                    }
212            }
213            if (pointedlen < 0)
214                    free(pointed);
215            else
216                    pointed[pointedlen] = 0;
217 #else
218          char pointed[PATH_MAX];
219          memset(pointed, 0, sizeof(pointed));
220
221          pointedlen = readlink(newsource, pointed, sizeof(pointed) - 1);
222 #endif
223
224          if (pointedlen >= 0) {
225             if(symlink(pointed, newdest) == 0)
226             {
227                if (lchown(newdest, pwd->pw_uid, pwd->pw_gid) != 0)
228                {
229                    pam_syslog(NULL, LOG_DEBUG,
230                               "unable to change perms on link %s: %m", newdest);
231                    closedir(d);
232 #ifndef PATH_MAX
233                    free(pointed);
234                    free(newsource);
235                    free(newdest);
236 #endif
237                    return PAM_PERM_DENIED;
238                }
239             }
240 #ifndef PATH_MAX
241             free(pointed);
242 #endif
243          }
244 #ifndef PATH_MAX
245          free(newsource); newsource = NULL;
246          free(newdest); newdest = NULL;
247 #endif
248          continue;
249       }
250
251       /* If it's not a regular file, it's probably not a good idea to create
252        * the new device node, FIFO, or whatever it is. */
253       if (!S_ISREG(st.st_mode))
254       {
255 #ifndef PATH_MAX
256          free(newsource); newsource = NULL;
257          free(newdest); newdest = NULL;
258 #endif
259          continue;
260       }
261
262       /* Open the source file */
263       if ((srcfd = open(newsource, O_RDONLY)) < 0 || fstat(srcfd, &st) != 0)
264       {
265          pam_syslog(NULL, LOG_DEBUG,
266                     "unable to open src file %s: %m", newsource);
267          closedir(d);
268
269 #ifndef PATH_MAX
270          free(newsource); newsource = NULL;
271          free(newdest); newdest = NULL;
272 #endif
273
274          return PAM_PERM_DENIED;
275       }
276       if (stat(newsource, &st) != 0)
277         {
278           pam_syslog(NULL, LOG_DEBUG, "unable to stat src file %s: %m",
279                      newsource);
280           close(srcfd);
281           closedir(d);
282
283 #ifndef PATH_MAX
284           free(newsource); newsource = NULL;
285           free(newdest); newdest = NULL;
286 #endif
287
288           return PAM_PERM_DENIED;
289         }
290
291       /* Open the dest file */
292       if ((destfd = open(newdest, O_WRONLY | O_TRUNC | O_CREAT, 0600)) < 0)
293       {
294          pam_syslog(NULL, LOG_DEBUG,
295                     "unable to open dest file %s: %m", newdest);
296          close(srcfd);
297          closedir(d);
298
299 #ifndef PATH_MAX
300          free(newsource); newsource = NULL;
301          free(newdest); newdest = NULL;
302 #endif
303          return PAM_PERM_DENIED;
304       }
305
306       /* Set the proper ownership and permissions for the module. We make
307          the file a+w and then mask it with the set mask. This preseves
308          execute bits */
309       if (fchmod(destfd, (st.st_mode | 0222) & (~u_mask)) != 0 ||
310           fchown(destfd, pwd->pw_uid, pwd->pw_gid) != 0)
311       {
312          pam_syslog(NULL, LOG_DEBUG,
313                     "unable to change perms on copy %s: %m", newdest);
314          close(srcfd);
315          close(destfd);
316          closedir(d);
317
318 #ifndef PATH_MAX
319          free(newsource); newsource = NULL;
320          free(newdest); newdest = NULL;
321 #endif
322
323          return PAM_PERM_DENIED;
324       }
325
326       /* Copy the file */
327       do
328       {
329          res = pam_modutil_read(srcfd, remark, sizeof(remark));
330
331          if (res == 0)
332              continue;
333
334          if (res > 0) {
335              if (pam_modutil_write(destfd, remark, res) == res)
336                 continue;
337          }
338
339          /* If we get here, pam_modutil_read returned a -1 or
340             pam_modutil_write returned something unexpected. */
341          pam_syslog(NULL, LOG_DEBUG, "unable to perform IO: %m");
342          close(srcfd);
343          close(destfd);
344          closedir(d);
345
346 #ifndef PATH_MAX
347          free(newsource); newsource = NULL;
348          free(newdest); newdest = NULL;
349 #endif
350
351          return PAM_PERM_DENIED;
352       }
353       while (res != 0);
354       close(srcfd);
355       close(destfd);
356
357 #ifndef PATH_MAX
358       free(newsource); newsource = NULL;
359       free(newdest); newdest = NULL;
360 #endif
361
362    }
363    closedir(d);
364
365    retval = PAM_SUCCESS;
366
367  go_out:
368
369    if (chmod(dest, 0777 & (~u_mask)) != 0 ||
370        chown(dest, pwd->pw_uid, pwd->pw_gid) != 0)
371    {
372       pam_syslog(NULL, LOG_DEBUG,
373                  "unable to change perms on directory %s: %m", dest);
374       return PAM_PERM_DENIED;
375    }
376
377    return retval;
378 }
379
380 int
381 main(int argc, char *argv[])
382 {
383    const struct passwd *pwd;
384    struct stat st;
385
386    if (argc < 2) {
387         fprintf(stderr, "Usage: %s <username> [<umask> [<skeldir>]]\n", argv[0]);
388         return PAM_SESSION_ERR;
389    }
390
391    pwd = getpwnam(argv[1]);
392    if (pwd == NULL) {
393         pam_syslog(NULL, LOG_ERR, "User unknown.");
394         return PAM_CRED_INSUFFICIENT;
395    }
396
397    if (argc >= 3) {
398         char *eptr;
399         errno = 0;
400         u_mask = strtoul(argv[2], &eptr, 0);
401         if (errno != 0 || *eptr != '\0') {
402                 pam_syslog(NULL, LOG_ERR, "Bogus umask value %s", argv[2]);
403                 return PAM_SESSION_ERR;
404         }
405    }
406
407    if (argc >= 4) {
408         if (strlen(argv[3]) >= sizeof(skeldir)) {
409                 pam_syslog(NULL, LOG_ERR, "Too long skeldir path.");
410                 return PAM_SESSION_ERR;
411         }
412         strcpy(skeldir, argv[3]);
413    }
414
415    /* Stat the home directory, if something exists then we assume it is
416       correct and return a success */
417    if (stat(pwd->pw_dir, &st) == 0)
418         return PAM_SUCCESS;
419
420    return create_homedir(pwd, skeldir, pwd->pw_dir);
421 }