/*
#ident	"@(#)smail/src:RELEASE-3_2_0_121:pwcache.c,v 1.32 2005/07/11 19:22:09 woods Exp"
 */

/*
 *    Copyright (C) 1987, 1988 Ronald S. Karr and Landon Curt Noll
 *    Copyright (C) 1992  Ronald S. Karr
 * 
 * See the file COPYING, distributed with smail, for restriction
 * and warranty information.
 */

/*
 * pwcache:
 *	manage a passwd and group entry cache.
 *
 * The mailer can make a large number of acesses to the passwd and
 * group files while processing a message.  To increase the efficiency
 * of this, we maintain a cache of entries read from each of these
 * files.
 */

#include "defs.h"

#include <sys/types.h>
#include <sys/param.h>
#include <stdio.h>
#include <ctype.h>
#include <limits.h>
#include <grp.h>
#include <pwd.h>
#include <utmp.h>

#ifdef STDC_HEADERS
# include <stdlib.h>
# include <stddef.h>
#else
# ifdef HAVE_STDLIB_H
#  include <stdlib.h>
# endif
#endif

#if defined(HAVE_UNISTD_H)
# include <unistd.h>
#endif

#ifdef HAVE_STRING_H
# if !defined(STDC_HEADERS) && defined(HAVE_MEMORY_H)
#  include <memory.h>
# endif
# include <string.h>
#endif
#ifdef HAVE_STRINGS_H
# include <strings.h>
#endif

#ifdef __STDC__
# include <stdarg.h>
#else
# include <varargs.h>
#endif

#include "smail.h"
#include "alloc.h"
#include "list.h"
#include "main.h"
#include "addr.h"
#include "exitcodes.h"
#include "log.h"
#include "hash.h"
#include "smailstring.h"
#include "dys.h"
#include "extern.h"
#include "debug.h"
#include "smailport.h"

/* functions local to this file */
static void fill_pw_cache __P((struct passwd *));
static void fill_gr_cache __P((struct group *));

/*
 * There are separate caches for four mappings:
 *
 * uid->username gid->groupname username->uid groupname->gid
 */

/* number of entries in each cache */
#ifdef	SMALL_MEMORY
# define N_CACHE	16
#else
# define N_CACHE	128
#endif

/* invalid names will begin with this char */
#define BAD_NAME	(':')		/* XXX assumes unix passwd format */

/* invalid ID's are set to this ID */
#define BAD_ID		BOGUS_USER	/* should be (-1) */

static struct hash_table *uid_cache = NULL; /* uid->username cache */
static struct hash_table *gid_cache = NULL; /* gid->groupname cache */
static struct hash_table *user_cache = NULL; /* username->uid cache */
static struct hash_table *group_cache = NULL; /* groupname->gid cache */



static struct passwd pwbyid_ret;

/*
 * uid->username, through cache
 */
struct passwd *
getpwbyuid(uid)
    uid_t uid;
{
    struct passwd *entp;
    static char uid_buf[MAXLONG_B10_DIGITS + 1];

    if (uid_cache == NULL) {
	uid_cache = new_hash_table(N_CACHE,
				   (struct block *) NULL, /* XXX pwcache block */
				   HASH_DEFAULT);
    }
    (void) sprintf(uid_buf, "%u", (unsigned int) uid);
    if (lookup_in_hash(uid_buf, (void **) &entp, (size_t *) NULL,
		       uid_cache) == ALREADY_HASHED)
    {
	if (entp->pw_name[0] == BAD_NAME) {
	    return NULL;
	}
	pwbyid_ret = *entp;		/* use our own local storage */

	return &pwbyid_ret;
    } else {
	struct passwd ent;		/* may add to uid_cache as BAD_NAME */
	register struct passwd *pw;

	setpwent();
	pw = getpwuid(uid);
	if (! pw) {
	    ent.pw_uid = uid;
	    ent.pw_name[0] = BAD_NAME;
	    (void) add_to_hash(uid_buf, (void *) &ent, sizeof(ent), uid_cache);
	    return NULL;
	}
	if (user_cache == NULL) {
	    user_cache = new_hash_table(N_CACHE,
					(struct block *) NULL, /* XXX pwcache block */
					HASH_DEFAULT);
	}
	fill_pw_cache(pw);
	pwbyid_ret = *pw;		/* use our own local storage */

	return &pwbyid_ret;
    }
}

static struct group grbyid_ret;

/*
 * gid->groupname, through cache
 */
struct group *
getgrbygid(gid)
    gid_t gid;
{
    struct group *entp;
    static char gid_buf[MAXLONG_B10_DIGITS + 1];

    if (gid_cache == NULL) {
	gid_cache = new_hash_table(N_CACHE,
				   (struct block *) NULL, /* XXX grcache block */
				   HASH_DEFAULT);
    }
    (void) sprintf(gid_buf, "%u", (unsigned int) gid);
    if (lookup_in_hash(gid_buf, (void **) &entp, (size_t *) NULL,
		       gid_cache) == ALREADY_HASHED)
    {
	if (entp->gr_name[0] == BAD_NAME) {
	    return NULL;
	}
	grbyid_ret = *entp;		/* use our own local storage */

	return &grbyid_ret;
    } else {
	struct group ent;		/* may add to gid_cache as BAD_NAME */
	register struct group *gr;

	setgrent();
	gr = getgrgid(gid);
	if (! gr) {
	    ent.gr_gid = gid;
	    ent.gr_name[0] = BAD_NAME;
	    (void) add_to_hash(gid_buf, (void *) &ent, sizeof(ent), gid_cache);

	    return NULL;
	}
	if (group_cache == NULL) {
	    group_cache = new_hash_table(N_CACHE,
					 (struct block *) NULL,	/* XXX grcache block */
					 HASH_DEFAULT);
	}
	fill_gr_cache(gr);
	grbyid_ret = *gr;		/* use our own local storage */

	return &grbyid_ret;
    }
}

