/*
#ident	"@(#)smail/src:RELEASE-3_2_0_120:lookup.c,v 1.99 2004/05/28 19:45:34 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.
 */

/*
 * lookup.c:
 *	search for values corresponding to keys using a specified
 *	access method.
 *
 *	external functions:  open_database, close_database, lookup_database
 *
 * NeXT NetInfo aliases support added by Dan Danz <dan@az.stratus.com>
 */

#include "defs.h"

#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <ctype.h>
#include <setjmp.h>
#include <signal.h>
#include <errno.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

#if defined(UNIX_SYS5) || defined(POSIX_OS) || defined(USE_FCNTL)
# include <fcntl.h>
#else
# if defined(UNIX_BSD)
#  include <sys/file.h>
# endif
#endif
#ifdef __GLIBC__
# include <sys/file.h>
#endif
#ifdef HAVE_SYS_LOCKING_H
# include <sys/locking.h>
#endif

#ifdef HAVE_YP
# include "jump.h"
# include <rpcsvc/ypclnt.h>
#endif

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

#ifdef HAVE_NIALIAS
# include <aliasdb.h>
#endif
/*
 * XXX WARNING: on SunOS-5.6 you'll get a T_UNSPEC redefined warning in
 * <sys/tpicommon.h> as it is also defined in <arpa/nameser.h>....
 */
#ifdef  HAVE_NISPLUS
# include <rpcsvc/nis.h>
#endif

#include "smail.h"
#include "alloc.h"
#include "list.h"
#include "smailsock.h"
#include "main.h"
#include "parse.h"
#include "addr.h"
#include "smailstring.h"
#include "dys.h"
#include "log.h"
#include "lookup.h"
#ifdef USE_LSEARCH_REGEXCMP
# include <pcre.h>
# include "match.h"
#endif
#include "exitcodes.h"
#include "smailconf.h"
#include "extern.h"
#include "debug.h"
#include "smailport.h"

#if defined(HAVE_DBM) && !defined(HAVE_NDBM)
# undef NULL
# include <dbm.h>
# undef NULL
# define NULL 0
#else
# ifdef HAVE_NDBM
#  include <ndbm.h>
#  ifdef __bsdi__
extern int dbm_error __P((DBM *));
#  endif
# else
#  include "sdbm.h"
#  define HAVE_NDBM
# endif
#endif

#ifdef STANDALONE
# define xmalloc malloc
# define xrealloc realloc
# define xfree free
#endif	/* STANDALONE */

/*
 * The following structure defines the available access methods.
 *
 * Methods are identified by their protocol name when the open_database()
 * generic wrapper function is called.
 */
struct db_proto {
    char *proto;				/* name of the access method */
    int (*open) __P((char *, struct db_proto *, int, unsigned int, struct stat *, char **, char **)); /* open database function */
    void (*close) __P((struct generic_db *));	/* close database function */
    int (*lookup) __P((struct generic_db *, char *, char **, char **)); /* lookup in database function */
};

/* functions local to this file */
static int bsearch_open __P((char *, struct db_proto *, int, unsigned int, struct stat *, char **, char **));
static void bsearch_close __P((struct generic_db *));
static int bsearch_lookup __P((struct generic_db *, char *, char **, char **));

static int lsearch_open __P((char *, struct db_proto *, int, unsigned int, struct stat *, char **, char **));
static void lsearch_close __P((struct generic_db *));
static int lsearch_lookup __P((struct generic_db *, char *, char **, char **));
#ifdef USE_LSEARCH_REGEXCMP
static int lsearch_regexcmp __P((char *, char *, size_t *));
#endif

static int dbmbase_open __P((char *, struct db_proto *, int, unsigned int, struct stat *, char **, char **));
static void dbmbase_close __P((struct generic_db *));
static int dbmbase_lookup __P((struct generic_db *, char *, char **, char **));

#ifdef	HAVE_YP
static int yp_open __P((char *, struct db_proto *, int, unsigned int, struct stat *, char **, char **));
static void yp_close __P((struct generic_db *));
static int yp_lookup __P((struct generic_db *, char *, char **, char **));

static int aliasyp_open __P((char *, struct db_proto *, int, unsigned int, struct stat *, char **, char **));
static void aliasyp_close __P((struct generic_db *));
static int aliasyp_lookup __P((struct generic_db *, char *, char **, char **));
#endif

#ifdef HAVE_NIALIAS
static int aliasni_open __P((char *, struct db_proto *, int, unsigned int, struct stat *, char **, char **));
static void aliasni_close __P((struct generic_db *));
static int aliasni_lookup __P((struct generic_db *, char *, char **, char **));
#endif

#ifdef  HAVE_NISPLUS
static int nisplus_open __P((char *, struct db_proto *, int, unsigned int, struct stat *, char **, char **));
static void nisplus_close __P((struct generic_db *));
static int nisplus_lookup __P((struct generic_db *, char *, char **, char **));
#endif

static struct db_proto protos[] = {
    { "bsearch",			/* binary search */
      bsearch_open, bsearch_close, bsearch_lookup },
    { "lsearch",
      lsearch_open, lsearch_close, lsearch_lookup },
    { "dbm",				/* DBM database search */
      dbmbase_open, dbmbase_close, dbmbase_lookup },
#ifdef	HAVE_YP
    { "yp",				/* YP remote database search */
      yp_open, yp_close, yp_lookup },
    { "aliasyp",			/* mail.aliases-style YP database */
      aliasyp_open, aliasyp_close, aliasyp_lookup },
#endif
#ifdef HAVE_NIALIAS
    { "nialias",			/* NetInfo remote alias search */
      aliasni_open, aliasni_close, aliasni_lookup },
#endif /* HAVE_NIALIAS */
#ifdef  HAVE_NISPLUS
    { "nisplus",			/* NIS+ remote database search */
      nisplus_open, nisplus_close, nisplus_lookup },
#endif /* HAVE_NISPLUS */
#ifdef FIXME
    { NULL, NULL, NULL, NULL }
#endif
};
/* FIXME:  avoid this ugly stuff -- use null-entry termination semantics... */
/* point to end of protos table */
struct db_proto *end_protos = protos + sizeof(protos)/sizeof(protos[0]);

/*
 * open_database - open a database of the specified type
 *
 * given a database name and an access method return a pointer to opaque
 * data that can be used to access that database.
 *
 * return:
 *   FILE_SUCCEED
 *		if the open was successful.  The database info will
 *		be stored in *db.
 *   FILE_AGAIN	if the open failed but may succeed at a later time
 *		(e.g., a remote host is down right now).  An error will
 *		be stored in *error.
 *   FILE_FAIL	if an unrecoverable failure occured.  An error will be
 *		stored in *error.
 *   FILE_NOMATCH
 *		if the database does not appear to exist.  An error will
 *		be stored in *error.
 */
int
open_database(name, proto, retries, interval, statp, db, error)
    char *name;				/* name of database */
    char *proto;			/* access method name */
    int retries;			/* retry count */
    unsigned int interval;		/* retry interval */
    struct stat *statp;			/* return a stat buffer */
    char **db;				/* store open database info here */
    char **error;			/* store error message here */
{
    register struct db_proto *pp;

    DEBUG2(DBG_DRIVER_HI, "open_database: db name = %v, db proto = %v.\n", name, proto);

    /* FIXME:  avoid this ugly stuff -- use null-entry termination semantics... */
    for (pp = protos; proto && pp < end_protos; pp++) {
	DEBUG1(DBG_DRIVER_HI, "open_database: checking against db proto = %s.\n", pp->proto);
	if (EQ(proto, pp->proto)) {
	    /* found the requested access method */
	    DEBUG1(DBG_DRIVER_HI, "open_database: found matching db proto = %s.\n", pp->proto);
	    return (*pp->open)(name, pp, retries, interval, statp, db, error);
	}
    }

    /* access method was not found */
    *error = "unknown access protocol";
    return FILE_FAIL;
}

