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
setdevaccess(char * dev,uid_t uid,gid_t gid,mode_t mode,void (* errmsg)(char *))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
logindevperm(const char * ttyn,uid_t uid,gid_t gid,void (* errmsg)(char *))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
devfs_resolve_link(char * devpath,char ** devfs_path)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
check_driver_match(char * path,char * line)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
is_login_user(uid_t uid)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
dir_dev_acc(char * path,char * left_to_do,uid_t uid,gid_t gid,mode_t mode,char * line,void (* errmsg)(char *))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(®ex, 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(®ex, 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(®ex);
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
di_devperm_login(const char * ttyn,uid_t uid,gid_t gid,void (* errmsg)(char *))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
di_devperm_logout(const char * ttyn)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
logerror(char * errstring)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
getnexttoken(char * next,char ** nextp,char ** tokenpp,char * tchar)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
getvalue(char * token,int * valuep)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 *
i_devfs_read_minor_perm(char * drvname,void (* errcb)(minorperm_err_t,int))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 *
devfs_read_minor_perm(void (* errcb)(minorperm_err_t,int))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 *
i_devfs_read_minor_perm_by_driver(char * drvname,void (* errcb)(minorperm_err_t mp_err,int key))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
devfs_free_minor_perm(struct mperm * mplist)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
i_devfs_add_perm_entry(nvlist_t * nvl,struct mperm * mp)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 *
i_devfs_minor_perm_nvlist(struct mperm * mplist,void (* errcb)(minorperm_err_t,int))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
devfs_load_minor_perm(struct mperm * mplist,void (* errcb)(minorperm_err_t,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
i_devfs_update_minor_perm(char * drv,int ctl,void (* errcb)(minorperm_err_t,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
devfs_add_minor_perm(char * drv,void (* errcb)(minorperm_err_t,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
devfs_rm_minor_perm(char * drv,void (* errcb)(minorperm_err_t,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
is_blank(char * line)1119 is_blank(char *line)
1120 {
1121 for (/* nothing */; *line != '\0'; line++)
1122 if (!isspace(*line))
1123 return (0);
1124 return (1);
1125 }
1126