/* * Copyright (c) 1997-2004 Erez Zadok * Copyright (c) 1989 Jan-Simon Pendry * Copyright (c) 1989 Imperial College of Science, Technology & Medicine * Copyright (c) 1989 The Regents of the University of California. * All rights reserved. * * This code is derived from software contributed to Berkeley by * Jan-Simon Pendry at Imperial College, London. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgment: * This product includes software developed by the University of * California, Berkeley and its contributors. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * %W% (Berkeley) %G% * * $Id: homedir.c,v 1.5.2.11 2004/01/06 03:15:23 ezk Exp $ * $FreeBSD$ * * HLFSD was written at Columbia University Computer Science Department, by * Erez Zadok and Alexander Dupuy * It is being distributed under the same terms and conditions as amd does. */ #ifdef HAVE_CONFIG_H # include #endif /* HAVE_CONFIG_H */ #include #include /* * STATIC VARIABLES AND FUNCTIONS: */ static FILE *passwd_fp = NULL; static char pw_name[16], pw_dir[128]; static int cur_pwtab_num = 0, max_pwtab_num = 0; static int hlfsd_diskspace(char *); static int hlfsd_stat(char *, struct stat *); static int passwd_line = 0; static int plt_reset(void); static struct passwd passwd_ent; static uid2home_t *lastchild; static uid2home_t *pwtab; static void delay(uid2home_t *, int); static void table_add(int, const char *, const char *); static char mboxfile[MAXPATHLEN]; static char *root_home; /* root's home directory */ /* GLOBAL FUNCTIONS */ char *homeof(char *username); int uidof(char *username); /* GLOBALS VARIABLES */ username2uid_t *untab; /* user name table */ /* * Return the home directory pathname for the user with uid "userid". */ char * homedir(int userid, int groupid) { static char linkval[MAXPATHLEN + 1]; static struct timeval tp; uid2home_t *found; char *homename; struct stat homestat; int old_groupid, old_userid; clock_valid = 0; /* invalidate logging clock */ if ((found = plt_search(userid)) == (uid2home_t *) NULL) { return alt_spooldir; /* use alt spool for unknown uid */ } homename = found->home; if (homename[0] != '/' || homename[1] == '\0') { found->last_status = 1; return alt_spooldir; /* use alt spool for / or rel. home */ } if ((int) userid == 0) /* force all uid 0 to use root's home */ sprintf(linkval, "%s/%s", root_home, home_subdir); else sprintf(linkval, "%s/%s", homename, home_subdir); if (noverify) { found->last_status = 0; return linkval; } /* * To optimize hlfsd, we don't actually check the validity of the * symlink if it has been checked in the last N seconds. It is * very likely that the link, machine, and filesystem are still * valid, as long as N is small. But if N is large, that may not be * true. That's why the default N is 5 minutes, but we allow the * user to override this value via a command line option. Note that * we do not update the last_access_time each time it is accessed, * but only once every N seconds. */ if (gettimeofday(&tp, (struct timezone *) NULL) < 0) { tp.tv_sec = 0; } else { if ((tp.tv_sec - found->last_access_time) < cache_interval) { if (found->last_status == 0) { return linkval; } else { return alt_spooldir; } } else { found->last_access_time = tp.tv_sec; } } #ifdef DEBUG /* * only run this forking code if asked for -D fork * or if did not ask for -D nofork */ amuDebug(D_FORK) { #endif /* DEBUG */ /* fork child to process request if none in progress */ if (found->child && kill(found->child, 0) < 0) found->child = 0; if (found->child) delay(found, 5); /* wait a bit if in progress */ #if defined(DEBUG) && defined(HAVE_WAITPID) if (found->child) { /* perhaps it's a child we lost count of? let's wait on it */ int status, child; if ((child = waitpid((pid_t) found->child, &status, WNOHANG)) > 0) { plog(XLOG_ERROR, "found lost child %d", child); found->child = 0; if (WIFEXITED(status)) found->last_status = WEXITSTATUS(status); else if (WIFSIGNALED(status)) found->last_status = -WTERMSIG(status); else { plog(XLOG_ERROR, "unknown child exit status (%d) ???", status); found->last_status = 255; } } } #endif /* DEBUG && HAVE_WAITPID */ if (found->child) { found->last_status = 1; /* better safe than sorry - maybe */ return alt_spooldir; } if ((found->child = fork()) < 0) { found->last_status = 1; return alt_spooldir; } if (found->child) { /* PARENT */ #ifdef DEBUG if (lastchild) plog(XLOG_INFO, "cache spill uid = %ld, pid = %ld, home = %s", (long) lastchild->uid, (long) lastchild->child, lastchild->home); #endif /* DEBUG */ lastchild = found; return (char *) NULL; /* return NULL to parent, so it can continue */ } #ifdef DEBUG } /* end of Debug(D_FORK) */ #endif /* DEBUG */ /* * CHILD: (or parent if -D nofork) * * Check and create dir if needed. * Check disk space and/or quotas too. * * We don't need to set the _last_status field of found after the fork * in the child, b/c that information would be later determined in * nfsproc_readlink_2() and the correct exit status would be returned * to the parent upon SIGCHLD in interlock(). * */ am_set_mypid(); /* for logging routines */ if ((old_groupid = setgid(groupid)) < 0) { plog(XLOG_WARNING, "could not setgid to %d: %m", groupid); return linkval; } if ((old_userid = seteuid(userid)) < 0) { plog(XLOG_WARNING, "could not seteuid to %d: %m", userid); setgid(old_groupid); return linkval; } if (hlfsd_stat(linkval, &homestat) < 0) { if (errno == ENOENT) { /* make the spool dir if possible */ /* don't use recursive mkdirs here */ if (mkdir(linkval, PERS_SPOOLMODE) < 0) { seteuid(old_userid); setgid(old_groupid); plog(XLOG_WARNING, "can't make directory %s: %m", linkval); return alt_spooldir; } /* fall through to testing the disk space / quota */ } else { /* the home dir itself must not exist then */ seteuid(old_userid); setgid(old_groupid); plog(XLOG_WARNING, "bad link to %s: %m", linkval); return alt_spooldir; } } /* * If gets here, then either the spool dir in the home dir exists, * or it was just created. In either case, we now need to * test if we can create a small file and write at least one * byte into it. This will test that we have both enough inodes * and disk blocks to spare, or they fall within the user's quotas too. * We are still seteuid to the user at this point. */ if (hlfsd_diskspace(linkval) < 0) { seteuid(old_userid); setgid(old_groupid); plog(XLOG_WARNING, "no more space in %s: %m", linkval); return alt_spooldir; } else { seteuid(old_userid); setgid(old_groupid); return linkval; } } static int hlfsd_diskspace(char *path) { char buf[MAXPATHLEN]; int fd, len; clock_valid = 0; /* invalidate logging clock */ sprintf(buf, "%s/._hlfstmp_%lu", path, (long) getpid()); if ((fd = open(buf, O_RDWR | O_CREAT, 0600)) < 0) { plog(XLOG_ERROR, "cannot open %s: %m", buf); return -1; } len = strlen(buf); if (write(fd, buf, len) < len) { plog(XLOG_ERROR, "cannot write \"%s\" (%d bytes) to %s : %m", buf, len, buf); close(fd); unlink(buf); /* cleanup just in case */ return -1; } if (unlink(buf) < 0) { plog(XLOG_ERROR, "cannot unlink %s : %m", buf); } close(fd); return 0; } static int hlfsd_stat(char *path, struct stat *statp) { if (stat(path, statp) < 0) return -1; else if (!S_ISDIR(statp->st_mode)) { errno = ENOTDIR; return -1; } return 0; } static void delay(uid2home_t *found, int secs) { struct timeval tv; #ifdef DEBUG if (found) dlog("delaying on child %ld for %d seconds", (long) found->child, secs); #endif /* DEBUG */ tv.tv_usec = 0; do { tv.tv_sec = secs; if (select(0, 0, 0, 0, &tv) == 0) break; } while (--secs && found->child); } /* * This function is called when a child has terminated after * servicing an nfs request. We need to check the exit status and * update the last_status field of the requesting user. */ RETSIGTYPE interlock(int signum) { int child; uid2home_t *lostchild; int status; int max_errors = 10; /* avoid infinite loops */ #ifdef HAVE_WAITPID while ((child = waitpid((pid_t) -1, &status, WNOHANG)) != 0) { #else /* not HAVE_WAITPID */ while ((child = wait3(&status, WNOHANG, (struct rusage *) 0)) != 0) { #endif /* not HAVE_WAITPID */ if (child < 0) { plog(XLOG_WARNING, "waitpid/wait3: %m"); if (--max_errors > 0) continue; else break; } /* high chances this was the last child forked */ if (lastchild && lastchild->child == child) { lastchild->child = 0; if (WIFEXITED(status)) lastchild->last_status = WEXITSTATUS(status); else if (WIFSIGNALED(status)) lastchild->last_status = -WTERMSIG(status); else { plog(XLOG_ERROR, "unknown child exit status (%d) ???", status); lastchild->last_status = 255; } lastchild = (uid2home_t *) NULL; } else { /* and if not, we have to search for it... */ int found = 0; for (lostchild = pwtab; lostchild < &pwtab[cur_pwtab_num]; lostchild++) { if (lostchild->child == child) { lostchild->child = 0; if (WIFEXITED(status)) lostchild->last_status = WEXITSTATUS(status); else if (WIFSIGNALED(status)) lostchild->last_status = -WTERMSIG(status); else { plog(XLOG_ERROR, "unknown child exit status (%d) ???", status); lostchild->last_status = 255; } found = 1; break; } } if (!found) plog(XLOG_ERROR, "no record of child %d found???", child); } } } /* * PASSWORD AND USERNAME LOOKUP TABLES FUNCTIONS */ /* * get index of UserName table entry which matches username. * must not return uid_t because we want to return a negative number. */ int untab_index(char *username) { int max, min, mid, cmp; max = cur_pwtab_num - 1; min = 0; do { mid = (max + min) / 2; cmp = strcmp(untab[mid].username, username); if (cmp == 0) /* record found! */ return mid; if (cmp > 0) max = mid; else min = mid; } while (max > min + 1); if (STREQ(untab[max].username, username)) return max; if (STREQ(untab[min].username, username)) return min; /* if gets here then record was not found */ return -1; } /* * Don't make this return a uid_t, because we need to return negative * numbers as well (error codes.) */ int uidof(char *username) { int idx; if ((idx = untab_index(username)) < 0) /* not found */ return INVALIDID; /* an invalid user id */ return untab[idx].uid; } /* * Don't make this return a uid_t, because we need to return negative * numbers as well (error codes.) */ char * homeof(char *username) { int idx; if ((idx = untab_index(username)) < 0) /* not found */ return (char *) NULL; /* an invalid user id */ return untab[idx].home; } char * mailbox(int uid, char *username) { char *home; if (uid < 0) return (char *) NULL; /* not found */ if ((home = homeof(username)) == (char *) NULL) return (char *) NULL; if (STREQ(home, "/")) sprintf(mboxfile, "/%s/%s", home_subdir, username); else sprintf(mboxfile, "%s/%s/%s", home, home_subdir, username); return mboxfile; } static int plt_compare_fxn(const voidp x, const voidp y) { uid2home_t *i = (uid2home_t *) x; uid2home_t *j = (uid2home_t *) y; return i->uid - j->uid; } static int unt_compare_fxn(const voidp x, const voidp y) { username2uid_t *i = (username2uid_t *) x; username2uid_t *j = (username2uid_t *) y; return strcmp(i->username, j->username); } /* perform initialization of user passwd database */ static void hlfsd_setpwent(void) { if (!passwdfile) { setpwent(); return; } passwd_fp = fopen(passwdfile, "r"); if (!passwd_fp) { plog(XLOG_ERROR, "unable to read passwd file %s: %m", passwdfile); return; } plog(XLOG_INFO, "reading password entries from file %s", passwdfile); passwd_line = 0; memset((char *) &passwd_ent, 0, sizeof(struct passwd)); passwd_ent.pw_name = (char *) &pw_name; passwd_ent.pw_dir = (char *) &pw_dir; } /* perform de-initialization of user passwd database */ static void hlfsd_endpwent(void) { if (!passwdfile) { /* * Don't actually run this because we will be making more passwd calls * afterwards. On Solaris 2.5.1, making getpwent() calls after calling * endpwent() results in a memory leak! (and no, even Purify didn't * detect it...) * endpwent(); */ return; } if (passwd_fp) { fclose(passwd_fp); } } /* perform record reading/parsing of individual passwd database records */ static struct passwd * hlfsd_getpwent(void) { char buf[256], *cp; /* check if to perform standard unix function */ if (!passwdfile) { return getpwent(); } clock_valid = 0; /* invalidate logging clock */ /* return here to read another entry */ readent: /* return NULL if reached end of file */ if (feof(passwd_fp)) return NULL; pw_name[0] = pw_dir[0] = '\0'; /* read records */ buf[0] = '\0'; fgets(buf, 256, passwd_fp); passwd_line++; if (!buf || buf[0] == '\0') goto readent; /* read user name */ cp = strtok(buf, ":"); if (!cp || cp[0] == '\0') { plog(XLOG_ERROR, "no user name on line %d of %s", passwd_line, passwdfile); goto readent; } strcpy(pw_name, cp); /* will show up in passwd_ent.pw_name */ /* skip passwd */ strtok(NULL, ":"); /* read uid */ cp = strtok(NULL, ":"); if (!cp || cp[0] == '\0') { plog(XLOG_ERROR, "no uid on line %d of %s", passwd_line, passwdfile); goto readent; } passwd_ent.pw_uid = atoi(cp); /* skip gid and gcos */ strtok(NULL, ":"); strtok(NULL, ":"); /* read home dir */ cp = strtok(NULL, ":"); if (!cp || cp[0] == '\0') { plog(XLOG_ERROR, "no home dir on line %d of %s", passwd_line, passwdfile); goto readent; } strcpy(pw_dir, cp); /* will show up in passwd_ent.pw_dir */ /* the rest of the fields are unimportant and not being considered */ plog(XLOG_USER, "hlfsd_getpwent: name=%s, uid=%ld, dir=%s", passwd_ent.pw_name, (long) passwd_ent.pw_uid, passwd_ent.pw_dir); return &passwd_ent; } /* * read and hash the passwd file or NIS map */ void plt_init(void) { struct passwd *pent_p; if (plt_reset() < 0) /* could not reset table. skip. */ return; plog(XLOG_INFO, "reading password map"); hlfsd_setpwent(); /* prepare to read passwd entries */ while ((pent_p = hlfsd_getpwent()) != (struct passwd *) NULL) { table_add(pent_p->pw_uid, pent_p->pw_dir, pent_p->pw_name); if (STREQ("root", pent_p->pw_name)) { int len; if (root_home) XFREE(root_home); root_home = strdup(pent_p->pw_dir); len = strlen(root_home); /* remove any trailing '/' chars from root's home (even if just one) */ while (len > 0 && root_home[len - 1] == '/') { len--; root_home[len] = '\0'; } } } hlfsd_endpwent(); qsort((char *) pwtab, cur_pwtab_num, sizeof(uid2home_t), plt_compare_fxn); qsort((char *) untab, cur_pwtab_num, sizeof(username2uid_t), unt_compare_fxn); if (!root_home) root_home = strdup(""); plog(XLOG_INFO, "password map read and sorted"); } /* * This is essentially so that we don't reset known good lookup tables when a * YP server goes down. */ static int plt_reset(void) { int i; clock_valid = 0; /* invalidate logging clock */ hlfsd_setpwent(); if (hlfsd_getpwent() == (struct passwd *) NULL) { hlfsd_endpwent(); return -1; /* did not reset table */ } hlfsd_endpwent(); lastchild = (uid2home_t *) NULL; if (max_pwtab_num > 0) /* was used already. cleanup old table */ for (i = 0; i < cur_pwtab_num; ++i) { if (pwtab[i].home) { XFREE(pwtab[i].home); pwtab[i].home = (char *) NULL; } pwtab[i].uid = INVALIDID; /* not a valid uid (yet...) */ pwtab[i].child = (pid_t) 0; pwtab[i].uname = (char *) NULL; /* only a ptr to untab[i].username */ if (untab[i].username) { XFREE(untab[i].username); untab[i].username = (char *) NULL; } untab[i].uid = INVALIDID; /* invalid uid */ untab[i].home = (char *) NULL; /* only a ptr to pwtab[i].home */ } cur_pwtab_num = 0; /* zero current size */ if (root_home) XFREE(root_home); return 0; /* resetting ok */ } /* * u: uid number * h: home directory * n: user ID name */ static void table_add(int u, const char *h, const char *n) { int i; clock_valid = 0; /* invalidate logging clock */ if (max_pwtab_num <= 0) { /* was never initialized */ max_pwtab_num = 1; pwtab = (uid2home_t *) xmalloc(max_pwtab_num * sizeof(uid2home_t)); memset((char *) &pwtab[0], 0, max_pwtab_num * sizeof(uid2home_t)); untab = (username2uid_t *) xmalloc(max_pwtab_num * sizeof(username2uid_t)); memset((char *) &untab[0], 0, max_pwtab_num * sizeof(username2uid_t)); } /* check if need more space. */ if (cur_pwtab_num + 1 > max_pwtab_num) { /* need more space in table */ max_pwtab_num *= 2; plog(XLOG_INFO, "reallocating table spaces to %d entries", max_pwtab_num); pwtab = (uid2home_t *) xrealloc(pwtab, sizeof(uid2home_t) * max_pwtab_num); untab = (username2uid_t *) xrealloc(untab, sizeof(username2uid_t) * max_pwtab_num); /* zero out newly added entries */ for (i=cur_pwtab_num; i u) max = mid; else min = mid; } while (max > min + 1); if (pwtab[max].uid == u) return &pwtab[max]; if (pwtab[min].uid == u) return &pwtab[min]; /* if gets here then record was not found */ return (uid2home_t *) NULL; } #if defined(DEBUG) || defined(DEBUG_PRINT) void plt_print(int signum) { FILE *dumpfile; int dumpfd; char dumptmp[] = "/usr/tmp/hlfsd.dump.XXXXXX"; int i; #ifdef HAVE_MKSTEMP dumpfd = mkstemp(dumptmp); #else /* not HAVE_MKSTEMP */ mktemp(dumptmp); if (!dumptmp) { plog(XLOG_ERROR, "cannot create temporary dump file"); return; } dumpfd = open(dumptmp, O_RDONLY); #endif /* not HAVE_MKSTEMP */ if (dumpfd < 0) { plog(XLOG_ERROR, "cannot open temporary dump file"); return; } if ((dumpfile = fdopen(dumpfd, "a")) != NULL) { plog(XLOG_INFO, "dumping internal state to file %s", dumptmp); fprintf(dumpfile, "\n\nNew plt_dump():\n"); for (i = 0; i < cur_pwtab_num; ++i) fprintf(dumpfile, "%4d %5lu %10lu %1d %4lu \"%s\" uname=\"%s\"\n", i, (long) pwtab[i].child, pwtab[i].last_access_time, pwtab[i].last_status, (long) pwtab[i].uid, pwtab[i].home, pwtab[i].uname); fprintf(dumpfile, "\nUserName table by plt_print():\n"); for (i = 0; i < cur_pwtab_num; ++i) fprintf(dumpfile, "%4d : \"%s\" %4lu \"%s\"\n", i, untab[i].username, (long) untab[i].uid, untab[i].home); close(dumpfd); fclose(dumpfile); } } void plt_dump(uid2home_t *lastc, pid_t this) { FILE *dumpfile; int i; if ((dumpfile = fopen("/var/tmp/hlfsdump", "a")) != NULL) { fprintf(dumpfile, "\n\nNEW PLT_DUMP -- "); fprintf(dumpfile, "lastchild->child=%d ", (int) (lastc ? lastc->child : -999)); fprintf(dumpfile, ", child from wait3=%lu:\n", (long) this); for (i = 0; i < cur_pwtab_num; ++i) fprintf(dumpfile, "%4d %5lu: %4lu \"%s\" uname=\"%s\"\n", i, (long) pwtab[i].child, (long) pwtab[i].uid, pwtab[i].home, pwtab[i].uname); fprintf(dumpfile, "\nUserName table by plt_dump():\n"); for (i = 0; i < cur_pwtab_num; ++i) fprintf(dumpfile, "%4d : \"%s\" %4lu \"%s\"\n", i, untab[i].username, (long) untab[i].uid, untab[i].home); fprintf(dumpfile, "ezk: ent=%d, uid=%lu, home=\"%s\"\n", untab_index("ezk"), (long) untab[untab_index("ezk")].uid, pwtab[untab[untab_index("ezk")].uid].home); fprintf(dumpfile, "rezk: ent=%d, uid=%lu, home=\"%s\"\n", untab_index("rezk"), (long) untab[untab_index("rezk")].uid, pwtab[untab[untab_index("rezk")].uid].home); fclose(dumpfile); } } #endif /* defined(DEBUG) || defined(DEBUG_PRINT) */