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
21static char *policyfile = NULL;
22static int warn_no_match = 0;
23static int null_terminated = 0;
24static 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"
38static int iamrestorecon;
39
40/* Behavior flags determined based on setfiles vs. restorecon */
41static int ctx_validate; /* Validate contexts */
42static const char *altpath; /* Alternate path to file_contexts */
43
44void 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
62static int nerr = 0;
63
64void 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
75void 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
93int 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
115static void maybe_audit_mass_relabel(int mass_relabel __attribute__((unused)),
116				     int mass_relabel_errs __attribute__((unused)))
117{
118#else
119static 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
145int 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