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