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 }