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 2006 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 DIR *dirp; 388 struct dirent *direntp; 389 char errstring[MAX_LINELEN]; 390 char *p; 391 regex_t regex; 392 int alwaysmatch = 0; 393 char *match; 394 char *name, *newpath, *remainder_path; 395 finddevhdl_t handle; 396 int find_method; 397 398 /* 399 * Determine if the search needs to be performed via finddev, 400 * which returns only persisted names in the global /dev, or 401 * readdir, for paths other than /dev and non-global zones. 402 * This use of finddev avoids triggering potential implicit 403 * reconfig for names managed by logindevperm but not present 404 * on the system. 405 */ 406 find_method = ((getzoneid() == GLOBAL_ZONEID) && 407 ((strcmp(path, "/dev") == 0) || 408 (strncmp(path, "/dev/", 5) == 0))) ? 409 FLAG_USE_FINDDEV : FLAG_USE_READDIR; 410 411 /* path must be a valid name */ 412 if (find_method == FLAG_USE_FINDDEV && !device_exists(path)) { 413 return (-1); 414 } 415 if (stat(path, &stat_buf) == -1) { 416 /* 417 * ENOENT errors are expected errors when there are 418 * dangling /dev device links. Ignore them silently 419 */ 420 if (errno == ENOENT) { 421 return (0); 422 } 423 if (errmsg) { 424 (void) snprintf(errstring, MAX_LINELEN, 425 "failed to stat %s: %s\n", path, 426 strerror(errno)); 427 (*errmsg)(errstring); 428 } 429 return (-1); 430 } else { 431 if (!S_ISDIR(stat_buf.st_mode)) { 432 if (strlen(left_to_do) == 0) { 433 /* finally check the driver matches */ 434 if (check_driver_match(path, line) == 0) { 435 /* we are done, set the permissions */ 436 if (setdevaccess(path, 437 uid, gid, mode, errmsg)) { 438 439 return (-1); 440 } 441 } 442 } 443 return (0); 444 } 445 } 446 447 if (find_method == FLAG_USE_READDIR) { 448 dirp = opendir(path); 449 if (dirp == NULL) 450 return (0); 451 } else { 452 if (finddev_readdir(path, &handle) != 0) 453 return (0); 454 } 455 456 p = strchr(left_to_do, '/'); 457 alwaysmatch = 0; 458 459 newpath = (char *)malloc(MAXPATHLEN); 460 if (newpath == NULL) { 461 return (-1); 462 } 463 match = (char *)calloc(MAXPATHLEN, 1); 464 if (match == NULL) { 465 free(newpath); 466 return (-1); 467 } 468 469 if (p) { 470 (void) strncpy(match, left_to_do, p - left_to_do); 471 } else { 472 (void) strcpy(match, left_to_do); 473 } 474 475 if (strcmp(match, "*") == 0) { 476 alwaysmatch = 1; 477 } else { 478 if (regcomp(®ex, match, REG_EXTENDED) != 0) { 479 free(newpath); 480 free(match); 481 return (-1); 482 } 483 } 484 485 for (;;) { 486 if (find_method == FLAG_USE_READDIR) { 487 if ((direntp = readdir(dirp)) == NULL) 488 break; 489 name = direntp->d_name; 490 if ((strcmp(name, ".") == 0) || 491 (strcmp(name, "..") == 0)) 492 continue; 493 } else { 494 if ((name = (char *)finddev_next(handle)) == NULL) 495 break; 496 } 497 498 if (alwaysmatch || 499 regexec(®ex, name, 0, NULL, 0) == 0) { 500 if (strcmp(path, "/") == 0) { 501 (void) snprintf(newpath, 502 MAXPATHLEN, "%s%s", path, name); 503 } else { 504 (void) snprintf(newpath, 505 MAXPATHLEN, "%s/%s", path, name); 506 } 507 508 /* 509 * recurse but adjust what is still left to do 510 */ 511 remainder_path = (p ? 512 left_to_do + (p - left_to_do) + 1 : 513 &left_to_do[strlen(left_to_do)]); 514 if (dir_dev_acc(newpath, remainder_path, 515 uid, gid, mode, line, errmsg)) { 516 err = -1; 517 } 518 } 519 } 520 521 if (find_method == FLAG_USE_READDIR) { 522 (void) closedir(dirp); 523 } else { 524 finddev_close(handle); 525 } 526 free(newpath); 527 free(match); 528 if (!alwaysmatch) { 529 regfree(®ex); 530 } 531 532 return (err); 533 } 534 535 /* 536 * di_devperm_login - modify access of devices in /etc/logindevperm 537 * by changing owner/group/permissions to that of ttyn. 538 */ 539 int 540 di_devperm_login(const char *ttyn, uid_t uid, gid_t gid, 541 void (*errmsg)(char *)) 542 { 543 int err; 544 struct group grp, *grpp; 545 gid_t tty_gid; 546 char grbuf[NSS_BUFLEN_GROUP]; 547 548 if (errmsg == NULL) 549 errmsg = logerror; 550 551 if (ttyn == NULL) { 552 (*errmsg)("di_devperm_login: NULL tty device\n"); 553 return (-1); 554 } 555 556 if (getgrnam_r("tty", &grp, grbuf, NSS_BUFLEN_GROUP, &grpp) != 0) { 557 tty_gid = grpp->gr_gid; 558 } else { 559 /* 560 * this should never happen, but if it does set 561 * group to tty's traditional value. 562 */ 563 tty_gid = 7; 564 } 565 566 /* set the login console device permission */ 567 err = setdevaccess((char *)ttyn, uid, tty_gid, 568 S_IRUSR|S_IWUSR|S_IWGRP, errmsg); 569 if (err) { 570 return (err); 571 } 572 573 /* set the device permissions */ 574 return (logindevperm(ttyn, uid, gid, errmsg)); 575 } 576 577 /* 578 * di_devperm_logout - clean up access of devices in /etc/logindevperm 579 * by resetting owner/group/permissions. 580 */ 581 int 582 di_devperm_logout(const char *ttyn) 583 { 584 struct passwd *pwd; 585 uid_t root_uid; 586 gid_t root_gid; 587 588 if (ttyn == NULL) 589 return (-1); 590 591 pwd = getpwnam("root"); 592 if (pwd != NULL) { 593 root_uid = pwd->pw_uid; 594 root_gid = pwd->pw_gid; 595 } else { 596 /* 597 * this should never happen, but if it does set user 598 * and group to root's traditional values. 599 */ 600 root_uid = 0; 601 root_gid = 0; 602 } 603 604 return (logindevperm(ttyn, root_uid, root_gid, NULL)); 605 } 606 607 static void 608 logerror(char *errstring) 609 { 610 syslog(LOG_AUTH | LOG_CRIT, "%s", errstring); 611 } 612 613 614 /* 615 * Tokens are separated by ' ', '\t', ':', '=', '&', '|', ';', '\n', or '\0' 616 */ 617 static int 618 getnexttoken(char *next, char **nextp, char **tokenpp, char *tchar) 619 { 620 char *cp; 621 char *cp1; 622 char *tokenp; 623 624 cp = next; 625 while (*cp == ' ' || *cp == '\t') { 626 cp++; /* skip leading spaces */ 627 } 628 tokenp = cp; /* start of token */ 629 while (*cp != '\0' && *cp != '\n' && *cp != ' ' && *cp != '\t' && 630 *cp != ':' && *cp != '=' && *cp != '&' && 631 *cp != '|' && *cp != ';') { 632 cp++; /* point to next character */ 633 } 634 /* 635 * If terminating character is a space or tab, look ahead to see if 636 * there's another terminator that's not a space or a tab. 637 * (This code handles trailing spaces.) 638 */ 639 if (*cp == ' ' || *cp == '\t') { 640 cp1 = cp; 641 while (*++cp1 == ' ' || *cp1 == '\t') 642 ; 643 if (*cp1 == '=' || *cp1 == ':' || *cp1 == '&' || *cp1 == '|' || 644 *cp1 == ';' || *cp1 == '\n' || *cp1 == '\0') { 645 *cp = NULL; /* terminate token */ 646 cp = cp1; 647 } 648 } 649 if (tchar != NULL) { 650 *tchar = *cp; /* save terminating character */ 651 if (*tchar == '\0') { 652 *tchar = '\n'; 653 } 654 } 655 *cp++ = '\0'; /* terminate token, point to next */ 656 *nextp = cp; /* set pointer to next character */ 657 if (cp - tokenp - 1 == 0) { 658 return (0); 659 } 660 *tokenpp = tokenp; 661 return (1); 662 } 663 664 /* 665 * get a decimal octal or hex number. Handle '~' for one's complement. 666 */ 667 static int 668 getvalue(char *token, int *valuep) 669 { 670 int radix; 671 int retval = 0; 672 int onescompl = 0; 673 int negate = 0; 674 char c; 675 676 if (*token == '~') { 677 onescompl++; /* perform one's complement on result */ 678 token++; 679 } else if (*token == '-') { 680 negate++; 681 token++; 682 } 683 if (*token == '0') { 684 token++; 685 c = *token; 686 687 if (c == '\0') { 688 *valuep = 0; /* value is 0 */ 689 return (0); 690 } 691 692 if (c == 'x' || c == 'X') { 693 radix = 16; 694 token++; 695 } else { 696 radix = 8; 697 } 698 } else 699 radix = 10; 700 701 while ((c = *token++)) { 702 switch (radix) { 703 case 8: 704 if (c >= '0' && c <= '7') { 705 c -= '0'; 706 } else { 707 /* invalid number */ 708 return (0); 709 } 710 retval = (retval << 3) + c; 711 break; 712 case 10: 713 if (c >= '0' && c <= '9') { 714 c -= '0'; 715 } else { 716 /* invalid number */ 717 return (0); 718 } 719 retval = (retval * 10) + c; 720 break; 721 case 16: 722 if (c >= 'a' && c <= 'f') { 723 c = c - 'a' + 10; 724 } else if (c >= 'A' && c <= 'F') { 725 c = c - 'A' + 10; 726 } else if (c >= '0' && c <= '9') { 727 c -= '0'; 728 } else { 729 /* invalid number */ 730 return (0); 731 } 732 retval = (retval << 4) + c; 733 break; 734 } 735 } 736 if (onescompl) { 737 retval = ~retval; 738 } 739 if (negate) { 740 retval = -retval; 741 } 742 *valuep = retval; 743 return (1); 744 } 745 746 /* 747 * Read /etc/minor_perm, return mperm list of entries 748 */ 749 struct mperm * 750 i_devfs_read_minor_perm(char *drvname, void (*errcb)(minorperm_err_t, int)) 751 { 752 FILE *pfd; 753 struct mperm *mp; 754 char line[MAX_MINOR_PERM_LINE]; 755 char *cp, *p, t; 756 struct mperm *minor_perms = NULL; 757 struct mperm *mptail = NULL; 758 struct passwd *pw; 759 struct group *gp; 760 uid_t root_uid; 761 gid_t sys_gid; 762 int ln = 0; 763 764 /* 765 * Get root/sys ids, these being the most common 766 */ 767 if ((pw = getpwnam(DEFAULT_DEV_USER)) != NULL) { 768 root_uid = pw->pw_uid; 769 } else { 770 (*errcb)(MP_CANT_FIND_USER_ERR, 0); 771 root_uid = (uid_t)0; /* assume 0 is root */ 772 } 773 if ((gp = getgrnam(DEFAULT_DEV_GROUP)) != NULL) { 774 sys_gid = gp->gr_gid; 775 } else { 776 (*errcb)(MP_CANT_FIND_GROUP_ERR, 0); 777 sys_gid = (gid_t)3; /* assume 3 is sys */ 778 } 779 780 if ((pfd = fopen(MINOR_PERM_FILE, "r")) == NULL) { 781 (*errcb)(MP_FOPEN_ERR, errno); 782 return (NULL); 783 } 784 while (fgets(line, MAX_MINOR_PERM_LINE, pfd) != NULL) { 785 ln++; 786 /* cut off comments starting with '#' */ 787 if ((cp = strchr(line, '#')) != NULL) 788 *cp = '\0'; 789 /* ignore comment or blank lines */ 790 if (is_blank(line)) 791 continue; 792 mp = (struct mperm *)calloc(1, sizeof (struct mperm)); 793 if (mp == NULL) { 794 (*errcb)(MP_ALLOC_ERR, sizeof (struct mperm)); 795 continue; 796 } 797 cp = line; 798 /* sanity-check */ 799 if (getnexttoken(cp, &cp, &p, &t) == 0) { 800 (*errcb)(MP_IGNORING_LINE_ERR, ln); 801 devfs_free_minor_perm(mp); 802 continue; 803 } 804 mp->mp_drvname = strdup(p); 805 if (mp->mp_drvname == NULL) { 806 (*errcb)(MP_ALLOC_ERR, strlen(p)+1); 807 devfs_free_minor_perm(mp); 808 continue; 809 } else if (t == '\n' || t == '\0') { 810 (*errcb)(MP_IGNORING_LINE_ERR, ln); 811 devfs_free_minor_perm(mp); 812 continue; 813 } 814 if (t == ':') { 815 if (getnexttoken(cp, &cp, &p, &t) == 0) { 816 (*errcb)(MP_IGNORING_LINE_ERR, ln); 817 devfs_free_minor_perm(mp); 818 } 819 mp->mp_minorname = strdup(p); 820 if (mp->mp_minorname == NULL) { 821 (*errcb)(MP_ALLOC_ERR, strlen(p)+1); 822 devfs_free_minor_perm(mp); 823 continue; 824 } 825 } else { 826 mp->mp_minorname = NULL; 827 } 828 829 if (t == '\n' || t == '\0') { 830 devfs_free_minor_perm(mp); 831 (*errcb)(MP_IGNORING_LINE_ERR, ln); 832 continue; 833 } 834 if (getnexttoken(cp, &cp, &p, &t) == 0) { 835 goto link; 836 } 837 if (getvalue(p, (int *)&mp->mp_mode) == 0) { 838 goto link; 839 } 840 if (t == '\n' || t == '\0') { /* no owner or group */ 841 goto link; 842 } 843 if (getnexttoken(cp, &cp, &p, &t) == 0) { 844 goto link; 845 } 846 mp->mp_owner = strdup(p); 847 if (mp->mp_owner == NULL) { 848 (*errcb)(MP_ALLOC_ERR, strlen(p)+1); 849 devfs_free_minor_perm(mp); 850 continue; 851 } else if (t == '\n' || t == '\0') { /* no group */ 852 goto link; 853 } 854 if (getnexttoken(cp, &cp, &p, 0) == 0) { 855 goto link; 856 } 857 mp->mp_group = strdup(p); 858 if (mp->mp_group == NULL) { 859 (*errcb)(MP_ALLOC_ERR, strlen(p)+1); 860 devfs_free_minor_perm(mp); 861 continue; 862 } 863 link: 864 if (drvname != NULL) { 865 /* 866 * We only want the minor perm entry for a 867 * the named driver. The driver name is the 868 * minor in the clone case. 869 */ 870 if (strcmp(mp->mp_drvname, "clone") == 0) { 871 if (mp->mp_minorname == NULL || 872 strcmp(drvname, mp->mp_minorname) != 0) { 873 devfs_free_minor_perm(mp); 874 continue; 875 } 876 } else { 877 if (strcmp(drvname, mp->mp_drvname) != 0) { 878 devfs_free_minor_perm(mp); 879 continue; 880 } 881 } 882 } 883 if (minor_perms == NULL) { 884 minor_perms = mp; 885 } else { 886 mptail->mp_next = mp; 887 } 888 mptail = mp; 889 890 /* 891 * Compute the uid's and gid's here - there are 892 * fewer lines in the /etc/minor_perm file than there 893 * are devices to be stat(2)ed. And almost every 894 * device is 'root sys'. See 1135520. 895 */ 896 if (mp->mp_owner == NULL || 897 strcmp(mp->mp_owner, DEFAULT_DEV_USER) == 0 || 898 (pw = getpwnam(mp->mp_owner)) == NULL) { 899 mp->mp_uid = root_uid; 900 } else { 901 mp->mp_uid = pw->pw_uid; 902 } 903 904 if (mp->mp_group == NULL || 905 strcmp(mp->mp_group, DEFAULT_DEV_GROUP) == 0 || 906 (gp = getgrnam(mp->mp_group)) == NULL) { 907 mp->mp_gid = sys_gid; 908 } else { 909 mp->mp_gid = gp->gr_gid; 910 } 911 } 912 913 if (fclose(pfd) == EOF) { 914 (*errcb)(MP_FCLOSE_ERR, errno); 915 } 916 917 return (minor_perms); 918 } 919 920 struct mperm * 921 devfs_read_minor_perm(void (*errcb)(minorperm_err_t, int)) 922 { 923 return (i_devfs_read_minor_perm(NULL, errcb)); 924 } 925 926 static struct mperm * 927 i_devfs_read_minor_perm_by_driver(char *drvname, 928 void (*errcb)(minorperm_err_t mp_err, int key)) 929 { 930 return (i_devfs_read_minor_perm(drvname, errcb)); 931 } 932 933 /* 934 * Free mperm list of entries 935 */ 936 void 937 devfs_free_minor_perm(struct mperm *mplist) 938 { 939 struct mperm *mp, *next; 940 941 for (mp = mplist; mp != NULL; mp = next) { 942 next = mp->mp_next; 943 944 if (mp->mp_drvname) 945 free(mp->mp_drvname); 946 if (mp->mp_minorname) 947 free(mp->mp_minorname); 948 if (mp->mp_owner) 949 free(mp->mp_owner); 950 if (mp->mp_group) 951 free(mp->mp_group); 952 free(mp); 953 } 954 } 955 956 static int 957 i_devfs_add_perm_entry(nvlist_t *nvl, struct mperm *mp) 958 { 959 int err; 960 961 err = nvlist_add_string(nvl, mp->mp_drvname, mp->mp_minorname); 962 if (err != 0) 963 return (err); 964 965 err = nvlist_add_int32(nvl, "mode", (int32_t)mp->mp_mode); 966 if (err != 0) 967 return (err); 968 969 err = nvlist_add_int32(nvl, "uid", (int32_t)mp->mp_uid); 970 if (err != 0) 971 return (err); 972 973 err = nvlist_add_int32(nvl, "gid", (int32_t)mp->mp_gid); 974 return (err); 975 } 976 977 static nvlist_t * 978 i_devfs_minor_perm_nvlist(struct mperm *mplist, 979 void (*errcb)(minorperm_err_t, int)) 980 { 981 int err; 982 struct mperm *mp; 983 nvlist_t *nvl = NULL; 984 985 if ((err = nvlist_alloc(&nvl, 0, 0)) != 0) { 986 (*errcb)(MP_NVLIST_ERR, err); 987 return (NULL); 988 } 989 990 for (mp = mplist; mp != NULL; mp = mp->mp_next) { 991 if ((err = i_devfs_add_perm_entry(nvl, mp)) != 0) { 992 (*errcb)(MP_NVLIST_ERR, err); 993 nvlist_free(nvl); 994 return (NULL); 995 } 996 } 997 998 return (nvl); 999 } 1000 1001 /* 1002 * Load all minor perm entries into the kernel 1003 * Done at boot time via devfsadm 1004 */ 1005 int 1006 devfs_load_minor_perm(struct mperm *mplist, 1007 void (*errcb)(minorperm_err_t, int)) 1008 { 1009 int err; 1010 char *buf = NULL; 1011 size_t buflen; 1012 nvlist_t *nvl; 1013 1014 nvl = i_devfs_minor_perm_nvlist(mplist, errcb); 1015 if (nvl == NULL) 1016 return (-1); 1017 1018 if (nvlist_pack(nvl, &buf, &buflen, NV_ENCODE_NATIVE, 0) != 0) { 1019 nvlist_free(nvl); 1020 return (-1); 1021 } 1022 1023 err = modctl(MODLOADMINORPERM, buf, buflen); 1024 nvlist_free(nvl); 1025 free(buf); 1026 1027 return (err); 1028 } 1029 1030 /* 1031 * Add/remove minor perm entry for a driver 1032 */ 1033 static int 1034 i_devfs_update_minor_perm(char *drv, int ctl, 1035 void (*errcb)(minorperm_err_t, int)) 1036 { 1037 int err; 1038 char *buf; 1039 size_t buflen; 1040 nvlist_t *nvl; 1041 struct mperm *mplist; 1042 1043 mplist = i_devfs_read_minor_perm_by_driver(drv, errcb); 1044 1045 nvl = i_devfs_minor_perm_nvlist(mplist, errcb); 1046 if (nvl == NULL) 1047 return (-1); 1048 1049 buf = NULL; 1050 if (nvlist_pack(nvl, &buf, &buflen, NV_ENCODE_NATIVE, 0) != 0) { 1051 nvlist_free(nvl); 1052 return (-1); 1053 } 1054 1055 err = modctl(ctl, buf, buflen); 1056 nvlist_free(nvl); 1057 devfs_free_minor_perm(mplist); 1058 free(buf); 1059 1060 return (err); 1061 } 1062 1063 int 1064 devfs_add_minor_perm(char *drv, 1065 void (*errcb)(minorperm_err_t, int)) 1066 { 1067 return (i_devfs_update_minor_perm(drv, MODADDMINORPERM, errcb)); 1068 } 1069 1070 int 1071 devfs_rm_minor_perm(char *drv, 1072 void (*errcb)(minorperm_err_t, int)) 1073 { 1074 return (i_devfs_update_minor_perm(drv, MODREMMINORPERM, errcb)); 1075 } 1076 1077 /* 1078 * is_blank() returns 1 (true) if a line specified is composed of 1079 * whitespace characters only. otherwise, it returns 0 (false). 1080 * 1081 * Note. the argument (line) must be null-terminated. 1082 */ 1083 static int 1084 is_blank(char *line) 1085 { 1086 for (/* nothing */; *line != '\0'; line++) 1087 if (!isspace(*line)) 1088 return (0); 1089 return (1); 1090 } 1091