usr/src/lib/libc/port/gen/priv_str_xlate.c
author Jon Tibble <meths@btinternet.com>
Thu, 09 Dec 2010 22:32:39 +0100
changeset 13255 4afa820d78b9
parent 6812 febeba71273d
permissions -rw-r--r--
298 SPARC build fails in smt_pause.o 478 Build needs fixing for pkgdepend flag day Reviewed by: [email protected] Reviewed by: [email protected] Reviewed by: [email protected] Approved by: [email protected]

/*
 * 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.
 */

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

/*
 *	priv_str_xlate.c - Privilege translation routines.
 */

#pragma weak _priv_str_to_set = priv_str_to_set
#pragma weak _priv_set_to_str = priv_set_to_str
#pragma weak _priv_gettext = priv_gettext

#include "lint.h"
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <strings.h>
#include <errno.h>
#include <string.h>
#include <locale.h>
#include <sys/param.h>
#include <priv.h>
#include <alloca.h>
#include <locale.h>
#include "libc.h"
#include "../i18n/_loc_path.h"
#include "priv_private.h"

priv_set_t *
priv_basic(void)
{
	priv_data_t *d;

	LOADPRIVDATA(d);

	return (d->pd_basicset);
}

/*
 *	Name:	priv_str_to_set()
 *
 *	Description:	Given a buffer with privilege strings, the
 *	equivalent privilege set is returned.
 *
 *	Special tokens recognized: all, none, basic and "".
 *
 *	On failure, this function returns NULL.
 *	*endptr == NULL and errno set: resource error.
 *	*endptr != NULL: parse error.
 */
priv_set_t *
priv_str_to_set(const char *priv_names,
		const char *separators,
		const char **endptr)
{

	char *base;
	char *offset;
	char *last;
	priv_set_t *pset = NULL;
	priv_set_t *zone;
	priv_set_t *basic;

	if (endptr != NULL)
		*endptr = NULL;

	if ((base = libc_strdup(priv_names)) == NULL ||
	    (pset = priv_allocset()) == NULL) {
		/* Whether base is NULL or allocated, this works */
		libc_free(base);
		return (NULL);
	}

	priv_emptyset(pset);
	basic = priv_basic();
	zone = privdata->pd_zoneset;

	/* This is how to use strtok_r nicely in a while loop ... */
	last = base;

	while ((offset = strtok_r(NULL, separators, &last)) != NULL) {
		/*
		 * Search for these special case strings.
		 */
		if (basic != NULL && strcasecmp(offset, "basic") == 0) {
			priv_union(basic, pset);
		} else if (strcasecmp(offset, "none") == 0) {
			priv_emptyset(pset);
		} else if (strcasecmp(offset, "all") == 0) {
			priv_fillset(pset);
		} else if (strcasecmp(offset, "zone") == 0) {
			priv_union(zone, pset);
		} else {
			boolean_t neg = (*offset == '-' || *offset == '!');
			int privid;
			int slen;

			privid = priv_getbyname(offset +
			    ((neg || *offset == '+') ? 1 : 0));
			if (privid < 0) {
				slen = offset - base;
				libc_free(base);
				priv_freeset(pset);
				if (endptr != NULL)
					*endptr = priv_names + slen;
				errno = EINVAL;
				return (NULL);
			} else {
				if (neg)
					PRIV_DELSET(pset, privid);
				else
					PRIV_ADDSET(pset, privid);
			}
		}
	}

	libc_free(base);
	return (pset);
}

/*
 *	Name:	priv_set_to_str()
 *
 *	Description:	Given a set of privileges, list of privileges are
 *	returned in privilege numeric order (which can be an ASCII sorted
 *	list as our implementation allows renumbering.
 *
 *	String "none" identifies an empty privilege set, and string "all"
 *	identifies a full set.
 *
 *	A pointer to a buffer is returned which needs to be freed by
 *	the caller.
 *
 *	Several types of output are supported:
 *		PRIV_STR_PORT		- portable output: basic,!basic
 *		PRIV_STR_LIT		- literal output
 *		PRIV_STR_SHORT		- shortest output
 *
 * NOTE: this function is called both from inside the library for the
 * current environment and from outside the library using an externally
 * generated priv_data_t * in order to analyze core files.  It should
 * return strings which can be free()ed by applications and it should
 * not use any data from the current environment except in the special
 * case that it is called from within libc, with a NULL priv_data_t *
 * argument.
 */

