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