/*
 * 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 acknowledgement:
 *      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.12 1993/06/08 00:05:37 ezk Exp ezk $
 *
 * HLFSD was written at Columbia University Computer Science Department, by
 * Erez Zadok <ezk@cs.columbia.edu> and Alexander Dupuy <dupuy@cs.columbia.edu>
 * It is being distributed under the same terms and conditions as amd does.
 */

#include "hlfs.h"
#include <sys/param.h>
#include <sys/types.h>
#include <sys/stat.h>
#include WAIT
#include <pwd.h>
#include <errno.h>

#define PERS_SPOOLMODE 0755

#ifdef TEST_HOMEDIR
#include <stdio.h>
#define plog(x,y,z)	printf((y),(z)),printf("\n")
#endif

static uid2home_t *pwtab;
static uid2home_t *lastchild;
username2uid_t *untab;		/* user name table */

static int cur_pwtab_num = 0, max_pwtab_num = 0;

static void delay P((uid2home_t *, int));
static void plt_init (P_void);
static void table_add P((int, char *, char *));
static int plt_reset (P_void);
static hlfsd_stat P((char *, struct stat *));

char mboxfile[MAXPATHLEN];

#if defined(DEBUG) || defined(DEBUG_PRINT)
extern void plt_print (P_void);
extern void plt_dump P((uid2home_t *, pid_t));
#endif

void init_homedir()
{				/* read and hash the passwd file or NIS map */
	plt_init();
}


char *homedir(userid)
uid_t userid;
{
	static char linkval[MAXPATHLEN + 1];
	static struct timeval tp;
	uid2home_t *found;
	char *homename;
	struct stat homestat;

	if (userid == 0)		/* force superuser to use alt spool */
		return alt_spooldir;

	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')
		return alt_spooldir;	/* use alt spool for / or rel. home */

	(void) sprintf(linkval, "%s/%s", homename, home_subdir);

	if (noverify)
		return linkval;

	/*
	 * To optimize hlfsd, we don't actually check the validity of the
	 * symlink if it has been in 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 ls 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) {
			return linkval;
		} else {
			found->last_access_time = tp.tv_sec;
		}
	}

	/* fork child to process request if none in progress */

	if (found->child && kill(found->child, 0))
		found->child = 0;

	if (found->child)
		delay (found, 5); /* wait a bit if in progress */

	if (found->child)	/* better safe than sorry - maybe */
		return alt_spooldir;

#ifdef DEBUG
	if (!(debug_flags & D_FORK)) {
#endif
	if ((found->child = fork()) < 0)
		return alt_spooldir;

	if (found->child) {
#ifdef DEBUG
		if (lastchild)
			plog(XLOG_INFO,"cache spill uid = %d, pid = %d, home = %s",
			     lastchild->uid, lastchild->child,
			     lastchild->home);
#endif
		lastchild = found;
		return (char *) NULL;
	}
#ifdef DEBUG
	}
#endif DEBUG

	/* check and create dir if needed */

	if (seteuid(userid) < 0)
		plog(XLOG_WARNING, "could not seteuid to %d", userid);
	else {
		if (hlfsd_stat(linkval, &homestat) < 0) {

			if (errno == ENOENT) {
				/* don't use recursive mkdirs here */

				if (mkdir(linkval, PERS_SPOOLMODE) == 0) {
					seteuid(0);
					return linkval;
				}
				plog(XLOG_WARNING,
				     "can't make directory %s: %m", linkval);
			} else
				plog(XLOG_WARNING, "bad link to %s: %m",
				     linkval);
			seteuid(0);
			return alt_spooldir;
		}

		seteuid(0);
	}
	return linkval;
}

static int hlfsd_stat(path, statp)
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(found, secs)
uid2home_t *found;
int secs;
{
	struct timeval tv;

	tv.tv_usec = 0;

	do
	{
		tv.tv_sec = secs;
		if (select(0, 0, 0, 0, &tv) == 0)
			break;
	} while (--secs && found->child);
}

