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