priv_str_xlate.c revision 2550:8d932f4716ae
1/*
2 * CDDL HEADER START
3 *
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License (the "License").
6 * You may not use this file except in compliance with the License.
7 *
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
12 *
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
18 *
19 * CDDL HEADER END
20 */
21/*
22 * Copyright 2006 Sun Microsystems, Inc.  All rights reserved.
23 * Use is subject to license terms.
24 */
25
26#pragma ident	"%Z%%M%	%I%	%E% SMI"
27
28/*
29 *	priv_str_xlate.c - Privilege translation routines.
30 */
31
32#pragma weak priv_str_to_set = _priv_str_to_set
33#pragma weak priv_set_to_str = _priv_set_to_str
34#pragma weak priv_gettext = _priv_gettext
35
36#include "synonyms.h"
37#include <stdio.h>
38#include <stdlib.h>
39#include <ctype.h>
40#include <strings.h>
41#include <errno.h>
42#include <string.h>
43#include <locale.h>
44#include <sys/param.h>
45#include <priv.h>
46#include <alloca.h>
47#include <locale.h>
48#include "libc.h"
49#include "../i18n/_loc_path.h"
50#include "priv_private.h"
51
52priv_set_t *
53priv_basic(void)
54{
55	priv_data_t *d;
56
57	LOADPRIVDATA(d);
58
59	return (d->pd_basicset);
60}
61
62/*
63 *	Name:	priv_str_to_set()
64 *
65 *	Description:	Given a buffer with privilege strings, the
66 *	equivalent privilege set is returned.
67 *
68 *	Special tokens recognized: all, none, basic and "".
69 *
70 *	On failure, this function returns NULL.
71 *	*endptr == NULL and errno set: resource error.
72 *	*endptr != NULL: parse error.
73 */
74priv_set_t *
75priv_str_to_set(const char *priv_names,
76		const char *separators,
77		const char **endptr)
78{
79
80	char *base;
81	char *offset;
82	char *last;
83	priv_set_t *pset = NULL;
84	priv_set_t *zone;
85	priv_set_t *basic;
86
87	if (endptr != NULL)
88		*endptr = NULL;
89
90	if ((base = libc_strdup(priv_names)) == NULL ||
91	    (pset = priv_allocset()) == NULL) {
92		/* Whether base is NULL or allocated, this works */
93		libc_free(base);
94		return (NULL);
95	}
96
97	priv_emptyset(pset);
98	basic = priv_basic();
99	zone = privdata->pd_zoneset;
100
101	/* This is how to use strtok_r nicely in a while loop ... */
102	last = base;
103
104	while ((offset = strtok_r(NULL, separators, &last)) != NULL) {
105		/*
106		 * Search for these special case strings.
107		 */
108		if (basic != NULL && strcasecmp(offset, "basic") == 0) {
109			priv_union(basic, pset);
110		} else if (strcasecmp(offset, "none") == 0) {
111			priv_emptyset(pset);
112		} else if (strcasecmp(offset, "all") == 0) {
113			priv_fillset(pset);
114		} else if (strcasecmp(offset, "zone") == 0) {
115			priv_union(zone, pset);
116		} else {
117			boolean_t neg = (*offset == '-' || *offset == '!');
118			int privid;
119			int slen;
120
121			privid = priv_getbyname(offset +
122			    ((neg || *offset == '+') ? 1 : 0));
123			if (privid < 0) {
124				slen = offset - base;
125				libc_free(base);
126				priv_freeset(pset);
127				if (endptr != NULL)
128					*endptr = priv_names + slen;
129				errno = EINVAL;
130				return (NULL);
131			} else {
132				if (neg)
133					PRIV_DELSET(pset, privid);
134				else
135					PRIV_ADDSET(pset, privid);
136			}
137		}
138	}
139
140	libc_free(base);
141	return (pset);
142}
143
144/*
145 *	Name:	priv_set_to_str()
146 *
147 *	Description:	Given a set of privileges, list of privileges are
148 *	returned in privilege numeric order (which can be an ASCII sorted
149 *	list as our implementation allows renumbering.
150 *
151 *	String "none" identifies an empty privilege set, and string "all"
152 *	identifies a full set.
153 *
154 *	A pointer to a buffer is returned which needs to be freed by
155 *	the caller.
156 *
157 *	Several types of output are supported:
158 *		PRIV_STR_PORT		- portable output: basic,!basic
159 *		PRIV_STR_LIT		- literal output
160 *		PRIV_STR_SHORT		- shortest output
161 *
162 * NOTE: this function is called both from inside the library for the
163 * current environment and from outside the library using an externally
164 * generated priv_data_t * in order to analyze core files.  It should
165 * return strings which can be free()ed by applications and it should
166 * not use any data from the current environment except in the special
167 * case that it is called from within libc, with a NULL priv_data_t *
168 * argument.
169 */
170
171char *
172__priv_set_to_str(
173	priv_data_t *d,
174	const priv_set_t *pset,
175	char separator,
176	int flag)
177{
178	const char *pstr;
179	char *res, *resp;
180	int i;
181	char neg = separator == '!' ? '-' : '!';
182	priv_set_t *zone;
183	boolean_t all;
184	boolean_t use_libc_data = (d == NULL);
185
186	if (use_libc_data)
187		LOADPRIVDATA(d);
188
189	if (flag != PRIV_STR_PORT && __priv_isemptyset(d, pset))
190		return (strdup("none"));
191	if (flag != PRIV_STR_LIT && __priv_isfullset(d, pset))
192		return (strdup("all"));
193
194	/* Safe upper bound: global info contains all NULL separated privs */
195	res = resp = alloca(d->pd_pinfo->priv_globalinfosize);
196
197	/*
198	 * Compute the shortest form; i.e., the form with the fewest privilege
199	 * tokens.
200	 * The following forms are possible:
201	 *	literal: priv1,priv2,priv3
202	 *		tokcount = present
203	 *	port: basic,!missing_basic,other
204	 *		tokcount = 1 + present - presentbasic + missingbasic
205	 *	zone: zone,!missing_zone
206	 *		tokcount = 1 + missingzone
207	 *	all: all,!missing1,!missing2
208	 *		tokcount = 1 + d->pd_nprivs - present;
209	 *
210	 * Note that zone and all forms are identical in the global zone;
211	 * in that case (or any other where the token count is the same),
212	 * all is preferred.  Also, the zone form is only used when the
213	 * indicated privileges are a subset of the zone set.
214	 */
215
216	if (use_libc_data)
217		LOCKPRIVDATA();
218
219	if (flag == PRIV_STR_SHORT) {
220		int presentbasic, missingbasic, present, missing;
221		int presentzone, missingzone;
222		int count;
223
224		presentbasic = missingbasic = present = 0;
225		presentzone = missingzone = 0;
226		zone = d->pd_zoneset;
227
228		for (i = 0; i < d->pd_nprivs; i++) {
229			int mem = PRIV_ISMEMBER(pset, i);
230			if (d->pd_basicset != NULL &&
231			    PRIV_ISMEMBER(d->pd_basicset, i)) {
232				if (mem)
233					presentbasic++;
234				else
235					missingbasic++;
236			}
237			if (zone != NULL && PRIV_ISMEMBER(zone, i)) {
238				if (mem)
239					presentzone++;
240				else
241					missingzone++;
242			}
243			if (mem)
244				present++;
245		}
246		missing = d->pd_nprivs - present;
247
248		if (1 - presentbasic + missingbasic < 0) {
249			flag = PRIV_STR_PORT;
250			count = present + 1 - presentbasic + missingbasic;
251		} else {
252			flag = PRIV_STR_LIT;
253			count = present;
254		}
255		if (count >= 1 + missing) {
256			flag = PRIV_STR_SHORT;
257			count = 1 + missing;
258			all = B_TRUE;
259		}
260		if (present == presentzone && 1 + missingzone < count) {
261			flag = PRIV_STR_SHORT;
262			all = B_FALSE;
263		}
264	}
265
266	switch (flag) {
267	case PRIV_STR_LIT:
268		*res = '\0';
269		break;
270	case PRIV_STR_PORT:
271		(void) strcpy(res, "basic");
272		if (d->pd_basicset == NULL)
273			flag = PRIV_STR_LIT;
274		break;
275	case PRIV_STR_SHORT:
276		if (all)
277			(void) strcpy(res, "all");
278		else
279			(void) strcpy(res, "zone");
280		break;
281	default:
282		if (use_libc_data)
283			UNLOCKPRIVDATA();
284		return (NULL);
285	}
286	res += strlen(res);
287
288	for (i = 0; i < d->pd_nprivs; i++) {
289		/* Map the privilege to the next one sorted by name */
290		int priv = d->pd_setsort[i];
291
292		if (PRIV_ISMEMBER(pset, priv)) {
293			switch (flag) {
294			case PRIV_STR_SHORT:
295				if (all || PRIV_ISMEMBER(zone, priv))
296					continue;
297				break;
298			case PRIV_STR_PORT:
299				if (PRIV_ISMEMBER(d->pd_basicset, priv))
300					continue;
301				break;
302			case PRIV_STR_LIT:
303				break;
304			}
305			if (res != resp)
306				*res++ = separator;
307		} else {
308			switch (flag) {
309			case PRIV_STR_LIT:
310				continue;
311			case PRIV_STR_PORT:
312				if (!PRIV_ISMEMBER(d->pd_basicset, priv))
313					continue;
314				break;
315			case PRIV_STR_SHORT:
316				if (!all && !PRIV_ISMEMBER(zone, priv))
317					continue;
318				break;
319			}
320			if (res != resp)
321				*res++ = separator;
322			*res++ = neg;
323		}
324		pstr = __priv_getbynum(d, priv);
325		(void) strcpy(res, pstr);
326		res += strlen(pstr);
327	}
328	if (use_libc_data)
329		UNLOCKPRIVDATA();
330	/* Special case the set with some high bits set */
331	return (strdup(*resp == '\0' ? "none" : resp));
332}
333
334/*
335 * priv_set_to_str() is defined to return a string that
336 * the caller must deallocate with free(3C).  Grr...
337 */
338char *
339priv_set_to_str(const priv_set_t *pset, char separator, int flag)
340{
341	return (__priv_set_to_str(NULL, pset, separator, flag));
342}
343
344static char *
345do_priv_gettext(const char *priv, const char *file)
346{
347	char buf[8*1024];
348	boolean_t inentry = B_FALSE;
349	FILE	*namefp;
350
351	namefp = fopen(file, "rF");
352	if (namefp == NULL)
353		return (NULL);
354
355	/*
356	 * parse the file; it must have the following format
357	 * Lines starting with comments "#"
358	 * Lines starting with non white space with one single token:
359	 * the privileges; white space indented lines which are the
360	 * description; no empty lines are allowed in the description.
361	 */
362	while (fgets(buf, sizeof (buf), namefp) != NULL) {
363		char *lp;		/* pointer to the current line */
364
365		if (buf[0] == '#')
366			continue;
367
368		if (buf[0] == '\n') {
369			inentry = B_FALSE;
370			continue;
371		}
372
373		if (inentry)
374			continue;
375
376		/* error; not skipping; yet line starts with white space */
377		if (isspace((unsigned char)buf[0]))
378			goto out;
379
380		/* Trim trailing newline */
381		buf[strlen(buf) - 1] = '\0';
382
383		if (strcasecmp(buf, priv) != 0) {
384			inentry = B_TRUE;
385			continue;
386		}
387
388		lp = buf;
389		while (fgets(lp, sizeof (buf) - (lp - buf), namefp) != NULL) {
390			char *tstart;	/* start of text */
391			int len;
392
393			/* Empty line or start of next entry terminates */
394			if (*lp == '\n' || !isspace((unsigned char)*lp)) {
395				*lp = '\0';
396				(void) fclose(namefp);
397				return (strdup(buf));
398			}
399
400			/* Remove leading white space */
401			tstart = lp;
402			while (*tstart != '\0' &&
403				isspace((unsigned char)*tstart)) {
404				tstart++;
405			}
406
407			len = strlen(tstart);
408			(void) memmove(lp, tstart, len + 1);
409			lp += len;
410
411			/* Entry to big; prevent fgets() loop */
412			if (lp == &buf[sizeof (buf) - 1])
413				goto out;
414		}
415		if (lp != buf) {
416			*lp = '\0';
417			(void) fclose(namefp);
418			return (strdup(buf));
419		}
420	}
421out:
422	(void) fclose(namefp);
423	return (NULL);
424}
425
426/*
427 * priv_gettext() is defined to return a string that
428 * the caller must deallocate with free(3C).  Grr...
429 */
430char *
431priv_gettext(const char *priv)
432{
433	char file[MAXPATHLEN];
434	const char *loc;
435	char	*ret;
436
437	/* Not a valid privilege */
438	if (priv_getbyname(priv) < 0)
439		return (NULL);
440
441	if ((loc = setlocale(LC_MESSAGES, NULL)) == NULL)
442		loc = "C";
443
444	if (snprintf(file, sizeof (file),
445	    _DFLT_LOC_PATH "%s/LC_MESSAGES/priv_names", loc) < sizeof (file)) {
446		ret = do_priv_gettext(priv, (const char *)file);
447		if (ret != NULL)
448			return (ret);
449	}
450
451	/* If the path is too long or can't be opened, punt to default */
452	ret = do_priv_gettext(priv, "/etc/security/priv_names");
453	return (ret);
454}
455