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 } 699