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 (the "License"). 6 * You may not use this file except in compliance with the License. 7 * 8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 9 * or http://www.opensolaris.org/os/licensing. 10 * See the License for the specific language governing permissions 11 * and limitations under the License. 12 * 13 * When distributing Covered Code, include this CDDL HEADER in each 14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 15 * If applicable, add the following below this CDDL HEADER, with the 16 * fields enclosed by brackets "[]" replaced with your own identifying 17 * information: Portions Copyright [yyyy] [name of copyright owner] 18 * 19 * CDDL HEADER END 20 */ 21 /* 22 * Copyright 2007 Sun Microsystems, Inc. All rights reserved. 23 * Use is subject to license terms. 24 */ 25 26 #pragma ident "%Z%%M% %I% %E% SMI" 27 28 #define _POSIX_PTHREAD_SEMANTICS /* for getgrnam_r */ 29 #ifdef lint 30 #define _REENTRANT /* for strtok_r */ 31 #endif 32 33 #include <stdio.h> 34 #include <stdlib.h> 35 #include <ctype.h> 36 #include <string.h> 37 #include <unistd.h> 38 #include <dirent.h> 39 #include <errno.h> 40 #include <grp.h> 41 #include <pwd.h> 42 #include <nss_dbdefs.h> 43 #include <stdarg.h> 44 #include <syslog.h> 45 #include <sys/acl.h> 46 #include <sys/types.h> 47 #include <sys/stat.h> 48 #include <sys/ddi.h> 49 #include <sys/sunddi.h> 50 #include <sys/devinfo_impl.h> 51 #include <sys/hwconf.h> 52 #include <sys/modctl.h> 53 #include <libnvpair.h> 54 #include <device_info.h> 55 #include <regex.h> 56 #include <strings.h> 57 #include <libdevinfo.h> 58 #include <zone.h> 59 60 extern int is_minor_node(const char *, const char **); 61 62 static int logindevperm(const char *, uid_t, gid_t, void (*)()); 63 static int dir_dev_acc(char *, char *, uid_t, gid_t, mode_t, char *line, 64 void (*)()); 65 static int setdevaccess(char *, uid_t, gid_t, mode_t, void (*)()); 66 static void logerror(char *); 67 68 static int is_blank(char *); 69 70 #define MAX_LINELEN 256 71 #define LOGINDEVPERM "/etc/logindevperm" 72 #define DIRWILD "/*" /* directory wildcard */ 73 #define DIRWLDLEN 2 /* strlen(DIRWILD) */ 74 75 /* 76 * Revoke all access to a device node and make sure that there are 77 * no interposed streams devices attached. Must be called before a 78 * device is actually opened. 79 * When fdetach is called, the underlying device node is revealed; it 80 * will have the previous owner and that owner can re-attach; so we 81 * retry until we win. 82 * Ignore non-existent devices. 83 */ 84 static int 85 setdevaccess(char *dev, uid_t uid, gid_t gid, mode_t mode, 86 void (*errmsg)(char *)) 87 { 88 int err = 0, local_errno; 89 char errstring[MAX_LINELEN]; 90 struct stat st; 91 92 if (chown(dev, uid, gid) == -1) { 93 if (errno == ENOENT) /* no such file */ 94 return (0); 95 err = -1; 96 local_errno = errno; 97 } 98 99 /* 100 * don't fdetach block devices, as it will unmount them 101 */ 102 if (!((stat(dev, &st) == 0) && ((st.st_mode & S_IFMT) == S_IFBLK))) { 103 while (fdetach(dev) == 0) { 104 if (chown(dev, uid, gid) == -1) { 105 err = -1; 106 local_errno = errno; 107 } 108 } 109 if (err && errmsg) { 110 (void) snprintf(errstring, MAX_LINELEN, 111 "failed to chown device %s: %s\n", 112 dev, strerror(local_errno)); 113 (*errmsg)(errstring); 114 } 115 } 116 117 /* 118 * strip_acl sets an acl and changes the files owner/group 119 */ 120 err = acl_strip(dev, uid, gid, mode); 121 122 if (err != 0) { 123 /* 124 * If the file system returned ENOSYS, we know that it 125 * doesn't support ACLs, therefore, we must assume that 126 * there were no ACLs to remove in the first place. 127 */ 128 err = 0; 129 if (errno != ENOSYS) { 130 err = -1; 131 132 if (errmsg) { 133 (void) snprintf(errstring, MAX_LINELEN, 134 "failed to set acl on device %s: %s\n", 135 dev, strerror(errno)); 136 (*errmsg)(errstring); 137 } 138 } 139 if (chmod(dev, mode) == -1) { 140 err = -1; 141 if (errmsg) { 142 (void) snprintf(errstring, MAX_LINELEN, 143 "failed to chmod device %s: %s\n", 144 dev, strerror(errno)); 145 (*errmsg)(errstring); 146 } 147 } 148 } 149 150 return (err); 151 } 152 153 /* 154 * logindevperm - change owner/group/permissions of devices 155 * list in /etc/logindevperm. 156 */ 157 static int 158 logindevperm(const char *ttyn, uid_t uid, gid_t gid, void (*errmsg)(char *)) 159 { 160 int err = 0, lineno = 0; 161 const char *field_delims = " \t\n"; 162 char line[MAX_LINELEN], errstring[MAX_LINELEN]; 163 char saveline[MAX_LINELEN]; 164 char *console; 165 char *mode_str; 166 char *dev_list; 167 char *device; 168 char *ptr; 169 int mode; 170 FILE *fp; 171 172 if ((fp = fopen(LOGINDEVPERM, "r")) == NULL) { 173 if (errmsg) { 174 (void) snprintf(errstring, MAX_LINELEN, 175 LOGINDEVPERM ": open failed: %s\n", 176 strerror(errno)); 177 (*errmsg)(errstring); 178 } 179 return (-1); 180 } 181 182 while (fgets(line, MAX_LINELEN, fp) != NULL) { 183 char *last; 184 lineno++; 185 186 if ((ptr = strchr(line, '#')) != NULL) 187 *ptr = '\0'; /* handle comments */ 188 189 (void) strcpy(saveline, line); 190 191 console = strtok_r(line, field_delims, &last); 192 if (console == NULL) 193 continue; /* ignore blank lines */ 194 195 if (strcmp(console, ttyn) != 0) 196 continue; /* not our tty, skip */ 197 198 mode_str = strtok_r(last, field_delims, &last); 199 if (mode_str == NULL) { 200 err = -1; /* invalid entry, skip */ 201 if (errmsg) { 202 (void) snprintf(errstring, MAX_LINELEN, 203 LOGINDEVPERM 204 ": line %d, invalid entry -- %s\n", 205 lineno, line); 206 (*errmsg)(errstring); 207 } 208 continue; 209 } 210 211 /* convert string to octal value */ 212 mode = strtol(mode_str, &ptr, 8); 213 if (mode < 0 || mode > 0777 || *ptr != '\0') { 214 err = -1; /* invalid mode, skip */ 215 if (errmsg) { 216 (void) snprintf(errstring, MAX_LINELEN, 217 LOGINDEVPERM 218 ": line %d, invalid mode -- %s\n", 219 lineno, mode_str); 220 (*errmsg)(errstring); 221 } 222 continue; 223 } 224 225 dev_list = strtok_r(last, field_delims, &last); 226 if (dev_list == NULL) { 227 err = -1; /* empty device list, skip */ 228 if (errmsg) { 229 (void) snprintf(errstring, MAX_LINELEN, 230 LOGINDEVPERM 231 ": line %d, empty device list -- %s\n", 232 lineno, line); 233 (*errmsg)(errstring); 234 } 235 continue; 236 } 237 238 device = strtok_r(dev_list, ":", &last); 239 while (device != NULL) { 240 if ((device[0] != '/') || (strlen(device) <= 1)) { 241 err = -1; 242 } else if (dir_dev_acc("/", &device[1], uid, gid, mode, 243 saveline, errmsg)) { 244 err = -1; 245 } 246 device = strtok_r(last, ":", &last); 247 } 248 } 249 (void) fclose(fp); 250 return (err); 251 } 252 253 /* 254 * returns 0 if resolved, -1 otherwise. 255 * devpath: Absolute path to /dev link 256 * devfs_path: Returns malloced string: /devices path w/out "/devices" 257 */ 258 static int 259 resolve_link(char *devpath, char **devfs_path) 260 { 261 char contents[PATH_MAX + 1]; 262 char stage_link[PATH_MAX + 1]; 263 char *ptr; 264 int linksize; 265 char *slashdev = "/dev/"; 266 267 if (devfs_path) { 268 *devfs_path = NULL; 269 } 270 271 linksize = readlink(devpath, contents, PATH_MAX); 272 273 if (linksize <= 0) { 274 return (-1); 275 } else { 276 contents[linksize] = '\0'; 277 } 278 279 /* 280 * if the link contents is not a minor node assume 281 * that link contents is really a pointer to another 282 * link, and if so recurse and read its link contents. 283 */ 284 if (is_minor_node((const char *)contents, (const char **)&ptr) != 285 1) { 286 if (strncmp(contents, slashdev, strlen(slashdev)) == 0) { 287 /* absolute path, starting with /dev */ 288 (void) strcpy(stage_link, contents); 289 } else { 290 /* relative path, prefix devpath */ 291 if ((ptr = strrchr(devpath, '/')) == NULL) { 292 /* invalid link */ 293 return (-1); 294 } 295 *ptr = '\0'; 296 (void) strcpy(stage_link, devpath); 297 *ptr = '/'; 298 (void) strcat(stage_link, "/"); 299 (void) strcat(stage_link, contents); 300 301 } 302 return (resolve_link(stage_link, devfs_path)); 303 } 304 305 if (devfs_path) { 306 *devfs_path = strdup(ptr); 307 if (*devfs_path == NULL) { 308 return (-1); 309 } 310 } 311 312 return (0); 313 } 314 315 /* 316 * check a logindevperm line for a driver list and match this against 317 * the driver of the minor node 318 * returns 0 if no drivers were specified or a driver match 319 */ 320 static int 321 check_driver_match(char *path, char *line) 322 { 323 char *drv, *driver, *lasts; 324 char *devfs_path = NULL; 325 char saveline[MAX_LINELEN]; 326 char *p; 327 328 if (resolve_link(path, &devfs_path) == 0) { 329 char *p; 330 char pwd_buf[PATH_MAX]; 331 di_node_t node; 332 333 /* truncate on : so we can take a snapshot */ 334 (void) strcpy(pwd_buf, devfs_path); 335 p = strrchr(pwd_buf, ':'); 336 *p = '\0'; 337 338 node = di_init(pwd_buf, DINFOMINOR); 339 free(devfs_path); 340 341 if (node) { 342 drv = di_driver_name(node); 343 di_fini(node); 344 } else { 345 return (0); 346 } 347 } else { 348 return (0); 349 } 350 351 (void) strcpy(saveline, line); 352 353 p = strstr(saveline, "driver"); 354 if (p == NULL) { 355 return (0); 356 } 357 358 driver = strtok_r(p, "=", &lasts); 359 if (driver) { 360 if (strcmp(driver, "driver") == 0) { 361 driver = strtok_r(NULL, ", \t\n", &lasts); 362 while (driver) { 363 if (strcmp(driver, drv) == 0) { 364 return (0); 365 } 366 driver = strtok_r(NULL, ", \t\n", &lasts); 367 } 368 } 369 } 370 371 return (-1); 372 } 373 374 /* 375 * Apply owner/group/perms to all files (except "." and "..") 376 * in a directory. 377 * This function is recursive. We start with "/" and the rest of the pathname 378 * in left_to_do argument, and we walk the entire pathname which may contain 379 * regular expressions or '*' for each directory name or basename. 380 */ 381 static int 382 dir_dev_acc(char *path, char *left_to_do, uid_t uid, gid_t gid, mode_t mode, 383 char *line, void (*errmsg)(char *)) 384 { 385 struct stat stat_buf; 386 int err = 0; 387 char errstring[MAX_LINELEN]; 388 char *p; 389 regex_t regex; 390 int alwaysmatch = 0; 391 char *match; 392 char *name, *newpath, *remainder_path; 393 finddevhdl_t handle; 394 395 /* 396 * Determine if the search needs to be performed via finddev, 397 * which returns only persisted names in the global /dev, or 398 * readdir, for paths other than /dev and non-global zones. 399 * This use of finddev avoids triggering potential implicit 400 * reconfig for names managed by logindevperm but not present 401 * on the system. 402 */ 403 if (!device_exists(path)) { 404 return (-1); 405 } 406 if (stat(path, &stat_buf) == -1) { 407 /* 408 * ENOENT errors are expected errors when there are 409 * dangling /dev device links. Ignore them silently 410 */ 411 if (errno == ENOENT) { 412 return (0); 413 } 414 if (errmsg) { 415 (void) snprintf(errstring, MAX_LINELEN, 416 "failed to stat %s: %s\n", path, 417 strerror(errno)); 418 (*errmsg)(errstring); 419 } 420 return (-1); 421 } else { 422 if (!S_ISDIR(stat_buf.st_mode)) { 423 if (strlen(left_to_do) == 0) { 424 /* finally check the driver matches */ 425 if (check_driver_match(path, line) == 0) { 426 /* we are done, set the permissions */ 427 if (setdevaccess(path, 428 uid, gid, mode, errmsg)) { 429 430 return (-1); 431 } 432 } 433 } 434 return (0); 435 } 436 } 437 438 if (finddev_readdir(path, &handle) != 0) 439 return (0); 440 441 p = strchr(left_to_do, '/'); 442 alwaysmatch = 0; 443 444 newpath = (char *)malloc(MAXPATHLEN); 445 if (newpath == NULL) { 446 finddev_close(handle); 447 return (-1); 448 } 449 match = (char *)calloc(MAXPATHLEN + 2, 1); 450 if (match == NULL) { 451 finddev_close(handle); 452 free(newpath); 453 return (-1); 454 } 455 456 /* transform pattern into ^pattern$ for exact match */ 457 if (snprintf(match, MAXPATHLEN + 2, "^%.*s$", 458 p ? (p - left_to_do) : strlen(left_to_do), left_to_do) >= 459 MAXPATHLEN + 2) { 460 finddev_close(handle); 461 free(newpath); 462 free(match); 463 return (-1); 464 } 465 466 if (strcmp(match, "^*$") == 0) { 467 alwaysmatch = 1; 468 } else { 469 if (regcomp(®ex, match, REG_EXTENDED) != 0) { 470 free(newpath); 471 free(match); 472 finddev_close(handle); 473 return (-1); 474 } 475 } 476 477 while ((name = (char *)finddev_next(handle)) != NULL) { 478 if (alwaysmatch || 479 regexec(®ex, name, 0, NULL, 0) == 0) { 480 if (strcmp(path, "/") == 0) { 481 (void) snprintf(newpath, 482 MAXPATHLEN, "%s%s", path, name); 483 } else { 484 (void) snprintf(newpath, 485 MAXPATHLEN, "%s/%s", path, name); 486 } 487 488 /* 489 * recurse but adjust what is still left to do 490 */ 491 remainder_path = (p ? 492 left_to_do + (p - left_to_do) + 1 : 493 &left_to_do[strlen(left_to_do)]); 494 if (dir_dev_acc(newpath, remainder_path, 495 uid, gid, mode, line, errmsg)) { 496 err = -1; 497 } 498 } 499 } 500 501 finddev_close(handle); 502 free(newpath); 503 free(match); 504 if (!alwaysmatch) { 505 regfree(®ex); 506 } 507 508 return (err); 509 } 510 511 /* 512 * di_devperm_login - modify access of devices in /etc/logindevperm 513 * by changing owner/group/permissions to that of ttyn. 514 */ 515 int 516 di_devperm_login(const char *ttyn, uid_t uid, gid_t gid, 517 void (*errmsg)(char *)) 518 { 519 int err; 520 struct group grp, *grpp; 521 gid_t tty_gid; 522 char grbuf[NSS_BUFLEN_GROUP]; 523 524 if (errmsg == NULL) 525 errmsg = logerror; 526 527 if (ttyn == NULL) { 528 (*errmsg)("di_devperm_login: NULL tty device\n"); 529 return (-1); 530 } 531 532 if (getgrnam_r("tty", &grp, grbuf, NSS_BUFLEN_GROUP, &grpp) != 0) { 533 tty_gid = grpp->gr_gid; 534 } else { 535 /* 536 * this should never happen, but if it does set 537 * group to tty's traditional value. 538 */ 539 tty_gid = 7; 540 } 541 542 /* set the login console device permission */ 543 err = setdevaccess((char *)ttyn, uid, tty_gid, 544 S_IRUSR|S_IWUSR|S_IWGRP, errmsg); 545 if (err) { 546 return (err); 547 } 548 549 /* set the device permissions */ 550 return (logindevperm(ttyn, uid, gid, errmsg)); 551 } 552 553 /* 554 * di_devperm_logout - clean up access of devices in /etc/logindevperm 555 * by resetting owner/group/permissions. 556 */ 557 int 558 di_devperm_logout(const char *ttyn) 559 { 560 struct passwd *pwd; 561 uid_t root_uid; 562 gid_t root_gid; 563 564 if (ttyn == NULL) 565 return (-1); 566 567 pwd = getpwnam("root"); 568 if (pwd != NULL) { 569 root_uid = pwd->pw_uid; 570 root_gid = pwd->pw_gid; 571 } else { 572 /* 573 * this should never happen, but if it does set user 574 * and group to root's traditional values. 575 */ 576 root_uid = 0; 577 root_gid = 0; 578 } 579 580 return (logindevperm(ttyn, root_uid, root_gid, NULL)); 581 } 582 583 static void 584 logerror(char *errstring) 585 { 586 syslog(LOG_AUTH | LOG_CRIT, "%s", errstring); 587 } 588 589 590 /* 591 * Tokens are separated by ' ', '\t', ':', '=', '&', '|', ';', '\n', or '\0' 592 */ 593 static int 594 getnexttoken(char *next, char **nextp, char **tokenpp, char *tchar) 595 { 596 char *cp; 597 char *cp1; 598 char *tokenp; 599 600 cp = next; 601 while (*cp == ' ' || *cp == '\t') { 602 cp++; /* skip leading spaces */ 603 } 604 tokenp = cp; /* start of token */ 605 while (*cp != '\0' && *cp != '\n' && *cp != ' ' && *cp != '\t' && 606 *cp != ':' && *cp != '=' && *cp != '&' && 607 *cp != '|' && *cp != ';') { 608 cp++; /* point to next character */ 609 } 610 /* 611 * If terminating character is a space or tab, look ahead to see if 612 * there's another terminator that's not a space or a tab. 613 * (This code handles trailing spaces.) 614 */ 615 if (*cp == ' ' || *cp == '\t') { 616 cp1 = cp; 617 while (*++cp1 == ' ' || *cp1 == '\t') 618 ; 619 if (*cp1 == '=' || *cp1 == ':' || *cp1 == '&' || *cp1 == '|' || 620 *cp1 == ';' || *cp1 == '\n' || *cp1 == '\0') { 621 *cp = NULL; /* terminate token */ 622 cp = cp1; 623 } 624 } 625 if (tchar != NULL) { 626 *tchar = *cp; /* save terminating character */ 627 if (*tchar == '\0') { 628 *tchar = '\n'; 629 } 630 } 631 *cp++ = '\0'; /* terminate token, point to next */ 632 *nextp = cp; /* set pointer to next character */ 633 if (cp - tokenp - 1 == 0) { 634 return (0); 635 } 636 *tokenpp = tokenp; 637 return (1); 638 } 639 640 /* 641 * get a decimal octal or hex number. Handle '~' for one's complement. 642 */ 643 static int 644 getvalue(char *token, int *valuep) 645 { 646 int radix; 647 int retval = 0; 648 int onescompl = 0; 649 int negate = 0; 650 char c; 651 652 if (*token == '~') { 653 onescompl++; /* perform one's complement on result */ 654 token++; 655 } else if (*token == '-') { 656 negate++; 657 token++; 658 } 659 if (*token == '0') { 660 token++; 661 c = *token; 662 663 if (c == '\0') { 664 *valuep = 0; /* value is 0 */ 665 return (0); 666 } 667 668 if (c == 'x' || c == 'X') { 669 radix = 16; 670 token++; 671 } else { 672 radix = 8; 673 } 674 } else 675 radix = 10; 676 677 while ((c = *token++)) { 678 switch (radix) { 679 case 8: 680 if (c >= '0' && c <= '7') { 681 c -= '0'; 682 } else { 683 /* invalid number */ 684 return (0); 685 } 686 retval = (retval << 3) + c; 687 break; 688 case 10: 689 if (c >= '0' && c <= '9') { 690 c -= '0'; 691 } else { 692 /* invalid number */ 693 return (0); 694 } 695 retval = (retval * 10) + c; 696 break; 697 case 16: 698 if (c >= 'a' && c <= 'f') { 699 c = c - 'a' + 10; 700 } else if (c >= 'A' && c <= 'F') { 701 c = c - 'A' + 10; 702 } else if (c >= '0' && c <= '9') { 703 c -= '0'; 704 } else { 705 /* invalid number */ 706 return (0); 707 } 708 retval = (retval << 4) + c; 709 break; 710 } 711 } 712 if (onescompl) { 713 retval = ~retval; 714 } 715 if (negate) { 716 retval = -retval; 717 } 718 *valuep = retval; 719 return (1); 720 } 721 722 /* 723 * Read /etc/minor_perm, return mperm list of entries 724 */ 725 struct mperm * 726 i_devfs_read_minor_perm(char *drvname, void (*errcb)(minorperm_err_t, int)) 727 { 728 FILE *pfd; 729 struct mperm *mp; 730 char line[MAX_MINOR_PERM_LINE]; 731 char *cp, *p, t; 732 struct mperm *minor_perms = NULL; 733 struct mperm *mptail = NULL; 734 struct passwd *pw; 735 struct group *gp; 736 uid_t root_uid; 737 gid_t sys_gid; 738 int ln = 0; 739 740 /* 741 * Get root/sys ids, these being the most common 742 */ 743 if ((pw = getpwnam(DEFAULT_DEV_USER)) != NULL) { 744 root_uid = pw->pw_uid; 745 } else { 746 (*errcb)(MP_CANT_FIND_USER_ERR, 0); 747 root_uid = (uid_t)0; /* assume 0 is root */ 748 } 749 if ((gp = getgrnam(DEFAULT_DEV_GROUP)) != NULL) { 750 sys_gid = gp->gr_gid; 751 } else { 752 (*errcb)(MP_CANT_FIND_GROUP_ERR, 0); 753 sys_gid = (gid_t)3; /* assume 3 is sys */ 754 } 755 756 if ((pfd = fopen(MINOR_PERM_FILE, "r")) == NULL) { 757 (*errcb)(MP_FOPEN_ERR, errno); 758 return (NULL); 759 } 760 while (fgets(line, MAX_MINOR_PERM_LINE, pfd) != NULL) { 761 ln++; 762 /* cut off comments starting with '#' */ 763 if ((cp = strchr(line, '#')) != NULL) 764 *cp = '\0'; 765 /* ignore comment or blank lines */ 766 if (is_blank(line)) 767 continue; 768 mp = (struct mperm *)calloc(1, sizeof (struct mperm)); 769 if (mp == NULL) { 770 (*errcb)(MP_ALLOC_ERR, sizeof (struct mperm)); 771 continue; 772 } 773 cp = line; 774 /* sanity-check */ 775 if (getnexttoken(cp, &cp, &p, &t) == 0) { 776 (*errcb)(MP_IGNORING_LINE_ERR, ln); 777 devfs_free_minor_perm(mp); 778 continue; 779 } 780 mp->mp_drvname = strdup(p); 781 if (mp->mp_drvname == NULL) { 782 (*errcb)(MP_ALLOC_ERR, strlen(p)+1); 783 devfs_free_minor_perm(mp); 784 continue; 785 } else if (t == '\n' || t == '\0') { 786 (*errcb)(MP_IGNORING_LINE_ERR, ln); 787 devfs_free_minor_perm(mp); 788 continue; 789 } 790 if (t == ':') { 791 if (getnexttoken(cp, &cp, &p, &t) == 0) { 792 (*errcb)(MP_IGNORING_LINE_ERR, ln); 793 devfs_free_minor_perm(mp); 794 } 795 mp->mp_minorname = strdup(p); 796 if (mp->mp_minorname == NULL) { 797 (*errcb)(MP_ALLOC_ERR, strlen(p)+1); 798 devfs_free_minor_perm(mp); 799 continue; 800 } 801 } else { 802 mp->mp_minorname = NULL; 803 } 804 805 if (t == '\n' || t == '\0') { 806 devfs_free_minor_perm(mp); 807 (*errcb)(MP_IGNORING_LINE_ERR, ln); 808 continue; 809 } 810 if (getnexttoken(cp, &cp, &p, &t) == 0) { 811 goto link; 812 } 813 if (getvalue(p, (int *)&mp->mp_mode) == 0) { 814 goto link; 815 } 816 if (t == '\n' || t == '\0') { /* no owner or group */ 817 goto link; 818 } 819 if (getnexttoken(cp, &cp, &p, &t) == 0) { 820 goto link; 821 } 822 mp->mp_owner = strdup(p); 823 if (mp->mp_owner == NULL) { 824 (*errcb)(MP_ALLOC_ERR, strlen(p)+1); 825 devfs_free_minor_perm(mp); 826 continue; 827 } else if (t == '\n' || t == '\0') { /* no group */ 828 goto link; 829 } 830 if (getnexttoken(cp, &cp, &p, 0) == 0) { 831 goto link; 832 } 833 mp->mp_group = strdup(p); 834 if (mp->mp_group == NULL) { 835 (*errcb)(MP_ALLOC_ERR, strlen(p)+1); 836 devfs_free_minor_perm(mp); 837 continue; 838 } 839 link: 840 if (drvname != NULL) { 841 /* 842 * We only want the minor perm entry for a 843 * the named driver. The driver name is the 844 * minor in the clone case. 845 */ 846 if (strcmp(mp->mp_drvname, "clone") == 0) { 847 if (mp->mp_minorname == NULL || 848 strcmp(drvname, mp->mp_minorname) != 0) { 849 devfs_free_minor_perm(mp); 850 continue; 851 } 852 } else { 853 if (strcmp(drvname, mp->mp_drvname) != 0) { 854 devfs_free_minor_perm(mp); 855 continue; 856 } 857 } 858 } 859 if (minor_perms == NULL) { 860 minor_perms = mp; 861 } else { 862 mptail->mp_next = mp; 863 } 864 mptail = mp; 865 866 /* 867 * Compute the uid's and gid's here - there are 868 * fewer lines in the /etc/minor_perm file than there 869 * are devices to be stat(2)ed. And almost every 870 * device is 'root sys'. See 1135520. 871 */ 872 if (mp->mp_owner == NULL || 873 strcmp(mp->mp_owner, DEFAULT_DEV_USER) == 0 || 874 (pw = getpwnam(mp->mp_owner)) == NULL) { 875 mp->mp_uid = root_uid; 876 } else { 877 mp->mp_uid = pw->pw_uid; 878 } 879 880 if (mp->mp_group == NULL || 881 strcmp(mp->mp_group, DEFAULT_DEV_GROUP) == 0 || 882 (gp = getgrnam(mp->mp_group)) == NULL) { 883 mp->mp_gid = sys_gid; 884 } else { 885 mp->mp_gid = gp->gr_gid; 886 } 887 } 888 889 if (fclose(pfd) == EOF) { 890 (*errcb)(MP_FCLOSE_ERR, errno); 891 } 892 893 return (minor_perms); 894 } 895 896 struct mperm * 897 devfs_read_minor_perm(void (*errcb)(minorperm_err_t, int)) 898 { 899 return (i_devfs_read_minor_perm(NULL, errcb)); 900 } 901 902 static struct mperm * 903 i_devfs_read_minor_perm_by_driver(char *drvname, 904 void (*errcb)(minorperm_err_t mp_err, int key)) 905 { 906 return (i_devfs_read_minor_perm(drvname, errcb)); 907 } 908 909 /* 910 * Free mperm list of entries 911 */ 912 void 913 devfs_free_minor_perm(struct mperm *mplist) 914 { 915 struct mperm *mp, *next; 916 917 for (mp = mplist; mp != NULL; mp = next) { 918 next = mp->mp_next; 919 920 if (mp->mp_drvname) 921 free(mp->mp_drvname); 922 if (mp->mp_minorname) 923 free(mp->mp_minorname); 924 if (mp->mp_owner) 925 free(mp->mp_owner); 926 if (mp->mp_group) 927 free(mp->mp_group); 928 free(mp); 929 } 930 } 931 932 static int 933 i_devfs_add_perm_entry(nvlist_t *nvl, struct mperm *mp) 934 { 935 int err; 936 937 err = nvlist_add_string(nvl, mp->mp_drvname, mp->mp_minorname); 938 if (err != 0) 939 return (err); 940 941 err = nvlist_add_int32(nvl, "mode", (int32_t)mp->mp_mode); 942 if (err != 0) 943 return (err); 944 945 err = nvlist_add_uint32(nvl, "uid", mp->mp_uid); 946 if (err != 0) 947 return (err); 948 949 err = nvlist_add_uint32(nvl, "gid", mp->mp_gid); 950 return (err); 951 } 952 953 static nvlist_t * 954 i_devfs_minor_perm_nvlist(struct mperm *mplist, 955 void (*errcb)(minorperm_err_t, int)) 956 { 957 int err; 958 struct mperm *mp; 959 nvlist_t *nvl = NULL; 960 961 if ((err = nvlist_alloc(&nvl, 0, 0)) != 0) { 962 (*errcb)(MP_NVLIST_ERR, err); 963 return (NULL); 964 } 965 966 for (mp = mplist; mp != NULL; mp = mp->mp_next) { 967 if ((err = i_devfs_add_perm_entry(nvl, mp)) != 0) { 968 (*errcb)(MP_NVLIST_ERR, err); 969 nvlist_free(nvl); 970 return (NULL); 971 } 972 } 973 974 return (nvl); 975 } 976 977 /* 978 * Load all minor perm entries into the kernel 979 * Done at boot time via devfsadm 980 */ 981 int 982 devfs_load_minor_perm(struct mperm *mplist, 983 void (*errcb)(minorperm_err_t, int)) 984 { 985 int err; 986 char *buf = NULL; 987 size_t buflen; 988 nvlist_t *nvl; 989 990 nvl = i_devfs_minor_perm_nvlist(mplist, errcb); 991 if (nvl == NULL) 992 return (-1); 993 994 if (nvlist_pack(nvl, &buf, &buflen, NV_ENCODE_NATIVE, 0) != 0) { 995 nvlist_free(nvl); 996 return (-1); 997 } 998 999 err = modctl(MODLOADMINORPERM, buf, buflen); 1000 nvlist_free(nvl); 1001 free(buf); 1002 1003 return (err); 1004 } 1005 1006 /* 1007 * Add/remove minor perm entry for a driver 1008 */ 1009 static int 1010 i_devfs_update_minor_perm(char *drv, int ctl, 1011 void (*errcb)(minorperm_err_t, int)) 1012 { 1013 int err; 1014 char *buf; 1015 size_t buflen; 1016 nvlist_t *nvl; 1017 struct mperm *mplist; 1018 1019 mplist = i_devfs_read_minor_perm_by_driver(drv, errcb); 1020 1021 nvl = i_devfs_minor_perm_nvlist(mplist, errcb); 1022 if (nvl == NULL) 1023 return (-1); 1024 1025 buf = NULL; 1026 if (nvlist_pack(nvl, &buf, &buflen, NV_ENCODE_NATIVE, 0) != 0) { 1027 nvlist_free(nvl); 1028 return (-1); 1029 } 1030 1031 err = modctl(ctl, buf, buflen); 1032 nvlist_free(nvl); 1033 devfs_free_minor_perm(mplist); 1034 free(buf); 1035 1036 return (err); 1037 } 1038 1039 int 1040 devfs_add_minor_perm(char *drv, 1041 void (*errcb)(minorperm_err_t, int)) 1042 { 1043 return (i_devfs_update_minor_perm(drv, MODADDMINORPERM, errcb)); 1044 } 1045 1046 int 1047 devfs_rm_minor_perm(char *drv, 1048 void (*errcb)(minorperm_err_t, int)) 1049 { 1050 return (i_devfs_update_minor_perm(drv, MODREMMINORPERM, errcb)); 1051 } 1052 1053 /* 1054 * is_blank() returns 1 (true) if a line specified is composed of 1055 * whitespace characters only. otherwise, it returns 0 (false). 1056 * 1057 * Note. the argument (line) must be null-terminated. 1058 */ 1059 static int 1060 is_blank(char *line) 1061 { 1062 for (/* nothing */; *line != '\0'; line++) 1063 if (!isspace(*line)) 1064 return (0); 1065 return (1); 1066 } 1067