4 * Copyright (c) 1997-2009 Erez Zadok
5 * Copyright (c) 1989 Jan-Simon Pendry
6 * Copyright (c) 1989 Imperial College of Science, Technology & Medicine
7 * Copyright (c) 1989 The Regents of the University of California.
10 * This code is derived from software contributed to Berkeley by
11 * Jan-Simon Pendry at Imperial College, London.
13 * Redistribution and use in source and binary forms, with or without
14 * modification, are permitted provided that the following conditions
16 * 1. Redistributions of source code must retain the above copyright
17 * notice, this list of conditions and the following disclaimer.
18 * 2. Redistributions in binary form must reproduce the above copyright
19 * notice, this list of conditions and the following disclaimer in the
20 * documentation and/or other materials provided with the distribution.
21 * 3. All advertising materials mentioning features or use of this software
22 * must display the following acknowledgment:
23 * This product includes software developed by the University of
24 * California, Berkeley and its contributors.
25 * 4. Neither the name of the University nor the names of its contributors
26 * may be used to endorse or promote products derived from this software
27 * without specific prior written permission.
29 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
30 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
31 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
32 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
33 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
34 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
35 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
36 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
37 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
38 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
42 * File: am-utils/hlfsd/homedir.c
44 * HLFSD was written at Columbia University Computer Science Department, by
45 * Erez Zadok <ezk@cs.columbia.edu> and Alexander Dupuy <dupuy@cs.columbia.edu>
46 * It is being distributed under the same terms and conditions as amd does.
51 #endif /* HAVE_CONFIG_H */
57 * STATIC VARIABLES AND FUNCTIONS:
59 static FILE *passwd_fp
= NULL
;
60 static char pw_name
[16], pw_dir
[128];
61 static int cur_pwtab_num
= 0, max_pwtab_num
= 0;
62 static int hlfsd_diskspace(char *);
63 static int hlfsd_stat(char *, struct stat
*);
64 static int passwd_line
= 0;
65 static int plt_reset(void);
66 static struct passwd passwd_ent
;
67 static uid2home_t
*lastchild
;
68 static uid2home_t
*pwtab
;
69 static void delay(uid2home_t
*, int);
70 static void table_add(u_int
, const char *, const char *);
71 static char mboxfile
[MAXPATHLEN
];
72 static char *root_home
; /* root's home directory */
74 /* GLOBAL FUNCTIONS */
75 char *homeof(char *username
);
76 int uidof(char *username
);
78 /* GLOBALS VARIABLES */
79 username2uid_t
*untab
; /* user name table */
82 * Return the home directory pathname for the user with uid "userid".
85 homedir(int userid
, int groupid
)
87 static char linkval
[MAXPATHLEN
+ 1];
88 static struct timeval tp
;
92 int old_groupid
, old_userid
;
94 if ((found
= plt_search(userid
)) == (uid2home_t
*) NULL
) {
95 return alt_spooldir
; /* use alt spool for unknown uid */
97 homename
= found
->home
;
99 if (homename
[0] != '/' || homename
[1] == '\0') {
100 found
->last_status
= 1;
101 return alt_spooldir
; /* use alt spool for / or rel. home */
103 if ((int) userid
== 0) /* force all uid 0 to use root's home */
104 xsnprintf(linkval
, sizeof(linkval
), "%s/%s", root_home
, home_subdir
);
106 xsnprintf(linkval
, sizeof(linkval
), "%s/%s", homename
, home_subdir
);
109 found
->last_status
= 0;
114 * To optimize hlfsd, we don't actually check the validity of the
115 * symlink if it has been checked in the last N seconds. It is
116 * very likely that the link, machine, and filesystem are still
117 * valid, as long as N is small. But if N is large, that may not be
118 * true. That's why the default N is 5 minutes, but we allow the
119 * user to override this value via a command line option. Note that
120 * we do not update the last_access_time each time it is accessed,
121 * but only once every N seconds.
123 if (gettimeofday(&tp
, (struct timezone
*) NULL
) < 0) {
126 if ((tp
.tv_sec
- found
->last_access_time
) < cache_interval
) {
127 if (found
->last_status
== 0) {
133 found
->last_access_time
= tp
.tv_sec
;
138 * Only run this forking code if ask for -D fork (default).
139 * Disable forking using -D nofork.
141 if (amuDebug(D_FORK
)) {
142 /* fork child to process request if none in progress */
143 if (found
->child
&& kill(found
->child
, 0))
147 delay(found
, 5); /* wait a bit if in progress */
148 if (found
->child
) { /* better safe than sorry - maybe */
149 found
->last_status
= 1;
152 if ((found
->child
= fork()) < 0) {
153 found
->last_status
= 1;
156 if (found
->child
) { /* PARENT */
158 dlog("cache spill uid = %ld, pid = %ld, home = %s",
159 (long) lastchild
->uid
, (long) lastchild
->child
,
162 return (char *) NULL
; /* return NULL to parent, so it can continue */
167 * CHILD: (or parent if -D fork)
169 * Check and create dir if needed.
170 * Check disk space and/or quotas too.
172 * We don't need to set the _last_status field of found after the fork
173 * in the child, b/c that information would be later determined in
174 * nfsproc_readlink_2() and the correct exit status would be returned
175 * to the parent upon SIGCHLD in interlock().
178 am_set_mypid(); /* for logging routines */
179 if ((old_groupid
= setgid(groupid
)) < 0) {
180 plog(XLOG_WARNING
, "could not setgid to %d: %m", groupid
);
183 if ((old_userid
= seteuid(userid
)) < 0) {
184 plog(XLOG_WARNING
, "could not seteuid to %d: %m", userid
);
188 if (hlfsd_stat(linkval
, &homestat
) < 0) {
189 if (errno
== ENOENT
) { /* make the spool dir if possible */
190 /* don't use recursive mkdirs here */
191 if (mkdir(linkval
, PERS_SPOOLMODE
) < 0) {
194 plog(XLOG_WARNING
, "can't make directory %s: %m", linkval
);
197 /* fall through to testing the disk space / quota */
198 } else { /* the home dir itself must not exist then */
201 plog(XLOG_WARNING
, "bad link to %s: %m", linkval
);
207 * If gets here, then either the spool dir in the home dir exists,
208 * or it was just created. In either case, we now need to
209 * test if we can create a small file and write at least one
210 * byte into it. This will test that we have both enough inodes
211 * and disk blocks to spare, or they fall within the user's quotas too.
212 * We are still seteuid to the user at this point.
214 if (hlfsd_diskspace(linkval
) < 0) {
217 plog(XLOG_WARNING
, "no more space in %s: %m", linkval
);
228 hlfsd_diskspace(char *path
)
230 char buf
[MAXPATHLEN
];
233 xsnprintf(buf
, sizeof(buf
), "%s/._hlfstmp_%lu", path
, (long) getpid());
234 if ((fd
= open(buf
, O_RDWR
| O_CREAT
, 0600)) < 0) {
235 plog(XLOG_ERROR
, "cannot open %s: %m", buf
);
239 if (write(fd
, buf
, len
) < len
) {
240 plog(XLOG_ERROR
, "cannot write \"%s\" (%d bytes) to %s : %m", buf
, len
, buf
);
242 unlink(buf
); /* cleanup just in case */
245 if (unlink(buf
) < 0) {
246 plog(XLOG_ERROR
, "cannot unlink %s : %m", buf
);
254 hlfsd_stat(char *path
, struct stat
*statp
)
256 if (stat(path
, statp
) < 0)
258 else if (!S_ISDIR(statp
->st_mode
)) {
267 delay(uid2home_t
*found
, int secs
)
272 dlog("delaying on child %ld for %d seconds", (long) found
->child
, secs
);
278 if (select(0, NULL
, NULL
, NULL
, &tv
) == 0)
280 } while (--secs
&& found
->child
);
285 * This function is called when a child has terminated after
286 * servicing an nfs request. We need to check the exit status and
287 * update the last_status field of the requesting user.
290 interlock(int signum
)
293 uid2home_t
*lostchild
;
297 while ((child
= waitpid((pid_t
) -1, &status
, WNOHANG
)) > 0) {
298 #else /* not HAVE_WAITPID */
299 while ((child
= wait3(&status
, WNOHANG
, (struct rusage
*) NULL
)) > 0) {
300 #endif /* not HAVE_WAITPID */
302 /* high chances this was the last child forked */
303 if (lastchild
&& lastchild
->child
== child
) {
304 lastchild
->child
= 0;
306 if (WIFEXITED(status
))
307 lastchild
->last_status
= WEXITSTATUS(status
);
308 lastchild
= (uid2home_t
*) NULL
;
310 /* and if not, we have to search for it... */
311 for (lostchild
= pwtab
; lostchild
< &pwtab
[cur_pwtab_num
]; lostchild
++) {
312 if (lostchild
->child
== child
) {
313 if (WIFEXITED(status
))
314 lostchild
->last_status
= WEXITSTATUS(status
);
315 lostchild
->child
= 0;
325 * PASSWORD AND USERNAME LOOKUP TABLES FUNCTIONS
329 * get index of UserName table entry which matches username.
330 * must not return uid_t because we want to return a negative number.
333 untab_index(char *username
)
335 int max
, min
, mid
, cmp
;
337 max
= cur_pwtab_num
- 1;
341 mid
= (max
+ min
) / 2;
342 cmp
= strcmp(untab
[mid
].username
, username
);
343 if (cmp
== 0) /* record found! */
349 } while (max
> min
+ 1);
351 if (STREQ(untab
[max
].username
, username
))
353 if (STREQ(untab
[min
].username
, username
))
356 /* if gets here then record was not found */
362 * Don't make this return a uid_t, because we need to return negative
363 * numbers as well (error codes.)
366 uidof(char *username
)
370 if ((idx
= untab_index(username
)) < 0) /* not found */
371 return INVALIDID
; /* an invalid user id */
372 return untab
[idx
].uid
;
377 * Don't make this return a uid_t, because we need to return negative
378 * numbers as well (error codes.)
381 homeof(char *username
)
385 if ((idx
= untab_index(username
)) < 0) /* not found */
386 return (char *) NULL
; /* an invalid user id */
387 return untab
[idx
].home
;
392 mailbox(int uid
, char *username
)
397 return (char *) NULL
; /* not found */
399 if ((home
= homeof(username
)) == (char *) NULL
)
400 return (char *) NULL
;
401 if (STREQ(home
, "/"))
402 xsnprintf(mboxfile
, sizeof(mboxfile
),
403 "/%s/%s", home_subdir
, username
);
405 xsnprintf(mboxfile
, sizeof(mboxfile
),
406 "%s/%s/%s", home
, home_subdir
, username
);
412 plt_compare_fxn(const voidp x
, const voidp y
)
415 uid2home_t
*i
= (uid2home_t
*) x
;
416 uid2home_t
*j
= (uid2home_t
*) y
;
418 return i
->uid
- j
->uid
;
423 unt_compare_fxn(const voidp x
, const voidp y
)
425 username2uid_t
*i
= (username2uid_t
*) x
;
426 username2uid_t
*j
= (username2uid_t
*) y
;
428 return strcmp(i
->username
, j
->username
);
432 /* perform initialization of user passwd database */
441 passwd_fp
= fopen(passwdfile
, "r");
443 plog(XLOG_ERROR
, "unable to read passwd file %s: %m", passwdfile
);
446 plog(XLOG_INFO
, "reading password entries from file %s", passwdfile
);
449 memset((char *) &passwd_ent
, 0, sizeof(struct passwd
));
450 passwd_ent
.pw_name
= (char *) &pw_name
;
451 passwd_ent
.pw_dir
= (char *) &pw_dir
;
455 /* perform de-initialization of user passwd database */
461 * Don't actually run this because we will be making more passwd calls
462 * afterwards. On Solaris 2.5.1, making getpwent() calls after calling
463 * endpwent() results in a memory leak! (and no, even Purify didn't
477 /* perform record reading/parsing of individual passwd database records */
478 static struct passwd
*
483 /* check if to perform standard unix function */
488 /* return here to read another entry */
491 /* return NULL if reached end of file */
495 pw_name
[0] = pw_dir
[0] = '\0';
499 fgets(buf
, 256, passwd_fp
);
501 if (!buf
|| buf
[0] == '\0')
505 cp
= strtok(buf
, ":");
506 if (!cp
|| cp
[0] == '\0') {
507 plog(XLOG_ERROR
, "no user name on line %d of %s", passwd_line
, passwdfile
);
510 /* pw_name will show up in passwd_ent.pw_name */
511 xstrlcpy(pw_name
, cp
, sizeof(pw_name
));
517 cp
= strtok(NULL
, ":");
518 if (!cp
|| cp
[0] == '\0') {
519 plog(XLOG_ERROR
, "no uid on line %d of %s", passwd_line
, passwdfile
);
522 passwd_ent
.pw_uid
= atoi(cp
);
524 /* skip gid and gcos */
529 cp
= strtok(NULL
, ":");
530 if (!cp
|| cp
[0] == '\0') {
531 plog(XLOG_ERROR
, "no home dir on line %d of %s", passwd_line
, passwdfile
);
534 /* pw_dir will show up in passwd_ent.pw_dir */
535 xstrlcpy(pw_dir
, cp
, sizeof(pw_dir
));
537 /* the rest of the fields are unimportant and not being considered */
539 plog(XLOG_USER
, "hlfsd_getpwent: name=%s, uid=%ld, dir=%s",
540 passwd_ent
.pw_name
, (long) passwd_ent
.pw_uid
, passwd_ent
.pw_dir
);
547 * read and hash the passwd file or NIS map
552 struct passwd
*pent_p
;
554 if (plt_reset() < 0) /* could not reset table. skip. */
557 plog(XLOG_INFO
, "reading password map");
559 hlfsd_setpwent(); /* prepare to read passwd entries */
560 while ((pent_p
= hlfsd_getpwent()) != (struct passwd
*) NULL
) {
561 table_add(pent_p
->pw_uid
, pent_p
->pw_dir
, pent_p
->pw_name
);
562 if (STREQ("root", pent_p
->pw_name
)) {
566 root_home
= strdup(pent_p
->pw_dir
);
567 len
= strlen(root_home
);
568 /* remove any trailing '/' chars from root's home (even if just one) */
569 while (len
> 0 && root_home
[len
- 1] == '/') {
571 root_home
[len
] = '\0';
577 qsort((char *) pwtab
, cur_pwtab_num
, sizeof(uid2home_t
),
579 qsort((char *) untab
, cur_pwtab_num
, sizeof(username2uid_t
),
583 root_home
= strdup("");
585 plog(XLOG_INFO
, "password map read and sorted");
590 * This is essentially so that we don't reset known good lookup tables when a
591 * YP server goes down.
599 if (hlfsd_getpwent() == (struct passwd
*) NULL
) {
601 return -1; /* did not reset table */
605 lastchild
= (uid2home_t
*) NULL
;
607 if (max_pwtab_num
> 0) /* was used already. cleanup old table */
608 for (i
= 0; i
< cur_pwtab_num
; ++i
) {
610 XFREE(pwtab
[i
].home
);
611 pwtab
[i
].home
= (char *) NULL
;
613 pwtab
[i
].uid
= INVALIDID
; /* not a valid uid (yet...) */
614 pwtab
[i
].child
= (pid_t
) 0;
615 pwtab
[i
].uname
= (char *) NULL
; /* only a ptr to untab[i].username */
616 if (untab
[i
].username
) {
617 XFREE(untab
[i
].username
);
618 untab
[i
].username
= (char *) NULL
;
620 untab
[i
].uid
= INVALIDID
; /* invalid uid */
621 untab
[i
].home
= (char *) NULL
; /* only a ptr to pwtab[i].home */
623 cur_pwtab_num
= 0; /* zero current size */
628 return 0; /* resetting ok */
638 table_add(u_int u
, const char *h
, const char *n
)
642 if (max_pwtab_num
<= 0) { /* was never initialized */
644 pwtab
= (uid2home_t
*) xmalloc(max_pwtab_num
*
646 memset((char *) &pwtab
[0], 0, max_pwtab_num
* sizeof(uid2home_t
));
647 untab
= (username2uid_t
*) xmalloc(max_pwtab_num
*
648 sizeof(username2uid_t
));
649 memset((char *) &untab
[0], 0, max_pwtab_num
* sizeof(username2uid_t
));
652 /* check if need more space. */
653 if (cur_pwtab_num
+ 1 > max_pwtab_num
) {
654 /* need more space in table */
656 plog(XLOG_INFO
, "reallocating table spaces to %d entries", max_pwtab_num
);
657 pwtab
= (uid2home_t
*) xrealloc(pwtab
,
658 sizeof(uid2home_t
) * max_pwtab_num
);
659 untab
= (username2uid_t
*) xrealloc(untab
,
660 sizeof(username2uid_t
) *
662 /* zero out newly added entries */
663 for (i
=cur_pwtab_num
; i
<max_pwtab_num
; ++i
) {
664 memset((char *) &pwtab
[i
], 0, sizeof(uid2home_t
));
665 memset((char *) &untab
[i
], 0, sizeof(username2uid_t
));
669 /* do NOT add duplicate entries (this is an O(N^2) algorithm... */
670 for (i
=0; i
<cur_pwtab_num
; ++i
)
671 if (u
== pwtab
[i
].uid
&& u
!= 0 ) {
672 dlog("ignoring duplicate home %s for uid %d (already %s)",
673 h
, u
, pwtab
[i
].home
);
677 /* add new password entry */
678 pwtab
[cur_pwtab_num
].home
= strdup(h
);
679 pwtab
[cur_pwtab_num
].child
= 0;
680 pwtab
[cur_pwtab_num
].last_access_time
= 0;
681 pwtab
[cur_pwtab_num
].last_status
= 0; /* assume best: used homedir */
682 pwtab
[cur_pwtab_num
].uid
= u
;
684 /* add new userhome entry */
685 untab
[cur_pwtab_num
].username
= strdup(n
);
687 /* just a second pointer */
688 pwtab
[cur_pwtab_num
].uname
= untab
[cur_pwtab_num
].username
;
689 untab
[cur_pwtab_num
].uid
= u
;
690 untab
[cur_pwtab_num
].home
= pwtab
[cur_pwtab_num
].home
; /* a ptr */
692 /* increment counter */
698 * return entry in lookup table
706 * empty table should not happen,
707 * but I have a bug with signals to trace...
709 if (pwtab
== (uid2home_t
*) NULL
)
710 return (uid2home_t
*) NULL
;
712 max
= cur_pwtab_num
- 1;
716 mid
= (max
+ min
) / 2;
717 if (pwtab
[mid
].uid
== u
) /* record found! */
719 if (pwtab
[mid
].uid
> u
)
723 } while (max
> min
+ 1);
725 if (pwtab
[max
].uid
== u
)
727 if (pwtab
[min
].uid
== u
)
730 /* if gets here then record was not found */
731 return (uid2home_t
*) NULL
;
735 #if defined(DEBUG) || defined(DEBUG_PRINT)
737 plt_print(int signum
)
741 char dumptmp
[] = "/usr/tmp/hlfsd.dump.XXXXXX";
745 dumpfd
= mkstemp(dumptmp
);
746 #else /* not HAVE_MKSTEMP */
749 plog(XLOG_ERROR
, "cannot create temporary dump file");
752 dumpfd
= open(dumptmp
, O_RDONLY
);
753 #endif /* not HAVE_MKSTEMP */
755 plog(XLOG_ERROR
, "cannot open temporary dump file");
758 if ((dumpfile
= fdopen(dumpfd
, "a")) != NULL
) {
759 plog(XLOG_INFO
, "dumping internal state to file %s", dumptmp
);
760 fprintf(dumpfile
, "\n\nNew plt_dump():\n");
761 for (i
= 0; i
< cur_pwtab_num
; ++i
)
763 "%4d %5lu %10lu %1d %4lu \"%s\" uname=\"%s\"\n",
765 (long) pwtab
[i
].child
,
766 pwtab
[i
].last_access_time
,
767 pwtab
[i
].last_status
,
771 fprintf(dumpfile
, "\nUserName table by plt_print():\n");
772 for (i
= 0; i
< cur_pwtab_num
; ++i
)
773 fprintf(dumpfile
, "%4d : \"%s\" %4lu \"%s\"\n", i
,
774 untab
[i
].username
, (long) untab
[i
].uid
, untab
[i
].home
);
782 plt_dump(uid2home_t
*lastc
, pid_t
this)
787 if ((dumpfile
= fopen("/var/tmp/hlfsdump", "a")) != NULL
) {
788 fprintf(dumpfile
, "\n\nNEW PLT_DUMP -- ");
789 fprintf(dumpfile
, "lastchild->child=%d ",
790 (int) (lastc
? lastc
->child
: -999));
791 fprintf(dumpfile
, ", child from wait3=%lu:\n", (long) this);
792 for (i
= 0; i
< cur_pwtab_num
; ++i
)
793 fprintf(dumpfile
, "%4d %5lu: %4lu \"%s\" uname=\"%s\"\n", i
,
794 (long) pwtab
[i
].child
, (long) pwtab
[i
].uid
,
795 pwtab
[i
].home
, pwtab
[i
].uname
);
796 fprintf(dumpfile
, "\nUserName table by plt_dump():\n");
797 for (i
= 0; i
< cur_pwtab_num
; ++i
)
798 fprintf(dumpfile
, "%4d : \"%s\" %4lu \"%s\"\n", i
,
799 untab
[i
].username
, (long) untab
[i
].uid
, untab
[i
].home
);
800 fprintf(dumpfile
, "ezk: ent=%d, uid=%lu, home=\"%s\"\n",
802 (long) untab
[untab_index("ezk")].uid
,
803 pwtab
[untab
[untab_index("ezk")].uid
].home
);
804 fprintf(dumpfile
, "rezk: ent=%d, uid=%lu, home=\"%s\"\n",
806 (long) untab
[untab_index("rezk")].uid
,
807 pwtab
[untab
[untab_index("rezk")].uid
].home
);
811 #endif /* defined(DEBUG) || defined(DEBUG_PRINT) */