/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */

/*
 * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

/*
 * nis_common.c
 *
 * Common code and structures used by name-service-switch "nis" backends.
 */

#include "nis_common.h"
#include <string.h>
#include <synch.h>
#include <rpcsvc/ypclnt.h>
#include <rpcsvc/yp_prot.h>
#include <thread.h>
#include <ctype.h>
#include <stdlib.h>
#include <signal.h>

#ifndef	MT_UNSAFE_YP		/* Is the libnsl YP client code MT-unsafe? */
#define	MT_UNSAFE_YP	0	/* No, not any longer */
#endif

#if	MT_UNSAFE_YP
static mutex_t	one_lane = DEFAULTMUTEX;
#endif

/* <rpcsvc/ypclnt.h> uses (char *) where it should use (const char *) */
typedef char *grrr;

/*
 * The YP client code thinks it's being helpful by appending '\n' and '\0'
 *   to the values returned by yp_match() et al.  In order to do this it
 *   ends up doing more malloc()ing and data copying than would otherwise
 *   be necessary.  If we're interested in performance we should provide
 *   alternative library interfaces that skip the helpfulness and instead
 *   let the XDR routines dump the value directly into the buffer where
 *   we really want it.  For now, though, we just use the vanilla interface.
 */

static nss_status_t
switch_err(ypstatus, ismatch)
	int			ypstatus;
	int			ismatch;
{
	switch (ypstatus) {
	case 0:
		errno = 0;
		return (NSS_SUCCESS);

	case YPERR_BADARGS:
	case YPERR_KEY:
		errno = 0;
		return (NSS_NOTFOUND);

		/*
		 *  When the YP server is running in DNS forwarding mode,
		 *  the forwarder will return YPERR_NOMORE to us if it
		 *  is unable to contact a server (i.e., it has timed out).
		 *  The NSS_NISSERVDNS_TRYAGAIN is returned for timeout errors.
		 */
	case YPERR_NOMORE:
		if (ismatch)
			return (NSS_NISSERVDNS_TRYAGAIN);
		else
			return (NSS_NOTFOUND);

	case YPERR_DOMAIN:
	case YPERR_YPSERV:
	case YPERR_BUSY:
		return (NSS_TRYAGAIN);

	default:
		return (NSS_UNAVAIL);
	}
}

/*ARGSUSED*/
nss_status_t
_nss_nis_setent(be, dummy)
	nis_backend_ptr_t	be;
	void			*dummy;
{
	if (be->enum_key != 0) {
		free(be->enum_key);
		be->enum_key = 0;
	}
	be->enum_keylen = 0;
	return (NSS_SUCCESS);
}

nss_status_t
_nss_nis_endent(be, dummy)
	nis_backend_ptr_t	be;
	void			*dummy;
{
	return (_nss_nis_setent(be, dummy));
	/* Nothing else we can clean up, is there? */
}

void
massage_netdb(const char **valp, int *vallenp)
{
	const char		*first;
	const char		*last;
	const char		*val	= *valp;
	int			vallen	= *vallenp;

	if ((last = memchr(val, '#', vallen)) == 0) {
		last = val + vallen;
	}
	for (first = val;  first < last && isspace(*first);  first++) {
		;
	}
	for (/* cstyle */;  first < last && isspace(last[-1]);  last--) {
		;
	}
	/*
	 * Don't check for an empty line because it shouldn't ever
	 *   have made it into the YP map.
	 */
	*valp = first;
	*vallenp = (int)(last - first);
}

nss_status_t
_nss_nis_ypmatch(domain, map, key, valp, vallenp, ypstatusp)
	const char		*domain;
	const char		*map;
	const char		*key;
	char			**valp;
	int			*vallenp;
	int			*ypstatusp;
{
	int			ypstatus;

#if	MT_UNSAFE_YP
	sigset_t		oldmask, newmask;

	(void) sigfillset(&newmask);
	(void) thr_sigsetmask(SIG_SETMASK, &newmask, &oldmask);
	(void) mutex_lock(&one_lane);
#endif
	ypstatus = __yp_match_cflookup((grrr)domain, (grrr)map,
			    (grrr)key, (int)strlen(key), valp, vallenp, 0);
#if	MT_UNSAFE_YP
	(void) mutex_unlock(&one_lane);
	(void) thr_sigsetmask(SIG_SETMASK, &oldmask, NULL);
#endif

	if (ypstatusp != 0) {
		*ypstatusp = ypstatus;
	}
	return (switch_err(ypstatus, 1));
}

