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