/*
#ident	"@(#)smail/src:RELEASE-3_2_0_120:pwcache.c,v 1.27 2004/03/18 20:40:15 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 <stdio.h>
#include <pwd.h>
#include <grp.h>
#include <ctype.h>

#ifdef STDC_HEADERS
# include <stdlib.h>
# include <stddef.h>
#else
# ifdef HAVE_STDLIB_H
#  include <stdlib.h>
# endif
#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 "config.h"
#include "main.h"
#include "addr.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	5
#else
# define N_CACHE	31
#endif

/* buffer size to hold user and group names (with ending NUL byte) */
#define PW_SIZE		15		/* XXX should be system dependent!!! (LOGIN_NAME_MAX or UT_NAMESIZE) */

/* invalid names will begin with this char */
#define BAD_NAME	('@')

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

/* entries for the passwd file cache */
/* XXX this is bogus -- we should just use 'struct passwd' and xmalloc() the char * fields */
struct	pw_cache {
    char pw_name[PW_SIZE];		/* name of user */
    uid_t pw_uid;			/* uid of user */
    gid_t pw_gid;			/* base gid of user */
    char *pw_dir;			/* home directory */
    int	size_pw_dir;			/* size allocated for home directory */
};

/* entries for the group file cache */
/* XXX this is bogus -- we should just use 'struct group' and xmalloc() the char * fields */
struct	gr_cache {
    char gr_name[PW_SIZE];		/* name of group */
    uid_t gr_gid;			/* gid of group */
};

/* we rely on these being initialized to nulls */
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 */

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

    if (uid_cache == NULL) {
	/* XXX - hash table should be associated with a block */
	uid_cache = new_hash_table(N_CACHE,
				   (struct block *) NULL,
				   HASH_DEFAULT);
    }
    (void) sprintf(uid_buf, "%lu", (unsigned long int) uid);
    if (lookup_in_hash(uid_buf, (char **) &entp, (size_t *) NULL,
		       uid_cache) == ALREADY_HASHED)
    {
	if (entp->pw_name[0] == BAD_NAME) {
	    return NULL;
	}
	ret.pw_name = entp->pw_name;
	ret.pw_uid = entp->pw_uid;
	ret.pw_gid = entp->pw_gid;
	ret.pw_dir = entp->pw_dir;
	return &ret;
    } else {
	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, (char *) &ent, sizeof(ent), uid_cache);
	    return NULL;
	}
	if (user_cache == NULL) {
	    /* XXX - hash table should be associated with a block */
	    user_cache = new_hash_table(N_CACHE,
					(struct block *) NULL,
					HASH_DEFAULT);
	}
	fill_pw_cache(pw);
	return pw;
    }
}

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

    if (gid_cache == NULL) {
	/* XXX - hash table should be associated with a block */
	gid_cache = new_hash_table(N_CACHE,
				   (struct block *) NULL,
				   HASH_DEFAULT);
    }
    (void) sprintf(gid_buf, "%lu", (unsigned long int) gid);
    if (lookup_in_hash(gid_buf, (char **) &entp, (size_t *) NULL,
		       gid_cache) == ALREADY_HASHED)
    {
	if (entp->gr_name[0] == BAD_NAME) {
	    return NULL;
	}
	ret.gr_name = entp->gr_name;
	ret.gr_gid = entp->gr_gid;
	return &ret;
    } else {
	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, (char *) &ent, sizeof(ent), gid_cache);
	    return NULL;
	}
	if (group_cache == NULL) {
	    /* XXX - hash table should be associated with a block */
	    group_cache = new_hash_table(N_CACHE,
					 (struct block *) NULL,
					 HASH_DEFAULT);
	}
	fill_gr_cache(gr);
	return gr;
    }
}

/*
 * username->uid, through cache
 */