static struct passwd pwbynm_ret;

/*
 * username->uid, through cache
 */
struct passwd *
getpwbyname(icase, user)
    int icase;
    char *user;
{
    struct passwd *entp;

    DEBUG2(DBG_HASH_HI, "getpwbyname(): called for %s username: '%s'\n", icase ? "caseless" : "exact", user);

    if (user_cache == NULL) {
	user_cache = new_hash_table(N_CACHE,
				    (struct block *) NULL, /* XXX pwcache block */
				    HASH_DEFAULT);
    }
    if (lookup_in_hash(user, (void **) &entp, (size_t *) NULL,
		       user_cache) == ALREADY_HASHED)
    {
	if (entp->pw_uid == BAD_ID) {
	    DEBUG1(DBG_HASH_HI, "getpwbyname(): found previously cached BAD_ID: '%s'\n", user);
	    return NULL;
	}
	pwbynm_ret = *entp;		/* use our own local storage */

	return &pwbynm_ret;
    } else {
	struct passwd ent;		/* may add to user_cache as BAD_ID */
	register struct passwd *pw;

	ent.pw_name = COPY_STRING(user);
	if (icase) {
	    str2lower(ent.pw_name);
	}
	setpwent();
	pw = getpwnam(ent.pw_name);
	if (! pw) {
		DEBUG1(DBG_HASH_MID, "getpwbyname(): caching invalid username: '%s'\n", user);
		ent.pw_uid = BAD_ID;
		(void) add_to_hash(user, (void *) &ent, sizeof(ent), user_cache);

		return NULL;
	}
	if (uid_cache == NULL) {
	    uid_cache = new_hash_table(N_CACHE,
				       (struct block *) NULL, /* XXX pwcache block */
				       HASH_DEFAULT);
	}
	fill_pw_cache(pw);
	pwbynm_ret = *pw;		/* use our own local storage */

	xfree(ent.pw_name);

	return &pwbynm_ret;
    }
}

static struct group grbynm_ret;

/*
 * groupname->gid, through cache
 */
struct group *
getgrbyname(group)
    char *group;
{
    struct group *entp;

    if (group_cache == NULL) {
	group_cache = new_hash_table(N_CACHE,
				     (struct block *) NULL, /* XXX grcache block */
				     HASH_DEFAULT);
    }
    if (lookup_in_hash(group, (void **) &entp, (size_t *) NULL,
		       group_cache) == ALREADY_HASHED)
    {
	if (entp->gr_gid == BAD_ID) {
	    DEBUG1(DBG_HASH_HI, "getgrbyname(): found previously cached BAD_ID: '%s'\n", group);
	    return NULL;
	}
	grbynm_ret = *entp;		/* use our own local storage */

	return &grbynm_ret;
    } else {
	struct group ent;		/* may add to group_cache as BAD_ID */
	register struct group *gr;

	ent.gr_name = group;
	setgrent();
	gr = getgrnam(ent.gr_name);
	if (! gr) {
	    DEBUG1(DBG_HASH_MID, "getgrbyname(): caching invalid groupname: '%s'\n", group);
	    ent.gr_gid = BAD_ID;
	    (void) add_to_hash(group, (void *) &ent, sizeof(ent), group_cache);

	    return NULL;
	}
	if (gid_cache == NULL) {
	    gid_cache = new_hash_table(N_CACHE,
				       (struct block *) NULL, /* XXX grcache block */
				       HASH_DEFAULT);
	}
	fill_gr_cache(gr);
	grbynm_ret = *gr;		/* use our own local storage */

	return &grbynm_ret;
    }
}

/* fill the uid and username caches from a passwd file entry */
static void
fill_pw_cache(pw)
    register struct passwd *pw;
{
    struct passwd newpw;
    char uid_buf[MAXLONG_B10_DIGITS + 1];

    /* NOTE:  only fills the fields we care about... */
    memset((void *) &newpw, '\0', sizeof(newpw));
    newpw.pw_name = COPY_STRING(pw->pw_name); /* XXX alloc in pwcache block... */
    newpw.pw_gecos = COPY_STRING(pw->pw_gecos); /* XXX alloc in pwcache block... */
    newpw.pw_uid = pw->pw_uid;
    newpw.pw_gid = pw->pw_gid;
    newpw.pw_dir = COPY_STRING(pw->pw_dir); /* XXX alloc in pwcache block... */

    (void) add_to_hash(pw->pw_name, (void *) &newpw, sizeof(newpw), user_cache);

    (void) sprintf(uid_buf, "%u", (unsigned int) pw->pw_uid);
    (void) add_to_hash(uid_buf, (void *) &newpw, sizeof(newpw), uid_cache);

    return;
}

/* fill the gid and groupname caches from a group file entry */
static void
fill_gr_cache(gr)
    register struct group *gr;
{
    struct group newgr;
    char gid_buf[MAXLONG_B10_DIGITS + 1];

    /* NOTE:  only fills the fields we care about... */
    memset((void *) &newgr, '\0', sizeof(newgr));
    newgr.gr_name = COPY_STRING(gr->gr_name); /* XXX alloc in grcache block... */
    newgr.gr_gid = gr->gr_gid;

    (void) add_to_hash(gr->gr_name, (void *) &newgr, sizeof(newgr), group_cache);

    (void) sprintf(gid_buf, "%u", (unsigned int) gr->gr_gid);
    (void) add_to_hash(gid_buf, (void *) &newgr, sizeof(newgr), gid_cache);

    return;
}

/* 
 * Local Variables:
 * c-file-style: "smail"
 * End:
 */