/*
 * close_database - close an open database and free its resources
 */
void
close_database(priv)
    char *priv;				/* database's private data */
{
    register struct generic_db *gp = (struct generic_db *)priv;

    (*gp->proto->close)(gp);
}

/*
 * lookup_database - find a value corresponding to a key
 *
 * given an open database, perform a lookup operation to find a match for
 * a given key.
 *
 * Return:
 *   DB_SUCCEED if the lookup was successful.  The matched value will
 * 		be stored in *value.
 *   DB_NOMATCH	if the lookup operation did not find a match for the key.
 *   DB_AGAIN	if the lookup operation failed but may succeed at a later
 *		time (e.g., a remote host is down right now).  An error
 *		will be stored in *error.
 *   DB_FAIL	if an unrecoverable failure occured.  An error will be
 *		stored in *error.
 *   FILE_AGAIN	if the lookup operation failed because of an error accessing
 *		the file.  The file should be considered unreachable until
 *		some later time.
 *   FILE_FAIL	if the lookup operation failed because of a permanent error
 *		accessing the file.  Retrying at a later time is not assumed
 *		to be possible.
 */
int
lookup_database(db, key, valuep, errorp)
    char *db;				/* open database */
    char *key;				/* search key */
    char **valuep;			/* store value here */
    char **errorp;			/* store error message here */
{
    register struct generic_db *gdb = (struct generic_db *)db;
    register int ret;

    DEBUG1(DBG_DRIVER_HI, "lookup_database(): looking for '%v'.\n", key);

    *valuep = (char *) NULL;
    *errorp = (char *) NULL;
    ret = (*gdb->proto->lookup)(gdb, key, valuep, errorp);

    DEBUG4(DBG_DRIVER_HI,
	   "lookup_database(): returning %d, after looking for '%v', found value = '%v', error = %s.\n",
	   ret, key,
	   *valuep ? *valuep : "(none)",
	   *errorp ? *errorp : "(none)");

    return ret;
}

/*
 * bsearch access method:
 *
 *	access a file containing sorted lines of data.  keys
 *	are at the start of each line followed by a colon and/or
 *	white space.
 */

/* private data: */
struct bsearch_db {
    struct db_proto *proto;		/* access method table entry */
    char *name;				/* name of file */
    FILE *f;				/* open file */
    long size;				/* size of file */
};

/* bsearch_open - open a sorted file */
static int
bsearch_open(name, proto, retries, interval, statp, db, error)
    char *name;				/* name of file */
    struct db_proto *proto;		/* access method */
    int retries;			/* retry count */
    unsigned int interval;		/* retry interval */
    struct stat *statp;			/* save stat results here */
    char **db;				/* store open database info here */
    char **error;			/* store error message here */
{
    register struct bsearch_db *priv;
    register FILE *f;

    DEBUG1(DBG_DRIVER_HI, "bsearch_open: called to open %v.\n", name);

    name = make_lib_fn(name);
    if (name == NULL) {
	*error = "No directory for file";
	return FILE_FAIL;
    }
    f = fopen(name, "r");
    if (f == NULL) {
	int left = retries;

	while (left-- > 0) {
	    DEBUG2(DBG_DRIVER_HI, "bsearch_open: will retry open(%v) after %d seconds\n", name, interval);
	    (void) sleep(interval);
	    if ((f = fopen(name, "r"))) break;
	}
	if (f == NULL) {
	    *error = strerror(errno);
	    if (errno == ENOENT) {
		return FILE_NOMATCH;
	    }
	    return FILE_FAIL;
	}
    }

#if !defined(HAVE_RENAME) && defined(lock_fd_rd)
    /*
     * Lock the file to make sure no-one else is playing with it.  This
     * might be superfluous since the only time we would really care is if
     * we happen to start reading just after the file has been truncated by
     * a poorly designed editor in preparation for rewrite.
     *
     * It may also be counter-productive or dangerous since any user can
     * place an advisory exclusive lock on any file for any amount of time.
     *
     * However normally these files will be updated by our very own mkaliases
     * utility so we're going to assume the best and only use locks if we
     * really have to.
     */
    if (lock_fd_rd(fileno(f)) < 0) {
	*error = xprintf("%v database is locked, try again later", name);
	return FILE_AGAIN;
    }
#endif

    /* seek to the end of the file to find the size */
    (void) fseek(f, 0L, 2);

    /* build the private data */
    priv = (struct bsearch_db *)xmalloc(sizeof(*priv));
    priv->proto = proto;
    priv->name = name;
    priv->f = f;
    priv->size = ftell(f);		/* remember the fseek() */
    if (statp) {
	(void) fstat(fileno(f), statp);
    }

    *db = (char *)priv;
    return FILE_SUCCEED;
}

/* bsearch_close - close the file */
static void
bsearch_close(gdb)
    struct generic_db *gdb;
{
    register struct bsearch_db *db = (struct bsearch_db *) gdb;

    DEBUG1(DBG_DRIVER_HI, "bsearch_close: called to close %v.\n", db->name);

    (void) fclose(db->f);
    xfree(db->name);
    xfree((char *)db);
}

/*
 * bsearch_lookup - look up key in ascii sorted key/data line database.
 *
 * This routine taken from smail version 2.3, though it has been
 * modified to work with the new version and has also been
 * generalized from the original routine getpath().
 */
/*ARGSUSED*/
static int
bsearch_lookup(gdb, key, value, error)
    register struct generic_db *gdb;
    char *key;
    char **value;
    char **error;
{
    long middle, hi, lo;
    int c;
    int flag;
    static struct str str;		/* string in which to store data */
    static int str_inited = FALSE;	/* TRUE if str has been STR_INIT'd */
    int i;				/* temp */
    register struct bsearch_db *db = (struct bsearch_db *) gdb;

    DEBUG2(DBG_DRIVER_HI, "bsearch_lookup: looking for '%v' in %v.\n", key, db->name);

    if (!str_inited) {
	/*
	 * note, the string is reused in each call to bsearch.
	 */
	str_inited = TRUE;
	STR_INIT(&str);
    } else {
	STR_CHECK(&str);
    }

    lo = 0;
    hi = db->size;

    /*
     * "Binary search routines are never written right the first time around."
     * - Robert G. Sheldon.
     * << above comment retained 'cause I thought it was cute -- tron >>
     * << it is also true -- tron >>
     */
    for (;;) {
	middle = (hi + lo + 1) / 2;
	(void) fseek(db->f, middle, 0); /* find midpoint */
	if (middle != 0) {		/* go to beginning of next line */
	    while ((c = getc(db->f)) != EOF && c != '\n') {
		;
	    }
	    if (c == EOF && ferror(db->f)) {
		*error = strerror(errno);
		clearerr(db->f);
		return FILE_FAIL;
	    }
	}
	STR_CLEAR(&str);
	while ((c = getc(db->f)) != EOF && !isspace((int) c) && c != ':') {
	    STR_NEXT(&str, c);
	}
	if (c == EOF && ferror(db->f)) {
	    *error = strerror(errno);
	    clearerr(db->f);
	    return FILE_FAIL;
	}
	STR_NEXT(&str, '\0');
	flag = strcmpic(STR(&str), key);
	if (flag == 0) {
	    break;			/* Got it! */
	}
	if (lo >= middle) {		/* failure? */
	    return DB_NOMATCH;
	}
	if (c != EOF && flag < 0) {	/* close window */
	    lo = middle;
	} else {
	    hi = middle - 1;
	}
    }

    /*
     * Now just copy the result.
     */
    i = -1;				/* index to last non-space */
    STR_CLEAR(&str);			/* clear out the region */
    while (((c = getc(db->f)) != EOF) && (c != '\n')) {
	STR_NEXT(&str, c);
    }
    if (c == EOF && ferror(db->f)) {
	*error = strerror(errno);
	clearerr(db->f);
	return FILE_FAIL;
    }
    i = STR_LEN(&str);		/* note this is still one less */

    /* shrink to fit */
    STR_TRIM(&str, i + 1);

    STR_NEXT(&str, '\0');

    DEBUG2(DBG_DRIVER_HI, "bsearch_lookup: found <%v> for <%v>\n", STR(&str), key);
    *value = STR(&str);

    return DB_SUCCEED;
}

