diff options
-rw-r--r-- | ChangeLog | 16 | ||||
-rw-r--r-- | README | 16 | ||||
-rw-r--r-- | bootstrap.conf | 4 | ||||
-rw-r--r-- | gl/lib/se-context_.h | 31 | ||||
-rw-r--r-- | gl/lib/se-selinux_.h | 54 | ||||
-rw-r--r-- | gl/lib/selinux-at.c | 94 | ||||
-rw-r--r-- | gl/lib/selinux-at.h | 24 | ||||
-rw-r--r-- | gl/m4/selinux-context-h.m4 | 18 | ||||
-rw-r--r-- | gl/m4/selinux-selinux-h.m4 | 18 | ||||
-rw-r--r-- | gl/modules/selinux-at | 32 | ||||
-rw-r--r-- | gl/modules/selinux-h | 54 | ||||
-rw-r--r-- | src/Makefile.am | 3 | ||||
-rw-r--r-- | src/chcon.c | 589 |
13 files changed, 943 insertions, 10 deletions
@@ -917,6 +917,22 @@ 2007-01-04 Jim Meyering <jim@meyering.net> + New program: chcon + * gl/modules/selinux-at: New module. Check for libselinux and set + LIB_SELINUX here, unconditionally, rather than depending on + the configure-time --enable-selinux option. + * gl/modules/selinux-h: New module. + * bootstrap.conf (gnulib_modules): Add selinux-at. + * gl/lib/selinux-at.c, gl/lib/selinux-at.h: New files. + * gl/lib/se-selinux_.h: New file. + * gl/lib/se-context_.h: New file. + * gl/m4/selinux-selinux-h.m4: New file. + * gl/m4/selinux-context-h.m4: New file. + * src/Makefile.am (bin_PROGRAMS): Add chcon. + (chcon_LDADD): Define. + * README: Add chcon to the list of programs. + * src/chcon.c: Rewrite the original (Red Hat) chcon to use fts. + * Makefile.cfg (local-checks-to-skip): Skip strftime-check, in case you don't have convenient access to glibc info documentation. @@ -7,14 +7,14 @@ arbitrary limits. The programs that can be built with this package are: - [ base64 basename cat chgrp chmod chown chroot cksum comm cp csplit cut date - dd df dir dircolors dirname du echo env expand expr factor false fmt fold - groups head hostid hostname id install join kill link ln logname ls - md5sum mkdir mkfifo mknod mv nice nl nohup od paste pathchk pinky pr - printenv printf ptx pwd readlink rm rmdir seq sha1sum sha224sum sha256sum - sha384sum sha512sum shred shuf sleep sort split stat stty su sum sync tac - tail tee test touch tr true tsort tty uname unexpand uniq unlink uptime - users vdir wc who whoami yes + [ base64 basename cat chcon chgrp chmod chown chroot cksum comm cp + csplit cut date dd df dir dircolors dirname du echo env expand expr + factor false fmt fold groups head hostid hostname id install join + kill link ln logname ls md5sum mkdir mkfifo mknod mv nice nl nohup od + paste pathchk pinky pr printenv printf ptx pwd readlink rm rmdir seq + sha1sum sha224sum sha256sum sha384sum sha512sum shred shuf sleep sort + split stat stty su sum sync tac tail tee test touch tr true tsort tty + uname unexpand uniq unlink uptime users vdir wc who whoami yes See the file NEWS for a list of major changes in the current release. diff --git a/bootstrap.conf b/bootstrap.conf index 4ec43fae6..c391a62dc 100644 --- a/bootstrap.conf +++ b/bootstrap.conf @@ -60,7 +60,9 @@ gnulib_modules=" root-dev-ino rpmatch safe-read same - save-cwd savedir savewd settime sha1 sig2str ssize_t stat-macros + save-cwd savedir savewd + selinux-at + settime sha1 sig2str ssize_t stat-macros stat-time stdbool stdlib-safer stpcpy strftime strpbrk strtoimax strtoumax strverscmp sys_stat timespec tzset unicodeio unistd-safer unlink-busy unlinkdir unlocked-io diff --git a/gl/lib/se-context_.h b/gl/lib/se-context_.h new file mode 100644 index 000000000..26e1709f1 --- /dev/null +++ b/gl/lib/se-context_.h @@ -0,0 +1,31 @@ +#ifndef SELINUX_CONTEXT_H +# define SELINUX_CONTEXT_H + +# include <errno.h> +/* Some systems don't have ENOSYS. */ +# ifndef ENOSYS +# ifdef ENOTSUP +# define ENOSYS ENOTSUP +# else +/* Some systems don't have ENOTSUP either. */ +# define ENOSYS EINVAL +# endif +# endif + +typedef int context_t; +static inline context_t context_new (char const *s) + { errno = ENOTSUP; return 0; } +static inline char *context_str (context_t con) + { errno = ENOTSUP; return (void *) 0; } +static inline void context_free (context_t c) {} + +static inline int context_user_set (context_t sc, char const *s) + { errno = ENOTSUP; return -1; } +static inline int context_role_set (context_t sc, char const *s) + { errno = ENOTSUP; return -1; } +static inline int context_range_set (context_t sc, char const *s) + { errno = ENOTSUP; return -1; } +static inline int context_type_set (context_t sc, char const *s) + { errno = ENOTSUP; return -1; } + +#endif diff --git a/gl/lib/se-selinux_.h b/gl/lib/se-selinux_.h new file mode 100644 index 000000000..b08c7eee4 --- /dev/null +++ b/gl/lib/se-selinux_.h @@ -0,0 +1,54 @@ +#ifndef SELINUX_SELINUX_H +# define SELINUX_SELINUX_H + +# include <sys/types.h> +# include <errno.h> +/* Some systems don't have ENOSYS. */ +# ifndef ENOSYS +# ifdef ENOTSUP +# define ENOSYS ENOTSUP +# else +/* Some systems don't have ENOTSUP either. */ +# define ENOSYS EINVAL +# endif +# endif + +typedef unsigned short security_class_t; +# define security_context_t char* +# define is_selinux_enabled() 0 + +static inline int getcon (security_context_t *con) { errno = ENOTSUP; return -1; } +static inline void freecon (security_context_t con) {} + + +static inline int getfscreatecon (security_context_t *con) + { errno = ENOTSUP; return -1; } +static inline int setfscreatecon (security_context_t con) + { errno = ENOTSUP; return -1; } +static inline int matchpathcon (char const *s, mode_t m, + security_context_t *con) + { errno = ENOTSUP; return -1; } + +static inline int getfilecon (char const *s, security_context_t *con) + { errno = ENOTSUP; return -1; } +static inline int lgetfilecon (char const *s, security_context_t *con) + { errno = ENOTSUP; return -1; } +static inline int setfilecon (char const *s, security_context_t con) + { errno = ENOTSUP; return -1; } +static inline int lsetfilecon (char const *s, security_context_t con) + { errno = ENOTSUP; return -1; } +static inline int fsetfilecon (int fd, security_context_t con) + { errno = ENOTSUP; return -1; } + +static inline int security_check_context (security_context_t con) + { errno = ENOTSUP; return -1; } +static inline int security_check_context_raw (security_context_t con) + { errno = ENOTSUP; return -1; } +static inline int setexeccon (security_context_t con) + { errno = ENOTSUP; return -1; } +static inline int security_compute_create (security_context_t scon, + security_context_t tcon, + security_class_t tclass, + security_context_t *newcon) + { errno = ENOTSUP; return -1; } +#endif diff --git a/gl/lib/selinux-at.c b/gl/lib/selinux-at.c new file mode 100644 index 000000000..ebc41ee7a --- /dev/null +++ b/gl/lib/selinux-at.c @@ -0,0 +1,94 @@ +/* openat-style fd-relative functions for SE Linux + Copyright (C) 2007 Free Software Foundation, Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software Foundation, + Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +/* written by Jim Meyering */ + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#include "selinux-at.h" +#include "openat.h" + +#include <stdlib.h> +#include <unistd.h> +#include <errno.h> +#include <fcntl.h> + +#include "dirname.h" /* solely for definition of IS_ABSOLUTE_FILE_NAME */ +#include "save-cwd.h" + +#include "gettext.h" +#define _(msgid) gettext (msgid) + +#include "openat-priv.h" + +#define AT_FUNC_NAME getfileconat +#define AT_FUNC_F1 getfilecon +#define AT_FUNC_F2 getfilecon +#define AT_FUNC_USE_F1_COND 1 +#define AT_FUNC_POST_FILE_PARAM_DECLS , security_context_t *con +#define AT_FUNC_POST_FILE_ARGS , con +#include "at-func.c" +#undef AT_FUNC_NAME +#undef AT_FUNC_F1 +#undef AT_FUNC_F2 +#undef AT_FUNC_USE_F1_COND +#undef AT_FUNC_POST_FILE_PARAM_DECLS +#undef AT_FUNC_POST_FILE_ARGS + +#define AT_FUNC_NAME lgetfileconat +#define AT_FUNC_F1 lgetfilecon +#define AT_FUNC_F2 lgetfilecon +#define AT_FUNC_USE_F1_COND 1 +#define AT_FUNC_POST_FILE_PARAM_DECLS , security_context_t *con +#define AT_FUNC_POST_FILE_ARGS , con +#include "at-func.c" +#undef AT_FUNC_NAME +#undef AT_FUNC_F1 +#undef AT_FUNC_F2 +#undef AT_FUNC_USE_F1_COND +#undef AT_FUNC_POST_FILE_PARAM_DECLS +#undef AT_FUNC_POST_FILE_ARGS + +#define AT_FUNC_NAME setfileconat +#define AT_FUNC_F1 setfilecon +#define AT_FUNC_F2 setfilecon +#define AT_FUNC_USE_F1_COND 1 +#define AT_FUNC_POST_FILE_PARAM_DECLS , security_context_t con +#define AT_FUNC_POST_FILE_ARGS , con +#include "at-func.c" +#undef AT_FUNC_NAME +#undef AT_FUNC_F1 +#undef AT_FUNC_F2 +#undef AT_FUNC_USE_F1_COND +#undef AT_FUNC_POST_FILE_PARAM_DECLS +#undef AT_FUNC_POST_FILE_ARGS + +#define AT_FUNC_NAME lsetfileconat +#define AT_FUNC_F1 lsetfilecon +#define AT_FUNC_F2 lsetfilecon +#define AT_FUNC_USE_F1_COND 1 +#define AT_FUNC_POST_FILE_PARAM_DECLS , security_context_t con +#define AT_FUNC_POST_FILE_ARGS , con +#include "at-func.c" +#undef AT_FUNC_NAME +#undef AT_FUNC_F1 +#undef AT_FUNC_F2 +#undef AT_FUNC_USE_F1_COND +#undef AT_FUNC_POST_FILE_PARAM_DECLS +#undef AT_FUNC_POST_FILE_ARGS diff --git a/gl/lib/selinux-at.h b/gl/lib/selinux-at.h new file mode 100644 index 000000000..f12022c51 --- /dev/null +++ b/gl/lib/selinux-at.h @@ -0,0 +1,24 @@ +/* Prototypes for openat-style fd-relative SELinux functions + Copyright (C) 2007 Free Software Foundation, Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software Foundation, + Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +#include <selinux/selinux.h> +#include <selinux/context.h> + +int getfileconat (int fd, char const *file, security_context_t *con); +int lgetfileconat (int fd, char const *file, security_context_t *con); +int setfileconat (int fd, char const *file, security_context_t con); +int lsetfileconat (int fd, char const *file, security_context_t con); diff --git a/gl/m4/selinux-context-h.m4 b/gl/m4/selinux-context-h.m4 new file mode 100644 index 000000000..4011dde2a --- /dev/null +++ b/gl/m4/selinux-context-h.m4 @@ -0,0 +1,18 @@ +# serial 1 -*- Autoconf -*- +# Copyright (C) 2006 Free Software Foundation, Inc. +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# From Jim Meyering +# Provide <selinux/context.h>, if necessary. + +AC_DEFUN([gl_HEADERS_SELINUX_CONTEXT_H], +[ + AC_LIBSOURCES([se-context_.h]) + # Check for <selinux/context.h>, + AC_CHECK_HEADERS([selinux/context.h], + [SELINUX_CONTEXT_H=], + [SELINUX_CONTEXT_H=selinux/context.h]) + AC_SUBST([SELINUX_CONTEXT_H]) +]) diff --git a/gl/m4/selinux-selinux-h.m4 b/gl/m4/selinux-selinux-h.m4 new file mode 100644 index 000000000..13ce2ac9a --- /dev/null +++ b/gl/m4/selinux-selinux-h.m4 @@ -0,0 +1,18 @@ +# serial 1 -*- Autoconf -*- +# Copyright (C) 2006 Free Software Foundation, Inc. +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# From Jim Meyering +# Provide <selinux/selinux.h>, if necessary. + +AC_DEFUN([gl_HEADERS_SELINUX_SELINUX_H], +[ + AC_LIBSOURCES([se-selinux_.h]) + # Check for <selinux/selinux.h>, + AC_CHECK_HEADERS([selinux/selinux.h], + [SELINUX_SELINUX_H=], + [SELINUX_SELINUX_H=selinux/selinux.h]) + AC_SUBST([SELINUX_SELINUX_H]) +]) diff --git a/gl/modules/selinux-at b/gl/modules/selinux-at new file mode 100644 index 000000000..759908397 --- /dev/null +++ b/gl/modules/selinux-at @@ -0,0 +1,32 @@ +Description: +openat-style fd-relative functions for SE Linux + +Files: +lib/selinux-at.c +lib/selinux-at.h + +Depends-on: +selinux-h + +configure.ac: +# FIXME: put this in an .m4 file? +# For runcon. +AC_CHECK_HEADERS([selinux/flask.h]) +AC_LIBOBJ([selinux-at]) +ac_save_LIBS="$LIBS" + AC_SEARCH_LIBS(setfilecon, selinux, + [test "$ac_cv_search_setfilecon" = "none required" || + LIB_SELINUX=$ac_cv_search_setfilecon]) + AC_SUBST(LIB_SELINUX) +LIBS="$ac_save_LIBS" + +Makefile.am: + +Include: +selinux-at.h + +License: +LGPL + +Maintainer: +Jim Meyering diff --git a/gl/modules/selinux-h b/gl/modules/selinux-h new file mode 100644 index 000000000..915b9d276 --- /dev/null +++ b/gl/modules/selinux-h @@ -0,0 +1,54 @@ +Description: +SELinux-related headers for systems that lack them. + +Files: +lib/se-context_.h +lib/se-selinux_.h +m4/selinux-context-h.m4 +m4/selinux-selinux-h.m4 + +Depends-on: + +configure.ac: +gl_HEADERS_SELINUX_SELINUX_H +gl_HEADERS_SELINUX_CONTEXT_H + +Makefile.am: +BUILT_SOURCES += $(SELINUX_SELINUX_H) +selinux/selinux.h: se-selinux_.h + mkdir -p selinux + cp $(srcdir)/se-selinux_.h $@-t + chmod a-x $@-t + mv $@-t $@ +MOSTLYCLEANFILES += selinux/selinux.h selinux/selinux.h-t + +BUILT_SOURCES += $(SELINUX_CONTEXT_H) +selinux/context.h: se-context_.h + mkdir -p selinux + cp $(srcdir)/se-context_.h $@-t + chmod a-x $@-t + mv $@-t $@ +MOSTLYCLEANFILES += selinux/context.h selinux/context.h-t +MOSTLYCLEANDIRS += selinux + +Include: +#include <selinux/selinux.h> +#include <selinux/context.h> + +License: +LGPL + +Maintainer: +Jim Meyering + +# lib/selinux-at.c +# +# # For runcon. +# AC_CHECK_HEADERS([selinux/flask.h]) +# +# ac_save_LIBS="$LIBS" +# AC_SEARCH_LIBS(setfilecon, selinux, +# [test "$ac_cv_search_setfilecon" = "none required" || +# LIB_SELINUX=$ac_cv_search_setfilecon]) +# AC_SUBST(LIB_SELINUX) +# LIBS="$ac_save_LIBS" diff --git a/src/Makefile.am b/src/Makefile.am index 076821692..6a740b65b 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -19,7 +19,7 @@ EXTRA_PROGRAMS = chroot df hostid nice pinky stty su uname uptime users who bin_SCRIPTS = groups -bin_PROGRAMS = [ chgrp chown chmod cp dd dircolors du \ +bin_PROGRAMS = [ chcon chgrp chown chmod cp dd dircolors du \ ginstall link ln dir vdir ls mkdir \ mkfifo mknod mv nohup readlink rm rmdir shred stat sync touch unlink \ cat cksum comm csplit cut expand fmt fold head join md5sum \ @@ -60,6 +60,7 @@ AM_CPPFLAGS = -I$(top_srcdir)/lib LDADD = ../lib/libcoreutils.a $(LIBINTL) ../lib/libcoreutils.a # for eaccess in lib/euidaccess.c. +chcon_LDADD = $(LDADD) $(LIB_SELINUX) cp_LDADD = $(LDADD) $(LIB_EACCESS) ginstall_LDADD = $(LDADD) $(LIB_EACCESS) mv_LDADD = $(LDADD) $(LIB_EACCESS) diff --git a/src/chcon.c b/src/chcon.c new file mode 100644 index 000000000..66fda1d96 --- /dev/null +++ b/src/chcon.c @@ -0,0 +1,589 @@ +/* chcon -- change security context of files + Copyright (C) 2005-2007 Free Software Foundation, Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software Foundation, + Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +#include <config.h> +#include <stdio.h> +#include <sys/types.h> +#include <getopt.h> + +#include "system.h" +#include "dev-ino.h" +#include "dirname.h" +#include "error.h" +#include "openat.h" +#include "quote.h" +#include "quotearg.h" +#include "root-dev-ino.h" +#include "selinux-at.h" +#include "xfts.h" + +/* The official name of this program (e.g., no `g' prefix). */ +#define PROGRAM_NAME "chcon" + +#define AUTHORS "Russell Coker", "Jim Meyering" + +enum Change_status +{ + CH_NOT_APPLIED, + CH_SUCCEEDED, + CH_FAILED, + CH_NO_CHANGE_REQUESTED +}; + +enum Verbosity +{ + /* Print a message for each file that is processed. */ + V_high, + + /* Print a message for each file whose attributes we change. */ + V_changes_only, + + /* Do not be verbose. This is the default. */ + V_off +}; + +/* The name the program was run with. */ +char *program_name; + +/* If nonzero, and the systems has support for it, change the context + of symbolic links rather than any files they point to. */ +static bool affect_symlink_referent; + +/* If true, change the modes of directories recursively. */ +static bool recurse; + +/* Level of verbosity. */ +static bool verbose; + +/* Pointer to the device and inode numbers of `/', when --recursive. + Otherwise NULL. */ +static struct dev_ino *root_dev_ino; + +/* The name of the context file is being given. */ +static char const *specified_context; + +/* Specific components of the context */ +static char const *specified_user; +static char const *specified_role; +static char const *specified_range; +static char const *specified_type; + +/* For long options that have no equivalent short option, use a + non-character as a pseudo short option, starting with CHAR_MAX + 1. */ +enum +{ + DEREFERENCE_OPTION = CHAR_MAX + 1, + NO_PRESERVE_ROOT, + PRESERVE_ROOT, + REFERENCE_FILE_OPTION +}; + +static struct option const long_options[] = +{ + {"recursive", no_argument, NULL, 'R'}, + {"dereference", no_argument, NULL, DEREFERENCE_OPTION}, + {"no-dereference", no_argument, NULL, 'h'}, + {"no-preserve-root", no_argument, NULL, NO_PRESERVE_ROOT}, + {"preserve-root", no_argument, NULL, PRESERVE_ROOT}, + {"reference", required_argument, NULL, REFERENCE_FILE_OPTION}, + {"user", required_argument, NULL, 'u'}, + {"role", required_argument, NULL, 'r'}, + {"type", required_argument, NULL, 't'}, + {"range", required_argument, NULL, 'l'}, + {"verbose", no_argument, NULL, 'v'}, + {GETOPT_HELP_OPTION_DECL}, + {GETOPT_VERSION_OPTION_DECL}, + {NULL, 0, NULL, 0} +}; + +/* Given a security context, CONTEXT, derive a context_t (*RET), + setting any portions selected via the global variables, specified_user, + specified_role, etc. */ +static int +compute_context_from_mask (security_context_t 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"), + quotearg_colon (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; +} + +/* Change the context of FILE, using specified components. + If it is a directory and -R is given, recurse. + Return 0 if successful, 1 if errors occurred. */ + +static int +change_file_context (int fd, char const *file) +{ + security_context_t file_context = NULL; + context_t context; + security_context_t context_string; + int errors = 0; + + if (specified_context == NULL) + { + 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"), + quote (file)); + return 1; + } + + /* If the file doesn't have a context, and we're not setting all of + the context components, there isn't really an obvious default. + Thus, we just give up. */ + if (file_context == NULL) + { + error (0, 0, _("can't apply partial context to unlabeled file %s"), + quote (file)); + return 1; + } + + if (compute_context_from_mask (file_context, &context)) + return 1; + } + else + { + /* FIXME: this should be done exactly once, in main. */ + context = context_new (specified_context); + if (!context) + abort (); + } + + context_string = context_str (context); + + if (file_context == NULL || ! 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"), + quote_n (0, file), quote_n (1, context_string)); + } + } + + context_free (context); + freecon (file_context); + + return errors; +} + +/* Change the context of FILE. + Return true if successful. This function is called + once for every file system object that fts encounters. */ + +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)) + { + /* This happens e.g., with "chcon -R --preserve-root ... /" + and with "chcon -RH --preserve-root ... symlink-to-root". */ + ROOT_DEV_INO_WARN (file_full_name); + /* Tell fts not to traverse into this hierarchy. */ + fts_set (fts, ent, FTS_SKIP); + /* Ensure that we do not process "/" on the second visit. */ + ent = fts_read (fts); + return false; + } + return true; + } + break; + + case FTS_DP: + if (! recurse) + return true; + break; + + case FTS_NS: + /* For a top-level file or directory, this FTS_NS (stat failed) + indicator is determined at the time of the initial fts_open call. + With programs like chmod, chown, and chgrp, that modify + permissions, it is possible that the file in question is + accessible when control reaches this point. So, if this is + the first time we've seen the FTS_NS for this file, tell + fts_read to stat it "again". */ + 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"), quote (file_full_name)); + ok = false; + break; + + case FTS_ERR: + error (0, ent->fts_errno, _("%s"), quote (file_full_name)); + ok = false; + break; + + case FTS_DNR: + error (0, ent->fts_errno, _("cannot read directory %s"), + quote (file_full_name)); + ok = 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"), + quote (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; +} + +/* Recursively operate on the specified FILES (the last entry + of which is NULL). BIT_FLAGS controls how fts works. + Return true if successful. */ + +static bool +process_files (char **files, int bit_flags) +{ + bool ok = true; + + FTS *fts = xfts_open (files, bit_flags, NULL); + + while (1) + { + FTSENT *ent; + + ent = fts_read (fts); + if (ent == NULL) + { + if (errno != 0) + { + /* FIXME: try to give a better message */ + error (0, errno, _("fts_read failed")); + ok = false; + } + break; + } + + ok &= process_file (fts, ent); + } + + /* Ignore failure, since the only way it can do so is in failing to + return to the original directory, and since we're about to exit, + that doesn't matter. */ + fts_close (fts); + + return ok; +} + +void +usage (int status) +{ + if (status != EXIT_SUCCESS) + fprintf (stderr, _("Try `%s --help' for more information.\n"), + program_name); + 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 security context of each FILE to CONTEXT.\n\ +With --reference, change the security context of each FILE to that of RFILE.\n\ +\n\ + -c, --changes like verbose but report only when a change is made\n\ + -h, --no-dereference affect symbolic links instead of any referenced file\n\ + (available only on systems with lchown system call)\n\ + --reference=RFILE use RFILE's security context rather than specifying\n\ + a CONTEXT value\n\ + -R, --recursive operate on files and directories recursively\n\ + -v, --verbose output a diagnostic for every file processed\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\ +\n\ +"), stdout); + fputs (_("\ +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); + } + exit (status); +} + +int +main (int argc, char **argv) +{ + security_context_t ref_context = NULL; + + /* Bit flags that control how fts works. */ + int bit_flags = FTS_PHYSICAL; + + /* 1 if --dereference, 0 if --no-dereference, -1 if neither has been + specified. */ + int dereference = -1; + + bool ok; + bool preserve_root = false; + bool component_specified = false; + char *reference_file = NULL; + int optc; + + initialize_main (&argc, &argv); + program_name = argv[0]; + setlocale (LC_ALL, ""); + bindtextdomain (PACKAGE, LOCALEDIR); + textdomain (PACKAGE); + + atexit (close_stdout); + + while ((optc = getopt_long (argc, argv, "HLPRchvu:r:t:l:", long_options, NULL)) + != -1) + { + switch (optc) + { + case 'H': /* Traverse command-line symlinks-to-directories. */ + bit_flags = FTS_COMFOLLOW | FTS_PHYSICAL; + break; + + case 'L': /* Traverse all symlinks-to-directories. */ + bit_flags = FTS_LOGICAL; + break; + + case 'P': /* Traverse no symlinks-to-directories. */ + bit_flags = FTS_PHYSICAL; + break; + + case 'h': /* --no-dereference: affect symlinks */ + dereference = 0; + break; + + case DEREFERENCE_OPTION: /* --dereference: affect the referent + of each symlink */ + 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': + /* ignore */ + 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) + { + if (getfilecon (reference_file, &ref_context) < 0) + error (EXIT_FAILURE, errno, _("failed to get security context of %s"), + quote (reference_file)); + + specified_context = ref_context; + } + else if (component_specified) + { + /* FIXME: it's already null, so this is a no-op. */ + specified_context = NULL; + } + else + { + context_t context; + specified_context = argv[optind++]; + context = context_new (specified_context); + if (!context) + error (EXIT_FAILURE, 0, _("invalid context: %s"), + quotearg_colon (specified_context)); + context_free (context); + } + + if (reference_file && component_specified) + { + error (0, 0, _("conflicting security context specifiers given")); + usage (1); + } + + 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 == NULL) + error (EXIT_FAILURE, errno, _("failed to get attributes of %s"), + quote ("/")); + } + else + { + root_dev_ino = NULL; + } + + ok = process_files (argv + optind, bit_flags | FTS_NOSTAT); + + exit (ok ? EXIT_SUCCESS : EXIT_FAILURE); +} |