/*
 * Copyright (c) 1992, 1995, 1996 Eric P. Allman.
 * Copyright (c) 1992, 1993
 *	The Regents of the University of California.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *	This product includes software developed by the University of
 *	California, Berkeley and its contributors.
 * 4. Neither the name of the University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#ifndef lint
static char sccsid[] = "@(#)map.c	8.146 (Berkeley) 11/24/96";
#endif /* not lint */

#include "sendmail.h"

#ifdef NDBM
# include <ndbm.h>
# ifdef R_FIRST
  ERROR README:	You are running the Berkeley DB version of ndbm.h.  See
  ERROR README:	the READ_ME file about tweaking Berkeley DB so it can
  ERROR README:	coexist with NDBM, or delete -DNDBM from the Makefile
  ERROR README: and use -DNEWDB instead.
# endif
#endif
#ifdef NEWDB
# include <db.h>
#endif
#ifdef NIS
  struct dom_binding;	/* forward reference needed on IRIX */
# include <rpcsvc/ypclnt.h>
# ifdef NDBM
#  define NDBM_YP_COMPAT	/* create YP-compatible NDBM files */
# endif
#endif

/*
**  MAP.C -- implementations for various map classes.
**
**	Each map class implements a series of functions:
**
**	bool map_parse(MAP *map, char *args)
**		Parse the arguments from the config file.  Return TRUE
**		if they were ok, FALSE otherwise.  Fill in map with the
**		values.
**
**	char *map_lookup(MAP *map, char *key, char **args, int *pstat)
**		Look up the key in the given map.  If found, do any
**		rewriting the map wants (including "args" if desired)
**		and return the value.  Set *pstat to the appropriate status
**		on error and return NULL.  Args will be NULL if called
**		from the alias routines, although this should probably
**		not be relied upon.  It is suggested you call map_rewrite
**		to return the results -- it takes care of null termination
**		and uses a dynamically expanded buffer as needed.
**
**	void map_store(MAP *map, char *key, char *value)
**		Store the key:value pair in the map.
**
**	bool map_open(MAP *map, int mode)
**		Open the map for the indicated mode.  Mode should
**		be either O_RDONLY or O_RDWR.  Return TRUE if it
**		was opened successfully, FALSE otherwise.  If the open
**		failed an the MF_OPTIONAL flag is not set, it should
**		also print an error.  If the MF_ALIAS bit is set
**		and this map class understands the @:@ convention, it
**		should call aliaswait() before returning.
**
**	void map_close(MAP *map)
**		Close the map.
**
**	This file also includes the implementation for getcanonname.
**	It is currently implemented in a pretty ad-hoc manner; it ought
**	to be more properly integrated into the map structure.
*/

#define DBMMODE		0644

#ifndef EX_NOTFOUND
# define EX_NOTFOUND	EX_NOHOST
#endif

extern bool	aliaswait __P((MAP *, char *, int));
extern bool	extract_canonname __P((char *, char *, char[], int));

#if O_EXLOCK && HASFLOCK
# define LOCK_ON_OPEN	1	/* we can open/create a locked file */
#else
# define LOCK_ON_OPEN	0	/* no such luck -- bend over backwards */
#endif
/*
**  MAP_PARSEARGS -- parse config line arguments for database lookup
**
**	This is a generic version of the map_parse method.
**
**	Parameters:
**		map -- the map being initialized.
**		ap -- a pointer to the args on the config line.
**
**	Returns:
**		TRUE -- if everything parsed OK.
**		FALSE -- otherwise.
**
**	Side Effects:
**		null terminates the filename; stores it in map
*/

bool
map_parseargs(map, ap)
	MAP *map;
	char *ap;
{
	register char *p = ap;

	map->map_mflags |= MF_TRY0NULL | MF_TRY1NULL;
	for (;;)
	{
		while (isascii(*p) && isspace(*p))
			p++;
		if (*p != '-')
			break;
		switch (*++p)
		{
		  case 'N':
			map->map_mflags |= MF_INCLNULL;
			map->map_mflags &= ~MF_TRY0NULL;
			break;

		  case 'O':
			map->map_mflags &= ~MF_TRY1NULL;
			break;

		  case 'o':
			map->map_mflags |= MF_OPTIONAL;
			break;

		  case 'f':
			map->map_mflags |= MF_NOFOLDCASE;
			break;

		  case 'm':
			map->map_mflags |= MF_MATCHONLY;
			break;

		  case 'A':
			map->map_mflags |= MF_APPEND;
			break;

		  case 'q':
			map->map_mflags |= MF_KEEPQUOTES;
			break;

		  case 'a':
			map->map_app = ++p;
			break;

		  case 'k':
			while (isascii(*++p) && isspace(*p))
				continue;
			map->map_keycolnm = p;
			break;

		  case 'v':
			while (isascii(*++p) && isspace(*p))
				continue;
			map->map_valcolnm = p;
			break;

		  case 'z':
			if (*++p != '\\')
				map->map_coldelim = *p;
			else
			{
				switch (*++p)
				{
				  case 'n':
					map->map_coldelim = '\n';
					break;

				  case 't':
					map->map_coldelim = '\t';
					break;

				  default:
					map->map_coldelim = '\\';
				}
			}
			break;

		  case 't':
			map->map_mflags |= MF_NODEFER;
			break;

#ifdef RESERVED_FOR_SUN
		  case 'd':
			map->map_mflags |= MF_DOMAIN_WIDE;
			break;

		  case 's':
			/* info type */
			break;
#endif
		}
		while (*p != '\0' && !(isascii(*p) && isspace(*p)))
			p++;
		if (*p != '\0')
			*p++ = '\0';
	}
	if (map->map_app != NULL)
		map->map_app = newstr(map->map_app);
	if (map->map_keycolnm != NULL)
		map->map_keycolnm = newstr(map->map_keycolnm);
	if (map->map_valcolnm != NULL)
		map->map_valcolnm = newstr(map->map_valcolnm);

	if (*p != '\0')
	{
		map->map_file = p;
		while (*p != '\0' && !(isascii(*p) && isspace(*p)))
			p++;
		if (*p != '\0')
			*p++ = '\0';
		map->map_file = newstr(map->map_file);
	}

	while (*p != '\0' && isascii(*p) && isspace(*p))
		p++;
	if (*p != '\0')
		map->map_rebuild = newstr(p);

	if (map->map_file == NULL &&
	    !bitset(MCF_OPTFILE, map->map_class->map_cflags))
	{
		syserr("No file name for %s map %s",
			map->map_class->map_cname, map->map_mname);
		return FALSE;
	}
	return TRUE;
}
/*
**  MAP_REWRITE -- rewrite a database key, interpolating %n indications.
**
**	It also adds the map_app string.  It can be used as a utility
**	in the map_lookup method.
**
**	Parameters:
**		map -- the map that causes this.
**		s -- the string to rewrite, NOT necessarily null terminated.
**		slen -- the length of s.
**		av -- arguments to interpolate into buf.
**
**	Returns:
**		Pointer to rewritten result.  This is static data that
**		should be copied if it is to be saved!
**
**	Side Effects:
**		none.
*/

char *
map_rewrite(map, s, slen, av)
	register MAP *map;
	register char *s;
	int slen;
	char **av;
{
	register char *bp;
	register char c;
	char **avp;
	register char *ap;
	int i;
	int len;
	static int buflen = -1;
	static char *buf = NULL;

	if (tTd(39, 1))
	{
		printf("map_rewrite(%.*s), av =", slen, s);
		if (av == NULL)
			printf(" (nullv)");
		else
		{
			for (avp = av; *avp != NULL; avp++)
				printf("\n\t%s", *avp);
		}
		printf("\n");
	}

	/* count expected size of output (can safely overestimate) */
	i = len = slen;
	if (av != NULL)
	{
		bp = s;
		for (i = slen; --i >= 0 && (c = *bp++) != 0; )
		{
			if (c != '%')
				continue;
			if (--i < 0)
				break;
			c = *bp++;
			if (!(isascii(c) && isdigit(c)))
				continue;
			for (avp = av; --c >= '0' && *avp != NULL; avp++)
				continue;
			if (*avp == NULL)
				continue;
			len += strlen(*avp);
		}
	}
	if (map->map_app != NULL)
		len += strlen(map->map_app);
	if (buflen < ++len)
	{
		/* need to malloc additional space */
		buflen = len;
		if (buf != NULL)
			free(buf);
		buf = xalloc(buflen);
	}

	bp = buf;
	if (av == NULL)
	{
		bcopy(s, bp, slen);
		bp += slen;
	}
	else
	{
		while (--slen >= 0 && (c = *s++) != '\0')
		{
			if (c != '%')
			{
  pushc:
				*bp++ = c;
				continue;
			}
			if (--slen < 0 || (c = *s++) == '\0')
				c = '%';
			if (c == '%')
				goto pushc;
			if (!(isascii(c) && isdigit(c)))
			{
				*bp++ = '%';
				goto pushc;
			}
			for (avp = av; --c >= '0' && *avp != NULL; avp++)
				continue;
			if (*avp == NULL)
				continue;

			/* transliterate argument into output string */
			for (ap = *avp; (c = *ap++) != '\0'; )
				*bp++ = c;
		}
	}
	if (map->map_app != NULL)
		strcpy(bp, map->map_app);
	else
		*bp = '\0';
	if (tTd(39, 1))
		printf("map_rewrite => %s\n", buf);
	return buf;
}
/*
**  INITMAPS -- initialize for aliasing
**
**	Parameters:
**		rebuild -- if TRUE, this rebuilds the cached versions.
**		e -- current envelope.
**
**	Returns:
**		none.
**
**	Side Effects:
**		initializes aliases:
**		if NDBM:  opens the database.
**		if ~NDBM: reads the aliases into the symbol table.
*/

void
initmaps(rebuild, e)
	bool rebuild;
	register ENVELOPE *e;
{
	extern void map_init();

#if XDEBUG
	checkfd012("entering initmaps");
#endif
	CurEnv = e;
	if (rebuild)
	{
		stabapply(map_init, 1);
		stabapply(map_init, 2);
	}
	else
	{
		stabapply(map_init, 0);
	}
#if XDEBUG
	checkfd012("exiting initmaps");
#endif
}

void
map_init(s, rebuild)
	register STAB *s;
	int rebuild;
{
	register MAP *map;

	/* has to be a map */
	if (s->s_type != ST_MAP)
		return;

	map = &s->s_map;
	if (!bitset(MF_VALID, map->map_mflags))
		return;

	if (tTd(38, 2))
		printf("map_init(%s:%s, %s, %d)\n",
			map->map_class->map_cname == NULL ? "NULL" :
				map->map_class->map_cname,
			map->map_mname == NULL ? "NULL" : map->map_mname,
			map->map_file == NULL ? "NULL" : map->map_file,
			rebuild);

	if (rebuild == (bitset(MF_ALIAS, map->map_mflags) &&
		    bitset(MCF_REBUILDABLE, map->map_class->map_cflags) ? 1 : 2))
	{
		if (tTd(38, 3))
			printf("\twrong pass\n");
		return;
	}

	/* if already open, close it (for nested open) */
	if (bitset(MF_OPEN, map->map_mflags))
	{
		map->map_class->map_close(map);
		map->map_mflags &= ~(MF_OPEN|MF_WRITABLE);
	}

	if (rebuild == 2)
	{
		rebuildaliases(map, FALSE);
	}
	else
	{
		if (map->map_class->map_open(map, O_RDONLY))
		{
			if (tTd(38, 4))
				printf("\t%s:%s %s: valid\n",
					map->map_class->map_cname == NULL ? "NULL" :
						map->map_class->map_cname,
					map->map_mname == NULL ? "NULL" :
						map->map_mname,
					map->map_file == NULL ? "NULL" :
						map->map_file);
			map->map_mflags |= MF_OPEN;
		}
		else
		{
			if (tTd(38, 4))
				printf("\t%s:%s %s: invalid: %s\n",
					map->map_class->map_cname == NULL ? "NULL" :
						map->map_class->map_cname,
					map->map_mname == NULL ? "NULL" :
						map->map_mname,
					map->map_file == NULL ? "NULL" :
						map->map_file,
					errstring(errno));
			if (!bitset(MF_OPTIONAL, map->map_mflags))
			{
				extern MAPCLASS BogusMapClass;

				map->map_class = &BogusMapClass;
				map->map_mflags |= MF_OPEN;
			}
		}
	}
}
/*
**  GETCANONNAME -- look up name using service switch
**
**	Parameters:
**		host -- the host name to look up.
**		hbsize -- the size of the host buffer.
**		trymx -- if set, try MX records.
**
**	Returns:
**		TRUE -- if the host was found.
**		FALSE -- otherwise.
*/

