xref: /illumos-gate/usr/src/cmd/modload/plcysubr.c (revision e4a991eb)
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