/*
 * lsearch access method:
 *
 *	access a file containing lines of data, in the format expected
 *	by read_entry() in parse.c.  Keys are at the start of each line
 *	followed by a colon and/or white space.
 */

/* private data: */
struct lsearch_db {
    struct db_proto *proto;		/* access method table entry */
    char *name;				/* name of file */
    FILE *f;				/* open file */
};

/* lsearch_open - open a file */
static int
lsearch_open(name, proto, retries, interval, statp, db, error)
    char *name;				/* name of file */
    struct db_proto *proto;		/* access method */
    int retries;			/* retry count */
    unsigned int interval;		/* retry interval */
    struct stat *statp;			/* save stat results here */
    char **db;				/* store open database info here */
    char **error;			/* store error message here */
{
    register struct lsearch_db *priv;
    register FILE *f;

    DEBUG1(DBG_DRIVER_HI, "lsearch_open: called to open %v.\n", name);

    name = make_lib_fn(name);
    if (name == NULL) {
	*error = "No directory for file";
	return FILE_FAIL;
    }
    f = fopen(name, "r");
    if (f == NULL) {
	int left = retries;

	while (left-- > 0) {
	    DEBUG2(DBG_DRIVER_HI, "lsearch_open: will retry open(%v) after %d seconds\n", name, interval);
	    (void) sleep(interval);
	    if ((f = fopen(name, "r"))) {
		break;
	    }
	}
	if (f == NULL) {
	    *error = strerror(errno);
	    if (errno == ENOENT) {
		return FILE_NOMATCH;
	    }
	    return FILE_FAIL;
	}
    }

#if !defined(HAVE_RENAME) && defined(lock_fd_rd)
    /*
     * Lock the file to make sure no-one else is playing with it.  This
     * might be superfluous since the only time we would really care is if
     * we happen to start reading just after the file has been truncated by
     * a poorly designed editor in preparation for rewrite.
     *
     * It may also be counter-productive or dangerous since any user can
     * place an advisory exclusive lock on any file for any amount of time.
     *
     * Unfortunately not all text editors (or other programs which might
     * modify these files) will safely write their new content to a
     * temporary file and then rename it to overwrite the true file.
     *
     * However we're going to assume the best and only use locks if we really
     * have to.
     */
    if (lock_fd_rd(fileno(f)) < 0) {
	*error = xprintf("%v database is locked, try again later", name);
	return FILE_AGAIN;
    }
#endif

    /* build the private data */
    priv = (struct lsearch_db *)xmalloc(sizeof(*priv));
    priv->proto = proto;
    priv->name = name;
    priv->f = f;
    if (statp) {
	(void) fstat(fileno(f), statp);
    }

    *db = (char *)priv;
    return FILE_SUCCEED;
}

/* lsearch_close - close the file */
static void
lsearch_close(gdb)
    struct generic_db *gdb;
{
    register struct lsearch_db *db = (struct lsearch_db *) gdb;

    DEBUG1(DBG_DRIVER_HI, "lsearch_close: called to close %v.\n", db->name);

    (void) fclose(db->f);
    xfree(db->name);
    xfree((char *)db);
}

#ifdef USE_LSEARCH_REGEXCMP
/*
 * lsearch_regexcmp - do a regular expression matching
 *
 * returns 0 on match, non-zero on no match [just like strcmp() does]
 */
static int
lsearch_regexcmp(entry, key, lenp)
    char *entry;
    char *key;
    size_t *lenp;
{
    static int inited = FALSE;
    static struct str exp;
    char *s;
	
# ifndef NODEBUG
    if (*entry != '\"') {
	panic(EX_SOFTWARE, "lsearch_regexcmp: called for non-RE <%v>", entry);
	/*NOTREACHED*/
    }
# endif
    s = &entry[1];			/* note this style may allow for multibyte */
    if (!strchr(s, '\"')) {
	write_log(WRITE_LOG_PANIC, "lsearch_regexcmp: un-terminated RE <%v>", entry);
	return (-1);			/* equivalent to "no-match" */
    }
    if (!inited) {
	STR_INIT(&exp);
	inited = TRUE;
    } else {
	STR_LEN(&exp) = 0;
    }
    STR_NEXT(&exp, '^');		/* create a fully anchored RE */
    while (*s && *s != '\"') {
	STR_NEXT(&exp, *s++);
    }
    STR_CAT(&exp, "$");

    *lenp = s + 1 - entry;		/* mark the char following closing quote.... */

    /* ignore errors, act like strcasecmp() */
    return ((match_re(key, STR(&exp), TRUE,
		      (char **) NULL, (char **) NULL,
		      (char **) NULL, (char **) NULL) == MATCH_MATCHED) ? 0 : 1);
}
#endif /* USE_LSEARCH_REGEXCMP */

/*
 * lsearch_lookup - look up key with a linear search
 */
/*ARGSUSED*/
static int
lsearch_lookup(gdb, key, value, error)
    register struct generic_db *gdb;
    char *key;
    char **value;
    char **error;
{
    size_t len;				/* length of comparison */
    register char *entry;		/* entry from read_entry() */
    register struct lsearch_db *db = (struct lsearch_db *) gdb;

    DEBUG2(DBG_DRIVER_HI, "lsearch_lookup: looking for <%v> in %v.\n", key, db->name);

    /* always start from the beginning of the file */
    (void) fseek(db->f, 0L, 0);

#ifndef USE_LSEARCH_REGEXCMP
    len = strlen(key);			/* only count once if that's all we need */
#endif

    while ((entry = read_entry(db->f))) {
	int rc;

	/*
	 * skip leading <whitespace> or comments
	 */
	entry = skip_space(entry);

#ifdef USE_LSEARCH_REGEXCMP
	len = strlen(key);
	if (*entry == '\"') {
	    rc = lsearch_regexcmp(entry, key, &len);
	} else
#endif
	{
	    rc = strncmpic(entry, key, len);
	}

	if ((rc == 0) && (entry[len] == ':' || isspace((int) entry[len]))) {
	    /* found a matching entry in the file, find the data */
	    entry += len;
	    /*
	     * skip <whitespace>:<whitespace>
	     *
	     * XXX should we be using skip_space()?
	     */
	    while (isspace((int) *entry)) {
		entry++;
	    }
	    if (*entry == ':') {
		entry++;
		while (isspace((int) *entry)) {
		    entry++;
		}
	    }

	    /*
	     * skip any opening comments at the beginning of the entry
	     *
	     * NOTE: we don't try to eliminate any comments from within the
	     * value -- those must be dealt with by the caller since there may
	     * be special syntax requirements.  For example callers will often
	     * use process_field() on the value and if it's given the F_ALIAS
	     * flag then it will deal with comments properly.
	     */
	    entry = skip_space(entry);

	    /* we will trim the trailing newline, just to avoid confusion */
	    len = strlen(entry);
	    if (entry[len - 1] == '\n') {
		entry[len - 1] = '\0';
	    }

	    DEBUG2(DBG_DRIVER_HI, "lsearch_lookup: key '%v': returning value: '%v'\n", key, entry);

	    *value = entry;
	    return DB_SUCCEED;
	}
    }
    if (ferror(db->f)) {
	*error = strerror(errno);
	return FILE_FAIL;
    }
    DEBUG1(DBG_DRIVER_HI, "lsearch_lookup: did not find '%v'\n", key);
    return DB_NOMATCH;
}

