Home | History | Annotate | Download | only in setfiles
      1 #include "restore.h"
      2 #include <unistd.h>
      3 #include <fcntl.h>
      4 #include <stdio_ext.h>
      5 #include <ctype.h>
      6 #include <regex.h>
      7 #include <sys/vfs.h>
      8 #define __USE_XOPEN_EXTENDED 1	/* nftw */
      9 #include <libgen.h>
     10 #ifdef USE_AUDIT
     11 #include <libaudit.h>
     12 
     13 #ifndef AUDIT_FS_RELABEL
     14 #define AUDIT_FS_RELABEL 2309
     15 #endif
     16 #endif
     17 
     18 
     19 /* cmdline opts*/
     20 
     21 static char *policyfile = NULL;
     22 static int warn_no_match = 0;
     23 static int null_terminated = 0;
     24 static struct restore_opts r_opts;
     25 
     26 #define STAT_BLOCK_SIZE 1
     27 
     28 /* setfiles will abort its operation after reaching the
     29  * following number of errors (e.g. invalid contexts),
     30  * unless it is used in "debug" mode (-d option).
     31  */
     32 #ifndef ABORT_ON_ERRORS
     33 #define ABORT_ON_ERRORS	10
     34 #endif
     35 
     36 #define SETFILES "setfiles"
     37 #define RESTORECON "restorecon"
     38 static int iamrestorecon;
     39 
     40 /* Behavior flags determined based on setfiles vs. restorecon */
     41 static int ctx_validate; /* Validate contexts */
     42 static const char *altpath; /* Alternate path to file_contexts */
     43 
     44 void usage(const char *const name)
     45 {
     46 	if (iamrestorecon) {
     47 		fprintf(stderr,
     48 			"usage:  %s [-iFnprRv0] [-e excludedir] pathname...\n"
     49 			"usage:  %s [-iFnprRv0] [-e excludedir] -f filename\n",
     50 			name, name);
     51 	} else {
     52 		fprintf(stderr,
     53 			"usage:  %s [-dilnpqvFW] [-e excludedir] [-r alt_root_path] spec_file pathname...\n"
     54 			"usage:  %s [-dilnpqvFW] [-e excludedir] [-r alt_root_path] spec_file -f filename\n"
     55 			"usage:  %s -s [-dilnpqvFW] spec_file\n"
     56 			"usage:  %s -c policyfile spec_file\n",
     57 			name, name, name, name);
     58 	}
     59 	exit(-1);
     60 }
     61 
     62 static int nerr = 0;
     63 
     64 void inc_err(void)
     65 {
     66 	nerr++;
     67 	if (nerr > ABORT_ON_ERRORS - 1 && !r_opts.debug) {
     68 		fprintf(stderr, "Exiting after %d errors.\n", ABORT_ON_ERRORS);
     69 		exit(-1);
     70 	}
     71 }
     72 
     73 
     74 
     75 void set_rootpath(const char *arg)
     76 {
     77 	int len;
     78 
     79 	r_opts.rootpath = strdup(arg);
     80 	if (NULL == r_opts.rootpath) {
     81 		fprintf(stderr, "%s:  insufficient memory for r_opts.rootpath\n",
     82 			r_opts.progname);
     83 		exit(-1);
     84 	}
     85 
     86 	/* trim trailing /, if present */
     87 	len = strlen(r_opts.rootpath);
     88 	while (len && ('/' == r_opts.rootpath[len - 1]))
     89 		r_opts.rootpath[--len] = 0;
     90 	r_opts.rootpathlen = len;
     91 }
     92 
     93 int canoncon(char **contextp)
     94 {
     95 	char *context = *contextp, *tmpcon;
     96 	int rc = 0;
     97 
     98 	if (policyfile) {
     99 		if (sepol_check_context(context) < 0) {
    100 			fprintf(stderr, "invalid context %s\n", context);
    101 			exit(-1);
    102 		}
    103 	} else if (security_canonicalize_context_raw(context, &tmpcon) == 0) {
    104 		free(context);
    105 		*contextp = tmpcon;
    106 	} else if (errno != ENOENT) {
    107 		rc = -1;
    108 		inc_err();
    109 	}
    110 
    111 	return rc;
    112 }
    113 
    114 #ifndef USE_AUDIT
    115 static void maybe_audit_mass_relabel(int mass_relabel __attribute__((unused)),
    116 				     int mass_relabel_errs __attribute__((unused)))
    117 {
    118 #else
    119 static void maybe_audit_mass_relabel(int mass_relabel, int mass_relabel_errs)
    120 {
    121 	int audit_fd = -1;
    122 	int rc = 0;
    123 
    124 	if (!mass_relabel)		/* only audit a forced full relabel */
    125 		return;
    126 
    127 	audit_fd = audit_open();
    128 
    129 	if (audit_fd < 0) {
    130 		fprintf(stderr, "Error connecting to audit system.\n");
    131 		exit(-1);
    132 	}
    133 
    134 	rc = audit_log_user_message(audit_fd, AUDIT_FS_RELABEL,
    135 				    "op=mass relabel", NULL, NULL, NULL, !mass_relabel_errs);
    136 	if (rc <= 0) {
    137 		fprintf(stderr, "Error sending audit message: %s.\n",
    138 			strerror(errno));
    139 		/* exit(-1); -- don't exit atm. as fix for eff_cap isn't in most kernels */
    140 	}
    141 	audit_close(audit_fd);
    142 #endif
    143 }
    144 
    145 int main(int argc, char **argv)
    146 {
    147 	struct stat sb;
    148 	int opt, i = 0;
    149 	const char *input_filename = NULL;
    150 	int use_input_file = 0;
    151 	char *buf = NULL;
    152 	size_t buf_len;
    153 	int recurse; /* Recursive descent. */
    154 	const char *base;
    155 	int mass_relabel = 0, errors = 0;
    156 	const char *ropts = "e:f:hilno:pqrsvFRW0";
    157 	const char *sopts = "c:de:f:hilno:pqr:svFR:W0";
    158 	const char *opts;
    159 
    160 	memset(&r_opts, 0, sizeof(r_opts));
    161 
    162 	/* Initialize variables */
    163 	r_opts.progress = 0;
    164 	r_opts.count = 0;
    165 	r_opts.nfile = 0;
    166 	r_opts.debug = 0;
    167 	r_opts.change = 1;
    168 	r_opts.verbose = 0;
    169 	r_opts.logging = 0;
    170 	r_opts.rootpath = NULL;
    171 	r_opts.rootpathlen = 0;
    172 	r_opts.outfile = NULL;
    173 	r_opts.force = 0;
    174 	r_opts.hard_links = 1;
    175 
    176 	altpath = NULL;
    177 
    178 	r_opts.progname = strdup(argv[0]);
    179 	if (!r_opts.progname) {
    180 		fprintf(stderr, "%s:  Out of memory!\n", argv[0]);
    181 		exit(-1);
    182 	}
    183 	base = basename(r_opts.progname);
    184 
    185 	if (!strcmp(base, SETFILES)) {
    186 		/*
    187 		 * setfiles:
    188 		 * Recursive descent,
    189 		 * Does not expand paths via realpath,
    190 		 * Aborts on errors during the file tree walk,
    191 		 * Try to track inode associations for conflict detection,
    192 		 * Does not follow mounts,
    193 		 * Validates all file contexts at init time.
    194 		 */
    195 		iamrestorecon = 0;
    196 		recurse = 1;
    197 		r_opts.expand_realpath = 0;
    198 		r_opts.abort_on_error = 1;
    199 		r_opts.add_assoc = 1;
    200 		r_opts.fts_flags = FTS_PHYSICAL | FTS_XDEV;
    201 		ctx_validate = 1;
    202 		opts = sopts;
    203 	} else {
    204 		/*
    205 		 * restorecon:
    206 		 * No recursive descent unless -r/-R,
    207 		 * Expands paths via realpath,
    208 		 * Do not abort on errors during the file tree walk,
    209 		 * Do not try to track inode associations for conflict detection,
    210 		 * Follows mounts,
    211 		 * Does lazy validation of contexts upon use.
    212 		 */
    213 		if (strcmp(base, RESTORECON) && !r_opts.quiet)
    214 			printf("Executed with an unrecognized name (%s), defaulting to %s behavior.\n", base, RESTORECON);
    215 		iamrestorecon = 1;
    216 		recurse = 0;
    217 		r_opts.expand_realpath = 1;
    218 		r_opts.abort_on_error = 0;
    219 		r_opts.add_assoc = 0;
    220 		r_opts.fts_flags = FTS_PHYSICAL;
    221 		ctx_validate = 0;
    222 		opts = ropts;
    223 
    224 		/* restorecon only:  silent exit if no SELinux.
    225 		   Allows unconditional execution by scripts. */
    226 		if (is_selinux_enabled() <= 0)
    227 			exit(0);
    228 	}
    229 
    230 	/* This must happen before getopt. */
    231 	r_opts.nfile = exclude_non_seclabel_mounts();
    232 
    233 	/* Process any options. */
    234 	while ((opt = getopt(argc, argv, opts)) > 0) {
    235 		switch (opt) {
    236 		case 'c':
    237 			{
    238 				FILE *policystream;
    239 
    240 				if (iamrestorecon)
    241 					usage(argv[0]);
    242 
    243 				policyfile = optarg;
    244 
    245 				policystream = fopen(policyfile, "r");
    246 				if (!policystream) {
    247 					fprintf(stderr,
    248 						"Error opening %s: %s\n",
    249 						policyfile, strerror(errno));
    250 					exit(-1);
    251 				}
    252 				__fsetlocking(policystream,
    253 					      FSETLOCKING_BYCALLER);
    254 
    255 				if (sepol_set_policydb_from_file(policystream) <
    256 				    0) {
    257 					fprintf(stderr,
    258 						"Error reading policy %s: %s\n",
    259 						policyfile, strerror(errno));
    260 					exit(-1);
    261 				}
    262 				fclose(policystream);
    263 
    264 				ctx_validate = 1;
    265 
    266 				break;
    267 			}
    268 		case 'e':
    269 			remove_exclude(optarg);
    270 			if (lstat(optarg, &sb) < 0 && errno != EACCES) {
    271 				fprintf(stderr, "Can't stat exclude path \"%s\", %s - ignoring.\n",
    272 					optarg, strerror(errno));
    273 				break;
    274 			}
    275 			if (add_exclude(optarg))
    276 				exit(-1);
    277 			break;
    278 		case 'f':
    279 			use_input_file = 1;
    280 			input_filename = optarg;
    281 			break;
    282 		case 'd':
    283 			if (iamrestorecon)
    284 				usage(argv[0]);
    285 			r_opts.debug = 1;
    286 			break;
    287 		case 'i':
    288 			r_opts.ignore_enoent = 1;
    289 			break;
    290 		case 'l':
    291 			r_opts.logging = 1;
    292 			break;
    293 		case 'F':
    294 			r_opts.force = 1;
    295 			break;
    296 		case 'n':
    297 			r_opts.change = 0;
    298 			break;
    299 		case 'o':
    300 			if (strcmp(optarg, "-") == 0) {
    301 				r_opts.outfile = stdout;
    302 				break;
    303 			}
    304 
    305 			r_opts.outfile = fopen(optarg, "w");
    306 			if (!r_opts.outfile) {
    307 				fprintf(stderr, "Error opening %s: %s\n",
    308 					optarg, strerror(errno));
    309 
    310 				usage(argv[0]);
    311 			}
    312 			__fsetlocking(r_opts.outfile, FSETLOCKING_BYCALLER);
    313 			break;
    314 		case 'q':
    315 			r_opts.quiet = 1;
    316 			break;
    317 		case 'R':
    318 		case 'r':
    319 			if (iamrestorecon) {
    320 				recurse = 1;
    321 				break;
    322 			}
    323 			if (NULL != r_opts.rootpath) {
    324 				fprintf(stderr,
    325 					"%s: only one -r can be specified\n",
    326 					argv[0]);
    327 				exit(-1);
    328 			}
    329 			set_rootpath(optarg);
    330 			break;
    331 		case 's':
    332 			use_input_file = 1;
    333 			input_filename = "-";
    334 			r_opts.add_assoc = 0;
    335 			break;
    336 		case 'v':
    337 			if (r_opts.progress) {
    338 				fprintf(stderr,
    339 					"Progress and Verbose mutually exclusive\n");
    340 				exit(-1);
    341 			}
    342 			r_opts.verbose++;
    343 			break;
    344 		case 'p':
    345 			if (r_opts.verbose) {
    346 				fprintf(stderr,
    347 					"Progress and Verbose mutually exclusive\n");
    348 				usage(argv[0]);
    349 			}
    350 			r_opts.progress++;
    351 			break;
    352 		case 'W':
    353 			warn_no_match = 1;
    354 			break;
    355 		case '0':
    356 			null_terminated = 1;
    357 			break;
    358 		case 'h':
    359 		case '?':
    360 			usage(argv[0]);
    361 		}
    362 	}
    363 
    364 	for (i = optind; i < argc; i++) {
    365 		if (!strcmp(argv[i], "/")) {
    366 			mass_relabel = 1;
    367 			if (r_opts.progress)
    368 				r_opts.progress++;
    369 		}
    370 	}
    371 
    372 	if (!iamrestorecon) {
    373 		if (policyfile) {
    374 			if (optind != (argc - 1))
    375 				usage(argv[0]);
    376 		} else if (use_input_file) {
    377 			if (optind != (argc - 1)) {
    378 				/* Cannot mix with pathname arguments. */
    379 				usage(argv[0]);
    380 			}
    381 		} else {
    382 			if (optind > (argc - 2))
    383 				usage(argv[0]);
    384 		}
    385 
    386 		/* Use our own invalid context checking function so that
    387 		   we can support either checking against the active policy or
    388 		   checking against a binary policy file. */
    389 		selinux_set_callback(SELINUX_CB_VALIDATE,
    390 				     (union selinux_callback)&canoncon);
    391 
    392 		if (stat(argv[optind], &sb) < 0) {
    393 			perror(argv[optind]);
    394 			exit(-1);
    395 		}
    396 		if (!S_ISREG(sb.st_mode)) {
    397 			fprintf(stderr, "%s:  spec file %s is not a regular file.\n",
    398 				argv[0], argv[optind]);
    399 			exit(-1);
    400 		}
    401 
    402 		altpath = argv[optind];
    403 		optind++;
    404 	} else if (argc == 1)
    405 		usage(argv[0]);
    406 
    407 	/* Load the file contexts configuration and check it. */
    408 	r_opts.selabel_opt_validate = (ctx_validate ? (char *)1 : NULL);
    409 	r_opts.selabel_opt_path = altpath;
    410 
    411 	if (nerr)
    412 		exit(-1);
    413 
    414 	restore_init(&r_opts);
    415 	if (use_input_file) {
    416 		FILE *f = stdin;
    417 		ssize_t len;
    418 		int delim;
    419 		if (strcmp(input_filename, "-") != 0)
    420 			f = fopen(input_filename, "r");
    421 		if (f == NULL) {
    422 			fprintf(stderr, "Unable to open %s: %s\n", input_filename,
    423 				strerror(errno));
    424 			usage(argv[0]);
    425 		}
    426 		__fsetlocking(f, FSETLOCKING_BYCALLER);
    427 
    428 		delim = (null_terminated != 0) ? '\0' : '\n';
    429 		while ((len = getdelim(&buf, &buf_len, delim, f)) > 0) {
    430 			buf[len - 1] = 0;
    431 			if (!strcmp(buf, "/"))
    432 				mass_relabel = 1;
    433 			errors |= process_glob(buf, recurse) < 0;
    434 		}
    435 		if (strcmp(input_filename, "-") != 0)
    436 			fclose(f);
    437 	} else {
    438 		for (i = optind; i < argc; i++)
    439 			errors |= process_glob(argv[i], recurse) < 0;
    440 	}
    441 
    442 	maybe_audit_mass_relabel(mass_relabel, errors);
    443 
    444 	if (warn_no_match)
    445 		selabel_stats(r_opts.hnd);
    446 
    447 	selabel_close(r_opts.hnd);
    448 	restore_finish();
    449 
    450 	if (r_opts.outfile)
    451 		fclose(r_opts.outfile);
    452 
    453 	if (r_opts.progress && r_opts.count >= STAR_COUNT)
    454 		printf("\n");
    455 	exit(errors ? -1: 0);
    456 }
    457