/*
#ident	"@(#)smail/src:RELEASE-3_2_0_114:pwcache.c,v 1.14 2000/09/11 01:17: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 <sys/types.h>
#include <stdio.h>
#include <pwd.h>
#include <grp.h>
#include <ctype.h>
#include "defs.h"
#include "smail.h"
#include "main.h"
#include "addr.h"
#include "hash.h"
#include "alloc.h"
#include "dys.h"
#include "extern.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? */

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

/* invalid id's are set to this unlikely id */
#define BAD_ID		(-10101)

/* entries for the passwd file cache */
struct	pw_cache {
    char pw_name[PW_SIZE];		/* name of user */
    int	pw_uid;				/* uid of user */
    int	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 */
struct	gr_cache {
    char gr_name[PW_SIZE];		/* name of group */
    int 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)
    unsigned int uid;
{
    struct pw_cache *entp, ent;
    static struct passwd ret;
    static char uid_buf[50];		/* big enough to hold a number */

    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, "%d", uid);
    if (lookup_in_hash(uid_buf, (char **)&entp, (int *)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 == NULL) {
	    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)
    unsigned int gid;
{
    struct gr_cache *entp, ent;
    static struct group ret;
    static char gid_buf[50];		/* big enough to hold a number */

    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, "%d", gid);
    if (lookup_in_hash(gid_buf, (char **)&entp, (int *)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 == NULL) {
	    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(user)
    char *user;
{
    struct pw_cache *entp, ent;
    static struct passwd ret;

    if ((int)strlen(user) > PW_SIZE - 1) {
	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, (int *)NULL,
		       user_cache) == ALREADY_HASHED)
    {
	if (entp->pw_uid == BAD_ID) {
	    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;
	register char *p;

	(void) strncpy(ent.pw_name, user, PW_SIZE - 1);
	ent.pw_name[PW_SIZE - 1] = '\0';
	for (p = ent.pw_name; *p; p++) {
	    *p = lowercase((unsigned char) *p);
	}

	setpwent();
	pw = getpwnam(ent.pw_name);
	if (pw == NULL) {
	    (void) strncpy(ent.pw_name, user, PW_SIZE - 1);
	    ent.pw_name[PW_SIZE - 1] = '\0';
	    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) {
	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, (int *)NULL,
		       group_cache) == ALREADY_HASHED)
    {
	if (entp->gr_gid == BAD_ID) {
	    return NULL;
	}
	ret.gr_gid = entp->gr_gid;
	ret.gr_name = entp->gr_name;
	return &ret;
    } else {
	register struct group *gr;
	register char *p;

	(void) strncpy(ent.gr_name, group, PW_SIZE - 1);
	ent.gr_name[PW_SIZE - 1] = '\0';
	for (p = ent.gr_name; *p; p++) {
	    *p = lowercase((unsigned char) *p);
	}

	setgrent();
	gr = getgrnam(ent.gr_name);
	if (gr == NULL) {
	    (void) strncpy(ent.gr_name, group, PW_SIZE - 1);
	    ent.gr_name[PW_SIZE - 1] = '\0';
	    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[50];			/* big enough to hold a number */
    register char *p;
    unsigned int size_pw_dir;

    (void) strncpy(ent.pw_name, pw->pw_name, PW_SIZE - 1);
    ent.pw_name[PW_SIZE - 1] = '\0';
    for (p = ent.pw_name; *p; p++) {
	*p = lowercase((unsigned char) *p);
    }
    ent.pw_uid = pw->pw_uid;
    ent.pw_gid = pw->pw_gid;
    size_pw_dir = strlen(pw->pw_dir) + 1;
    ent.pw_dir = xmalloc(size_pw_dir);
    (void) strcpy(ent.pw_dir, pw->pw_dir);
    (void) sprintf(uid_buf, "%lu", (long unsigned int) pw->pw_uid);
    (void) add_to_hash(uid_buf, (char *)&ent, sizeof(ent), uid_cache);
    (void) add_to_hash(ent.pw_name, (char *)&ent, sizeof(ent), user_cache);
}

/* 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[50];			/* big enough to hold a number */
    register char *p;

    (void) strncpy(ent.gr_name, gr->gr_name, PW_SIZE - 1);
    ent.gr_name[PW_SIZE - 1] = '\0';
    for (p = ent.gr_name; *p; p++) {
	*p = lowercase((unsigned char) *p);
    }
    ent.gr_gid = gr->gr_gid;
    (void)sprintf(gid_buf, "%lu", (long unsigned int) gr->gr_gid);
    (void) add_to_hash(gid_buf, (char *)&ent, sizeof(ent), gid_cache);
    (void) add_to_hash(ent.gr_name, (char *)&ent, sizeof(ent), group_cache);
}