bool
getcanonname(host, hbsize, trymx)
	char *host;
	int hbsize;
	bool trymx;
{
	int nmaps;
	int mapno;
	bool found = FALSE;
	bool got_tempfail = FALSE;
	auto int stat;
	char *maptype[MAXMAPSTACK];
	short mapreturn[MAXMAPACTIONS];

	nmaps = switch_map_find("hosts", maptype, mapreturn);
	for (mapno = 0; mapno < nmaps; mapno++)
	{
		int i;

		if (tTd(38, 20))
			printf("getcanonname(%s), trying %s\n",
				host, maptype[mapno]);
		if (strcmp("files", maptype[mapno]) == 0)
		{
			extern bool text_getcanonname __P((char *, int, int *));

			found = text_getcanonname(host, hbsize, &stat);
		}
#ifdef NIS
		else if (strcmp("nis", maptype[mapno]) == 0)
		{
			extern bool nis_getcanonname __P((char *, int, int *));

			found = nis_getcanonname(host, hbsize, &stat);
		}
#endif
#ifdef NISPLUS
		else if (strcmp("nisplus", maptype[mapno]) == 0)
		{
			extern bool nisplus_getcanonname __P((char *, int, int *));

			found = nisplus_getcanonname(host, hbsize, &stat);
		}
#endif
#if NAMED_BIND
		else if (strcmp("dns", maptype[mapno]) == 0)
		{
			extern bool dns_getcanonname __P((char *, int, bool, int *));

			found = dns_getcanonname(host, hbsize, trymx, &stat);
		}
#endif
#if NETINFO
		else if (strcmp("netinfo", maptype[mapno]) == 0)
		{
			extern bool ni_getcanonname __P((char *, int, int *));

			found = ni_getcanonname(host, hbsize, &stat);
		}
#endif
		else
		{
			found = FALSE;
			stat = EX_UNAVAILABLE;
		}

		/*
		**  Heuristic: if $m is not set, we are running during system
		**  startup.  In this case, when a name is apparently found
		**  but has no dot, treat is as not found.  This avoids
		**  problems if /etc/hosts has no FQDN but is listed first
		**  in the service switch.
		*/

		if (found &&
		    (macvalue('m', CurEnv) != NULL || strchr(host, '.') != NULL))
			break;

		/* see if we should continue */
		if (stat == EX_TEMPFAIL)
		{
			i = MA_TRYAGAIN;
			got_tempfail = TRUE;
		}
		else if (stat == EX_NOTFOUND)
			i = MA_NOTFOUND;
		else
			i = MA_UNAVAIL;
		if (bitset(1 << mapno, mapreturn[i]))
			break;
	}

	if (found)
	{
		char *d;

		if (tTd(38, 20))
			printf("getcanonname(%s), found\n", host);

		/*
		**  If returned name is still single token, compensate
		**  by tagging on $m.  This is because some sites set
		**  up their DNS or NIS databases wrong.
		*/

		if ((d = strchr(host, '.')) == NULL || d[1] == '\0')
		{
			d = macvalue('m', CurEnv);
			if (d != NULL &&
			    hbsize > (int) (strlen(host) + strlen(d) + 1))
			{
				if (host[strlen(host) - 1] != '.')
					strcat(host, ".");
				strcat(host, d);
			}
			else
			{
				return FALSE;
			}
		}
		return TRUE;
	}

	if (tTd(38, 20))
		printf("getcanonname(%s), failed, stat=%d\n", host, stat);

#if NAMED_BIND
	if (got_tempfail)
		h_errno = TRY_AGAIN;
	else
		h_errno = HOST_NOT_FOUND;
#endif

	return FALSE;
}
/*
**  EXTRACT_CANONNAME -- extract canonical name from /etc/hosts entry
**
**	Parameters:
**		name -- the name against which to match.
**		line -- the /etc/hosts line.
**		cbuf -- the location to store the result.
**		cbuflen -- the size of cbuf.
**
**	Returns:
**		TRUE -- if the line matched the desired name.
**		FALSE -- otherwise.
*/

bool
extract_canonname(name, line, cbuf, cbuflen)
	char *name;
	char *line;
	char cbuf[];
	int cbuflen;
{
	int i;
	char *p;
	bool found = FALSE;
	int l;
	extern char *get_column __P((char *, int, char, char *, int));

	cbuf[0] = '\0';
	l = cbuflen;
	if (line[0] == '#')
		return FALSE;

	for (i = 1; ; i++)
	{
		char nbuf[MAXNAME + 1];

		p = get_column(line, i, '\0', nbuf, sizeof nbuf);
		if (p == NULL)
			break;
		if (*p == '\0')
			continue;
		if (cbuf[0] == '\0' ||
		    (strchr(cbuf, '.') == NULL && strchr(p, '.') != NULL))
		{
			snprintf(cbuf, cbuflen, "%s", p);
		}
		if (strcasecmp(name, p) == 0)
			found = TRUE;
	}
	if (found && strchr(cbuf, '.') == NULL)
	{
		/* try to add a domain on the end of the name */
		char *domain = macvalue('m', CurEnv);

		if (domain != NULL &&
		    strlen(domain) + strlen(cbuf) + 1 < cbuflen)
		{
			p = &cbuf[strlen(cbuf)];
			*p++ = '.';
			strcpy(p, domain);
		}
	}
	return found;
}
/*
**  NDBM modules
*/

#ifdef NDBM

/*
**  DBM_MAP_OPEN -- DBM-style map open
*/

bool
ndbm_map_open(map, mode)
	MAP *map;
	int mode;
{
	register DBM *dbm;
	struct stat st;
	int fd;

	if (tTd(38, 2))
		printf("ndbm_map_open(%s, %s, %d)\n",
			map->map_mname, map->map_file, mode);
	map->map_lockfd = -1;

#if LOCK_ON_OPEN
	if (mode == O_RDONLY)
		mode |= O_SHLOCK;
	else
		mode |= O_CREAT|O_TRUNC|O_EXLOCK;
#else
	if (mode == O_RDWR)
	{
# if NOFTRUNCATE
		/*
		**  Warning: race condition.  Try to lock the file as
		**  quickly as possible after opening it.
		*/

		mode |= O_CREAT|O_TRUNC;
# else
		/*
		**  This ugly code opens the map without truncating it,
		**  locks the file, then truncates it.  Necessary to
		**  avoid race conditions.
		*/

		int dirfd;
		int pagfd;
		char dirfile[MAXNAME + 1];
		char pagfile[MAXNAME + 1];

		snprintf(dirfile, sizeof dirfile, "%s.dir", map->map_file);
		snprintf(pagfile, sizeof pagfile, "%s.pag", map->map_file);
		dirfd = open(dirfile, mode|O_CREAT, DBMMODE);
		pagfd = open(pagfile, mode|O_CREAT, DBMMODE);

		if (dirfd < 0 || pagfd < 0)
		{
			syserr("ndbm_map_open: cannot create database %s",
				map->map_file);
			close(dirfd);
			close(pagfd);
			return FALSE;
		}
		if (!lockfile(dirfd, map->map_file, ".dir", LOCK_EX))
			syserr("ndbm_map_open: cannot lock %s.dir",
				map->map_file);
		if (ftruncate(dirfd, (off_t) 0) < 0)
			syserr("ndbm_map_open: cannot truncate %s.dir",
				map->map_file);
		if (ftruncate(pagfd, (off_t) 0) < 0)
			syserr("ndbm_map_open: cannot truncate %s.pag",
				map->map_file);

		/* have to save the lock for the duration (bletch) */
		map->map_lockfd = dirfd;
		close(pagfd);
# endif
	}
#endif

	/* open the database */
	dbm = dbm_open(map->map_file, mode, DBMMODE);
	if (dbm == NULL)
	{
		if (bitset(MF_ALIAS, map->map_mflags) &&
		    aliaswait(map, ".pag", FALSE))
			return TRUE;
		if (!bitset(MF_OPTIONAL, map->map_mflags))
			syserr("Cannot open DBM database %s", map->map_file);
		return FALSE;
	}
	map->map_db1 = (void *) dbm;
	fd = dbm_dirfno((DBM *) map->map_db1);
	if (mode == O_RDONLY)
	{
#if LOCK_ON_OPEN
		if (fd >= 0)
			(void) lockfile(fd, map->map_file, ".pag", LOCK_UN);
#endif
		if (bitset(MF_ALIAS, map->map_mflags) &&
		    !aliaswait(map, ".pag", TRUE))
			return FALSE;
	}
	else
	{
		map->map_mflags |= MF_LOCKED;
	}
	if (fstat(dbm_dirfno((DBM *) map->map_db1), &st) >= 0)
		map->map_mtime = st.st_mtime;
	return TRUE;
}


/*
**  DBM_MAP_LOOKUP -- look up a datum in a DBM-type map
*/

char *
ndbm_map_lookup(map, name, av, statp)
	MAP *map;
	char *name;
	char **av;
	int *statp;
{
	datum key, val;
	int fd;
	char keybuf[MAXNAME + 1];

	if (tTd(38, 20))
		printf("ndbm_map_lookup(%s, %s)\n",
			map->map_mname, name);

	key.dptr = name;
	key.dsize = strlen(name);
	if (!bitset(MF_NOFOLDCASE, map->map_mflags))
	{
		if (key.dsize > sizeof keybuf - 1)
			key.dsize = sizeof keybuf - 1;
		bcopy(key.dptr, keybuf, key.dsize);
		keybuf[key.dsize] = '\0';
		makelower(keybuf);
		key.dptr = keybuf;
	}
	fd = dbm_dirfno((DBM *) map->map_db1);
	if (fd >= 0 && !bitset(MF_LOCKED, map->map_mflags))
		(void) lockfile(fd, map->map_file, ".dir", LOCK_SH);
	val.dptr = NULL;
	if (bitset(MF_TRY0NULL, map->map_mflags))
	{
		val = dbm_fetch((DBM *) map->map_db1, key);
		if (val.dptr != NULL)
			map->map_mflags &= ~MF_TRY1NULL;
	}
	if (val.dptr == NULL && bitset(MF_TRY1NULL, map->map_mflags))
	{
		key.dsize++;
		val = dbm_fetch((DBM *) map->map_db1, key);
		if (val.dptr != NULL)
			map->map_mflags &= ~MF_TRY0NULL;
	}
	if (fd >= 0 && !bitset(MF_LOCKED, map->map_mflags))
		(void) lockfile(fd, map->map_file, ".dir", LOCK_UN);
	if (val.dptr == NULL)
		return NULL;
	if (bitset(MF_MATCHONLY, map->map_mflags))
		return map_rewrite(map, name, strlen(name), NULL);
	else
		return map_rewrite(map, val.dptr, val.dsize, av);
}


/*
**  DBM_MAP_STORE -- store a datum in the database
*/

void
ndbm_map_store(map, lhs, rhs)
	register MAP *map;
	char *lhs;
	char *rhs;
{
	datum key;
	datum data;
	int stat;
	char keybuf[MAXNAME + 1];

	if (tTd(38, 12))
		printf("ndbm_map_store(%s, %s, %s)\n",
			map->map_mname, lhs, rhs);

	key.dsize = strlen(lhs);
	key.dptr = lhs;
	if (!bitset(MF_NOFOLDCASE, map->map_mflags))
	{
		if (key.dsize > sizeof keybuf - 1)
			key.dsize = sizeof keybuf - 1;
		bcopy(key.dptr, keybuf, key.dsize);
		keybuf[key.dsize] = '\0';
		makelower(keybuf);
		key.dptr = keybuf;
	}

	data.dsize = strlen(rhs);
	data.dptr = rhs;

	if (bitset(MF_INCLNULL, map->map_mflags))
	{
		key.dsize++;
		data.dsize++;
	}

	stat = dbm_store((DBM *) map->map_db1, key, data, DBM_INSERT);
	if (stat > 0)
	{
		if (!bitset(MF_APPEND, map->map_mflags))
			usrerr("050 Warning: duplicate alias name %s", lhs);
		else
		{
			static char *buf = NULL;
			static int bufsiz = 0;
			auto int xstat;
			datum old;

			old.dptr = ndbm_map_lookup(map, key.dptr, NULL, &xstat);
			if (old.dptr != NULL && *(char *) old.dptr != '\0')
			{
				old.dsize = strlen(old.dptr);
				if (data.dsize + old.dsize + 2 > bufsiz)
				{
					if (buf != NULL)
						(void) free(buf);
					bufsiz = data.dsize + old.dsize + 2;
					buf = xalloc(bufsiz);
				}
				snprintf(buf, bufsiz, "%s,%s",
					data.dptr, old.dptr);
				data.dsize = data.dsize + old.dsize + 1;
				data.dptr = buf;
				if (tTd(38, 9))
					printf("ndbm_map_store append=%s\n", data.dptr);
			}
		}
		stat = dbm_store((DBM *) map->map_db1, key, data, DBM_REPLACE);
	}
	if (stat != 0)
		syserr("readaliases: dbm put (%s)", lhs);
}


/*
**  NDBM_MAP_CLOSE -- close the database
*/