char *
__priv_set_to_str(
	priv_data_t *d,
	const priv_set_t *pset,
	char separator,
	int flag)
{
	const char *pstr;
	char *res, *resp;
	int i;
	char neg = separator == '!' ? '-' : '!';
	priv_set_t *zone;
	boolean_t all;
	boolean_t use_libc_data = (d == NULL);

	if (use_libc_data)
		LOADPRIVDATA(d);

	if (flag != PRIV_STR_PORT && __priv_isemptyset(d, pset))
		return (strdup("none"));
	if (flag != PRIV_STR_LIT && __priv_isfullset(d, pset))
		return (strdup("all"));

	/* Safe upper bound: global info contains all NULL separated privs */
	res = resp = alloca(d->pd_pinfo->priv_globalinfosize);

	/*
	 * Compute the shortest form; i.e., the form with the fewest privilege
	 * tokens.
	 * The following forms are possible:
	 *	literal: priv1,priv2,priv3
	 *		tokcount = present
	 *	port: basic,!missing_basic,other
	 *		tokcount = 1 + present - presentbasic + missingbasic
	 *	zone: zone,!missing_zone
	 *		tokcount = 1 + missingzone
	 *	all: all,!missing1,!missing2
	 *		tokcount = 1 + d->pd_nprivs - present;
	 *
	 * Note that zone and all forms are identical in the global zone;
	 * in that case (or any other where the token count is the same),
	 * all is preferred.  Also, the zone form is only used when the
	 * indicated privileges are a subset of the zone set.
	 */

	if (use_libc_data)
		LOCKPRIVDATA();

	if (flag == PRIV_STR_SHORT) {
		int presentbasic, missingbasic, present, missing;
		int presentzone, missingzone;
		int count;

		presentbasic = missingbasic = present = 0;
		presentzone = missingzone = 0;
		zone = d->pd_zoneset;

		for (i = 0; i < d->pd_nprivs; i++) {
			int mem = PRIV_ISMEMBER(pset, i);
			if (d->pd_basicset != NULL &&
			    PRIV_ISMEMBER(d->pd_basicset, i)) {
				if (mem)
					presentbasic++;
				else
					missingbasic++;
			}
			if (zone != NULL && PRIV_ISMEMBER(zone, i)) {
				if (mem)
					presentzone++;
				else
					missingzone++;
			}
			if (mem)
				present++;
		}
		missing = d->pd_nprivs - present;

		if (1 - presentbasic + missingbasic < 0) {
			flag = PRIV_STR_PORT;
			count = present + 1 - presentbasic + missingbasic;
		} else {
			flag = PRIV_STR_LIT;
			count = present;
		}
		if (count >= 1 + missing) {
			flag = PRIV_STR_SHORT;
			count = 1 + missing;
			all = B_TRUE;
		}
		if (present == presentzone && 1 + missingzone < count) {
			flag = PRIV_STR_SHORT;
			all = B_FALSE;
		}
	}

	switch (flag) {
	case PRIV_STR_LIT:
		*res = '\0';
		break;
	case PRIV_STR_PORT:
		(void) strcpy(res, "basic");
		if (d->pd_basicset == NULL)
			flag = PRIV_STR_LIT;
		break;
	case PRIV_STR_SHORT:
		if (all)
			(void) strcpy(res, "all");
		else
			(void) strcpy(res, "zone");
		break;
	default:
		if (use_libc_data)
			UNLOCKPRIVDATA();
		return (NULL);
	}
	res += strlen(res);

	for (i = 0; i < d->pd_nprivs; i++) {
		/* Map the privilege to the next one sorted by name */
		int priv = d->pd_setsort[i];

		if (PRIV_ISMEMBER(pset, priv)) {
			switch (flag) {
			case PRIV_STR_SHORT:
				if (all || PRIV_ISMEMBER(zone, priv))
					continue;
				break;
			case PRIV_STR_PORT:
				if (PRIV_ISMEMBER(d->pd_basicset, priv))
					continue;
				break;
			case PRIV_STR_LIT:
				break;
			}
			if (res != resp)
				*res++ = separator;
		} else {
			switch (flag) {
			case PRIV_STR_LIT:
				continue;
			case PRIV_STR_PORT:
				if (!PRIV_ISMEMBER(d->pd_basicset, priv))
					continue;
				break;
			case PRIV_STR_SHORT:
				if (!all && !PRIV_ISMEMBER(zone, priv))
					continue;
				break;
			}
			if (res != resp)
				*res++ = separator;
			*res++ = neg;
		}
		pstr = __priv_getbynum(d, priv);
		(void) strcpy(res, pstr);
		res += strlen(pstr);
	}
	if (use_libc_data)
		UNLOCKPRIVDATA();
	/* Special case the set with some high bits set */
	return (strdup(*resp == '\0' ? "none" : resp));
}

