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