/*
 *	ns_nis.c
 *
 *	Copyright (c) 1988-1996 Sun Microsystems Inc
 *	All Rights Reserved.
 */

#pragma ident	"@(#)ns_nis.c	1.32	97/04/25 SMI"

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <syslog.h>
#include <string.h>
#include <ctype.h>
#include <nsswitch.h>
#include <sys/param.h>
#include <sys/types.h>
#include <sys/systeminfo.h>
#include <rpc/rpc.h>
#include <rpcsvc/nfs_prot.h>
#include <rpcsvc/ypclnt.h>
#include <rpcsvc/yp_prot.h>
#include <sys/errno.h>
#include "automount.h"

#define	KEY		0
#define	CONTENTS	1

static int replace_undscr_by_dot(char *);
static int nis_err(int);

static char nis_mydomain[YPMAXDOMAIN];

struct dir_cbdata {
	struct dir_entry **list;
	struct dir_entry *last;
	int error;
};

static int readdir_callback(int, char *, int, const char *,
				int, struct dir_cbdata *);

void
init_nis(char **stack, char ***stkptr)
{
#ifdef lint
	stack = stack;
	stkptr = stkptr;
#endif /* lint */

	(void) sysinfo(SI_SRPC_DOMAIN, nis_mydomain, sizeof (nis_mydomain));
	(void) __nis_reset_state();	/* XXX temporary hack for csh bug */
}

getmapent_nis(key, map, ml, stack, stkptr, iswildcard)
	char *key, *map;
	struct mapline *ml;
	char **stack;
	char ***stkptr;
	bool_t *iswildcard;
{
	char *nisline = NULL;
	char *my_map = NULL;
	char *lp, *lq;
	int nislen, len;
	int nserr;

#ifdef lint
	stack = stack;
	stkptr = stkptr;
#endif /* lint */

	if (iswildcard)
		*iswildcard = FALSE;
	nserr = yp_match(nis_mydomain, map, key, strlen(key),
						&nisline, &nislen);
	if (nserr == YPERR_MAP) {
		my_map = strdup(map);
		if (my_map == NULL) {
			syslog(LOG_ERR,
				"getmapent_nis: memory alloc failed: %m");
			return (__NSW_UNAVAIL);
		}
		if (replace_undscr_by_dot(my_map))
			nserr = yp_match(nis_mydomain, my_map, key,
					strlen(key), &nisline, &nislen);
	}

	if (nserr) {
		if (nserr == YPERR_KEY) {
			/*
			 * Try the default entry "*"
			 */
			if (my_map == NULL)
				nserr = yp_match(nis_mydomain, map, "*", 1,
						&nisline, &nislen);
			else
				nserr = yp_match(nis_mydomain, my_map, "*", 1,
						&nisline, &nislen);
			if (!nserr && iswildcard)
				*iswildcard = TRUE;
		} else {
			if (verbose)
				syslog(LOG_ERR, "%s: %s",
					map, yperr_string(nserr));
			nserr = 1;
		}
	}
	if (my_map != NULL)
		free(my_map);

	nserr = nis_err(nserr);
	if (nserr)
		goto done;

	/*
	 * at this point we are sure that yp_match succeeded
	 * so massage the entry by
	 * 1. ignoring # and beyond
	 * 2. trim the trailing whitespace
	 */
	if (lp = strchr(nisline, '#'))
		*lp = '\0';
	len = strlen(nisline);
	if (len == 0) {
		nserr = __NSW_NOTFOUND;
		goto done;
	}
	lp = &nisline[len - 1];
	while (lp > nisline && isspace(*lp))
		*lp-- = '\0';
	if (lp == nisline) {
		nserr = __NSW_NOTFOUND;
		goto done;
	}
	(void) strcpy(ml->linebuf, nisline);
	lp = ml->linebuf;
	lq = ml->lineqbuf;
	unquote(lp, lq);
	/* now we have the correct line */

	nserr = __NSW_SUCCESS;
done:
	if (nisline)
		free((char *) nisline);
	return (nserr);

}

