xz: Add support for sandboxing with Capsicum.

The sandboxing is used conditionally as described in main.c.
This isn't optimal but it was much easier to implement than
a full sandboxing solution and it still covers the most common
use cases where xz is writing to standard output. This should
have practically no effect on performance even with small files
as fork() isn't needed.

C and locale libraries can open files as needed. This has been
fine in the past, but it's a problem with things like Capsicum.
io_sandbox_enter() tries to ensure that various locale-related
files have been loaded before cap_enter() is called, but it's
possible that there are other similar problems which haven't
been seen yet.

Currently Capsicum is available on FreeBSD 10 and later
and there is a port to Linux too.

Thanks to Loganaden Velvindron for help.
diff --git a/configure.ac b/configure.ac
index c8fdb5e..aa68e33 100644
--- a/configure.ac
+++ b/configure.ac
@@ -468,6 +468,30 @@
 AM_CONDITIONAL([COND_SYMVERS], [test "x$enable_symbol_versions" = xyes])
 
 
+##############
+# Sandboxing #
+##############
+
+AC_MSG_CHECKING([if sandboxing should be used])
+AC_ARG_ENABLE([sandbox], [AS_HELP_STRING([--enable-sandbox=METHOD],
+		[Sandboxing METHOD can be `auto', `no', or `capsicum'.
+		The default is `auto' which enables sandboxing if
+		a supported sandboxing method is found.])],
+	[], [enable_sandbox=auto])
+case $enable_sandbox in
+	auto)
+		AC_MSG_RESULT([maybe (autodetect)])
+		;;
+	no | capsicum)
+		AC_MSG_RESULT([$enable_sandbox])
+		;;
+	*)
+		AC_MSG_RESULT([])
+		AC_MSG_ERROR([--enable-sandbox only accepts `auto', `no', or `capsicum'.])
+		;;
+esac
+
+
 ###############################################################################
 # Checks for programs.
 ###############################################################################
@@ -698,6 +722,23 @@
 #include <immintrin.h>
 #endif])
 
+# Check for sandbox support. If one is found, set enable_sandbox=found.
+case $enable_sandbox in
+	auto | capsicum)
+		AX_CHECK_CAPSICUM([enable_sandbox=found], [:])
+		;;
+esac
+
+# If a specific sandboxing method was explicitly requested and it wasn't
+# found, give an error.
+case $enable_sandbox in
+	auto | no | found)
+		;;
+	*)
+		AC_MSG_ERROR([$enable_sandbox support not found])
+		;;
+esac
+
 
 ###############################################################################
 # If using GCC, set some additional AM_CFLAGS:
diff --git a/src/xz/Makefile.am b/src/xz/Makefile.am
index 1f4a279..500be41 100644
--- a/src/xz/Makefile.am
+++ b/src/xz/Makefile.am
@@ -49,7 +49,7 @@
 	-I$(top_srcdir)/src/liblzma/api \
 	-I$(top_builddir)/lib
 
-xz_LDADD = $(top_builddir)/src/liblzma/liblzma.la
+xz_LDADD = $(top_builddir)/src/liblzma/liblzma.la $(CAPSICUM_LIB)
 
 if COND_GNULIB
 xz_LDADD += $(top_builddir)/lib/libgnu.a
diff --git a/src/xz/file_io.c b/src/xz/file_io.c
index 20f512a..308fa1d 100644
--- a/src/xz/file_io.c
+++ b/src/xz/file_io.c
@@ -27,6 +27,14 @@
 #	include <utime.h>
 #endif
 
+#ifdef HAVE_CAPSICUM
+#	ifdef HAVE_SYS_CAPSICUM_H
+#		include <sys/capsicum.h>
+#	else
+#		include <sys/capability.h>
+#	endif
+#endif
+
 #include "tuklib_open_stdxxx.h"
 
 #ifndef O_BINARY
@@ -48,6 +56,11 @@
 /// If true, try to create sparse files when decompressing.
 static bool try_sparse = true;
 
+#ifdef ENABLE_SANDBOX
+/// True if the conditions for sandboxing (described in main()) have been met.
+static bool sandbox_allowed = false;
+#endif
+
 #ifndef TUKLIB_DOSLIKE
 /// File status flags of standard input. This is used by io_open_src()
 /// and io_close_src().
@@ -139,6 +152,69 @@
 }
 
 
+#ifdef ENABLE_SANDBOX
+extern void
+io_allow_sandbox(void)
+{
+	sandbox_allowed = true;
+	return;
+}
+
+
+/// Enables operating-system-specific sandbox if it is possible.
+/// src_fd is the file descriptor of the input file.
+static void
+io_sandbox_enter(int src_fd)
+{
+	if (!sandbox_allowed) {
+		message(V_DEBUG, _("Sandbox is disabled due "
+				"to incompatible command line arguments"));
+		return;
+	}
+
+	const char dummy_str[] = "x";
+
+	// Try to ensure that both libc and xz locale files have been
+	// loaded when NLS is enabled.
+	snprintf(NULL, 0, "%s%s", _(dummy_str), strerror(EINVAL));
+
+	// Try to ensure that iconv data files needed for handling multibyte
+	// characters have been loaded. This is needed at least with glibc.
+	tuklib_mbstr_width(dummy_str, NULL);
+
+#ifdef HAVE_CAPSICUM
+	// Capsicum needs FreeBSD 10.0 or later.
+	cap_rights_t rights;
+
+	if (cap_rights_limit(src_fd, cap_rights_init(&rights,
+			CAP_EVENT, CAP_FCNTL, CAP_LOOKUP, CAP_READ, CAP_SEEK)))
+		goto error;
+
+	if (cap_rights_limit(STDOUT_FILENO, cap_rights_init(&rights,
+			CAP_EVENT, CAP_FCNTL, CAP_FSTAT, CAP_LOOKUP,
+			CAP_WRITE, CAP_SEEK)))
+		goto error;
+
+	if (cap_rights_limit(user_abort_pipe[1], cap_rights_init(&rights,
+			CAP_EVENT, CAP_WRITE)))
+		goto error;
+
+	if (cap_enter())
+		goto error;
+
+#else
+#	error ENABLE_SANDBOX is defined but no sandboxing method was found.
+#endif
+
+	message(V_DEBUG, _("Sandbox was successfully enabled"));
+	return;
+
+error:
+	message(V_DEBUG, _("Failed to enable the sandbox"));
+}
+#endif // ENABLE_SANDBOX
+
+
 #ifndef TUKLIB_DOSLIKE
 /// \brief      Waits for input or output to become available or for a signal
 ///
@@ -656,6 +732,11 @@
 	const bool error = io_open_src_real(&pair);
 	signals_unblock();
 
+#ifdef ENABLE_SANDBOX
+	if (!error)
+		io_sandbox_enter(pair.src_fd);
+#endif
+
 	return error ? NULL : &pair;
 }
 
diff --git a/src/xz/file_io.h b/src/xz/file_io.h
index 2de3379..6722aef 100644
--- a/src/xz/file_io.h
+++ b/src/xz/file_io.h
@@ -80,6 +80,12 @@
 extern void io_no_sparse(void);
 
 
+#ifdef ENABLE_SANDBOX
+/// \brief      main() calls this if conditions for sandboxing have been met.
+extern void io_allow_sandbox(void);
+#endif
+
+
 /// \brief      Open the source file
 extern file_pair *io_open_src(const char *src_name);
 
diff --git a/src/xz/main.c b/src/xz/main.c
index 5608229..5e0789a 100644
--- a/src/xz/main.c
+++ b/src/xz/main.c
@@ -205,6 +205,24 @@
 	if (opt_mode != MODE_LIST)
 		signals_init();
 
+#ifdef ENABLE_SANDBOX
+	// Set a flag that sandboxing is allowed if all these are true:
+	//   - --files or --files0 wasn't used.
+	//   - There is exactly one input file or we are reading from stdin.
+	//   - We won't create any files: output goes to stdout or --test
+	//     or --list was used. Note that --test implies opt_stdout = true
+	//     but --list doesn't.
+	//
+	// This is obviously not ideal but it was easy to implement and
+	// it covers the most common use cases.
+	//
+	// TODO: Make sandboxing work for other situations too.
+	if (args.files_name == NULL && args.arg_count == 1
+			&& (opt_stdout || strcmp("-", args.arg_names[0]) == 0
+				|| opt_mode == MODE_LIST))
+		io_allow_sandbox();
+#endif
+
 	// coder_run() handles compression, decompression, and testing.
 	// list_file() is for --list.
 	void (*run)(const char *filename) = opt_mode == MODE_LIST
diff --git a/src/xz/private.h b/src/xz/private.h
index 4acfa8d..b84cbbb 100644
--- a/src/xz/private.h
+++ b/src/xz/private.h
@@ -45,6 +45,10 @@
 #	define STDERR_FILENO (fileno(stderr))
 #endif
 
+#ifdef HAVE_CAPSICUM
+#	define ENABLE_SANDBOX 1
+#endif
+
 #include "main.h"
 #include "mytime.h"
 #include "coder.h"