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