* Copyright 2018 by Intra2net AG
*/
-#define BOOST_TEST_IGNORE_NON_ZERO_CHILD_CODE
+#include <boost/version.hpp>
+#if BOOST_VERSION > /* guessed */ 104400
+/*
+ * Boost overeagerly terminates a unit test when a child exits non-zero
+ * without offering a means of disabling this behavior locally. All we
+ * have is below macro which isn’t even available on older versions of
+ * the unittest runner.
+ */
+# define BOOST_TEST_IGNORE_NON_ZERO_CHILD_CODE
+#else
+/* Boost too old; skip test that validate error handling. */
+# define NO_CHILD_FAIL_TESTS
+#endif
#define BOOST_TEST_DYN_LINK
#include <boost/test/unit_test.hpp>
#define TO_CHARP_TOK(x) #x
#define TO_CHARP(x) TO_CHARP_TOK(x)
-BOOST_AUTO_TEST_SUITE(pipestream)
-
- BOOST_AUTO_TEST_SUITE(read)
-
- # define ENOUGH_ZEROS 42
- const char *const zero_bytes_argv [] =
- { "/usr/bin/head", "-c", TO_CHARP(ENOUGH_ZEROS), "/dev/zero", NULL };
-
- BOOST_AUTO_TEST_CASE(abspath_zeros_shell_ok)
- {
- const std::string result =
- capture_exec (I2n::join_string (zero_bytes_argv, " "));
-
- BOOST_CHECK_EQUAL(result.size (), ENOUGH_ZEROS);
- }
-
- BOOST_AUTO_TEST_CASE(abspath_zeros_shell_ok_result)
- {
- ExecResult exres;
- const std::string result =
- capture_exec (I2n::join_string (zero_bytes_argv, " "),
- exres);
-
- BOOST_CHECK(exres.normal_exit);
- BOOST_CHECK_EQUAL(exres.return_code, 0);
- BOOST_CHECK(!exres.terminated_by_signal);
- BOOST_CHECK_EQUAL(result.size (), ENOUGH_ZEROS);
- }
-
- BOOST_AUTO_TEST_CASE(abspath_zeros_noshell_ok)
- {
- const std::string result = capture_exec (zero_bytes_argv);
-
- BOOST_CHECK_EQUAL(result.size (), ENOUGH_ZEROS);
- }
-
- BOOST_AUTO_TEST_CASE(abspath_zeros_noshell_ok_strvec)
- {
- std::vector<std::string> argvec;
- const char *const *argp = zero_bytes_argv;
- const char * cur = NULL;
-
- while ((cur = *argp++) != NULL) {
- argvec.push_back (std::string (cur));
- }
-
- const std::string result = capture_exec (argvec);
-
- BOOST_CHECK_EQUAL(result.size (), ENOUGH_ZEROS);
- }
+#define I2N_EXTRA_ENV "I2N_EXTRA_ENV"
- BOOST_AUTO_TEST_CASE(abspath_zeros_noshell_ok_result)
- {
- ExecResult exres;
- const std::string result = capture_exec (zero_bytes_argv, exres);
+struct Test_Pipestream_Fixture
+{
+ char **saved_environ;
- BOOST_CHECK(exres.normal_exit);
- BOOST_CHECK_EQUAL(exres.return_code, 0);
- BOOST_CHECK(!exres.terminated_by_signal);
- BOOST_CHECK_EQUAL(result.size (), ENOUGH_ZEROS);
- }
+ Test_Pipestream_Fixture (void) : saved_environ (environ) { }
+
+ ~Test_Pipestream_Fixture (void) {
+ environ = this->saved_environ;
+ (void)unsetenv (I2N_EXTRA_ENV);
+ }
+};
+
+BOOST_FIXTURE_TEST_SUITE(pipestream, Test_Pipestream_Fixture)
+
+BOOST_AUTO_TEST_SUITE(read)
+
+# define ENOUGH_ZEROS 42
+const char *const zero_bytes_argv [] =
+ { "/usr/bin/head", "-c", TO_CHARP(ENOUGH_ZEROS), "/dev/zero", NULL };
+
+BOOST_AUTO_TEST_CASE(abspath_zeros_shell_ok)
+{
+ const std::string result =
+ capture_exec (I2n::join_string (zero_bytes_argv, " "));
+
+ BOOST_CHECK_EQUAL(result.size (), ENOUGH_ZEROS);
+}
+
+BOOST_AUTO_TEST_CASE(abspath_zeros_shell_ok_result)
+{
+ ExecResult exres = ExecResult ();
+ const std::string result =
+ capture_exec (I2n::join_string (zero_bytes_argv, " "), exres);
+
+ BOOST_CHECK(exres.normal_exit);
+ BOOST_CHECK_EQUAL(exres.return_code, 0);
+ BOOST_CHECK(!exres.terminated_by_signal);
+ BOOST_CHECK_EQUAL(result.size (), ENOUGH_ZEROS);
+}
+
+BOOST_AUTO_TEST_CASE(abspath_zeros_noshell_ok)
+{
+ const std::string result = capture_exec (zero_bytes_argv);
+
+ BOOST_CHECK_EQUAL(result.size (), ENOUGH_ZEROS);
+}
+
+BOOST_AUTO_TEST_CASE(abspath_zeros_noshell_ok_strvec)
+{
+ std::vector<std::string> argvec;
+ const char *const *argp = zero_bytes_argv;
+ const char * cur = NULL;
+
+ while ((cur = *argp++) != NULL) {
+ argvec.push_back (std::string (cur));
+ }
+
+ const std::string result = capture_exec (argvec);
+
+ BOOST_CHECK_EQUAL(result.size (), ENOUGH_ZEROS);
+}
+
+BOOST_AUTO_TEST_CASE(abspath_zeros_noshell_ok_result)
+{
+ ExecResult exres = ExecResult ();
+ const std::string result = capture_exec (zero_bytes_argv, exres);
+
+ BOOST_CHECK(exres.normal_exit);
+ BOOST_CHECK_EQUAL(exres.return_code, 0);
+ BOOST_CHECK(!exres.terminated_by_signal);
+ BOOST_CHECK_EQUAL(result.size (), ENOUGH_ZEROS);
+}
+
+const char *const bad_command [] = { "/does_not_exist", NULL };
+
+# ifndef NO_CHILD_FAIL_TESTS
+BOOST_AUTO_TEST_CASE(abspath_bad_shell_fail)
+{
+ assert (access(bad_command [0], X_OK) != 0);
+
+ ExecResult exres = ExecResult ();
+ /*
+ * Note that the next line will make the unit test spew a message
+ * to stderr which cannot be prevented due to the limitations of
+ * popen(3).
+ */
+ const std::string result =
+ capture_exec (I2n::join_string (bad_command, " "));
+
+ BOOST_CHECK(!exres.normal_exit);
+ BOOST_CHECK_EQUAL(result.size (), 0);
+}
+# endif /* [!NO_CHILD_FAIL_TESTS] */
+
+# ifndef NO_CHILD_FAIL_TESTS
+BOOST_AUTO_TEST_CASE(abspath_bad_noshell_fail)
+{
+ assert (access(bad_command [0], X_OK) != 0);
+
+ ExecResult exres = ExecResult ();
+ const std::string result = capture_exec (bad_command, exres);
+
+ BOOST_CHECK(!exres.normal_exit); /* failed to exec() */
+ BOOST_CHECK(!exres.terminated_by_signal);
+ BOOST_CHECK_EQUAL(result.size (), 0);
+ BOOST_CHECK_EQUAL(exres.error_message,
+ "child failed to exec(): "
+ "error 2 (No such file or directory)");
+}
+# endif /* [!NO_CHILD_FAIL_TESTS] */
+
+# ifndef NO_CHILD_FAIL_TESTS
+BOOST_AUTO_TEST_CASE(abspath_bad_noshell_stderr)
+{
+ assert (access(bad_command [0], X_OK) != 0);
+
+ ExecResult exres = ExecResult ();
+ const std::string result = capture_exec (bad_command, exres,
+ capture_flag::collect_err);
+
+ BOOST_CHECK(!exres.normal_exit); /* failed to exec() */
+ BOOST_CHECK(!exres.terminated_by_signal);
+ BOOST_CHECK_EQUAL(result.size (), 0);
+ BOOST_CHECK_EQUAL(exres.error_message,
+ "child failed to exec(): "
+ "error 2 (No such file or directory)");
+}
+# endif /* [!NO_CHILD_FAIL_TESTS] */
+
+const char *const false_argv_abs [] = { "/bin/false", NULL };
+const char *const true_argv_abs [] = { "/bin/true" , NULL };
+const char *const false_argv_rel [] = { "false" , NULL };
+const char *const true_argv_rel [] = { "true" , NULL };
+
+# ifndef NO_CHILD_FAIL_TESTS
+BOOST_AUTO_TEST_CASE(abspath_false_noshell_fail_exit)
+{
+ ExecResult exres = ExecResult ();
+ const std::string result =
+ capture_exec (false_argv_abs, exres, capture_flag::collect_out);
+
+ BOOST_CHECK(exres.normal_exit);
+ BOOST_CHECK_EQUAL(exres.return_code, EXIT_FAILURE);
+ BOOST_CHECK_EQUAL(result.size (), 0);
+}
+# endif /* [!NO_CHILD_FAIL_TESTS] */
+
+# ifndef NO_CHILD_FAIL_TESTS
+BOOST_AUTO_TEST_CASE(abspath_false_shell_fail_exit)
+{
+ ExecResult exres = ExecResult ();
+ const std::string result =
+ capture_exec (std::string (false_argv_abs [0]), exres);
+
+ BOOST_CHECK(exres.normal_exit);
+ BOOST_CHECK_EQUAL(exres.return_code, EXIT_FAILURE);
+ BOOST_CHECK_EQUAL(result.size (), 0);
+}
+# endif /* [!NO_CHILD_FAIL_TESTS] */
+
+BOOST_AUTO_TEST_CASE(relpath_true_noshell_ok)
+{
+ ExecResult exres = ExecResult ();
+ const std::string result =
+ capture_exec (true_argv_rel, exres,
+ capture_flag::collect_out | capture_flag::search_path);
+
+ BOOST_CHECK(exres.normal_exit);
+ BOOST_CHECK_EQUAL(exres.return_code, EXIT_SUCCESS);
+ BOOST_CHECK_EQUAL(result.size (), 0);
+}
+
+# ifndef NO_CHILD_FAIL_TESTS
+BOOST_AUTO_TEST_CASE(relpath_true_noshell_fail)
+{
+ ExecResult exres = ExecResult ();
+ const std::string result =
+ capture_exec (true_argv_rel, exres, capture_flag::collect_out);
+
+ BOOST_CHECK(!exres.normal_exit); /* failed to exec() */
+ /* no return code check since we couldn't exit */
+ BOOST_CHECK_EQUAL(result.size (), 0);
+ BOOST_CHECK_EQUAL(exres.error_message,
+ "child failed to exec(): "
+ "error 2 (No such file or directory)");
+}
+# endif /* [!NO_CHILD_FAIL_TESTS] */
+
+BOOST_AUTO_TEST_CASE(abspath_true_noshell_ok)
+{
+ ExecResult exres = ExecResult ();
+ const std::string result =
+ capture_exec (true_argv_abs, exres,
+ capture_flag::collect_out | capture_flag::search_path);
+
+ BOOST_CHECK(exres.normal_exit);
+ BOOST_CHECK_EQUAL(exres.return_code, EXIT_SUCCESS);
+ BOOST_CHECK_EQUAL(result.size (), 0);
+}
+
+# ifndef NO_CHILD_FAIL_TESTS
+BOOST_AUTO_TEST_CASE(relpath_false_noshell_fail)
+{
+ ExecResult exres = ExecResult ();
+ const std::string result =
+ capture_exec (false_argv_rel, exres,
+ capture_flag::collect_out | capture_flag::search_path);
+
+ BOOST_CHECK(exres.normal_exit);
+ /* no return code check since we couldn't exit */
+ BOOST_CHECK_EQUAL(result.size (), 0);
+}
+# endif /* [!NO_CHILD_FAIL_TESTS] */
+
+const char *const echo_abs = "/bin/echo";
+const char *const echo_rel = "echo";
+
+static std::vector<std::string>
+mk_echo_argv (const std::string &text, const bool absolute=true)
+{
+ std::vector<std::string> ret;
+
+ ret.push_back (absolute ? echo_abs : echo_rel);
+ ret.push_back ("-n");
+ ret.push_back (text);
+
+ return ret;
+}
+
+BOOST_AUTO_TEST_CASE(abspath_echo_noshell_capture_ok)
+{
+ ExecResult exres = ExecResult ();
+ const std::string text = "The significant owl hoots in the night.";
+ const std::vector<std::string> argv = mk_echo_argv (text);
+ const std::string result = capture_exec (argv, exres,
+ capture_flag::collect_out
+ | capture_flag::search_path);
+
+ BOOST_CHECK(exres.normal_exit);
+ BOOST_CHECK_EQUAL(exres.return_code, EXIT_SUCCESS);
+ BOOST_CHECK_EQUAL(result, text);
+}
+
+BOOST_AUTO_TEST_CASE(relpath_echo_noshell_capture_ok)
+{
+ ExecResult exres = ExecResult ();
+ const std::string text = "Yet many grey lords go sadly to the masterless men.";
+ const std::vector<std::string> argv = mk_echo_argv (text, false);
+ const std::string result = capture_exec (argv, exres,
+ capture_flag::collect_out
+ | capture_flag::search_path);
+
+ BOOST_CHECK(exres.normal_exit);
+ BOOST_CHECK_EQUAL(exres.return_code, EXIT_SUCCESS);
+ BOOST_CHECK_EQUAL(result, text);
+}
+
+static std::vector<std::string>
+mk_errcho_argv (const std::string &text)
+{
+ /*
+ * Hack cause there’s no way to make echo print to stderr without
+ * redirection.
+ */
+ std::vector<std::string> ret;
+
+ ret.push_back ("/bin/sh");
+ ret.push_back ("-c");
+ ret.push_back (std::string ("1>&- 1>&2 echo -n '") + text + "'"); /* brr */
+
+ return ret;
+}
+
+BOOST_AUTO_TEST_CASE(sh_errcho_capture_ok)
+{
+ ExecResult exres = ExecResult ();
+ const std::string text = "Hooray, hooray for the spinster’s sister’s daughter.";
+ const std::vector<std::string> argv = mk_errcho_argv (text);
+ const std::string result = capture_exec (argv, exres,
+ capture_flag::collect_err
+ | capture_flag::search_path);
+
+ BOOST_CHECK(exres.normal_exit);
+ BOOST_CHECK_EQUAL(exres.return_code, EXIT_SUCCESS);
+ BOOST_CHECK_EQUAL(result, text);
+}
+
+BOOST_AUTO_TEST_CASE(sh_errcho_stdout_empty_ok)
+{
+ ExecResult exres = ExecResult ();
+ const std::string text = "To the axeman, all supplicants are the same height.";
+ const std::vector<std::string> argv = mk_errcho_argv (text);
+ const std::string result = capture_exec (argv, exres,
+ capture_flag::collect_out
+ | capture_flag::search_path);
+
+ BOOST_CHECK(exres.normal_exit);
+ BOOST_CHECK_EQUAL(exres.return_code, EXIT_SUCCESS);
+ BOOST_CHECK_EQUAL(result.size (), 0);
+}
+
+BOOST_AUTO_TEST_SUITE_END() /* [pipestream->read] */
+
+BOOST_AUTO_TEST_SUITE(env)
+
+static const char *const env_argv_abs [] = { "/usr/bin/env", NULL };
+
+#define I2N_EXTRA_ENVIRON I2N_EXTRA_ENV "=Yet verily, the rose is within the thorn."
+static char *i2n_extra_environ [] =
+ { const_cast<char*> (I2N_EXTRA_ENVIRON) , NULL };
+
+BOOST_AUTO_TEST_CASE(env_passthrough)
+{
+ ExecResult exres = ExecResult ();
+ environ = i2n_extra_environ;
+
+ const std::string result =
+ capture_exec (env_argv_abs, exres,
+ capture_flag::collect_out
+ | capture_flag::collect_err
+ | capture_flag::env_passthru);
+
+ BOOST_CHECK(exres.normal_exit);
+ BOOST_CHECK_EQUAL(exres.return_code, EXIT_SUCCESS);
+ BOOST_CHECK_EQUAL(result, (std::string)(I2N_EXTRA_ENVIRON "\n"));
+}
+
+BOOST_AUTO_TEST_CASE(env_nil)
+{
+ ExecResult exres = ExecResult ();
+ environ = i2n_extra_environ;
+
+ const std::string result =
+ capture_exec (env_argv_abs, exres,
+ capture_flag::collect_out
+ | capture_flag::collect_err);
+
+ BOOST_CHECK(exres.normal_exit);
+ BOOST_CHECK_EQUAL(exres.return_code, EXIT_SUCCESS);
+ BOOST_CHECK_EQUAL(result, std::string());
+}
+
+BOOST_AUTO_TEST_SUITE_END() /* [pipestream->env] */
+
+BOOST_AUTO_TEST_SUITE(privs)
+
+#define I2N_EXPECT_OUTPUT "the caged whale knows nothing of the mighty deeps"
+const char *const echo_argv [] = { "echo", I2N_EXPECT_OUTPUT, NULL };
- const char *const bad_command [] = { "/does_not_exist", NULL };
-
- BOOST_AUTO_TEST_CASE(abspath_bad_shell_fail)
- {
- assert (access(bad_command [0], X_OK) != 0);
-
- ExecResult exres;
- const std::string result =
- capture_exec (I2n::join_string (bad_command, " "));
-
- BOOST_CHECK_EQUAL(result.size (), 0);
- }
-
- BOOST_AUTO_TEST_CASE(abspath_bad_noshell_fail)
- {
- assert (access(bad_command [0], X_OK) != 0);
-
- ExecResult exres;
- const std::string result = capture_exec (bad_command, exres);
-
- BOOST_CHECK(!exres.terminated_by_signal);
- BOOST_CHECK_EQUAL(result.size (), 0);
- }
-
- BOOST_AUTO_TEST_CASE(abspath_bad_noshell_stderr)
- {
- assert (access(bad_command [0], X_OK) != 0);
-
- ExecResult exres;
- const std::string result = capture_exec (bad_command, exres, false, true);
-
- BOOST_CHECK_NE(result.size (), 0);
- }
-
- const char *const false_argv [] = { "/bin/false", NULL };
-
- BOOST_AUTO_TEST_CASE(abspath_false_noshell_fail_exit)
- {
- ExecResult exres;
- const std::string result = capture_exec (false_argv, exres, true, false);
-
- BOOST_CHECK(exres.normal_exit);
- BOOST_CHECK_EQUAL(exres.return_code, EXIT_FAILURE);
- BOOST_CHECK_EQUAL(result.size (), 0);
- }
+/*
+ * this is not as such a functionality test, in the sense that
+ * we can’t easily (let alone portably) test the behavior of suid
+ * binaries. thus we only check that the option is indeed accepted
+ * in a trivial case.
+ */
+BOOST_AUTO_TEST_CASE(no_new_privs)
+{
+ ExecResult exres = ExecResult ();
- BOOST_AUTO_TEST_CASE(abspath_false_shell_fail_exit)
- {
- ExecResult exres;
- const std::string result =
- capture_exec (std::string (false_argv [0]), exres);
+ const std::string result = capture_exec (echo_argv, exres,
+ capture_flag::collect_out
+ | capture_flag::search_path
+ | capture_flag::no_new_privs);
- BOOST_CHECK(exres.normal_exit);
- BOOST_CHECK_EQUAL(exres.return_code, EXIT_FAILURE);
- BOOST_CHECK_EQUAL(result.size (), 0);
- }
+ BOOST_CHECK(exres.normal_exit);
+ BOOST_CHECK_EQUAL(exres.return_code, EXIT_SUCCESS);
+ BOOST_CHECK_EQUAL(result, (std::string)I2N_EXPECT_OUTPUT + "\n");
+}
- BOOST_AUTO_TEST_SUITE_END() /* [pipestream->read] */
+BOOST_AUTO_TEST_SUITE_END() /* [pipestream->privs] */
BOOST_AUTO_TEST_SUITE_END() /* [pipestream] */