xref: /titanic_51/usr/src/lib/libdevinfo/devinfo_devperm.c (revision 522caefd66f4664db43a7b0008a469b547aea942)
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, Version 1.0 only
6  * (the "License").  You may not use this file except in compliance
7  * with the License.
8  *
9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10  * or http://www.opensolaris.org/os/licensing.
11  * See the License for the specific language governing permissions
12  * and limitations under the License.
13  *
14  * When distributing Covered Code, include this CDDL HEADER in each
15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16  * If applicable, add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your own identifying
18  * information: Portions Copyright [yyyy] [name of copyright owner]
19  *
20  * CDDL HEADER END
21  */
22 /*
23  * Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 #pragma ident	"%Z%%M%	%I%	%E% SMI"
28 
29 #define	_POSIX_PTHREAD_SEMANTICS	/* for readdir_r */
30 #ifdef lint
31 #define	_REENTRANT			/* for strtok_r */
32 #endif
33 
34 #include <stdio.h>
35 #include <stdlib.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 <alloca.h>
43 #include <nss_dbdefs.h>
44 #include <stdarg.h>
45 #include <syslog.h>
46 #include <sys/acl.h>
47 #include <sys/types.h>
48 #include <sys/stat.h>
49 #include <sys/ddi.h>
50 #include <sys/sunddi.h>
51 #include <sys/devinfo_impl.h>
52 #include <sys/hwconf.h>
53 #include <sys/modctl.h>
54 #include <libnvpair.h>
55 #include <device_info.h>
56 #include <regex.h>
57 #include <strings.h>
58 #include <libdevinfo.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 #define	MAX_LINELEN	256
69 #define	LOGINDEVPERM	"/etc/logindevperm"
70 #define	DIRWILD		"/*"			/* directory wildcard */
71 #define	DIRWLDLEN	2			/* strlen(DIRWILD) */
72 
73 /*
74  * Revoke all access to a device node and make sure that there are
75  * no interposed streams devices attached.  Must be called before a
76  * device is actually opened.
77  * When fdetach is called, the underlying device node is revealed; it
78  * will have the previous owner and that owner can re-attach; so we
79  * retry until we win.
80  * Ignore non-existent devices.
81  */
82 static int
83 setdevaccess(char *dev, uid_t uid, gid_t gid, mode_t mode,
84     void (*errmsg)(char *))
85 {
86 	int err = 0, local_errno;
87 	char errstring[MAX_LINELEN];
88 
89 	if (chown(dev, uid, gid) == -1) {
90 		if (errno == ENOENT)	/* no such file */
91 			return (0);
92 		err = -1;
93 	}
94 
95 	while (fdetach(dev) == 0) {
96 		if (chown(dev, uid, gid) == -1) {
97 			err = -1;
98 			local_errno = errno;
99 		}
100 	}
101 	if (err && errmsg) {
102 		(void) snprintf(errstring, MAX_LINELEN,
103 		    "failed to chown device %s: %s\n",
104 		    dev, strerror(local_errno));
105 		(*errmsg)(errstring);
106 	}
107 
108 	/*
109 	 * strip_acl sets an acl and changes the files owner/group
110 	 */
111 	err = acl_strip(dev, uid, gid, mode);
112 
113 	if (err != 0) {
114 		/*
115 		 * If the file system returned ENOSYS, we know that it
116 		 * doesn't support ACLs, therefore, we must assume that
117 		 * there were no ACLs to remove in the first place.
118 		 */
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, *result;
379 
380 	/* path must be a valid name */
381 	if (stat(path, &stat_buf) == -1) {
382 		return (-1);
383 	} else {
384 		if (!S_ISDIR(stat_buf.st_mode)) {
385 			if (strlen(left_to_do) == 0) {
386 				/* finally check the driver matches */
387 				if (check_driver_match(path, line) == 0) {
388 					/* we are done, set the permissions */
389 					if (setdevaccess(path,
390 					    uid, gid, mode, errmsg)) {
391 
392 						return (-1);
393 					}
394 				}
395 			}
396 			return (0);
397 		}
398 	}
399 
400 	dirp = opendir(path);
401 	if (dirp == NULL) {
402 		return (0);
403 	} else {
404 		char *p = strchr(left_to_do, '/');
405 		regex_t regex;
406 		int alwaysmatch = 0;
407 		char *match;
408 		char *name, *newpath, *remainder_path;
409 
410 		newpath = (char *)malloc(MAXPATHLEN);
411 		if (newpath == NULL) {
412 			return (-1);
413 		}
414 		match = (char *)calloc(MAXPATHLEN, 1);
415 		if (match == NULL) {
416 			free(newpath);
417 			return (-1);
418 		}
419 
420 		if (p) {
421 			(void) strncpy(match, left_to_do, p - left_to_do);
422 		} else {
423 			(void) strcpy(match, left_to_do);
424 		}
425 
426 		if (strcmp(match, "*") == 0) {
427 			alwaysmatch = 1;
428 		} else {
429 			if (regcomp(&regex, match, REG_EXTENDED) != 0) {
430 				free(newpath);
431 				free(match);
432 				return (-1);
433 			}
434 		}
435 
436 		direntp = alloca(sizeof (struct dirent) + MAXPATHLEN);
437 		while (readdir_r(dirp, direntp, &result) == 0) {
438 			if (result == NULL)
439 				break;
440 
441 			name = direntp->d_name;
442 			if ((strcmp(name, ".") == 0) ||
443 			    (strcmp(name, "..") == 0))
444 				continue;
445 
446 			if (alwaysmatch ||
447 			    regexec(&regex, name, 0, NULL, 0) == 0) {
448 				if (strcmp(path, "/") == 0) {
449 					(void) snprintf(newpath,
450 					    MAXPATHLEN, "%s%s", path, name);
451 				} else {
452 					(void) snprintf(newpath,
453 					    MAXPATHLEN, "%s/%s", path, name);
454 				}
455 
456 				/*
457 				 * recurse but adjust what is still left to do
458 				 */
459 				remainder_path = (p ?
460 				    left_to_do + (p - left_to_do) + 1 :
461 				    &left_to_do[strlen(left_to_do)]);
462 				if (dir_dev_acc(newpath, remainder_path,
463 				    uid, gid, mode, line, errmsg)) {
464 					err = -1;
465 					break;
466 				}
467 			}
468 		}
469 		(void) closedir(dirp);
470 		free(newpath);
471 		free(match);
472 		if (!alwaysmatch) {
473 			regfree(&regex);
474 		}
475 	}
476 
477 	return (err);
478 }
479 
480 /*
481  * di_devperm_login - modify access of devices in /etc/logindevperm
482  * by changing owner/group/permissions to that of ttyn.
483  */
484 int
485 di_devperm_login(const char *ttyn, uid_t uid, gid_t gid,
486     void (*errmsg)(char *))
487 {
488 	int err;
489 	struct group grp, *grpp;
490 	gid_t tty_gid;
491 	char grbuf[NSS_BUFLEN_GROUP];
492 
493 	if (errmsg == NULL)
494 		errmsg = logerror;
495 
496 	if (ttyn == NULL) {
497 		(*errmsg)("di_devperm_login: NULL tty device\n");
498 		return (-1);
499 	}
500 
501 	if (getgrnam_r("tty", &grp, grbuf, NSS_BUFLEN_GROUP, &grpp) != 0) {
502 		tty_gid = grpp->gr_gid;
503 	} else {
504 		/*
505 		 * this should never happen, but if it does set
506 		 * group to tty's traditional value.
507 		 */
508 		tty_gid = 7;
509 	}
510 
511 	/* set the login console device permission */
512 	err = setdevaccess((char *)ttyn, uid, tty_gid,
513 	    S_IRUSR|S_IWUSR|S_IWGRP, errmsg);
514 	if (err) {
515 		return (err);
516 	}
517 
518 	/* set the device permissions */
519 	return (logindevperm(ttyn, uid, gid, errmsg));
520 }
521 
522 /*
523  * di_devperm_logout - clean up access of devices in /etc/logindevperm
524  * by resetting owner/group/permissions.
525  */
526 int
527 di_devperm_logout(const char *ttyn)
528 {
529 	struct passwd *pwd;
530 	uid_t root_uid;
531 	gid_t root_gid;
532 
533 	if (ttyn == NULL)
534 		return (-1);
535 
536 	pwd = getpwnam("root");
537 	if (pwd != NULL) {
538 		root_uid = pwd->pw_uid;
539 		root_gid = pwd->pw_gid;
540 	} else {
541 		/*
542 		 * this should never happen, but if it does set user
543 		 * and group to root's traditional values.
544 		 */
545 		root_uid = 0;
546 		root_gid = 0;
547 	}
548 
549 	return (logindevperm(ttyn, root_uid, root_gid, NULL));
550 }
551 
552 static void
553 logerror(char *errstring)
554 {
555 	syslog(LOG_AUTH | LOG_CRIT, "%s", errstring);
556 }
557 
558 
559 /*
560  * Tokens are separated by ' ', '\t', ':', '=', '&', '|', ';', '\n', or '\0'
561  */
562 static int
563 getnexttoken(char *next, char **nextp, char **tokenpp, char *tchar)
564 {
565 	char *cp;
566 	char *cp1;
567 	char *tokenp;
568 
569 	cp = next;
570 	while (*cp == ' ' || *cp == '\t') {
571 		cp++;			/* skip leading spaces */
572 	}
573 	tokenp = cp;			/* start of token */
574 	while (*cp != '\0' && *cp != '\n' && *cp != ' ' && *cp != '\t' &&
575 		*cp != ':' && *cp != '=' && *cp != '&' &&
576 		*cp != '|' && *cp != ';') {
577 		cp++;			/* point to next character */
578 	}
579 	/*
580 	 * If terminating character is a space or tab, look ahead to see if
581 	 * there's another terminator that's not a space or a tab.
582 	 * (This code handles trailing spaces.)
583 	 */
584 	if (*cp == ' ' || *cp == '\t') {
585 		cp1 = cp;
586 		while (*++cp1 == ' ' || *cp1 == '\t')
587 			;
588 		if (*cp1 == '=' || *cp1 == ':' || *cp1 == '&' || *cp1 == '|' ||
589 			*cp1 == ';' || *cp1 == '\n' || *cp1 == '\0') {
590 			*cp = NULL;	/* terminate token */
591 			cp = cp1;
592 		}
593 	}
594 	if (tchar != NULL) {
595 		*tchar = *cp;		/* save terminating character */
596 		if (*tchar == '\0') {
597 			*tchar = '\n';
598 		}
599 	}
600 	*cp++ = '\0';			/* terminate token, point to next */
601 	*nextp = cp;			/* set pointer to next character */
602 	if (cp - tokenp - 1 == 0) {
603 		return (0);
604 	}
605 	*tokenpp = tokenp;
606 	return (1);
607 }
608 
609 /*
610  * get a decimal octal or hex number. Handle '~' for one's complement.
611  */
612 static int
613 getvalue(char *token, int *valuep)
614 {
615 	int radix;
616 	int retval = 0;
617 	int onescompl = 0;
618 	int negate = 0;
619 	char c;
620 
621 	if (*token == '~') {
622 		onescompl++; /* perform one's complement on result */
623 		token++;
624 	} else if (*token == '-') {
625 		negate++;
626 		token++;
627 	}
628 	if (*token == '0') {
629 		token++;
630 		c = *token;
631 
632 		if (c == '\0') {
633 			*valuep = 0;	/* value is 0 */
634 			return (0);
635 		}
636 
637 		if (c == 'x' || c == 'X') {
638 			radix = 16;
639 			token++;
640 		} else {
641 			radix = 8;
642 		}
643 	} else
644 		radix = 10;
645 
646 	while ((c = *token++)) {
647 		switch (radix) {
648 		case 8:
649 			if (c >= '0' && c <= '7') {
650 				c -= '0';
651 			} else {
652 				/* invalid number */
653 				return (0);
654 			}
655 			retval = (retval << 3) + c;
656 			break;
657 		case 10:
658 			if (c >= '0' && c <= '9') {
659 				c -= '0';
660 			} else {
661 				/* invalid number */
662 				return (0);
663 			}
664 			retval = (retval * 10) + c;
665 			break;
666 		case 16:
667 			if (c >= 'a' && c <= 'f') {
668 				c = c - 'a' + 10;
669 			} else if (c >= 'A' && c <= 'F') {
670 				c = c - 'A' + 10;
671 			} else if (c >= '0' && c <= '9') {
672 				c -= '0';
673 			} else {
674 				/* invalid number */
675 				return (0);
676 			}
677 			retval = (retval << 4) + c;
678 			break;
679 		}
680 	}
681 	if (onescompl) {
682 		retval = ~retval;
683 	}
684 	if (negate) {
685 		retval = -retval;
686 	}
687 	*valuep = retval;
688 	return (1);
689 }
690 
691 /*
692  * Read /etc/minor_perm, return mperm list of entries
693  */
694 struct mperm *
695 i_devfs_read_minor_perm(char *drvname, void (*errcb)(minorperm_err_t, int))
696 {
697 	FILE *pfd;
698 	struct mperm *mp;
699 	char line[MAX_MINOR_PERM_LINE];
700 	char *cp, *p, t;
701 	struct mperm *minor_perms = NULL;
702 	struct mperm *mptail = NULL;
703 	struct passwd *pw;
704 	struct group *gp;
705 	uid_t root_uid;
706 	gid_t sys_gid;
707 	int ln = 0;
708 
709 	/*
710 	 * Get root/sys ids, these being the most common
711 	 */
712 	if ((pw = getpwnam(DEFAULT_DEV_USER)) != NULL) {
713 		root_uid = pw->pw_uid;
714 	} else {
715 		(*errcb)(MP_CANT_FIND_USER_ERR, 0);
716 		root_uid = (uid_t)0;	/* assume 0 is root */
717 	}
718 	if ((gp = getgrnam(DEFAULT_DEV_GROUP)) != NULL) {
719 		sys_gid = gp->gr_gid;
720 	} else {
721 		(*errcb)(MP_CANT_FIND_GROUP_ERR, 0);
722 		sys_gid = (gid_t)3;	/* assume 3 is sys */
723 	}
724 
725 	if ((pfd = fopen(MINOR_PERM_FILE, "r")) == NULL) {
726 		(*errcb)(MP_FOPEN_ERR, errno);
727 		return (NULL);
728 	}
729 	while (fgets(line, MAX_MINOR_PERM_LINE - 1, pfd) != NULL) {
730 		ln++;
731 		mp = (struct mperm *)calloc(1, sizeof (struct mperm));
732 		if (mp == NULL) {
733 			(*errcb)(MP_ALLOC_ERR, sizeof (struct mperm));
734 			continue;
735 		}
736 		cp = line;
737 		if (getnexttoken(cp, &cp, &p, &t) == 0) {
738 			(*errcb)(MP_IGNORING_LINE_ERR, ln);
739 			devfs_free_minor_perm(mp);
740 			continue;
741 		}
742 		mp->mp_drvname = strdup(p);
743 		if (mp->mp_drvname == NULL) {
744 			(*errcb)(MP_ALLOC_ERR, strlen(p)+1);
745 			devfs_free_minor_perm(mp);
746 			continue;
747 		} else if (t == '\n' || t == '\0') {
748 			(*errcb)(MP_IGNORING_LINE_ERR, ln);
749 			devfs_free_minor_perm(mp);
750 			continue;
751 		}
752 		if (t == ':') {
753 			if (getnexttoken(cp, &cp, &p, &t) == 0) {
754 				(*errcb)(MP_IGNORING_LINE_ERR, ln);
755 				devfs_free_minor_perm(mp);
756 			}
757 			mp->mp_minorname = strdup(p);
758 			if (mp->mp_minorname == NULL) {
759 				(*errcb)(MP_ALLOC_ERR, strlen(p)+1);
760 				devfs_free_minor_perm(mp);
761 				continue;
762 			}
763 		} else {
764 			mp->mp_minorname = NULL;
765 		}
766 
767 		if (t == '\n' || t == '\0') {
768 			devfs_free_minor_perm(mp);
769 			(*errcb)(MP_IGNORING_LINE_ERR, ln);
770 			continue;
771 		}
772 		if (getnexttoken(cp, &cp, &p, &t) == 0) {
773 			goto link;
774 		}
775 		if (getvalue(p, (int *)&mp->mp_mode) == 0) {
776 			goto link;
777 		}
778 		if (t == '\n' || t == '\0') {	/* no owner or group */
779 			goto link;
780 		}
781 		if (getnexttoken(cp, &cp, &p, &t) == 0) {
782 			goto link;
783 		}
784 		mp->mp_owner = strdup(p);
785 		if (mp->mp_owner == NULL) {
786 			(*errcb)(MP_ALLOC_ERR, strlen(p)+1);
787 			devfs_free_minor_perm(mp);
788 			continue;
789 		} else if (t == '\n' || t == '\0') {	/* no group */
790 			goto link;
791 		}
792 		if (getnexttoken(cp, &cp, &p, 0) == 0) {
793 			goto link;
794 		}
795 		mp->mp_group = strdup(p);
796 		if (mp->mp_group == NULL) {
797 			(*errcb)(MP_ALLOC_ERR, strlen(p)+1);
798 			devfs_free_minor_perm(mp);
799 			continue;
800 		}
801 link:
802 		if (drvname != NULL) {
803 			/*
804 			 * We only want the minor perm entry for a
805 			 * the named driver.  The driver name is the
806 			 * minor in the clone case.
807 			 */
808 			if (strcmp(mp->mp_drvname, "clone") == 0) {
809 				if (mp->mp_minorname == NULL ||
810 				    strcmp(drvname, mp->mp_minorname) != 0) {
811 					devfs_free_minor_perm(mp);
812 					continue;
813 				}
814 			} else {
815 				if (strcmp(drvname, mp->mp_drvname) != 0) {
816 					devfs_free_minor_perm(mp);
817 					continue;
818 				}
819 			}
820 		}
821 		if (minor_perms == NULL) {
822 			minor_perms = mp;
823 		} else {
824 			mptail->mp_next = mp;
825 		}
826 		mptail = mp;
827 
828 		/*
829 		 * Compute the uid's and gid's here - there are
830 		 * fewer lines in the /etc/minor_perm file than there
831 		 * are devices to be stat(2)ed.  And almost every
832 		 * device is 'root sys'.  See 1135520.
833 		 */
834 		if (mp->mp_owner == NULL ||
835 		    strcmp(mp->mp_owner, DEFAULT_DEV_USER) == 0 ||
836 		    (pw = getpwnam(mp->mp_owner)) == NULL) {
837 			mp->mp_uid = root_uid;
838 		} else {
839 			mp->mp_uid = pw->pw_uid;
840 		}
841 
842 		if (mp->mp_group == NULL ||
843 		    strcmp(mp->mp_group, DEFAULT_DEV_GROUP) == 0 ||
844 		    (gp = getgrnam(mp->mp_group)) == NULL) {
845 			mp->mp_gid = sys_gid;
846 		} else {
847 			mp->mp_gid = gp->gr_gid;
848 		}
849 	}
850 
851 	if (fclose(pfd) == EOF) {
852 		(*errcb)(MP_FCLOSE_ERR, errno);
853 	}
854 
855 	return (minor_perms);
856 }
857 
858 struct mperm *
859 devfs_read_minor_perm(void (*errcb)(minorperm_err_t, int))
860 {
861 	return (i_devfs_read_minor_perm(NULL, errcb));
862 }
863 
864 static struct mperm *
865 i_devfs_read_minor_perm_by_driver(char *drvname,
866 	void (*errcb)(minorperm_err_t mp_err, int key))
867 {
868 	return (i_devfs_read_minor_perm(drvname, errcb));
869 }
870 
871 /*
872  * Free mperm list of entries
873  */
874 void
875 devfs_free_minor_perm(struct mperm *mplist)
876 {
877 	struct mperm *mp, *next;
878 
879 	for (mp = mplist; mp != NULL; mp = next) {
880 		next = mp->mp_next;
881 
882 		if (mp->mp_drvname)
883 			free(mp->mp_drvname);
884 		if (mp->mp_minorname)
885 			free(mp->mp_minorname);
886 		if (mp->mp_owner)
887 			free(mp->mp_owner);
888 		if (mp->mp_group)
889 			free(mp->mp_group);
890 		free(mp);
891 	}
892 }
893 
894 static int
895 i_devfs_add_perm_entry(nvlist_t *nvl, struct mperm *mp)
896 {
897 	int err;
898 
899 	err = nvlist_add_string(nvl, mp->mp_drvname, mp->mp_minorname);
900 	if (err != 0)
901 		return (err);
902 
903 	err = nvlist_add_int32(nvl, "mode", (int32_t)mp->mp_mode);
904 	if (err != 0)
905 		return (err);
906 
907 	err = nvlist_add_int32(nvl, "uid", (int32_t)mp->mp_uid);
908 	if (err != 0)
909 		return (err);
910 
911 	err = nvlist_add_int32(nvl, "gid", (int32_t)mp->mp_gid);
912 	return (err);
913 }
914 
915 static nvlist_t *
916 i_devfs_minor_perm_nvlist(struct mperm *mplist,
917 	void (*errcb)(minorperm_err_t, int))
918 {
919 	int err;
920 	struct mperm *mp;
921 	nvlist_t *nvl = NULL;
922 
923 	if ((err = nvlist_alloc(&nvl, 0, 0)) != 0) {
924 		(*errcb)(MP_NVLIST_ERR, err);
925 		return (NULL);
926 	}
927 
928 	for (mp = mplist; mp != NULL; mp = mp->mp_next) {
929 		if ((err = i_devfs_add_perm_entry(nvl, mp)) != 0) {
930 			(*errcb)(MP_NVLIST_ERR, err);
931 			nvlist_free(nvl);
932 			return (NULL);
933 		}
934 	}
935 
936 	return (nvl);
937 }
938 
939 /*
940  * Load all minor perm entries into the kernel
941  * Done at boot time via devfsadm
942  */
943 int
944 devfs_load_minor_perm(struct mperm *mplist,
945 	void (*errcb)(minorperm_err_t, int))
946 {
947 	int err;
948 	char *buf = NULL;
949 	size_t buflen;
950 	nvlist_t *nvl;
951 
952 	nvl = i_devfs_minor_perm_nvlist(mplist, errcb);
953 	if (nvl == NULL)
954 		return (-1);
955 
956 	if (nvlist_pack(nvl, &buf, &buflen, NV_ENCODE_NATIVE, 0) != 0) {
957 		nvlist_free(nvl);
958 		return (-1);
959 	}
960 
961 	err = modctl(MODLOADMINORPERM, buf, buflen);
962 	nvlist_free(nvl);
963 	free(buf);
964 
965 	return (err);
966 }
967 
968 /*
969  * Add/remove minor perm entry for a driver
970  */
971 static int
972 i_devfs_update_minor_perm(char *drv, int ctl,
973 	void (*errcb)(minorperm_err_t, int))
974 {
975 	int err;
976 	char *buf;
977 	size_t buflen;
978 	nvlist_t *nvl;
979 	struct mperm *mplist;
980 
981 	mplist = i_devfs_read_minor_perm_by_driver(drv, errcb);
982 
983 	nvl = i_devfs_minor_perm_nvlist(mplist, errcb);
984 	if (nvl == NULL)
985 		return (-1);
986 
987 	buf = NULL;
988 	if (nvlist_pack(nvl, &buf, &buflen, NV_ENCODE_NATIVE, 0) != 0) {
989 		nvlist_free(nvl);
990 		return (-1);
991 	}
992 
993 	err = modctl(ctl, buf, buflen);
994 	nvlist_free(nvl);
995 	devfs_free_minor_perm(mplist);
996 	free(buf);
997 
998 	return (err);
999 }
1000 
1001 int
1002 devfs_add_minor_perm(char *drv,
1003 	void (*errcb)(minorperm_err_t, int))
1004 {
1005 	return (i_devfs_update_minor_perm(drv, MODADDMINORPERM, errcb));
1006 }
1007 
1008 int
1009 devfs_rm_minor_perm(char *drv,
1010 	void (*errcb)(minorperm_err_t, int))
1011 {
1012 	return (i_devfs_update_minor_perm(drv, MODREMMINORPERM, errcb));
1013 }
1014