void
ndbm_map_close(map)
	register MAP  *map;
{
	if (tTd(38, 9))
		printf("ndbm_map_close(%s, %s, %x)\n",
			map->map_mname, map->map_file, map->map_mflags);

	if (bitset(MF_WRITABLE, map->map_mflags))
	{
#ifdef NDBM_YP_COMPAT
		bool inclnull;
		char buf[200];

		inclnull = bitset(MF_INCLNULL, map->map_mflags);
		map->map_mflags &= ~MF_INCLNULL;

		if (strstr(map->map_file, "/yp/") != NULL)
		{
			long save_mflags = map->map_mflags;

			map->map_mflags |= MF_NOFOLDCASE;

			(void) snprintf(buf, sizeof buf, "%010ld", curtime());
			ndbm_map_store(map, "YP_LAST_MODIFIED", buf);

			(void) gethostname(buf, sizeof buf);
			ndbm_map_store(map, "YP_MASTER_NAME", buf);

			map->map_mflags = save_mflags;
		}

		if (inclnull)
			map->map_mflags |= MF_INCLNULL;
#endif

		/* write out the distinguished alias */
		ndbm_map_store(map, "@", "@");
	}
	dbm_close((DBM *) map->map_db1);

	/* release lock (if needed) */
#if !LOCK_ON_OPEN
	if (map->map_lockfd >= 0)
		(void) close(map->map_lockfd);
#endif
}

#endif
/*
**  NEWDB (Hash and BTree) Modules
*/

#ifdef NEWDB

/*
**  BT_MAP_OPEN, HASH_MAP_OPEN -- database open primitives.
**
**	These do rather bizarre locking.  If you can lock on open,
**	do that to avoid the condition of opening a database that
**	is being rebuilt.  If you don't, we'll try to fake it, but
**	there will be a race condition.  If opening for read-only,
**	we immediately release the lock to avoid freezing things up.
**	We really ought to hold the lock, but guarantee that we won't
**	be pokey about it.  That's hard to do.
*/

extern bool	db_map_open __P((MAP *, int, DBTYPE, const void *));

/* these should be K line arguments */
#ifndef DB_CACHE_SIZE
# define DB_CACHE_SIZE	(1024 * 1024)	/* database memory cache size */
#endif
#ifndef DB_HASH_NELEM
# define DB_HASH_NELEM	4096		/* (starting) size of hash table */
#endif

bool
bt_map_open(map, mode)
	MAP *map;
	int mode;
{
	BTREEINFO btinfo;

	if (tTd(38, 2))
		printf("bt_map_open(%s, %s, %d)\n",
			map->map_mname, map->map_file, mode);

	bzero(&btinfo, sizeof btinfo);
	btinfo.cachesize = DB_CACHE_SIZE;
	return db_map_open(map, mode, DB_BTREE, &btinfo);
}

bool
hash_map_open(map, mode)
	MAP *map;
	int mode;
{
	HASHINFO hinfo;

	if (tTd(38, 2))
		printf("hash_map_open(%s, %s, %d)\n",
			map->map_mname, map->map_file, mode);

	bzero(&hinfo, sizeof hinfo);
	hinfo.nelem = DB_HASH_NELEM;
	hinfo.cachesize = DB_CACHE_SIZE;
	return db_map_open(map, mode, DB_HASH, &hinfo);
}

bool
db_map_open(map, mode, dbtype, openinfo)
	MAP *map;
	int mode;
	DBTYPE dbtype;
	const void *openinfo;
{
	DB *db;
	int i;
	int omode;
	int fd;
	int saveerrno;
	struct stat st;
	char buf[MAXNAME + 1];

	snprintf(buf, sizeof buf - 3, "%s", map->map_file);
	i = strlen(buf);
	if (i < 3 || strcmp(&buf[i - 3], ".db") != 0)
		(void) strcat(buf, ".db");
	map->map_lockfd = -1;

	omode = mode;

#if LOCK_ON_OPEN
	if (mode == O_RDWR)
		omode |= O_CREAT|O_TRUNC|O_EXLOCK;
# if !OLD_NEWDB
	else
		omode |= O_SHLOCK;
# endif
#else
	if (mode == O_RDWR)
		omode |= O_CREAT;

	/*
	**  Pre-lock the file to avoid race conditions.  In particular,
	**  since dbopen returns NULL if the file is zero length, we
	**  must have a locked instance around the dbopen.
	*/

	fd = open(buf, omode, DBMMODE);

	if (fd < 0)
	{
		if (!bitset(MF_OPTIONAL, map->map_mflags))
			syserr("db_map_open: cannot pre-open database %s", buf);
		close(fd);
		return FALSE;
	}
	if (!lockfile(fd, map->map_file, ".db",
		      mode == O_RDONLY ? LOCK_SH : LOCK_EX))
		syserr("db_map_open: cannot lock %s", buf);
	if (mode == O_RDWR)
		omode |= O_TRUNC;
#endif

	db = dbopen(buf, omode, DBMMODE, dbtype, openinfo);
	saveerrno = errno;

#if !LOCK_ON_OPEN
	if (mode == O_RDWR)
		map->map_lockfd = fd;
	else
		(void) close(fd);
#endif

	if (db == NULL)
	{
		if (mode == O_RDONLY && bitset(MF_ALIAS, map->map_mflags) &&
		    aliaswait(map, ".db", FALSE))
			return TRUE;
		errno = saveerrno;
		if (!bitset(MF_OPTIONAL, map->map_mflags))
			syserr("Cannot open DB database %s", map->map_file);
		return FALSE;
	}

	if (mode == O_RDWR)
		map->map_mflags |= MF_LOCKED;
#if !OLD_NEWDB
	fd = db->fd(db);
# if LOCK_ON_OPEN
	if (fd >= 0 && mode == O_RDONLY)
	{
		(void) lockfile(fd, map->map_file, ".db", LOCK_UN);
	}
# endif
#endif

	/* try to make sure that at least the database header is on disk */
	if (mode == O_RDWR)
#if OLD_NEWDB
		(void) db->sync(db);
#else
		(void) db->sync(db, 0);

	if (fd >= 0 && fstat(fd, &st) >= 0)
		map->map_mtime = st.st_mtime;
#endif

	map->map_db2 = (void *) db;
	if (mode == O_RDONLY && bitset(MF_ALIAS, map->map_mflags) &&
	    !aliaswait(map, ".db", TRUE))
		return FALSE;
	return TRUE;
}


/*
**  DB_MAP_LOOKUP -- look up a datum in a BTREE- or HASH-type map
*/

char *
db_map_lookup(map, name, av, statp)
	MAP *map;
	char *name;
	char **av;
	int *statp;
{
	DBT key, val;
	register DB *db = (DB *) map->map_db2;
	int st;
	int saveerrno;
	int fd;
	char keybuf[MAXNAME + 1];

	if (tTd(38, 20))
		printf("db_map_lookup(%s, %s)\n",
			map->map_mname, name);

	key.size = strlen(name);
	if (key.size > sizeof keybuf - 1)
		key.size = sizeof keybuf - 1;
	key.data = keybuf;
	bcopy(name, keybuf, key.size);
	keybuf[key.size] = '\0';
	if (!bitset(MF_NOFOLDCASE, map->map_mflags))
		makelower(keybuf);
#if !OLD_NEWDB
	fd = db->fd(db);
	if (fd >= 0 && !bitset(MF_LOCKED, map->map_mflags))
		(void) lockfile(db->fd(db), map->map_file, ".db", LOCK_SH);
#endif
	st = 1;
	if (bitset(MF_TRY0NULL, map->map_mflags))
	{
		st = db->get(db, &key, &val, 0);
		if (st == 0)
			map->map_mflags &= ~MF_TRY1NULL;
	}
	if (st != 0 && bitset(MF_TRY1NULL, map->map_mflags))
	{
		key.size++;
		st = db->get(db, &key, &val, 0);
		if (st == 0)
			map->map_mflags &= ~MF_TRY0NULL;
	}
	saveerrno = errno;
#if !OLD_NEWDB
	if (fd >= 0 && !bitset(MF_LOCKED, map->map_mflags))
		(void) lockfile(fd, map->map_file, ".db", LOCK_UN);
#endif
	if (st != 0)
	{
		errno = saveerrno;
		if (st < 0)
			syserr("db_map_lookup: get (%s)", name);
		return NULL;
	}
	if (bitset(MF_MATCHONLY, map->map_mflags))
		return map_rewrite(map, name, strlen(name), NULL);
	else
		return map_rewrite(map, val.data, val.size, av);
}


/*
**  DB_MAP_STORE -- store a datum in the NEWDB database
*/

void
db_map_store(map, lhs, rhs)
	register MAP *map;
	char *lhs;
	char *rhs;
{
	int stat;
	DBT key;
	DBT data;
	register DB *db = map->map_db2;
	char keybuf[MAXNAME + 1];

	if (tTd(38, 12))
		printf("db_map_store(%s, %s, %s)\n",
			map->map_mname, lhs, rhs);

	key.size = strlen(lhs);
	key.data = lhs;
	if (!bitset(MF_NOFOLDCASE, map->map_mflags))
	{
		if (key.size > sizeof keybuf - 1)
			key.size = sizeof keybuf - 1;
		bcopy(key.data, keybuf, key.size);
		keybuf[key.size] = '\0';
		makelower(keybuf);
		key.data = keybuf;
	}

	data.size = strlen(rhs);
	data.data = rhs;

	if (bitset(MF_INCLNULL, map->map_mflags))
	{
		key.size++;
		data.size++;
	}

	stat = db->put(db, &key, &data, R_NOOVERWRITE);
	if (stat > 0)
	{
		if (!bitset(MF_APPEND, map->map_mflags))
			usrerr("050 Warning: duplicate alias name %s", lhs);
		else
		{
			static char *buf = NULL;
			static int bufsiz = 0;
			DBT old;

			old.data = db_map_lookup(map, key.data, NULL, &stat);
			if (old.data != NULL)
			{
				old.size = strlen(old.data);
				if (data.size + old.size + 2 > bufsiz)
				{
					if (buf != NULL)
						(void) free(buf);
					bufsiz = data.size + old.size + 2;
					buf = xalloc(bufsiz);
				}
				snprintf(buf, bufsiz, "%s,%s",
					data.data, old.data);
				data.size = data.size + old.size + 1;
				data.data = buf;
				if (tTd(38, 9))
					printf("db_map_store append=%s\n",
					       (char *) data.data);
			}
		}
		stat = db->put(db, &key, &data, 0);
	}
	if (stat != 0)
		syserr("readaliases: db put (%s)", lhs);
}


/*
**  DB_MAP_CLOSE -- add distinguished entries and close the database
*/

void
db_map_close(map)
	MAP *map;
{
	register DB *db = map->map_db2;

	if (tTd(38, 9))
		printf("db_map_close(%s, %s, %lx)\n",
			map->map_mname, map->map_file, map->map_mflags);

	if (bitset(MF_WRITABLE, map->map_mflags))
	{
		/* write out the distinguished alias */
		db_map_store(map, "@", "@");
	}

	if (db->close(db) != 0)
		syserr("readaliases: db close failure");

#if !LOCK_ON_OPEN
	if (map->map_lockfd >= 0)
		(void) close(map->map_lockfd);
#endif
}

#endif
/*
**  NIS Modules
*/

# ifdef NIS

# ifndef YPERR_BUSY
#  define YPERR_BUSY	16
# endif

/*
**  NIS_MAP_OPEN -- open DBM map
*/

bool
nis_map_open(map, mode)
	MAP *map;
	int mode;
{
	int yperr;
	register char *p;
	auto char *vp;
	auto int vsize;

	if (tTd(38, 2))
		printf("nis_map_open(%s, %s, %d)\n",
			map->map_mname, map->map_file, mode);

	if (mode != O_RDONLY)
	{
		/* issue a pseudo-error message */
#ifdef ENOSYS
		errno = ENOSYS;
#else
# ifdef EFTYPE
		errno = EFTYPE;
# else
		errno = ENXIO;
# endif
#endif
		return FALSE;
	}

	p = strchr(map->map_file, '@');
	if (p != NULL)
	{
		*p++ = '\0';
		if (*p != '\0')
			map->map_domain = p;
	}

	if (*map->map_file == '\0')
		map->map_file = "mail.aliases";

	if (map->map_domain == NULL)
	{
		yperr = yp_get_default_domain(&map->map_domain);
		if (yperr != 0)
		{
			if (!bitset(MF_OPTIONAL, map->map_mflags))
				syserr("421 NIS map %s specified, but NIS not running",
					map->map_file);
			return FALSE;
		}
	}

	/* check to see if this map actually exists */
	yperr = yp_match(map->map_domain, map->map_file, "@", 1,
			&vp, &vsize);
	if (tTd(38, 10))
		printf("nis_map_open: yp_match(@, %s, %s) => %s\n",
			map->map_domain, map->map_file, yperr_string(yperr));
	if (yperr == 0 || yperr == YPERR_KEY || yperr == YPERR_BUSY)
	{
		/*
		**  We ought to be calling aliaswait() here if this is an
		**  alias file, but powerful HP-UX NIS servers  apparently
		**  don't insert the @:@ token into the alias map when it
		**  is rebuilt, so aliaswait() just hangs.  I hate HP-UX.
		*/

#if 0
		if (!bitset(MF_ALIAS, map->map_mflags) ||
		    aliaswait(map, NULL, TRUE))
#endif
			return TRUE;
	}

	if (!bitset(MF_OPTIONAL, map->map_mflags))
	{
		syserr("421 Cannot bind to map %s in domain %s: %s",
			map->map_file, map->map_domain, yperr_string(yperr));
	}

	return FALSE;
}


