summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/basic/env-util.c15
-rw-r--r--src/basic/env-util.h1
-rw-r--r--src/core/dbus-execute.c33
-rw-r--r--src/core/execute.c43
-rw-r--r--src/core/execute.h1
-rw-r--r--src/core/load-fragment-gperf.gperf.m41
-rw-r--r--src/core/load-fragment.c64
-rw-r--r--src/core/load-fragment.h1
-rw-r--r--src/network/networkd-link.c33
-rw-r--r--src/shared/bus-util.c15
-rw-r--r--src/test/test-execute.c35
-rw-r--r--src/test/test-socket-util.c41
-rw-r--r--src/test/test-unit-file.c39
13 files changed, 294 insertions, 28 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 93eeeabe66..18cbbf0dcc 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/network/networkd-link.c b/src/network/networkd-link.c
index a7d0908bdd..00c57b2960 100644
--- a/src/network/networkd-link.c
+++ b/src/network/networkd-link.c
@@ -2017,6 +2017,35 @@ static int link_set_ipv6_hop_limit(Link *link) {
return 0;
}
+static int link_drop_foreign_config(Link *link) {
+ Address *address;
+ Route *route;
+ Iterator i;
+ int r;
+
+ SET_FOREACH(address, link->addresses_foreign, i) {
+ /* we consider IPv6LL addresses to be managed by the kernel */
+ if (address->family == AF_INET6 && in_addr_is_link_local(AF_INET6, &address->in_addr) == 1)
+ continue;
+
+ r = address_remove(address, link, link_address_remove_handler);
+ if (r < 0)
+ return r;
+ }
+
+ SET_FOREACH(route, link->routes_foreign, i) {
+ /* do not touch routes managed by the kernel */
+ if (route->protocol == RTPROT_KERNEL)
+ continue;
+
+ r = route_remove(route, link, link_address_remove_handler);
+ if (r < 0)
+ return r;
+ }
+
+ return 0;
+}
+
static int link_configure(Link *link) {
int r;
@@ -2024,6 +2053,10 @@ static int link_configure(Link *link) {
assert(link->network);
assert(link->state == LINK_STATE_PENDING);
+ r = link_drop_foreign_config(link);
+ if (r < 0)
+ return r;
+
r = link_set_bridge_fdb(link);
if (r < 0)
return r;
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-socket-util.c b/src/test/test-socket-util.c
index b9699b2028..33ff3755bc 100644
--- a/src/test/test-socket-util.c
+++ b/src/test/test-socket-util.c
@@ -41,28 +41,25 @@ static void test_socket_address_parse(void) {
assert_se(socket_address_parse(&a, "65535") >= 0);
- if (socket_ipv6_is_supported()) {
- assert_se(socket_address_parse(&a, "[::1]") < 0);
- assert_se(socket_address_parse(&a, "[::1]8888") < 0);
- assert_se(socket_address_parse(&a, "::1") < 0);
- assert_se(socket_address_parse(&a, "[::1]:0") < 0);
- assert_se(socket_address_parse(&a, "[::1]:65536") < 0);
- assert_se(socket_address_parse(&a, "[a:b:1]:8888") < 0);
-
- assert_se(socket_address_parse(&a, "8888") >= 0);
- assert_se(a.sockaddr.sa.sa_family == AF_INET6);
-
- assert_se(socket_address_parse(&a, "[2001:0db8:0000:85a3:0000:0000:ac1f:8001]:8888") >= 0);
- assert_se(a.sockaddr.sa.sa_family == AF_INET6);
-
- assert_se(socket_address_parse(&a, "[::1]:8888") >= 0);
- assert_se(a.sockaddr.sa.sa_family == AF_INET6);
- } else {
- assert_se(socket_address_parse(&a, "[::1]:8888") < 0);
-
- assert_se(socket_address_parse(&a, "8888") >= 0);
- assert_se(a.sockaddr.sa.sa_family == AF_INET);
- }
+ /* The checks below will pass even if ipv6 is disabled in
+ * kernel. The underlying glibc's inet_pton() is just a string
+ * parser and doesn't make any syscalls. */
+
+ assert_se(socket_address_parse(&a, "[::1]") < 0);
+ assert_se(socket_address_parse(&a, "[::1]8888") < 0);
+ assert_se(socket_address_parse(&a, "::1") < 0);
+ assert_se(socket_address_parse(&a, "[::1]:0") < 0);
+ assert_se(socket_address_parse(&a, "[::1]:65536") < 0);
+ assert_se(socket_address_parse(&a, "[a:b:1]:8888") < 0);
+
+ assert_se(socket_address_parse(&a, "8888") >= 0);
+ assert_se(a.sockaddr.sa.sa_family == (socket_ipv6_is_supported() ? AF_INET6 : AF_INET));
+
+ assert_se(socket_address_parse(&a, "[2001:0db8:0000:85a3:0000:0000:ac1f:8001]:8888") >= 0);
+ assert_se(a.sockaddr.sa.sa_family == AF_INET6);
+
+ assert_se(socket_address_parse(&a, "[::1]:8888") >= 0);
+ assert_se(a.sockaddr.sa.sa_family == AF_INET6);
assert_se(socket_address_parse(&a, "192.168.1.254:8888") >= 0);
assert_se(a.sockaddr.sa.sa_family == AF_INET);
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();