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