/*
 * XXX special version of _nss_nis_ypmatch() for handling C2 (passwd.adjunct)
 * lookups when we need a reserved port.
 */

static nss_status_t
_nss_nis_ypmatch_rsvdport(domain, map, key, valp, vallenp, ypstatusp)
	const char		*domain;
	const char		*map;
	const char		*key;
	char			**valp;
	int			*vallenp;
	int			*ypstatusp;
{
	int			ypstatus;

#if	MT_UNSAFE_YP
	sigset_t		oldmask, newmask;

	(void) sigfillset(&newmask);
	(void) thr_sigsetmask(SIG_SETMASK, &newmask, &oldmask);
	(void) mutex_lock(&one_lane);
#endif
	ypstatus = __yp_match_rsvdport_cflookup((grrr)domain, (grrr)map,
			    (grrr)key, strlen(key), valp, vallenp, 0);
#if	MT_UNSAFE_YP
	(void) mutex_unlock(&one_lane);
	(void) thr_sigsetmask(SIG_SETMASK, &oldmask, NULL);
#endif

	if (ypstatusp != 0) {
		*ypstatusp = ypstatus;
	}
	return (switch_err(ypstatus, 1));
}

nss_status_t
_nss_nis_lookup(be, args, netdb, map, key, ypstatusp)
	nis_backend_ptr_t	be;
	nss_XbyY_args_t		*args;
	int			netdb;
	const char		*map;
	const char		*key;
	int			*ypstatusp;
{
	nss_status_t		res;
	int			vallen;
	char			*val;
	char			*free_ptr;
	int			parsestat;

	if ((res = _nss_nis_ypmatch(be->domain, map, key, &val, &vallen,
				    ypstatusp)) != NSS_SUCCESS) {
		return (res);
	}

	parsestat = NSS_STR_PARSE_SUCCESS;
	if (strcmp(map, "passwd.byname") == 0 ||
	    strcmp(map, "passwd.byuid") == 0) {
		parsestat = validate_passwd_ids(&val, &vallen, 1);
	} else if (strcmp(map, "group.byname") == 0)
		parsestat = validate_group_ids(&val, &vallen, 1);
	if (parsestat != NSS_STR_PARSE_SUCCESS) {
		free(val);
		return (NSS_NOTFOUND);
	}

	free_ptr = val;

	if (netdb) {
		massage_netdb((const char **)&val, &vallen);
	}

	args->returnval = NULL;
	args->returnlen = 0;
	parsestat = (*args->str2ent)(val, vallen,
			args->buf.result, args->buf.buffer, args->buf.buflen);
	if (parsestat == NSS_STR_PARSE_SUCCESS) {
		args->returnval = args->buf.result;
		args->returnlen = vallen;
		res = NSS_SUCCESS;
	} else if (parsestat == NSS_STR_PARSE_ERANGE) {
		args->erange = 1;
		/* We won't find this otherwise, anyway */
		res = NSS_NOTFOUND;
	} /* else if (parsestat == NSS_STR_PARSE_PARSE) won't happen ! */

	free(free_ptr);

	return (res);
}

