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 
usage(const char * const name)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 
inc_err(void)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 
set_rootpath(const char * arg)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 
canoncon(char ** contextp)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
maybe_audit_mass_relabel(int mass_relabel,int mass_relabel_errs)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