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