nss_status_t
_nss_nis_lookup_rsvdport(be, args, netdb, map, key, ypstatusp)
	nis_backend_ptr_t	be;
	nss_XbyY_args_t		*args;
	int			netdb;
	const char		*map;
	const char		*key;
	int			*ypstatusp;
{
	nss_status_t		res;
	int			vallen;
	char			*val;
	char			*free_ptr;
	int			parsestat;

	if ((res = _nss_nis_ypmatch_rsvdport(be->domain, map, key, &val,
				    &vallen, ypstatusp)) != NSS_SUCCESS) {
		return (res);
	}

	free_ptr = val;

	if (netdb) {
		massage_netdb((const char **)&val, &vallen);
	}

	args->returnval = NULL;
	args->returnlen = 0;
	parsestat = (*args->str2ent)(val, vallen,
			args->buf.result, args->buf.buffer, args->buf.buflen);
	if (parsestat == NSS_STR_PARSE_SUCCESS) {
		args->returnval = args->buf.result;
		args->returnlen = vallen;
		res = NSS_SUCCESS;
	} else if (parsestat == NSS_STR_PARSE_ERANGE) {
		args->erange = 1;
		/* We won't find this otherwise, anyway */
		res = NSS_NOTFOUND;
	} /* else if (parsestat == NSS_STR_PARSE_PARSE) won't happen ! */

	free(free_ptr);

	return (res);
}

static nss_status_t
do_getent(be, args, netdb)
	nis_backend_ptr_t	be;
	nss_XbyY_args_t		*args;
	int			netdb;
{
	nss_status_t		res;
	int			ypstatus;
	int			outkeylen, outvallen;
	char			*outkey, *outval;
	char			*free_ptr;
	int			parsestat;

#if	MT_UNSAFE_YP
	sigset_t		oldmask, newmask;

	(void) sigfillset(&newmask);
	(void) thr_sigsetmask(SIG_SETMASK, &newmask, &oldmask);
	(void) mutex_lock(&one_lane);
#endif
	if (be->enum_key == 0) {
		ypstatus = __yp_first_cflookup((grrr)be->domain,
					    (grrr)be->enum_map, &outkey,
					    &outkeylen, &outval,
					    &outvallen, 0);
	} else {
		ypstatus = __yp_next_cflookup((grrr)be->domain,
					    (grrr)be->enum_map, be->enum_key,
					    be->enum_keylen, &outkey,
					    &outkeylen, &outval,
					    &outvallen, 0);
	}
#if	MT_UNSAFE_YP
	(void) mutex_unlock(&one_lane);
	(void) thr_sigsetmask(SIG_SETMASK, &oldmask, NULL);
#endif

	if ((res = switch_err(ypstatus, 0)) != NSS_SUCCESS) {
		return (res);
	}

	free_ptr = outval;

	if (netdb) {
		massage_netdb((const char **)&outval, &outvallen);
	}

	args->returnval = NULL;
	args->returnlen = 0;
	parsestat = (*args->str2ent)(outval, outvallen,
			args->buf.result, args->buf.buffer, args->buf.buflen);
	if (parsestat == NSS_STR_PARSE_SUCCESS) {
		args->returnval = args->buf.result;
		args->returnlen = outvallen;
		res = NSS_SUCCESS;
	} else if (parsestat == NSS_STR_PARSE_ERANGE) {
		args->erange = 1;
		/* We won't find this otherwise, anyway */
		res = NSS_NOTFOUND;
	} /* else if (parsestat == NSS_STR_PARSE_PARSE) won't happen ! */

	free(free_ptr);

	if (be->enum_key != 0) {
		free(be->enum_key);
	}
	be->enum_key = outkey;
	be->enum_keylen = outkeylen;

	return (res);
}

nss_status_t
_nss_nis_getent_rigid(be, args)
	nis_backend_ptr_t	be;
	void			*args;
{
	return (do_getent(be, (nss_XbyY_args_t *)args, 0));
}

nss_status_t
_nss_nis_getent_netdb(be, args)
	nis_backend_ptr_t	be;
	void			*args;
{
	return (do_getent(be, (nss_XbyY_args_t *)args, 1));
}


struct cb_data {
	void			*args;
	const char		*filter;
	nis_do_all_func_t	func;
	nss_status_t		result;
};

enum { ITER_NEXT = 0, ITER_STOP = 1 };	/* Should be in <rpcsvc/ypclnt.h> */

