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 /* 31 * Copyright 2020 OmniOS Community Edition (OmniOSce) Association. 32 */ 33 34 #include <stdio.h> 35 #include <stdlib.h> 36 #include <unistd.h> 37 #include <string.h> 38 #include <ctype.h> 39 #include <priv.h> 40 #include <string.h> 41 #include <libgen.h> 42 #include <libintl.h> 43 #include <errno.h> 44 #include <alloca.h> 45 #include <sys/modctl.h> 46 #include <sys/devpolicy.h> 47 #include <sys/stat.h> 48 #include <sys/sysmacros.h> 49 50 #include "addrem.h" 51 #include "errmsg.h" 52 #include "plcysubr.h" 53 54 size_t devplcysys_sz; 55 const priv_impl_info_t *privimplinfo; 56 57 /* 58 * New token types should be parsed in parse_plcy_entry. 59 */ 60 #define PSET 0 61 62 typedef struct token { 63 const char *token; 64 int type; 65 ptrdiff_t off; 66 } token_t; 67 68 static token_t toktab[] = { 69 { DEVPLCY_TKN_RDP, PSET /* offsetof(devplcysys_t, dps_rdp) */ }, 70 { DEVPLCY_TKN_WRP, PSET /* offsetof(devplcysys_t, dps_wrp) */ }, 71 }; 72 73 #define RDPOL 0 74 #define WRPOL 1 75 76 #define NTOK (sizeof (toktab)/sizeof (token_t)) 77 78 /* 79 * Compute the size of the datastructures needed. 80 */ 81 void 82 devplcy_init(void) 83 { 84 if ((privimplinfo = getprivimplinfo()) == NULL) { 85 (void) fprintf(stderr, gettext(ERR_PRIVIMPL)); 86 exit(1); 87 } 88 89 devplcysys_sz = DEVPLCYSYS_SZ(privimplinfo); 90 91 toktab[RDPOL].off = 92 (char *)DEVPLCYSYS_RDP((devplcysys_t *)0, privimplinfo) - (char *)0; 93 toktab[WRPOL].off = 94 (char *)DEVPLCYSYS_WRP((devplcysys_t *)0, privimplinfo) - (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 } 492 return (1); 493 } 494 priv_copyset(pset, item); 495 priv_freeset(pset); 496 break; 497 default: 498 (void) fprintf(stderr, 499 "Internal Error: bad token type: %d\n", 500 toktab[i].type); 501 return (1); 502 } 503 /* Standard cleanup & return for good tokens */ 504 val[-1] = '='; 505 return (0); 506 } 507 } 508 (void) fprintf(stderr, gettext(ERR_BAD_TOKEN), token); 509 return (1); 510 } 511 512 static int 513 add2str(char **dstp, const char *str, size_t *sz) 514 { 515 char *p = *dstp; 516 size_t len = strlen(p) + strlen(str) + 1; 517 518 if (len > *sz) { 519 *sz *= 2; 520 if (*sz < len) 521 *sz = len; 522 *dstp = p = realloc(p, *sz); 523 if (p == NULL) { 524 (void) fprintf(stderr, gettext(ERR_NO_MEM)); 525 return (-1); 526 } 527 } 528 (void) strcat(p, str); 529 return (0); 530 } 531 532 /* 533 * Verify that the policy entry is valid and return the canonical entry. 534 */ 535 char * 536 check_plcy_entry(char *entry, const char *driver, boolean_t todel) 537 { 538 char *res; 539 devplcysys_t *ds; 540 char *tok; 541 size_t sz = strlen(entry) * 2 + strlen(driver) + 3; 542 boolean_t tokseen = B_FALSE; 543 544 devplcy_init(); 545 546 res = malloc(sz); 547 ds = alloca(devplcysys_sz); 548 549 if (res == NULL || ds == NULL) { 550 (void) fprintf(stderr, gettext(ERR_NO_MEM)); 551 free(res); 552 return (NULL); 553 } 554 555 *res = '\0'; 556 557 while ((tok = strtok(entry, " \t\n")) != NULL) { 558 entry = NULL; 559 560 /* It's not a token */ 561 if (strchr(tok, '=') == NULL) { 562 if (strchr(tok, ':') != NULL) { 563 (void) fprintf(stderr, gettext(ERR_BAD_MINOR)); 564 free(res); 565 return (NULL); 566 } 567 if (*res != '\0' && add2str(&res, "\n", &sz) != 0) 568 return (NULL); 569 570 if (*tok == '(') { 571 char type; 572 if (parse_minor_range(tok, &ds->dps_lomin, 573 &ds->dps_himin, &type) != 0 || 574 (!todel && type == '\0')) { 575 (void) fprintf(stderr, 576 gettext(ERR_BAD_MINOR)); 577 free(res); 578 return (NULL); 579 } 580 } else { 581 char *tmp = strchr(tok, '*'); 582 583 if (tmp != NULL && 584 strchr(tmp + 1, '*') != NULL) { 585 (void) fprintf(stderr, 586 gettext(ERR_BAD_MINOR)); 587 free(res); 588 } 589 } 590 591 if (add2str(&res, driver, &sz) != 0) 592 return (NULL); 593 if (add2str(&res, ":", &sz) != 0) 594 return (NULL); 595 if (add2str(&res, tok, &sz) != 0) 596 return (NULL); 597 tokseen = B_FALSE; 598 } else { 599 if (*res == '\0') { 600 if (add2str(&res, driver, &sz) != 0) 601 return (NULL); 602 if (add2str(&res, ":*", &sz) != 0) 603 return (NULL); 604 } 605 if (parse_plcy_token(tok, ds) != 0) { 606 free(res); 607 return (NULL); 608 } 609 610 if (add2str(&res, "\t", &sz) != 0) 611 return (NULL); 612 if (add2str(&res, tok, &sz) != 0) 613 return (NULL); 614 tokseen = B_TRUE; 615 } 616 } 617 if ((todel && tokseen) || *res == '\0' || (!todel && !tokseen)) { 618 (void) fprintf(stderr, gettext(ERR_INVALID_PLCY)); 619 free(res); 620 return (NULL); 621 } 622 if (!todel) 623 if (add2str(&res, "\n", &sz) != 0) 624 return (NULL); 625 return (res); 626 } 627 628 int 629 update_device_policy(const char *filename, const char *entry, boolean_t repl) 630 { 631 FILE *fp; 632 633 if (repl) { 634 char *dup, *tok, *s1; 635 636 dup = strdup(entry); 637 if (dup == NULL) { 638 (void) fprintf(stderr, gettext(ERR_NO_MEM)); 639 return (ERROR); 640 } 641 642 /* 643 * Split the entry in lines; then get the first token 644 * of each line. 645 */ 646 for (tok = strtok_r(dup, "\n", &s1); tok != NULL; 647 tok = strtok_r(NULL, "\n", &s1)) { 648 649 tok = strtok(tok, " \n\t"); 650 651 if (delete_one_entry(filename, tok) != 0) { 652 free(dup); 653 return (ERROR); 654 } 655 } 656 657 free(dup); 658 } 659 660 fp = fopen(filename, "a"); 661 if (fp == NULL) 662 return (ERROR); 663 664 (void) fputs(entry, fp); 665 666 if (fflush(fp) != 0 || fsync(fileno(fp)) != 0 || fclose(fp) != 0) 667 return (ERROR); 668 669 return (NOERR); 670 } 671 672 673 /* 674 * We need to allocate the privileges now or the privilege set 675 * parsing code will not allow them. 676 */ 677 int 678 check_priv_entry(const char *privlist, boolean_t add) 679 { 680 char *l = strdup(privlist); 681 char *pr; 682 683 if (l == NULL) { 684 (void) fprintf(stderr, gettext(ERR_NO_MEM)); 685 return (ERROR); 686 } 687 688 while ((pr = strtok_r(l, ",", &l)) != NULL) { 689 /* Privilege already exists */ 690 if (priv_getbyname(pr) != -1) 691 continue; 692 693 if (add && modctl(MODALLOCPRIV, pr) != 0) { 694 (void) fprintf(stderr, gettext(ERR_BAD_PRIV), pr, 695 strerror(errno)); 696 return (ERROR); 697 } 698 } 699 return (NOERR); 700 } 701