/*
 * dbmbase access method:
 *
 *	access a database stored as a DBM database.  Note that the
 *	DBM semantics only allow for one DBM file in the life of
 *	a process.  As a result, these files should never be closed
 *	and only one reference to a DBM-type database can exist in
 *	the application.
 *
 *	BUGS:  extensive use of #ifdef within functions is ugly.
 */

/*
 * form for private data:
 */
struct dbmbase_db {
    struct db_proto *proto;		/* access method table entry */
    char *name;				/* name of database */
#ifdef HAVE_NDBM
    DBM *db;				/* open database */
#endif
};

#ifndef HAVE_NDBM
static struct dbmbase_db *opendb = NULL;
#endif

/* dbmbase_open - open a DBM database */
static int
dbmbase_open(name, proto, retries, interval, statp, db, error)
    char *name;				/* name of database */
    struct db_proto *proto;		/* access method */
    int retries;			/* retry count */
    unsigned int interval;		/* retry interval */
    struct stat *statp;			/* return a stat structure */
    char **db;				/* store open database info here */
    char **error;			/* store error message here */
{
    char *pag_file;			/* name of .pag file */
    register struct dbmbase_db *priv;

    DEBUG1(DBG_DRIVER_HI, "dbmbase_open: called to open %v.\n", name);

    name = make_lib_fn(name);
    if (name == NULL) {
	*error = "No directory for file";
	return FILE_FAIL;
    }
    priv = (struct dbmbase_db *)xmalloc(sizeof(*priv));

#ifndef HAVE_NDBM
    if (opendb != NULL) {
	dbmclose();
	opendb = NULL;
    }
#endif

#ifdef HAVE_NDBM
    if ((priv->db = dbm_open(name, 0, 0)) == NULL)
#else
    if (dbminit(name) < 0)
#endif
    {
	int succeed = FAIL;
	int left = retries;

	if (left < 1) {
	    /* DBM databases cannot be moved atomicly, so require
	     * at least two retries */
	    left = 2;
	}
	if (interval < 2) {
	    /* require a somewhat reasonable interval as well */
	    interval = 2;
	}

	while (left-- > 0) {
	    DEBUG2(DBG_DRIVER_HI, "dbmbase_open: will retry open(%v) after %d seconds\n", name, interval);
	    (void) sleep(interval);
#ifdef HAVE_NDBM
	    if ((priv->db = dbm_open(name, 0, 0)) != NULL)
#else
	    if (dbminit(name) == 0)
#endif
	    {
		succeed = SUCCEED;
		break;
	    }
	    if (errno != ENOENT) {
		break;
	    }
	}
	if (succeed != DB_SUCCEED) {
	    *error = strerror(errno);
	    if (errno == ENOENT) {
		return FILE_NOMATCH;
	    }
	    return FILE_FAIL;
	}
    }

#ifndef HAVE_NDBM
    opendb = priv;
#endif

    /* if required, get a stat structure from the .pag file */
    if (statp) {
	pag_file = xmalloc(strlen(name) + sizeof(".pag"));
	(void) sprintf(pag_file, "%s.pag", name);
	(void) stat(pag_file, statp);
	xfree(pag_file);
    }
    priv->proto = proto;
    priv->name = name;
    *db = (char *)priv;
#if defined(HAVE_NDBM) && defined(lock_fd_rd_wait) && defined(HAVE_DBM_PAGFNO)
    if (lock_fd_rd_wait(dbm_pagfno(priv->db)) < 0) {
	*error = xprintf("%v database is locked, try again later", name);
	return FILE_AGAIN;
    }
#endif /* HAVE_NDBM && lock_fd_rd_wait && HAVE_DBM_PAGFNO */
    return FILE_SUCCEED;
}

/* dbmbase_close - close the database */
static void
dbmbase_close(gdb)
    struct generic_db *gdb;
{
    register struct dbmbase_db *db = (struct dbmbase_db *) gdb;

    DEBUG1(DBG_DRIVER_HI, "dbmbase_close: called to close %v.\n", db->name);

#ifdef HAVE_NDBM
    (void) dbm_close(db->db);
#else
    if (opendb == db) {
	dbmclose();
	opendb = NULL;
    }
#endif
    xfree(db->name);
    xfree((char *)db);
    return;
}

/* dbmbase_lookup - call on the DBM routines to find that data */
static int
dbmbase_lookup(gdb, key, value, error)
    register struct generic_db *gdb;
    char *key;
    char **value;
    char **error;
{
    datum the_key;
    datum the_value;
    static size_t temp_size;		/* size of temp_key area */
    static char *temp_data = NULL;	/* growable temp area */
    size_t len;				/* length of key */
    register struct dbmbase_db *db = (struct dbmbase_db *) gdb;

#ifndef HAVE_NDBM
    if (opendb != db) {
	if (opendb) {
	    dbmclose();
	    opendb = NULL;
	}
	if (dbminit(db->name) < 0) {
	    *error = xprintf("dbminit(): failed to initialize %v: %s.", db->name, strerror(errno));
	    DEBUG1(DBG_DRIVER_LO, "dbmbase_lookup: dbminit(): %s.\n", *error);
	    return FILE_FAIL;
	}
    }
#endif

    /*
     * convert the key to lower case, as (dbm_)fetch() is case-sensitive for
     * efficiency, keep around the malloc'd region used to store the down-cased
     * key.
     */
    len = strlen(key) + 1;
    if (temp_data == NULL) {
	temp_size = len;
	temp_data = xmalloc(temp_size);
    } else if (temp_size < len) {
	temp_size = len;
	temp_data = xrealloc(temp_data, temp_size);
    }
    strcpy(temp_data, key);
    str2lower(temp_data);

    DEBUG2(DBG_DRIVER_HI, "dbmbase_lookup: looking for <%v> in %v.\n", temp_data, db->name);

    the_key.dptr = temp_data;
    the_key.dsize = len;
#ifdef HAVE_NDBM
    the_value = dbm_fetch(db->db, the_key);
    if (dbm_error(db->db)) {
	*error = xprintf("Error in fetching %v: %s", temp_data, strerror(errno));
	DEBUG1(DBG_DRIVER_LO, "dbmbase_lookup: dbm_error(): %s.\n", *error);
	return FILE_FAIL;
    }
#else
    the_value = fetch(the_key);
#endif

    if (the_value.dptr) {
	if (temp_size < (size_t) the_value.dsize + 1) {
	    temp_size = the_value.dsize + 1;
	    temp_data = xrealloc(temp_data, temp_size);
	}
	(void) strcpy(temp_data, the_value.dptr);
	*value = temp_data;

	DEBUG1(DBG_DRIVER_HI, "dbmbase_lookup: return <%v>\n", *value);

	return DB_SUCCEED;
    }

    DEBUG1(DBG_DRIVER_HI, "dbmbase_lookup: did not find <%v>\n", temp_data);

    return DB_NOMATCH;
}