/*ARGSUSED*/
static int
do_cback(instatus, inkey, inkeylen, inval, invallen, indata)
	int			instatus;
	const char		*inkey;
	int			inkeylen;
	const char		*inval;
	int			invallen;
	struct cb_data		*indata;
{
	nss_status_t		res;

	if (instatus != YP_TRUE) {
		return (ITER_NEXT);	/* yp_all may decide otherwise... */
	}

	if (indata->filter != 0 && strstr(inval, indata->filter) == 0) {
		/*
		 * Optimization:  if the entry doesn't contain the filter
		 *   string then it can't be the entry we want, so don't
		 *   bother looking more closely at it.
		 */
		return (ITER_NEXT);
	}

	res = (*indata->func)(inval, invallen, indata->args);

	if (res == NSS_NOTFOUND) {
		return (ITER_NEXT);
	} else {
		indata->result = res;
		return (ITER_STOP);
	}
}

nss_status_t
_nss_nis_do_all(be, args, filter, func)
	nis_backend_ptr_t	be;
	void			*args;
	const char		*filter;
	nis_do_all_func_t	func;
{
	int			ypall_status;
	struct cb_data		data;
	struct ypall_callback	cback;

	data.args	= args;
	data.filter	= filter;
	data.func	= func;
	data.result	= NSS_NOTFOUND;

	cback.foreach	= do_cback;
	cback.data	= (char *)&data;

#if	MT_UNSAFE_YP
	sigset_t		oldmask, newmask;

	(void) sigfillset(&newmask);
	(void) thr_sigsetmask(SIG_SETMASK, &newmask, &oldmask);
	(void) mutex_lock(&one_lane);
#endif
	ypall_status = __yp_all_cflookup((grrr)be->domain,
			(grrr) be->enum_map, &cback, 0);
#if	MT_UNSAFE_YP
	(void) mutex_unlock(&one_lane);
	(void) thr_sigsetmask(SIG_SETMASK, &oldmask, NULL);
#endif

	switch (ypall_status) {
	    case 0:
		return (data.result);
	    case YPERR_DOMAIN:
	    case YPERR_YPSERV:
	    case YPERR_BUSY:		/* Probably never get this, but... */
		return (NSS_TRYAGAIN);
	    default:
		return (NSS_UNAVAIL);
	}
}

struct XbyY_data {
	nss_XbyY_args_t		*args;
	nis_XY_check_func	func;
	int			netdb;
};

static nss_status_t
XbyY_iterator(instr, instr_len, a)
	const char		*instr;
	int			instr_len;
	void			*a;
{
	struct XbyY_data	*xydata	= (struct XbyY_data *)a;
	nss_XbyY_args_t		*args	= xydata->args;
	nss_status_t		res;
	int			parsestat;

	if (xydata->netdb) {
		massage_netdb(&instr, &instr_len);
	}

	args->returnval = NULL;
	args->returnlen = 0;
	parsestat = (*args->str2ent)(instr, instr_len,
			args->buf.result, args->buf.buffer, args->buf.buflen);
	if (parsestat == NSS_STR_PARSE_SUCCESS) {
		args->returnval = args->buf.result;
		if ((*xydata->func)(args)) {
			res = NSS_SUCCESS;
			args->returnlen = instr_len;
		} else {
			res = NSS_NOTFOUND;
			args->returnval = 0;
		}
	} else if (parsestat == NSS_STR_PARSE_ERANGE) {
		/*
		 * If we got here because (*str2ent)() found that the buffer
		 * wasn't big enough, maybe we should quit and return erange.
		 * Instead we'll keep looking and eventually return "not
		 * found" -- it's a bug, but not an earth-shattering one.
		 */
		args->erange = 1;	/* <== Is this a good idea? */
		res = NSS_NOTFOUND;
	} /* else if (parsestat == NSS_STR_PARSE_PARSE) won't happen ! */

	return (res);
}

