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