loadmaster_nis(mapname, defopts, stack, stkptr)
	char *mapname, *defopts;
	char **stack;
	char ***stkptr;
{
	int first, err;
	char *key, *nkey, *val;
	int kl, nkl, vl;
	char dir[256], map[256], qbuff[256];
	char *pmap, *opts, *my_mapname;
	int count = 0;

	first = 1;
	key  = NULL; kl  = 0;
	nkey = NULL; nkl = 0;
	val  = NULL; vl  = 0;

	/*
	 * need a private copy of mapname, because we may change
	 * the underscores by dots. We however do not want the
	 * orignal to be changed, as we may want to use the
	 * original name in some other name service
	 */
	my_mapname = strdup(mapname);
	if (my_mapname == NULL) {
		syslog(LOG_ERR, "loadmaster_yp: memory alloc failed: %m");
		/* not the name svc's fault but ... */
		return (__NSW_UNAVAIL);
	}
	for (;;) {
		if (first) {
			first = 0;
			err = yp_first(nis_mydomain, my_mapname,
				&nkey, &nkl, &val, &vl);

			if ((err == YPERR_MAP) &&
			    (replace_undscr_by_dot(my_mapname)))
				err = yp_first(nis_mydomain, my_mapname,
					&nkey, &nkl, &val, &vl);

			if ((err == YPERR_DOMAIN) || (err == YPERR_YPBIND)) {
				syslog(LOG_ERR,
					"can't read nis map %s: %s - retrying",
					my_mapname, yperr_string(err));
				while ((err == YPERR_DOMAIN) ||
					(err == YPERR_YPBIND)) {
					(void) sleep(20);
					err = yp_first(nis_mydomain, my_mapname,
						&nkey, &nkl, &val, &vl);
				}
				syslog(LOG_ERR,
					"nis map %s: read OK.", my_mapname);
			}
		} else {
			err = yp_next(nis_mydomain, my_mapname, key, kl,
				&nkey, &nkl, &val, &vl);
		}
		if (err) {
			if (err != YPERR_NOMORE && err != YPERR_MAP)
				if (verbose)
					syslog(LOG_ERR, "%s: %s",
					my_mapname, yperr_string(err));
			break;
		}
		if (key)
			free(key);
		key = nkey;
		kl = nkl;


		if (kl >= 256 || vl >= 256)
			break;
		if (kl < 2 || vl < 1)
			break;
		if (isspace(*key) || *key == '#')
			break;
		(void) strncpy(dir, key, kl);
		dir[kl] = '\0';
		if (macro_expand("", dir, qbuff, sizeof (dir))) {
			syslog(LOG_ERR,
			    "%s in NIS map %s: entry too long (max %d chars)",
			    dir, my_mapname, sizeof (dir) - 1);
			break;
		}
		(void) strncpy(map, val, vl);
		map[vl] = '\0';
		if (macro_expand(dir, map, qbuff, sizeof (map))) {
			syslog(LOG_ERR,
			    "%s in NIS map %s: entry too long (max %d chars)",
			    map, my_mapname, sizeof (map) - 1);
			break;
		}
		pmap = map;
		while (*pmap && isspace(*pmap))
			pmap++;		/* skip blanks in front of map */
		opts = pmap;
		while (*opts && !isspace(*opts))
			opts++;
		if (*opts) {
			*opts++ = '\0';
			while (*opts && isspace(*opts))
				opts++;
			if (*opts == '-')
				opts++;
			else
				opts = defopts;
		}
		free(val);

		/*
		 * Check for no embedded blanks.
		 */
		if (strcspn(opts, " 	") == strlen(opts)) {
			dirinit(dir, pmap, opts, 0, stack, stkptr);
			count++;
		} else {
pr_msg("Warning: invalid entry for %s in NIS map %s ignored.\n", dir, mapname);
		}

	}
	if (my_mapname)
		free(my_mapname);

	/*
	 * In the context of a master map, if no entry is
	 * found, it is like NOTFOUND
	 */
	if (count > 0 && err == YPERR_NOMORE)
		return (__NSW_SUCCESS);
	else {
		if (err)
			return (nis_err(err));
		else
			/*
			 * This case will happen if map is empty
			 *  or none of the entries is valid
			 */
			return (__NSW_NOTFOUND);
	}
}

loaddirect_nis(nismap, localmap, opts, stack, stkptr)
	char *nismap, *localmap, *opts;
	char **stack;
	char ***stkptr;
{
	int first, err, count;
	char *key, *nkey, *val, *my_nismap;
	int kl, nkl, vl;
	char dir[100];

	first = 1;
	key  = NULL; kl  = 0;
	nkey = NULL; nkl = 0;
	val  = NULL; vl  = 0;
	count = 0;
	my_nismap = NULL;

	my_nismap = strdup(nismap);
	if (my_nismap == NULL) {
		syslog(LOG_ERR, "loadmaster_yp: memory alloc failed: %m");
		return (__NSW_UNAVAIL);
	}
	for (;;) {
		if (first) {
			first = 0;
			err = yp_first(nis_mydomain, my_nismap, &nkey, &nkl,
					&val, &vl);

			if ((err == YPERR_MAP) &&
			    (replace_undscr_by_dot(my_nismap)))
				err = yp_first(nis_mydomain, my_nismap,
						&nkey, &nkl, &val, &vl);

			if ((err == YPERR_DOMAIN) || (err == YPERR_YPBIND)) {
				syslog(LOG_ERR,
					"can't read nis map %s: %s - retrying",
					my_nismap, yperr_string(err));
				while ((err == YPERR_DOMAIN) ||
					(err == YPERR_YPBIND)) {
					(void) sleep(20);
					err = yp_first(nis_mydomain, my_nismap,
						&nkey, &nkl, &val, &vl);
				}
				syslog(LOG_ERR,
					"nis map %s: read OK.", my_nismap);
			}
		} else {
			err = yp_next(nis_mydomain, my_nismap, key, kl,
					&nkey, &nkl, &val, &vl);
		}
		if (err) {
			if (err != YPERR_NOMORE && err != YPERR_MAP)
				syslog(LOG_ERR, "%s: %s",
					my_nismap, yperr_string(err));
			break;
		}
		if (key)
			free(key);
		key = nkey;
		kl = nkl;

		if (kl < 2 || kl >= 100)
			continue;
		if (isspace(*key) || *key == '#')
			continue;
		(void) strncpy(dir, key, kl);
		dir[kl] = '\0';

		dirinit(dir, localmap, opts, 1, stack, stkptr);
		count++;
		free(val);
	}

	if (my_nismap)
		free(my_nismap);

	if (count > 0 && err == YPERR_NOMORE)
			return (__NSW_SUCCESS);
	else
		return (nis_err(err));

}