#ifdef	HAVE_YP
/*
 * The YP database routines takes names of the form:
 *
 *	domain:database_name
 * or	database_name
 *
 * in the former case, the `domain' specified is the YP domain to use
 * for yp_match operations.  In the second case, the default YP domain
 * is used.
 *
 * There are two forms for the lookup: regular yp and aliasyp.  The
 * second form is used for accessing the standard Sun mail.aliases map
 * format, which does not fit the form of other YP maps.  The
 * difference is that a nul-byte is counted in the length of a key for
 * mail.aliases, while it is not counted for other maps.
 */

/*
 * form for private data:
 */
struct yp_db {
    struct db_proto *proto;		/* access method table entry */
    char *map;				/* name of database */
    char *domain;			/* yp domain */
};

static char *default_yp_domain = NULL;	/* from yp_get_default_domain(3N) */
static int common_yp_lookup __P((register struct yp_db *, char *, char **, char **, int));

#ifdef notyet

#if ((__STDC__ - 0) <= 0)
/* most traditional compilers won't optimize global variables */
int save_time;				/* saved value from alarm() */
JUMPSIG save_sigalrm;			/* previous SIGALRM handler */
#else
static volatile int save_time;		/* saved value from alarm() */
static volatile JUMPSIG save_sigalrm;	/* previous SIGALRM handler */
#endif

static JUMP_ENVBUF alarm_jmp;		/* jump here on SIGALRM */

static void yp_sigalrm __P((void));	/* catch SIGALRM for YP timeouts */

#define YP_TIMEOUT	30		/* 30 second timeout for YP */
#endif

/*
 * yp_open, aliasyp_open - create a yp_private structure for a database
 *
 * yp_order(3N) is called to verify that the database is accessible.
 */
static int
aliasyp_open(name, proto, retries, interval, statp, db, error)
    char *name;				/* name of database */
    struct db_proto *proto;		/* access method */
    int retries;			/* retry count */
    unsigned int interval;		/* retry interval */
    struct stat *statp;			/* return a stat structure */
    char **db;				/* store open database info here */
    char **error;			/* store error message here */
{
    DEBUG1(DBG_DRIVER_HI, "aliasyp_open: called to open %v.\n", name);

    return yp_open(name, proto, retries, interval, statp, db, error);
}

/*ARGSUSED*/
static int
yp_open(name, proto, retries, interval, statp, db, error)
    char *name;				/* name of database */
    struct db_proto *proto;		/* access method */
    int retries;			/* retry count */
    unsigned int interval;		/* retry interval */
    struct stat *statp;			/* return a stat structure */
    char **db;				/* store open database info here */
    char **error;			/* store error message here */
{
    register struct yp_db *priv;
    register char *p, *pname;
    int err;				/* error from yp functions */
#if (defined(__SVR4) && defined(__sun)) || (defined(__svr4__) && defined(__sun__))
    unsigned long order;		/* output from yp_order Solaris-2.x */
#else  /* *BSD and the rest */
    int order;				/* output from yp_order */
#endif
    char *domain;

    priv = (struct yp_db *)xmalloc(sizeof(*priv));
    pname = COPY_STRING(name);

    /* is a YP domain specified? */
    p = strchr(pname, ':');
    if (p == NULL) {
	/* no, use the default YP domain */
	priv->domain = NULL;
	priv->map = pname;
    } else {
	priv->domain = pname;
	priv->domain[p - pname] = '\0';
	priv->map = p + 1;
    }

    /* if stat required, just zero it out, there is nothing to put there */
    if (statp) {
	(void) memset((char *) statp, '\0', sizeof(*statp));
    }

    if (priv->domain) {
	domain = priv->domain;
    } else {
	if (default_yp_domain == NULL &&
	    (err = yp_get_default_domain(&default_yp_domain)))
	{
	    /* this should only fail if the domainname is not set, right? */
	    *error = yperr_string(err);
	    return FILE_FAIL;
	}
	domain = default_yp_domain;
    }

#ifdef notyet
    save_time = alarm(0);

    /* verify that we can access the database */
    if (JUMP_SETJMP(alarm_jmp)) {
	(void) alarm(0);
	JUMP_CLEARSIG(SIGALRM, &save_sigalrm);
	if (save_time) {
	    (void) alarm(save_time);
	}

	*error = "YP timeout";
	return FILE_AGAIN;
    } else {

	/* arrange to timeout if the operation blocks */
	JUMP_SETSIG(SIGALRM, yp_sigalrm, &save_sigalrm);
	(void) alarm(YP_TIMEOUT);
#endif

	/* potentially blocking operation */
	/* XXX: third arg to yp_order() is (ulong *) on Solaris-2 */
	err = yp_order(domain, priv->map, &order);

#ifdef notyet
	/* restore previous alarm setting */
	(void) alarm(0);
	JUMP_CLEARSIG(SIGALRM, &save_sigalrm);
	if (save_time) {
	    (void) alarm(save_time);
	}
#endif

	if (err) {
	    /* analyze the reason for the failure */
	    *error = yperr_string(err);
	    switch (err) {
	    case YPERR_RPC:		/* cases where failure is temporary */
	    case YPERR_YPERR:
	    case YPERR_RESRC:
	    case YPERR_PMAP:
	    case YPERR_YPBIND:
	    case YPERR_YPSERV:
		return FILE_AGAIN;

	    case YPERR_MAP:		/* no such map */
	    case YPERR_DOMAIN:		/* no such domain */
		return FILE_NOMATCH;
	    }
	    return FILE_FAIL;
	}
#ifdef notyet
    }
#endif

    priv->proto = proto;
    *db = (char *)priv;
    return FILE_SUCCEED;
}

/* yp_close, aliasyp_close - free up data allocated for to a YP database */
static void
aliasyp_close(gdb)
    struct generic_db *gdb;
{
    register struct yp_db *db = (struct yp_db *) gdb;

    DEBUG1(DBG_DRIVER_HI, "aliasyp_close: called to close %v.\n", db->map);

    yp_close(gdb);
}

/*ARGSUSED*/
static void
yp_close(gdb)
    struct generic_db *gdb;
{
    register struct yp_db *db = (struct yp_db *) gdb;

    if (db->domain) {
	xfree(db->domain);
    } else {
	xfree(db->map);
    }
    xfree((char *) db);
    return;
}

/* yp_lookup, aliasyp_lookup - call on the YP routines to find that data */
static int
yp_lookup(gdb, key, value, error)
    register struct generic_db *gdb;	/* open YP database */
    char *key;				/* search key */
    char **value;			/* return value here */
    char **error;			/* return error message here */
{
    register struct yp_db *db = (struct yp_db *) gdb;

    DEBUG2(DBG_DRIVER_HI, "yp_lookup: looking for <%v> in %v.\n", key, db->map);

    return common_yp_lookup(db, key, value, error, FALSE);
}

static int
aliasyp_lookup(gdb, key, value, error)
    register struct generic_db *gdb;	/* open YP database */
    char *key;				/* search key */
    char **value;			/* return value here */
    char **error;			/* return error message here */
{
    register struct yp_db *db = (struct yp_db *) gdb;

    DEBUG2(DBG_DRIVER_HI, "aliasyp_lookup: looking for <%v> in %v.\n", key, db->map);

    return common_yp_lookup(db, key, value, error, TRUE);
}

