f6b82ec701
- improves the manual page for both setfiles and restorecon (formatting including alphabetical re-ordering of options, undocumented options, references and a few cosmetic changes); - de-hardcodes a couple of constants in the source files and makes a dynamic use of them to create the manual pages after the compilation and prior to the installation: more specifically the constants are the number of errors for the setfiles' validation process abort condition and the sensitivity of the progress meter for both programs (uses external programs grep and awk); - improves the usage message for both programs and introduces a -h (aliased with currently existing -?) option where not already available; - print out the usage message for restorecon when it is called without arguments; - white-space/tab conversion to get proper indentation towards the end of the main source file. [eparis add .gitignore] Signed-off-by: Guido Trentalancia <guido@trentalancia.com> Signed-off-by: Eric Paris <eparis@redhat.com>
451 lines
9.9 KiB
C
451 lines
9.9 KiB
C
#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] [-o filename] pathname...\n"
|
|
"usage: %s [-iFnprRv0] [-e excludedir] [-o filename] -f filename\n",
|
|
name, name);
|
|
} else {
|
|
fprintf(stderr,
|
|
"usage: %s [-dilnpqvFW] [-e excludedir] [-o filename] [-r alt_root_path] spec_file pathname...\n"
|
|
"usage: %s [-dilnpqvFW] [-e excludedir] [-o filename] [-r alt_root_path] spec_file -f filename\n"
|
|
"usage: %s -s [-dilnpqvFW] [-o filename] spec_file\n"
|
|
"usage: %s -c policyfile spec_file\n",
|
|
name, name, name, name);
|
|
}
|
|
exit(1);
|
|
}
|
|
|
|
static int nerr = 0;
|
|
|
|
void inc_err()
|
|
{
|
|
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;
|
|
char *input_filename = NULL;
|
|
int use_input_file = 0;
|
|
char *buf = NULL;
|
|
size_t buf_len;
|
|
int recurse; /* Recursive descent. */
|
|
char *base;
|
|
int mass_relabel = 0, errors = 0;
|
|
|
|
memset(&r_opts, 0, sizeof(r_opts));
|
|
|
|
/* Initialize variables */
|
|
r_opts.progress = 0;
|
|
r_opts.count = 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;
|
|
} 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;
|
|
|
|
/* restorecon only: silent exit if no SELinux.
|
|
Allows unconditional execution by scripts. */
|
|
if (is_selinux_enabled() <= 0)
|
|
exit(0);
|
|
}
|
|
|
|
/* This must happen before getopt. */
|
|
exclude_non_seclabel_mounts();
|
|
|
|
/* Process any options. */
|
|
while ((opt = getopt(argc, argv, "c:de:f:hilno:pqrsvFRW0")) > 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 (optind + 1 >= argc) {
|
|
fprintf(stderr, "usage: %s -r rootpath\n",
|
|
argv[0]);
|
|
exit(1);
|
|
}
|
|
if (NULL != r_opts.rootpath) {
|
|
fprintf(stderr,
|
|
"%s: only one -r can be specified\n",
|
|
argv[0]);
|
|
exit(1);
|
|
}
|
|
set_rootpath(argv[optind++]);
|
|
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 = 1;
|
|
break;
|
|
case 'W':
|
|
warn_no_match = 1;
|
|
break;
|
|
case '0':
|
|
null_terminated = 1;
|
|
break;
|
|
case 'h':
|
|
case '?':
|
|
usage(argv[0]);
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
if (strcmp(input_filename, "-") != 0)
|
|
fclose(f);
|
|
} else {
|
|
for (i = optind; i < argc; i++) {
|
|
if (!strcmp(argv[i], "/"))
|
|
mass_relabel = 1;
|
|
|
|
errors |= process_glob(argv[i], recurse);
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|