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#pragma ident	"%Z%%M%	%I%	%E% SMI"
31
32#include <stdio.h>
33#include <stdlib.h>
34#include <unistd.h>
35#include <string.h>
36#include <ctype.h>
37#include <priv.h>
38#include <string.h>
39#include <libgen.h>
40#include <libintl.h>
41#include <errno.h>
42#include <alloca.h>
43#include <sys/modctl.h>
44#include <sys/devpolicy.h>
45#include <sys/stat.h>
46#include <sys/sysmacros.h>
47
48#include "addrem.h"
49#include "errmsg.h"
50#include "plcysubr.h"
51
52size_t devplcysys_sz;
53const priv_impl_info_t *privimplinfo;
54
55/*
56 * New token types should be parsed in parse_plcy_entry.
57 */
58#define	PSET	0
59
60typedef struct token {
61	const char	*token;
62	int		type;
63	ptrdiff_t	off;
64} token_t;
65
66static token_t toktab[] = {
67	{ DEVPLCY_TKN_RDP, PSET /* offsetof(devplcysys_t, dps_rdp) */ },
68	{ DEVPLCY_TKN_WRP, PSET /* offsetof(devplcysys_t, dps_wrp) */ },
69};
70
71#define	RDPOL	0
72#define	WRPOL	1
73
74#define	NTOK	(sizeof (toktab)/sizeof (token_t))
75
76/*
77 * Compute the size of the datastructures needed.
78 */
79void
80devplcy_init(void)
81{
82	if ((privimplinfo = getprivimplinfo()) == NULL) {
83		(void) fprintf(stderr, gettext(ERR_PRIVIMPL));
84		exit(1);
85	}
86
87	devplcysys_sz = DEVPLCYSYS_SZ(privimplinfo);
88
89	toktab[RDPOL].off =
90		(char *)DEVPLCYSYS_RDP((devplcysys_t *)0, privimplinfo) -
91				(char *)0;
92	toktab[WRPOL].off =
93		(char *)DEVPLCYSYS_WRP((devplcysys_t *)0, privimplinfo) -
94				(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 */
106fileentry_t *
107fgetline(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 */
187int
188parse_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
230static void
231put_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
249static int
250delete_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
434int
435delete_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 */
461int
462parse_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					return (1);
492				}
493				priv_copyset(pset, item);
494				priv_freeset(pset);
495				break;
496			default:
497				(void) fprintf(stderr,
498					"Internal Error: bad token type: %d\n",
499						toktab[i].type);
500				return (1);
501			}
502			/* Standard cleanup & return for good tokens */
503			val[-1] = '=';
504			return (0);
505		}
506	}
507	(void) fprintf(stderr, gettext(ERR_BAD_TOKEN), token);
508	return (1);
509}
510
511static int
512add2str(char **dstp, const char *str, size_t *sz)
513{
514	char *p = *dstp;
515	size_t len = strlen(p) + strlen(str) + 1;
516
517	if (len > *sz) {
518		*sz *= 2;
519		if (*sz < len)
520			*sz = len;
521		*dstp = p = realloc(p, *sz);
522		if (p == NULL) {
523			(void) fprintf(stderr, gettext(ERR_NO_MEM));
524			return (-1);
525		}
526	}
527	(void) strcat(p, str);
528	return (0);
529}
530
531/*
532 * Verify that the policy entry is valid and return the canonical entry.
533 */
534char *
535check_plcy_entry(char *entry, const char *driver, boolean_t todel)
536{
537	char *res;
538	devplcysys_t *ds;
539	char *tok;
540	size_t sz = strlen(entry) * 2 + strlen(driver) + 3;
541	boolean_t tokseen = B_FALSE;
542
543	devplcy_init();
544
545	res = malloc(sz);
546	ds = alloca(devplcysys_sz);
547
548	if (res == NULL || ds == NULL) {
549		(void) fprintf(stderr, gettext(ERR_NO_MEM));
550		return (NULL);
551	}
552
553	*res = '\0';
554
555	while ((tok = strtok(entry, " \t\n")) != NULL) {
556		entry = NULL;
557
558		/* It's not a token */
559		if (strchr(tok, '=') == NULL) {
560			if (strchr(tok, ':') != NULL) {
561				(void) fprintf(stderr, gettext(ERR_BAD_MINOR));
562				free(res);
563				return (NULL);
564			}
565			if (*res != '\0' && add2str(&res, "\n", &sz) != 0)
566				return (NULL);
567
568			if (*tok == '(') {
569				char type;
570				if (parse_minor_range(tok, &ds->dps_lomin,
571				    &ds->dps_himin, &type) != 0 ||
572				    (!todel && type == '\0')) {
573					(void) fprintf(stderr,
574					    gettext(ERR_BAD_MINOR));
575					free(res);
576					return (NULL);
577				}
578			} else {
579				char *tmp = strchr(tok, '*');
580
581				if (tmp != NULL &&
582				    strchr(tmp + 1, '*') != NULL) {
583					(void) fprintf(stderr,
584					    gettext(ERR_BAD_MINOR));
585					free(res);
586				}
587			}
588
589			if (add2str(&res, driver, &sz) != 0)
590				return (NULL);
591			if (add2str(&res, ":", &sz) != 0)
592				return (NULL);
593			if (add2str(&res, tok, &sz) != 0)
594				return (NULL);
595			tokseen = B_FALSE;
596		} else {
597			if (*res == '\0') {
598				if (add2str(&res, driver, &sz) != 0)
599					return (NULL);
600				if (add2str(&res, ":*", &sz) != 0)
601					return (NULL);
602			}
603			if (parse_plcy_token(tok, ds) != 0) {
604				free(res);
605				return (NULL);
606			}
607
608			if (add2str(&res, "\t", &sz) != 0)
609				return (NULL);
610			if (add2str(&res, tok, &sz) != 0)
611				return (NULL);
612			tokseen = B_TRUE;
613		}
614	}
615	if (todel && tokseen || *res == '\0' || !todel && !tokseen) {
616		(void) fprintf(stderr, gettext(ERR_INVALID_PLCY));
617		free(res);
618		return (NULL);
619	}
620	if (!todel)
621		if (add2str(&res, "\n", &sz) != 0)
622			return (NULL);
623	return (res);
624}
625
626int
627update_device_policy(const char *filename, const char *entry, boolean_t repl)
628{
629	FILE *fp;
630
631	if (repl) {
632		char *dup, *tok, *s1;
633
634		dup = strdup(entry);
635		if (dup == NULL) {
636			(void) fprintf(stderr, gettext(ERR_NO_MEM));
637			return (ERROR);
638		}
639
640		/*
641		 * Split the entry in lines; then get the first token
642		 * of each line.
643		 */
644		for (tok = strtok_r(dup, "\n", &s1); tok != NULL;
645		    tok = strtok_r(NULL, "\n", &s1)) {
646
647			tok = strtok(tok, " \n\t");
648
649			if (delete_one_entry(filename, tok) != 0) {
650				free(dup);
651				return (ERROR);
652			}
653		}
654
655		free(dup);
656	}
657
658	fp = fopen(filename, "a");
659	if (fp == NULL)
660		return (ERROR);
661
662	(void) fputs(entry, fp);
663
664	if (fflush(fp) != 0 || fsync(fileno(fp)) != 0 || fclose(fp) != 0)
665		return (ERROR);
666
667	return (NOERR);
668}
669
670
671/*
672 * We need to allocate the privileges now or the privilege set
673 * parsing code will not allow them.
674 */
675int
676check_priv_entry(const char *privlist, boolean_t add)
677{
678	char *l = strdup(privlist);
679	char *pr;
680
681	if (l == NULL) {
682		(void) fprintf(stderr, gettext(ERR_NO_MEM));
683		return (ERROR);
684	}
685
686	while ((pr = strtok_r(l, ",", &l)) != NULL) {
687		/* Privilege already exists */
688		if (priv_getbyname(pr) != -1)
689			continue;
690
691		if (add && modctl(MODALLOCPRIV, pr) != 0) {
692			(void) fprintf(stderr, gettext(ERR_BAD_PRIV), pr,
693				strerror(errno));
694			return (ERROR);
695		}
696	}
697	return (NOERR);
698}
699