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