setfiles,restorecon: new SELinux applets by Yuichi Nakamura <ynakam@hitachisoft.jp>
[oweals/busybox.git] / selinux / setfiles.c
1 /*
2   setfiles: based on policycoreutils 2.0.19
3   policycoreutils was released under GPL 2.
4   Port to BusyBox by 2007 Yuichi Nakamura <ynakam@hitachisoft.jp>
5 */
6
7 #include "libbb.h"
8 #if ENABLE_FEATURE_SETFILES_CHECK_OPTION
9 #include <sepol/sepol.h>
10 #endif
11
12 #define MAX_EXCLUDES 50
13
14 struct edir {
15         char *directory;
16         size_t size;
17 };
18
19 struct globals {
20         FILE *outfile;
21         char *policyfile;
22         char *rootpath;
23         int rootpathlen;
24         unsigned count;
25         int excludeCtr;
26         int errors;
27         int verbose; /* getopt32 uses it, has to be int */
28         //smallint force;
29         //smallint progress;
30         //smallint debug;
31         //smallint dry_run;
32         //smallint quiet;
33         //smallint ignore_enoent;
34         //smallint take_log;
35         //smallint warn_no_match;
36         smallint recurse; /* Recursive descent */
37         smallint follow_mounts;
38         /* Behavior flags determined based on setfiles vs. restorecon */
39         smallint expand_realpath;  /* Expand paths via realpath */
40         smallint abort_on_error; /* Abort the file tree walk upon an error */
41         int add_assoc; /* Track inode associations for conflict detection */
42         int matchpathcon_flags; /* Flags to matchpathcon */
43         dev_t dev_id; /* Device id where target file exists */
44         int nerr;
45         struct edir excludeArray[MAX_EXCLUDES];
46 };
47
48 #define G (*(struct globals*)&bb_common_bufsiz1)
49 void BUG_setfiles_globals_too_big(void);
50 #define INIT_G() do { \
51         if (sizeof(G) > COMMON_BUFSIZE) \
52                 BUG_setfiles_globals_too_big(); \
53         /* memset(&G, 0, sizeof(G)); - already is */ \
54 } while (0)
55 #define outfile            (G.outfile           )
56 #define policyfile         (G.policyfile        )
57 #define rootpath           (G.rootpath          )
58 #define rootpathlen        (G.rootpathlen       )
59 #define count              (G.count             )
60 #define excludeCtr         (G.excludeCtr        )
61 #define errors             (G.errors            )
62 #define verbose            (G.verbose           )
63 //#define force              (G.force             )
64 //#define progress           (G.progress          )
65 //#define debug              (G.debug             )
66 //#define dry_run            (G.dry_run           )
67 //#define quiet              (G.quiet             )
68 //#define ignore_enoent      (G.ignore_enoent     )
69 //#define take_log           (G.take_log          )
70 //#define warn_no_match      (G.warn_no_match     )
71 #define recurse            (G.recurse           )
72 #define follow_mounts      (G.follow_mounts     )
73 #define expand_realpath    (G.expand_realpath   )
74 #define abort_on_error     (G.abort_on_error    )
75 #define add_assoc          (G.add_assoc         )
76 #define matchpathcon_flags (G.matchpathcon_flags)
77 #define dev_id             (G.dev_id            )
78 #define nerr               (G.nerr              )
79 #define excludeArray       (G.excludeArray      )
80
81 /* Must match getopt32 string! */
82 enum {
83         OPT_d = (1 << 0),
84         OPT_e = (1 << 1),
85         OPT_f = (1 << 2),
86         OPT_i = (1 << 3),
87         OPT_l = (1 << 4),
88         OPT_n = (1 << 5),
89         OPT_p = (1 << 6),
90         OPT_q = (1 << 7),
91         OPT_r = (1 << 8),
92         OPT_s = (1 << 9),
93         OPT_v = (1 << 10),
94         OPT_o = (1 << 11),
95         OPT_F = (1 << 12),
96         OPT_W = (1 << 13),
97         OPT_c = (1 << 14), /* c only for setfiles */
98         OPT_R = (1 << 14), /* R only for restorecon */
99 };
100 #define FLAG_d_debug         (option_mask32 & OPT_d)
101 #define FLAG_e               (option_mask32 & OPT_e)
102 #define FLAG_f               (option_mask32 & OPT_f)
103 #define FLAG_i_ignore_enoent (option_mask32 & OPT_i)
104 #define FLAG_l_take_log      (option_mask32 & OPT_l)
105 #define FLAG_n_dry_run       (option_mask32 & OPT_n)
106 #define FLAG_p_progress      (option_mask32 & OPT_p)
107 #define FLAG_q_quiet         (option_mask32 & OPT_q)
108 #define FLAG_r               (option_mask32 & OPT_r)
109 #define FLAG_s               (option_mask32 & OPT_s)
110 #define FLAG_v               (option_mask32 & OPT_v)
111 #define FLAG_o               (option_mask32 & OPT_o)
112 #define FLAG_F_force         (option_mask32 & OPT_F)
113 #define FLAG_W_warn_no_match (option_mask32 & OPT_W)
114 #define FLAG_c               (option_mask32 & OPT_c)
115 #define FLAG_R               (option_mask32 & OPT_R)
116
117
118 static void qprintf(const char *fmt, ...)
119 {
120         /* quiet, do nothing */
121 }
122
123 static void inc_err(void)
124 {
125         nerr++;
126         if (nerr > 9 && !FLAG_d_debug) {
127                 bb_error_msg_and_die("exiting after 10 errors");
128         }
129 }
130
131 static int add_exclude(const char *directory)
132 {
133         struct stat sb;
134         size_t len = 0;
135
136         if (directory == NULL || directory[0] != '/') {
137                 bb_error_msg("full path required for exclude: %s", directory);
138                 return 1;
139         }
140         if (lstat(directory, &sb)) {
141                 bb_error_msg("directory \"%s\" not found, ignoring", directory);
142                 return 0;
143         }
144         if ((sb.st_mode & S_IFDIR) == 0) {
145                 bb_error_msg("\"%s\" is not a directory: mode %o, ignoring",
146                         directory, sb.st_mode);
147                 return 0;
148         }
149         if (excludeCtr == MAX_EXCLUDES) {
150                 bb_error_msg("maximum excludes %d exceeded", MAX_EXCLUDES);
151                 return 1;
152         }
153
154         len = strlen(directory);
155         while (len > 1 && directory[len - 1] == '/') {
156                 len--;
157         }
158         excludeArray[excludeCtr].directory = xstrndup(directory, len);
159         excludeArray[excludeCtr++].size = len;
160
161         return 0;
162 }
163
164 static int exclude(const char *file)
165 {
166         int i = 0;
167         for (i = 0; i < excludeCtr; i++) {
168                 if (strncmp(file, excludeArray[i].directory,
169                                         excludeArray[i].size) == 0) {
170                         if (file[excludeArray[i].size] == '\0'
171                          || file[excludeArray[i].size] == '/') {
172                                 return 1;
173                         }
174                 }
175         }
176         return 0;
177 }
178
179 static int match(const char *name, struct stat *sb, char **con)
180 {
181         int ret;
182         char path[PATH_MAX + 1];
183         char *tmp_path = xstrdup(name);
184
185         if (excludeCtr > 0) {
186                 if (exclude(name)) {
187                         goto err;
188                 }
189         }
190         ret = lstat(name, sb);
191         if (ret) {
192                 if (FLAG_i_ignore_enoent && errno == ENOENT) {
193                         free(tmp_path);
194                         return 0;
195                 }
196                 bb_error_msg("stat(%s)", name);
197                 goto err;
198         }
199
200         if (expand_realpath) {
201                 if (S_ISLNK(sb->st_mode)) {
202                         char *p = NULL;
203                         char *file_sep;
204
205                         size_t len = 0;
206
207                         if (verbose > 1)
208                                 bb_error_msg("warning! %s refers to a symbolic link, not following last component", name);
209
210                         file_sep = strrchr(tmp_path, '/');
211                         if (file_sep == tmp_path) {
212                                 file_sep++;
213                                 p[0] = '\0';
214                                 p = path;
215                         } else if (file_sep) {
216                                 *file_sep++ = '\0';
217                                 p = realpath(tmp_path, path);
218                         } else {
219                                 file_sep = tmp_path;
220                                 p = realpath("./", path);
221                         }
222                         if (p)
223                                 len = strlen(p);
224                         if (!p || len + strlen(file_sep) + 2 > PATH_MAX) {
225                                 bb_perror_msg("realpath(%s) failed", name);
226                                 goto err;
227                         }
228                         p += len;
229                         /* ensure trailing slash of directory name */
230                         if (len == 0 || p[-1] != '/') {
231                                 *p++ = '/';
232                         }
233                         strcpy(p, file_sep);
234                         name = path;
235                         if (excludeCtr > 0 && exclude(name))
236                                 goto err;
237
238                 } else {
239                         char *p;
240                         p = realpath(name, path);
241                         if (!p) {
242                                 bb_perror_msg("realpath(%s)", name);
243                                 goto err;
244                         }
245                         name = p;
246                         if (excludeCtr > 0 && exclude(name))
247                                 goto err;
248                 }
249         }
250
251         /* name will be what is matched in the policy */
252         if (NULL != rootpath) {
253                 if (0 != strncmp(rootpath, name, rootpathlen)) {
254                         bb_error_msg("%s is not located in %s",
255                                 name, rootpath);
256                         goto err;
257                 }
258                 name += rootpathlen;
259         }
260
261         free(tmp_path);
262         if (rootpath != NULL && name[0] == '\0')
263                 /* this is actually the root dir of the alt root */
264                 return matchpathcon_index("/", sb->st_mode, con);
265         return matchpathcon_index(name, sb->st_mode, con);
266  err:
267         free(tmp_path);
268         return -1;
269 }
270
271 /* Compare two contexts to see if their differences are "significant",
272  * or whether the only difference is in the user. */
273 static int only_changed_user(const char *a, const char *b)
274 {
275         if (FLAG_F_force)
276                 return 0;
277         if (!a || !b)
278                 return 0;
279         a = strchr(a, ':'); /* Rest of the context after the user */
280         b = strchr(b, ':');
281         if (!a || !b)
282                 return 0;
283         return (strcmp(a, b) == 0);
284 }
285
286 static int restore(const char *file)
287 {
288         char *my_file = xstrdup(file);
289         char *my_file_orig = my_file;
290         struct stat my_sb;
291         int i, j, ret;
292         char *context = NULL;
293         char *newcon = NULL;
294         int user_only_changed = 0;
295         size_t len = strlen(my_file);
296         int retval = 0;
297
298         /* Skip the extra slashes at the beginning and end, if present. */
299         if (file[0] == '/' && file[1] == '/')
300                 my_file++;
301         if (len > 1 && my_file[len - 1] == '/')
302                 my_file[len - 1] = '\0';
303
304         i = match(my_file, &my_sb, &newcon);
305
306         if (i < 0) /* No matching specification. */
307                 goto out;
308
309
310         if (FLAG_p_progress) {
311                 count++;
312                 if (count % 0x400 == 0) { /* every 1024 times */
313                         count = (count % (80*0x400));
314                         if (count == 0)
315                                 fputc('\n', stdout);
316                         fputc('*', stdout);
317                         fflush(stdout);
318                 }
319         }
320
321         /*
322          * Try to add an association between this inode and
323          * this specification. If there is already an association
324          * for this inode and it conflicts with this specification,
325          * then use the last matching specification.
326          */
327         if (add_assoc) {
328                 j = matchpathcon_filespec_add(my_sb.st_ino, i, my_file);
329                 if (j < 0)
330                         goto err;
331
332                 if (j != i) {
333                         /* There was already an association and it took precedence. */
334                         goto out;
335                 }
336         }
337
338         if (FLAG_d_debug)
339                 printf("%s: %s matched by %s\n", applet_name, my_file, newcon);
340
341         /* Get the current context of the file. */
342         ret = lgetfilecon_raw(my_file, &context);
343         if (ret < 0) {
344                 if (errno == ENODATA) {
345                         context = NULL; /* paranoia */
346                 } else {
347                         bb_perror_msg("lgetfilecon_raw on %s", my_file);
348                         goto err;
349                 }
350                 user_only_changed = 0;
351         } else
352                 user_only_changed = only_changed_user(context, newcon);
353
354         /*
355          * Do not relabel the file if the matching specification is
356          * <<none>> or the file is already labeled according to the
357          * specification.
358          */
359         if ((strcmp(newcon, "<<none>>") == 0)
360          || (context && (strcmp(context, newcon) == 0) && !FLAG_F_force)) {
361                 goto out;
362         }
363
364         if (!FLAG_F_force && context && (is_context_customizable(context) > 0)) {
365                 if (verbose > 1) {
366                         bb_error_msg("skipping %s. %s is customizable_types",
367                                 my_file, context);
368                 }
369                 goto out;
370         }
371
372         if (verbose) {
373                 /* If we're just doing "-v", trim out any relabels where
374                  * the user has changed but the role and type are the
375                  * same.  For "-vv", emit everything. */
376                 if (verbose > 1 || !user_only_changed) {
377                         bb_info_msg("%s: reset %s context %s->%s",
378                                 applet_name, my_file, context ?: "", newcon);
379                 }
380         }
381
382         if (FLAG_l_take_log && !user_only_changed) {
383                 if (context)
384                         bb_info_msg("relabeling %s from %s to %s", my_file, context, newcon);
385                 else
386                         bb_info_msg("labeling %s to %s", my_file, newcon);
387         }
388
389         if (outfile && !user_only_changed)
390                 fprintf(outfile, "%s\n", my_file);
391
392         /*
393          * Do not relabel the file if -n was used.
394          */
395         if (FLAG_n_dry_run || user_only_changed)
396                 goto out;
397
398         /*
399          * Relabel the file to the specified context.
400          */
401         ret = lsetfilecon(my_file, newcon);
402         if (ret) {
403                 bb_perror_msg("lsetfileconon(%s,%s)", my_file, newcon);
404                 goto err;
405         }
406
407  out:
408         freecon(context);
409         freecon(newcon);
410         free(my_file_orig);
411         return retval;
412  err:
413         retval--; /* -1 */
414         goto out;
415 }
416
417 /*
418  * Apply the last matching specification to a file.
419  * This function is called by recursive_action on each file during
420  * the directory traversal.
421  */
422 static int apply_spec(const char *file,
423                       struct stat *sb, void *userData, int depth)
424 {
425         if (!follow_mounts) {
426                 /* setfiles does not process across different mount points */
427                 if (sb->st_dev != dev_id) {
428                         return SKIP;
429                 }
430         }
431         errors |= restore(file);
432         if (abort_on_error && errors)
433                 return FALSE;
434         return TRUE;
435 }
436
437
438 static int canoncon(const char *path, unsigned lineno, char **contextp)
439 {
440         static const char err_msg[] = "%s: line %u has invalid context %s";
441
442         char *tmpcon;
443         char *context = *contextp;
444         int invalid = 0;
445
446 #if ENABLE_FEATURE_SETFILES_CHECK_OPTION
447         if (policyfile) {
448                 if (sepol_check_context(context) >= 0)
449                         return 0;
450                 /* Exit immediately if we're in checking mode. */
451                 bb_error_msg_and_die(err_msg, path, lineno, context);
452         }
453 #endif
454
455         if (security_canonicalize_context_raw(context, &tmpcon) < 0) {
456                 if (errno != ENOENT) {
457                         invalid = 1;
458                         inc_err();
459                 }
460         } else {
461                 free(context);
462                 *contextp = tmpcon;
463         }
464
465         if (invalid) {
466                 bb_error_msg(err_msg, path, lineno, context);
467         }
468
469         return invalid;
470 }
471
472 static int process_one(char *name)
473 {
474         struct stat sb;
475         int rc;
476
477         rc = lstat(name, &sb);
478         if (rc < 0) {
479                 if (FLAG_i_ignore_enoent && errno == ENOENT)
480                         return 0;
481                 bb_perror_msg("stat(%s)", name);
482                 goto err;
483         }
484         dev_id = sb.st_dev;
485
486         if (S_ISDIR(sb.st_mode) && recurse) {
487                 if (recursive_action(name,
488                                      ACTION_RECURSE,
489                                      apply_spec,
490                                      apply_spec,
491                                      NULL, 0) != TRUE) {
492                         bb_error_msg("error while labeling %s", name);
493                         goto err;
494                 }
495         } else {
496                 rc = restore(name);
497                 if (rc)
498                         goto err;
499         }
500
501  out:
502         if (add_assoc) {
503                 if (FLAG_q_quiet)
504                         set_matchpathcon_printf(&qprintf);
505                 matchpathcon_filespec_eval();
506                 set_matchpathcon_printf(NULL);
507                 matchpathcon_filespec_destroy();
508         }
509
510         return rc;
511
512  err:
513         rc = -1;
514         goto out;
515 }
516
517 int setfiles_main(int argc, char **argv);
518 int setfiles_main(int argc, char **argv)
519 {
520         struct stat sb;
521         int rc, i = 0;
522         const char *input_filename = NULL;
523         int use_input_file = 0;
524         char *buf = NULL;
525         size_t buf_len;
526         int flags;
527         llist_t *exclude_dir = NULL;
528         char *out_filename = NULL;
529
530         INIT_G();
531
532         if (applet_name[0] == 's') { /* "setfiles" */
533                 /*
534                  * setfiles:
535                  * Recursive descent,
536                  * Does not expand paths via realpath,
537                  * Aborts on errors during the file tree walk,
538                  * Try to track inode associations for conflict detection,
539                  * Does not follow mounts,
540                  * Validates all file contexts at init time.
541                  */
542                 recurse = 1;
543                 abort_on_error = 1;
544                 add_assoc = 1;
545                 /* follow_mounts = 0; - already is */
546                 matchpathcon_flags = MATCHPATHCON_VALIDATE | MATCHPATHCON_NOTRANS;
547         } else {
548                 /*
549                  * restorecon:
550                  * No recursive descent unless -r/-R,
551                  * Expands paths via realpath,
552                  * Do not abort on errors during the file tree walk,
553                  * Do not try to track inode associations for conflict detection,
554                  * Follows mounts,
555                  * Does lazy validation of contexts upon use.
556                  */
557                 expand_realpath = 1;
558                 follow_mounts = 1;
559                 matchpathcon_flags = MATCHPATHCON_NOTRANS;
560                 /* restorecon only */
561                 selinux_or_die();
562         }
563
564         set_matchpathcon_flags(matchpathcon_flags);
565
566         opt_complementary = "e::vv:v--p:p--v";
567         /* Option order must match OPT_x definitions! */
568         if (applet_name[0] == 'r') { /* restorecon */
569                 flags = getopt32(argc, argv, "de:f:ilnpqrsvo:FWR",
570                         &exclude_dir, &input_filename, &out_filename, &verbose);
571         } else { /* setfiles */
572                 flags = getopt32(argc, argv, "de:f:ilnpqr:svo:FW"
573                                 USE_FEATURE_SETFILES_CHECK_OPTION("c:"),
574                         &exclude_dir, &input_filename, &rootpath, &out_filename,
575                                  USE_FEATURE_SETFILES_CHECK_OPTION(&policyfile,)
576                         &verbose);
577         }
578
579 #if ENABLE_FEATURE_SETFILES_CHECK_OPTION
580         if ((applet_name[0] == 's') && (flags & OPT_c)) {
581                 FILE *policystream;
582
583                 policystream = xfopen(policyfile, "r");
584                 if (sepol_set_policydb_from_file(policystream) < 0) {
585                         bb_error_msg_and_die("sepol_set_policydb_from_file on %s", policyfile);
586                 }
587                 fclose(policystream);
588
589                 /* Only process the specified file_contexts file, not
590                    any .homedirs or .local files, and do not perform
591                    context translations. */
592                 set_matchpathcon_flags(MATCHPATHCON_BASEONLY |
593                                        MATCHPATHCON_NOTRANS |
594                                        MATCHPATHCON_VALIDATE);
595         }
596 #endif
597
598         //if (flags & OPT_d) {
599         //      debug = 1;
600         //}
601         if (flags & OPT_e) {
602                 if (exclude_dir == NULL) {
603                         bb_show_usage();
604                 }
605                 while (exclude_dir) {
606                         if (add_exclude(llist_pop(&exclude_dir)))
607                                 exit(1);
608                 }
609         }
610         //if (flags & OPT_i) {
611         //      ignore_enoent = 1;
612         //}
613         //if (flags & OPT_l) {
614         //      take_log = 1;
615         //}
616         //if (flags & OPT_F) {
617         //      force = 1;
618         //}
619         //if (flags & OPT_n) {
620         //      dry_run = 1;
621         //}
622         if (flags & OPT_o) {
623                 outfile = stdout;
624                 if (NOT_LONE_CHAR(out_filename, '-')) {
625                         outfile = xfopen(out_filename, "w");
626                 }
627         }
628         //if (flags & OPT_q) {
629         //      quiet = 1;
630         //}
631         if (applet_name[0] == 'r') { /* restorecon */
632                 if (flags & (OPT_r | OPT_R))
633                         recurse = 1;
634         } else { /* setfiles */
635                 if (flags & OPT_r)
636                         rootpathlen = strlen(rootpath);
637         }
638         if (flags & OPT_s) {
639                 use_input_file = 1;
640                 input_filename = "-";
641                 add_assoc = 0;
642         }
643         //if (flags & OPT_p) {
644         //      progress = 1;
645         //}
646         //if (flags & OPT_W) {
647         //      warn_no_match = 1;
648         //}
649
650         if (applet_name[0] == 's') { /* setfiles */
651                 /* Use our own invalid context checking function so that
652                    we can support either checking against the active policy or
653                    checking against a binary policy file. */
654                 set_matchpathcon_canoncon(&canoncon);
655                 if (argc == 1)
656                         bb_show_usage();
657                 if (stat(argv[optind], &sb) < 0) {
658                         bb_perror_msg_and_die("%s", argv[optind]);
659                 }
660                 if (!S_ISREG(sb.st_mode)) {
661                         bb_error_msg_and_die("spec file %s is not a regular file", argv[optind]);
662                 }
663                 /* Load the file contexts configuration and check it. */
664                 rc = matchpathcon_init(argv[optind]);
665                 if (rc < 0) {
666                         bb_perror_msg_and_die("%s", argv[optind]);
667                 }
668
669                 optind++;
670
671                 if (nerr)
672                         exit(1);
673         }
674
675         if (use_input_file) {
676                 ssize_t len;
677                 FILE *f = stdin;
678
679                 if (NOT_LONE_CHAR(input_filename, '-'))
680                         f = xfopen(input_filename, "r");
681                 while ((len = getline(&buf, &buf_len, f)) > 0) {
682                         buf[len - 1] = '\0';
683                         errors |= process_one(buf);
684                 }
685                 if (ENABLE_FEATURE_CLEAN_UP)
686                         fclose_if_not_stdin(f);
687         } else {
688                 if (optind >= argc)
689                         bb_show_usage();
690                 for (i = optind; i < argc; i++) {
691                         errors |= process_one(argv[i]);
692                 }
693         }
694
695         if (FLAG_W_warn_no_match)
696                 matchpathcon_checkmatches(argv[0]);
697
698         if (ENABLE_FEATURE_CLEAN_UP && outfile)
699                 fclose(outfile);
700
701         return errors;
702 }