summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ChangeLog16
-rw-r--r--README16
-rw-r--r--bootstrap.conf4
-rw-r--r--gl/lib/se-context_.h31
-rw-r--r--gl/lib/se-selinux_.h54
-rw-r--r--gl/lib/selinux-at.c94
-rw-r--r--gl/lib/selinux-at.h24
-rw-r--r--gl/m4/selinux-context-h.m418
-rw-r--r--gl/m4/selinux-selinux-h.m418
-rw-r--r--gl/modules/selinux-at32
-rw-r--r--gl/modules/selinux-h54
-rw-r--r--src/Makefile.am3
-rw-r--r--src/chcon.c589
13 files changed, 943 insertions, 10 deletions
diff --git a/ChangeLog b/ChangeLog
index 1396ed803..8d4692ded 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -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.
diff --git a/README b/README
index 218bad00a..13ef2dc72 100644
--- a/README
+++ b/README
@@ -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);
+}