/*++
/* NAME
/*	dict_unix 3
/* SUMMARY
/*	dictionary manager interface to UNIX tables
/* SYNOPSIS
/*	#include <dict_unix.h>
/*
/*	DICT	*dict_unix_open(map, dummy, dict_flags)
/*	const char *map;
/*	int	dummy;
/*	int	dict_flags;
/* DESCRIPTION
/*	dict_unix_open() makes the specified UNIX table accessible via
/*	the generic dictionary operations described in dict_open(3).
/*	The \fIdummy\fR argument is not used.
/*
/*	Known map names:
/* .IP passwd.byname
/*	The table is the UNIX password database. The key is a login name.
/*	The result is a password file entry in passwd(5) format.
/* .IP group.byname
/*	The table is the UNIX group database. The key is a group name.
/*	The result is a group file entry in group(5) format.
/* SEE ALSO
/*	dict(3) generic dictionary manager
/* DIAGNOSTICS
/*	Fatal errors: out of memory, unknown map name, attempt to update map.
/* LICENSE
/* .ad
/* .fi
/*	The Secure Mailer license must be distributed with this software.
/* AUTHOR(S)
/*	Wietse Venema
/*	IBM T.J. Watson Research
/*	P.O. Box 704
/*	Yorktown Heights, NY 10598, USA
/*--*/

/* System library. */

#include "sys_defs.h"
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <pwd.h>
#include <grp.h>

/* Utility library. */

#include "msg.h"
#include "mymalloc.h"
#include "vstring.h"
#include "stringops.h"
#include "dict.h"
#include "dict_unix.h"

/* Application-specific. */

typedef struct {
    DICT    dict;			/* generic members */
} DICT_UNIX;

/* dict_unix_getpwnam - find password table entry */

static const char *dict_unix_getpwnam(DICT *dict, const char *key)
{
    struct passwd *pwd;
    static VSTRING *buf;
    static int sanity_checked;

    dict->error = 0;

    /*
     * Optionally fold the key.
     */
    if (dict->flags & DICT_FLAG_FOLD_FIX) {
	if (dict->fold_buf == 0)
	    dict->fold_buf = vstring_alloc(10);
	vstring_strcpy(dict->fold_buf, key);
	key = lowercase(vstring_str(dict->fold_buf));
    }
    if ((pwd = getpwnam(key)) == 0) {
	if (sanity_checked == 0) {
	    sanity_checked = 1;
	    errno = 0;
	    if (getpwuid(0) == 0) {
		msg_warn("cannot access UNIX password database: %m");
		dict->error = DICT_ERR_RETRY;
	    }
	}
	return (0);
    } else {
	if (buf == 0)
	    buf = vstring_alloc(10);
	sanity_checked = 1;
	vstring_sprintf(buf, "%s:%s:%ld:%ld:%s:%s:%s",
			pwd->pw_name, pwd->pw_passwd, (long) pwd->pw_uid,
			(long) pwd->pw_gid, pwd->pw_gecos, pwd->pw_dir,
			pwd->pw_shell);
	return (vstring_str(buf));
    }
}

/* dict_unix_getgrnam - find group table entry */

static const char *dict_unix_getgrnam(DICT *dict, const char *key)
{
    struct group *grp;
    static VSTRING *buf;
    char  **cpp;
    static int sanity_checked;

    dict->error = 0;

    /*
     * Optionally fold the key.
     */
    if (dict->flags & DICT_FLAG_FOLD_FIX) {
	if (dict->fold_buf == 0)
	    dict->fold_buf = vstring_alloc(10);
	vstring_strcpy(dict->fold_buf, key);
	key = lowercase(vstring_str(dict->fold_buf));
    }
    if ((grp = getgrnam(key)) == 0) {
	if (sanity_checked == 0) {
	    sanity_checked = 1;
	    errno = 0;
	    if (getgrgid(0) == 0) {
		msg_warn("cannot access UNIX group database: %m");
		dict->error = DICT_ERR_RETRY;
	    }
	}
	return (0);
    } else {
	if (buf == 0)
	    buf = vstring_alloc(10);
	sanity_checked = 1;
	vstring_sprintf(buf, "%s:%s:%ld:",
			grp->gr_name, grp->gr_passwd, (long) grp->gr_gid);
	for (cpp = grp->gr_mem; *cpp; cpp++) {
	    vstring_strcat(buf, *cpp);
	    if (cpp[1])
		VSTRING_ADDCH(buf, ',');
	}
	VSTRING_TERMINATE(buf);
	return (vstring_str(buf));
    }
}

/* dict_unix_close - close UNIX map */

static void dict_unix_close(DICT *dict)
{
    if (dict->fold_buf)
	vstring_free(dict->fold_buf);
    dict_free(dict);
}

/* dict_unix_open - open UNIX map */

DICT   *dict_unix_open(const char *map, int open_flags, int dict_flags)
{
    DICT_UNIX *dict_unix;
    struct dict_unix_lookup {
	char   *name;
	const char *(*lookup) (DICT *, const char *);
    };
    static struct dict_unix_lookup dict_unix_lookup[] = {
	"passwd.byname", dict_unix_getpwnam,
	"group.byname", dict_unix_getgrnam,
	0,
    };
    struct dict_unix_lookup *lp;

    /*
     * Sanity checks.
     */
    if (open_flags != O_RDONLY)
	return (dict_surrogate(DICT_TYPE_UNIX, map, open_flags, dict_flags,
			       "%s:%s map requires O_RDONLY access mode",
			       DICT_TYPE_UNIX, map));

    /*
     * "Open" the database.
     */
    for (lp = dict_unix_lookup; /* void */ ; lp++) {
	if (lp->name == 0)
	    return (dict_surrogate(DICT_TYPE_UNIX, map, open_flags, dict_flags,
			      "unknown table: %s:%s", DICT_TYPE_UNIX, map));
	if (strcmp(map, lp->name) == 0)
	    break;
    }
    dict_unix = (DICT_UNIX *) dict_alloc(DICT_TYPE_UNIX, map,
					 sizeof(*dict_unix));
    dict_unix->dict.lookup = lp->lookup;
    dict_unix->dict.close = dict_unix_close;
    dict_unix->dict.flags = dict_flags | DICT_FLAG_FIXED;
    if (dict_flags & DICT_FLAG_FOLD_FIX)
	dict_unix->dict.fold_buf = vstring_alloc(10);
    dict_unix->dict.owner.status = DICT_OWNER_TRUSTED;

    return (DICT_DEBUG (&dict_unix->dict));
}
