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