/*
**  NIS_MAP_LOOKUP -- look up a datum in a NIS map
*/

char *
nis_map_lookup(map, name, av, statp)
	MAP *map;
	char *name;
	char **av;
	int *statp;
{
	char *vp;
	auto int vsize;
	int buflen;
	int yperr;
	char keybuf[MAXNAME + 1];

	if (tTd(38, 20))
		printf("nis_map_lookup(%s, %s)\n",
			map->map_mname, name);

	buflen = strlen(name);
	if (buflen > sizeof keybuf - 1)
		buflen = sizeof keybuf - 1;
	bcopy(name, keybuf, buflen);
	keybuf[buflen] = '\0';
	if (!bitset(MF_NOFOLDCASE, map->map_mflags))
		makelower(keybuf);
	yperr = YPERR_KEY;
	if (bitset(MF_TRY0NULL, map->map_mflags))
	{
		yperr = yp_match(map->map_domain, map->map_file, keybuf, buflen,
			     &vp, &vsize);
		if (yperr == 0)
			map->map_mflags &= ~MF_TRY1NULL;
	}
	if (yperr == YPERR_KEY && bitset(MF_TRY1NULL, map->map_mflags))
	{
		buflen++;
		yperr = yp_match(map->map_domain, map->map_file, keybuf, buflen,
			     &vp, &vsize);
		if (yperr == 0)
			map->map_mflags &= ~MF_TRY0NULL;
	}
	if (yperr != 0)
	{
		if (yperr != YPERR_KEY && yperr != YPERR_BUSY)
			map->map_mflags &= ~(MF_VALID|MF_OPEN);
		return NULL;
	}
	if (bitset(MF_MATCHONLY, map->map_mflags))
		return map_rewrite(map, name, strlen(name), NULL);
	else
		return map_rewrite(map, vp, vsize, av);
}


/*
**  NIS_GETCANONNAME -- look up canonical name in NIS
*/

bool
nis_getcanonname(name, hbsize, statp)
	char *name;
	int hbsize;
	int *statp;
{
	char *vp;
	auto int vsize;
	int keylen;
	int yperr;
	static bool try0null = TRUE;
	static bool try1null = TRUE;
	static char *yp_domain = NULL;
	char host_record[MAXLINE];
	char cbuf[MAXNAME];
	char nbuf[MAXNAME + 1];

	if (tTd(38, 20))
		printf("nis_getcanonname(%s)\n", name);

	if (strlen(name) >= sizeof nbuf)
	{
		*statp = EX_UNAVAILABLE;
		return FALSE;
	}
	(void) strcpy(nbuf, name);
	shorten_hostname(nbuf);
	keylen = strlen(nbuf);

	if (yp_domain == NULL)
		yp_get_default_domain(&yp_domain);
	makelower(nbuf);
	yperr = YPERR_KEY;
	if (try0null)
	{
		yperr = yp_match(yp_domain, "hosts.byname", nbuf, keylen,
			     &vp, &vsize);
		if (yperr == 0)
			try1null = FALSE;
	}
	if (yperr == YPERR_KEY && try1null)
	{
		keylen++;
		yperr = yp_match(yp_domain, "hosts.byname", nbuf, keylen,
			     &vp, &vsize);
		if (yperr == 0)
			try0null = FALSE;
	}
	if (yperr != 0)
	{
		if (yperr == YPERR_KEY)
			*statp = EX_NOHOST;
		else if (yperr == YPERR_BUSY)
			*statp = EX_TEMPFAIL;
		else
			*statp = EX_UNAVAILABLE;
		return FALSE;
	}
	strncpy(host_record, vp, vsize);
	host_record[vsize] = '\0';
	if (tTd(38, 44))
		printf("got record `%s'\n", host_record);
	if (!extract_canonname(nbuf, host_record, cbuf, sizeof cbuf))
	{
		/* this should not happen, but.... */
		*statp = EX_NOHOST;
		return FALSE;
	}
	if (hbsize < strlen(cbuf))
	{
		*statp = EX_UNAVAILABLE;
		return FALSE;
	}
	strcpy(name, cbuf);
	*statp = EX_OK;
	return TRUE;
}

#endif
/*
**  NISPLUS Modules
**
**	This code donated by Sun Microsystems.
*/

#ifdef NISPLUS

#undef NIS /* symbol conflict in nis.h */
#include <rpcsvc/nis.h>
#include <rpcsvc/nislib.h>

#define EN_col(col)	zo_data.objdata_u.en_data.en_cols.en_cols_val[(col)].ec_value.ec_value_val
#define COL_NAME(res,i)	((res->objects.objects_val)->TA_data.ta_cols.ta_cols_val)[i].tc_name
#define COL_MAX(res)	((res->objects.objects_val)->TA_data.ta_cols.ta_cols_len)
#define PARTIAL_NAME(x)	((x)[strlen(x) - 1] != '.')

/*
**  NISPLUS_MAP_OPEN -- open nisplus table
*/

bool
nisplus_map_open(map, mode)
	MAP *map;
	int mode;
{
	nis_result *res = NULL;
	int retry_cnt, max_col, i;
	char qbuf[MAXLINE + NIS_MAXNAMELEN];

	if (tTd(38, 2))
		printf("nisplus_map_open(%s, %s, %d)\n",
			map->map_mname, map->map_file, mode);

	if (mode != O_RDONLY)
	{
		errno = ENODEV;
		return FALSE;
	}

	if (*map->map_file == '\0')
		map->map_file = "mail_aliases.org_dir";

	if (PARTIAL_NAME(map->map_file) && map->map_domain == NULL)
	{
		/* set default NISPLUS Domain to $m */
		extern char *nisplus_default_domain();

		map->map_domain = newstr(nisplus_default_domain());
		if (tTd(38, 2))
			printf("nisplus_map_open(%s): using domain %s\n",
				 map->map_file, map->map_domain);
	}
	if (!PARTIAL_NAME(map->map_file))
		map->map_domain = newstr("");

	/* check to see if this map actually exists */
	if (PARTIAL_NAME(map->map_file))
		snprintf(qbuf, sizeof qbuf, "%s.%s",
			map->map_file, map->map_domain);
	else
		strcpy(qbuf, map->map_file);
	
	retry_cnt = 0;
	while (res == NULL || res->status != NIS_SUCCESS)
	{
		res = nis_lookup(qbuf, FOLLOW_LINKS);
		switch (res->status)
		{
		  case NIS_SUCCESS:
			break;

		  case NIS_TRYAGAIN:
		  case NIS_RPCERROR:
		  case NIS_NAMEUNREACHABLE:
			if (retry_cnt++ > 4)
			{
				errno = EBADR;
				return FALSE;
			}
			/* try not to overwhelm hosed server */
			sleep(2);
			break;

		  default:		/* all other nisplus errors */
#if 0
			if (!bitset(MF_OPTIONAL, map->map_mflags))
				syserr("421 Cannot find table %s.%s: %s",
					map->map_file, map->map_domain,
					nis_sperrno(res->status));
#endif
			errno = EBADR;
			return FALSE;
		}
	}

	if (NIS_RES_NUMOBJ(res) != 1 ||
	    (NIS_RES_OBJECT(res)->zo_data.zo_type != TABLE_OBJ))
	{
		if (tTd(38, 10))
			printf("nisplus_map_open: %s is not a table\n", qbuf);
#if 0
		if (!bitset(MF_OPTIONAL, map->map_mflags))
			syserr("421 %s.%s: %s is not a table",
				map->map_file, map->map_domain,
				nis_sperrno(res->status));
#endif
		errno = EBADR;
		return FALSE;
	}
	/* default key column is column 0 */
	if (map->map_keycolnm == NULL)
		map->map_keycolnm = newstr(COL_NAME(res,0));

	max_col = COL_MAX(res);
	
	/* verify the key column exist */
	for (i=0; i< max_col; i++)
	{
		if (!strcmp(map->map_keycolnm, COL_NAME(res,i)))
			break;
	}
	if (i == max_col)
	{
		if (tTd(38, 2))
			printf("nisplus_map_open(%s): can not find key column %s\n",
				map->map_file, map->map_keycolnm);
		errno = EBADR;
		return FALSE;
	}

	/* default value column is the last column */
	if (map->map_valcolnm == NULL)
	{
		map->map_valcolno = max_col - 1;
		return TRUE;
	}

	for (i=0; i< max_col; i++)
	{
		if (strcmp(map->map_valcolnm, COL_NAME(res,i)) == 0)
		{
			map->map_valcolno = i;
			return TRUE;
		}
	}

	if (tTd(38, 2))
		printf("nisplus_map_open(%s): can not find column %s\n",
			 map->map_file, map->map_keycolnm);
	errno = EBADR;
	return FALSE;
}


/*
**  NISPLUS_MAP_LOOKUP -- look up a datum in a NISPLUS table
*/

char *
nisplus_map_lookup(map, name, av, statp)
	MAP *map;
	char *name;
	char **av;
	int *statp;
{
	char *vp;
	auto int vsize;
	int buflen;
	char search_key[MAXNAME + 1];
	char qbuf[MAXLINE + NIS_MAXNAMELEN];
	nis_result *result;

	if (tTd(38, 20))
		printf("nisplus_map_lookup(%s, %s)\n",
			map->map_mname, name);

	if (!bitset(MF_OPEN, map->map_mflags))
	{
		if (nisplus_map_open(map, O_RDONLY))
			map->map_mflags |= MF_OPEN;
		else
		{
			*statp = EX_UNAVAILABLE;
			return NULL;
		}
	}
		
	buflen = strlen(name);
	if (buflen > sizeof search_key - 1)
		buflen = sizeof search_key - 1;
	bcopy(name, search_key, buflen);
	search_key[buflen] = '\0';
	if (!bitset(MF_NOFOLDCASE, map->map_mflags))
		makelower(search_key);

	/* construct the query */
	if (PARTIAL_NAME(map->map_file))
		snprintf(qbuf, sizeof qbuf, "[%s=%s],%s.%s",
			map->map_keycolnm, search_key, map->map_file,
			map->map_domain);
	else
		snprintf(qbuf, sizeof qbuf, "[%s=%s],%s",
			map->map_keycolnm, search_key, map->map_file);

	if (tTd(38, 20))
		printf("qbuf=%s\n", qbuf);
	result = nis_list(qbuf, FOLLOW_LINKS | FOLLOW_PATH, NULL, NULL);
	if (result->status == NIS_SUCCESS)
	{
		int count;
		char *str;

		if ((count = NIS_RES_NUMOBJ(result)) != 1)
		{
			if (LogLevel > 10)
				syslog(LOG_WARNING,
				  "%s: lookup error, expected 1 entry, got %d",
				    map->map_file, count);

			/* ignore second entry */
			if (tTd(38, 20))
				printf("nisplus_map_lookup(%s), got %d entries, additional entries ignored\n",
					name, count);
		}

		vp = ((NIS_RES_OBJECT(result))->EN_col(map->map_valcolno));
		/* set the length of the result */
		if (vp == NULL)
			vp = "";
		vsize = strlen(vp);
		if (tTd(38, 20))
			printf("nisplus_map_lookup(%s), found %s\n",
				name, vp);
		if (bitset(MF_MATCHONLY, map->map_mflags))
			str = map_rewrite(map, name, strlen(name), NULL);
		else
			str = map_rewrite(map, vp, vsize, av);
		nis_freeresult(result);
		*statp = EX_OK;
		return str;
	}
	else
	{
		if (result->status == NIS_NOTFOUND)
			*statp = EX_NOTFOUND;
		else if (result->status == NIS_TRYAGAIN)
			*statp = EX_TEMPFAIL;
		else
		{
			*statp = EX_UNAVAILABLE;
			map->map_mflags &= ~(MF_VALID|MF_OPEN);
		}
	}
	if (tTd(38, 20))
		printf("nisplus_map_lookup(%s), failed\n", name);
	nis_freeresult(result);
	return NULL;
}



/*
**  NISPLUS_GETCANONNAME -- look up canonical name in NIS+
*/