nss_status_t
_nss_nis_XY_all(be, args, netdb, filter, func)
	nis_backend_ptr_t	be;
	nss_XbyY_args_t		*args;
	int			netdb;
	const char		*filter;
	nis_XY_check_func	func;
{
	struct XbyY_data	data;

	data.args = args;
	data.func = func;
	data.netdb = netdb;

	return (_nss_nis_do_all(be, &data, filter, XbyY_iterator));
	/* Now how many levels of callbacks was that? */
}


/*ARGSUSED*/
nss_status_t
_nss_nis_destr(be, dummy)
	nis_backend_ptr_t	be;
	void			*dummy;
{
	if (be != 0) {
		/* === Should change to invoke ops[ENDENT] ? */
		(void) _nss_nis_endent(be, 0);
		free(be);
	}
	return (NSS_SUCCESS);	/* In case anyone is dumb enough to check */
}

/* We want to lock this even if the YP routines are MT-safe */
static mutex_t	yp_domain_lock = DEFAULTMUTEX;
static char	*yp_domain;

const char *
_nss_nis_domain()
{
	char			*domain;

	/*
	 * This much locking is probably more "by the book" than necessary...
	 */
	sigset_t		oldmask, newmask;

	(void) sigfillset(&newmask);
	(void) thr_sigsetmask(SIG_SETMASK, &newmask, &oldmask);
	(void) mutex_lock(&yp_domain_lock);

	if ((domain = yp_domain) == 0) {
#if	MT_UNSAFE_YP
		(void) mutex_lock(&one_lane);
#endif
		if (yp_get_default_domain(&yp_domain) == 0) {
			domain = yp_domain;
		}
#if	MT_UNSAFE_YP
		(void) mutex_unlock(&one_lane);
#endif
	}

	(void) mutex_unlock(&yp_domain_lock);
	(void) thr_sigsetmask(SIG_SETMASK, &oldmask, NULL);

	return (domain);
}

nss_backend_t *
_nss_nis_constr(ops, n_ops, enum_map)
	nis_backend_op_t	ops[];
	int			n_ops;
	const char		*enum_map;
{
	const char		*domain;
	nis_backend_ptr_t	be;

	if ((domain = _nss_nis_domain()) == 0 ||
	    (be = (nis_backend_ptr_t)malloc(sizeof (*be))) == 0) {
		return (0);
	}
	be->ops		= ops;
	be->n_ops	= n_ops;
	be->domain	= domain;
	be->enum_map	= enum_map;   /* Don't strdup, assume valid forever */
	be->enum_key	= 0;
	be->enum_keylen	= 0;

	return ((nss_backend_t *)be);
}

/*
 * This routine is used to parse lines of the form:
 * 	name number aliases
 * It returns 1 if the key in argp matches any one of the
 * names in the line, otherwise 0
 * Used by rpc
 */
int
_nss_nis_check_name_aliases(nss_XbyY_args_t *argp, const char *line,
	int linelen)
{
	const char	*limit, *linep, *keyp;

	linep = line;
	limit = line + linelen;
	keyp = argp->key.name;

	/* compare name */
	while (*keyp && linep < limit && !isspace(*linep) && *keyp == *linep) {
		keyp++;
		linep++;
	}
	if (*keyp == '\0' && linep < limit && isspace(*linep))
		return (1);
	/* skip remainder of the name, if any */
	while (linep < limit && !isspace(*linep))
		linep++;
	/* skip the delimiting spaces */
	while (linep < limit && isspace(*linep))
		linep++;
	/* compare with the aliases */
	while (linep < limit) {
		/*
		 * 1st pass: skip number
		 * Other passes: skip remainder of the alias name, if any
		 */
		while (linep < limit && !isspace(*linep))
			linep++;
		/* skip the delimiting spaces */
		while (linep < limit && isspace(*linep))
			linep++;
		/* compare with the alias name */
		keyp = argp->key.name;
		while (*keyp && linep < limit && !isspace(*linep) &&
		    *keyp == *linep) {
			keyp++;
			linep++;
		}
		if (*keyp == '\0' && (linep == limit || isspace(*linep)))
			return (1);
	}
	return (0);
}