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, Version 1.0 only
   6  * (the "License").  You may not use this file except in compliance
   7  * with the License.
   8  *
   9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
  10  * or http://www.opensolaris.org/os/licensing.
  11  * See the License for the specific language governing permissions
  12  * and limitations under the License.
  13  *
  14  * When distributing Covered Code, include this CDDL HEADER in each
  15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
  16  * If applicable, add the following below this CDDL HEADER, with the
  17  * fields enclosed by brackets "[]" replaced with your own identifying
  18  * information: Portions Copyright [yyyy] [name of copyright owner]
  19  *
  20  * CDDL HEADER END
  21  */
  22 /*
  23  * Copyright 2003 Sun Microsystems, Inc.  All rights reserved.
  24  * Use is subject to license terms.
  25  *
  26  * Device policy specific subroutines.  We cannot merge them with
  27  * drvsubr.c because of static linking requirements.
  28  */
  29 
  30 #include <stdio.h>
  31 #include <stdlib.h>
  32 #include <unistd.h>
  33 #include <string.h>
  34 #include <ctype.h>
  35 #include <priv.h>
  36 #include <string.h>
  37 #include <libgen.h>
  38 #include <libintl.h>
  39 #include <errno.h>
  40 #include <alloca.h>
  41 #include <sys/modctl.h>
  42 #include <sys/devpolicy.h>
  43 #include <sys/stat.h>
  44 #include <sys/sysmacros.h>
  45 
  46 #include "addrem.h"
  47 #include "errmsg.h"
  48 #include "plcysubr.h"
  49 
  50 size_t devplcysys_sz;
  51 const priv_impl_info_t *privimplinfo;
  52 
  53 /*
  54  * New token types should be parsed in parse_plcy_entry.
  55  */
  56 #define PSET    0
  57 
  58 typedef struct token {
  59         const char      *token;
  60         int             type;
  61         ptrdiff_t       off;
  62 } token_t;
  63 
  64 static token_t toktab[] = {
  65         { DEVPLCY_TKN_RDP, PSET /* offsetof(devplcysys_t, dps_rdp) */ },
  66         { DEVPLCY_TKN_WRP, PSET /* offsetof(devplcysys_t, dps_wrp) */ },
  67 };
  68 
  69 #define RDPOL   0
  70 #define WRPOL   1
  71 
  72 #define NTOK    (sizeof (toktab)/sizeof (token_t))
  73 
  74 /*
  75  * Compute the size of the datastructures needed.
  76  */
  77 void
  78 devplcy_init(void)
  79 {
  80         if ((privimplinfo = getprivimplinfo()) == NULL) {
  81                 (void) fprintf(stderr, gettext(ERR_PRIVIMPL));
  82                 exit(1);
  83         }
  84 
  85         devplcysys_sz = DEVPLCYSYS_SZ(privimplinfo);
  86 
  87         toktab[RDPOL].off =
  88                 (char *)DEVPLCYSYS_RDP((devplcysys_t *)0, privimplinfo) -
  89                                 (char *)0;
  90         toktab[WRPOL].off =
  91                 (char *)DEVPLCYSYS_WRP((devplcysys_t *)0, privimplinfo) -
  92                                 (char *)0;
  93 }
  94 
  95 /*
  96  * Read a configuration file line and return a static buffer pointing to it.
  97  * It returns a static struct fileentry which has several fields:
  98  *      - rawbuf, which includes the lines including empty lines and comments
  99  *      leading up to the file and the entry as found in the file
 100  *      - orgentry, pointer in rawbuf to the start of the entry proper.
 101  *      - entry, a pre-parsed entry, escaped newlines removed.
 102  *      - startline, the line number of the first line in the file
 103  */
 104 fileentry_t *
 105 fgetline(FILE *fp)
 106 {
 107         static size_t sz = BUFSIZ;
 108         static struct fileentry fe;
 109         static int linecnt = 1;
 110 
 111         char *buf = fe.rawbuf;
 112         ptrdiff_t off;
 113         char *p;
 114         int c, lastc, i;
 115 
 116         if (buf == NULL) {
 117                 fe.rawbuf = buf = malloc(sz);
 118                 if (buf == NULL)
 119                         return (NULL);
 120         }
 121         if (fe.entry != NULL) {
 122                 free(fe.entry);
 123                 fe.orgentry = fe.entry = NULL;
 124         }
 125 
 126         i = 0;
 127         off = -1;
 128         c = '\n';
 129 
 130         while (lastc = c, (c = getc(fp)) != EOF) {
 131                 buf[i++] = c;
 132 
 133                 if (i == sz) {
 134                         sz *= 2;
 135                         fe.rawbuf = buf = realloc(buf, sz);
 136                         if (buf == NULL)
 137                                 return (NULL);
 138                 }
 139 
 140                 if (c == '\n') {
 141                         linecnt++;
 142                         /* Newline, escaped or not yet processing an entry */
 143                         if (off == -1 || lastc == '\\')
 144                                 continue;
 145                 } else if (lastc == '\n' && off == -1) {
 146                         /* Start of more comments */
 147                         if (c == '#')
 148                                 continue;
 149                         /* Found start of entry */
 150                         off = i - 1;
 151                         fe.startline = linecnt;
 152                         continue;
 153                 } else
 154                         continue;
 155 
 156                 buf[i] = '\0';
 157                 fe.orgentry = buf + off;
 158                 p = fe.entry = strdup(fe.orgentry);
 159 
 160                 if (p == NULL)
 161                         return (NULL);
 162 
 163                 /* Remove <backslash><newline> */
 164                 if ((p = strchr(p, '\\')) != NULL) {
 165                         for (off = 0; (p[-off] = p[0]) != '\0'; p++)
 166                                 if (p[0] == '\\' && p[1] == '\n') {
 167                                         off += 2;
 168                                         p++;
 169                                 }
 170                 }
 171                 return (&fe);
 172         }
 173         if (lastc != '\n' || off != -1)
 174                 return (NULL);
 175         buf[i] = '\0';
 176         linecnt = 1;
 177         return (&fe);
 178 }
 179 
 180 /*
 181  * Parse minor number ranges:
 182  *      (minor) or (lowminor-highminor)
 183  * Return 0 for success, -1 for failure.
 184  */
 185 int
 186 parse_minor_range(const char *range, minor_t *lo, minor_t *hi, char *type)
 187 {
 188         unsigned long tmp;
 189         char *p;
 190 
 191         if (*range++ != '(')
 192                 return (-1);
 193 
 194         errno = 0;
 195         tmp = strtoul(range, &p, 0);
 196         if (tmp > L_MAXMIN32 || (tmp == 0 && errno != 0) ||
 197             (*p != '-' && *p != ')'))
 198                 return (-1);
 199         *lo = tmp;
 200         if (*p == '-') {
 201                 errno = 0;
 202                 tmp = strtoul(p + 1, &p, 0);
 203                 if (tmp > L_MAXMIN32 || (tmp == 0 && errno != 0) || *p != ')')
 204                         return (-1);
 205         }
 206         *hi = tmp;
 207         if (*lo > *hi)
 208                 return (-1);
 209 
 210         switch (p[1]) {
 211         case '\0':
 212                 *type = '\0';
 213                 break;
 214         case 'c':
 215         case 'C':
 216                 *type = 'c';
 217                 break;
 218         case 'b':
 219         case 'B':
 220                 *type = 'b';
 221                 break;
 222         default:
 223                 return (-1);
 224         }
 225         return (0);
 226 }
 227 
 228 static void
 229 put_minor_range(FILE *fp, fileentry_t *old, const char *devn, const char *tail,
 230     minor_t lo, minor_t hi, char type)
 231 {
 232         /* Preserve preceeding comments */
 233         if (old != NULL && old->rawbuf != old->orgentry)
 234                 (void) fwrite(old->rawbuf, 1, old->orgentry - old->rawbuf, fp);
 235 
 236         if (type == '\0') {
 237                 put_minor_range(fp, NULL, devn, tail, lo, hi, 'b');
 238                 put_minor_range(fp, NULL, devn, tail, lo, hi, 'c');
 239         } else if (lo == hi) {
 240                 (void) fprintf(fp, "%s:(%d)%c%s", devn, (int)lo, type, tail);
 241         } else {
 242                 (void) fprintf(fp, "%s:(%d-%d)%c%s", devn, (int)lo, (int)hi,
 243                     type, tail);
 244         }
 245 }
 246 
 247 static int
 248 delete_one_entry(const char *filename, const char *entry)
 249 {
 250         char tfile[MAXPATHLEN];
 251         char ofile[MAXPATHLEN];
 252         char *nfile;
 253         FILE *old, *new;
 254         fileentry_t *fep;
 255         struct stat buf;
 256         int newfd;
 257         char *mpart;
 258         boolean_t delall;
 259         boolean_t delrange;
 260         minor_t rlo, rhi;
 261         char rtype;
 262 
 263         mpart = strchr(entry, ':');
 264         if (mpart == NULL) {
 265                 delall = B_TRUE;
 266                 delrange = B_FALSE;
 267         } else {
 268                 delall = B_FALSE;
 269                 mpart++;
 270                 if (*mpart == '(') {
 271                         if (parse_minor_range(mpart, &rlo, &rhi, &rtype) != 0)
 272                                 return (-1);
 273                         delrange = B_TRUE;
 274                 } else {
 275                         delrange = B_FALSE;
 276                 }
 277         }
 278 
 279         if (strlen(filename) + sizeof (XEND)  > sizeof (tfile))
 280                 return (-1);
 281 
 282         old = fopen(filename, "r");
 283 
 284         if (old == NULL)
 285                 return (-1);
 286 
 287         (void) snprintf(tfile, sizeof (tfile), "%s%s", filename, XEND);
 288         (void) snprintf(ofile, sizeof (ofile), "%s%s", filename, ".old");
 289 
 290         nfile = mktemp(tfile);
 291 
 292         new = fopen(nfile, "w");
 293         if (new == NULL) {
 294                 (void) fclose(old);
 295                 return (ERROR);
 296         }
 297 
 298         newfd = fileno(new);
 299 
 300         /* Copy permissions, ownership */
 301         if (fstat(fileno(old), &buf) == 0) {
 302                 (void) fchown(newfd, buf.st_uid, buf.st_gid);
 303                 (void) fchmod(newfd, buf.st_mode);
 304         } else {
 305                 (void) fchown(newfd, 0, 3);     /* root:sys */
 306                 (void) fchmod(newfd, 0644);
 307         }
 308 
 309         while ((fep = fgetline(old))) {
 310                 char *tok;
 311                 char *min;
 312                 char *tail;
 313                 char tc;
 314                 int len;
 315 
 316                 /* Trailing comments */
 317                 if (fep->entry == NULL) {
 318                         (void) fputs(fep->rawbuf, new);
 319                         break;
 320                 }
 321 
 322                 tok = fep->entry;
 323                 while (*tok && isspace(*tok))
 324                         tok++;
 325 
 326                 if (*tok == '\0') {
 327                         (void) fputs(fep->rawbuf, new);
 328                         break;
 329                 }
 330 
 331                 /* Make sure we can recover the remainder incl. whitespace */
 332                 tail = strpbrk(tok, "\t\n ");
 333                 if (tail == NULL)
 334                         tail = tok + strlen(tok);
 335                 tc = *tail;
 336                 *tail = '\0';
 337 
 338                 min = strchr(tok, ':');
 339                 if (min && (delall || delrange))
 340                         *min++ = '\0';
 341 
 342                 len = strlen(tok);
 343                 if (delrange) {
 344                         minor_t lo, hi;
 345                         char type;
 346 
 347                         /*
 348                          * Delete or shrink overlapping ranges.
 349                          */
 350                         if (strncmp(entry, tok, len) == 0 &&
 351                             entry[len] == ':' &&
 352                             min != NULL &&
 353                             parse_minor_range(min, &lo, &hi, &type) == 0 &&
 354                             (type == rtype || rtype == '\0') &&
 355                             lo <= rhi && hi >= rlo) {
 356                                 minor_t newlo, newhi;
 357 
 358                                 /* Complete overlap, then drop it. */
 359                                 if (lo >= rlo && hi <= rhi)
 360                                         continue;
 361 
 362                                 /* Partial overlap, shrink range */
 363                                 if (lo < rlo)
 364                                         newhi = rlo - 1;
 365                                 else
 366                                         newhi = hi;
 367                                 if (hi > rhi)
 368                                         newlo = rhi + 1;
 369                                 else
 370                                         newlo = lo;
 371 
 372                                 /* restore NULed character */
 373                                 *tail = tc;
 374 
 375                                 /* Split range? */
 376                                 if (newlo > newhi) {
 377                                         /*
 378                                          * We have two ranges:
 379                                          * lo ... newhi (== rlo - 1)
 380                                          * newlo (== rhi + 1) .. hi
 381                                          */
 382                                         put_minor_range(new, fep, tok, tail,
 383                                             lo, newhi, type);
 384                                         put_minor_range(new, NULL, tok, tail,
 385                                             newlo, hi, type);
 386                                 } else {
 387                                         put_minor_range(new, fep, tok, tail,
 388                                             newlo, newhi, type);
 389                                 }
 390                                 continue;
 391                         }
 392                 } else if (strcmp(entry, tok) == 0 ||
 393                     (strncmp(entry, tok, len) == 0 &&
 394                     entry[len] == ':' &&
 395                     entry[len+1] == '*' &&
 396                     entry[len+2] == '\0')) {
 397                         /*
 398                          * Delete exact match.
 399                          */
 400                         continue;
 401                 }
 402 
 403                 /* Copy unaffected entry. */
 404                 (void) fputs(fep->rawbuf, new);
 405         }
 406         (void) fclose(old);
 407         (void) fflush(new);
 408         (void) fsync(newfd);
 409         if (ferror(new) == 0 && fclose(new) == 0 && fep != NULL) {
 410                 if (rename(filename, ofile) != 0) {
 411                         perror(NULL);
 412                         (void) fprintf(stderr, gettext(ERR_UPDATE), ofile);
 413                         (void) unlink(ofile);
 414                         (void) unlink(nfile);
 415                         return (ERROR);
 416                 } else if (rename(nfile, filename) != 0) {
 417                         perror(NULL);
 418                         (void) fprintf(stderr, gettext(ERR_UPDATE), ofile);
 419                         (void) rename(ofile, filename);
 420                         (void) unlink(nfile);
 421                         return (ERROR);
 422                 }
 423                 (void) unlink(ofile);
 424         } else
 425                 (void) unlink(nfile);
 426         return (0);
 427 }
 428 
 429 
 430 int
 431 delete_plcy_entry(const char *filename, const char *entry)
 432 {
 433         char *p, *single;
 434         char *copy;
 435         int ret = 0;
 436 
 437         copy = strdup(entry);
 438         if (copy == NULL)
 439                 return (ERROR);
 440 
 441         for (single = strtok_r(copy, " \t\n", &p);
 442             single != NULL;
 443             single = strtok_r(NULL, " \t\n", &p)) {
 444                 if ((ret = delete_one_entry(filename, single)) != 0) {
 445                         free(copy);
 446                         return (ret);
 447                 }
 448         }
 449         free(copy);
 450         return (0);
 451 }
 452 
 453 /*
 454  * Analyze the device policy token; new tokens should be added to
 455  * toktab; new token types should be coded here.
 456  */
 457 int
 458 parse_plcy_token(char *token, devplcysys_t *dp)
 459 {
 460         char *val = strchr(token, '=');
 461         const char *perr;
 462         int i;
 463         priv_set_t *pset;
 464 
 465         if (val == NULL) {
 466                 (void) fprintf(stderr, gettext(ERR_NO_EQUALS), token);
 467                 return (1);
 468         }
 469         *val++ = '\0';
 470 
 471         for (i = 0; i < NTOK; i++) {
 472                 if (strcmp(token, toktab[i].token) == 0) {
 473                         /* standard pointer computation for tokens */
 474                         void *item = (char *)dp + toktab[i].off;
 475 
 476                         switch (toktab[i].type) {
 477                         case PSET:
 478                                 pset = priv_str_to_set(val, ",", &perr);
 479                                 if (pset == NULL) {
 480                                         if (perr == NULL)
 481                                             (void) fprintf(stderr,
 482                                                         gettext(ERR_NO_MEM));
 483                                         else
 484                                             (void) fprintf(stderr,
 485                                                 gettext(ERR_BAD_PRIVS),
 486                                                 perr - val, val, perr);
 487                                         return (1);
 488                                 }
 489                                 priv_copyset(pset, item);
 490                                 priv_freeset(pset);
 491                                 break;
 492                         default:
 493                                 (void) fprintf(stderr,
 494                                         "Internal Error: bad token type: %d\n",
 495                                                 toktab[i].type);
 496                                 return (1);
 497                         }
 498                         /* Standard cleanup & return for good tokens */
 499                         val[-1] = '=';
 500                         return (0);
 501                 }
 502         }
 503         (void) fprintf(stderr, gettext(ERR_BAD_TOKEN), token);
 504         return (1);
 505 }
 506 
 507 static int
 508 add2str(char **dstp, const char *str, size_t *sz)
 509 {
 510         char *p = *dstp;
 511         size_t len = strlen(p) + strlen(str) + 1;
 512 
 513         if (len > *sz) {
 514                 *sz *= 2;
 515                 if (*sz < len)
 516                         *sz = len;
 517                 *dstp = p = realloc(p, *sz);
 518                 if (p == NULL) {
 519                         (void) fprintf(stderr, gettext(ERR_NO_MEM));
 520                         return (-1);
 521                 }
 522         }
 523         (void) strcat(p, str);
 524         return (0);
 525 }
 526 
 527 /*
 528  * Verify that the policy entry is valid and return the canonical entry.
 529  */
 530 char *
 531 check_plcy_entry(char *entry, const char *driver, boolean_t todel)
 532 {
 533         char *res;
 534         devplcysys_t *ds;
 535         char *tok;
 536         size_t sz = strlen(entry) * 2 + strlen(driver) + 3;
 537         boolean_t tokseen = B_FALSE;
 538 
 539         devplcy_init();
 540 
 541         res = malloc(sz);
 542         ds = alloca(devplcysys_sz);
 543 
 544         if (res == NULL || ds == NULL) {
 545                 (void) fprintf(stderr, gettext(ERR_NO_MEM));
 546                 return (NULL);
 547         }
 548 
 549         *res = '\0';
 550 
 551         while ((tok = strtok(entry, " \t\n")) != NULL) {
 552                 entry = NULL;
 553 
 554                 /* It's not a token */
 555                 if (strchr(tok, '=') == NULL) {
 556                         if (strchr(tok, ':') != NULL) {
 557                                 (void) fprintf(stderr, gettext(ERR_BAD_MINOR));
 558                                 free(res);
 559                                 return (NULL);
 560                         }
 561                         if (*res != '\0' && add2str(&res, "\n", &sz) != 0)
 562                                 return (NULL);
 563 
 564                         if (*tok == '(') {
 565                                 char type;
 566                                 if (parse_minor_range(tok, &ds->dps_lomin,
 567                                     &ds->dps_himin, &type) != 0 ||
 568                                     (!todel && type == '\0')) {
 569                                         (void) fprintf(stderr,
 570                                             gettext(ERR_BAD_MINOR));
 571                                         free(res);
 572                                         return (NULL);
 573                                 }
 574                         } else {
 575                                 char *tmp = strchr(tok, '*');
 576 
 577                                 if (tmp != NULL &&
 578                                     strchr(tmp + 1, '*') != NULL) {
 579                                         (void) fprintf(stderr,
 580                                             gettext(ERR_BAD_MINOR));
 581                                         free(res);
 582                                 }
 583                         }
 584 
 585                         if (add2str(&res, driver, &sz) != 0)
 586                                 return (NULL);
 587                         if (add2str(&res, ":", &sz) != 0)
 588                                 return (NULL);
 589                         if (add2str(&res, tok, &sz) != 0)
 590                                 return (NULL);
 591                         tokseen = B_FALSE;
 592                 } else {
 593                         if (*res == '\0') {
 594                                 if (add2str(&res, driver, &sz) != 0)
 595                                         return (NULL);
 596                                 if (add2str(&res, ":*", &sz) != 0)
 597                                         return (NULL);
 598                         }
 599                         if (parse_plcy_token(tok, ds) != 0) {
 600                                 free(res);
 601                                 return (NULL);
 602                         }
 603 
 604                         if (add2str(&res, "\t", &sz) != 0)
 605                                 return (NULL);
 606                         if (add2str(&res, tok, &sz) != 0)
 607                                 return (NULL);
 608                         tokseen = B_TRUE;
 609                 }
 610         }
 611         if (todel && tokseen || *res == '\0' || !todel && !tokseen) {
 612                 (void) fprintf(stderr, gettext(ERR_INVALID_PLCY));
 613                 free(res);
 614                 return (NULL);
 615         }
 616         if (!todel)
 617                 if (add2str(&res, "\n", &sz) != 0)
 618                         return (NULL);
 619         return (res);
 620 }
 621 
 622 int
 623 update_device_policy(const char *filename, const char *entry, boolean_t repl)
 624 {
 625         FILE *fp;
 626 
 627         if (repl) {
 628                 char *dup, *tok, *s1;
 629 
 630                 dup = strdup(entry);
 631                 if (dup == NULL) {
 632                         (void) fprintf(stderr, gettext(ERR_NO_MEM));
 633                         return (ERROR);
 634                 }
 635 
 636                 /*
 637                  * Split the entry in lines; then get the first token
 638                  * of each line.
 639                  */
 640                 for (tok = strtok_r(dup, "\n", &s1); tok != NULL;
 641                     tok = strtok_r(NULL, "\n", &s1)) {
 642 
 643                         tok = strtok(tok, " \n\t");
 644 
 645                         if (delete_one_entry(filename, tok) != 0) {
 646                                 free(dup);
 647                                 return (ERROR);
 648                         }
 649                 }
 650 
 651                 free(dup);
 652         }
 653 
 654         fp = fopen(filename, "a");
 655         if (fp == NULL)
 656                 return (ERROR);
 657 
 658         (void) fputs(entry, fp);
 659 
 660         if (fflush(fp) != 0 || fsync(fileno(fp)) != 0 || fclose(fp) != 0)
 661                 return (ERROR);
 662 
 663         return (NOERR);
 664 }
 665 
 666 
 667 /*
 668  * We need to allocate the privileges now or the privilege set
 669  * parsing code will not allow them.
 670  */
 671 int
 672 check_priv_entry(const char *privlist, boolean_t add)
 673 {
 674         char *l = strdup(privlist);
 675         char *pr;
 676 
 677         if (l == NULL) {
 678                 (void) fprintf(stderr, gettext(ERR_NO_MEM));
 679                 return (ERROR);
 680         }
 681 
 682         while ((pr = strtok_r(l, ",", &l)) != NULL) {
 683                 /* Privilege already exists */
 684                 if (priv_getbyname(pr) != -1)
 685                         continue;
 686 
 687                 if (add && modctl(MODALLOCPRIV, pr) != 0) {
 688                         (void) fprintf(stderr, gettext(ERR_BAD_PRIV), pr,
 689                                 strerror(errno));
 690                         return (ERROR);
 691                 }
 692         }
 693         return (NOERR);
 694 }