static int
replace_undscr_by_dot(map)
	char *map;
{
	int ret_val = 0;

	while (*map) {
		if (*map == '_') {
			ret_val = 1;
			*map = '.';
		}
		map++;
	}
	return (ret_val);
}

static int
nis_err(err)
	int err;
{
	switch (err) {
	case 0:
		return (__NSW_SUCCESS);
	case YPERR_KEY:
		return (__NSW_NOTFOUND);
	case YPERR_MAP:
		return (__NSW_UNAVAIL);
	default:
		return (__NSW_UNAVAIL);
	}
}

int
getmapkeys_nis(nsmap, list, error, cache_time, stack, stkptr)
	char *nsmap;
	struct dir_entry **list;
	int *error;
	int *cache_time;
	char **stack;
	char ***stkptr;
{
	int nserr;
	struct dir_cbdata readdir_cbdata;
	struct ypall_callback cback;
	char *my_map = NULL;

	char *key = NULL, *val = NULL;
	int nkl, vl;

#ifdef lint
	stack = stack;
	stkptr = stkptr;
#endif /* lint */

	*cache_time = RDDIR_CACHE_TIME;

	/*
	 * XXX Hack to determine if we need to replace '_' with '.'
	 * Have to use yp_first() since yp_all() simply fails if
	 * the map is not present
	 */
	my_map = strdup(nsmap);
	if (my_map == NULL) {
		syslog(LOG_ERR,
			"getmapkeys_nis: memory alloc failed: %m");
		*error = ENOMEM;
		return (__NSW_UNAVAIL);
	}
	nserr = yp_first(nis_mydomain, my_map, &key, &nkl, &val, &vl);
	if (nserr == YPERR_MAP) {
		if (replace_undscr_by_dot(my_map)) {
			nserr = yp_first(nis_mydomain, my_map,
					&key, &nkl, &val, &vl);
		}
		if (nserr == YPERR_MAP) {
			/*
			 * map not found
			 */
			*error = 0;	/* return an empty list */
			if (verbose) {
				syslog(LOG_ERR, "%s: %s",
					nsmap, yperr_string(nserr));
			}
			free(my_map);
			return (nis_err(nserr));
		}
		if (key)
			free(key);
		if (val)
			free(val);
	}

	readdir_cbdata.list = list;
	readdir_cbdata.last = NULL;
	readdir_cbdata.error = 0;

	cback.foreach = readdir_callback;
	cback.data = (char *) &readdir_cbdata;

	/*
	 * after all this song and dance we finally
	 * ask for the list of entries
	 */
	nserr = yp_all(nis_mydomain, my_map, &cback);

	free(my_map);
	*error = readdir_cbdata.error;
	if (nserr) {
		if (verbose)
			syslog(LOG_ERR, "%s: %s", nsmap, yperr_string(nserr));
		nserr = 1;
		if (*error == 0)
			*error = ENOENT;

		return (nis_err(nserr));
	}

	return (__NSW_SUCCESS);
}

static int
readdir_callback(instatus, inkey, inkeylen, inval, invallen, indata)
	int instatus;
	char *inkey;
	int inkeylen;
	const char *inval;
	int invallen;
	struct dir_cbdata *indata;
{
	struct dir_entry **list = indata->list;
	struct dir_entry *last = indata->last;
	char key[MAXPATHLEN];

#ifdef lint
	inval = inval;
	invallen = invallen;
#endif

	if (instatus != YP_TRUE)
		return (0);	/* next entry. yp_all may decide otherwise... */

	if (inkeylen == 0 || isspace(*inkey) || *inkey == '#')
		return (0);

	/*
	 * yp_all allocates inkey with two extra bytes which contain
	 * NEWLINE and null but these two bytes are not reflected in
	 * inkeylen.
	 */
	strncpy(key, inkey, inkeylen);
	key[inkeylen] = '\0';

	/*
	 * Wildcard entry should be ignored - following entries should continue
	 * to be read to corroborate with the way we search for entries in yp,
	 * i.e., first for an exact key match and then a wildcard, if there's
	 * no exact key match.
	 */
	if (key[0] == '*' && key[1] == '\0')
		return (0);

	if (add_dir_entry(key, list, &last)) {
		indata->error = ENOMEM;
		return (1);	/* get no more entries */
	}

	indata->last = last;
	indata->error = 0;

	return (0);
}