bool
nisplus_getcanonname(name, hbsize, statp)
	char *name;
	int hbsize;
	int *statp;
{
	char *vp;
	auto int vsize;
	nis_result *result;
	char *p;
	char nbuf[MAXNAME + 1];
	char qbuf[MAXLINE + NIS_MAXNAMELEN];

	if (strlen(name) >= sizeof nbuf)
	{
		*statp = EX_UNAVAILABLE;
		return FALSE;
	}
	(void) strcpy(nbuf, name);
	shorten_hostname(nbuf);

	p = strchr(nbuf, '.');
	if (p == NULL)
	{
		/* single token */
		snprintf(qbuf, sizeof qbuf, "[name=%s],hosts.org_dir", nbuf);
	}
	else if (p[1] != '\0')
	{
		/* multi token -- take only first token in nbuf */
		*p = '\0';
		snprintf(qbuf, sizeof qbuf, "[name=%s],hosts.org_dir.%s",
			nbuf, &p[1]);
	}
	else
	{
		*statp = EX_NOHOST;
		return FALSE;
	}

	if (tTd(38, 20))
		printf("\nnisplus_getcanoname(%s), qbuf=%s\n",
			 name, qbuf);

	result = nis_list(qbuf, EXPAND_NAME|FOLLOW_LINKS|FOLLOW_PATH,
		NULL, NULL);

	if (result->status == NIS_SUCCESS)
	{
		int count;
		char *domain;

		if ((count = NIS_RES_NUMOBJ(result)) != 1)
		{
#ifdef LOG
			if (LogLevel > 10)
				syslog(LOG_WARNING,
				       "nisplus_getcanonname: lookup error, expected 1 entry, got %d",
				       count);
#endif

			/* ignore second entry */
			if (tTd(38, 20))
				printf("nisplus_getcanoname(%s), got %d entries, all but first ignored\n",
					name, count);
		}

		if (tTd(38, 20))
			printf("nisplus_getcanoname(%s), found in directory \"%s\"\n",
			       name, (NIS_RES_OBJECT(result))->zo_domain);


		vp = ((NIS_RES_OBJECT(result))->EN_col(0));
		vsize = strlen(vp);
		if (tTd(38, 20))
			printf("nisplus_getcanonname(%s), found %s\n",
				name, vp);
		if (strchr(vp, '.') != NULL)
		{
			domain = "";
		}
		else
		{
			domain = macvalue('m', CurEnv);
			if (domain == NULL)
				domain = "";
		}
		if (hbsize > vsize + (int) strlen(domain) + 1)
		{
			if (domain[0] == '\0')
				strcpy(name, vp);
			else
				snprintf(name, hbsize, "%s.%s", vp, domain);
			*statp = EX_OK;
		}
		else
			*statp = EX_NOHOST;
		nis_freeresult(result);
		return TRUE;
	}
	else
	{
		if (result->status == NIS_NOTFOUND)
			*statp = EX_NOHOST;
		else if (result->status == NIS_TRYAGAIN)
			*statp = EX_TEMPFAIL;
		else
			*statp = EX_UNAVAILABLE;
	}
	if (tTd(38, 20))
		printf("nisplus_getcanonname(%s), failed, status=%d, nsw_stat=%d\n",
			name, result->status, *statp);
	nis_freeresult(result);
	return FALSE;
}


char *
nisplus_default_domain()
{
	static char default_domain[MAXNAME + 1] = "";
	char *p;

	if (default_domain[0] != '\0')
		return(default_domain);
	
	p = nis_local_directory();
	snprintf(default_domain, sizeof default_domain, "%s", p);
	return default_domain;
}

#endif /* NISPLUS */
/*
**  LDAP Modules
**
**	Contributed by Booker C. Bense <bbense@networking.stanford.edu>.
**	Get your support from him.
*/

#ifdef LDAPMAP

# undef NEEDGETOPT		/* used for something else in LDAP */

# include <lber.h>
# include <ldap.h>
# include "ldap_map.h"

/*
**  LDAP_MAP_OPEN -- open LDAP map
**
**	Since LDAP is TCP-based there is not much we can or should do
**	here.  It might be a good idea to attempt an open/close here.
*/

bool
ldap_map_open(map, mode)
	MAP *map;
	int mode;
{
	if (tTd(38, 2))
		printf("ldap_map_open(%s, %d)\n", map->map_mname, mode);

	if (mode != O_RDONLY)
	{
		/* issue a pseudo-error message */
#ifdef ENOSYS
		errno = ENOSYS;
#else
# ifdef EFTYPE
		errno = EFTYPE;
# else
		errno = ENXIO;
# endif
#endif
		return FALSE;
	}
	return TRUE;
}


/*
**  LDAP_MAP_START -- actually open LDAP map
**
**	Caching should be investigated.
*/

bool
ldap_map_start(map)
	MAP *map;
{
	LDAP_MAP_STRUCT *lmap;
	LDAP *ld;

	if (tTd(38, 2))
		printf("ldap_map_start(%s)\n", map->map_mname);

	lmap = (LDAP_MAP_STRUCT *) map->map_db1;

	if (tTd(38,9))
		printf("ldap_open(%s, %d)\n", lmap->ldaphost, lmap->ldapport);

	if ((ld = ldap_open(lmap->ldaphost,lmap->ldapport)) == NULL)
	{
		if (!bitset(MF_OPTIONAL, map->map_mflags))
		{
			syserr("ldapopen failed to %s in map %s",
				lmap->ldaphost, map->map_mname);
		}
		return FALSE;
	}

	ld->ld_deref = lmap->deref;
	ld->ld_timelimit = lmap->timelimit;
	ld->ld_sizelimit = lmap->sizelimit;
	ld->ld_options = lmap->ldap_options;

	if (ldap_bind_s(ld, lmap->binddn,lmap->passwd,lmap->method) != LDAP_SUCCESS)
	{
		if (!bitset(MF_OPTIONAL, map->map_mflags))
		{
			syserr("421 Cannot bind to map %s in ldap server %s",
				map->map_mname, lmap->ldaphost);
		}
	}
	else
	{
		/* We need to cast ld into the map structure */
		lmap->ld = ld;
		return TRUE;
	}

	return FALSE;
}


/*
** LDAP_MAP_CLOSE -- close ldap map
*/

void
ldap_map_close(map)
	MAP *map;
{
	LDAP_MAP_STRUCT *lmap ;
	lmap = (LDAP_MAP_STRUCT *) map->map_db1;
	if (lmap->ld != NULL)
		ldap_unbind(lmap->ld);
}


#ifdef SUNET_ID
/*
** SUNET_ID_HASH -- Convert a string to it's Sunet_id canonical form
** This only makes sense at Stanford University.
*/

char *
sunet_id_hash(str)
	char *str;
{
	char *p, *p_last;

	p = str;
	p_last = p;
	while (*p != '\0')
	{
		if (islower(*p) || isdigit(*p))
		{
			*p_last = *p;
			p_last++;
		}
		else if (isupper(*p))
		{
			*p_last = tolower(*p);
			p_last++;
		}
		++p;
	}
	if (*p_last != '\0')
		*p_last = '\0';
	return (str);
}



#endif /* SUNET_ID */
/*
** LDAP_MAP_LOOKUP -- look up a datum in a LDAP map
*/

char *
ldap_map_lookup(map, name, av, statp)
	MAP *map;
	char *name;
	char **av;
	int *statp;
{
	LDAP_MAP_STRUCT *lmap = NULL;
	LDAPMessage *entry;
	char *vp;
	auto int vsize;
	char keybuf[MAXNAME + 1];
	char filter[LDAP_MAP_MAX_FILTER + 1];
	char **attr_values = NULL;
	char *result;
	int name_len;

	if (tTd(38, 20))
		printf("ldap_map_lookup(%s, %s)\n", map->map_mname, name);

	/* actually open the map */
	if (!ldap_map_start(map))
	{
		result = NULL;
		*statp = EX_TEMPFAIL;
		goto quick_exit;
	}

	/* Get ldap struct pointer from map */
	lmap = (LDAP_MAP_STRUCT *) map->map_db1;

	name_len = strlen(name);
	if (name_len > MAXNAME)
		name_len = MAXNAME;
	strncpy(keybuf, name, name_len);
	keybuf[name_len] = '\0';

	if (!bitset(MF_NOFOLDCASE, map->map_mflags))
#ifdef SUNET_ID
		sunet_id_hash(keybuf);
#else
		makelower(keybuf);
#endif /*SUNET_ID */

	/* sprintf keybuf into filter */
	snprintf(filter, sizeof filter, lmap->filter, keybuf);

	if (ldap_search_st(lmap->ld, lmap->base,lmap->scope,filter,
			   &(lmap->attr), lmap->attrsonly, &(lmap->timeout),
			   &(lmap->res)) != LDAP_SUCCESS)
	{
		/* try close/opening map */
		ldap_map_close(map);
		if (!ldap_map_start(map))
		{
			result = NULL;
			*statp = EX_TEMPFAIL;
			goto quick_exit;
		}
		if (ldap_search_st(lmap->ld, lmap->base, lmap->scope, filter,
				   &(lmap->attr), lmap->attrsonly,
				   &(lmap->timeout), &(lmap->res))
			!= LDAP_SUCCESS)
		{
			if (!bitset(MF_OPTIONAL, map->map_mflags))
			{
				syserr("Error in ldap_search_st using %s in map %s",
					filter, map->map_mname);
			}
			result = NULL;
			*statp = EX_UNAVAILABLE;
			goto quick_exit;
		}
	}

	entry = ldap_first_entry(lmap->ld,lmap->res);
	if (entry == NULL)
	{
	        result = NULL;
		*statp = EX_NOTFOUND;
		goto quick_exit;
	}

	/* Need to build the args for map_rewrite here */
	attr_values = ldap_get_values(lmap->ld,entry,lmap->attr);
	if (attr_values == NULL)
	{
		/* bad things happened */
		result = NULL;
		*statp = EX_NOTFOUND;
		goto quick_exit;
	}

	*statp = EX_OK;

	/* If there is more that one use the first */
	vp = attr_values[0];
	vsize = strlen(vp);

# ifdef LOG
	if (LogLevel > 9)
		syslog(LOG_INFO, "%s: ldap %.100s => %s",
			CurEnv->e_id == NULL ? "NOQUEUE" : CurEnv->e_id,
			name, vp);
# endif
	if (bitset(MF_MATCHONLY, map->map_mflags))
		result = map_rewrite(map, name, strlen(name), NULL);
	else
		result = map_rewrite(map, vp, vsize, av);

  quick_exit:
	if (attr_values != NULL)
		ldap_value_free(attr_values);
	if (lmap != NULL)
		ldap_msgfree(lmap->res);
	ldap_map_close(map);
	return result ;
}


/*
** LDAP_MAP_DEQUOTE - helper routine for ldap_map_parseargs
*/

char *
ldap_map_dequote(str)
	char *str;
{
	char *p;
	char *start;
	p = str;

	if (*p == '"')
	{
		start = ++p;
		/* Should probably swallow initial whitespace here */
	}
	else
	{
		return(str);
	}
	while (*p != '"' && *p != '\0')
	{
		p++;
	}
	if (*p != '\0')
		*p = '\0';
	return start;
}

/*
** LDAP_MAP_PARSEARGS -- parse ldap map definition args.
*/

