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