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