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