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