void interlock()
{
	int child;
#if defined(_POSIX_SOURCE) && !defined(sigmask) && !defined(_AIX) && !defined(__osf__) || defined(__svr4__)
	int	status;
#else
	union wait status;
#endif
	uid2home_t *lostchild;

#ifdef SYS5_SIGNALS
	if ((child = waitpid((pid_t) -1, (WAIT_STATUS_TYPE *) &status, WNOHANG))>0) {
#else
	while ((child = wait3((WAIT_STATUS_TYPE *) &status, WNOHANG, (struct rusage *) 0)) > 0) {
/* 	while ((child = wait3(&status, WNOHANG, (struct rusage *) 0)) > 0) { */
#endif /* SYS5_SIGNALS */
		if (lastchild && lastchild->child == child) {
			lastchild->child = 0;
			lastchild = (uid2home_t *) NULL;
		}
		else
		{
			for (lostchild = pwtab;
			     lostchild < &pwtab[cur_pwtab_num]; lostchild++)
			{
				if (lostchild->child == child) {
					lostchild->child = 0;
					break;
				}
			}
		}
	}
}



/*
 * 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(username)
char *username;
{
	register 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 (!strcmp(untab[max].username, username))
		return max;
	if (!strcmp(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(username)
char *username;
{
  register int idx;

  if ((idx = untab_index(username)) < 0) /* not found */
    return -3;			/* an invalid user id */
  return untab[idx].uid;
}

char *mailbox(uid)
int uid;
{
	uid2home_t *found;
	if (uid < 0)
		return (char *) NULL; /* not found */
	if ((found = plt_search(uid)) == (uid2home_t *) NULL) /* not found */
		return (char *) NULL;
	sprintf(mboxfile, "%s/%s/%s", found->home, home_subdir, found->uname);
	return mboxfile;
}

static int plt_compare_fxn(i, j)
uid2home_t *i, *j;
{
	return i->uid - j->uid;
}

static int unt_compare_fxn(i, j)
username2uid_t *i, *j;
{
	return strcmp(i->username, j->username);
}


static void plt_init()
{
	struct passwd *pent_p;

	if (plt_reset() < 0)	/* could not reset table. skip. */
		return;

	setpwent();		/* prepare to read passwd entries */
	while ((pent_p = getpwent()) != (struct passwd *) NULL)	{
		table_add(pent_p->pw_uid, pent_p->pw_dir, pent_p->pw_name);
	}
	endpwent();

#if defined(TEST_HOMEDIR) || defined(DEBUG_PRINT)
	plt_print();
#endif /* TEST_HOMEDIR || DEBUG_PRINT */

	(void) qsort((char *) pwtab, cur_pwtab_num, sizeof (uid2home_t),
		     plt_compare_fxn);
	(void) qsort((char *) untab, cur_pwtab_num, sizeof (username2uid_t),
		     unt_compare_fxn);

#if defined(TEST_HOMEDIR) || defined(DEBUG_PRINT)
	plt_print();
#endif /* TEST_HOMEDIR || DEBUG_PRINT */
}


/*
 * This is essentially so that we don't reset known good lookup tables when a
 * YP server goes down.
 */
static int plt_reset()
{
	register int i;

	setpwent();
	if (getpwent() == (struct passwd *) NULL) {
		endpwent();
		return -1;	/* did not reset table */
	}
	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)
				free(pwtab[i].home);
			pwtab[i].home = (char *) NULL;
			pwtab[i].uid = -3;	/* 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)
				free(untab[i].username);
			untab[i].username = (char *) NULL;
			untab[i].uid = -3; /* invalid uid */
		}
	cur_pwtab_num = 0;	/* zero current size */

	return 0;		/* resetting ok */
}


