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