struct passwd *
getpwbyname(icase, user)
    smail_bool_t icase;
    char *user;
{
    struct pw_cache *entp, ent;
    static struct passwd ret;

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

    /*
     * XXX until PW_SIZE == LOGIN_NAME_MAX this is bogus
     */
    if ((int) strlen(user) > (PW_SIZE - 1)) {
	DEBUG1(DBG_HASH_LO, "getpwbyname(): username too long: '%s'\n", user);
	return NULL;
    }
    if (user_cache == NULL) {
	/* XXX - hash table should be associated with a block */
	user_cache = new_hash_table(N_CACHE,
				    (struct block *) NULL,
				    HASH_DEFAULT);
    }
    if (lookup_in_hash(user, (char **) &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;
	}
	ret.pw_name = entp->pw_name;
	ret.pw_uid = entp->pw_uid;
	ret.pw_gid = entp->pw_gid;
	ret.pw_dir = entp->pw_dir;
	return &ret;
    } else {
	register struct passwd *pw;

	(void) strncpy(ent.pw_name, user, (size_t) (PW_SIZE - 1));
	ent.pw_name[PW_SIZE - 1] = '\0';
	setpwent();
	/* try the direct approach first */
	pw = getpwnam(ent.pw_name);
	if (! pw) {
	    if (icase) {
		/* otherwise do it the hard way if we have to... */
		setpwent();
		while ((pw = getpwent())) {
		    if (strcmpic(pw->pw_name, ent.pw_name) == 0) {
			DEBUG2(DBG_HASH_MID, "getpwbyname(): found username with different case: '%s' = '%s'\n",
			       pw->pw_name, user);
			break;
		    }
		    /* XXX maybe we should cache all of these? */
		}
	    }
	    if (! pw) {
		if (icase) {
		    DEBUG1(DBG_HASH_MID, "getpwbyname(): caching invalid username: '%s'\n", user);
		    ent.pw_uid = BAD_ID;
		    (void) add_to_hash(user, (char *) &ent, sizeof(ent), user_cache);
		}
		return NULL;
	    }
	}
	if (uid_cache == NULL) {
	    /* XXX - hash table should be associated with a block */
	    uid_cache = new_hash_table(N_CACHE,
				       (struct block *) NULL,
				       HASH_DEFAULT);
	}
	fill_pw_cache(pw);
	return pw;
    }
}

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

    if ((int) strlen(group) > (PW_SIZE - 1)) {
	DEBUG1(DBG_HASH_LO, "getgrbyname(): groupname too long: '%s'\n", group);
	return NULL;
    }
    if (group_cache == NULL) {
	/* XXX - hash table should be associated with a block */
	group_cache = new_hash_table(N_CACHE,
				     (struct block *) NULL,
				     HASH_DEFAULT);
    }
    if (lookup_in_hash(group, (char **) &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;
	}
	ret.gr_gid = entp->gr_gid;
	ret.gr_name = entp->gr_name;
	return &ret;
    } else {
	register struct group *gr;

	(void) strncpy(ent.gr_name, group, (size_t) (PW_SIZE - 1));
	ent.gr_name[PW_SIZE - 1] = '\0';
	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, (char *) &ent, sizeof(ent), group_cache);
	    return NULL;
	}
	if (gid_cache == NULL) {
	    /* XXX - hash table should be associated with a block */
	    gid_cache = new_hash_table(N_CACHE,
				       (struct block *) NULL,
				       HASH_DEFAULT);
	}
	fill_gr_cache(gr);
	return gr;
    }
}

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

    (void) strncpy(ent.pw_name, pw->pw_name, (size_t) (PW_SIZE - 1));
    ent.pw_name[PW_SIZE - 1] = '\0';
    ent.pw_uid = pw->pw_uid;
    ent.pw_gid = pw->pw_gid;
    ent.pw_dir = COPY_STRING(pw->pw_dir);

    (void) add_to_hash(ent.pw_name, (char *) &ent, sizeof(ent), user_cache);

    (void) sprintf(uid_buf, "%lu", (unsigned long int) pw->pw_uid);
    (void) add_to_hash(uid_buf, (char *) &ent, sizeof(ent), 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 gr_cache ent;		/* fill this with gr info */
    char gid_buf[MAXLONG_B10_DIGITS + 1];

    (void) strncpy(ent.gr_name, gr->gr_name, (size_t) (PW_SIZE - 1));
    ent.gr_name[PW_SIZE - 1] = '\0';
    ent.gr_gid = gr->gr_gid;

    (void) add_to_hash(ent.gr_name, (char *) &ent, sizeof(ent), group_cache);

    (void) sprintf(gid_buf, "%lu", (unsigned long int) gr->gr_gid);
    (void) add_to_hash(gid_buf, (char *) &ent, sizeof(ent), gid_cache);

    return;
}

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