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