bool
ldap_map_parseargs(map,args)
	MAP *map;
	char *args;
{
	register char *p = args;
	register int done;
	LDAP_MAP_STRUCT *lmap;

	/* We need to alloc an LDAP_MAP_STRUCT struct */
	lmap  = (LDAP_MAP_STRUCT *) xalloc(sizeof(LDAP_MAP_STRUCT));

	/* Set default int's here , default strings below */
	lmap->ldapport =  DEFAULT_LDAP_MAP_PORT;
	lmap->deref = DEFAULT_LDAP_MAP_DEREF;
	lmap->timelimit = DEFAULT_LDAP_MAP_TIMELIMIT;
	lmap->sizelimit = DEFAULT_LDAP_MAP_SIZELIMIT;
	lmap->ldap_options = DEFAULT_LDAP_MAP_LDAP_OPTIONS;
	lmap->method = DEFAULT_LDAP_MAP_METHOD;
	lmap->scope = DEFAULT_LDAP_MAP_SCOPE;
	lmap->attrsonly = DEFAULT_LDAP_MAP_ATTRSONLY;
	lmap->timeout.tv_sec = DEFAULT_LDAP_MAP_TIMELIMIT;
	lmap->timeout.tv_usec = 0;

	/* Default char ptrs to NULL */
	lmap->binddn = NULL;
	lmap->passwd = NULL;
	lmap->base   = NULL;
	lmap->ldaphost = NULL;
	map->map_mflags |= MF_TRY0NULL | MF_TRY1NULL;
	for (;;)
	{
		while (isascii(*p) && isspace(*p))
			p++;
		if (*p != '-')
			break;
		switch (*++p)
		{
		  case 'N':
			map->map_mflags |= MF_INCLNULL;
			map->map_mflags &= ~MF_TRY0NULL;
			break;

		  case 'O':
			map->map_mflags &= ~MF_TRY1NULL;
			break;

		  case 'o':
			map->map_mflags |= MF_OPTIONAL;
			break;

		  case 'f':
			map->map_mflags |= MF_NOFOLDCASE;
			break;

		  case 'm':
			map->map_mflags |= MF_MATCHONLY;
			break;

		  case 'A':
			map->map_mflags |= MF_APPEND;
			break;

		  case 'q':
			map->map_mflags |= MF_KEEPQUOTES;
			break;

		  case 't':
			map->map_mflags |= MF_NODEFER;
			break;

		  case 'a':
			map->map_app = ++p;
			break;

			/* Start of ldap_map specific args */
		  case 'k':		/* search field */
			while (isascii(*++p) && isspace(*p))
				continue;
			lmap->filter = p;
			break;

		  case 'v':		/* attr to return */
			while (isascii(*++p) && isspace(*p))
				continue;
			lmap->attr = p;
			break;

			/* args stolen from ldapsearch.c */
		  case 'R':		/* don't auto chase referrals */
#ifdef LDAP_REFERRALS
			lmap->ldap_options &= ~LDAP_OPT_REFERRALS;
#else  /* LDAP_REFERRALS */
			syserr("compile with -DLDAP_REFERRALS for referral support\n");
#endif /* LDAP_REFERRALS */
			break;

		  case 'n':		/* retrieve attribute names only -- no values */
			lmap->attrsonly += 1;
			break;

		  case 's':		/* search scope */
			if (strncasecmp(p, "base", 4) == 0)
			{
				lmap->scope = LDAP_SCOPE_BASE;
			}
			else if (strncasecmp(p, "one", 3) == 0)
			{
				lmap->scope = LDAP_SCOPE_ONELEVEL;
			}
			else if (strncasecmp(p, "sub", 3) == 0)
			{
				lmap->scope = LDAP_SCOPE_SUBTREE;
			}
			else
			{		/* bad config line */
				if (!bitset(MCF_OPTFILE, map->map_class->map_cflags))
				{
					syserr("Scope must be [base|one|sub] not %s in map %s",
						p, map->map_mname);
					return FALSE;
				}
			}
			break;

		  case 'h':		/* ldap host */
			while (isascii(*++p) && isspace(*p))
				continue;
			map->map_domain = p;
			lmap->ldaphost = p;
			break;

		  case 'b':		/* search base */
			while (isascii(*++p) && isspace(*p))
				continue;
			lmap->base = p;
			break;

		  case 'p':		/* ldap port */
			while (isascii(*++p) && isspace(*p))
				continue;
			lmap->ldapport = atoi(p);
			break;

		  case 'l':		/* time limit */
			while (isascii(*++p) && isspace(*p))
				continue;
			lmap->timelimit = atoi(p);
			break;

		}

		/* need to account for quoted strings here arggg... */
		done =  isascii(*p) && isspace(*p);
		while (*p != '\0' && !done)
		{
			if (*p == '"')
			{
				while (*++p != '"' && *p != '\0')
				{
					continue;
				}
				if (*p != '\0')
					p++;
			}
			else
			{
				p++;
			}
			done = isascii(*p) && isspace(*p);
		}

		if (*p != '\0')
			*p++ = '\0';
	}

	if (map->map_app != NULL)
		map->map_app = newstr(ldap_map_dequote(map->map_app));

	if (map->map_domain != NULL)
		map->map_domain = newstr(ldap_map_dequote(map->map_domain));

	/*
	** We need to swallow up all the stuff into a struct
	** and dump it into map->map_dbptr1
	*/

	if (lmap->ldaphost != NULL)
		lmap->ldaphost = newstr(ldap_map_dequote(lmap->ldaphost));
	else
	{
		syserr("LDAP map: -h flag is required");
		return FALSE;
	}

	if (lmap->binddn != NULL)
		lmap->binddn = newstr(ldap_map_dequote(lmap->binddn));
	else
		lmap->binddn = DEFAULT_LDAP_MAP_BINDDN;


	if (lmap->passwd != NULL)
		lmap->passwd = newstr(ldap_map_dequote(lmap->passwd));
	else
		lmap->passwd = DEFAULT_LDAP_MAP_PASSWD;

	if (lmap->base != NULL)
		lmap->base = newstr(ldap_map_dequote(lmap->base));
	else
	{
		syserr("LDAP map: -b flag is required");
		return FALSE;
	}


	if (lmap->filter != NULL)
		lmap->filter = newstr(ldap_map_dequote(lmap->filter));
	else
	{
		if (!bitset(MCF_OPTFILE, map->map_class->map_cflags))
		{
			syserr("No filter given in map %s", map->map_mname);
			return FALSE;
		}
	}
	if (lmap->attr != NULL)
		lmap->attr = newstr(ldap_map_dequote(lmap->attr));
	else
	{
		if (!bitset(MCF_OPTFILE, map->map_class->map_cflags))
		{
			syserr("No return attribute in %s", map->map_mname);
			return FALSE;
		}
	}

	map->map_db1 = (ARBPTR_T) lmap;
	return TRUE;
}

#endif /* LDAP Modules */
/*
**  HESIOD Modules
*/

#ifdef HESIOD

#include <hesiod.h>

bool
hes_map_open(map, mode)
	MAP *map;
	int mode;
{
	if (tTd(38, 2))
		printf("hes_map_open(%s, %s, %d)\n",
			map->map_mname, map->map_file, mode);

	if (mode != O_RDONLY)
	{
		/* issue a pseudo-error message */
#ifdef ENOSYS
		errno = ENOSYS;
#else
# ifdef EFTYPE
		errno = EFTYPE;
# else
		errno = ENXIO;
# endif
#endif
		return FALSE;
	}

	if (hes_error() == HES_ER_UNINIT)
		hes_init();
	switch (hes_error())
	{
	  case HES_ER_OK:
	  case HES_ER_NOTFOUND:
		return TRUE;
	}

	if (!bitset(MF_OPTIONAL, map->map_mflags))
		syserr("421 cannot initialize Hesiod map (%d)", hes_error());

	return FALSE;
}

char *
hes_map_lookup(map, name, av, statp)
	MAP *map;
	char *name;
	char **av;
	int *statp;
{
	char **hp;

	if (tTd(38, 20))
		printf("hes_map_lookup(%s, %s)\n", map->map_file, name);

	if (name[0] == '\\')
	{
		char *np;
		int nl;
		char nbuf[MAXNAME];

		nl = strlen(name);
		if (nl < sizeof nbuf - 1)
			np = nbuf;
		else
			np = xalloc(strlen(name) + 2);
		np[0] = '\\';
		strcpy(&np[1], name);
		hp = hes_resolve(np, map->map_file);
		if (np != nbuf)
			free(np);
	}
	else
	{
		hp = hes_resolve(name, map->map_file);
	}
	if (hp == NULL || hp[0] == NULL)
	{
		switch (hes_error())
		{
		  case HES_ER_OK:
			*statp = EX_OK;
			break;

		  case HES_ER_NOTFOUND:
			*statp = EX_NOTFOUND;
			break;

		  case HES_ER_CONFIG:
			*statp = EX_UNAVAILABLE;
			break;

		  case HES_ER_NET:
			*statp = EX_TEMPFAIL;
			break;
		}
		return NULL;
	}
	
	if (bitset(MF_MATCHONLY, map->map_mflags))
		return map_rewrite(map, name, strlen(name), NULL);
	else
		return map_rewrite(map, hp[0], strlen(hp[0]), av);
}

#endif
/*
**  NeXT NETINFO Modules
*/

#if NETINFO

# define NETINFO_DEFAULT_DIR		"/aliases"
# define NETINFO_DEFAULT_PROPERTY	"members"

extern char	*ni_propval __P((char *, char *, char *, char *, int));


/*
**  NI_MAP_OPEN -- open NetInfo Aliases
*/

bool
ni_map_open(map, mode)
	MAP *map;
	int mode;
{
	char *p;

	if (tTd(38, 2))
		printf("ni_map_open(%s, %s, %d)\n",
			map->map_mname, map->map_file, mode);

	if (*map->map_file == '\0')
		map->map_file = NETINFO_DEFAULT_DIR;

	if (map->map_valcolnm == NULL)
		map->map_valcolnm = NETINFO_DEFAULT_PROPERTY;

	if (map->map_coldelim == '\0' && bitset(MF_ALIAS, map->map_mflags))
		map->map_coldelim = ',';

	return TRUE;
}


/*
**  NI_MAP_LOOKUP -- look up a datum in NetInfo
*/

char *
ni_map_lookup(map, name, av, statp)
	MAP *map;
	char *name;
	char **av;
	int *statp;
{
	char *res;
	char *propval;

	if (tTd(38, 20))
		printf("ni_map_lookup(%s, %s)\n", map->map_mname, name);

	propval = ni_propval(map->map_file, map->map_keycolnm, name,
			     map->map_valcolnm, map->map_coldelim);

	if (propval == NULL)
		return NULL;

	if (bitset(MF_MATCHONLY, map->map_mflags))
		res = map_rewrite(map, name, strlen(name), NULL);
	else
		res = map_rewrite(map, propval, strlen(propval), av);
	free(propval);
	return res;
}


bool
ni_getcanonname(name, hbsize, statp)
	char *name;
	int hbsize;
	int *statp;
{
	char *vptr;
	char nbuf[MAXNAME + 1];

	if (tTd(38, 20))
		printf("ni_getcanonname(%s)\n", name);

	if (strlen(name) >= sizeof nbuf)
	{
		*statp = EX_UNAVAILABLE;
		return FALSE;
	}
	(void) strcpy(nbuf, name);
	shorten_hostname(nbuf);

	/* we only accept single token search key */
	if (strchr(nbuf, '.'))
	{
		*statp = EX_NOHOST;
		return FALSE;
	}

	/* Do the search */
	vptr = ni_propval("/machines", NULL, nbuf, "name", '\0');

	if (vptr == NULL)
	{
		*statp = EX_NOHOST;
		return FALSE;
	}

	if (hbsize >= strlen(vptr))
	{
		strcpy(name, vptr);
		*statp = EX_OK;
		return TRUE;
	}
	*statp = EX_UNAVAILABLE;
	free(vptr);
	return FALSE;
}


/*
**  NI_PROPVAL -- NetInfo property value lookup routine
**
**	Parameters:
**		keydir -- the NetInfo directory name in which to search
**			for the key.
**		keyprop -- the name of the property in which to find the
**			property we are interested.  Defaults to "name".
**		keyval -- the value for which we are really searching.
**		valprop -- the property name for the value in which we
**			are interested.
**		sepchar -- if non-nil, this can be multiple-valued, and
**			we should return a string separated by this
**			character.
**
**	Returns:
**		NULL -- if:
**			1. the directory is not found
**			2. the property name is not found
**			3. the property contains multiple values
**			4. some error occured
**		else -- the value of the lookup.
**
**	Example:
**		To search for an alias value, use:
**		  ni_propval("/aliases", "name", aliasname, "members", ',')
**
**	Notes:
**      	Caller should free the return value of ni_proval
*/

# include <netinfo/ni.h>

# define LOCAL_NETINFO_DOMAIN    "."
# define PARENT_NETINFO_DOMAIN   ".."
# define MAX_NI_LEVELS           256

char *
ni_propval(keydir, keyprop, keyval, valprop, sepchar)
	char *keydir;
	char *keyprop;
	char *keyval;
	char *valprop;
	int sepchar;
{
	char *propval = NULL;
	int i;
	int j, alen;
	void *ni = NULL;
	void *lastni = NULL;
	ni_status nis;
	ni_id nid;
	ni_namelist ninl;
	register char *p;
	char keybuf[1024];

	/*
	**  Create the full key from the two parts.
	**
	**	Note that directory can end with, e.g., "name=" to specify
	**	an alternate search property.
	*/

	i = strlen(keydir) + strlen(keyval) + 2;
	if (keyprop != NULL)
		i += strlen(keyprop) + 1;
	if (i > sizeof keybuf)
		return NULL;
	strcpy(keybuf, keydir);
	strcat(keybuf, "/");
	if (keyprop != NULL)
	{
		strcat(keybuf, keyprop);
		strcat(keybuf, "=");
	}
	strcat(keybuf, keyval);

	if (tTd(38, 21))
		printf("ni_propval(%s, %s, %s, %s, %d) keybuf='%s'\n",
			keydir, keyprop, keyval, valprop, sepchar, keybuf);
	/*
	**  If the passed directory and property name are found
	**  in one of netinfo domains we need to search (starting
	**  from the local domain moving all the way back to the
	**  root domain) set propval to the property's value
	**  and return it.
	*/

	for (i = 0; i < MAX_NI_LEVELS && propval == NULL; i++)
	{
		if (i == 0)
		{
			nis = ni_open(NULL, LOCAL_NETINFO_DOMAIN, &ni);
			if (tTd(38, 20))
				printf("ni_open(LOCAL) = %d\n", nis);
		}
		else
		{
			if (lastni != NULL)
				ni_free(lastni);
			lastni = ni;
			nis = ni_open(lastni, PARENT_NETINFO_DOMAIN, &ni);
			if (tTd(38, 20))
				printf("ni_open(PARENT) = %d\n", nis);
		}

		/*
		**  Don't bother if we didn't get a handle on a
		**  proper domain.  This is not necessarily an error.
		**  We would get a positive ni_status if, for instance
		**  we never found the directory or property and tried
		**  to open the parent of the root domain!
		*/

		if (nis != 0)
			break;

		/*
		**  Find the path to the server information.
		*/

		if (ni_pathsearch(ni, &nid, keybuf) != 0)
			continue;

		/*
		**  Find associated value information.
		*/

		if (ni_lookupprop(ni, &nid, valprop, &ninl) != 0)
			continue;

		if (tTd(38, 20))
			printf("ni_lookupprop: len=%d\n", ninl.ni_namelist_len);
		/*
		**  See if we have an acceptable number of values.
		*/

		if (ninl.ni_namelist_len <= 0)
			continue;

		if (sepchar == '\0' && ninl.ni_namelist_len > 1)
		{
			ni_namelist_free(&ninl);
			continue;
		}

		/*
		**  Calculate number of bytes needed and build result
		*/

		alen = 1;
		for (j = 0; j < ninl.ni_namelist_len; j++)
			alen += strlen(ninl.ni_namelist_val[j]) + 1;
		propval = p = xalloc(alen);
		for (j = 0; j < ninl.ni_namelist_len; j++)
		{
			strcpy(p, ninl.ni_namelist_val[j]);
			p += strlen(p);
			*p++ = sepchar;
		}
		*--p = '\0';

		ni_namelist_free(&ninl);
	}

	/*
	**  Clean up.
	*/

	if (ni != NULL)
		ni_free(ni);
	if (lastni != NULL && ni != lastni)
		ni_free(lastni);
	if (tTd(38, 20))
		printf("ni_propval returns: '%s'\n", propval);

	return propval;
}

