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