blob: 9ac3ebd3fa8a95ca33d1d003f6d4ed4786afb988 [file] [log] [blame]
#include "restore.h"
#include <unistd.h>
#include <fcntl.h>
#include <stdio_ext.h>
#include <ctype.h>
#include <regex.h>
#include <sys/vfs.h>
#define __USE_XOPEN_EXTENDED 1 /* nftw */
#include <libgen.h>
#ifdef USE_AUDIT
#include <libaudit.h>
#ifndef AUDIT_FS_RELABEL
#define AUDIT_FS_RELABEL 2309
#endif
#endif
/* cmdline opts*/
static char *policyfile = NULL;
static int warn_no_match = 0;
static int null_terminated = 0;
static struct restore_opts r_opts;
#define STAT_BLOCK_SIZE 1
/* setfiles will abort its operation after reaching the
* following number of errors (e.g. invalid contexts),
* unless it is used in "debug" mode (-d option).
*/
#ifndef ABORT_ON_ERRORS
#define ABORT_ON_ERRORS 10
#endif
#define SETFILES "setfiles"
#define RESTORECON "restorecon"
static int iamrestorecon;
/* Behavior flags determined based on setfiles vs. restorecon */
static int ctx_validate; /* Validate contexts */
static const char *altpath; /* Alternate path to file_contexts */
void usage(const char *const name)
{
if (iamrestorecon) {
fprintf(stderr,
"usage: %s [-iFnprRv0] [-e excludedir] pathname...\n"
"usage: %s [-iFnprRv0] [-e excludedir] -f filename\n",
name, name);
} else {
fprintf(stderr,
"usage: %s [-dilnpqvFW] [-e excludedir] [-r alt_root_path] spec_file pathname...\n"
"usage: %s [-dilnpqvFW] [-e excludedir] [-r alt_root_path] spec_file -f filename\n"
"usage: %s -s [-dilnpqvFW] spec_file\n"
"usage: %s -c policyfile spec_file\n",
name, name, name, name);
}
exit(-1);
}
static int nerr = 0;
void inc_err(void)
{
nerr++;
if (nerr > ABORT_ON_ERRORS - 1 && !r_opts.debug) {
fprintf(stderr, "Exiting after %d errors.\n", ABORT_ON_ERRORS);
exit(-1);
}
}
void set_rootpath(const char *arg)
{
int len;
r_opts.rootpath = strdup(arg);
if (NULL == r_opts.rootpath) {
fprintf(stderr, "%s: insufficient memory for r_opts.rootpath\n",
r_opts.progname);
exit(-1);
}
/* trim trailing /, if present */
len = strlen(r_opts.rootpath);
while (len && ('/' == r_opts.rootpath[len - 1]))
r_opts.rootpath[--len] = 0;
r_opts.rootpathlen = len;
}
int canoncon(char **contextp)
{
char *context = *contextp, *tmpcon;
int rc = 0;
if (policyfile) {
if (sepol_check_context(context) < 0) {
fprintf(stderr, "invalid context %s\n", context);
exit(-1);
}
} else if (security_canonicalize_context_raw(context, &tmpcon) == 0) {
free(context);
*contextp = tmpcon;
} else if (errno != ENOENT) {
rc = -1;
inc_err();
}
return rc;
}
#ifndef USE_AUDIT
static void maybe_audit_mass_relabel(int mass_relabel __attribute__((unused)),
int mass_relabel_errs __attribute__((unused)))
{
#else
static void maybe_audit_mass_relabel(int mass_relabel, int mass_relabel_errs)
{
int audit_fd = -1;
int rc = 0;
if (!mass_relabel) /* only audit a forced full relabel */
return;
audit_fd = audit_open();
if (audit_fd < 0) {
fprintf(stderr, "Error connecting to audit system.\n");
exit(-1);
}
rc = audit_log_user_message(audit_fd, AUDIT_FS_RELABEL,
"op=mass relabel", NULL, NULL, NULL, !mass_relabel_errs);
if (rc <= 0) {
fprintf(stderr, "Error sending audit message: %s.\n",
strerror(errno));
/* exit(-1); -- don't exit atm. as fix for eff_cap isn't in most kernels */
}
audit_close(audit_fd);
#endif
}
int main(int argc, char **argv)
{
struct stat sb;
int opt, i = 0;
const char *input_filename = NULL;
int use_input_file = 0;
char *buf = NULL;
size_t buf_len;
int recurse; /* Recursive descent. */
const char *base;
int mass_relabel = 0, errors = 0;
const char *ropts = "e:f:hilno:pqrsvFRW0";
const char *sopts = "c:de:f:hilno:pqr:svFR:W0";
const char *opts;
memset(&r_opts, 0, sizeof(r_opts));
/* Initialize variables */
r_opts.progress = 0;
r_opts.count = 0;
r_opts.nfile = 0;
r_opts.debug = 0;
r_opts.change = 1;
r_opts.verbose = 0;
r_opts.logging = 0;
r_opts.rootpath = NULL;
r_opts.rootpathlen = 0;
r_opts.outfile = NULL;
r_opts.force = 0;
r_opts.hard_links = 1;
altpath = NULL;
r_opts.progname = strdup(argv[0]);
if (!r_opts.progname) {
fprintf(stderr, "%s: Out of memory!\n", argv[0]);
exit(-1);
}
base = basename(r_opts.progname);
if (!strcmp(base, SETFILES)) {
/*
* setfiles:
* Recursive descent,
* Does not expand paths via realpath,
* Aborts on errors during the file tree walk,
* Try to track inode associations for conflict detection,
* Does not follow mounts,
* Validates all file contexts at init time.
*/
iamrestorecon = 0;
recurse = 1;
r_opts.expand_realpath = 0;
r_opts.abort_on_error = 1;
r_opts.add_assoc = 1;
r_opts.fts_flags = FTS_PHYSICAL | FTS_XDEV;
ctx_validate = 1;
opts = sopts;
} else {
/*
* restorecon:
* No recursive descent unless -r/-R,
* Expands paths via realpath,
* Do not abort on errors during the file tree walk,
* Do not try to track inode associations for conflict detection,
* Follows mounts,
* Does lazy validation of contexts upon use.
*/
if (strcmp(base, RESTORECON) && !r_opts.quiet)
printf("Executed with an unrecognized name (%s), defaulting to %s behavior.\n", base, RESTORECON);
iamrestorecon = 1;
recurse = 0;
r_opts.expand_realpath = 1;
r_opts.abort_on_error = 0;
r_opts.add_assoc = 0;
r_opts.fts_flags = FTS_PHYSICAL;
ctx_validate = 0;
opts = ropts;
/* restorecon only: silent exit if no SELinux.
Allows unconditional execution by scripts. */
if (is_selinux_enabled() <= 0)
exit(0);
}
/* This must happen before getopt. */
r_opts.nfile = exclude_non_seclabel_mounts();
/* Process any options. */
while ((opt = getopt(argc, argv, opts)) > 0) {
switch (opt) {
case 'c':
{
FILE *policystream;
if (iamrestorecon)
usage(argv[0]);
policyfile = optarg;
policystream = fopen(policyfile, "r");
if (!policystream) {
fprintf(stderr,
"Error opening %s: %s\n",
policyfile, strerror(errno));
exit(-1);
}
__fsetlocking(policystream,
FSETLOCKING_BYCALLER);
if (sepol_set_policydb_from_file(policystream) <
0) {
fprintf(stderr,
"Error reading policy %s: %s\n",
policyfile, strerror(errno));
exit(-1);
}
fclose(policystream);
ctx_validate = 1;
break;
}
case 'e':
remove_exclude(optarg);
if (lstat(optarg, &sb) < 0 && errno != EACCES) {
fprintf(stderr, "Can't stat exclude path \"%s\", %s - ignoring.\n",
optarg, strerror(errno));
break;
}
if (add_exclude(optarg))
exit(-1);
break;
case 'f':
use_input_file = 1;
input_filename = optarg;
break;
case 'd':
if (iamrestorecon)
usage(argv[0]);
r_opts.debug = 1;
break;
case 'i':
r_opts.ignore_enoent = 1;
break;
case 'l':
r_opts.logging = 1;
break;
case 'F':
r_opts.force = 1;
break;
case 'n':
r_opts.change = 0;
break;
case 'o':
if (strcmp(optarg, "-") == 0) {
r_opts.outfile = stdout;
break;
}
r_opts.outfile = fopen(optarg, "w");
if (!r_opts.outfile) {
fprintf(stderr, "Error opening %s: %s\n",
optarg, strerror(errno));
usage(argv[0]);
}
__fsetlocking(r_opts.outfile, FSETLOCKING_BYCALLER);
break;
case 'q':
r_opts.quiet = 1;
break;
case 'R':
case 'r':
if (iamrestorecon) {
recurse = 1;
break;
}
if (NULL != r_opts.rootpath) {
fprintf(stderr,
"%s: only one -r can be specified\n",
argv[0]);
exit(-1);
}
set_rootpath(optarg);
break;
case 's':
use_input_file = 1;
input_filename = "-";
r_opts.add_assoc = 0;
break;
case 'v':
if (r_opts.progress) {
fprintf(stderr,
"Progress and Verbose mutually exclusive\n");
exit(-1);
}
r_opts.verbose++;
break;
case 'p':
if (r_opts.verbose) {
fprintf(stderr,
"Progress and Verbose mutually exclusive\n");
usage(argv[0]);
}
r_opts.progress++;
break;
case 'W':
warn_no_match = 1;
break;
case '0':
null_terminated = 1;
break;
case 'h':
case '?':
usage(argv[0]);
}
}
for (i = optind; i < argc; i++) {
if (!strcmp(argv[i], "/")) {
mass_relabel = 1;
if (r_opts.progress)
r_opts.progress++;
}
}
if (!iamrestorecon) {
if (policyfile) {
if (optind != (argc - 1))
usage(argv[0]);
} else if (use_input_file) {
if (optind != (argc - 1)) {
/* Cannot mix with pathname arguments. */
usage(argv[0]);
}
} else {
if (optind > (argc - 2))
usage(argv[0]);
}
/* Use our own invalid context checking function so that
we can support either checking against the active policy or
checking against a binary policy file. */
selinux_set_callback(SELINUX_CB_VALIDATE,
(union selinux_callback)&canoncon);
if (stat(argv[optind], &sb) < 0) {
perror(argv[optind]);
exit(-1);
}
if (!S_ISREG(sb.st_mode)) {
fprintf(stderr, "%s: spec file %s is not a regular file.\n",
argv[0], argv[optind]);
exit(-1);
}
altpath = argv[optind];
optind++;
} else if (argc == 1)
usage(argv[0]);
/* Load the file contexts configuration and check it. */
r_opts.selabel_opt_validate = (ctx_validate ? (char *)1 : NULL);
r_opts.selabel_opt_path = altpath;
if (nerr)
exit(-1);
restore_init(&r_opts);
if (use_input_file) {
FILE *f = stdin;
ssize_t len;
int delim;
if (strcmp(input_filename, "-") != 0)
f = fopen(input_filename, "r");
if (f == NULL) {
fprintf(stderr, "Unable to open %s: %s\n", input_filename,
strerror(errno));
usage(argv[0]);
}
__fsetlocking(f, FSETLOCKING_BYCALLER);
delim = (null_terminated != 0) ? '\0' : '\n';
while ((len = getdelim(&buf, &buf_len, delim, f)) > 0) {
buf[len - 1] = 0;
if (!strcmp(buf, "/"))
mass_relabel = 1;
errors |= process_glob(buf, recurse) < 0;
}
if (strcmp(input_filename, "-") != 0)
fclose(f);
} else {
for (i = optind; i < argc; i++)
errors |= process_glob(argv[i], recurse) < 0;
}
maybe_audit_mass_relabel(mass_relabel, errors);
if (warn_no_match)
selabel_stats(r_opts.hnd);
selabel_close(r_opts.hnd);
restore_finish();
if (r_opts.outfile)
fclose(r_opts.outfile);
if (r_opts.progress && r_opts.count >= STAR_COUNT)
printf("\n");
exit(errors ? -1: 0);
}