xref: /titanic_52/usr/src/lib/libdevinfo/devinfo_devperm.c (revision 2cb27123907a098a777e39eebc349d73e99a518f)
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, 1);
450 	if (match == NULL) {
451 		finddev_close(handle);
452 		free(newpath);
453 		return (-1);
454 	}
455 
456 	if (p) {
457 		(void) strncpy(match, left_to_do, p - left_to_do);
458 	} else {
459 		(void) strcpy(match, left_to_do);
460 	}
461 
462 	if (strcmp(match, "*") == 0) {
463 		alwaysmatch = 1;
464 	} else {
465 		if (regcomp(&regex, match, REG_EXTENDED) != 0) {
466 			free(newpath);
467 			free(match);
468 			finddev_close(handle);
469 			return (-1);
470 		}
471 	}
472 
473 	while ((name = (char *)finddev_next(handle)) != NULL) {
474 		if (alwaysmatch ||
475 		    regexec(&regex, name, 0, NULL, 0) == 0) {
476 			if (strcmp(path, "/") == 0) {
477 				(void) snprintf(newpath,
478 				    MAXPATHLEN, "%s%s", path, name);
479 			} else {
480 				(void) snprintf(newpath,
481 				    MAXPATHLEN, "%s/%s", path, name);
482 			}
483 
484 			/*
485 			 * recurse but adjust what is still left to do
486 			 */
487 			remainder_path = (p ?
488 			    left_to_do + (p - left_to_do) + 1 :
489 			    &left_to_do[strlen(left_to_do)]);
490 			if (dir_dev_acc(newpath, remainder_path,
491 			    uid, gid, mode, line, errmsg)) {
492 				err = -1;
493 			}
494 		}
495 	}
496 
497 	finddev_close(handle);
498 	free(newpath);
499 	free(match);
500 	if (!alwaysmatch) {
501 		regfree(&regex);
502 	}
503 
504 	return (err);
505 }
506 
507 /*
508  * di_devperm_login - modify access of devices in /etc/logindevperm
509  * by changing owner/group/permissions to that of ttyn.
510  */
511 int
512 di_devperm_login(const char *ttyn, uid_t uid, gid_t gid,
513     void (*errmsg)(char *))
514 {
515 	int err;
516 	struct group grp, *grpp;
517 	gid_t tty_gid;
518 	char grbuf[NSS_BUFLEN_GROUP];
519 
520 	if (errmsg == NULL)
521 		errmsg = logerror;
522 
523 	if (ttyn == NULL) {
524 		(*errmsg)("di_devperm_login: NULL tty device\n");
525 		return (-1);
526 	}
527 
528 	if (getgrnam_r("tty", &grp, grbuf, NSS_BUFLEN_GROUP, &grpp) != 0) {
529 		tty_gid = grpp->gr_gid;
530 	} else {
531 		/*
532 		 * this should never happen, but if it does set
533 		 * group to tty's traditional value.
534 		 */
535 		tty_gid = 7;
536 	}
537 
538 	/* set the login console device permission */
539 	err = setdevaccess((char *)ttyn, uid, tty_gid,
540 	    S_IRUSR|S_IWUSR|S_IWGRP, errmsg);
541 	if (err) {
542 		return (err);
543 	}
544 
545 	/* set the device permissions */
546 	return (logindevperm(ttyn, uid, gid, errmsg));
547 }
548 
549 /*
550  * di_devperm_logout - clean up access of devices in /etc/logindevperm
551  * by resetting owner/group/permissions.
552  */
553 int
554 di_devperm_logout(const char *ttyn)
555 {
556 	struct passwd *pwd;
557 	uid_t root_uid;
558 	gid_t root_gid;
559 
560 	if (ttyn == NULL)
561 		return (-1);
562 
563 	pwd = getpwnam("root");
564 	if (pwd != NULL) {
565 		root_uid = pwd->pw_uid;
566 		root_gid = pwd->pw_gid;
567 	} else {
568 		/*
569 		 * this should never happen, but if it does set user
570 		 * and group to root's traditional values.
571 		 */
572 		root_uid = 0;
573 		root_gid = 0;
574 	}
575 
576 	return (logindevperm(ttyn, root_uid, root_gid, NULL));
577 }
578 
579 static void
580 logerror(char *errstring)
581 {
582 	syslog(LOG_AUTH | LOG_CRIT, "%s", errstring);
583 }
584 
585 
586 /*
587  * Tokens are separated by ' ', '\t', ':', '=', '&', '|', ';', '\n', or '\0'
588  */
589 static int
590 getnexttoken(char *next, char **nextp, char **tokenpp, char *tchar)
591 {
592 	char *cp;
593 	char *cp1;
594 	char *tokenp;
595 
596 	cp = next;
597 	while (*cp == ' ' || *cp == '\t') {
598 		cp++;			/* skip leading spaces */
599 	}
600 	tokenp = cp;			/* start of token */
601 	while (*cp != '\0' && *cp != '\n' && *cp != ' ' && *cp != '\t' &&
602 		*cp != ':' && *cp != '=' && *cp != '&' &&
603 		*cp != '|' && *cp != ';') {
604 		cp++;			/* point to next character */
605 	}
606 	/*
607 	 * If terminating character is a space or tab, look ahead to see if
608 	 * there's another terminator that's not a space or a tab.
609 	 * (This code handles trailing spaces.)
610 	 */
611 	if (*cp == ' ' || *cp == '\t') {
612 		cp1 = cp;
613 		while (*++cp1 == ' ' || *cp1 == '\t')
614 			;
615 		if (*cp1 == '=' || *cp1 == ':' || *cp1 == '&' || *cp1 == '|' ||
616 			*cp1 == ';' || *cp1 == '\n' || *cp1 == '\0') {
617 			*cp = NULL;	/* terminate token */
618 			cp = cp1;
619 		}
620 	}
621 	if (tchar != NULL) {
622 		*tchar = *cp;		/* save terminating character */
623 		if (*tchar == '\0') {
624 			*tchar = '\n';
625 		}
626 	}
627 	*cp++ = '\0';			/* terminate token, point to next */
628 	*nextp = cp;			/* set pointer to next character */
629 	if (cp - tokenp - 1 == 0) {
630 		return (0);
631 	}
632 	*tokenpp = tokenp;
633 	return (1);
634 }
635 
636 /*
637  * get a decimal octal or hex number. Handle '~' for one's complement.
638  */
639 static int
640 getvalue(char *token, int *valuep)
641 {
642 	int radix;
643 	int retval = 0;
644 	int onescompl = 0;
645 	int negate = 0;
646 	char c;
647 
648 	if (*token == '~') {
649 		onescompl++; /* perform one's complement on result */
650 		token++;
651 	} else if (*token == '-') {
652 		negate++;
653 		token++;
654 	}
655 	if (*token == '0') {
656 		token++;
657 		c = *token;
658 
659 		if (c == '\0') {
660 			*valuep = 0;	/* value is 0 */
661 			return (0);
662 		}
663 
664 		if (c == 'x' || c == 'X') {
665 			radix = 16;
666 			token++;
667 		} else {
668 			radix = 8;
669 		}
670 	} else
671 		radix = 10;
672 
673 	while ((c = *token++)) {
674 		switch (radix) {
675 		case 8:
676 			if (c >= '0' && c <= '7') {
677 				c -= '0';
678 			} else {
679 				/* invalid number */
680 				return (0);
681 			}
682 			retval = (retval << 3) + c;
683 			break;
684 		case 10:
685 			if (c >= '0' && c <= '9') {
686 				c -= '0';
687 			} else {
688 				/* invalid number */
689 				return (0);
690 			}
691 			retval = (retval * 10) + c;
692 			break;
693 		case 16:
694 			if (c >= 'a' && c <= 'f') {
695 				c = c - 'a' + 10;
696 			} else if (c >= 'A' && c <= 'F') {
697 				c = c - 'A' + 10;
698 			} else if (c >= '0' && c <= '9') {
699 				c -= '0';
700 			} else {
701 				/* invalid number */
702 				return (0);
703 			}
704 			retval = (retval << 4) + c;
705 			break;
706 		}
707 	}
708 	if (onescompl) {
709 		retval = ~retval;
710 	}
711 	if (negate) {
712 		retval = -retval;
713 	}
714 	*valuep = retval;
715 	return (1);
716 }
717 
718 /*
719  * Read /etc/minor_perm, return mperm list of entries
720  */
721 struct mperm *
722 i_devfs_read_minor_perm(char *drvname, void (*errcb)(minorperm_err_t, int))
723 {
724 	FILE *pfd;
725 	struct mperm *mp;
726 	char line[MAX_MINOR_PERM_LINE];
727 	char *cp, *p, t;
728 	struct mperm *minor_perms = NULL;
729 	struct mperm *mptail = NULL;
730 	struct passwd *pw;
731 	struct group *gp;
732 	uid_t root_uid;
733 	gid_t sys_gid;
734 	int ln = 0;
735 
736 	/*
737 	 * Get root/sys ids, these being the most common
738 	 */
739 	if ((pw = getpwnam(DEFAULT_DEV_USER)) != NULL) {
740 		root_uid = pw->pw_uid;
741 	} else {
742 		(*errcb)(MP_CANT_FIND_USER_ERR, 0);
743 		root_uid = (uid_t)0;	/* assume 0 is root */
744 	}
745 	if ((gp = getgrnam(DEFAULT_DEV_GROUP)) != NULL) {
746 		sys_gid = gp->gr_gid;
747 	} else {
748 		(*errcb)(MP_CANT_FIND_GROUP_ERR, 0);
749 		sys_gid = (gid_t)3;	/* assume 3 is sys */
750 	}
751 
752 	if ((pfd = fopen(MINOR_PERM_FILE, "r")) == NULL) {
753 		(*errcb)(MP_FOPEN_ERR, errno);
754 		return (NULL);
755 	}
756 	while (fgets(line, MAX_MINOR_PERM_LINE, pfd) != NULL) {
757 		ln++;
758 		/* cut off comments starting with '#' */
759 		if ((cp = strchr(line, '#')) != NULL)
760 			*cp = '\0';
761 		/* ignore comment or blank lines */
762 		if (is_blank(line))
763 			continue;
764 		mp = (struct mperm *)calloc(1, sizeof (struct mperm));
765 		if (mp == NULL) {
766 			(*errcb)(MP_ALLOC_ERR, sizeof (struct mperm));
767 			continue;
768 		}
769 		cp = line;
770 		/* sanity-check */
771 		if (getnexttoken(cp, &cp, &p, &t) == 0) {
772 			(*errcb)(MP_IGNORING_LINE_ERR, ln);
773 			devfs_free_minor_perm(mp);
774 			continue;
775 		}
776 		mp->mp_drvname = strdup(p);
777 		if (mp->mp_drvname == NULL) {
778 			(*errcb)(MP_ALLOC_ERR, strlen(p)+1);
779 			devfs_free_minor_perm(mp);
780 			continue;
781 		} else if (t == '\n' || t == '\0') {
782 			(*errcb)(MP_IGNORING_LINE_ERR, ln);
783 			devfs_free_minor_perm(mp);
784 			continue;
785 		}
786 		if (t == ':') {
787 			if (getnexttoken(cp, &cp, &p, &t) == 0) {
788 				(*errcb)(MP_IGNORING_LINE_ERR, ln);
789 				devfs_free_minor_perm(mp);
790 			}
791 			mp->mp_minorname = strdup(p);
792 			if (mp->mp_minorname == NULL) {
793 				(*errcb)(MP_ALLOC_ERR, strlen(p)+1);
794 				devfs_free_minor_perm(mp);
795 				continue;
796 			}
797 		} else {
798 			mp->mp_minorname = NULL;
799 		}
800 
801 		if (t == '\n' || t == '\0') {
802 			devfs_free_minor_perm(mp);
803 			(*errcb)(MP_IGNORING_LINE_ERR, ln);
804 			continue;
805 		}
806 		if (getnexttoken(cp, &cp, &p, &t) == 0) {
807 			goto link;
808 		}
809 		if (getvalue(p, (int *)&mp->mp_mode) == 0) {
810 			goto link;
811 		}
812 		if (t == '\n' || t == '\0') {	/* no owner or group */
813 			goto link;
814 		}
815 		if (getnexttoken(cp, &cp, &p, &t) == 0) {
816 			goto link;
817 		}
818 		mp->mp_owner = strdup(p);
819 		if (mp->mp_owner == NULL) {
820 			(*errcb)(MP_ALLOC_ERR, strlen(p)+1);
821 			devfs_free_minor_perm(mp);
822 			continue;
823 		} else if (t == '\n' || t == '\0') {	/* no group */
824 			goto link;
825 		}
826 		if (getnexttoken(cp, &cp, &p, 0) == 0) {
827 			goto link;
828 		}
829 		mp->mp_group = strdup(p);
830 		if (mp->mp_group == NULL) {
831 			(*errcb)(MP_ALLOC_ERR, strlen(p)+1);
832 			devfs_free_minor_perm(mp);
833 			continue;
834 		}
835 link:
836 		if (drvname != NULL) {
837 			/*
838 			 * We only want the minor perm entry for a
839 			 * the named driver.  The driver name is the
840 			 * minor in the clone case.
841 			 */
842 			if (strcmp(mp->mp_drvname, "clone") == 0) {
843 				if (mp->mp_minorname == NULL ||
844 				    strcmp(drvname, mp->mp_minorname) != 0) {
845 					devfs_free_minor_perm(mp);
846 					continue;
847 				}
848 			} else {
849 				if (strcmp(drvname, mp->mp_drvname) != 0) {
850 					devfs_free_minor_perm(mp);
851 					continue;
852 				}
853 			}
854 		}
855 		if (minor_perms == NULL) {
856 			minor_perms = mp;
857 		} else {
858 			mptail->mp_next = mp;
859 		}
860 		mptail = mp;
861 
862 		/*
863 		 * Compute the uid's and gid's here - there are
864 		 * fewer lines in the /etc/minor_perm file than there
865 		 * are devices to be stat(2)ed.  And almost every
866 		 * device is 'root sys'.  See 1135520.
867 		 */
868 		if (mp->mp_owner == NULL ||
869 		    strcmp(mp->mp_owner, DEFAULT_DEV_USER) == 0 ||
870 		    (pw = getpwnam(mp->mp_owner)) == NULL) {
871 			mp->mp_uid = root_uid;
872 		} else {
873 			mp->mp_uid = pw->pw_uid;
874 		}
875 
876 		if (mp->mp_group == NULL ||
877 		    strcmp(mp->mp_group, DEFAULT_DEV_GROUP) == 0 ||
878 		    (gp = getgrnam(mp->mp_group)) == NULL) {
879 			mp->mp_gid = sys_gid;
880 		} else {
881 			mp->mp_gid = gp->gr_gid;
882 		}
883 	}
884 
885 	if (fclose(pfd) == EOF) {
886 		(*errcb)(MP_FCLOSE_ERR, errno);
887 	}
888 
889 	return (minor_perms);
890 }
891 
892 struct mperm *
893 devfs_read_minor_perm(void (*errcb)(minorperm_err_t, int))
894 {
895 	return (i_devfs_read_minor_perm(NULL, errcb));
896 }
897 
898 static struct mperm *
899 i_devfs_read_minor_perm_by_driver(char *drvname,
900 	void (*errcb)(minorperm_err_t mp_err, int key))
901 {
902 	return (i_devfs_read_minor_perm(drvname, errcb));
903 }
904 
905 /*
906  * Free mperm list of entries
907  */
908 void
909 devfs_free_minor_perm(struct mperm *mplist)
910 {
911 	struct mperm *mp, *next;
912 
913 	for (mp = mplist; mp != NULL; mp = next) {
914 		next = mp->mp_next;
915 
916 		if (mp->mp_drvname)
917 			free(mp->mp_drvname);
918 		if (mp->mp_minorname)
919 			free(mp->mp_minorname);
920 		if (mp->mp_owner)
921 			free(mp->mp_owner);
922 		if (mp->mp_group)
923 			free(mp->mp_group);
924 		free(mp);
925 	}
926 }
927 
928 static int
929 i_devfs_add_perm_entry(nvlist_t *nvl, struct mperm *mp)
930 {
931 	int err;
932 
933 	err = nvlist_add_string(nvl, mp->mp_drvname, mp->mp_minorname);
934 	if (err != 0)
935 		return (err);
936 
937 	err = nvlist_add_int32(nvl, "mode", (int32_t)mp->mp_mode);
938 	if (err != 0)
939 		return (err);
940 
941 	err = nvlist_add_int32(nvl, "uid", (int32_t)mp->mp_uid);
942 	if (err != 0)
943 		return (err);
944 
945 	err = nvlist_add_int32(nvl, "gid", (int32_t)mp->mp_gid);
946 	return (err);
947 }
948 
949 static nvlist_t *
950 i_devfs_minor_perm_nvlist(struct mperm *mplist,
951 	void (*errcb)(minorperm_err_t, int))
952 {
953 	int err;
954 	struct mperm *mp;
955 	nvlist_t *nvl = NULL;
956 
957 	if ((err = nvlist_alloc(&nvl, 0, 0)) != 0) {
958 		(*errcb)(MP_NVLIST_ERR, err);
959 		return (NULL);
960 	}
961 
962 	for (mp = mplist; mp != NULL; mp = mp->mp_next) {
963 		if ((err = i_devfs_add_perm_entry(nvl, mp)) != 0) {
964 			(*errcb)(MP_NVLIST_ERR, err);
965 			nvlist_free(nvl);
966 			return (NULL);
967 		}
968 	}
969 
970 	return (nvl);
971 }
972 
973 /*
974  * Load all minor perm entries into the kernel
975  * Done at boot time via devfsadm
976  */
977 int
978 devfs_load_minor_perm(struct mperm *mplist,
979 	void (*errcb)(minorperm_err_t, int))
980 {
981 	int err;
982 	char *buf = NULL;
983 	size_t buflen;
984 	nvlist_t *nvl;
985 
986 	nvl = i_devfs_minor_perm_nvlist(mplist, errcb);
987 	if (nvl == NULL)
988 		return (-1);
989 
990 	if (nvlist_pack(nvl, &buf, &buflen, NV_ENCODE_NATIVE, 0) != 0) {
991 		nvlist_free(nvl);
992 		return (-1);
993 	}
994 
995 	err = modctl(MODLOADMINORPERM, buf, buflen);
996 	nvlist_free(nvl);
997 	free(buf);
998 
999 	return (err);
1000 }
1001 
1002 /*
1003  * Add/remove minor perm entry for a driver
1004  */
1005 static int
1006 i_devfs_update_minor_perm(char *drv, int ctl,
1007 	void (*errcb)(minorperm_err_t, int))
1008 {
1009 	int err;
1010 	char *buf;
1011 	size_t buflen;
1012 	nvlist_t *nvl;
1013 	struct mperm *mplist;
1014 
1015 	mplist = i_devfs_read_minor_perm_by_driver(drv, errcb);
1016 
1017 	nvl = i_devfs_minor_perm_nvlist(mplist, errcb);
1018 	if (nvl == NULL)
1019 		return (-1);
1020 
1021 	buf = NULL;
1022 	if (nvlist_pack(nvl, &buf, &buflen, NV_ENCODE_NATIVE, 0) != 0) {
1023 		nvlist_free(nvl);
1024 		return (-1);
1025 	}
1026 
1027 	err = modctl(ctl, buf, buflen);
1028 	nvlist_free(nvl);
1029 	devfs_free_minor_perm(mplist);
1030 	free(buf);
1031 
1032 	return (err);
1033 }
1034 
1035 int
1036 devfs_add_minor_perm(char *drv,
1037 	void (*errcb)(minorperm_err_t, int))
1038 {
1039 	return (i_devfs_update_minor_perm(drv, MODADDMINORPERM, errcb));
1040 }
1041 
1042 int
1043 devfs_rm_minor_perm(char *drv,
1044 	void (*errcb)(minorperm_err_t, int))
1045 {
1046 	return (i_devfs_update_minor_perm(drv, MODREMMINORPERM, errcb));
1047 }
1048 
1049 /*
1050  * is_blank() returns 1 (true) if a line specified is composed of
1051  * whitespace characters only. otherwise, it returns 0 (false).
1052  *
1053  * Note. the argument (line) must be null-terminated.
1054  */
1055 static int
1056 is_blank(char *line)
1057 {
1058 	for (/* nothing */; *line != '\0'; line++)
1059 		if (!isspace(*line))
1060 			return (0);
1061 	return (1);
1062 }
1063