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