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