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 if (fgets(buf, 256, passwd_fp) == NULL)
500 cp = strtok(buf, ":");
501 if (!cp || cp[0] == '\0') {
502 plog(XLOG_ERROR, "no user name on line %d of %s", passwd_line, passwdfile);
505 /* pw_name will show up in passwd_ent.pw_name */
506 xstrlcpy(pw_name, cp, sizeof(pw_name));
512 cp = strtok(NULL, ":");
513 if (!cp || cp[0] == '\0') {
514 plog(XLOG_ERROR, "no uid on line %d of %s", passwd_line, passwdfile);
517 passwd_ent.pw_uid = atoi(cp);
519 /* skip gid and gcos */
524 cp = strtok(NULL, ":");
525 if (!cp || cp[0] == '\0') {
526 plog(XLOG_ERROR, "no home dir on line %d of %s", passwd_line, passwdfile);
529 /* pw_dir will show up in passwd_ent.pw_dir */
530 xstrlcpy(pw_dir, cp, sizeof(pw_dir));
532 /* the rest of the fields are unimportant and not being considered */
534 plog(XLOG_USER, "hlfsd_getpwent: name=%s, uid=%ld, dir=%s",
535 passwd_ent.pw_name, (long) passwd_ent.pw_uid, passwd_ent.pw_dir);
542 * read and hash the passwd file or NIS map
547 struct passwd *pent_p;
549 if (plt_reset() < 0) /* could not reset table. skip. */
552 plog(XLOG_INFO, "reading password map");
554 hlfsd_setpwent(); /* prepare to read passwd entries */
555 while ((pent_p = hlfsd_getpwent()) != (struct passwd *) NULL) {
556 table_add(pent_p->pw_uid, pent_p->pw_dir, pent_p->pw_name);
557 if (STREQ("root", pent_p->pw_name)) {
561 root_home = xstrdup(pent_p->pw_dir);
562 len = strlen(root_home);
563 /* remove any trailing '/' chars from root's home (even if just one) */
564 while (len > 0 && root_home[len - 1] == '/') {
566 root_home[len] = '\0';
572 qsort((char *) pwtab, cur_pwtab_num, sizeof(uid2home_t),
574 qsort((char *) untab, cur_pwtab_num, sizeof(username2uid_t),
578 root_home = xstrdup("");
580 plog(XLOG_INFO, "password map read and sorted");
585 * This is essentially so that we don't reset known good lookup tables when a
586 * YP server goes down.
594 if (hlfsd_getpwent() == (struct passwd *) NULL) {
596 return -1; /* did not reset table */
600 lastchild = (uid2home_t *) NULL;
602 if (max_pwtab_num > 0) /* was used already. cleanup old table */
603 for (i = 0; i < cur_pwtab_num; ++i) {
605 XFREE(pwtab[i].home);
606 pwtab[i].home = (char *) NULL;
608 pwtab[i].uid = INVALIDID; /* not a valid uid (yet...) */
609 pwtab[i].child = (pid_t) 0;
610 pwtab[i].uname = (char *) NULL; /* only a ptr to untab[i].username */
611 if (untab[i].username) {
612 XFREE(untab[i].username);
613 untab[i].username = (char *) NULL;
615 untab[i].uid = INVALIDID; /* invalid uid */
616 untab[i].home = (char *) NULL; /* only a ptr to pwtab[i].home */
618 cur_pwtab_num = 0; /* zero current size */
623 return 0; /* resetting ok */
633 table_add(u_int u, const char *h, const char *n)
637 if (max_pwtab_num <= 0) { /* was never initialized */
639 pwtab = (uid2home_t *) xmalloc(max_pwtab_num *
641 memset((char *) &pwtab[0], 0, max_pwtab_num * sizeof(uid2home_t));
642 untab = (username2uid_t *) xmalloc(max_pwtab_num *
643 sizeof(username2uid_t));
644 memset((char *) &untab[0], 0, max_pwtab_num * sizeof(username2uid_t));
647 /* check if need more space. */
648 if (cur_pwtab_num + 1 > max_pwtab_num) {
649 /* need more space in table */
651 plog(XLOG_INFO, "reallocating table spaces to %d entries", max_pwtab_num);
652 pwtab = (uid2home_t *) xrealloc(pwtab,
653 sizeof(uid2home_t) * max_pwtab_num);
654 untab = (username2uid_t *) xrealloc(untab,
655 sizeof(username2uid_t) *
657 /* zero out newly added entries */
658 for (i=cur_pwtab_num; i<max_pwtab_num; ++i) {
659 memset((char *) &pwtab[i], 0, sizeof(uid2home_t));
660 memset((char *) &untab[i], 0, sizeof(username2uid_t));
664 /* do NOT add duplicate entries (this is an O(N^2) algorithm... */
665 for (i=0; i<cur_pwtab_num; ++i)
666 if (u == pwtab[i].uid && u != 0 ) {
667 dlog("ignoring duplicate home %s for uid %d (already %s)",
668 h, u, pwtab[i].home);
672 /* add new password entry */
673 pwtab[cur_pwtab_num].home = xstrdup(h);
674 pwtab[cur_pwtab_num].child = 0;
675 pwtab[cur_pwtab_num].last_access_time = 0;
676 pwtab[cur_pwtab_num].last_status = 0; /* assume best: used homedir */
677 pwtab[cur_pwtab_num].uid = u;
679 /* add new userhome entry */
680 untab[cur_pwtab_num].username = xstrdup(n);
682 /* just a second pointer */
683 pwtab[cur_pwtab_num].uname = untab[cur_pwtab_num].username;
684 untab[cur_pwtab_num].uid = u;
685 untab[cur_pwtab_num].home = pwtab[cur_pwtab_num].home; /* a ptr */
687 /* increment counter */
693 * return entry in lookup table
701 * empty table should not happen,
702 * but I have a bug with signals to trace...
704 if (pwtab == (uid2home_t *) NULL)
705 return (uid2home_t *) NULL;
707 max = cur_pwtab_num - 1;
711 mid = (max + min) / 2;
712 if (pwtab[mid].uid == u) /* record found! */
714 if (pwtab[mid].uid > u)
718 } while (max > min + 1);
720 if (pwtab[max].uid == u)
722 if (pwtab[min].uid == u)
725 /* if gets here then record was not found */
726 return (uid2home_t *) NULL;
730 #if defined(DEBUG) || defined(DEBUG_PRINT)
732 plt_print(int signum)
736 char dumptmp[] = "/usr/tmp/hlfsd.dump.XXXXXX";
740 dumpfd = mkstemp(dumptmp);
741 #else /* not HAVE_MKSTEMP */
744 plog(XLOG_ERROR, "cannot create temporary dump file");
747 dumpfd = open(dumptmp, O_RDONLY);
748 #endif /* not HAVE_MKSTEMP */
750 plog(XLOG_ERROR, "cannot open temporary dump file");
753 if ((dumpfile = fdopen(dumpfd, "a")) != NULL) {
754 plog(XLOG_INFO, "dumping internal state to file %s", dumptmp);
755 fprintf(dumpfile, "\n\nNew plt_dump():\n");
756 for (i = 0; i < cur_pwtab_num; ++i)
758 "%4d %5lu %10lu %1d %4lu \"%s\" uname=\"%s\"\n",
760 (long) pwtab[i].child,
761 pwtab[i].last_access_time,
762 pwtab[i].last_status,
766 fprintf(dumpfile, "\nUserName table by plt_print():\n");
767 for (i = 0; i < cur_pwtab_num; ++i)
768 fprintf(dumpfile, "%4d : \"%s\" %4lu \"%s\"\n", i,
769 untab[i].username, (long) untab[i].uid, untab[i].home);
777 plt_dump(uid2home_t *lastc, pid_t this)
782 if ((dumpfile = fopen("/var/tmp/hlfsdump", "a")) != NULL) {
783 fprintf(dumpfile, "\n\nNEW PLT_DUMP -- ");
784 fprintf(dumpfile, "lastchild->child=%d ",
785 (int) (lastc ? lastc->child : -999));
786 fprintf(dumpfile, ", child from wait3=%lu:\n", (long) this);
787 for (i = 0; i < cur_pwtab_num; ++i)
788 fprintf(dumpfile, "%4d %5lu: %4lu \"%s\" uname=\"%s\"\n", i,
789 (long) pwtab[i].child, (long) pwtab[i].uid,
790 pwtab[i].home, pwtab[i].uname);
791 fprintf(dumpfile, "\nUserName table by plt_dump():\n");
792 for (i = 0; i < cur_pwtab_num; ++i)
793 fprintf(dumpfile, "%4d : \"%s\" %4lu \"%s\"\n", i,
794 untab[i].username, (long) untab[i].uid, untab[i].home);
795 fprintf(dumpfile, "ezk: ent=%d, uid=%lu, home=\"%s\"\n",
797 (long) untab[untab_index("ezk")].uid,
798 pwtab[untab[untab_index("ezk")].uid].home);
799 fprintf(dumpfile, "rezk: ent=%d, uid=%lu, home=\"%s\"\n",
801 (long) untab[untab_index("rezk")].uid,
802 pwtab[untab[untab_index("rezk")].uid].home);
806 #endif /* defined(DEBUG) || defined(DEBUG_PRINT) */