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