#endif
/*
**  TEXT (unindexed text file) Modules
**
**	This code donated by Sun Microsystems.
*/


/*
**  TEXT_MAP_OPEN -- open text table
*/

bool
text_map_open(map, mode)
	MAP *map;
	int mode;
{
	struct stat sbuf;

	if (tTd(38, 2))
		printf("text_map_open(%s, %s, %d)\n",
			map->map_mname, map->map_file, mode);

	if (mode != O_RDONLY)
	{
		errno = ENODEV;
		return FALSE;
	}

	if (*map->map_file == '\0')
	{
		syserr("text map \"%s\": file name required",
			map->map_mname);
		return FALSE;
	}

	if (map->map_file[0] != '/')
	{
		syserr("text map \"%s\": file name must be fully qualified",
			map->map_mname);
		return FALSE;
	}
	/* check to see if this map actually accessable */
	if (access(map->map_file, R_OK) <0)
	{
		if (tTd(38, 2))
			printf("text_map_open(%s, %s): cannot access: %s\n",
				map->map_mname, map->map_file, errstring(errno));
		if (!bitset(MF_OPTIONAL, map->map_mflags))
			syserr("text map \"%s\": cannot access file %s",
				map->map_mname, map->map_file);
		return FALSE;
	}

	/* check to see if this map actually exist */
	if (stat(map->map_file, &sbuf) <0)
	{
		syserr("text_map_open(%s, %s): cannot stat",
			map->map_mname, map->map_file);
		return FALSE;
	}

	if (!S_ISREG(sbuf.st_mode))
	{
		syserr("text map \"%s\": %s is not a regular file",
			map->map_mname, map->map_file);
		return FALSE;
	}

	if (map->map_keycolnm == NULL)
		map->map_keycolno = 0;
	else
	{
		if (!isdigit(*map->map_keycolnm))
		{
			syserr("text map \"%s\", file %s: -k should specify a number, not %s",
				map->map_mname, map->map_file,
				map->map_keycolnm);
			return FALSE;
		}
		map->map_keycolno = atoi(map->map_keycolnm);
	}

	if (map->map_valcolnm == NULL)
		map->map_valcolno = 0;
	else
	{
		if (!isdigit(*map->map_valcolnm))
		{
			syserr("text map \"%s\", file %s: -v should specify a number, not %s",
					map->map_mname, map->map_file,
					map->map_valcolnm);
			return FALSE;
		}
		map->map_valcolno = atoi(map->map_valcolnm);
	}

	if (tTd(38, 2))
	{
		printf("text_map_open(%s, %s): delimiter = ",
			map->map_mname, map->map_file);
		if (map->map_coldelim == '\0')
			printf("(white space)\n");
		else
			printf("%c\n", map->map_coldelim);
	}

	return TRUE;
}


/*
**  TEXT_MAP_LOOKUP -- look up a datum in a TEXT table
*/

char *
text_map_lookup(map, name, av, statp)
	MAP *map;
	char *name;
	char **av;
	int *statp;
{
	char *vp;
	auto int vsize;
	int buflen;
	char search_key[MAXNAME + 1];
	char linebuf[MAXLINE];
	FILE *f;
	char buf[MAXNAME + 1];
	char delim;
	int key_idx;
	bool found_it;
	extern char *get_column __P((char *, int, char, char *, int));

	found_it = FALSE;
	if (tTd(38, 20))
		printf("text_map_lookup(%s, %s)\n", map->map_mname,  name);

	buflen = strlen(name);
	if (buflen > sizeof search_key - 1)
		buflen = sizeof search_key - 1;
	bcopy(name, search_key, buflen);
	search_key[buflen] = '\0';
	if (!bitset(MF_NOFOLDCASE, map->map_mflags))
		makelower(search_key);

	f = fopen(map->map_file, "r");
	if (f == NULL)
	{
		map->map_mflags &= ~(MF_VALID|MF_OPEN);
		*statp = EX_UNAVAILABLE;
		return NULL;
	}
	key_idx = map->map_keycolno;
	delim = map->map_coldelim;
	while (fgets(linebuf, MAXLINE, f) != NULL)
	{
		char *p;

		/* skip comment line */
		if (linebuf[0] == '#')
			continue;
		p = strchr(linebuf, '\n');
		if (p != NULL)
			*p = '\0';
		p = get_column(linebuf, key_idx, delim, buf, sizeof buf);
		if (p != NULL && strcasecmp(search_key, p) == 0)
		{
			found_it = TRUE;
			break;
		}
	}
	fclose(f);
	if (!found_it)
	{
		*statp = EX_NOTFOUND;
		return NULL;
	}
	vp = get_column(linebuf, map->map_valcolno, delim, buf, sizeof buf);
	vsize = strlen(vp);
	*statp = EX_OK;
	if (bitset(MF_MATCHONLY, map->map_mflags))
		return map_rewrite(map, name, strlen(name), NULL);
	else
		return map_rewrite(map, vp, vsize, av);
}


/*
**  TEXT_GETCANONNAME -- look up canonical name in hosts file
*/

bool
text_getcanonname(name, hbsize, statp)
	char *name;
	int hbsize;
	int *statp;
{
	bool found;
	FILE *f;
	char linebuf[MAXLINE];
	char cbuf[MAXNAME + 1];
	char nbuf[MAXNAME + 1];
	extern char *get_column __P((char *, int, char, char *, int));

	if (tTd(38, 20))
		printf("text_getcanonname(%s)\n", name);

	if (strlen(name) >= (SIZE_T) sizeof nbuf)
	{
		*statp = EX_UNAVAILABLE;
		return FALSE;
	}
	(void) strcpy(nbuf, name);
	shorten_hostname(nbuf);

	f = fopen(HostsFile, "r");
	if (f == NULL)
	{
		*statp = EX_UNAVAILABLE;
		return FALSE;
	}
	found = FALSE;
	while (!found && fgets(linebuf, MAXLINE, f) != NULL)
	{
		char *p = strpbrk(linebuf, "#\n");

		if (p != NULL)
			*p = '\0';
		if (linebuf[0] != '\0')
			found = extract_canonname(nbuf, linebuf, cbuf, sizeof cbuf);
	}
	fclose(f);
	if (!found)
	{
		*statp = EX_NOHOST;
		return FALSE;
	}

	if ((SIZE_T) hbsize >= strlen(cbuf))
	{
		strcpy(name, cbuf);
		*statp = EX_OK;
		return TRUE;
	}
	*statp = EX_UNAVAILABLE;
	return FALSE;
}
/*
**  STAB (Symbol Table) Modules
*/


/*
**  STAB_MAP_LOOKUP -- look up alias in symbol table
*/

char *
stab_map_lookup(map, name, av, pstat)
	register MAP *map;
	char *name;
	char **av;
	int *pstat;
{
	register STAB *s;

	if (tTd(38, 20))
		printf("stab_lookup(%s, %s)\n",
			map->map_mname, name);

	s = stab(name, ST_ALIAS, ST_FIND);
	if (s != NULL)
		return (s->s_alias);
	return (NULL);
}


/*
**  STAB_MAP_STORE -- store in symtab (actually using during init, not rebuild)
*/

void
stab_map_store(map, lhs, rhs)
	register MAP *map;
	char *lhs;
	char *rhs;
{
	register STAB *s;

	s = stab(lhs, ST_ALIAS, ST_ENTER);
	s->s_alias = newstr(rhs);
}


/*
**  STAB_MAP_OPEN -- initialize (reads data file)
**
**	This is a wierd case -- it is only intended as a fallback for
**	aliases.  For this reason, opens for write (only during a
**	"newaliases") always fails, and opens for read open the
**	actual underlying text file instead of the database.
*/

bool
stab_map_open(map, mode)
	register MAP *map;
	int mode;
{
	FILE *af;
	struct stat st;

	if (tTd(38, 2))
		printf("stab_map_open(%s, %s, %d)\n",
			map->map_mname, map->map_file, mode);

	if (mode != O_RDONLY)
	{
		errno = ENODEV;
		return FALSE;
	}

	af = fopen(map->map_file, "r");
	if (af == NULL)
		return FALSE;
	readaliases(map, af, FALSE, FALSE);

	if (fstat(fileno(af), &st) >= 0)
		map->map_mtime = st.st_mtime;
	fclose(af);

	return TRUE;
}
/*
**  Implicit Modules
**
**	Tries several types.  For back compatibility of aliases.
*/


/*
**  IMPL_MAP_LOOKUP -- lookup in best open database
*/

char *
impl_map_lookup(map, name, av, pstat)
	MAP *map;
	char *name;
	char **av;
	int *pstat;
{
	if (tTd(38, 20))
		printf("impl_map_lookup(%s, %s)\n",
			map->map_mname, name);

#ifdef NEWDB
	if (bitset(MF_IMPL_HASH, map->map_mflags))
		return db_map_lookup(map, name, av, pstat);
#endif
#ifdef NDBM
	if (bitset(MF_IMPL_NDBM, map->map_mflags))
		return ndbm_map_lookup(map, name, av, pstat);
#endif
	return stab_map_lookup(map, name, av, pstat);
}

/*
**  IMPL_MAP_STORE -- store in open databases
*/

void
impl_map_store(map, lhs, rhs)
	MAP *map;
	char *lhs;
	char *rhs;
{
	if (tTd(38, 12))
		printf("impl_map_store(%s, %s, %s)\n",
			map->map_mname, lhs, rhs);
#ifdef NEWDB
	if (bitset(MF_IMPL_HASH, map->map_mflags))
		db_map_store(map, lhs, rhs);
#endif
#ifdef NDBM
	if (bitset(MF_IMPL_NDBM, map->map_mflags))
		ndbm_map_store(map, lhs, rhs);
#endif
	stab_map_store(map, lhs, rhs);
}

/*
**  IMPL_MAP_OPEN -- implicit database open
*/

bool
impl_map_open(map, mode)
	MAP *map;
	int mode;
{
	if (tTd(38, 2))
		printf("impl_map_open(%s, %s, %d)\n",
			map->map_mname, map->map_file, mode);

#ifdef NEWDB
	map->map_mflags |= MF_IMPL_HASH;
	if (hash_map_open(map, mode))
	{
# ifdef NDBM_YP_COMPAT
		if (mode == O_RDONLY || strstr(map->map_file, "/yp/") == NULL)
# endif
			return TRUE;
	}
	else
		map->map_mflags &= ~MF_IMPL_HASH;
#endif
#ifdef NDBM
	map->map_mflags |= MF_IMPL_NDBM;
	if (ndbm_map_open(map, mode))
	{
		return TRUE;
	}
	else
		map->map_mflags &= ~MF_IMPL_NDBM;
#endif

#if defined(NEWDB) || defined(NDBM)
	if (Verbose)
		message("WARNING: cannot open alias database %s", map->map_file);
#else
	if (mode != O_RDONLY)
		usrerr("Cannot rebuild aliases: no database format defined");
#endif

	return stab_map_open(map, mode);
}


/*
**  IMPL_MAP_CLOSE -- close any open database(s)
*/

void
impl_map_close(map)
	MAP *map;
{
	if (tTd(38, 9))
		printf("impl_map_close(%s, %s, %lx)\n",
			map->map_mname, map->map_file, map->map_mflags);
#ifdef NEWDB
	if (bitset(MF_IMPL_HASH, map->map_mflags))
	{
		db_map_close(map);
		map->map_mflags &= ~MF_IMPL_HASH;
	}
#endif

#ifdef NDBM
	if (bitset(MF_IMPL_NDBM, map->map_mflags))
	{
		ndbm_map_close(map);
		map->map_mflags &= ~MF_IMPL_NDBM;
	}
#endif
}
/*
**  User map class.
**
**	Provides access to the system password file.
*/

/*
**  USER_MAP_OPEN -- open user map
**
**	Really just binds field names to field numbers.
*/

