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, Version 1.0 only
6 * (the "License"). You may not use this file except in compliance
7 * with the License.
8 *
9 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10 * or http://www.opensolaris.org/os/licensing.
11 * See the License for the specific language governing permissions
12 * and limitations under the License.
13 *
14 * When distributing Covered Code, include this CDDL HEADER in each
15 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16 * If applicable, add the following below this CDDL HEADER, with the
17 * fields enclosed by brackets "[]" replaced with your own identifying
18 * information: Portions Copyright [yyyy] [name of copyright owner]
19 *
20 * CDDL HEADER END
21 */
22 /*
23 * Copyright 2003 Sun Microsystems, Inc. All rights reserved.
24 * Use is subject to license terms.
25 *
26 * Device policy specific subroutines. We cannot merge them with
27 * drvsubr.c because of static linking requirements.
28 */
29
30 /*
31 * Copyright 2020 OmniOS Community Edition (OmniOSce) Association.
32 */
33
34 #include <stdio.h>
35 #include <stdlib.h>
36 #include <unistd.h>
37 #include <string.h>
38 #include <ctype.h>
39 #include <priv.h>
40 #include <string.h>
41 #include <libgen.h>
42 #include <libintl.h>
43 #include <errno.h>
44 #include <alloca.h>
45 #include <sys/modctl.h>
46 #include <sys/devpolicy.h>
47 #include <sys/stat.h>
48 #include <sys/sysmacros.h>
49
50 #include "addrem.h"
51 #include "errmsg.h"
52 #include "plcysubr.h"
53
54 size_t devplcysys_sz;
55 const priv_impl_info_t *privimplinfo;
56
57 /*
58 * New token types should be parsed in parse_plcy_entry.
59 */
60 #define PSET 0
61
62 typedef struct token {
63 const char *token;
64 int type;
65 ptrdiff_t off;
66 } token_t;
67
68 static token_t toktab[] = {
69 { DEVPLCY_TKN_RDP, PSET /* offsetof(devplcysys_t, dps_rdp) */ },
70 { DEVPLCY_TKN_WRP, PSET /* offsetof(devplcysys_t, dps_wrp) */ },
71 };
72
73 #define RDPOL 0
74 #define WRPOL 1
75
76 #define NTOK (sizeof (toktab)/sizeof (token_t))
77
78 /*
79 * Compute the size of the datastructures needed.
80 */
81 void
devplcy_init(void)82 devplcy_init(void)
83 {
84 if ((privimplinfo = getprivimplinfo()) == NULL) {
85 (void) fprintf(stderr, gettext(ERR_PRIVIMPL));
86 exit(1);
87 }
88
89 devplcysys_sz = DEVPLCYSYS_SZ(privimplinfo);
90
91 toktab[RDPOL].off =
92 (char *)DEVPLCYSYS_RDP((devplcysys_t *)0, privimplinfo) - (char *)0;
93 toktab[WRPOL].off =
94 (char *)DEVPLCYSYS_WRP((devplcysys_t *)0, privimplinfo) - (char *)0;
95 }
96
97 /*
98 * Read a configuration file line and return a static buffer pointing to it.
99 * It returns a static struct fileentry which has several fields:
100 * - rawbuf, which includes the lines including empty lines and comments
101 * leading up to the file and the entry as found in the file
102 * - orgentry, pointer in rawbuf to the start of the entry proper.
103 * - entry, a pre-parsed entry, escaped newlines removed.
104 * - startline, the line number of the first line in the file
105 */
106 fileentry_t *
fgetline(FILE * fp)107 fgetline(FILE *fp)
108 {
109 static size_t sz = BUFSIZ;
110 static struct fileentry fe;
111 static int linecnt = 1;
112
113 char *buf = fe.rawbuf;
114 ptrdiff_t off;
115 char *p;
116 int c, lastc, i;
117
118 if (buf == NULL) {
119 fe.rawbuf = buf = malloc(sz);
120 if (buf == NULL)
121 return (NULL);
122 }
123 if (fe.entry != NULL) {
124 free(fe.entry);
125 fe.orgentry = fe.entry = NULL;
126 }
127
128 i = 0;
129 off = -1;
130 c = '\n';
131
132 while (lastc = c, (c = getc(fp)) != EOF) {
133 buf[i++] = c;
134
135 if (i == sz) {
136 sz *= 2;
137 fe.rawbuf = buf = realloc(buf, sz);
138 if (buf == NULL)
139 return (NULL);
140 }
141
142 if (c == '\n') {
143 linecnt++;
144 /* Newline, escaped or not yet processing an entry */
145 if (off == -1 || lastc == '\\')
146 continue;
147 } else if (lastc == '\n' && off == -1) {
148 /* Start of more comments */
149 if (c == '#')
150 continue;
151 /* Found start of entry */
152 off = i - 1;
153 fe.startline = linecnt;
154 continue;
155 } else
156 continue;
157
158 buf[i] = '\0';
159 fe.orgentry = buf + off;
160 p = fe.entry = strdup(fe.orgentry);
161
162 if (p == NULL)
163 return (NULL);
164
165 /* Remove <backslash><newline> */
166 if ((p = strchr(p, '\\')) != NULL) {
167 for (off = 0; (p[-off] = p[0]) != '\0'; p++)
168 if (p[0] == '\\' && p[1] == '\n') {
169 off += 2;
170 p++;
171 }
172 }
173 return (&fe);
174 }
175 if (lastc != '\n' || off != -1)
176 return (NULL);
177 buf[i] = '\0';
178 linecnt = 1;
179 return (&fe);
180 }
181
182 /*
183 * Parse minor number ranges:
184 * (minor) or (lowminor-highminor)
185 * Return 0 for success, -1 for failure.
186 */
187 int
parse_minor_range(const char * range,minor_t * lo,minor_t * hi,char * type)188 parse_minor_range(const char *range, minor_t *lo, minor_t *hi, char *type)
189 {
190 unsigned long tmp;
191 char *p;
192
193 if (*range++ != '(')
194 return (-1);
195
196 errno = 0;
197 tmp = strtoul(range, &p, 0);
198 if (tmp > L_MAXMIN32 || (tmp == 0 && errno != 0) ||
199 (*p != '-' && *p != ')'))
200 return (-1);
201 *lo = tmp;
202 if (*p == '-') {
203 errno = 0;
204 tmp = strtoul(p + 1, &p, 0);
205 if (tmp > L_MAXMIN32 || (tmp == 0 && errno != 0) || *p != ')')
206 return (-1);
207 }
208 *hi = tmp;
209 if (*lo > *hi)
210 return (-1);
211
212 switch (p[1]) {
213 case '\0':
214 *type = '\0';
215 break;
216 case 'c':
217 case 'C':
218 *type = 'c';
219 break;
220 case 'b':
221 case 'B':
222 *type = 'b';
223 break;
224 default:
225 return (-1);
226 }
227 return (0);
228 }
229
230 static void
put_minor_range(FILE * fp,fileentry_t * old,const char * devn,const char * tail,minor_t lo,minor_t hi,char type)231 put_minor_range(FILE *fp, fileentry_t *old, const char *devn, const char *tail,
232 minor_t lo, minor_t hi, char type)
233 {
234 /* Preserve preceeding comments */
235 if (old != NULL && old->rawbuf != old->orgentry)
236 (void) fwrite(old->rawbuf, 1, old->orgentry - old->rawbuf, fp);
237
238 if (type == '\0') {
239 put_minor_range(fp, NULL, devn, tail, lo, hi, 'b');
240 put_minor_range(fp, NULL, devn, tail, lo, hi, 'c');
241 } else if (lo == hi) {
242 (void) fprintf(fp, "%s:(%d)%c%s", devn, (int)lo, type, tail);
243 } else {
244 (void) fprintf(fp, "%s:(%d-%d)%c%s", devn, (int)lo, (int)hi,
245 type, tail);
246 }
247 }
248
249 static int
delete_one_entry(const char * filename,const char * entry)250 delete_one_entry(const char *filename, const char *entry)
251 {
252 char tfile[MAXPATHLEN];
253 char ofile[MAXPATHLEN];
254 char *nfile;
255 FILE *old, *new;
256 fileentry_t *fep;
257 struct stat buf;
258 int newfd;
259 char *mpart;
260 boolean_t delall;
261 boolean_t delrange;
262 minor_t rlo, rhi;
263 char rtype;
264
265 mpart = strchr(entry, ':');
266 if (mpart == NULL) {
267 delall = B_TRUE;
268 delrange = B_FALSE;
269 } else {
270 delall = B_FALSE;
271 mpart++;
272 if (*mpart == '(') {
273 if (parse_minor_range(mpart, &rlo, &rhi, &rtype) != 0)
274 return (-1);
275 delrange = B_TRUE;
276 } else {
277 delrange = B_FALSE;
278 }
279 }
280
281 if (strlen(filename) + sizeof (XEND) > sizeof (tfile))
282 return (-1);
283
284 old = fopen(filename, "r");
285
286 if (old == NULL)
287 return (-1);
288
289 (void) snprintf(tfile, sizeof (tfile), "%s%s", filename, XEND);
290 (void) snprintf(ofile, sizeof (ofile), "%s%s", filename, ".old");
291
292 nfile = mktemp(tfile);
293
294 new = fopen(nfile, "w");
295 if (new == NULL) {
296 (void) fclose(old);
297 return (ERROR);
298 }
299
300 newfd = fileno(new);
301
302 /* Copy permissions, ownership */
303 if (fstat(fileno(old), &buf) == 0) {
304 (void) fchown(newfd, buf.st_uid, buf.st_gid);
305 (void) fchmod(newfd, buf.st_mode);
306 } else {
307 (void) fchown(newfd, 0, 3); /* root:sys */
308 (void) fchmod(newfd, 0644);
309 }
310
311 while ((fep = fgetline(old))) {
312 char *tok;
313 char *min;
314 char *tail;
315 char tc;
316 int len;
317
318 /* Trailing comments */
319 if (fep->entry == NULL) {
320 (void) fputs(fep->rawbuf, new);
321 break;
322 }
323
324 tok = fep->entry;
325 while (*tok && isspace(*tok))
326 tok++;
327
328 if (*tok == '\0') {
329 (void) fputs(fep->rawbuf, new);
330 break;
331 }
332
333 /* Make sure we can recover the remainder incl. whitespace */
334 tail = strpbrk(tok, "\t\n ");
335 if (tail == NULL)
336 tail = tok + strlen(tok);
337 tc = *tail;
338 *tail = '\0';
339
340 if (delall || delrange) {
341 min = strchr(tok, ':');
342 if (min)
343 *min++ = '\0';
344 }
345
346 len = strlen(tok);
347 if (delrange) {
348 minor_t lo, hi;
349 char type;
350
351 /*
352 * Delete or shrink overlapping ranges.
353 */
354 if (strncmp(entry, tok, len) == 0 &&
355 entry[len] == ':' &&
356 min != NULL &&
357 parse_minor_range(min, &lo, &hi, &type) == 0 &&
358 (type == rtype || rtype == '\0') &&
359 lo <= rhi && hi >= rlo) {
360 minor_t newlo, newhi;
361
362 /* Complete overlap, then drop it. */
363 if (lo >= rlo && hi <= rhi)
364 continue;
365
366 /* Partial overlap, shrink range */
367 if (lo < rlo)
368 newhi = rlo - 1;
369 else
370 newhi = hi;
371 if (hi > rhi)
372 newlo = rhi + 1;
373 else
374 newlo = lo;
375
376 /* restore NULed character */
377 *tail = tc;
378
379 /* Split range? */
380 if (newlo > newhi) {
381 /*
382 * We have two ranges:
383 * lo ... newhi (== rlo - 1)
384 * newlo (== rhi + 1) .. hi
385 */
386 put_minor_range(new, fep, tok, tail,
387 lo, newhi, type);
388 put_minor_range(new, NULL, tok, tail,
389 newlo, hi, type);
390 } else {
391 put_minor_range(new, fep, tok, tail,
392 newlo, newhi, type);
393 }
394 continue;
395 }
396 } else if (strcmp(entry, tok) == 0 ||
397 (strncmp(entry, tok, len) == 0 &&
398 entry[len] == ':' &&
399 entry[len+1] == '*' &&
400 entry[len+2] == '\0')) {
401 /*
402 * Delete exact match.
403 */
404 continue;
405 }
406
407 /* Copy unaffected entry. */
408 (void) fputs(fep->rawbuf, new);
409 }
410 (void) fclose(old);
411 (void) fflush(new);
412 (void) fsync(newfd);
413 if (ferror(new) == 0 && fclose(new) == 0 && fep != NULL) {
414 if (rename(filename, ofile) != 0) {
415 perror(NULL);
416 (void) fprintf(stderr, gettext(ERR_UPDATE), ofile);
417 (void) unlink(ofile);
418 (void) unlink(nfile);
419 return (ERROR);
420 } else if (rename(nfile, filename) != 0) {
421 perror(NULL);
422 (void) fprintf(stderr, gettext(ERR_UPDATE), ofile);
423 (void) rename(ofile, filename);
424 (void) unlink(nfile);
425 return (ERROR);
426 }
427 (void) unlink(ofile);
428 } else
429 (void) unlink(nfile);
430 return (0);
431 }
432
433
434 int
delete_plcy_entry(const char * filename,const char * entry)435 delete_plcy_entry(const char *filename, const char *entry)
436 {
437 char *p, *single;
438 char *copy;
439 int ret = 0;
440
441 copy = strdup(entry);
442 if (copy == NULL)
443 return (ERROR);
444
445 for (single = strtok_r(copy, " \t\n", &p);
446 single != NULL;
447 single = strtok_r(NULL, " \t\n", &p)) {
448 if ((ret = delete_one_entry(filename, single)) != 0) {
449 free(copy);
450 return (ret);
451 }
452 }
453 free(copy);
454 return (0);
455 }
456
457 /*
458 * Analyze the device policy token; new tokens should be added to
459 * toktab; new token types should be coded here.
460 */
461 int
parse_plcy_token(char * token,devplcysys_t * dp)462 parse_plcy_token(char *token, devplcysys_t *dp)
463 {
464 char *val = strchr(token, '=');
465 const char *perr;
466 int i;
467 priv_set_t *pset;
468
469 if (val == NULL) {
470 (void) fprintf(stderr, gettext(ERR_NO_EQUALS), token);
471 return (1);
472 }
473 *val++ = '\0';
474
475 for (i = 0; i < NTOK; i++) {
476 if (strcmp(token, toktab[i].token) == 0) {
477 /* standard pointer computation for tokens */
478 void *item = (char *)dp + toktab[i].off;
479
480 switch (toktab[i].type) {
481 case PSET:
482 pset = priv_str_to_set(val, ",", &perr);
483 if (pset == NULL) {
484 if (perr == NULL) {
485 (void) fprintf(stderr,
486 gettext(ERR_NO_MEM));
487 } else {
488 (void) fprintf(stderr,
489 gettext(ERR_BAD_PRIVS),
490 perr - val, val, perr);
491 }
492 return (1);
493 }
494 priv_copyset(pset, item);
495 priv_freeset(pset);
496 break;
497 default:
498 (void) fprintf(stderr,
499 "Internal Error: bad token type: %d\n",
500 toktab[i].type);
501 return (1);
502 }
503 /* Standard cleanup & return for good tokens */
504 val[-1] = '=';
505 return (0);
506 }
507 }
508 (void) fprintf(stderr, gettext(ERR_BAD_TOKEN), token);
509 return (1);
510 }
511
512 static int
add2str(char ** dstp,const char * str,size_t * sz)513 add2str(char **dstp, const char *str, size_t *sz)
514 {
515 char *p = *dstp;
516 size_t len = strlen(p) + strlen(str) + 1;
517
518 if (len > *sz) {
519 *sz *= 2;
520 if (*sz < len)
521 *sz = len;
522 *dstp = p = realloc(p, *sz);
523 if (p == NULL) {
524 (void) fprintf(stderr, gettext(ERR_NO_MEM));
525 return (-1);
526 }
527 }
528 (void) strcat(p, str);
529 return (0);
530 }
531
532 /*
533 * Verify that the policy entry is valid and return the canonical entry.
534 */
535 char *
check_plcy_entry(char * entry,const char * driver,boolean_t todel)536 check_plcy_entry(char *entry, const char *driver, boolean_t todel)
537 {
538 char *res;
539 devplcysys_t *ds;
540 char *tok;
541 size_t sz = strlen(entry) * 2 + strlen(driver) + 3;
542 boolean_t tokseen = B_FALSE;
543
544 devplcy_init();
545
546 res = malloc(sz);
547 ds = alloca(devplcysys_sz);
548
549 if (res == NULL || ds == NULL) {
550 (void) fprintf(stderr, gettext(ERR_NO_MEM));
551 free(res);
552 return (NULL);
553 }
554
555 *res = '\0';
556
557 while ((tok = strtok(entry, " \t\n")) != NULL) {
558 entry = NULL;
559
560 /* It's not a token */
561 if (strchr(tok, '=') == NULL) {
562 if (strchr(tok, ':') != NULL) {
563 (void) fprintf(stderr, gettext(ERR_BAD_MINOR));
564 free(res);
565 return (NULL);
566 }
567 if (*res != '\0' && add2str(&res, "\n", &sz) != 0)
568 return (NULL);
569
570 if (*tok == '(') {
571 char type;
572 if (parse_minor_range(tok, &ds->dps_lomin,
573 &ds->dps_himin, &type) != 0 ||
574 (!todel && type == '\0')) {
575 (void) fprintf(stderr,
576 gettext(ERR_BAD_MINOR));
577 free(res);
578 return (NULL);
579 }
580 } else {
581 char *tmp = strchr(tok, '*');
582
583 if (tmp != NULL &&
584 strchr(tmp + 1, '*') != NULL) {
585 (void) fprintf(stderr,
586 gettext(ERR_BAD_MINOR));
587 free(res);
588 }
589 }
590
591 if (add2str(&res, driver, &sz) != 0)
592 return (NULL);
593 if (add2str(&res, ":", &sz) != 0)
594 return (NULL);
595 if (add2str(&res, tok, &sz) != 0)
596 return (NULL);
597 tokseen = B_FALSE;
598 } else {
599 if (*res == '\0') {
600 if (add2str(&res, driver, &sz) != 0)
601 return (NULL);
602 if (add2str(&res, ":*", &sz) != 0)
603 return (NULL);
604 }
605 if (parse_plcy_token(tok, ds) != 0) {
606 free(res);
607 return (NULL);
608 }
609
610 if (add2str(&res, "\t", &sz) != 0)
611 return (NULL);
612 if (add2str(&res, tok, &sz) != 0)
613 return (NULL);
614 tokseen = B_TRUE;
615 }
616 }
617 if ((todel && tokseen) || *res == '\0' || (!todel && !tokseen)) {
618 (void) fprintf(stderr, gettext(ERR_INVALID_PLCY));
619 free(res);
620 return (NULL);
621 }
622 if (!todel)
623 if (add2str(&res, "\n", &sz) != 0)
624 return (NULL);
625 return (res);
626 }
627
628 int
update_device_policy(const char * filename,const char * entry,boolean_t repl)629 update_device_policy(const char *filename, const char *entry, boolean_t repl)
630 {
631 FILE *fp;
632
633 if (repl) {
634 char *dup, *tok, *s1;
635
636 dup = strdup(entry);
637 if (dup == NULL) {
638 (void) fprintf(stderr, gettext(ERR_NO_MEM));
639 return (ERROR);
640 }
641
642 /*
643 * Split the entry in lines; then get the first token
644 * of each line.
645 */
646 for (tok = strtok_r(dup, "\n", &s1); tok != NULL;
647 tok = strtok_r(NULL, "\n", &s1)) {
648
649 tok = strtok(tok, " \n\t");
650
651 if (delete_one_entry(filename, tok) != 0) {
652 free(dup);
653 return (ERROR);
654 }
655 }
656
657 free(dup);
658 }
659
660 fp = fopen(filename, "a");
661 if (fp == NULL)
662 return (ERROR);
663
664 (void) fputs(entry, fp);
665
666 if (fflush(fp) != 0 || fsync(fileno(fp)) != 0 || fclose(fp) != 0)
667 return (ERROR);
668
669 return (NOERR);
670 }
671
672
673 /*
674 * We need to allocate the privileges now or the privilege set
675 * parsing code will not allow them.
676 */
677 int
check_priv_entry(const char * privlist,boolean_t add)678 check_priv_entry(const char *privlist, boolean_t add)
679 {
680 char *l = strdup(privlist);
681 char *pr;
682
683 if (l == NULL) {
684 (void) fprintf(stderr, gettext(ERR_NO_MEM));
685 return (ERROR);
686 }
687
688 while ((pr = strtok_r(l, ",", &l)) != NULL) {
689 /* Privilege already exists */
690 if (priv_getbyname(pr) != -1)
691 continue;
692
693 if (add && modctl(MODALLOCPRIV, pr) != 0) {
694 (void) fprintf(stderr, gettext(ERR_BAD_PRIV), pr,
695 strerror(errno));
696 return (ERROR);
697 }
698 }
699 return (NOERR);
700 }
701