/*
 * priv_set_to_str() is defined to return a string that
 * the caller must deallocate with free(3C).  Grr...
 */
char *
priv_set_to_str(const priv_set_t *pset, char separator, int flag)
{
	return (__priv_set_to_str(NULL, pset, separator, flag));
}

static char *
do_priv_gettext(const char *priv, const char *file)
{
	char buf[8*1024];
	boolean_t inentry = B_FALSE;
	FILE	*namefp;

	namefp = fopen(file, "rF");
	if (namefp == NULL)
		return (NULL);

	/*
	 * parse the file; it must have the following format
	 * Lines starting with comments "#"
	 * Lines starting with non white space with one single token:
	 * the privileges; white space indented lines which are the
	 * description; no empty lines are allowed in the description.
	 */
	while (fgets(buf, sizeof (buf), namefp) != NULL) {
		char *lp;		/* pointer to the current line */

		if (buf[0] == '#')
			continue;

		if (buf[0] == '\n') {
			inentry = B_FALSE;
			continue;
		}

		if (inentry)
			continue;

		/* error; not skipping; yet line starts with white space */
		if (isspace((unsigned char)buf[0]))
			goto out;

		/* Trim trailing newline */
		buf[strlen(buf) - 1] = '\0';

		if (strcasecmp(buf, priv) != 0) {
			inentry = B_TRUE;
			continue;
		}

		lp = buf;
		while (fgets(lp, sizeof (buf) - (lp - buf), namefp) != NULL) {
			char *tstart;	/* start of text */
			int len;

			/* Empty line or start of next entry terminates */
			if (*lp == '\n' || !isspace((unsigned char)*lp)) {
				*lp = '\0';
				(void) fclose(namefp);
				return (strdup(buf));
			}

			/* Remove leading white space */
			tstart = lp;
			while (*tstart != '\0' &&
			    isspace((unsigned char)*tstart)) {
				tstart++;
			}

			len = strlen(tstart);
			(void) memmove(lp, tstart, len + 1);
			lp += len;

			/* Entry to big; prevent fgets() loop */
			if (lp == &buf[sizeof (buf) - 1])
				goto out;
		}
		if (lp != buf) {
			*lp = '\0';
			(void) fclose(namefp);
			return (strdup(buf));
		}
	}
out:
	(void) fclose(namefp);
	return (NULL);
}

/*
 * priv_gettext() is defined to return a string that
 * the caller must deallocate with free(3C).  Grr...
 */
char *
priv_gettext(const char *priv)
{
	char file[MAXPATHLEN];
	const char *loc;
	char	*ret;

	/* Not a valid privilege */
	if (priv_getbyname(priv) < 0)
		return (NULL);

	if ((loc = setlocale(LC_MESSAGES, NULL)) == NULL)
		loc = "C";

	if (snprintf(file, sizeof (file),
	    _DFLT_LOC_PATH "%s/LC_MESSAGES/priv_names", loc) < sizeof (file)) {
		ret = do_priv_gettext(priv, (const char *)file);
		if (ret != NULL)
			return (ret);
	}

	/* If the path is too long or can't be opened, punt to default */
	ret = do_priv_gettext(priv, "/etc/security/priv_names");
	return (ret);
}