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 }