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