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