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