/*ARGSUSED*/
static int
common_yp_lookup(db, key, value, error, aliasflag)
    register struct yp_db *db;		/* open YP database */
    char *key;				/* search key */
    char **value;			/* return value here */
    char **error;			/* return error message here */
    int aliasflag;			/* TRUE for Sun mail.aliases format */
{
    int keylen;				/* length of key */
    static char *temp_key = NULL;	/* area for lower-case conversion */
    static size_t temp_size = 0;	/* size allocated for temp_key */
    register char *p;
    char *matchval;			/* matched data */
    int matchlen;			/* length of matched data */
    int err;				/* yp error */
    char *domain;

    /*
     * convert the key to lower case, as fetch() is case-sensitive
     * for efficiency, keep around the malloc'd region used to store
     * the down-cased key.
     */
    keylen = strlen(key);
    if (temp_key == NULL) {
	temp_size = keylen + 1;
	temp_key = xmalloc(temp_size);
    } else if (temp_size <= keylen) {
	temp_size = keylen + 1;
	temp_key = xrealloc(temp_key, temp_size);
    }
    for (p = temp_key; *key; p++, key++) {
	*p = tolower((int) *key);
    }
    *p = '\0';

    if (db->domain) {
	domain = db->domain;
    } else {
	domain = default_yp_domain;
    }

    if (aliasflag) {
	keylen++;
    }

#ifdef notyet
    save_time = alarm(0);

    if (JUMP_SETJMP(alarm_jmp)) {
	(void) alarm(0);
	JUMP_CLEARSIG(SIGALRM, &save_sigalrm);
	if (save_time) {
	    (void) alarm(save_time);
	}

	*error = "YP timeout";
	return FILE_AGAIN;
    } else {

	/* arrange to timeout if the operation blocks */
	JUMP_SETSIG(SIGALRM, yp_sigalrm, &save_sigalrm);
	(void) alarm(YP_TIMEOUT);
#endif

	/* potentially blocking operation */
	err = yp_match(domain, db->map, temp_key, keylen,
		       &matchval, &matchlen);

#ifdef notyet
	/* restore previous alarm setting */
	(void) alarm(0);
	JUMP_CLEARSIG(SIGALRM, &save_sigalrm);
	if (save_time) {
	    (void) alarm(save_time);
	}
#endif

	if (err) {
	    /* analyze the reason for the failure */
	    *error = yperr_string(err);
	    switch (err) {
	    case YPERR_RPC:		/* cases where failure is temporary */
	    case YPERR_DOMAIN:
	    case YPERR_YPERR:
	    case YPERR_RESRC:
	    case YPERR_PMAP:
	    case YPERR_YPBIND:
	    case YPERR_YPSERV:
		return FILE_AGAIN;

	    case YPERR_KEY:		/* key not in database */
		return DB_NOMATCH;
	    }
	    return FILE_FAIL;
	}
#ifdef notyet
    }
#endif

    matchval[matchlen] = '\0';		/* we don't want the extra newline */
    *value = matchval;
    return DB_SUCCEED;
}

#ifdef notyet
static void
yp_sigalrm()
{
    JUMP_LONGJMP(alarm_jmp, 1);
}
#endif

#endif	/* HAVE_YP */

#ifdef        HAVE_NIALIAS

/*
 * nialias access method
 *
 *            access the netinfo alias database.  
 *
 */

/* private data: */
struct aliasni_db {
    struct db_proto *proto;
    char *name;
    char *expansion_area;
    int expansion_size;
};

/* aliasni_open - open the netinfo database */

static int
aliasni_open(name, proto, retries, interval, statp, db, error)
    char *name;			/* name of file */
    struct db_proto *proto;	/* access method */
    int retries;		/* retry count */
    unsigned int interval;	/* retry interval */
    struct stat *statp;		/* save stat results here */
    char **db;			/* store open database info here */
    char **error;		/* store error message here */
{
    register struct aliasni_db *priv;

    DEBUG1(DBG_DRIVER_HI, "aliasni_open: called to open %v.\n", name);

    /* build the private area */

    alias_setent();

    priv = (struct aliasni_db *)xmalloc(sizeof(*priv));
    priv->proto = proto;
    priv->name = COPY_STRING(name);
    priv->expansion_area = NULL;
    priv->expansion_size = 0;

    /* if stat required, just zero it out, there is nothing to put there */
    if (statp) {
	(void) memset((char *) statp, '\0', sizeof(*statp));
    }

    *db = (char *)priv;
    /* always succeeds */
    return FILE_SUCCEED;
}

/* aliasni_close - close the NetInfo alias database */
static void
aliasni_close(gdb)
    struct generic_db *gdb;
{
    register struct aliasni_db *db = (struct aliasni_db *) gdb;

    DEBUG1(DBG_DRIVER_HI, "aliasni_close: called to close %v.\n", db->name);

    alias_endent();
    xfree(db->expansion_area);
    xfree((char *) db->name);
    xfree((char *) db);
}

/* aliasni_lookup - search for a key in the NetInfo alias database */

/*ARGSUSED*/
static int
aliasni_lookup(gdb, key, value, error)
    register struct generic_db *gdb;
    char *key;
    char **value;
    char **error;
{
    aliasent    *a;
    int i,n;
    char        *p;
    register struct aliasni_db *db = (struct aliasni_db *) gdb;

    DEBUG1(DBG_DRIVER_HI, "aliasni_lookup: looking for <%v> in %v.\n", key, db->name);

    a = alias_getbyname(key);
    if ((!a) || (a->alias_members_len == 0)) {
	DEBUG1(DBG_DRIVER_HI,"aliasni_lookup: looking for <%v>\n",
	       lc_fold(key));
	a = alias_getbyname(lc_fold(key));
	if ((!a) || (a->alias_members_len == 0)) {
	    DEBUG1(DBG_DRIVER_HI,"aliasni_lookup: did not find <%v>\n", key);
	    return DB_NOMATCH;
	}
    }

    /* Since this routine can return multiple member lists, then */
    /* we must expand it into a single string and then let the caller */ 
    /* parse it into the individual fields */

    /* calculate the size of the expansion area  */
    for (n=2,i=0;i < a->alias_members_len; i++)
	n += (strlen(a->alias_members[i])+1);

    if (db->expansion_size < n) {
	db->expansion_size = n;
	if (db->expansion_area)
	    db->expansion_area=xrealloc(db->expansion_area, db->expansion_size);
	else
	    db->expansion_area=xmalloc(db->expansion_size);
    }

    for (p = db->expansion_area, i=0; i < a->alias_members_len; i++)
	if (n = strlen(a->alias_members[i])) {
	    *p++ = ',';				/* add a separator */
	    memcpy(p,a->alias_members[i],n);	/* add these member(s) */
	    p += n;
        }
    *p = '\0';					/* terminate the string */

    /* if anything in the string, skip the opening separator */
    *value = (p == db->expansion_area)?p:db->expansion_area+1;

    DEBUG3(DBG_DRIVER_HI, "aliasni_lookup: returns %d %s <%v>\n",
	   a->alias_members_len, (a->alias_local ? "local" : "network"), *value);

    return DB_SUCCEED;
}

#endif /* HAVE_NIALIAS */

#ifdef  HAVE_NISPLUS
/*
 * NIS+ remote access method
 * (This is NIS version 3
 * for NIS version 2, it's called Yellow Pages so use HAVE_YP)
 *
 * NIS+ tables can be partially or fully qualified.  For example:
 *   mail_aliases.org_dir
 *   mail_aliases.org_dir.oes
 *   mail_aliases.org_dir.oes.amdahl.com.
 * The trailing dot "." indicates a fully-qualified NIS+ name
 *
 * If the system is properly configured, the server, domain, and search path
 * should already be established external to smail.  There may even be
 * mirrors and caches, all of which are transparent unless specifically
 * disabled.  (We won't disable them.)
 */

