diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/basic/env-util.c | 15 | ||||
-rw-r--r-- | src/basic/env-util.h | 1 | ||||
-rw-r--r-- | src/core/dbus-execute.c | 33 | ||||
-rw-r--r-- | src/core/execute.c | 43 | ||||
-rw-r--r-- | src/core/execute.h | 1 | ||||
-rw-r--r-- | src/core/load-fragment-gperf.gperf.m4 | 1 | ||||
-rw-r--r-- | src/core/load-fragment.c | 64 | ||||
-rw-r--r-- | src/core/load-fragment.h | 1 | ||||
-rw-r--r-- | src/shared/bus-util.c | 15 | ||||
-rw-r--r-- | src/test/test-execute.c | 35 | ||||
-rw-r--r-- | src/test/test-unit-file.c | 39 |
11 files changed, 242 insertions, 6 deletions
diff --git a/src/basic/env-util.c b/src/basic/env-util.c index 9ddac5d6a1..441169db31 100644 --- a/src/basic/env-util.c +++ b/src/basic/env-util.c @@ -138,6 +138,21 @@ bool strv_env_is_valid(char **e) { return true; } +bool strv_env_name_is_valid(char **l) { + char **p, **q; + + STRV_FOREACH(p, l) { + if (!env_name_is_valid(*p)) + return false; + + STRV_FOREACH(q, p + 1) + if (streq(*p, *q)) + return false; + } + + return true; +} + bool strv_env_name_or_assignment_is_valid(char **l) { char **p, **q; diff --git a/src/basic/env-util.h b/src/basic/env-util.h index 6485dade18..5efffa3dc7 100644 --- a/src/basic/env-util.h +++ b/src/basic/env-util.h @@ -36,6 +36,7 @@ bool strv_env_is_valid(char **e); #define strv_env_clean(l) strv_env_clean_with_callback(l, NULL, NULL) char **strv_env_clean_with_callback(char **l, void (*invalid_callback)(const char *p, void *userdata), void *userdata); +bool strv_env_name_is_valid(char **l); bool strv_env_name_or_assignment_is_valid(char **l); char **strv_env_merge(unsigned n_lists, ...); diff --git a/src/core/dbus-execute.c b/src/core/dbus-execute.c index db4206a523..093179c003 100644 --- a/src/core/dbus-execute.c +++ b/src/core/dbus-execute.c @@ -629,6 +629,7 @@ const sd_bus_vtable bus_exec_vtable[] = { SD_BUS_VTABLE_START(0), SD_BUS_PROPERTY("Environment", "as", NULL, offsetof(ExecContext, environment), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("EnvironmentFiles", "a(sb)", property_get_environment_files, 0, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("PassEnvironment", "as", NULL, offsetof(ExecContext, pass_environment), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("UMask", "u", bus_property_get_mode, offsetof(ExecContext, umask), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("LimitCPU", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_CPU]), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("LimitFSIZE", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_FSIZE]), SD_BUS_VTABLE_PROPERTY_CONST), @@ -1289,6 +1290,38 @@ int bus_exec_context_set_transient_property( return 1; + } else if (streq(name, "PassEnvironment")) { + + _cleanup_strv_free_ char **l = NULL; + + r = sd_bus_message_read_strv(message, &l); + if (r < 0) + return r; + + if (!strv_env_name_is_valid(l)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid PassEnvironment block."); + + if (mode != UNIT_CHECK) { + if (strv_isempty(l)) { + c->pass_environment = strv_free(c->pass_environment); + unit_write_drop_in_private_format(u, mode, name, "PassEnvironment=\n"); + } else { + _cleanup_free_ char *joined = NULL; + + r = strv_extend_strv(&c->pass_environment, l, true); + if (r < 0) + return r; + + joined = strv_join_quoted(c->pass_environment); + if (!joined) + return -ENOMEM; + + unit_write_drop_in_private_format(u, mode, name, "PassEnvironment=%s\n", joined); + } + } + + return 1; + } else if (STR_IN_SET(name, "ReadWriteDirectories", "ReadOnlyDirectories", "InaccessibleDirectories")) { _cleanup_strv_free_ char **l = NULL; diff --git a/src/core/execute.c b/src/core/execute.c index d751065af0..07979bf8b3 100644 --- a/src/core/execute.c +++ b/src/core/execute.c @@ -1332,6 +1332,34 @@ static int build_environment( return 0; } +static int build_pass_environment(const ExecContext *c, char ***ret) { + _cleanup_strv_free_ char **pass_env = NULL; + size_t n_env = 0, n_bufsize = 0; + char **i; + + STRV_FOREACH(i, c->pass_environment) { + _cleanup_free_ char *x = NULL; + char *v; + + v = getenv(*i); + if (!v) + continue; + x = strjoin(*i, "=", v, NULL); + if (!x) + return -ENOMEM; + if (!GREEDY_REALLOC(pass_env, n_bufsize, n_env + 2)) + return -ENOMEM; + pass_env[n_env++] = x; + pass_env[n_env] = NULL; + x = NULL; + } + + *ret = pass_env; + pass_env = NULL; + + return 0; +} + static bool exec_needs_mount_namespace( const ExecContext *context, const ExecParameters *params, @@ -1412,7 +1440,7 @@ static int exec_child( char **files_env, int *exit_status) { - _cleanup_strv_free_ char **our_env = NULL, **pam_env = NULL, **final_env = NULL, **final_argv = NULL; + _cleanup_strv_free_ char **our_env = NULL, **pass_env = NULL, **pam_env = NULL, **final_env = NULL, **final_argv = NULL; _cleanup_free_ char *mac_selinux_context_net = NULL; const char *username = NULL, *home = NULL, *shell = NULL, *wd; uid_t uid = UID_INVALID; @@ -1928,9 +1956,16 @@ static int exec_child( return r; } - final_env = strv_env_merge(5, + r = build_pass_environment(context, &pass_env); + if (r < 0) { + *exit_status = EXIT_MEMORY; + return r; + } + + final_env = strv_env_merge(6, params->environment, our_env, + pass_env, context->environment, files_env, pam_env, @@ -2088,6 +2123,7 @@ void exec_context_done(ExecContext *c) { c->environment = strv_free(c->environment); c->environment_files = strv_free(c->environment_files); + c->pass_environment = strv_free(c->pass_environment); for (l = 0; l < ELEMENTSOF(c->rlimit); l++) c->rlimit[l] = mfree(c->rlimit[l]); @@ -2358,6 +2394,9 @@ void exec_context_dump(ExecContext *c, FILE* f, const char *prefix) { STRV_FOREACH(e, c->environment_files) fprintf(f, "%sEnvironmentFile: %s\n", prefix, *e); + STRV_FOREACH(e, c->pass_environment) + fprintf(f, "%sPassEnvironment: %s\n", prefix, *e); + fprintf(f, "%sRuntimeDirectoryMode: %04o\n", prefix, c->runtime_directory_mode); STRV_FOREACH(d, c->runtime_directory) diff --git a/src/core/execute.h b/src/core/execute.h index f8995a4203..1faff160cb 100644 --- a/src/core/execute.h +++ b/src/core/execute.h @@ -99,6 +99,7 @@ struct ExecRuntime { struct ExecContext { char **environment; char **environment_files; + char **pass_environment; struct rlimit *rlimit[_RLIMIT_MAX]; char *working_directory, *root_directory; diff --git a/src/core/load-fragment-gperf.gperf.m4 b/src/core/load-fragment-gperf.gperf.m4 index 75b11ee4f2..3294054ef7 100644 --- a/src/core/load-fragment-gperf.gperf.m4 +++ b/src/core/load-fragment-gperf.gperf.m4 @@ -33,6 +33,7 @@ $1.CPUAffinity, config_parse_exec_cpu_affinity, 0, $1.UMask, config_parse_mode, 0, offsetof($1, exec_context.umask) $1.Environment, config_parse_environ, 0, offsetof($1, exec_context.environment) $1.EnvironmentFile, config_parse_unit_env_file, 0, offsetof($1, exec_context.environment_files) +$1.PassEnvironment, config_parse_pass_environ, 0, offsetof($1, exec_context.pass_environment) $1.StandardInput, config_parse_input, 0, offsetof($1, exec_context.std_input) $1.StandardOutput, config_parse_output, 0, offsetof($1, exec_context.std_output) $1.StandardError, config_parse_output, 0, offsetof($1, exec_context.std_error) diff --git a/src/core/load-fragment.c b/src/core/load-fragment.c index 79cabd26e7..53d96dee14 100644 --- a/src/core/load-fragment.c +++ b/src/core/load-fragment.c @@ -2196,6 +2196,70 @@ int config_parse_environ(const char *unit, return 0; } +int config_parse_pass_environ(const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + const char *whole_rvalue = rvalue; + char*** passenv = data; + _cleanup_strv_free_ char **n = NULL; + size_t nlen = 0, nbufsize = 0; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + if (isempty(rvalue)) { + /* Empty assignment resets the list */ + *passenv = strv_free(*passenv); + return 0; + } + + for (;;) { + _cleanup_free_ char *word = NULL; + + r = extract_first_word(&rvalue, &word, WHITESPACE, EXTRACT_QUOTES); + if (r == 0) + break; + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, + "Trailing garbage in %s, ignoring: %s", lvalue, whole_rvalue); + break; + } + + if (!env_name_is_valid(word)) { + log_syntax(unit, LOG_ERR, filename, line, EINVAL, + "Invalid environment name for %s, ignoring: %s", lvalue, word); + continue; + } + + if (!GREEDY_REALLOC(n, nbufsize, nlen + 2)) + return log_oom(); + n[nlen++] = word; + n[nlen] = NULL; + word = NULL; + } + + if (n) { + r = strv_extend_strv(passenv, n, true); + if (r < 0) + return r; + } + + return 0; +} + int config_parse_ip_tos(const char *unit, const char *filename, unsigned line, diff --git a/src/core/load-fragment.h b/src/core/load-fragment.h index 0cf821289c..eb4de1582f 100644 --- a/src/core/load-fragment.h +++ b/src/core/load-fragment.h @@ -84,6 +84,7 @@ int config_parse_syscall_filter(const char *unit, const char *filename, unsigned int config_parse_syscall_archs(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); int config_parse_syscall_errno(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); int config_parse_environ(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_pass_environ(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); int config_parse_unit_slice(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); int config_parse_cpu_shares(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); int config_parse_memory_limit(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); diff --git a/src/shared/bus-util.c b/src/shared/bus-util.c index a13991a960..73ceeba18f 100644 --- a/src/shared/bus-util.c +++ b/src/shared/bus-util.c @@ -1654,7 +1654,7 @@ int bus_append_unit_property_assignment(sd_bus_message *m, const char *assignmen r = sd_bus_message_append(m, "v", "i", i); - } else if (streq(field, "Environment")) { + } else if (STR_IN_SET(field, "Environment", "PassEnvironment")) { const char *p; r = sd_bus_message_open_container(m, 'v', "as"); @@ -1678,9 +1678,16 @@ int bus_append_unit_property_assignment(sd_bus_message *m, const char *assignmen if (r == 0) break; - if (!env_assignment_is_valid(word)) { - log_error("Invalid environment assignment: %s", eq); - return -EINVAL; + if (streq(field, "Environment")) { + if (!env_assignment_is_valid(word)) { + log_error("Invalid environment assignment: %s", word); + return -EINVAL; + } + } else { /* PassEnvironment */ + if (!env_name_is_valid(word)) { + log_error("Invalid environment variable name: %s", word); + return -EINVAL; + } } r = sd_bus_message_append_basic(m, 's', word); diff --git a/src/test/test-execute.c b/src/test/test-execute.c index e2ec53ee51..03ec0fcfc7 100644 --- a/src/test/test-execute.c +++ b/src/test/test-execute.c @@ -168,6 +168,30 @@ static void test_exec_environmentfile(Manager *m) { unlink("/tmp/test-exec_environmentfile.conf"); } +static void test_exec_passenvironment(Manager *m) { + /* test-execute runs under MANAGER_USER which, by default, forwards all + * variables present in the environment, but only those that are + * present _at the time it is created_! + * + * So these PassEnvironment checks are still expected to work, since we + * are ensuring the variables are not present at manager creation (they + * are unset explicitly in main) and are only set here. + * + * This is still a good approximation of how a test for MANAGER_SYSTEM + * would work. + */ + assert_se(setenv("VAR1", "word1 word2", 1) == 0); + assert_se(setenv("VAR2", "word3", 1) == 0); + assert_se(setenv("VAR3", "$word 5 6", 1) == 0); + test(m, "exec-passenvironment.service", 0, CLD_EXITED); + test(m, "exec-passenvironment-repeated.service", 0, CLD_EXITED); + test(m, "exec-passenvironment-empty.service", 0, CLD_EXITED); + assert_se(unsetenv("VAR1") == 0); + assert_se(unsetenv("VAR2") == 0); + assert_se(unsetenv("VAR3") == 0); + test(m, "exec-passenvironment-absent.service", 0, CLD_EXITED); +} + static void test_exec_umask(Manager *m) { test(m, "exec-umask-default.service", 0, CLD_EXITED); test(m, "exec-umask-0177.service", 0, CLD_EXITED); @@ -237,6 +261,7 @@ int main(int argc, char *argv[]) { test_exec_group, test_exec_environment, test_exec_environmentfile, + test_exec_passenvironment, test_exec_umask, test_exec_runtimedirectory, test_exec_capabilityboundingset, @@ -260,6 +285,16 @@ int main(int argc, char *argv[]) { assert_se(setenv("XDG_RUNTIME_DIR", "/tmp/", 1) == 0); assert_se(set_unit_path(TEST_DIR "/test-execute/") >= 0); + /* Unset VAR1, VAR2 and VAR3 which are used in the PassEnvironment test + * cases, otherwise (and if they are present in the environment), + * `manager_default_environment` will copy them into the default + * environment which is passed to each created job, which will make the + * tests that expect those not to be present to fail. + */ + assert_se(unsetenv("VAR1") == 0); + assert_se(unsetenv("VAR2") == 0); + assert_se(unsetenv("VAR3") == 0); + r = manager_new(MANAGER_USER, true, &m); if (IN_SET(r, -EPERM, -EACCES, -EADDRINUSE, -EHOSTDOWN, -ENOENT)) { printf("Skipping test: manager_new: %s", strerror(-r)); diff --git a/src/test/test-unit-file.c b/src/test/test-unit-file.c index 3648ec9c58..a8e5b6feee 100644 --- a/src/test/test-unit-file.c +++ b/src/test/test-unit-file.c @@ -741,6 +741,44 @@ static void test_config_parse_rlimit(void) { rl[RLIMIT_RTTIME] = mfree(rl[RLIMIT_RTTIME]); } +static void test_config_parse_pass_environ(void) { + /* int config_parse_pass_environ( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) */ + int r; + _cleanup_strv_free_ char **passenv = NULL; + + r = config_parse_pass_environ(NULL, "fake", 1, "section", 1, + "PassEnvironment", 0, "A B", + &passenv, NULL); + assert_se(r >= 0); + assert_se(strv_length(passenv) == 2); + assert_se(streq(passenv[0], "A")); + assert_se(streq(passenv[1], "B")); + + r = config_parse_pass_environ(NULL, "fake", 1, "section", 1, + "PassEnvironment", 0, "", + &passenv, NULL); + assert_se(r >= 0); + assert_se(strv_isempty(passenv)); + + r = config_parse_pass_environ(NULL, "fake", 1, "section", 1, + "PassEnvironment", 0, "'invalid name' 'normal_name' A=1 \\", + &passenv, NULL); + assert_se(r >= 0); + assert_se(strv_length(passenv) == 1); + assert_se(streq(passenv[0], "normal_name")); + +} + int main(int argc, char *argv[]) { int r; @@ -751,6 +789,7 @@ int main(int argc, char *argv[]) { test_config_parse_exec(); test_config_parse_bounding_set(); test_config_parse_rlimit(); + test_config_parse_pass_environ(); test_load_env_file_1(); test_load_env_file_2(); test_load_env_file_3(); |