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