/* private data structure */
struct nisplus_db {
    struct db_proto	*proto;
    char		*name;
    char		*table;
    int			fieldnum;
    int			retries;	/* saved retry for lookup */
    unsigned int	interval;	/* saved interval for lookup */
};

/* open a NIS+ database */
static int
nisplus_open(name, proto, retries, interval, statp, db, error)
    char *name;				/* name of file */
    struct db_proto *proto;		/* access method */
    int retries;			/* retry count */
    unsigned int interval;		/* retry interval */
    struct stat *statp;			/* save stat results here */
    char **db;				/* store open database info here */
    char **error;			/* store error message here */
{
    register struct nisplus_db	*priv;
    nis_result			*result;
    unsigned int		flags;
    char			*domain;

    DEBUG1(DBG_DRIVER_HI, "nisplus_open: called to open %v.\n", name);

    priv = (struct nisplus_db *)xmalloc(sizeof(*priv));
    priv->proto = proto;
    priv->fieldnum = 1;
    priv->retries = retries;
    priv->interval = interval;
    priv->name = COPY_STRING(name);

    /* check for directive to change field number */
    /* we use @ in character 0 because NIS+ strings cannot start with @ */
    if (priv->name[0] == '@') {
	char	*offset;

        priv->fieldnum = atoi(priv->name + 1);
	offset = strchr(priv->name,',');
	if ( offset ) {
            priv->table = COPY_STRING(offset+1);
        } else {
	    /* Yech!  No commas!  This is a failure condition */
	    *error = "syntax error in name given to nisplus proto";
	    xfree((char *) priv);
	    return FILE_FAIL;
	}
    } else {
        priv->fieldnum = 1;
        priv->table = priv->name;
    }

    /* if stat required, just zero it out, there is nothing to put there */
    if (statp) {
	(void) memset((char *) statp, '\0', sizeof(*statp));
    }

    /* find offset to domain name, advance past the last comma */
    if ( !(domain = strrchr(priv->name,','))) {
	domain = priv->name;
    } else {
	domain++;
    }

    DEBUG1(DBG_DRIVER_HI, "nisplus_open: verifying <%v>\n", domain);

    /* we'll just check if the table exists and then proceed */
    flags = FOLLOW_LINKS | EXPAND_NAME;
    do {
	result = nis_lookup ( domain, flags );
	switch ( result->status ) {

	    /* success - FILE_SUCCEED */
	    case NIS_SUCCESS:
	    case NIS_S_SUCCESS:
		/* we found it, but is it a table? */
		if ( result->objects.objects_len > 1 ) {
		    *error = "multiple results - NIS+ table name is ambiguous";
		    nis_freeresult(result);
		    DEBUG(DBG_DRIVER_HI, "nisplus_open: more than 1 value\n");
	            xfree((char *) priv);
		    return FILE_FAIL;
		}
		if ( result->objects.objects_val[0].zo_data.zo_type != TABLE_OBJ ) {
		    *error = "table open attempted on non-table NIS+ object";
		    nis_freeresult(result);
		    DEBUG(DBG_DRIVER_HI, "nisplus_open: not a table\n");
	            xfree((char *) priv);
		    return FILE_FAIL;
		}

		nis_freeresult(result);
		*db = (char*) priv;
	        DEBUG1(DBG_DRIVER_HI, "nisplus_open: found <%v>\n", domain);
		return FILE_SUCCEED;

	    /* immediate retry on cache expiration */
	    case NIS_CACHEEXPIRED:
		flags |= NO_CACHE;
		break;

	    /* immediate retry on server resource exhaustion */
	    case NIS_TRYAGAIN:
	    case NIS_NOMEMORY:
		break;

	    /* failure - not found - FILE_NOMATCH */
	    case NIS_NOTFOUND:
	    case NIS_NOSUCHNAME:
	    case NIS_NOSUCHTABLE:
	    case NIS_BADNAME:
	    case NIS_LINKNAMEERROR:
	    case NIS_FOREIGNNS:
		*error = nis_sperror(result->status, "nisplus_open");
		nis_freeresult(result);
	        DEBUG1(DBG_DRIVER_HI, "nisplus_open: <%v> not found\n", domain);
	        xfree((char *) priv);
		return FILE_NOMATCH;

	    /* failure - try again later - FILE_AGAIN */
	    case NIS_NAMEUNREACHABLE:
	    case NIS_SYSTEMERROR:
	    case NIS_NOT_ME:
	    case NIS_RPCERROR:
		*error = nis_sperror(result->status, "nisplus_open");
		nis_freeresult(result);
	        DEBUG1(DBG_DRIVER_HI, "nisplus_open: try again <%v>\n", domain);
	        xfree((char *) priv);
		return FILE_AGAIN;

	    /* failure - permanent failure on this object - FILE_FAIL */
	    case NIS_UNKNOWNOBJ:
	    case NIS_INVALIDOBJ:
	    default:
		*error = nis_sperror(result->status, "nisplus_open");
		nis_freeresult(result);
	        DEBUG1(DBG_DRIVER_HI, "nisplus_open: <%v> not found\n", domain);
	        xfree((char *) priv);
		return FILE_FAIL;
	}

	/* sleep interval for retry */
	if ( retries > 0 ) {
	    DEBUG2(DBG_DRIVER_HI, "nisplus_open: will retry open(%v) after %d seconds\n",
		   priv->name, interval);
	    sleep ( interval );
	}
    } while ( retries-- > 0 );

    /* if we get here, we ran out of immediate retries */
    /* the director should be told to try again later */
    *error = nis_sperror(result->status, "nisplus_open");
    nis_freeresult(result);
    DEBUG1(DBG_DRIVER_HI, "nisplus_open: try again <%v>\n", domain);
    xfree((char *) priv);
    return FILE_AGAIN;
}

/* close a NIS+ database */
static void
nisplus_close(gdb)
    struct generic_db *gdb;
{
    register struct nisplus_db *db = (struct nisplus_db *) gdb;

    DEBUG1(DBG_DRIVER_HI, "nisplus_close: called to close %v.\n", db->name);

    xfree(db->name);
    xfree((char *) db);
    return;
}

