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