2 * Copyright (c) 1997-2014 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. Neither the name of the University nor the names of its contributors
20 * may be used to endorse or promote products derived from this software
21 * without specific prior written permission.
23 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
24 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
27 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
29 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
32 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
36 * File: am-utils/hlfsd/homedir.c
38 * HLFSD was written at Columbia University Computer Science Department, by
39 * Erez Zadok <ezk@cs.columbia.edu> and Alexander Dupuy <dupuy@cs.columbia.edu>
40 * It is being distributed under the same terms and conditions as amd does.
45 #endif /* HAVE_CONFIG_H */
51 * STATIC VARIABLES AND FUNCTIONS:
53 static FILE *passwd_fp = NULL;
54 static char pw_name[16], pw_dir[128];
55 static int cur_pwtab_num = 0, max_pwtab_num = 0;
56 static int hlfsd_diskspace(char *);
57 static int hlfsd_stat(char *, struct stat *);
58 static int passwd_line = 0;
59 static int plt_reset(void);
60 static struct passwd passwd_ent;
61 static uid2home_t *lastchild;
62 static uid2home_t *pwtab;
63 static void delay(uid2home_t *, int);
64 static void table_add(u_int, const char *, const char *);
65 static char mboxfile[MAXPATHLEN];
66 static char *root_home; /* root's home directory */
68 /* GLOBAL FUNCTIONS */
69 char *homeof(char *username);
70 int uidof(char *username);
72 /* GLOBALS VARIABLES */
73 username2uid_t *untab; /* user name table */
76 * Return the home directory pathname for the user with uid "userid".
79 homedir(int userid, int groupid)
81 static char linkval[MAXPATHLEN + 1];
82 static struct timeval tp;
86 int old_groupid, old_userid;
88 if ((found = plt_search(userid)) == (uid2home_t *) NULL) {
89 return alt_spooldir; /* use alt spool for unknown uid */
91 homename = found->home;
93 if (homename[0] != '/' || homename[1] == '\0') {
94 found->last_status = 1;
95 return alt_spooldir; /* use alt spool for / or rel. home */
97 if ((int) userid == 0) /* force all uid 0 to use root's home */
98 xsnprintf(linkval, sizeof(linkval), "%s/%s", root_home, home_subdir);
100 xsnprintf(linkval, sizeof(linkval), "%s/%s", homename, home_subdir);
103 found->last_status = 0;
108 * To optimize hlfsd, we don't actually check the validity of the
109 * symlink if it has been checked in the last N seconds. It is
110 * very likely that the link, machine, and filesystem are still
111 * valid, as long as N is small. But if N is large, that may not be
112 * true. That's why the default N is 5 minutes, but we allow the
113 * user to override this value via a command line option. Note that
114 * we do not update the last_access_time each time it is accessed,
115 * but only once every N seconds.
117 if (gettimeofday(&tp, (struct timezone *) NULL) < 0) {
120 if ((tp.tv_sec - found->last_access_time) < cache_interval) {
121 if (found->last_status == 0) {
127 found->last_access_time = tp.tv_sec;
132 * Only run this forking code if ask for -D fork (default).
133 * Disable forking using -D nofork.
135 if (amuDebug(D_FORK)) {
136 /* fork child to process request if none in progress */
137 if (found->child && kill(found->child, 0))
141 delay(found, 5); /* wait a bit if in progress */
142 if (found->child) { /* better safe than sorry - maybe */
143 found->last_status = 1;
146 if ((found->child = fork()) < 0) {
147 found->last_status = 1;
150 if (found->child) { /* PARENT */
152 dlog("cache spill uid = %ld, pid = %ld, home = %s",
153 (long) lastchild->uid, (long) lastchild->child,
156 return (char *) NULL; /* return NULL to parent, so it can continue */
161 * CHILD: (or parent if -D fork)
163 * Check and create dir if needed.
164 * Check disk space and/or quotas too.
166 * We don't need to set the _last_status field of found after the fork
167 * in the child, b/c that information would be later determined in
168 * nfsproc_readlink_2() and the correct exit status would be returned
169 * to the parent upon SIGCHLD in interlock().
172 am_set_mypid(); /* for logging routines */
173 if ((old_groupid = setgid(groupid)) < 0) {
174 plog(XLOG_WARNING, "could not setgid to %d: %m", groupid);
177 if ((old_userid = seteuid(userid)) < 0) {
178 plog(XLOG_WARNING, "could not seteuid to %d: %m", userid);
182 if (hlfsd_stat(linkval, &homestat) < 0) {
183 if (errno == ENOENT) { /* make the spool dir if possible */
184 /* don't use recursive mkdirs here */
185 if (mkdir(linkval, PERS_SPOOLMODE) < 0) {
188 plog(XLOG_WARNING, "can't make directory %s: %m", linkval);
191 /* fall through to testing the disk space / quota */
192 } else { /* the home dir itself must not exist then */
195 plog(XLOG_WARNING, "bad link to %s: %m", linkval);
201 * If gets here, then either the spool dir in the home dir exists,
202 * or it was just created. In either case, we now need to
203 * test if we can create a small file and write at least one
204 * byte into it. This will test that we have both enough inodes
205 * and disk blocks to spare, or they fall within the user's quotas too.
206 * We are still seteuid to the user at this point.
208 if (hlfsd_diskspace(linkval) < 0) {
211 plog(XLOG_WARNING, "no more space in %s: %m", linkval);
222 hlfsd_diskspace(char *path)
224 char buf[MAXPATHLEN];
227 xsnprintf(buf, sizeof(buf), "%s/._hlfstmp_%lu", path, (long) getpid());
228 if ((fd = open(buf, O_RDWR | O_CREAT, 0600)) < 0) {
229 plog(XLOG_ERROR, "cannot open %s: %m", buf);
233 if (write(fd, buf, len) < len) {
234 plog(XLOG_ERROR, "cannot write \"%s\" (%d bytes) to %s : %m", buf, len, buf);
236 unlink(buf); /* cleanup just in case */
239 if (unlink(buf) < 0) {
240 plog(XLOG_ERROR, "cannot unlink %s : %m", buf);
248 hlfsd_stat(char *path, struct stat *statp)
250 if (stat(path, statp) < 0)
252 else if (!S_ISDIR(statp->st_mode)) {
261 delay(uid2home_t *found, int secs)
266 dlog("delaying on child %ld for %d seconds", (long) found->child, secs);
272 if (select(0, NULL, NULL, NULL, &tv) == 0)
274 } while (--secs && found->child);
279 * This function is called when a child has terminated after
280 * servicing an nfs request. We need to check the exit status and
281 * update the last_status field of the requesting user.
284 interlock(int signum)
287 uid2home_t *lostchild;
291 while ((child = waitpid((pid_t) -1, &status, WNOHANG)) > 0) {
292 #else /* not HAVE_WAITPID */
293 while ((child = wait3(&status, WNOHANG, (struct rusage *) NULL)) > 0) {
294 #endif /* not HAVE_WAITPID */
296 /* high chances this was the last child forked */
297 if (lastchild && lastchild->child == child) {
298 lastchild->child = 0;
300 if (WIFEXITED(status))
301 lastchild->last_status = WEXITSTATUS(status);
302 lastchild = (uid2home_t *) NULL;
304 /* and if not, we have to search for it... */
305 for (lostchild = pwtab; lostchild < &pwtab[cur_pwtab_num]; lostchild++) {
306 if (lostchild->child == child) {
307 if (WIFEXITED(status))
308 lostchild->last_status = WEXITSTATUS(status);
309 lostchild->child = 0;
319 * PASSWORD AND USERNAME LOOKUP TABLES FUNCTIONS
323 * get index of UserName table entry which matches username.
324 * must not return uid_t because we want to return a negative number.
327 untab_index(char *username)
329 int max, min, mid, cmp;
331 max = cur_pwtab_num - 1;
335 mid = (max + min) / 2;
336 cmp = strcmp(untab[mid].username, username);
337 if (cmp == 0) /* record found! */
343 } while (max > min + 1);
345 if (STREQ(untab[max].username, username))
347 if (STREQ(untab[min].username, username))
350 /* if gets here then record was not found */
356 * Don't make this return a uid_t, because we need to return negative
357 * numbers as well (error codes.)
360 uidof(char *username)
364 if ((idx = untab_index(username)) < 0) /* not found */
365 return INVALIDID; /* an invalid user id */
366 return untab[idx].uid;
371 * Don't make this return a uid_t, because we need to return negative
372 * numbers as well (error codes.)
375 homeof(char *username)
379 if ((idx = untab_index(username)) < 0) /* not found */
380 return (char *) NULL; /* an invalid user id */
381 return untab[idx].home;
386 mailbox(int uid, char *username)
391 return (char *) NULL; /* not found */
393 if ((home = homeof(username)) == (char *) NULL)
394 return (char *) NULL;
395 if (STREQ(home, "/"))
396 xsnprintf(mboxfile, sizeof(mboxfile),
397 "/%s/%s", home_subdir, username);
399 xsnprintf(mboxfile, sizeof(mboxfile),
400 "%s/%s/%s", home, home_subdir, username);
406 plt_compare_fxn(const voidp x, const voidp y)
409 uid2home_t *i = (uid2home_t *) x;
410 uid2home_t *j = (uid2home_t *) y;
412 return i->uid - j->uid;
417 unt_compare_fxn(const voidp x, const voidp y)
419 username2uid_t *i = (username2uid_t *) x;
420 username2uid_t *j = (username2uid_t *) y;
422 return strcmp(i->username, j->username);
426 /* perform initialization of user passwd database */
435 passwd_fp = fopen(passwdfile, "r");
437 plog(XLOG_ERROR, "unable to read passwd file %s: %m", passwdfile);
440 plog(XLOG_INFO, "reading password entries from file %s", passwdfile);
443 memset((char *) &passwd_ent, 0, sizeof(struct passwd));
444 passwd_ent.pw_name = (char *) &pw_name;
445 passwd_ent.pw_dir = (char *) &pw_dir;
449 /* perform de-initialization of user passwd database */
455 * Don't actually run this because we will be making more passwd calls
456 * afterwards. On Solaris 2.5.1, making getpwent() calls after calling
457 * endpwent() results in a memory leak! (and no, even Purify didn't
471 /* perform record reading/parsing of individual passwd database records */
472 static struct passwd *
477 /* check if to perform standard unix function */
482 /* return here to read another entry */
485 /* return NULL if reached end of file */
489 pw_name[0] = pw_dir[0] = '\0';
493 fgets(buf, 256, passwd_fp);
499 cp = strtok(buf, ":");
500 if (!cp || cp[0] == '\0') {
501 plog(XLOG_ERROR, "no user name on line %d of %s", passwd_line, passwdfile);
504 /* pw_name will show up in passwd_ent.pw_name */
505 xstrlcpy(pw_name, cp, sizeof(pw_name));
511 cp = strtok(NULL, ":");
512 if (!cp || cp[0] == '\0') {
513 plog(XLOG_ERROR, "no uid on line %d of %s", passwd_line, passwdfile);
516 passwd_ent.pw_uid = atoi(cp);
518 /* skip gid and gcos */
523 cp = strtok(NULL, ":");
524 if (!cp || cp[0] == '\0') {
525 plog(XLOG_ERROR, "no home dir on line %d of %s", passwd_line, passwdfile);
528 /* pw_dir will show up in passwd_ent.pw_dir */
529 xstrlcpy(pw_dir, cp, sizeof(pw_dir));
531 /* the rest of the fields are unimportant and not being considered */
533 plog(XLOG_USER, "hlfsd_getpwent: name=%s, uid=%ld, dir=%s",
534 passwd_ent.pw_name, (long) passwd_ent.pw_uid, passwd_ent.pw_dir);
541 * read and hash the passwd file or NIS map
546 struct passwd *pent_p;
548 if (plt_reset() < 0) /* could not reset table. skip. */
551 plog(XLOG_INFO, "reading password map");
553 hlfsd_setpwent(); /* prepare to read passwd entries */
554 while ((pent_p = hlfsd_getpwent()) != (struct passwd *) NULL) {
555 table_add(pent_p->pw_uid, pent_p->pw_dir, pent_p->pw_name);
556 if (STREQ("root", pent_p->pw_name)) {
560 root_home = xstrdup(pent_p->pw_dir);
561 len = strlen(root_home);
562 /* remove any trailing '/' chars from root's home (even if just one) */
563 while (len > 0 && root_home[len - 1] == '/') {
565 root_home[len] = '\0';
571 qsort((char *) pwtab, cur_pwtab_num, sizeof(uid2home_t),
573 qsort((char *) untab, cur_pwtab_num, sizeof(username2uid_t),
577 root_home = xstrdup("");
579 plog(XLOG_INFO, "password map read and sorted");
584 * This is essentially so that we don't reset known good lookup tables when a
585 * YP server goes down.
593 if (hlfsd_getpwent() == (struct passwd *) NULL) {
595 return -1; /* did not reset table */
599 lastchild = (uid2home_t *) NULL;
601 if (max_pwtab_num > 0) /* was used already. cleanup old table */
602 for (i = 0; i < cur_pwtab_num; ++i) {
604 XFREE(pwtab[i].home);
605 pwtab[i].home = (char *) NULL;
607 pwtab[i].uid = INVALIDID; /* not a valid uid (yet...) */
608 pwtab[i].child = (pid_t) 0;
609 pwtab[i].uname = (char *) NULL; /* only a ptr to untab[i].username */
610 if (untab[i].username) {
611 XFREE(untab[i].username);
612 untab[i].username = (char *) NULL;
614 untab[i].uid = INVALIDID; /* invalid uid */
615 untab[i].home = (char *) NULL; /* only a ptr to pwtab[i].home */
617 cur_pwtab_num = 0; /* zero current size */
622 return 0; /* resetting ok */
632 table_add(u_int u, const char *h, const char *n)
636 if (max_pwtab_num <= 0) { /* was never initialized */
638 pwtab = (uid2home_t *) xmalloc(max_pwtab_num *
640 memset((char *) &pwtab[0], 0, max_pwtab_num * sizeof(uid2home_t));
641 untab = (username2uid_t *) xmalloc(max_pwtab_num *
642 sizeof(username2uid_t));
643 memset((char *) &untab[0], 0, max_pwtab_num * sizeof(username2uid_t));
646 /* check if need more space. */
647 if (cur_pwtab_num + 1 > max_pwtab_num) {
648 /* need more space in table */
650 plog(XLOG_INFO, "reallocating table spaces to %d entries", max_pwtab_num);
651 pwtab = (uid2home_t *) xrealloc(pwtab,
652 sizeof(uid2home_t) * max_pwtab_num);
653 untab = (username2uid_t *) xrealloc(untab,
654 sizeof(username2uid_t) *
656 /* zero out newly added entries */
657 for (i=cur_pwtab_num; i<max_pwtab_num; ++i) {
658 memset((char *) &pwtab[i], 0, sizeof(uid2home_t));
659 memset((char *) &untab[i], 0, sizeof(username2uid_t));
663 /* do NOT add duplicate entries (this is an O(N^2) algorithm... */
664 for (i=0; i<cur_pwtab_num; ++i)
665 if (u == pwtab[i].uid && u != 0 ) {
666 dlog("ignoring duplicate home %s for uid %d (already %s)",
667 h, u, pwtab[i].home);
671 /* add new password entry */
672 pwtab[cur_pwtab_num].home = xstrdup(h);
673 pwtab[cur_pwtab_num].child = 0;
674 pwtab[cur_pwtab_num].last_access_time = 0;
675 pwtab[cur_pwtab_num].last_status = 0; /* assume best: used homedir */
676 pwtab[cur_pwtab_num].uid = u;
678 /* add new userhome entry */
679 untab[cur_pwtab_num].username = xstrdup(n);
681 /* just a second pointer */
682 pwtab[cur_pwtab_num].uname = untab[cur_pwtab_num].username;
683 untab[cur_pwtab_num].uid = u;
684 untab[cur_pwtab_num].home = pwtab[cur_pwtab_num].home; /* a ptr */
686 /* increment counter */
692 * return entry in lookup table
700 * empty table should not happen,
701 * but I have a bug with signals to trace...
703 if (pwtab == (uid2home_t *) NULL)
704 return (uid2home_t *) NULL;
706 max = cur_pwtab_num - 1;
710 mid = (max + min) / 2;
711 if (pwtab[mid].uid == u) /* record found! */
713 if (pwtab[mid].uid > u)
717 } while (max > min + 1);
719 if (pwtab[max].uid == u)
721 if (pwtab[min].uid == u)
724 /* if gets here then record was not found */
725 return (uid2home_t *) NULL;
729 #if defined(DEBUG) || defined(DEBUG_PRINT)
731 plt_print(int signum)
735 char dumptmp[] = "/usr/tmp/hlfsd.dump.XXXXXX";
739 dumpfd = mkstemp(dumptmp);
740 #else /* not HAVE_MKSTEMP */
743 plog(XLOG_ERROR, "cannot create temporary dump file");
746 dumpfd = open(dumptmp, O_RDONLY);
747 #endif /* not HAVE_MKSTEMP */
749 plog(XLOG_ERROR, "cannot open temporary dump file");
752 if ((dumpfile = fdopen(dumpfd, "a")) != NULL) {
753 plog(XLOG_INFO, "dumping internal state to file %s", dumptmp);
754 fprintf(dumpfile, "\n\nNew plt_dump():\n");
755 for (i = 0; i < cur_pwtab_num; ++i)
757 "%4d %5lu %10lu %1d %4lu \"%s\" uname=\"%s\"\n",
759 (long) pwtab[i].child,
760 pwtab[i].last_access_time,
761 pwtab[i].last_status,
765 fprintf(dumpfile, "\nUserName table by plt_print():\n");
766 for (i = 0; i < cur_pwtab_num; ++i)
767 fprintf(dumpfile, "%4d : \"%s\" %4lu \"%s\"\n", i,
768 untab[i].username, (long) untab[i].uid, untab[i].home);
776 plt_dump(uid2home_t *lastc, pid_t this)
781 if ((dumpfile = fopen("/var/tmp/hlfsdump", "a")) != NULL) {
782 fprintf(dumpfile, "\n\nNEW PLT_DUMP -- ");
783 fprintf(dumpfile, "lastchild->child=%d ",
784 (int) (lastc ? lastc->child : -999));
785 fprintf(dumpfile, ", child from wait3=%lu:\n", (long) this);
786 for (i = 0; i < cur_pwtab_num; ++i)
787 fprintf(dumpfile, "%4d %5lu: %4lu \"%s\" uname=\"%s\"\n", i,
788 (long) pwtab[i].child, (long) pwtab[i].uid,
789 pwtab[i].home, pwtab[i].uname);
790 fprintf(dumpfile, "\nUserName table by plt_dump():\n");
791 for (i = 0; i < cur_pwtab_num; ++i)
792 fprintf(dumpfile, "%4d : \"%s\" %4lu \"%s\"\n", i,
793 untab[i].username, (long) untab[i].uid, untab[i].home);
794 fprintf(dumpfile, "ezk: ent=%d, uid=%lu, home=\"%s\"\n",
796 (long) untab[untab_index("ezk")].uid,
797 pwtab[untab[untab_index("ezk")].uid].home);
798 fprintf(dumpfile, "rezk: ent=%d, uid=%lu, home=\"%s\"\n",
800 (long) untab[untab_index("rezk")].uid,
801 pwtab[untab[untab_index("rezk")].uid].home);
805 #endif /* defined(DEBUG) || defined(DEBUG_PRINT) */