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