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 
51 priv_set_t *
priv_basic(void)52 priv_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  */
73 priv_set_t *
priv_str_to_set(const char * priv_names,const char * separators,const char ** endptr)74 priv_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 
170 char *
__priv_set_to_str(priv_data_t * d,const priv_set_t * pset,char separator,int flag)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  */
337 char *
priv_set_to_str(const priv_set_t * pset,char separator,int flag)338 priv_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 
343 static char *
do_priv_gettext(const char * priv,const char * file)344 do_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 	}
420 out:
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  */
429 char *
priv_gettext(const char * priv)430 priv_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