2 * Copyright (c) 1997-2006 Erez Zadok
3 * Copyright (c) 1989 Jan-Simon Pendry
4 * Copyright (c) 1989 Imperial College of Science, Technology & Medicine
5 * Copyright (c) 1989 The Regents of the University of California.
8 * This code is derived from software contributed to Berkeley by
9 * Jan-Simon Pendry at Imperial College, London.
11 * Redistribution and use in source and binary forms, with or without
12 * modification, are permitted provided that the following conditions
14 * 1. Redistributions of source code must retain the above copyright
15 * notice, this list of conditions and the following disclaimer.
16 * 2. Redistributions in binary form must reproduce the above copyright
17 * notice, this list of conditions and the following disclaimer in the
18 * documentation and/or other materials provided with the distribution.
19 * 3. All advertising materials mentioning features or use of this software
20 * must display the following acknowledgment:
21 * This product includes software developed by the University of
22 * California, Berkeley and its contributors.
23 * 4. Neither the name of the University nor the names of its contributors
24 * may be used to endorse or promote products derived from this software
25 * without specific prior written permission.
27 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
28 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
29 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
30 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
31 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
32 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
33 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
34 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
35 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
36 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
40 * File: am-utils/hlfsd/homedir.c
42 * HLFSD was written at Columbia University Computer Science Department, by
43 * Erez Zadok <ezk@cs.columbia.edu> and Alexander Dupuy <dupuy@cs.columbia.edu>
44 * It is being distributed under the same terms and conditions as amd does.
49 #endif /* HAVE_CONFIG_H */
55 * STATIC VARIABLES AND FUNCTIONS:
57 static FILE *passwd_fp = NULL;
58 static char pw_name[16], pw_dir[128];
59 static int cur_pwtab_num = 0, max_pwtab_num = 0;
60 static int hlfsd_diskspace(char *);
61 static int hlfsd_stat(char *, struct stat *);
62 static int passwd_line = 0;
63 static int plt_reset(void);
64 static struct passwd passwd_ent;
65 static uid2home_t *lastchild;
66 static uid2home_t *pwtab;
67 static void delay(uid2home_t *, int);
68 static void table_add(u_int, const char *, const char *);
69 static char mboxfile[MAXPATHLEN];
70 static char *root_home; /* root's home directory */
72 /* GLOBAL FUNCTIONS */
73 char *homeof(char *username);
74 int uidof(char *username);
76 /* GLOBALS VARIABLES */
77 username2uid_t *untab; /* user name table */
80 * Return the home directory pathname for the user with uid "userid".
83 homedir(int userid, int groupid)
85 static char linkval[MAXPATHLEN + 1];
86 static struct timeval tp;
90 int old_groupid, old_userid;
92 if ((found = plt_search(userid)) == (uid2home_t *) NULL) {
93 return alt_spooldir; /* use alt spool for unknown uid */
95 homename = found->home;
97 if (homename[0] != '/' || homename[1] == '\0') {
98 found->last_status = 1;
99 return alt_spooldir; /* use alt spool for / or rel. home */
101 if ((int) userid == 0) /* force all uid 0 to use root's home */
102 xsnprintf(linkval, sizeof(linkval), "%s/%s", root_home, home_subdir);
104 xsnprintf(linkval, sizeof(linkval), "%s/%s", homename, home_subdir);
107 found->last_status = 0;
112 * To optimize hlfsd, we don't actually check the validity of the
113 * symlink if it has been checked in the last N seconds. It is
114 * very likely that the link, machine, and filesystem are still
115 * valid, as long as N is small. But if N is large, that may not be
116 * true. That's why the default N is 5 minutes, but we allow the
117 * user to override this value via a command line option. Note that
118 * we do not update the last_access_time each time it is accessed,
119 * but only once every N seconds.
121 if (gettimeofday(&tp, (struct timezone *) NULL) < 0) {
124 if ((tp.tv_sec - found->last_access_time) < cache_interval) {
125 if (found->last_status == 0) {
131 found->last_access_time = tp.tv_sec;
136 * only run this forking code if did not ask for -D fork
138 if (!amuDebug(D_FORK)) {
139 /* fork child to process request if none in progress */
140 if (found->child && kill(found->child, 0))
144 delay(found, 5); /* wait a bit if in progress */
145 if (found->child) { /* better safe than sorry - maybe */
146 found->last_status = 1;
149 if ((found->child = fork()) < 0) {
150 found->last_status = 1;
153 if (found->child) { /* PARENT */
155 dlog("cache spill uid = %ld, pid = %ld, home = %s",
156 (long) lastchild->uid, (long) lastchild->child,
159 return (char *) NULL; /* return NULL to parent, so it can continue */
164 * CHILD: (or parent if -D fork)
166 * Check and create dir if needed.
167 * Check disk space and/or quotas too.
169 * We don't need to set the _last_status field of found after the fork
170 * in the child, b/c that information would be later determined in
171 * nfsproc_readlink_2() and the correct exit status would be returned
172 * to the parent upon SIGCHLD in interlock().
175 am_set_mypid(); /* for logging routines */
176 if ((old_groupid = setgid(groupid)) < 0) {
177 plog(XLOG_WARNING, "could not setgid to %d: %m", groupid);
180 if ((old_userid = seteuid(userid)) < 0) {
181 plog(XLOG_WARNING, "could not seteuid to %d: %m", userid);
185 if (hlfsd_stat(linkval, &homestat) < 0) {
186 if (errno == ENOENT) { /* make the spool dir if possible */
187 /* don't use recursive mkdirs here */
188 if (mkdir(linkval, PERS_SPOOLMODE) < 0) {
191 plog(XLOG_WARNING, "can't make directory %s: %m", linkval);
194 /* fall through to testing the disk space / quota */
195 } else { /* the home dir itself must not exist then */
198 plog(XLOG_WARNING, "bad link to %s: %m", linkval);
204 * If gets here, then either the spool dir in the home dir exists,
205 * or it was just created. In either case, we now need to
206 * test if we can create a small file and write at least one
207 * byte into it. This will test that we have both enough inodes
208 * and disk blocks to spare, or they fall within the user's quotas too.
209 * We are still seteuid to the user at this point.
211 if (hlfsd_diskspace(linkval) < 0) {
214 plog(XLOG_WARNING, "no more space in %s: %m", linkval);
225 hlfsd_diskspace(char *path)
227 char buf[MAXPATHLEN];
230 xsnprintf(buf, sizeof(buf), "%s/._hlfstmp_%lu", path, (long) getpid());
231 if ((fd = open(buf, O_RDWR | O_CREAT, 0600)) < 0) {
232 plog(XLOG_ERROR, "cannot open %s: %m", buf);
236 if (write(fd, buf, len) < len) {
237 plog(XLOG_ERROR, "cannot write \"%s\" (%d bytes) to %s : %m", buf, len, buf);
239 unlink(buf); /* cleanup just in case */
242 if (unlink(buf) < 0) {
243 plog(XLOG_ERROR, "cannot unlink %s : %m", buf);
251 hlfsd_stat(char *path, struct stat *statp)
253 if (stat(path, statp) < 0)
255 else if (!S_ISDIR(statp->st_mode)) {
264 delay(uid2home_t *found, int secs)
268 dlog("delaying on child %ld for %d seconds", (long) found->child, secs);
274 if (select(0, 0, 0, 0, &tv) == 0)
276 } while (--secs && found->child);
281 * This function is called when a child has terminated after
282 * servicing an nfs request. We need to check the exit status and
283 * update the last_status field of the requesting user.
286 interlock(int signum)
289 uid2home_t *lostchild;
293 while ((child = waitpid((pid_t) -1, &status, WNOHANG)) > 0) {
294 #else /* not HAVE_WAITPID */
295 while ((child = wait3(&status, WNOHANG, (struct rusage *) 0)) > 0) {
296 #endif /* not HAVE_WAITPID */
298 /* high chances this was the last child forked */
299 if (lastchild && lastchild->child == child) {
300 lastchild->child = 0;
302 if (WIFEXITED(status))
303 lastchild->last_status = WEXITSTATUS(status);
304 lastchild = (uid2home_t *) NULL;
306 /* and if not, we have to search for it... */
307 for (lostchild = pwtab; lostchild < &pwtab[cur_pwtab_num]; lostchild++) {
308 if (lostchild->child == child) {
309 if (WIFEXITED(status))
310 lostchild->last_status = WEXITSTATUS(status);
311 lostchild->child = 0;
321 * PASSWORD AND USERNAME LOOKUP TABLES FUNCTIONS
325 * get index of UserName table entry which matches username.
326 * must not return uid_t because we want to return a negative number.
329 untab_index(char *username)
331 int max, min, mid, cmp;
333 max = cur_pwtab_num - 1;
337 mid = (max + min) / 2;
338 cmp = strcmp(untab[mid].username, username);
339 if (cmp == 0) /* record found! */
345 } while (max > min + 1);
347 if (STREQ(untab[max].username, username))
349 if (STREQ(untab[min].username, username))
352 /* if gets here then record was not found */
358 * Don't make this return a uid_t, because we need to return negative
359 * numbers as well (error codes.)
362 uidof(char *username)
366 if ((idx = untab_index(username)) < 0) /* not found */
367 return INVALIDID; /* an invalid user id */
368 return untab[idx].uid;
373 * Don't make this return a uid_t, because we need to return negative
374 * numbers as well (error codes.)
377 homeof(char *username)
381 if ((idx = untab_index(username)) < 0) /* not found */
382 return (char *) NULL; /* an invalid user id */
383 return untab[idx].home;
388 mailbox(int uid, char *username)
393 return (char *) NULL; /* not found */
395 if ((home = homeof(username)) == (char *) NULL)
396 return (char *) NULL;
397 if (STREQ(home, "/"))
398 xsnprintf(mboxfile, sizeof(mboxfile),
399 "/%s/%s", home_subdir, username);
401 xsnprintf(mboxfile, sizeof(mboxfile),
402 "%s/%s/%s", home, home_subdir, username);
408 plt_compare_fxn(const voidp x, const voidp y)
411 uid2home_t *i = (uid2home_t *) x;
412 uid2home_t *j = (uid2home_t *) y;
414 return i->uid - j->uid;
419 unt_compare_fxn(const voidp x, const voidp y)
421 username2uid_t *i = (username2uid_t *) x;
422 username2uid_t *j = (username2uid_t *) y;
424 return strcmp(i->username, j->username);
428 /* perform initialization of user passwd database */
437 passwd_fp = fopen(passwdfile, "r");
439 plog(XLOG_ERROR, "unable to read passwd file %s: %m", passwdfile);
442 plog(XLOG_INFO, "reading password entries from file %s", passwdfile);
445 memset((char *) &passwd_ent, 0, sizeof(struct passwd));
446 passwd_ent.pw_name = (char *) &pw_name;
447 passwd_ent.pw_dir = (char *) &pw_dir;
451 /* perform de-initialization of user passwd database */
457 * Don't actually run this because we will be making more passwd calls
458 * afterwards. On Solaris 2.5.1, making getpwent() calls after calling
459 * endpwent() results in a memory leak! (and no, even Purify didn't
473 /* perform record reading/parsing of individual passwd database records */
474 static struct passwd *
479 /* check if to perform standard unix function */
484 /* return here to read another entry */
487 /* return NULL if reached end of file */
491 pw_name[0] = pw_dir[0] = '\0';
495 fgets(buf, 256, passwd_fp);
497 if (!buf || buf[0] == '\0')
501 cp = strtok(buf, ":");
502 if (!cp || cp[0] == '\0') {
503 plog(XLOG_ERROR, "no user name on line %d of %s", passwd_line, passwdfile);
506 /* pw_name will show up in passwd_ent.pw_name */
507 xstrlcpy(pw_name, cp, sizeof(pw_name));
513 cp = strtok(NULL, ":");
514 if (!cp || cp[0] == '\0') {
515 plog(XLOG_ERROR, "no uid on line %d of %s", passwd_line, passwdfile);
518 passwd_ent.pw_uid = atoi(cp);
520 /* skip gid and gcos */
525 cp = strtok(NULL, ":");
526 if (!cp || cp[0] == '\0') {
527 plog(XLOG_ERROR, "no home dir on line %d of %s", passwd_line, passwdfile);
530 /* pw_dir will show up in passwd_ent.pw_dir */
531 xstrlcpy(pw_dir, cp, sizeof(pw_dir));
533 /* the rest of the fields are unimportant and not being considered */
535 plog(XLOG_USER, "hlfsd_getpwent: name=%s, uid=%ld, dir=%s",
536 passwd_ent.pw_name, (long) passwd_ent.pw_uid, passwd_ent.pw_dir);
543 * read and hash the passwd file or NIS map
548 struct passwd *pent_p;
550 if (plt_reset() < 0) /* could not reset table. skip. */
553 plog(XLOG_INFO, "reading password map");
555 hlfsd_setpwent(); /* prepare to read passwd entries */
556 while ((pent_p = hlfsd_getpwent()) != (struct passwd *) NULL) {
557 table_add(pent_p->pw_uid, pent_p->pw_dir, pent_p->pw_name);
558 if (STREQ("root", pent_p->pw_name)) {
562 root_home = strdup(pent_p->pw_dir);
563 len = strlen(root_home);
564 /* remove any trailing '/' chars from root's home (even if just one) */
565 while (len > 0 && root_home[len - 1] == '/') {
567 root_home[len] = '\0';
573 qsort((char *) pwtab, cur_pwtab_num, sizeof(uid2home_t),
575 qsort((char *) untab, cur_pwtab_num, sizeof(username2uid_t),
579 root_home = strdup("");
581 plog(XLOG_INFO, "password map read and sorted");
586 * This is essentially so that we don't reset known good lookup tables when a
587 * YP server goes down.
595 if (hlfsd_getpwent() == (struct passwd *) NULL) {
597 return -1; /* did not reset table */
601 lastchild = (uid2home_t *) NULL;
603 if (max_pwtab_num > 0) /* was used already. cleanup old table */
604 for (i = 0; i < cur_pwtab_num; ++i) {
606 XFREE(pwtab[i].home);
607 pwtab[i].home = (char *) NULL;
609 pwtab[i].uid = INVALIDID; /* not a valid uid (yet...) */
610 pwtab[i].child = (pid_t) 0;
611 pwtab[i].uname = (char *) NULL; /* only a ptr to untab[i].username */
612 if (untab[i].username) {
613 XFREE(untab[i].username);
614 untab[i].username = (char *) NULL;
616 untab[i].uid = INVALIDID; /* invalid uid */
617 untab[i].home = (char *) NULL; /* only a ptr to pwtab[i].home */
619 cur_pwtab_num = 0; /* zero current size */
624 return 0; /* resetting ok */
634 table_add(u_int u, const char *h, const char *n)
638 if (max_pwtab_num <= 0) { /* was never initialized */
640 pwtab = (uid2home_t *) xmalloc(max_pwtab_num *
642 memset((char *) &pwtab[0], 0, max_pwtab_num * sizeof(uid2home_t));
643 untab = (username2uid_t *) xmalloc(max_pwtab_num *
644 sizeof(username2uid_t));
645 memset((char *) &untab[0], 0, max_pwtab_num * sizeof(username2uid_t));
648 /* check if need more space. */
649 if (cur_pwtab_num + 1 > max_pwtab_num) {
650 /* need more space in table */
652 plog(XLOG_INFO, "reallocating table spaces to %d entries", max_pwtab_num);
653 pwtab = (uid2home_t *) xrealloc(pwtab,
654 sizeof(uid2home_t) * max_pwtab_num);
655 untab = (username2uid_t *) xrealloc(untab,
656 sizeof(username2uid_t) *
658 /* zero out newly added entries */
659 for (i=cur_pwtab_num; i<max_pwtab_num; ++i) {
660 memset((char *) &pwtab[i], 0, sizeof(uid2home_t));
661 memset((char *) &untab[i], 0, sizeof(username2uid_t));
665 /* do NOT add duplicate entries (this is an O(N^2) algorithm... */
666 for (i=0; i<cur_pwtab_num; ++i)
667 if (u == pwtab[i].uid && u != 0 ) {
668 dlog("ignoring duplicate home %s for uid %d (already %s)",
669 h, u, pwtab[i].home);
673 /* add new password entry */
674 pwtab[cur_pwtab_num].home = strdup(h);
675 pwtab[cur_pwtab_num].child = 0;
676 pwtab[cur_pwtab_num].last_access_time = 0;
677 pwtab[cur_pwtab_num].last_status = 0; /* assume best: used homedir */
678 pwtab[cur_pwtab_num].uid = u;
680 /* add new userhome entry */
681 untab[cur_pwtab_num].username = strdup(n);
683 /* just a second pointer */
684 pwtab[cur_pwtab_num].uname = untab[cur_pwtab_num].username;
685 untab[cur_pwtab_num].uid = u;
686 untab[cur_pwtab_num].home = pwtab[cur_pwtab_num].home; /* a ptr */
688 /* increment counter */
694 * return entry in lookup table
702 * empty table should not happen,
703 * but I have a bug with signals to trace...
705 if (pwtab == (uid2home_t *) NULL)
706 return (uid2home_t *) NULL;
708 max = cur_pwtab_num - 1;
712 mid = (max + min) / 2;
713 if (pwtab[mid].uid == u) /* record found! */
715 if (pwtab[mid].uid > u)
719 } while (max > min + 1);
721 if (pwtab[max].uid == u)
723 if (pwtab[min].uid == u)
726 /* if gets here then record was not found */
727 return (uid2home_t *) NULL;
731 #if defined(DEBUG) || defined(DEBUG_PRINT)
733 plt_print(int signum)
737 char dumptmp[] = "/usr/tmp/hlfsd.dump.XXXXXX";
741 dumpfd = mkstemp(dumptmp);
742 #else /* not HAVE_MKSTEMP */
745 plog(XLOG_ERROR, "cannot create temporary dump file");
748 dumpfd = open(dumptmp, O_RDONLY);
749 #endif /* not HAVE_MKSTEMP */
751 plog(XLOG_ERROR, "cannot open temporary dump file");
754 if ((dumpfile = fdopen(dumpfd, "a")) != NULL) {
755 plog(XLOG_INFO, "dumping internal state to file %s", dumptmp);
756 fprintf(dumpfile, "\n\nNew plt_dump():\n");
757 for (i = 0; i < cur_pwtab_num; ++i)
759 "%4d %5lu %10lu %1d %4lu \"%s\" uname=\"%s\"\n",
761 (long) pwtab[i].child,
762 pwtab[i].last_access_time,
763 pwtab[i].last_status,
767 fprintf(dumpfile, "\nUserName table by plt_print():\n");
768 for (i = 0; i < cur_pwtab_num; ++i)
769 fprintf(dumpfile, "%4d : \"%s\" %4lu \"%s\"\n", i,
770 untab[i].username, (long) untab[i].uid, untab[i].home);
778 plt_dump(uid2home_t *lastc, pid_t this)
783 if ((dumpfile = fopen("/var/tmp/hlfsdump", "a")) != NULL) {
784 fprintf(dumpfile, "\n\nNEW PLT_DUMP -- ");
785 fprintf(dumpfile, "lastchild->child=%d ",
786 (int) (lastc ? lastc->child : -999));
787 fprintf(dumpfile, ", child from wait3=%lu:\n", (long) this);
788 for (i = 0; i < cur_pwtab_num; ++i)
789 fprintf(dumpfile, "%4d %5lu: %4lu \"%s\" uname=\"%s\"\n", i,
790 (long) pwtab[i].child, (long) pwtab[i].uid,
791 pwtab[i].home, pwtab[i].uname);
792 fprintf(dumpfile, "\nUserName table by plt_dump():\n");
793 for (i = 0; i < cur_pwtab_num; ++i)
794 fprintf(dumpfile, "%4d : \"%s\" %4lu \"%s\"\n", i,
795 untab[i].username, (long) untab[i].uid, untab[i].home);
796 fprintf(dumpfile, "ezk: ent=%d, uid=%lu, home=\"%s\"\n",
798 (long) untab[untab_index("ezk")].uid,
799 pwtab[untab[untab_index("ezk")].uid].home);
800 fprintf(dumpfile, "rezk: ent=%d, uid=%lu, home=\"%s\"\n",
802 (long) untab[untab_index("rezk")].uid,
803 pwtab[untab[untab_index("rezk")].uid].home);
807 #endif /* defined(DEBUG) || defined(DEBUG_PRINT) */