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