/* look up an element of a NIS+ database */
/*ARGSUSED*/
static int
nisplus_lookup(gdb, key, value, error)
    register struct generic_db *gdb;
    char *key;
    char **value;
    char **error;
{
    nis_result			*result;
    struct nis_object		*objbuf;
    unsigned int		flags;
    int				retries;
    int				valsize;
    char			*name;
    char			*valbuf;
    register struct nisplus_db *db = (struct nisplus_db *) gdb;

    valsize = 0;
    retries = db->retries;

    /* make the name to look up */
    /* prepend the search string as a "subdomain" of the table */
    name = (char*) xmalloc(strlen(key)+strlen(db->table)+2);
    sprintf(name, db->table, key);

    DEBUG2(DBG_DRIVER_HI, "nisplus_lookup: looking for <%v> in %v.\n", key, db->name);
    DEBUG1(DBG_DRIVER_HI, "nisplus_lookup: asking NIS+ for <%v>\n", name);

    /* look up the object */
    flags = FOLLOW_LINKS | EXPAND_NAME | RETURN_RESULT;
    do {
	result = nis_list ( name, flags, NULL, NULL );
	switch ( result->status ) {

	    /* success - DB_SUCCEED */
	    /* with two exceptions... */
	    case NIS_SUCCESS:
	    case NIS_S_SUCCESS:
		/* OK, we found it, but did we find too many? */
		if ( result->objects.objects_len > 1 ) {
		    *error = "multiple results - NIS+ entry name is ambiguous";
		    nis_freeresult(result);
		    DEBUG(DBG_DRIVER_HI, "nisplus_lookup: more than 1 value\n");
		    xfree(name);
		    return DB_FAIL;
		}

		/* ...but did we really find anything? */
		if ( result->objects.objects_len < 1 ) {
		    *error = "empty results - NIS+ search returned nothing";
		    nis_freeresult(result);
		    DEBUG(DBG_DRIVER_HI, "nisplus_lookup: returned nothing\n");
		    xfree(name);
		    return DB_FAIL;
		}

		/* ...but is it a table entry? */
		if ( result->objects.objects_val[0].zo_data.zo_type != ENTRY_OBJ ) {
		    *error = "open attempted on non-entry NIS+ object";
		    nis_freeresult(result);
		    DEBUG(DBG_DRIVER_HI, "nisplus_lookup: not a table entry\n");
		    xfree(name);
		    return DB_FAIL;
		}

		/* ...but are there enough fields? */
		if ( result->objects.objects_val[0].zo_data.objdata_u.en_data.en_cols.en_cols_len <= db->fieldnum ) {
		    *error = "NIS+ result had too few fields for the request";
		    nis_freeresult(result);
		    DEBUG(DBG_DRIVER_HI, "nisplus_lookup: not enough fields\n");
		    xfree(name);
		    return DB_FAIL;
		}

		/* assemble the return value from the columns */
		objbuf = &(result->objects.objects_val[0]);
		valbuf = xmalloc((size_t) objbuf->zo_data.objdata_u.en_data.en_cols.en_cols_val[db->fieldnum].ec_value.ec_value_len + 1);
		strncpy(valbuf, objbuf->zo_data.objdata_u.en_data.en_cols.en_cols_val[db->fieldnum].ec_value.ec_value_val,
				(size_t) objbuf->zo_data.objdata_u.en_data.en_cols.en_cols_val[db->fieldnum].ec_value.ec_value_len);
		valsize = objbuf->zo_data.objdata_u.en_data.en_cols.en_cols_val[db->fieldnum].ec_value.ec_value_len+1;

		/* back up over any trailing whitespace */
		for (valsize--; valsize > 0 && isspace((int) valbuf[valsize]); valsize--);
		    valsize++;
		    if (isspace((int) valbuf[valsize])) {
			valbuf[valsize] = '\0';
		}

		DEBUG1(DBG_DRIVER_HI, "nisplus_lookup: got <%v>\n", valbuf);
		*value=valbuf;
		nis_freeresult(result);
		xfree(name);
		return DB_SUCCEED;

	    /* immediate retry on cache expiration */
	    case NIS_CACHEEXPIRED:
		flags |= NO_CACHE;
		break;

	    /* immediate retry on server resource exhaustion */
	    case NIS_TRYAGAIN:
	    case NIS_NOMEMORY:
		break;

	    /* failure - not found - DB_NOMATCH */
	    case NIS_NOTFOUND:
	    case NIS_S_NOTFOUND:
	    case NIS_NOSUCHNAME:
	    case NIS_NOSUCHTABLE:
	    case NIS_BADNAME:
	    case NIS_LINKNAMEERROR:
	    case NIS_FOREIGNNS:
	    case NIS_PARTIAL:
		*error = nis_sperror(result->status, "nisplus_lookup");
		nis_freeresult(result);
		DEBUG2(DBG_DRIVER_HI, "nisplus_lookup: <%v> not found: %s\n",
		       key, *error);
		xfree(name);
		return DB_NOMATCH;

	    /* failure - try again later - FILE_AGAIN */
	    case NIS_NAMEUNREACHABLE:
	    case NIS_SYSTEMERROR:
	    case NIS_NOT_ME:
	    case NIS_RPCERROR:
		*error = nis_sperror(result->status, "nisplus_lookup");
		nis_freeresult(result);
		DEBUG2(DBG_DRIVER_HI, "nisplus_lookup: try again <%v>: %s\n",
		       key, *error);
		xfree(name);
		return FILE_AGAIN;

	    /* failure - permanent failure on this object - DB_FAIL */
	    case NIS_UNKNOWNOBJ:
	    case NIS_INVALIDOBJ:
	    case NIS_BADATTRIBUTE:
	    case NIS_NOTSEARCHABLE:
	    case NIS_TOOMANYATTRS:
	    default:
		*error = nis_sperror(result->status, "nisplus_lookup");
		nis_freeresult(result);
		DEBUG2(DBG_DRIVER_HI, "nisplus_lookup: <%v> not found: %s\n",
		       key, *error);
		xfree(name);
		return DB_FAIL;
	}

	/* sleep interval for retry */
	if (retries > 0) {
	    DEBUG2(DBG_DRIVER_HI,
		   "nisplus_lookup: will retry open(%v) after %d seconds\n",
		   name, db->interval);
	    sleep(db->interval);
	}
    } while (retries-- > 0);

    /* if we get here, we ran out of immediate retries */
    /* the director should be told to try again later */
    *error = nis_sperror(result->status, "nisplus_lookup");
    nis_freeresult(result);
    DEBUG2(DBG_DRIVER_HI, "nisplus_lookup: try again <%v>: %s\n",
	   key, *error);
    xfree(name);
    return FILE_AGAIN;
    
}

#endif /* HAVE_NISPLUS */



#ifdef STANDALONE

int debug = 1000;
FILE *errfile = stderr;

char *smtp_local_net = "192.168.1/24";
int return_to_sender = FALSE;

char *program = "lookup";

int
main(argc, argv)
    int argc;
    char **argv;
{
    char *proto;
    char *name;
    char *db;
    char *error = NULL;
    unsigned int interval = 3;
    int retries = 0;
    int success;

    argv++;				/* skip argv[0] */

    for (;;) {
	if (*argv && EQ(*argv, "-r")) {
	    argv++;
	    retries = atoi(*argv++);
	    argc -= 2;
	    continue;
	}
	if (*argv && EQ(*argv, "-i")) {
	    argv++;
	    interval = atoi(*argv++);
	    argc -= 2;
	    continue;
	}
	break;
    }

    if (argc < 3) {
	fprintf(stderr,
		"Usage: %s [-r #retries] [-i interval] db-name db-proto key ...\n",
		program);
	exit(EX_USAGE);
    }

    name = *argv++;
    proto = *argv++;

    success = open_database(name, proto, retries, interval,
			    (struct stat *) NULL, &db, &error);
    switch (success) {
    case FILE_AGAIN:
	fprintf(stderr, "%s: try again later: %s\n", program, error);
	exit(EX_TEMPFAIL);

    case FILE_FAIL:
    case FILE_NOMATCH:
	fprintf(stderr, "%s: open failed: %s\n", program, error);
	exit(EX_UNAVAILABLE);
    }

    while (*argv) {
	char *value;

	switch (lookup_database(db, *argv, &value, &error)) {
	case DB_AGAIN:
	    fprintf(stderr, "%s: db error: %s: try again later: %s\n", program, *argv, error);
	    break;

	case DB_FAIL:
	    fprintf(stderr, "%s: db error: %s: failed: %s\n", program, *argv, error);
	    break;

	case DB_NOMATCH:
	    fprintf(stderr, "%s: db error: %s: no match\n", program, *argv);
	    break;

	case FILE_AGAIN:
	    fprintf(stderr, "%s: file error: %s: try again later: %s\n", program, *argv, error);
	    break;

	case FILE_FAIL:
	    fprintf(stderr, "%s: file error: %s: failed: %s\n", program, *argv, error);
	    break;

	case DB_SUCCEED:
	    printf("%s --> %s\n", *argv, value);
	    break;
	}
	argv++;
    }

    close_database(db);

    exit(EX_OK);
}

#endif	/* STANDALONE */

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