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
59 extern int is_minor_node(const char *, const char **);
60
61 static int is_login_user(uid_t);
62 static int logindevperm(const char *, uid_t, gid_t, void (*)());
63 static int dir_dev_acc(char *, char *, uid_t, gid_t, mode_t, char *line,
64 void (*)());
65 static int setdevaccess(char *, uid_t, gid_t, mode_t, void (*)());
66 static void logerror(char *);
67
68 static 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 */
84 static int
setdevaccess(char * dev,uid_t uid,gid_t gid,mode_t mode,void (* errmsg)(char *))85 setdevaccess(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 */
157 static int
logindevperm(const char * ttyn,uid_t uid,gid_t gid,void (* errmsg)(char *))158 logindevperm(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 */
270 int
devfs_resolve_link(char * devpath,char ** devfs_path)271 devfs_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 */
332 static int
check_driver_match(char * path,char * line)333 check_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 */
389 static int
is_login_user(uid_t uid)390 is_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 */
425 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 *))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 *))
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(®ex, 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(®ex, 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(®ex);
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 */
570 int
di_devperm_login(const char * ttyn,uid_t uid,gid_t gid,void (* errmsg)(char *))571 di_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 */
612 int
di_devperm_logout(const char * ttyn)613 di_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
638 static void
logerror(char * errstring)639 logerror(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 */
648 static int
getnexttoken(char * next,char ** nextp,char ** tokenpp,char * tchar)649 getnexttoken(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 */
698 static int
getvalue(char * token,int * valuep)699 getvalue(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 */
780 struct mperm *
i_devfs_read_minor_perm(char * drvname,void (* errcb)(minorperm_err_t,int))781 i_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 }
894 link:
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
951 struct mperm *
devfs_read_minor_perm(void (* errcb)(minorperm_err_t,int))952 devfs_read_minor_perm(void (*errcb)(minorperm_err_t, int))
953 {
954 return (i_devfs_read_minor_perm(NULL, errcb));
955 }
956
957 static struct mperm *
i_devfs_read_minor_perm_by_driver(char * drvname,void (* errcb)(minorperm_err_t mp_err,int key))958 i_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 */
967 void
devfs_free_minor_perm(struct mperm * mplist)968 devfs_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
987 static int
i_devfs_add_perm_entry(nvlist_t * nvl,struct mperm * mp)988 i_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
1008 static nvlist_t *
i_devfs_minor_perm_nvlist(struct mperm * mplist,void (* errcb)(minorperm_err_t,int))1009 i_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 */
1036 int
devfs_load_minor_perm(struct mperm * mplist,void (* errcb)(minorperm_err_t,int))1037 devfs_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 */
1064 static int
i_devfs_update_minor_perm(char * drv,int ctl,void (* errcb)(minorperm_err_t,int))1065 i_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
1094 int
devfs_add_minor_perm(char * drv,void (* errcb)(minorperm_err_t,int))1095 devfs_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
1101 int
devfs_rm_minor_perm(char * drv,void (* errcb)(minorperm_err_t,int))1102 devfs_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 */
1114 static int
is_blank(char * line)1115 is_blank(char *line)
1116 {
1117 for (/* nothing */; *line != '\0'; line++)
1118 if (!isspace(*line))
1119 return (0);
1120 return (1);
1121 }
1122