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 /*
  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 *
  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 *
 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 *
 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 *
 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 *
 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 }