bool
user_map_open(map, mode)
	MAP *map;
	int mode;
{
	if (tTd(38, 2))
		printf("user_map_open(%s, %d)\n",
			map->map_mname, mode);

	if (mode != O_RDONLY)
	{
		/* issue a pseudo-error message */
#ifdef ENOSYS
		errno = ENOSYS;
#else
# ifdef EFTYPE
		errno = EFTYPE;
# else
		errno = ENXIO;
# endif
#endif
		return FALSE;
	}
	if (map->map_valcolnm == NULL)
		/* nothing */ ;
	else if (strcasecmp(map->map_valcolnm, "name") == 0)
		map->map_valcolno = 1;
	else if (strcasecmp(map->map_valcolnm, "passwd") == 0)
		map->map_valcolno = 2;
	else if (strcasecmp(map->map_valcolnm, "uid") == 0)
		map->map_valcolno = 3;
	else if (strcasecmp(map->map_valcolnm, "gid") == 0)
		map->map_valcolno = 4;
	else if (strcasecmp(map->map_valcolnm, "gecos") == 0)
		map->map_valcolno = 5;
	else if (strcasecmp(map->map_valcolnm, "dir") == 0)
		map->map_valcolno = 6;
	else if (strcasecmp(map->map_valcolnm, "shell") == 0)
		map->map_valcolno = 7;
	else
	{
		syserr("User map %s: unknown column name %s",
			map->map_mname, map->map_valcolnm);
		return FALSE;
	}
	return TRUE;
}


/*
**  USER_MAP_LOOKUP -- look up a user in the passwd file.
*/

char *
user_map_lookup(map, key, av, statp)
	MAP *map;
	char *key;
	char **av;
	int *statp;
{
	struct passwd *pw;
	auto bool fuzzy;

	if (tTd(38, 20))
		printf("user_map_lookup(%s, %s)\n",
			map->map_mname, key);

	pw = finduser(key, &fuzzy);
	if (pw == NULL)
		return NULL;
	if (bitset(MF_MATCHONLY, map->map_mflags))
		return map_rewrite(map, key, strlen(key), NULL);
	else
	{
		char *rwval = NULL;
		char buf[30];

		switch (map->map_valcolno)
		{
		  case 0:
		  case 1:
			rwval = pw->pw_name;
			break;

		  case 2:
			rwval = pw->pw_passwd;
			break;

		  case 3:
			snprintf(buf, sizeof buf, "%d", pw->pw_uid);
			rwval = buf;
			break;

		  case 4:
			snprintf(buf, sizeof buf, "%d", pw->pw_gid);
			rwval = buf;
			break;

		  case 5:
			rwval = pw->pw_gecos;
			break;

		  case 6:
			rwval = pw->pw_dir;
			break;

		  case 7:
			rwval = pw->pw_shell;
			break;
		}
		return map_rewrite(map, rwval, strlen(rwval), av);
	}
}
/*
**  Program map type.
**
**	This provides access to arbitrary programs.  It should be used
**	only very sparingly, since there is no way to bound the cost
**	of invoking an arbitrary program.
*/

char *
prog_map_lookup(map, name, av, statp)
	MAP *map;
	char *name;
	char **av;
	int *statp;
{
	int i;
	register char *p;
	int fd;
	auto pid_t pid;
	char *rval;
	int stat;
	char *argv[MAXPV + 1];
	char buf[MAXLINE];

	if (tTd(38, 20))
		printf("prog_map_lookup(%s, %s) %s\n",
			map->map_mname, name, map->map_file);

	i = 0;
	argv[i++] = map->map_file;
	if (map->map_rebuild != NULL)
	{
		snprintf(buf, sizeof buf, "%s", map->map_rebuild);
		for (p = strtok(buf, " \t"); p != NULL; p = strtok(NULL, " \t"))
		{
			if (i >= MAXPV - 1)
				break;
			argv[i++] = p;
		}
	}
	argv[i++] = name;
	argv[i] = NULL;
	if (tTd(38, 21))
	{
		printf("prog_open:");
		for (i = 0; argv[i] != NULL; i++)
			printf(" %s", argv[i]);
		printf("\n");
	}
	(void) blocksignal(SIGCHLD);
	pid = prog_open(argv, &fd, CurEnv);
	if (pid < 0)
	{
		if (!bitset(MF_OPTIONAL, map->map_mflags))
			syserr("prog_map_lookup(%s) failed (%s) -- closing",
				map->map_mname, errstring(errno));
		else if (tTd(38, 9))
			printf("prog_map_lookup(%s) failed (%s) -- closing",
				map->map_mname, errstring(errno));
		map->map_mflags &= ~(MF_VALID|MF_OPEN);
		*statp = EX_OSFILE;
		return NULL;
	}
	i = read(fd, buf, sizeof buf - 1);
	if (i < 0)
	{
		syserr("prog_map_lookup(%s): read error %s\n",
			map->map_mname, errstring(errno));
		rval = NULL;
	}
	else if (i == 0)
	{
		if (tTd(38, 20))
			printf("prog_map_lookup(%s): empty answer\n",
				map->map_mname);
		rval = NULL;
	}
	else
	{
		buf[i] = '\0';
		p = strchr(buf, '\n');
		if (p != NULL)
			*p = '\0';

		/* collect the return value */
		if (bitset(MF_MATCHONLY, map->map_mflags))
			rval = map_rewrite(map, name, strlen(name), NULL);
		else
			rval = map_rewrite(map, buf, strlen(buf), NULL);

		/* now flush any additional output */
		while ((i = read(fd, buf, sizeof buf)) > 0)
			continue;
	}

	/* wait for the process to terminate */
	close(fd);
	stat = waitfor(pid);
	(void) releasesignal(SIGCHLD);

	if (stat == -1)
	{
		syserr("prog_map_lookup(%s): wait error %s\n",
			map->map_mname, errstring(errno));
		*statp = EX_SOFTWARE;
		rval = NULL;
	}
	else if (WIFEXITED(stat))
	{
		if ((*statp = WEXITSTATUS(stat)) != EX_OK)
			rval = NULL;
	}
	else
	{
		syserr("prog_map_lookup(%s): child died on signal %d",
			map->map_mname, stat);
		*statp = EX_UNAVAILABLE;
		rval = NULL;
	}
	return rval;
}
/*
**  Sequenced map type.
**
**	Tries each map in order until something matches, much like
**	implicit.  Stores go to the first map in the list that can
**	support storing.
**
**	This is slightly unusual in that there are two interfaces.
**	The "sequence" interface lets you stack maps arbitrarily.
**	The "switch" interface builds a sequence map by looking
**	at a system-dependent configuration file such as
**	/etc/nsswitch.conf on Solaris or /etc/svc.conf on Ultrix.
**
**	We don't need an explicit open, since all maps are
**	opened during startup, including underlying maps.
*/

/*
**  SEQ_MAP_PARSE -- Sequenced map parsing
*/

bool
seq_map_parse(map, ap)
	MAP *map;
	char *ap;
{
	int maxmap;

	if (tTd(38, 2))
		printf("seq_map_parse(%s, %s)\n", map->map_mname, ap);
	maxmap = 0;
	while (*ap != '\0')
	{
		register char *p;
		STAB *s;

		/* find beginning of map name */
		while (isascii(*ap) && isspace(*ap))
			ap++;
		for (p = ap; isascii(*p) && isalnum(*p); p++)
			continue;
		if (*p != '\0')
			*p++ = '\0';
		while (*p != '\0' && (!isascii(*p) || !isalnum(*p)))
			p++;
		if (*ap == '\0')
		{
			ap = p;
			continue;
		}
		s = stab(ap, ST_MAP, ST_FIND);
		if (s == NULL)
		{
			syserr("Sequence map %s: unknown member map %s",
				map->map_mname, ap);
		}
		else if (maxmap == MAXMAPSTACK)
		{
			syserr("Sequence map %s: too many member maps (%d max)",
				map->map_mname, MAXMAPSTACK);
			maxmap++;
		}
		else if (maxmap < MAXMAPSTACK)
		{
			map->map_stack[maxmap++] = &s->s_map;
		}
		ap = p;
	}
	return TRUE;
}


/*
**  SWITCH_MAP_OPEN -- open a switched map
**
**	This looks at the system-dependent configuration and builds
**	a sequence map that does the same thing.
**
**	Every system must define a switch_map_find routine in conf.c
**	that will return the list of service types associated with a
**	given service class.
*/

bool
switch_map_open(map, mode)
	MAP *map;
	int mode;
{
	int mapno;
	int nmaps;
	char *maptype[MAXMAPSTACK];

	if (tTd(38, 2))
		printf("switch_map_open(%s, %s, %d)\n",
			map->map_mname, map->map_file, mode);

	nmaps = switch_map_find(map->map_file, maptype, map->map_return);
	if (tTd(38, 19))
	{
		printf("\tswitch_map_find => %d\n", nmaps);
		for (mapno = 0; mapno < nmaps; mapno++)
			printf("\t\t%s\n", maptype[mapno]);
	}
	if (nmaps <= 0 || nmaps > MAXMAPSTACK)
		return FALSE;

	for (mapno = 0; mapno < nmaps; mapno++)
	{
		register STAB *s;
		char nbuf[MAXNAME + 1];

		if (maptype[mapno] == NULL)
			continue;
		(void) snprintf(nbuf, sizeof nbuf, "%s.%s",
			map->map_mname, maptype[mapno]);
		s = stab(nbuf, ST_MAP, ST_FIND);
		if (s == NULL)
		{
			syserr("Switch map %s: unknown member map %s",
				map->map_mname, nbuf);
		}
		else
		{
			map->map_stack[mapno] = &s->s_map;
			if (tTd(38, 4))
				printf("\tmap_stack[%d] = %s:%s\n",
					mapno, s->s_map.map_class->map_cname,
					nbuf);
		}
	}
	return TRUE;
}


/*
**  SEQ_MAP_CLOSE -- close all underlying maps
*/

void
seq_map_close(map)
	MAP *map;
{
	int mapno;

	if (tTd(38, 9))
		printf("seq_map_close(%s)\n", map->map_mname);

	for (mapno = 0; mapno < MAXMAPSTACK; mapno++)
	{
		MAP *mm = map->map_stack[mapno];

		if (mm == NULL || !bitset(MF_OPEN, mm->map_mflags))
			continue;
		mm->map_class->map_close(mm);
		mm->map_mflags &= ~(MF_OPEN|MF_WRITABLE);
	}
}


/*
**  SEQ_MAP_LOOKUP -- sequenced map lookup
*/

char *
seq_map_lookup(map, key, args, pstat)
	MAP *map;
	char *key;
	char **args;
	int *pstat;
{
	int mapno;
	int mapbit = 0x01;
	bool tempfail = FALSE;

	if (tTd(38, 20))
		printf("seq_map_lookup(%s, %s)\n", map->map_mname, key);

	for (mapno = 0; mapno < MAXMAPSTACK; mapbit <<= 1, mapno++)
	{
		MAP *mm = map->map_stack[mapno];
		char *rv;

		if (mm == NULL)
			continue;
		if (!bitset(MF_OPEN, mm->map_mflags))
		{
			if (bitset(mapbit, map->map_return[MA_UNAVAIL]))
			{
				*pstat = EX_UNAVAILABLE;
				return NULL;
			}
			continue;
		}
		*pstat = EX_OK;
		rv = mm->map_class->map_lookup(mm, key, args, pstat);
		if (rv != NULL)
			return rv;
		if (*pstat == EX_TEMPFAIL)
		{
			if (bitset(mapbit, map->map_return[MA_TRYAGAIN]))
				return NULL;
			tempfail = TRUE;
		}
		else if (bitset(mapbit, map->map_return[MA_NOTFOUND]))
			break;
	}
	if (tempfail)
		*pstat = EX_TEMPFAIL;
	else if (*pstat == EX_OK)
		*pstat = EX_NOTFOUND;
	return NULL;
}


/*
**  SEQ_MAP_STORE -- sequenced map store
*/

void
seq_map_store(map, key, val)
	MAP *map;
	char *key;
	char *val;
{
	int mapno;

	if (tTd(38, 12))
		printf("seq_map_store(%s, %s, %s)\n",
			map->map_mname, key, val);

	for (mapno = 0; mapno < MAXMAPSTACK; mapno++)
	{
		MAP *mm = map->map_stack[mapno];

		if (mm == NULL || !bitset(MF_WRITABLE, mm->map_mflags))
			continue;

		mm->map_class->map_store(mm, key, val);
		return;
	}
	syserr("seq_map_store(%s, %s, %s): no writable map",
		map->map_mname, key, val);
}
/*
**  NULL stubs
*/

bool
null_map_open(map, mode)
	MAP *map;
	int mode;
{
	return TRUE;
}

void
null_map_close(map)
	MAP *map;
{
	return;
}

char *
null_map_lookup(map, key, args, pstat)
	MAP *map;
	char *key;
	char **args;
	int *pstat;
{
	*pstat = EX_NOTFOUND;
	return NULL;
}

void
null_map_store(map, key, val)
	MAP *map;
	char *key;
	char *val;
{
	return;
}


/*
**  BOGUS stubs
*/

char *
bogus_map_lookup(map, key, args, pstat)
	MAP *map;
	char *key;
	char **args;
	int *pstat;
{
	*pstat = EX_TEMPFAIL;
	return NULL;
}

MAPCLASS	BogusMapClass =
{
	"bogus-map",		NULL,		0,
	NULL,		bogus_map_lookup,	null_map_store,
	null_map_open,	null_map_close,
};
