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 }