/*
 * 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 2006 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

/*
 * nis/getgrent.c -- "nis" backend for nsswitch "group" database
 */

#pragma ident	"%Z%%M%	%I%	%E% SMI"

#include <grp.h>
#include <pwd.h>
#include "nis_common.h"
#include <ctype.h>
#include <stdlib.h>
#include <string.h>
#include <rpc/auth.h>	/* for MAXNETNAMELEN */

static nss_status_t netid_lookup(struct nss_groupsbymem *argp);

static nss_status_t
getbyname(be, a)
	nis_backend_ptr_t	be;
	void			*a;
{
	nss_XbyY_args_t		*argp = (nss_XbyY_args_t *)a;

	return (_nss_nis_lookup(be, argp, 0,
				"group.byname", argp->key.name, 0));
}

static nss_status_t
getbygid(be, a)
	nis_backend_ptr_t	be;
	void			*a;
{
	nss_XbyY_args_t		*argp = (nss_XbyY_args_t *)a;
	char			gidstr[12];	/* More than enough */

	(void) snprintf(gidstr, 12, "%d", argp->key.gid);
	return (_nss_nis_lookup(be, argp, 0, "group.bygid", gidstr, 0));
}

static nss_status_t
getbymember(be, a)
	nis_backend_ptr_t	be;
	void			*a;
{
	struct nss_groupsbymem	*argp = (struct nss_groupsbymem *)a;

	if (strcmp(argp->username, "root") == 0) {
		/*
		 * Assume that "root" can only sensibly be in /etc/group,
		 *   not in NIS or NIS+
		 * If we don't do this, a hung name-service may cause
		 *   a root login or su to hang.
		 */
		return (NSS_NOTFOUND);
	}

	if (argp->force_slow_way != 1) {
		switch (netid_lookup(argp)) {
		case NSS_SUCCESS:
			/*
			 * Return SUCESS only if array is full. Explained
			 * in <nss_dbdefs.h>.
			 */
			return ((argp->numgids == argp->maxgids)
			    ? NSS_SUCCESS
			    : NSS_NOTFOUND);
		case NSS_NOTFOUND:
		case NSS_UNAVAIL:
			/*
			 * Failover to group map search if no luck with netid.
			 */
			break;
		case NSS_TRYAGAIN:
			return (NSS_TRYAGAIN);
		}
	}

	return (_nss_nis_do_all(be, argp, argp->username,
				(nis_do_all_func_t)argp->process_cstr));
}

static nis_backend_op_t group_ops[] = {
	_nss_nis_destr,
	_nss_nis_endent,
	_nss_nis_setent,
	_nss_nis_getent_rigid,
	getbyname,
	getbygid,
	getbymember
};

/*ARGSUSED*/
nss_backend_t *
_nss_nis_group_constr(dummy1, dummy2, dummy3)
	const char	*dummy1, *dummy2, *dummy3;
{
	return (_nss_nis_constr(group_ops,
				sizeof (group_ops) / sizeof (group_ops[0]),
				"group.byname"));
}

/*
 * Add gid to gid_array if it's not already there. gid_array must have room
 * for one more entry.  Return new size of array.
 */
static int
add_gid(gid_t gid_array[], int numgids, gid_t gid)
{
	int i = 0;

	for (i = 0; i < numgids; i++) {
		if (gid_array[i] == gid) {
			return (numgids);
		}
	}
	gid_array[numgids++] = gid;
	return (numgids);
}

/*
 * Given buf, a null-terminated string containing the result of a successful
 * netid lookup, add the gids to the gid_array.  The string may contain extra
 * whitesapce.  On parse error, the valid portion of the gid_array is not
 * modified.
 */
static int
parse_netid(const char *buf, gid_t gid_array[], int maxgids, int *numgids_ptr)
{
	int	numgids = *numgids_ptr;
	char	*buf_next;
	gid_t	gid;
	long	value;

	/* Scan past "<uid>:" */
	while (isspace(*buf) || isdigit(*buf)) {
		buf++;
	}

	if (*buf++ != ':') {
		return (NSS_STR_PARSE_PARSE);
	}

	/* buf should now point to a comma-separated list of gids */
	while (*buf != '\0' && *buf != '\n') {
		errno = 0;
		value = strtol(buf, &buf_next, 10);

		if (buf == buf_next) {
			return (NSS_STR_PARSE_PARSE);
		} else if ((value == LONG_MAX && errno == ERANGE) ||
		    (ulong_t)value > INT_MAX) {
			return (NSS_STR_PARSE_ERANGE);
		}

		gid = (gid_t)value;
		if (numgids < maxgids) {
			numgids = add_gid(gid_array, numgids, gid);
		}
		buf = buf_next;
		if (*buf == ',') {
			buf++;
		}
	}
	*numgids_ptr = numgids;
	return (NSS_STR_PARSE_SUCCESS);
}


/*
 * Perform a lookup in the netid map.  Fill in the gid_array if successful.
 * Return values are like those for _nss_nis_lookup().
 */
static nss_status_t
netid_lookup(struct nss_groupsbymem *argp)
{
	const char	*domain = _nss_nis_domain();
	struct passwd	pw;
	char		pwbuf[NSS_BUFLEN_PASSWD];
	char		netname[MAXNETNAMELEN + 1];
	nss_status_t	res;
	char		*val;
	int		vallen;
	int		parse_res;
	char		*lasts;

	/*
	 * Need to build up the netname for the user manually. Can't use
	 * user2netname() rpc library call, since that does all sorts of
	 * extra stuff based upon its own private name-service switch.
	 *
	 * Note that "root" has no user netname so return in error.
	 */
	if ((getpwnam_r(argp->username, &pw, pwbuf, sizeof (pwbuf)) == NULL) ||
	    (pw.pw_uid == 0)) {
		return (NSS_UNAVAIL);
	}
	if (snprintf(netname, MAXNETNAMELEN + 1, "unix.%d@%s",
	    pw.pw_uid, domain) < 0) {
		return (NSS_UNAVAIL);
	}

	if ((res = _nss_nis_ypmatch(domain, "netid.byname", netname,
			&val, &vallen, 0)) != NSS_SUCCESS) {
		return (res);
	}

	(void) strtok_r(val, "#", &lasts);

	parse_res = parse_netid(val, argp->gid_array, argp->maxgids,
			&argp->numgids);
	free(val);
	return ((parse_res == NSS_STR_PARSE_SUCCESS)
		? NSS_SUCCESS
		: NSS_NOTFOUND);
}