|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#include <config.h> |
|
|
#include <stdio.h> |
|
|
#include <sys/types.h> |
|
|
#include <getopt.h> |
|
|
#include <selinux/selinux.h> |
|
|
|
|
|
#include "system.h" |
|
|
#include "dev-ino.h" |
|
|
#include "ignore-value.h" |
|
|
#include "quote.h" |
|
|
#include "root-dev-ino.h" |
|
|
#include "selinux-at.h" |
|
|
#include "xfts.h" |
|
|
|
|
|
|
|
|
#define PROGRAM_NAME "chcon" |
|
|
|
|
|
#define AUTHORS \ |
|
|
proper_name ("Russell Coker"), \ |
|
|
proper_name ("Jim Meyering") |
|
|
|
|
|
|
|
|
|
|
|
static bool affect_symlink_referent; |
|
|
|
|
|
|
|
|
static bool recurse; |
|
|
|
|
|
|
|
|
static bool verbose; |
|
|
|
|
|
|
|
|
|
|
|
static struct dev_ino *root_dev_ino; |
|
|
|
|
|
|
|
|
static char const *specified_context; |
|
|
|
|
|
|
|
|
static char const *specified_user; |
|
|
static char const *specified_role; |
|
|
static char const *specified_range; |
|
|
static char const *specified_type; |
|
|
|
|
|
|
|
|
|
|
|
enum |
|
|
{ |
|
|
DEREFERENCE_OPTION = CHAR_MAX + 1, |
|
|
NO_PRESERVE_ROOT, |
|
|
PRESERVE_ROOT, |
|
|
REFERENCE_FILE_OPTION |
|
|
}; |
|
|
|
|
|
static struct option const long_options[] = |
|
|
{ |
|
|
{"recursive", no_argument, nullptr, 'R'}, |
|
|
{"dereference", no_argument, nullptr, DEREFERENCE_OPTION}, |
|
|
{"no-dereference", no_argument, nullptr, 'h'}, |
|
|
{"no-preserve-root", no_argument, nullptr, NO_PRESERVE_ROOT}, |
|
|
{"preserve-root", no_argument, nullptr, PRESERVE_ROOT}, |
|
|
{"reference", required_argument, nullptr, REFERENCE_FILE_OPTION}, |
|
|
{"user", required_argument, nullptr, 'u'}, |
|
|
{"role", required_argument, nullptr, 'r'}, |
|
|
{"type", required_argument, nullptr, 't'}, |
|
|
{"range", required_argument, nullptr, 'l'}, |
|
|
{"verbose", no_argument, nullptr, 'v'}, |
|
|
{GETOPT_HELP_OPTION_DECL}, |
|
|
{GETOPT_VERSION_OPTION_DECL}, |
|
|
{nullptr, 0, nullptr, 0} |
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static int |
|
|
compute_context_from_mask (char const *context, context_t *ret) |
|
|
{ |
|
|
bool ok = true; |
|
|
context_t new_context = context_new (context); |
|
|
if (!new_context) |
|
|
{ |
|
|
error (0, errno, _("failed to create security context: %s"), |
|
|
quote (context)); |
|
|
return 1; |
|
|
} |
|
|
|
|
|
#define SET_COMPONENT(C, comp) \ |
|
|
do \ |
|
|
{ \ |
|
|
if (specified_ ## comp \ |
|
|
&& context_ ## comp ## _set ((C), specified_ ## comp)) \ |
|
|
{ \ |
|
|
error (0, errno, \ |
|
|
_("failed to set %s security context component to %s"), \ |
|
|
#comp, quote (specified_ ## comp)); \ |
|
|
ok = false; \ |
|
|
} \ |
|
|
} \ |
|
|
while (0) |
|
|
|
|
|
SET_COMPONENT (new_context, user); |
|
|
SET_COMPONENT (new_context, range); |
|
|
SET_COMPONENT (new_context, role); |
|
|
SET_COMPONENT (new_context, type); |
|
|
|
|
|
if (!ok) |
|
|
{ |
|
|
int saved_errno = errno; |
|
|
context_free (new_context); |
|
|
errno = saved_errno; |
|
|
return 1; |
|
|
} |
|
|
|
|
|
*ret = new_context; |
|
|
return 0; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static int |
|
|
change_file_context (int fd, char const *file) |
|
|
{ |
|
|
char *file_context = nullptr; |
|
|
context_t context IF_LINT (= 0); |
|
|
char const * context_string; |
|
|
int errors = 0; |
|
|
|
|
|
if (specified_context == nullptr) |
|
|
{ |
|
|
int status = (affect_symlink_referent |
|
|
? getfileconat (fd, file, &file_context) |
|
|
: lgetfileconat (fd, file, &file_context)); |
|
|
|
|
|
if (status < 0 && errno != ENODATA) |
|
|
{ |
|
|
error (0, errno, _("failed to get security context of %s"), |
|
|
quoteaf (file)); |
|
|
return 1; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (file_context == nullptr) |
|
|
{ |
|
|
error (0, 0, _("can't apply partial context to unlabeled file %s"), |
|
|
quoteaf (file)); |
|
|
return 1; |
|
|
} |
|
|
|
|
|
if (compute_context_from_mask (file_context, &context)) |
|
|
return 1; |
|
|
|
|
|
context_string = context_str (context); |
|
|
} |
|
|
else |
|
|
{ |
|
|
context_string = specified_context; |
|
|
} |
|
|
|
|
|
if (file_context == nullptr || ! streq (context_string, file_context)) |
|
|
{ |
|
|
int fail = (affect_symlink_referent |
|
|
? setfileconat (fd, file, context_string) |
|
|
: lsetfileconat (fd, file, context_string)); |
|
|
|
|
|
if (fail) |
|
|
{ |
|
|
errors = 1; |
|
|
error (0, errno, _("failed to change context of %s to %s"), |
|
|
quoteaf_n (0, file), quote_n (1, context_string)); |
|
|
} |
|
|
} |
|
|
|
|
|
if (specified_context == nullptr) |
|
|
{ |
|
|
context_free (context); |
|
|
freecon (file_context); |
|
|
} |
|
|
|
|
|
return errors; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static bool |
|
|
process_file (FTS *fts, FTSENT *ent) |
|
|
{ |
|
|
char const *file_full_name = ent->fts_path; |
|
|
char const *file = ent->fts_accpath; |
|
|
const struct stat *file_stats = ent->fts_statp; |
|
|
bool ok = true; |
|
|
|
|
|
switch (ent->fts_info) |
|
|
{ |
|
|
case FTS_D: |
|
|
if (recurse) |
|
|
{ |
|
|
if (ROOT_DEV_INO_CHECK (root_dev_ino, ent->fts_statp)) |
|
|
{ |
|
|
|
|
|
|
|
|
ROOT_DEV_INO_WARN (file_full_name); |
|
|
|
|
|
fts_set (fts, ent, FTS_SKIP); |
|
|
|
|
|
ignore_value (fts_read (fts)); |
|
|
return false; |
|
|
} |
|
|
return true; |
|
|
} |
|
|
break; |
|
|
|
|
|
case FTS_DP: |
|
|
if (! recurse) |
|
|
return true; |
|
|
break; |
|
|
|
|
|
case FTS_NS: |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (ent->fts_level == 0 && ent->fts_number == 0) |
|
|
{ |
|
|
ent->fts_number = 1; |
|
|
fts_set (fts, ent, FTS_AGAIN); |
|
|
return true; |
|
|
} |
|
|
error (0, ent->fts_errno, _("cannot access %s"), |
|
|
quoteaf (file_full_name)); |
|
|
ok = false; |
|
|
break; |
|
|
|
|
|
case FTS_ERR: |
|
|
error (0, ent->fts_errno, "%s", quotef (file_full_name)); |
|
|
ok = false; |
|
|
break; |
|
|
|
|
|
case FTS_DNR: |
|
|
error (0, ent->fts_errno, _("cannot read directory %s"), |
|
|
quoteaf (file_full_name)); |
|
|
ok = false; |
|
|
break; |
|
|
|
|
|
case FTS_DC: |
|
|
if (cycle_warning_required (fts, ent)) |
|
|
{ |
|
|
emit_cycle_warning (file_full_name); |
|
|
return false; |
|
|
} |
|
|
break; |
|
|
|
|
|
default: |
|
|
break; |
|
|
} |
|
|
|
|
|
if (ent->fts_info == FTS_DP |
|
|
&& ok && ROOT_DEV_INO_CHECK (root_dev_ino, file_stats)) |
|
|
{ |
|
|
ROOT_DEV_INO_WARN (file_full_name); |
|
|
ok = false; |
|
|
} |
|
|
|
|
|
if (ok) |
|
|
{ |
|
|
if (verbose) |
|
|
printf (_("changing security context of %s\n"), |
|
|
quoteaf (file_full_name)); |
|
|
|
|
|
if (change_file_context (fts->fts_cwd_fd, file) != 0) |
|
|
ok = false; |
|
|
} |
|
|
|
|
|
if ( ! recurse) |
|
|
fts_set (fts, ent, FTS_SKIP); |
|
|
|
|
|
return ok; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static bool |
|
|
process_files (char **files, int bit_flags) |
|
|
{ |
|
|
bool ok = true; |
|
|
|
|
|
FTS *fts = xfts_open (files, bit_flags, nullptr); |
|
|
|
|
|
while (true) |
|
|
{ |
|
|
FTSENT *ent; |
|
|
|
|
|
ent = fts_read (fts); |
|
|
if (ent == nullptr) |
|
|
{ |
|
|
if (errno != 0) |
|
|
{ |
|
|
|
|
|
error (0, errno, _("fts_read failed")); |
|
|
ok = false; |
|
|
} |
|
|
break; |
|
|
} |
|
|
|
|
|
ok &= process_file (fts, ent); |
|
|
} |
|
|
|
|
|
if (fts_close (fts) != 0) |
|
|
{ |
|
|
error (0, errno, _("fts_close failed")); |
|
|
ok = false; |
|
|
} |
|
|
|
|
|
return ok; |
|
|
} |
|
|
|
|
|
void |
|
|
usage (int status) |
|
|
{ |
|
|
if (status != EXIT_SUCCESS) |
|
|
emit_try_help (); |
|
|
else |
|
|
{ |
|
|
printf (_("\ |
|
|
Usage: %s [OPTION]... CONTEXT FILE...\n\ |
|
|
or: %s [OPTION]... [-u USER] [-r ROLE] [-l RANGE] [-t TYPE] FILE...\n\ |
|
|
or: %s [OPTION]... --reference=RFILE FILE...\n\ |
|
|
"), |
|
|
program_name, program_name, program_name); |
|
|
fputs (_("\ |
|
|
Change the SELinux security context of each FILE to CONTEXT.\n\ |
|
|
With --reference, change the security context of each FILE to that of RFILE.\n\ |
|
|
"), stdout); |
|
|
|
|
|
emit_mandatory_arg_note (); |
|
|
|
|
|
fputs (_("\ |
|
|
--dereference affect the referent of each symbolic link (this is\n\ |
|
|
the default), rather than the symbolic link itself\n\ |
|
|
-h, --no-dereference affect symbolic links instead of any referenced file\n\ |
|
|
"), stdout); |
|
|
fputs (_("\ |
|
|
-u, --user=USER set user USER in the target security context\n\ |
|
|
-r, --role=ROLE set role ROLE in the target security context\n\ |
|
|
-t, --type=TYPE set type TYPE in the target security context\n\ |
|
|
-l, --range=RANGE set range RANGE in the target security context\n\ |
|
|
"), stdout); |
|
|
fputs (_("\ |
|
|
--no-preserve-root do not treat '/' specially (the default)\n\ |
|
|
--preserve-root fail to operate recursively on '/'\n\ |
|
|
"), stdout); |
|
|
fputs (_("\ |
|
|
--reference=RFILE use RFILE's security context rather than specifying\n\ |
|
|
a CONTEXT value\n\ |
|
|
"), stdout); |
|
|
fputs (_("\ |
|
|
-R, --recursive operate on files and directories recursively\n\ |
|
|
"), stdout); |
|
|
fputs (_("\ |
|
|
-v, --verbose output a diagnostic for every file processed\n\ |
|
|
"), stdout); |
|
|
fputs (_("\ |
|
|
\n\ |
|
|
The following options modify how a hierarchy is traversed when the -R\n\ |
|
|
option is also specified. If more than one is specified, only the final\n\ |
|
|
one takes effect.\n\ |
|
|
\n\ |
|
|
-H if a command line argument is a symbolic link\n\ |
|
|
to a directory, traverse it\n\ |
|
|
-L traverse every symbolic link to a directory\n\ |
|
|
encountered\n\ |
|
|
-P do not traverse any symbolic links (default)\n\ |
|
|
\n\ |
|
|
"), stdout); |
|
|
fputs (HELP_OPTION_DESCRIPTION, stdout); |
|
|
fputs (VERSION_OPTION_DESCRIPTION, stdout); |
|
|
emit_ancillary_info (PROGRAM_NAME); |
|
|
} |
|
|
exit (status); |
|
|
} |
|
|
|
|
|
int |
|
|
main (int argc, char **argv) |
|
|
{ |
|
|
|
|
|
int bit_flags = FTS_PHYSICAL; |
|
|
|
|
|
|
|
|
|
|
|
int dereference = -1; |
|
|
|
|
|
bool ok; |
|
|
bool preserve_root = false; |
|
|
bool component_specified = false; |
|
|
char *reference_file = nullptr; |
|
|
int optc; |
|
|
|
|
|
initialize_main (&argc, &argv); |
|
|
set_program_name (argv[0]); |
|
|
setlocale (LC_ALL, ""); |
|
|
bindtextdomain (PACKAGE, LOCALEDIR); |
|
|
textdomain (PACKAGE); |
|
|
|
|
|
atexit (close_stdout); |
|
|
|
|
|
while ((optc = getopt_long (argc, argv, "HLPRhvu:r:t:l:", |
|
|
long_options, nullptr)) |
|
|
!= -1) |
|
|
{ |
|
|
switch (optc) |
|
|
{ |
|
|
case 'H': |
|
|
bit_flags = FTS_COMFOLLOW | FTS_PHYSICAL; |
|
|
break; |
|
|
|
|
|
case 'L': |
|
|
bit_flags = FTS_LOGICAL; |
|
|
break; |
|
|
|
|
|
case 'P': |
|
|
bit_flags = FTS_PHYSICAL; |
|
|
break; |
|
|
|
|
|
case 'h': |
|
|
dereference = 0; |
|
|
break; |
|
|
|
|
|
case DEREFERENCE_OPTION: |
|
|
|
|
|
dereference = 1; |
|
|
break; |
|
|
|
|
|
case NO_PRESERVE_ROOT: |
|
|
preserve_root = false; |
|
|
break; |
|
|
|
|
|
case PRESERVE_ROOT: |
|
|
preserve_root = true; |
|
|
break; |
|
|
|
|
|
case REFERENCE_FILE_OPTION: |
|
|
reference_file = optarg; |
|
|
break; |
|
|
|
|
|
case 'R': |
|
|
recurse = true; |
|
|
break; |
|
|
|
|
|
case 'f': |
|
|
|
|
|
break; |
|
|
|
|
|
case 'v': |
|
|
verbose = true; |
|
|
break; |
|
|
|
|
|
case 'u': |
|
|
specified_user = optarg; |
|
|
component_specified = true; |
|
|
break; |
|
|
|
|
|
case 'r': |
|
|
specified_role = optarg; |
|
|
component_specified = true; |
|
|
break; |
|
|
|
|
|
case 't': |
|
|
specified_type = optarg; |
|
|
component_specified = true; |
|
|
break; |
|
|
|
|
|
case 'l': |
|
|
specified_range = optarg; |
|
|
component_specified = true; |
|
|
break; |
|
|
|
|
|
case_GETOPT_HELP_CHAR; |
|
|
case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS); |
|
|
default: |
|
|
usage (EXIT_FAILURE); |
|
|
} |
|
|
} |
|
|
|
|
|
if (recurse) |
|
|
{ |
|
|
if (bit_flags == FTS_PHYSICAL) |
|
|
{ |
|
|
if (dereference == 1) |
|
|
error (EXIT_FAILURE, 0, |
|
|
_("-R --dereference requires either -H or -L")); |
|
|
affect_symlink_referent = false; |
|
|
} |
|
|
else |
|
|
{ |
|
|
if (dereference == 0) |
|
|
error (EXIT_FAILURE, 0, _("-R -h requires -P")); |
|
|
affect_symlink_referent = true; |
|
|
} |
|
|
} |
|
|
else |
|
|
{ |
|
|
bit_flags = FTS_PHYSICAL; |
|
|
affect_symlink_referent = (dereference != 0); |
|
|
} |
|
|
|
|
|
if (argc - optind < (reference_file || component_specified ? 1 : 2)) |
|
|
{ |
|
|
if (argc <= optind) |
|
|
error (0, 0, _("missing operand")); |
|
|
else |
|
|
error (0, 0, _("missing operand after %s"), quote (argv[argc - 1])); |
|
|
usage (EXIT_FAILURE); |
|
|
} |
|
|
|
|
|
if (reference_file) |
|
|
{ |
|
|
char *ref_context = nullptr; |
|
|
|
|
|
if (getfilecon (reference_file, &ref_context) < 0) |
|
|
error (EXIT_FAILURE, errno, _("failed to get security context of %s"), |
|
|
quoteaf (reference_file)); |
|
|
|
|
|
specified_context = ref_context; |
|
|
} |
|
|
else if (component_specified) |
|
|
{ |
|
|
|
|
|
specified_context = nullptr; |
|
|
} |
|
|
else |
|
|
{ |
|
|
specified_context = argv[optind++]; |
|
|
if (0 < is_selinux_enabled () |
|
|
&& security_check_context (specified_context) < 0) |
|
|
error (EXIT_FAILURE, errno, _("invalid context: %s"), |
|
|
quote (specified_context)); |
|
|
} |
|
|
|
|
|
if (reference_file && component_specified) |
|
|
{ |
|
|
error (0, 0, _("conflicting security context specifiers given")); |
|
|
usage (EXIT_FAILURE); |
|
|
} |
|
|
|
|
|
if (recurse && preserve_root) |
|
|
{ |
|
|
static struct dev_ino dev_ino_buf; |
|
|
root_dev_ino = get_root_dev_ino (&dev_ino_buf); |
|
|
if (root_dev_ino == nullptr) |
|
|
error (EXIT_FAILURE, errno, _("failed to get attributes of %s"), |
|
|
quoteaf ("/")); |
|
|
} |
|
|
else |
|
|
{ |
|
|
root_dev_ino = nullptr; |
|
|
} |
|
|
|
|
|
ok = process_files (argv + optind, bit_flags | FTS_NOSTAT); |
|
|
|
|
|
return ok ? EXIT_SUCCESS : EXIT_FAILURE; |
|
|
} |
|
|
|