static void table_add(u, h, n)
int u;				/* uid number */
char *h;			/* home directory */
char *n;			/* user ID name */
{
	if (max_pwtab_num <= 0) {	/* was never initialized */
		max_pwtab_num = 1;
		pwtab = (uid2home_t *) xmalloc(max_pwtab_num *
					       sizeof (uid2home_t));
		untab = (username2uid_t *) xmalloc(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;
		pwtab = (uid2home_t *) xrealloc(pwtab,
					sizeof (uid2home_t) * max_pwtab_num);
		untab = (username2uid_t *) xrealloc(untab,
					sizeof (username2uid_t) *
					max_pwtab_num);
	}
	/* add new password entry */
	pwtab[cur_pwtab_num].home = xmalloc((strlen(h) + 1) * sizeof (char));
	strcpy(pwtab[cur_pwtab_num].home, h);
	pwtab[cur_pwtab_num].child = 0;
	pwtab[cur_pwtab_num].uid = u;
	/* add new userhome entry */
	untab[cur_pwtab_num].username = xmalloc((strlen(n) + 1) *
						sizeof (char));
	strcpy(untab[cur_pwtab_num].username, n);
	pwtab[cur_pwtab_num].uname = untab[cur_pwtab_num].username; /* just a second pointer */
	untab[cur_pwtab_num].uid = u;
	++cur_pwtab_num;	/* increment counter */
}


/* return entry in lookup table */
uid2home_t *plt_search(u)
int u;				/* user id */
{
	register int max, min, mid;

	/*
	 * empty table should not happen,
	 * but I have a bug with signals to trace...
	 */
	if (pwtab == (uid2home_t *) NULL)
	  return (uid2home_t *) NULL;

	max = cur_pwtab_num - 1;
	min = 0;

	do {
		mid = (max + min) / 2;
		if (pwtab[mid].uid == u)	/* record found! */
			return &pwtab[mid];
		if (pwtab[mid].uid > 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(TEST_HOMEDIR) || defined(DEBUG) || defined(DEBUG_PRINT)
void plt_print()
{
	FILE *dumpfile;
	register int i;

	if ((dumpfile = fopen("/tmp/hlfsdump", "a")) != NULL) {
		fprintf(dumpfile, "\n\nNew plt_dump():\n");
		for (i = 0; i < cur_pwtab_num; ++i)
			fprintf(dumpfile, "%4d %5d %10d:  %4d  \"%s\" uname=\"%s\"\n", i,
				pwtab[i].child, pwtab[i].last_access_time, 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\" %4d\n", i,
				untab[i].username, untab[i].uid);
		fclose(dumpfile);
	}
}

void plt_dump(lastc, this)
uid2home_t *lastc;
pid_t this;
{
	FILE *dumpfile;
	register int i;

	if ((dumpfile = fopen("/var/tmp/hlfsdump", "a")) != NULL) {
		fprintf(dumpfile, "\n\nNEW PLT_DUMP -- ");
		fprintf(dumpfile, "lastchild->child=%d ",
			lastc ? lastc->child : -999 );
		fprintf(dumpfile, ", child from wait3=%d:\n",this);
		for (i = 0; i < cur_pwtab_num; ++i)
			fprintf(dumpfile, "%4d %5d:  %4d  \"%s\" uname=\"%s\"\n", i,
				pwtab[i].child, 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\" %4d\n", i,
				untab[i].username, untab[i].uid);
		fprintf(dumpfile, "ezk: ent=%d, uid=%d, home=\"%s\"\n",
			untab_index("ezk"),
			untab[untab_index("ezk")].uid,
			pwtab[untab[untab_index("ezk")].uid].home);
		fprintf(dumpfile, "rezk: ent=%d, uid=%d, home=\"%s\"\n",
			untab_index("rezk"),
			untab[untab_index("rezk")].uid,
			pwtab[untab[untab_index("rezk")].uid].home);
		fclose(dumpfile);
	}
}
#endif

#ifdef TEST_HOMEDIR
main()
{				/* read and hash the passwd file or NIS map */
	register int i;

	printf("real uid = %d\n", getuid());
	printf("eff uid = %d\n", geteuid());

	plt_init();
	printf("\n");
	printf("%4d  \"%s\"\n", 4179, homedir(4179));

	printf("real uid = %d\n", getuid());
	printf("eff uid = %d\n", geteuid());
}

#endif
