Merge commit '0b2e8253986c5c761129b58cfdac46d204903de1'
[unleashed.git] / usr / src / lib / libdevinfo / devinfo_devperm.c
blob28d2af8b31072558a2443eeed5ded5add3730c97
1 /*
2 * CDDL HEADER START
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.
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.
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]
19 * CDDL HEADER END
22 * Copyright (c) 2011 Gary Mills
24 * Copyright (c) 2003, 2010, Oracle and/or its affiliates. All rights reserved.
27 #define _POSIX_PTHREAD_SEMANTICS /* for getgrnam_r */
29 #include <stdio.h>
30 #include <stdlib.h>
31 #include <ctype.h>
32 #include <string.h>
33 #include <unistd.h>
34 #include <dirent.h>
35 #include <errno.h>
36 #include <grp.h>
37 #include <pwd.h>
38 #include <nss_dbdefs.h>
39 #include <stdarg.h>
40 #include <syslog.h>
41 #include <sys/acl.h>
42 #include <sys/types.h>
43 #include <sys/stat.h>
44 #include <sys/ddi.h>
45 #include <sys/sunddi.h>
46 #include <sys/devinfo_impl.h>
47 #include <sys/hwconf.h>
48 #include <sys/modctl.h>
49 #include <libnvpair.h>
50 #include <device_info.h>
51 #include <regex.h>
52 #include <strings.h>
53 #include <libdevinfo.h>
54 #include <zone.h>
55 #include <fcntl.h>
56 #include <utmpx.h>
58 extern int is_minor_node(const char *, const char **);
60 static int is_login_user(uid_t);
61 static int logindevperm(const char *, uid_t, gid_t, void (*)());
62 static int dir_dev_acc(char *, char *, uid_t, gid_t, mode_t, char *line,
63 void (*)());
64 static int setdevaccess(char *, uid_t, gid_t, mode_t, void (*)());
65 static void logerror(char *);
67 static int is_blank(char *);
69 #define MAX_LINELEN 256
70 #define LOGINDEVPERM "/etc/logindevperm"
71 #define DIRWILD "/*" /* directory wildcard */
72 #define DIRWLDLEN 2 /* strlen(DIRWILD) */
75 * Revoke all access to a device node and make sure that there are
76 * no interposed streams devices attached. Must be called before a
77 * device is actually opened.
78 * When fdetach is called, the underlying device node is revealed; it
79 * will have the previous owner and that owner can re-attach; so we
80 * retry until we win.
81 * Ignore non-existent devices.
83 static int
84 setdevaccess(char *dev, uid_t uid, gid_t gid, mode_t mode,
85 void (*errmsg)(char *))
87 int err = 0, local_errno;
88 char errstring[MAX_LINELEN];
89 struct stat st;
91 if (chown(dev, uid, gid) == -1) {
92 if (errno == ENOENT) /* no such file */
93 return (0);
94 err = -1;
95 local_errno = errno;
99 * don't fdetach block devices, as it will unmount them
101 if (!((stat(dev, &st) == 0) && ((st.st_mode & S_IFMT) == S_IFBLK))) {
102 while (fdetach(dev) == 0) {
103 if (chown(dev, uid, gid) == -1) {
104 err = -1;
105 local_errno = errno;
108 if (err && errmsg) {
109 (void) snprintf(errstring, MAX_LINELEN,
110 "failed to chown device %s: %s\n",
111 dev, strerror(local_errno));
112 (*errmsg)(errstring);
117 * strip_acl sets an acl and changes the files owner/group
119 err = acl_strip(dev, uid, gid, mode);
121 if (err != 0) {
123 * If the file system returned ENOSYS, we know that it
124 * doesn't support ACLs, therefore, we must assume that
125 * there were no ACLs to remove in the first place.
127 err = 0;
128 if (errno != ENOSYS) {
129 err = -1;
131 if (errmsg) {
132 (void) snprintf(errstring, MAX_LINELEN,
133 "failed to set acl on device %s: %s\n",
134 dev, strerror(errno));
135 (*errmsg)(errstring);
138 if (chmod(dev, mode) == -1) {
139 err = -1;
140 if (errmsg) {
141 (void) snprintf(errstring, MAX_LINELEN,
142 "failed to chmod device %s: %s\n",
143 dev, strerror(errno));
144 (*errmsg)(errstring);
149 return (err);
153 * logindevperm - change owner/group/permissions of devices
154 * list in /etc/logindevperm.
156 static int
157 logindevperm(const char *ttyn, uid_t uid, gid_t gid, void (*errmsg)(char *))
159 int err = 0, lineno = 0;
160 const char *field_delims = " \t\n";
161 char line[MAX_LINELEN], errstring[MAX_LINELEN];
162 char saveline[MAX_LINELEN];
163 char *console;
164 char *mode_str;
165 char *dev_list;
166 char *device;
167 char *ptr;
168 int mode;
169 FILE *fp;
170 char ttyn_path[PATH_MAX + 1];
171 int n;
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);
180 return (-1);
183 if ((n = resolvepath(ttyn, ttyn_path, PATH_MAX)) == -1)
184 return (-1);
185 ttyn_path[n] = '\0';
187 while (fgets(line, MAX_LINELEN, fp) != NULL) {
188 char *last;
189 char tmp[PATH_MAX + 1];
191 lineno++;
193 if ((ptr = strchr(line, '#')) != NULL)
194 *ptr = '\0'; /* handle comments */
196 (void) strcpy(saveline, line);
198 console = strtok_r(line, field_delims, &last);
199 if (console == NULL)
200 continue; /* ignore blank lines */
202 if ((n = resolvepath(console, tmp, PATH_MAX)) == -1)
203 continue;
204 tmp[n] = '\0';
206 if (strcmp(ttyn_path, tmp) != 0)
207 continue;
209 mode_str = strtok_r(last, field_delims, &last);
210 if (mode_str == NULL) {
211 err = -1; /* invalid entry, skip */
212 if (errmsg) {
213 (void) snprintf(errstring, MAX_LINELEN,
214 LOGINDEVPERM
215 ": line %d, invalid entry -- %s\n",
216 lineno, line);
217 (*errmsg)(errstring);
219 continue;
222 /* convert string to octal value */
223 mode = strtol(mode_str, &ptr, 8);
224 if (mode < 0 || mode > 0777 || *ptr != '\0') {
225 err = -1; /* invalid mode, skip */
226 if (errmsg) {
227 (void) snprintf(errstring, MAX_LINELEN,
228 LOGINDEVPERM
229 ": line %d, invalid mode -- %s\n",
230 lineno, mode_str);
231 (*errmsg)(errstring);
233 continue;
236 dev_list = strtok_r(last, field_delims, &last);
237 if (dev_list == NULL) {
238 err = -1; /* empty device list, skip */
239 if (errmsg) {
240 (void) snprintf(errstring, MAX_LINELEN,
241 LOGINDEVPERM
242 ": line %d, empty device list -- %s\n",
243 lineno, line);
244 (*errmsg)(errstring);
246 continue;
249 device = strtok_r(dev_list, ":", &last);
250 while (device != NULL) {
251 if ((device[0] != '/') || (strlen(device) <= 1)) {
252 err = -1;
253 } else if (dir_dev_acc("/", &device[1], uid, gid, mode,
254 saveline, errmsg)) {
255 err = -1;
257 device = strtok_r(last, ":", &last);
260 (void) fclose(fp);
261 return (err);
265 * returns 0 if resolved, -1 otherwise.
266 * devpath: Absolute path to /dev link
267 * devfs_path: Returns malloced string: /devices path w/out "/devices"
270 devfs_resolve_link(char *devpath, char **devfs_path)
272 char contents[PATH_MAX + 1];
273 char stage_link[PATH_MAX + 1];
274 char *ptr;
275 int linksize;
276 char *slashdev = "/dev/";
278 if (devfs_path) {
279 *devfs_path = NULL;
282 linksize = readlink(devpath, contents, PATH_MAX);
284 if (linksize <= 0) {
285 return (-1);
286 } else {
287 contents[linksize] = '\0';
291 * if the link contents is not a minor node assume
292 * that link contents is really a pointer to another
293 * link, and if so recurse and read its link contents.
295 if (is_minor_node((const char *)contents, (const char **)&ptr) !=
296 1) {
297 if (strncmp(contents, slashdev, strlen(slashdev)) == 0) {
298 /* absolute path, starting with /dev */
299 (void) strcpy(stage_link, contents);
300 } else {
301 /* relative path, prefix devpath */
302 if ((ptr = strrchr(devpath, '/')) == NULL) {
303 /* invalid link */
304 return (-1);
306 *ptr = '\0';
307 (void) strcpy(stage_link, devpath);
308 *ptr = '/';
309 (void) strcat(stage_link, "/");
310 (void) strcat(stage_link, contents);
313 return (devfs_resolve_link(stage_link, devfs_path));
316 if (devfs_path) {
317 *devfs_path = strdup(ptr);
318 if (*devfs_path == NULL) {
319 return (-1);
323 return (0);
327 * check a logindevperm line for a driver list and match this against
328 * the driver of the minor node
329 * returns 0 if no drivers were specified or a driver match
331 static int
332 check_driver_match(char *path, char *line)
334 char *drv, *driver, *lasts;
335 char *devfs_path = NULL;
336 char saveline[MAX_LINELEN];
337 char *p;
339 if (devfs_resolve_link(path, &devfs_path) == 0) {
340 char *p;
341 char pwd_buf[PATH_MAX];
342 di_node_t node;
344 /* truncate on : so we can take a snapshot */
345 (void) strcpy(pwd_buf, devfs_path);
346 p = strrchr(pwd_buf, ':');
347 *p = '\0';
349 node = di_init(pwd_buf, DINFOMINOR);
350 free(devfs_path);
352 if (node) {
353 drv = di_driver_name(node);
354 di_fini(node);
355 } else {
356 return (0);
358 } else {
359 return (0);
362 (void) strcpy(saveline, line);
364 p = strstr(saveline, "driver");
365 if (p == NULL) {
366 return (0);
369 driver = strtok_r(p, "=", &lasts);
370 if (driver) {
371 if (strcmp(driver, "driver") == 0) {
372 driver = strtok_r(NULL, ", \t\n", &lasts);
373 while (driver) {
374 if (strcmp(driver, drv) == 0) {
375 return (0);
377 driver = strtok_r(NULL, ", \t\n", &lasts);
382 return (-1);
386 * Check whether the user has logged onto "/dev/console" or "/dev/vt/#".
388 static int
389 is_login_user(uid_t uid)
391 int changed = 0;
392 struct passwd pwd, *ppwd;
393 char pwd_buf[NSS_BUFLEN_PASSWD];
394 struct utmpx *utx;
396 if ((getpwuid_r(uid, &pwd, pwd_buf, NSS_BUFLEN_PASSWD, &ppwd) != 0) ||
397 (ppwd == NULL)) {
398 return (0);
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)) {
409 changed = 1;
410 break;
413 endutxent();
415 return (changed);
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.
425 static int
426 dir_dev_acc(char *path, char *left_to_do, uid_t uid, gid_t gid, mode_t mode,
427 char *line, void (*errmsg)(char *))
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;
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.
447 if (!device_exists(path)) {
448 return (-1);
450 if (stat(path, &stat_buf) == -1) {
452 * ENOENT errors are expected errors when there are
453 * dangling /dev device links. Ignore them silently
455 if (errno == ENOENT) {
456 return (0);
458 if (errmsg) {
459 (void) snprintf(errstring, MAX_LINELEN,
460 "failed to stat %s: %s\n", path,
461 strerror(errno));
462 (*errmsg)(errstring);
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) {
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.
477 if (is_login_user(stat_buf.st_uid)) {
479 return (0);
481 /* we are done, set the permissions */
482 if (setdevaccess(path,
483 uid, gid, mode, errmsg)) {
485 return (-1);
489 return (0);
493 if (finddev_readdir(path, &handle) != 0)
494 return (0);
496 p = strchr(left_to_do, '/');
497 alwaysmatch = 0;
499 newpath = (char *)malloc(MAXPATHLEN);
500 if (newpath == NULL) {
501 finddev_close(handle);
502 return (-1);
504 match = (char *)calloc(MAXPATHLEN + 2, 1);
505 if (match == NULL) {
506 finddev_close(handle);
507 free(newpath);
508 return (-1);
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);
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);
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);
544 * recurse but adjust what is still left to do
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;
556 finddev_close(handle);
557 free(newpath);
558 free(match);
559 if (!alwaysmatch) {
560 regfree(&regex);
563 return (err);
567 * di_devperm_login - modify access of devices in /etc/logindevperm
568 * by changing owner/group/permissions to that of ttyn.
571 di_devperm_login(const char *ttyn, uid_t uid, gid_t gid,
572 void (*errmsg)(char *))
574 int err;
575 struct group grp, *grpp;
576 gid_t tty_gid;
577 char grbuf[NSS_BUFLEN_GROUP];
579 if (errmsg == NULL)
580 errmsg = logerror;
582 if (ttyn == NULL) {
583 (*errmsg)("di_devperm_login: NULL tty device\n");
584 return (-1);
587 if (getgrnam_r("tty", &grp, grbuf, NSS_BUFLEN_GROUP, &grpp) != 0) {
588 tty_gid = grpp->gr_gid;
589 } else {
591 * this should never happen, but if it does set
592 * group to tty's traditional value.
594 tty_gid = 7;
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);
604 /* set the device permissions */
605 return (logindevperm(ttyn, uid, gid, errmsg));
609 * di_devperm_logout - clean up access of devices in /etc/logindevperm
610 * by resetting owner/group/permissions.
613 di_devperm_logout(const char *ttyn)
615 struct passwd *pwd;
616 uid_t root_uid;
617 gid_t root_gid;
619 if (ttyn == NULL)
620 return (-1);
622 pwd = getpwnam("root");
623 if (pwd != NULL) {
624 root_uid = pwd->pw_uid;
625 root_gid = pwd->pw_gid;
626 } else {
628 * this should never happen, but if it does set user
629 * and group to root's traditional values.
631 root_uid = 0;
632 root_gid = 0;
635 return (logindevperm(ttyn, root_uid, root_gid, NULL));
638 static void
639 logerror(char *errstring)
641 syslog(LOG_AUTH | LOG_CRIT, "%s", errstring);
646 * Tokens are separated by ' ', '\t', ':', '=', '&', '|', ';', '\n', or '\0'
648 static int
649 getnexttoken(char *next, char **nextp, char **tokenpp, char *tchar)
651 char *cp;
652 char *cp1;
653 char *tokenp;
655 cp = next;
656 while (*cp == ' ' || *cp == '\t') {
657 cp++; /* skip leading spaces */
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 */
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.)
670 if (*cp == ' ' || *cp == '\t') {
671 cp1 = cp;
672 while (*++cp1 == ' ' || *cp1 == '\t')
674 if (*cp1 == '=' || *cp1 == ':' || *cp1 == '&' || *cp1 == '|' ||
675 *cp1 == ';' || *cp1 == '\n' || *cp1 == '\0') {
676 *cp = '\0'; /* terminate token */
677 cp = cp1;
680 if (tchar != NULL) {
681 *tchar = *cp; /* save terminating character */
682 if (*tchar == '\0') {
683 *tchar = '\n';
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);
691 *tokenpp = tokenp;
692 return (1);
696 * get a decimal octal or hex number. Handle '~' for one's complement.
698 static int
699 getvalue(char *token, int *valuep)
701 int radix;
702 int retval = 0;
703 int onescompl = 0;
704 int negate = 0;
705 char c;
707 if (*token == '~') {
708 onescompl++; /* perform one's complement on result */
709 token++;
710 } else if (*token == '-') {
711 negate++;
712 token++;
714 if (*token == '0') {
715 token++;
716 c = *token;
718 if (c == '\0') {
719 *valuep = 0; /* value is 0 */
720 return (0);
723 if (c == 'x' || c == 'X') {
724 radix = 16;
725 token++;
726 } else {
727 radix = 8;
729 } else
730 radix = 10;
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);
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);
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);
763 retval = (retval << 4) + c;
764 break;
767 if (onescompl) {
768 retval = ~retval;
770 if (negate) {
771 retval = -retval;
773 *valuep = retval;
774 return (1);
778 * Read /etc/minor_perm, return mperm list of entries
780 struct mperm *
781 i_devfs_read_minor_perm(char *drvname, void (*errcb)(minorperm_err_t, int))
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;
796 * Get root/sys ids, these being the most common
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 */
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 */
811 if ((pfd = fopen(MINOR_PERM_FILE, "r")) == NULL) {
812 (*errcb)(MP_FOPEN_ERR, errno);
813 return (NULL);
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;
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;
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;
845 if (t == ':') {
846 if (getnexttoken(cp, &cp, &p, &t) == 0) {
847 (*errcb)(MP_IGNORING_LINE_ERR, ln);
848 devfs_free_minor_perm(mp);
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;
856 } else {
857 mp->mp_minorname = NULL;
860 if (t == '\n' || t == '\0') {
861 devfs_free_minor_perm(mp);
862 (*errcb)(MP_IGNORING_LINE_ERR, ln);
863 continue;
865 if (getnexttoken(cp, &cp, &p, &t) == 0) {
866 goto link;
868 if (getvalue(p, (int *)&mp->mp_mode) == 0) {
869 goto link;
871 if (t == '\n' || t == '\0') { /* no owner or group */
872 goto link;
874 if (getnexttoken(cp, &cp, &p, &t) == 0) {
875 goto link;
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;
885 if (getnexttoken(cp, &cp, &p, 0) == 0) {
886 goto link;
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;
894 link:
895 if (drvname != NULL) {
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.
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;
907 } else {
908 if (strcmp(drvname, mp->mp_drvname) != 0) {
909 devfs_free_minor_perm(mp);
910 continue;
914 if (minor_perms == NULL) {
915 minor_perms = mp;
916 } else {
917 mptail->mp_next = mp;
919 mptail = mp;
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.
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;
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;
944 if (fclose(pfd) == EOF) {
945 (*errcb)(MP_FCLOSE_ERR, errno);
948 return (minor_perms);
951 struct mperm *
952 devfs_read_minor_perm(void (*errcb)(minorperm_err_t, int))
954 return (i_devfs_read_minor_perm(NULL, errcb));
957 static struct mperm *
958 i_devfs_read_minor_perm_by_driver(char *drvname,
959 void (*errcb)(minorperm_err_t mp_err, int key))
961 return (i_devfs_read_minor_perm(drvname, errcb));
965 * Free mperm list of entries
967 void
968 devfs_free_minor_perm(struct mperm *mplist)
970 struct mperm *mp, *next;
972 for (mp = mplist; mp != NULL; mp = next) {
973 next = mp->mp_next;
975 free(mp->mp_drvname);
976 free(mp->mp_minorname);
977 free(mp->mp_owner);
978 free(mp->mp_group);
979 free(mp);
983 static int
984 i_devfs_add_perm_entry(nvlist_t *nvl, struct mperm *mp)
986 int err;
988 err = nvlist_add_string(nvl, mp->mp_drvname, mp->mp_minorname);
989 if (err != 0)
990 return (err);
992 err = nvlist_add_int32(nvl, "mode", (int32_t)mp->mp_mode);
993 if (err != 0)
994 return (err);
996 err = nvlist_add_uint32(nvl, "uid", mp->mp_uid);
997 if (err != 0)
998 return (err);
1000 err = nvlist_add_uint32(nvl, "gid", mp->mp_gid);
1001 return (err);
1004 static nvlist_t *
1005 i_devfs_minor_perm_nvlist(struct mperm *mplist,
1006 void (*errcb)(minorperm_err_t, int))
1008 int err;
1009 struct mperm *mp;
1010 nvlist_t *nvl = NULL;
1012 if ((err = nvlist_alloc(&nvl, 0, 0)) != 0) {
1013 (*errcb)(MP_NVLIST_ERR, err);
1014 return (NULL);
1017 for (mp = mplist; mp != NULL; mp = mp->mp_next) {
1018 if ((err = i_devfs_add_perm_entry(nvl, mp)) != 0) {
1019 (*errcb)(MP_NVLIST_ERR, err);
1020 nvlist_free(nvl);
1021 return (NULL);
1025 return (nvl);
1029 * Load all minor perm entries into the kernel
1030 * Done at boot time via devfsadm
1033 devfs_load_minor_perm(struct mperm *mplist,
1034 void (*errcb)(minorperm_err_t, int))
1036 int err;
1037 char *buf = NULL;
1038 size_t buflen;
1039 nvlist_t *nvl;
1041 nvl = i_devfs_minor_perm_nvlist(mplist, errcb);
1042 if (nvl == NULL)
1043 return (-1);
1045 if (nvlist_pack(nvl, &buf, &buflen, NV_ENCODE_NATIVE, 0) != 0) {
1046 nvlist_free(nvl);
1047 return (-1);
1050 err = modctl(MODLOADMINORPERM, buf, buflen);
1051 nvlist_free(nvl);
1052 free(buf);
1054 return (err);
1058 * Add/remove minor perm entry for a driver
1060 static int
1061 i_devfs_update_minor_perm(char *drv, int ctl,
1062 void (*errcb)(minorperm_err_t, int))
1064 int err;
1065 char *buf;
1066 size_t buflen;
1067 nvlist_t *nvl;
1068 struct mperm *mplist;
1070 mplist = i_devfs_read_minor_perm_by_driver(drv, errcb);
1072 nvl = i_devfs_minor_perm_nvlist(mplist, errcb);
1073 if (nvl == NULL)
1074 return (-1);
1076 buf = NULL;
1077 if (nvlist_pack(nvl, &buf, &buflen, NV_ENCODE_NATIVE, 0) != 0) {
1078 nvlist_free(nvl);
1079 return (-1);
1082 err = modctl(ctl, buf, buflen);
1083 nvlist_free(nvl);
1084 devfs_free_minor_perm(mplist);
1085 free(buf);
1087 return (err);
1091 devfs_add_minor_perm(char *drv,
1092 void (*errcb)(minorperm_err_t, int))
1094 return (i_devfs_update_minor_perm(drv, MODADDMINORPERM, errcb));
1098 devfs_rm_minor_perm(char *drv,
1099 void (*errcb)(minorperm_err_t, int))
1101 return (i_devfs_update_minor_perm(drv, MODREMMINORPERM, errcb));
1105 * is_blank() returns 1 (true) if a line specified is composed of
1106 * whitespace characters only. otherwise, it returns 0 (false).
1108 * Note. the argument (line) must be null-terminated.
1110 static int
1111 is_blank(char *line)
1113 for (/* nothing */; *line != '\0'; line++)
1114 if (!isspace(*line))
1115 return (0);
1116 return (1);