1/*
2 * File contexts backend for labeling system
3 *
4 * Author : Eamon Walsh <ewalsh@tycho.nsa.gov>
5 */
6
7#include <fcntl.h>
8#include <stdarg.h>
9#include <string.h>
10#include <stdio.h>
11#include <ctype.h>
12#include <errno.h>
13#include <limits.h>
14#include <regex.h>
15#include <sys/types.h>
16#include <sys/stat.h>
17#include <unistd.h>
18#include "callbacks.h"
19#include "label_internal.h"
20
21/*
22 * Internals, mostly moved over from matchpathcon.c
23 */
24
25/* A file security context specification. */
26typedef struct spec {
27	struct selabel_lookup_rec lr;	/* holds contexts for lookup result */
28	char *regex_str;	/* regular expession string for diagnostics */
29	char *type_str;		/* type string for diagnostic messages */
30	regex_t regex;		/* compiled regular expression */
31	char regcomp;           /* regex_str has been compiled to regex */
32	mode_t mode;		/* mode format value */
33	int matches;		/* number of matching pathnames */
34	int hasMetaChars;	/* regular expression has meta-chars */
35	int stem_id;		/* indicates which stem-compression item */
36} spec_t;
37
38/* A regular expression stem */
39typedef struct stem {
40	char *buf;
41	int len;
42} stem_t;
43
44/* Our stored configuration */
45struct saved_data {
46	/*
47	 * The array of specifications, initially in the same order as in
48	 * the specification file. Sorting occurs based on hasMetaChars.
49	 */
50	spec_t *spec_arr;
51	unsigned int nspec;
52	unsigned int ncomp;
53
54	/*
55	 * The array of regular expression stems.
56	 */
57	stem_t *stem_arr;
58	int num_stems;
59	int alloc_stems;
60};
61
62/* Return the length of the text that can be considered the stem, returns 0
63 * if there is no identifiable stem */
64static int get_stem_from_spec(const char *const buf)
65{
66	const char *tmp = strchr(buf + 1, '/');
67	const char *ind;
68
69	if (!tmp)
70		return 0;
71
72	for (ind = buf; ind < tmp; ind++) {
73		if (strchr(".^$?*+|[({", (int)*ind))
74			return 0;
75	}
76	return tmp - buf;
77}
78
79/* return the length of the text that is the stem of a file name */
80static int get_stem_from_file_name(const char *const buf)
81{
82	const char *tmp = strchr(buf + 1, '/');
83
84	if (!tmp)
85		return 0;
86	return tmp - buf;
87}
88
89/* find the stem of a file spec, returns the index into stem_arr for a new
90 * or existing stem, (or -1 if there is no possible stem - IE for a file in
91 * the root directory or a regex that is too complex for us). */
92static int find_stem_from_spec(struct saved_data *data, const char *buf)
93{
94	int i, num = data->num_stems;
95	int stem_len = get_stem_from_spec(buf);
96
97	if (!stem_len)
98		return -1;
99	for (i = 0; i < num; i++) {
100		if (stem_len == data->stem_arr[i].len
101		    && !strncmp(buf, data->stem_arr[i].buf, stem_len))
102			return i;
103	}
104	if (data->alloc_stems == num) {
105		stem_t *tmp_arr;
106		data->alloc_stems = data->alloc_stems * 2 + 16;
107		tmp_arr = realloc(data->stem_arr,
108				  sizeof(stem_t) * data->alloc_stems);
109		if (!tmp_arr)
110			return -1;
111		data->stem_arr = tmp_arr;
112	}
113	data->stem_arr[num].len = stem_len;
114	data->stem_arr[num].buf = malloc(stem_len + 1);
115	if (!data->stem_arr[num].buf)
116		return -1;
117	memcpy(data->stem_arr[num].buf, buf, stem_len);
118	data->stem_arr[num].buf[stem_len] = '\0';
119	data->num_stems++;
120	buf += stem_len;
121	return num;
122}
123
124/* find the stem of a file name, returns the index into stem_arr (or -1 if
125 * there is no match - IE for a file in the root directory or a regex that is
126 * too complex for us).  Makes buf point to the text AFTER the stem. */
127static int find_stem_from_file(struct saved_data *data, const char **buf)
128{
129	int i;
130	int stem_len = get_stem_from_file_name(*buf);
131
132	if (!stem_len)
133		return -1;
134	for (i = 0; i < data->num_stems; i++) {
135		if (stem_len == data->stem_arr[i].len
136		    && !strncmp(*buf, data->stem_arr[i].buf, stem_len)) {
137			*buf += stem_len;
138			return i;
139		}
140	}
141	return -1;
142}
143
144/*
145 * Warn about duplicate specifications.
146 */
147static int nodups_specs(struct saved_data *data, const char *path)
148{
149	int rc = 0;
150	unsigned int ii, jj;
151	struct spec *curr_spec, *spec_arr = data->spec_arr;
152
153	for (ii = 0; ii < data->nspec; ii++) {
154		curr_spec = &spec_arr[ii];
155		for (jj = ii + 1; jj < data->nspec; jj++) {
156			if ((!strcmp
157			     (spec_arr[jj].regex_str, curr_spec->regex_str))
158			    && (!spec_arr[jj].mode || !curr_spec->mode
159				|| spec_arr[jj].mode == curr_spec->mode)) {
160				rc = -1;
161				errno = EINVAL;
162				if (strcmp
163				    (spec_arr[jj].lr.ctx_raw,
164				     curr_spec->lr.ctx_raw)) {
165					selinux_log
166						(SELINUX_ERROR,
167						 "%s: Multiple different specifications for %s  (%s and %s).\n",
168						 path, curr_spec->regex_str,
169						 spec_arr[jj].lr.ctx_raw,
170						 curr_spec->lr.ctx_raw);
171				} else {
172					selinux_log
173						(SELINUX_ERROR,
174						 "%s: Multiple same specifications for %s.\n",
175						 path, curr_spec->regex_str);
176				}
177			}
178		}
179	}
180	return rc;
181}
182
183/* Determine if the regular expression specification has any meta characters. */
184static void spec_hasMetaChars(struct spec *spec)
185{
186	char *c;
187	int len;
188	char *end;
189
190	c = spec->regex_str;
191	len = strlen(spec->regex_str);
192	end = c + len;
193
194	spec->hasMetaChars = 0;
195
196	/* Look at each character in the RE specification string for a
197	 * meta character. Return when any meta character reached. */
198	while (c != end) {
199		switch (*c) {
200		case '.':
201		case '^':
202		case '$':
203		case '?':
204		case '*':
205		case '+':
206		case '|':
207		case '[':
208		case '(':
209		case '{':
210			spec->hasMetaChars = 1;
211			return;
212		case '\\':	/* skip the next character */
213			c++;
214			break;
215		default:
216			break;
217
218		}
219		c++;
220	}
221	return;
222}
223
224static int compile_regex(struct saved_data *data, spec_t *spec, char **errbuf)
225{
226	char *reg_buf, *anchored_regex, *cp;
227	stem_t *stem_arr = data->stem_arr;
228	size_t len;
229	int regerr;
230
231	if (spec->regcomp)
232		return 0; /* already done */
233
234	data->ncomp++; /* how many compiled regexes required */
235
236	/* Skip the fixed stem. */
237	reg_buf = spec->regex_str;
238	if (spec->stem_id >= 0)
239		reg_buf += stem_arr[spec->stem_id].len;
240
241	/* Anchor the regular expression. */
242	len = strlen(reg_buf);
243	cp = anchored_regex = malloc(len + 3);
244	if (!anchored_regex)
245		return -1;
246	/* Create ^...$ regexp.  */
247	*cp++ = '^';
248	memcpy(cp, reg_buf, len);
249	cp += len;
250	*cp++ = '$';
251	*cp = '\0';
252
253	/* Compile the regular expression. */
254	regerr = regcomp(&spec->regex, anchored_regex,
255			 REG_EXTENDED | REG_NOSUB);
256	if (regerr != 0) {
257		size_t errsz = 0;
258		errsz = regerror(regerr, &spec->regex, NULL, 0);
259		if (errsz && errbuf)
260			*errbuf = malloc(errsz);
261		if (errbuf && *errbuf)
262			(void)regerror(regerr, &spec->regex,
263				       *errbuf, errsz);
264
265		free(anchored_regex);
266		return -1;
267	}
268	free(anchored_regex);
269
270	/* Done. */
271	spec->regcomp = 1;
272
273	return 0;
274}
275
276
277static int process_line(struct selabel_handle *rec,
278			const char *path, const char *prefix,
279			char *line_buf, int pass, unsigned lineno)
280{
281	int items, len;
282	char buf1[BUFSIZ], buf2[BUFSIZ], buf3[BUFSIZ];
283	char *buf_p, *regex = buf1, *type = buf2, *context = buf3;
284	struct saved_data *data = (struct saved_data *)rec->data;
285	spec_t *spec_arr = data->spec_arr;
286	unsigned int nspec = data->nspec;
287
288	len = strlen(line_buf);
289	if (line_buf[len - 1] == '\n')
290		line_buf[len - 1] = 0;
291	buf_p = line_buf;
292	while (isspace(*buf_p))
293		buf_p++;
294	/* Skip comment lines and empty lines. */
295	if (*buf_p == '#' || *buf_p == 0)
296		return 0;
297	items = sscanf(line_buf, "%255s %255s %255s", regex, type, context);
298	if (items < 2) {
299		selinux_log(SELINUX_WARNING,
300			    "%s:  line %d is missing fields, skipping\n", path,
301			    lineno);
302		return 0;
303	} else if (items == 2) {
304		/* The type field is optional. */
305		context = type;
306		type = NULL;
307	}
308
309	len = get_stem_from_spec(regex);
310	if (len && prefix && strncmp(prefix, regex, len)) {
311		/* Stem of regex does not match requested prefix, discard. */
312		return 0;
313	}
314
315	if (pass == 1) {
316		/* On the second pass, process and store the specification in spec. */
317		char *errbuf = NULL;
318		spec_arr[nspec].stem_id = find_stem_from_spec(data, regex);
319		spec_arr[nspec].regex_str = strdup(regex);
320		if (!spec_arr[nspec].regex_str) {
321			selinux_log(SELINUX_WARNING,
322				   "%s:  out of memory at line %d on regex %s\n",
323				   path, lineno, regex);
324			return -1;
325
326		}
327		if (rec->validating && compile_regex(data, &spec_arr[nspec], &errbuf)) {
328			selinux_log(SELINUX_WARNING,
329				   "%s:  line %d has invalid regex %s:  %s\n",
330				   path, lineno, regex,
331				   (errbuf ? errbuf : "out of memory"));
332		}
333
334		/* Convert the type string to a mode format */
335		spec_arr[nspec].mode = 0;
336		if (!type)
337			goto skip_type;
338		spec_arr[nspec].type_str = strdup(type);
339		len = strlen(type);
340		if (type[0] != '-' || len != 2) {
341			selinux_log(SELINUX_WARNING,
342				    "%s:  line %d has invalid file type %s\n",
343				    path, lineno, type);
344			return 0;
345		}
346		switch (type[1]) {
347		case 'b':
348			spec_arr[nspec].mode = S_IFBLK;
349			break;
350		case 'c':
351			spec_arr[nspec].mode = S_IFCHR;
352			break;
353		case 'd':
354			spec_arr[nspec].mode = S_IFDIR;
355			break;
356		case 'p':
357			spec_arr[nspec].mode = S_IFIFO;
358			break;
359		case 'l':
360			spec_arr[nspec].mode = S_IFLNK;
361			break;
362		case 's':
363			spec_arr[nspec].mode = S_IFSOCK;
364			break;
365		case '-':
366			spec_arr[nspec].mode = S_IFREG;
367			break;
368		default:
369			selinux_log(SELINUX_WARNING,
370				    "%s:  line %d has invalid file type %s\n",
371				    path, lineno, type);
372			return 0;
373		}
374
375	skip_type:
376		spec_arr[nspec].lr.ctx_raw = strdup(context);
377
378		if (strcmp(context, "<<none>>") && rec->validating) {
379			if (selabel_validate(rec, &spec_arr[nspec].lr) < 0) {
380				selinux_log(SELINUX_WARNING,
381					    "%s:  line %d has invalid context %s\n",
382					    path, lineno, spec_arr[nspec].lr.ctx_raw);
383			}
384		}
385
386		/* Determine if specification has
387		 * any meta characters in the RE */
388		spec_hasMetaChars(&spec_arr[nspec]);
389	}
390
391	data->nspec = ++nspec;
392	return 0;
393}
394
395static int init(struct selabel_handle *rec, const struct selinux_opt *opts,
396		unsigned n)
397{
398	struct saved_data *data = (struct saved_data *)rec->data;
399	const char *path = NULL;
400	const char *prefix = NULL;
401	FILE *fp;
402	FILE *localfp = NULL;
403	FILE *homedirfp = NULL;
404	char local_path[PATH_MAX + 1];
405	char homedir_path[PATH_MAX + 1];
406	char line_buf[BUFSIZ];
407	unsigned int lineno, pass, i, j, maxnspec;
408	spec_t *spec_copy = NULL;
409	int status = -1, baseonly = 0;
410	struct stat sb;
411
412	/* Process arguments */
413	while (n--)
414		switch(opts[n].type) {
415		case SELABEL_OPT_PATH:
416			path = opts[n].value;
417			break;
418		case SELABEL_OPT_SUBSET:
419			prefix = opts[n].value;
420			break;
421		case SELABEL_OPT_BASEONLY:
422			baseonly = !!opts[n].value;
423			break;
424		}
425
426	/* Open the specification file. */
427	if ((fp = fopen(path, "r")) == NULL)
428		return -1;
429
430	if (fstat(fileno(fp), &sb) < 0)
431		return -1;
432	if (!S_ISREG(sb.st_mode)) {
433		errno = EINVAL;
434		return -1;
435	}
436
437	if (!baseonly) {
438		snprintf(homedir_path, sizeof(homedir_path), "%s.homedirs",
439			 path);
440		homedirfp = fopen(homedir_path, "r");
441
442		snprintf(local_path, sizeof(local_path), "%s.local", path);
443		localfp = fopen(local_path, "r");
444	}
445
446	/*
447	 * Perform two passes over the specification file.
448	 * The first pass counts the number of specifications and
449	 * performs simple validation of the input.  At the end
450	 * of the first pass, the spec array is allocated.
451	 * The second pass performs detailed validation of the input
452	 * and fills in the spec array.
453	 */
454	maxnspec = UINT_MAX / sizeof(spec_t);
455	for (pass = 0; pass < 2; pass++) {
456		lineno = 0;
457		data->nspec = 0;
458		data->ncomp = 0;
459		while (fgets(line_buf, sizeof line_buf - 1, fp)
460		       && data->nspec < maxnspec) {
461			if (process_line(rec, path, prefix, line_buf,
462					 pass, ++lineno) != 0)
463				goto finish;
464		}
465		if (pass == 1) {
466			status = nodups_specs(data, path);
467			if (status)
468				goto finish;
469		}
470		lineno = 0;
471		if (homedirfp)
472			while (fgets(line_buf, sizeof line_buf - 1, homedirfp)
473			       && data->nspec < maxnspec) {
474				if (process_line
475				    (rec, homedir_path, prefix,
476				     line_buf, pass, ++lineno) != 0)
477					goto finish;
478			}
479
480		lineno = 0;
481		if (localfp)
482			while (fgets(line_buf, sizeof line_buf - 1, localfp)
483			       && data->nspec < maxnspec) {
484				if (process_line
485				    (rec, local_path, prefix, line_buf,
486				     pass, ++lineno) != 0)
487					goto finish;
488			}
489
490		if (pass == 0) {
491			if (data->nspec == 0) {
492				status = 0;
493				goto finish;
494			}
495			if (NULL == (data->spec_arr =
496				     malloc(sizeof(spec_t) * data->nspec)))
497				goto finish;
498			memset(data->spec_arr, 0, sizeof(spec_t)*data->nspec);
499			maxnspec = data->nspec;
500			rewind(fp);
501			if (homedirfp)
502				rewind(homedirfp);
503			if (localfp)
504				rewind(localfp);
505		}
506	}
507
508	/* Move exact pathname specifications to the end. */
509	spec_copy = malloc(sizeof(spec_t) * data->nspec);
510	if (!spec_copy)
511		goto finish;
512	j = 0;
513	for (i = 0; i < data->nspec; i++)
514		if (data->spec_arr[i].hasMetaChars)
515			memcpy(&spec_copy[j++],
516			       &data->spec_arr[i], sizeof(spec_t));
517	for (i = 0; i < data->nspec; i++)
518		if (!data->spec_arr[i].hasMetaChars)
519			memcpy(&spec_copy[j++],
520			       &data->spec_arr[i], sizeof(spec_t));
521	free(data->spec_arr);
522	data->spec_arr = spec_copy;
523
524	status = 0;
525finish:
526	fclose(fp);
527	if (data->spec_arr != spec_copy)
528		free(data->spec_arr);
529	if (homedirfp)
530		fclose(homedirfp);
531	if (localfp)
532		fclose(localfp);
533	return status;
534}
535
536/*
537 * Backend interface routines
538 */
539static void closef(struct selabel_handle *rec)
540{
541	struct saved_data *data = (struct saved_data *)rec->data;
542	struct spec *spec;
543	struct stem *stem;
544	unsigned int i;
545
546	for (i = 0; i < data->nspec; i++) {
547		spec = &data->spec_arr[i];
548		free(spec->regex_str);
549		free(spec->type_str);
550		free(spec->lr.ctx_raw);
551		free(spec->lr.ctx_trans);
552		if (spec->regcomp)
553			regfree(&spec->regex);
554	}
555
556	for (i = 0; i < (unsigned int)data->num_stems; i++) {
557		stem = &data->stem_arr[i];
558		free(stem->buf);
559	}
560
561	if (data->spec_arr)
562		free(data->spec_arr);
563	if (data->stem_arr)
564		free(data->stem_arr);
565
566	free(data);
567}
568
569static struct selabel_lookup_rec *lookup(struct selabel_handle *rec,
570					 const char *key, int type)
571{
572	struct saved_data *data = (struct saved_data *)rec->data;
573	spec_t *spec_arr = data->spec_arr;
574	int i, rc, file_stem;
575	mode_t mode = (mode_t)type;
576	const char *buf;
577	struct selabel_lookup_rec *ret = NULL;
578	char *clean_key = NULL;
579	const char *prev_slash, *next_slash;
580	unsigned int sofar = 0;
581
582	if (!data->nspec) {
583		errno = ENOENT;
584		goto finish;
585	}
586
587	/* Remove duplicate slashes */
588	if ((next_slash = strstr(key, "//"))) {
589		clean_key = malloc(strlen(key) + 1);
590		if (!clean_key)
591			goto finish;
592		prev_slash = key;
593		while (next_slash) {
594			memcpy(clean_key + sofar, prev_slash, next_slash - prev_slash);
595			sofar += next_slash - prev_slash;
596			prev_slash = next_slash + 1;
597			next_slash = strstr(prev_slash, "//");
598		}
599		strcpy(clean_key + sofar, prev_slash);
600		key = clean_key;
601	}
602
603	buf = key;
604	file_stem = find_stem_from_file(data, &buf);
605	mode &= S_IFMT;
606
607	/*
608	 * Check for matching specifications in reverse order, so that
609	 * the last matching specification is used.
610	 */
611	for (i = data->nspec - 1; i >= 0; i--) {
612		/* if the spec in question matches no stem or has the same
613		 * stem as the file AND if the spec in question has no mode
614		 * specified or if the mode matches the file mode then we do
615		 * a regex check        */
616		if ((spec_arr[i].stem_id == -1
617		     || spec_arr[i].stem_id == file_stem)
618		    && (!mode || !spec_arr[i].mode
619			|| mode == spec_arr[i].mode)) {
620			if (compile_regex(data, &spec_arr[i], NULL) < 0)
621				goto finish;
622			if (spec_arr[i].stem_id == -1)
623				rc = regexec(&spec_arr[i].regex, key, 0, 0, 0);
624			else
625				rc = regexec(&spec_arr[i].regex, buf, 0, 0, 0);
626
627			if (rc == 0) {
628				spec_arr[i].matches++;
629				break;
630			}
631			if (rc == REG_NOMATCH)
632				continue;
633			/* else it's an error */
634			goto finish;
635		}
636	}
637
638	if (i < 0 || strcmp(spec_arr[i].lr.ctx_raw, "<<none>>") == 0) {
639		/* No matching specification. */
640		errno = ENOENT;
641		goto finish;
642	}
643
644	ret = &spec_arr[i].lr;
645
646finish:
647	free(clean_key);
648	return ret;
649}
650
651static void stats(struct selabel_handle *rec)
652{
653	struct saved_data *data = (struct saved_data *)rec->data;
654	unsigned int i, nspec = data->nspec;
655	spec_t *spec_arr = data->spec_arr;
656
657	for (i = 0; i < nspec; i++) {
658		if (spec_arr[i].matches == 0) {
659			if (spec_arr[i].type_str) {
660				selinux_log(SELINUX_WARNING,
661				    "Warning!  No matches for (%s, %s, %s)\n",
662				    spec_arr[i].regex_str,
663				    spec_arr[i].type_str,
664				    spec_arr[i].lr.ctx_raw);
665			} else {
666				selinux_log(SELINUX_WARNING,
667				    "Warning!  No matches for (%s, %s)\n",
668				    spec_arr[i].regex_str,
669				    spec_arr[i].lr.ctx_raw);
670			}
671		}
672	}
673}
674
675int selabel_file_init(struct selabel_handle *rec, const struct selinux_opt *opts,
676		      unsigned nopts)
677{
678	struct saved_data *data;
679
680	data = (struct saved_data *)malloc(sizeof(*data));
681	if (!data)
682		return -1;
683	memset(data, 0, sizeof(*data));
684
685	rec->data = data;
686	rec->func_close = &closef;
687	rec->func_stats = &stats;
688	rec->func_lookup = &lookup;
689
690	return init(rec, opts, nopts);
691}
692