summaryrefslogtreecommitdiff
path: root/src/core
diff options
context:
space:
mode:
authorLennart Poettering <lennart@poettering.net>2021-06-21 14:19:07 +0200
committerLennart Poettering <lennart@poettering.net>2021-07-08 09:30:56 +0200
commit43144be4a1a801442a995754214be58d99a76ff1 (patch)
tree16af56cc6011572742dfa869bc39c10e7bc62195 /src/core
parent5945640e2af1c5ebba0b8c1329f077baf90d31c9 (diff)
downloadsystemd-43144be4a1a801442a995754214be58d99a76ff1.tar.gz
pid1: add support for encrypted credentials
Diffstat (limited to 'src/core')
-rw-r--r--src/core/dbus-execute.c71
-rw-r--r--src/core/execute.c124
-rw-r--r--src/core/execute.h13
-rw-r--r--src/core/load-fragment-gperf.gperf.in2
-rw-r--r--src/core/load-fragment.c89
5 files changed, 224 insertions, 75 deletions
diff --git a/src/core/dbus-execute.c b/src/core/dbus-execute.c
index 50daef6702..f6783e924a 100644
--- a/src/core/dbus-execute.c
+++ b/src/core/dbus-execute.c
@@ -820,6 +820,9 @@ static int property_get_set_credential(
HASHMAP_FOREACH(sc, c->set_credentials) {
+ if (sc->encrypted != streq(property, "SetCredentialEncrypted"))
+ continue;
+
r = sd_bus_message_open_container(reply, 'r', "say");
if (r < 0)
return r;
@@ -850,7 +853,7 @@ static int property_get_load_credential(
sd_bus_error *error) {
ExecContext *c = userdata;
- char **i, **j;
+ ExecLoadCredential *lc;
int r;
assert(bus);
@@ -862,8 +865,12 @@ static int property_get_load_credential(
if (r < 0)
return r;
- STRV_FOREACH_PAIR(i, j, c->load_credentials) {
- r = sd_bus_message_append(reply, "(ss)", *i, *j);
+ HASHMAP_FOREACH(lc, c->load_credentials) {
+
+ if (lc->encrypted != streq(property, "LoadCredentialEncrypted"))
+ continue;
+
+ r = sd_bus_message_append(reply, "(ss)", lc->id, lc->path);
if (r < 0)
return r;
}
@@ -1144,7 +1151,9 @@ const sd_bus_vtable bus_exec_vtable[] = {
SD_BUS_PROPERTY("DynamicUser", "b", bus_property_get_bool, offsetof(ExecContext, dynamic_user), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("RemoveIPC", "b", bus_property_get_bool, offsetof(ExecContext, remove_ipc), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("SetCredential", "a(say)", property_get_set_credential, 0, SD_BUS_VTABLE_PROPERTY_CONST),
+ SD_BUS_PROPERTY("SetCredentialEncrypted", "a(say)", property_get_set_credential, 0, SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("LoadCredential", "a(ss)", property_get_load_credential, 0, SD_BUS_VTABLE_PROPERTY_CONST),
+ SD_BUS_PROPERTY("LoadCredentialEncrypted", "a(ss)", property_get_load_credential, 0, SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("SupplementaryGroups", "as", NULL, offsetof(ExecContext, supplementary_groups), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("PAMName", "s", NULL, offsetof(ExecContext, pam_name), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("ReadWritePaths", "as", NULL, offsetof(ExecContext, read_write_paths), SD_BUS_VTABLE_PROPERTY_CONST),
@@ -1916,7 +1925,7 @@ int bus_exec_context_set_transient_property(
return 1;
- } else if (streq(name, "SetCredential")) {
+ } else if (STR_IN_SET(name, "SetCredential", "SetCredentialEncrypted")) {
bool isempty = true;
r = sd_bus_message_enter_container(message, 'a', "(say)");
@@ -1964,20 +1973,24 @@ int bus_exec_context_set_transient_property(
if (old) {
free_and_replace(old->data, copy);
old->size = sz;
+ old->encrypted = streq(name, "SetCredentialEncrypted");
} else {
_cleanup_(exec_set_credential_freep) ExecSetCredential *sc = NULL;
- sc = new0(ExecSetCredential, 1);
+ sc = new(ExecSetCredential, 1);
if (!sc)
return -ENOMEM;
- sc->id = strdup(id);
+ *sc = (ExecSetCredential) {
+ .id = strdup(id),
+ .data = TAKE_PTR(copy),
+ .size = sz,
+ .encrypted = streq(name, "SetCredentialEncrypted"),
+ };
+
if (!sc->id)
return -ENOMEM;
- sc->data = TAKE_PTR(copy);
- sc->size = sz;
-
r = hashmap_ensure_put(&c->set_credentials, &exec_set_credential_hash_ops, sc->id, sc);
if (r < 0)
return r;
@@ -2008,7 +2021,7 @@ int bus_exec_context_set_transient_property(
return 1;
- } else if (streq(name, "LoadCredential")) {
+ } else if (STR_IN_SET(name, "LoadCredential", "LoadCredentialEncrypted")) {
bool isempty = true;
r = sd_bus_message_enter_container(message, 'a', "(ss)");
@@ -2033,9 +2046,39 @@ int bus_exec_context_set_transient_property(
isempty = false;
if (!UNIT_WRITE_FLAGS_NOOP(flags)) {
- r = strv_extend_strv(&c->load_credentials, STRV_MAKE(id, source), /* filter_duplicates = */ false);
- if (r < 0)
- return r;
+ _cleanup_free_ char *copy = NULL;
+ ExecLoadCredential *old;
+
+ copy = strdup(source);
+ if (!copy)
+ return -ENOMEM;
+
+ old = hashmap_get(c->load_credentials, id);
+ if (old) {
+ free_and_replace(old->path, copy);
+ old->encrypted = streq(name, "LoadCredentialEncrypted");
+ } else {
+ _cleanup_(exec_load_credential_freep) ExecLoadCredential *lc = NULL;
+
+ lc = new(ExecLoadCredential, 1);
+ if (!lc)
+ return -ENOMEM;
+
+ *lc = (ExecLoadCredential) {
+ .id = strdup(id),
+ .path = TAKE_PTR(copy),
+ .encrypted = streq(name, "LoadCredentialEncrypted"),
+ };
+
+ if (!lc->id)
+ return -ENOMEM;
+
+ r = hashmap_ensure_put(&c->load_credentials, &exec_load_credential_hash_ops, lc->id, lc);
+ if (r < 0)
+ return r;
+
+ TAKE_PTR(lc);
+ }
(void) unit_write_settingf(u, flags|UNIT_ESCAPE_SPECIFIERS, name, "%s=%s:%s", name, id, source);
}
@@ -2046,7 +2089,7 @@ int bus_exec_context_set_transient_property(
return r;
if (!UNIT_WRITE_FLAGS_NOOP(flags) && isempty) {
- c->load_credentials = strv_free(c->load_credentials);
+ c->load_credentials = hashmap_free(c->load_credentials);
(void) unit_write_settingf(u, flags, name, "%s=", name);
}
diff --git a/src/core/execute.c b/src/core/execute.c
index 2a337b55a2..f760d6ae07 100644
--- a/src/core/execute.c
+++ b/src/core/execute.c
@@ -46,6 +46,7 @@
#include "cgroup-setup.h"
#include "chown-recursive.h"
#include "cpu-set-util.h"
+#include "creds-util.h"
#include "data-fd-util.h"
#include "def.h"
#include "env-file.h"
@@ -1454,7 +1455,7 @@ static bool exec_context_has_credentials(const ExecContext *context) {
assert(context);
return !hashmap_isempty(context->set_credentials) ||
- context->load_credentials;
+ !hashmap_isempty(context->load_credentials);
}
#if HAVE_SECCOMP
@@ -2486,7 +2487,7 @@ static int write_credential(
return -errno;
}
- r = loop_write(fd, data, size, /* do_pool = */ false);
+ r = loop_write(fd, data, size, /* do_poll = */ false);
if (r < 0)
return r;
@@ -2519,8 +2520,6 @@ static int write_credential(
return 0;
}
-#define CREDENTIALS_BYTES_MAX (1024LU * 1024LU) /* Refuse to pass more than 1M, after all this is unswappable memory */
-
static int acquire_credentials(
const ExecContext *context,
const ExecParameters *params,
@@ -2529,10 +2528,10 @@ static int acquire_credentials(
uid_t uid,
bool ownership_ok) {
- uint64_t left = CREDENTIALS_BYTES_MAX;
+ uint64_t left = CREDENTIALS_TOTAL_SIZE_MAX;
_cleanup_close_ int dfd = -1;
+ ExecLoadCredential *lc;
ExecSetCredential *sc;
- char **id, **fn;
int r;
assert(context);
@@ -2542,39 +2541,23 @@ static int acquire_credentials(
if (dfd < 0)
return -errno;
- /* First we use the literally specified credentials. Note that they might be overridden again below,
- * and thus act as a "default" if the same credential is specified multiple times */
- HASHMAP_FOREACH(sc, context->set_credentials) {
- size_t add;
-
- add = strlen(sc->id) + sc->size;
- if (add > left)
- return -E2BIG;
-
- r = write_credential(dfd, sc->id, sc->data, sc->size, uid, ownership_ok);
- if (r < 0)
- return r;
-
- left -= add;
- }
-
- /* Then, load credential off disk (or acquire via AF_UNIX socket) */
- STRV_FOREACH_PAIR(id, fn, context->load_credentials) {
- ReadFullFileFlags flags = READ_FULL_FILE_SECURE;
+ /* First, load credentials off disk (or acquire via AF_UNIX socket) */
+ HASHMAP_FOREACH(lc, context->load_credentials) {
+ ReadFullFileFlags flags = READ_FULL_FILE_SECURE|READ_FULL_FILE_FAIL_WHEN_LARGER;
_cleanup_(erase_and_freep) char *data = NULL;
_cleanup_free_ char *j = NULL, *bindname = NULL;
bool missing_ok = true;
const char *source;
size_t size, add;
- if (path_is_absolute(*fn)) {
+ if (path_is_absolute(lc->path)) {
/* If this is an absolute path, read the data directly from it, and support AF_UNIX sockets */
- source = *fn;
+ source = lc->path;
flags |= READ_FULL_FILE_CONNECT_SOCKET;
/* Pass some minimal info about the unit and the credential name we are looking to acquire
* via the source socket address in case we read off an AF_UNIX socket. */
- if (asprintf(&bindname, "@%" PRIx64"/unit/%s/%s", random_u64(), unit, *id) < 0)
+ if (asprintf(&bindname, "@%" PRIx64"/unit/%s/%s", random_u64(), unit, lc->id) < 0)
return -ENOMEM;
missing_ok = false;
@@ -2583,7 +2566,7 @@ static int acquire_credentials(
/* If this is a relative path, take it relative to the credentials we received
* ourselves. We don't support the AF_UNIX stuff in this mode, since we are operating
* on a credential store, i.e. this is guaranteed to be regular files. */
- j = path_join(params->received_credentials, *fn);
+ j = path_join(params->received_credentials, lc->path);
if (!j)
return -ENOMEM;
@@ -2592,34 +2575,87 @@ static int acquire_credentials(
source = NULL;
if (source)
- r = read_full_file_full(AT_FDCWD, source, UINT64_MAX, SIZE_MAX, flags, bindname, &data, &size);
+ r = read_full_file_full(
+ AT_FDCWD, source,
+ UINT64_MAX,
+ lc->encrypted ? CREDENTIAL_ENCRYPTED_SIZE_MAX : CREDENTIAL_SIZE_MAX,
+ flags | (lc->encrypted ? READ_FULL_FILE_UNBASE64 : 0),
+ bindname,
+ &data, &size);
else
r = -ENOENT;
- if (r == -ENOENT && (missing_ok || faccessat(dfd, *id, F_OK, AT_SYMLINK_NOFOLLOW) >= 0)) {
+ if (r == -ENOENT && (missing_ok || hashmap_contains(context->set_credentials, lc->id))) {
/* Make a missing inherited credential non-fatal, let's just continue. After all apps
* will get clear errors if we don't pass such a missing credential on as they
* themselves will get ENOENT when trying to read them, which should not be much
* worse than when we handle the error here and make it fatal.
*
- * Also, if the source file doesn't exist, but we already acquired the key otherwise,
- * then don't fail either. */
- log_debug_errno(r, "Couldn't read inherited credential '%s', skipping: %m", *fn);
+ * Also, if the source file doesn't exist, but a fallback is set via SetCredentials=
+ * we are fine, too. */
+ log_debug_errno(r, "Couldn't read inherited credential '%s', skipping: %m", lc->path);
continue;
}
if (r < 0)
- return log_debug_errno(r, "Failed to read credential '%s': %m", *fn);
+ return log_debug_errno(r, "Failed to read credential '%s': %m", lc->path);
+
+ if (lc->encrypted) {
+ _cleanup_free_ void *plaintext = NULL;
+ size_t plaintext_size = 0;
+
+ r = decrypt_credential_and_warn(lc->id, now(CLOCK_REALTIME), NULL, data, size, &plaintext, &plaintext_size);
+ if (r < 0)
+ return r;
- add = strlen(*id) + size;
+ free_and_replace(data, plaintext);
+ size = plaintext_size;
+ }
+
+ add = strlen(lc->id) + size;
if (add > left)
return -E2BIG;
- r = write_credential(dfd, *id, data, size, uid, ownership_ok);
+ r = write_credential(dfd, lc->id, data, size, uid, ownership_ok);
if (r < 0)
return r;
left -= add;
}
+ /* First we use the literally specified credentials. Note that they might be overridden again below,
+ * and thus act as a "default" if the same credential is specified multiple times */
+ HASHMAP_FOREACH(sc, context->set_credentials) {
+ _cleanup_(erase_and_freep) void *plaintext = NULL;
+ const char *data;
+ size_t size, add;
+
+ if (faccessat(dfd, sc->id, F_OK, AT_SYMLINK_NOFOLLOW) >= 0)
+ continue;
+ if (errno != ENOENT)
+ return log_debug_errno(errno, "Failed to test if credential %s exists: %m", sc->id);
+
+ if (sc->encrypted) {
+ r = decrypt_credential_and_warn(sc->id, now(CLOCK_REALTIME), NULL, sc->data, sc->size, &plaintext, &size);
+ if (r < 0)
+ return r;
+
+ data = plaintext;
+ } else {
+ data = sc->data;
+ size = sc->size;
+ }
+
+ add = strlen(sc->id) + size;
+ if (add > left)
+ return -E2BIG;
+
+ r = write_credential(dfd, sc->id, data, size, uid, ownership_ok);
+ if (r < 0)
+ return r;
+
+
+ left -= add;
+ }
+
if (fchmod(dfd, 0500) < 0) /* Now take away the "w" bit */
return -errno;
@@ -2715,7 +2751,7 @@ static int setup_credentials_internal(
} else if (try == 1) {
_cleanup_free_ char *opts = NULL;
- if (asprintf(&opts, "mode=0700,nr_inodes=1024,size=%lu", CREDENTIALS_BYTES_MAX) < 0)
+ if (asprintf(&opts, "mode=0700,nr_inodes=1024,size=%zu", (size_t) CREDENTIALS_TOTAL_SIZE_MAX) < 0)
return -ENOMEM;
/* Fall back to "tmpfs" otherwise */
@@ -4928,7 +4964,7 @@ void exec_context_done(ExecContext *c) {
c->log_namespace = mfree(c->log_namespace);
- c->load_credentials = strv_free(c->load_credentials);
+ c->load_credentials = hashmap_free(c->load_credentials);
c->set_credentials = hashmap_free(c->set_credentials);
}
@@ -6597,7 +6633,17 @@ ExecSetCredential *exec_set_credential_free(ExecSetCredential *sc) {
return mfree(sc);
}
+ExecLoadCredential *exec_load_credential_free(ExecLoadCredential *lc) {
+ if (!lc)
+ return NULL;
+
+ free(lc->id);
+ free(lc->path);
+ return mfree(lc);
+}
+
DEFINE_HASH_OPS_WITH_VALUE_DESTRUCTOR(exec_set_credential_hash_ops, char, string_hash_func, string_compare_func, ExecSetCredential, exec_set_credential_free);
+DEFINE_HASH_OPS_WITH_VALUE_DESTRUCTOR(exec_load_credential_hash_ops, char, string_hash_func, string_compare_func, ExecLoadCredential, exec_load_credential_free);
static const char* const exec_input_table[_EXEC_INPUT_MAX] = {
[EXEC_INPUT_NULL] = "null",
diff --git a/src/core/execute.h b/src/core/execute.h
index 4c7a5b874f..fceb8bb6f7 100644
--- a/src/core/execute.h
+++ b/src/core/execute.h
@@ -150,9 +150,16 @@ typedef enum ExecCleanMask {
_EXEC_CLEAN_MASK_INVALID = -EINVAL,
} ExecCleanMask;
+/* A credential configured with LoadCredential= */
+typedef struct ExecLoadCredential {
+ char *id, *path;
+ bool encrypted;
+} ExecLoadCredential;
+
/* A credential configured with SetCredential= */
typedef struct ExecSetCredential {
char *id;
+ bool encrypted;
void *data;
size_t size;
} ExecSetCredential;
@@ -325,7 +332,7 @@ struct ExecContext {
usec_t timeout_clean_usec;
Hashmap *set_credentials; /* output id → ExecSetCredential */
- char **load_credentials; /* pairs of output id, path/input id */
+ Hashmap *load_credentials; /* output id → ExecLoadCredential */
};
static inline bool exec_context_restrict_namespaces_set(const ExecContext *c) {
@@ -458,7 +465,11 @@ bool exec_context_get_cpu_affinity_from_numa(const ExecContext *c);
ExecSetCredential *exec_set_credential_free(ExecSetCredential *sc);
DEFINE_TRIVIAL_CLEANUP_FUNC(ExecSetCredential*, exec_set_credential_free);
+ExecLoadCredential *exec_load_credential_free(ExecLoadCredential *lc);
+DEFINE_TRIVIAL_CLEANUP_FUNC(ExecLoadCredential*, exec_load_credential_free);
+
extern const struct hash_ops exec_set_credential_hash_ops;
+extern const struct hash_ops exec_load_credential_hash_ops;
const char* exec_output_to_string(ExecOutput i) _const_;
ExecOutput exec_output_from_string(const char *s) _pure_;
diff --git a/src/core/load-fragment-gperf.gperf.in b/src/core/load-fragment-gperf.gperf.in
index 42441eab6e..96507907f6 100644
--- a/src/core/load-fragment-gperf.gperf.in
+++ b/src/core/load-fragment-gperf.gperf.in
@@ -139,7 +139,9 @@
{{type}}.ConfigurationDirectoryMode, config_parse_mode, 0, offsetof({{type}}, exec_context.directories[EXEC_DIRECTORY_CONFIGURATION].mode)
{{type}}.ConfigurationDirectory, config_parse_exec_directories, 0, offsetof({{type}}, exec_context.directories[EXEC_DIRECTORY_CONFIGURATION].paths)
{{type}}.SetCredential, config_parse_set_credential, 0, offsetof({{type}}, exec_context)
+{{type}}.SetCredentialEncrypted, config_parse_set_credential, 1, offsetof({{type}}, exec_context)
{{type}}.LoadCredential, config_parse_load_credential, 0, offsetof({{type}}, exec_context)
+{{type}}.LoadCredentialEncrypted, config_parse_load_credential, 1, offsetof({{type}}, exec_context)
{{type}}.TimeoutCleanSec, config_parse_sec, 0, offsetof({{type}}, exec_context.timeout_clean_usec)
{% if HAVE_PAM %}
{{type}}.PAMName, config_parse_unit_string_printf, 0, offsetof({{type}}, exec_context.pam_name)
diff --git a/src/core/load-fragment.c b/src/core/load-fragment.c
index 8fb3c378ee..c72f5c22da 100644
--- a/src/core/load-fragment.c
+++ b/src/core/load-fragment.c
@@ -4469,12 +4469,15 @@ int config_parse_set_credential(
void *data,
void *userdata) {
- _cleanup_free_ char *word = NULL, *k = NULL, *unescaped = NULL;
+ _cleanup_free_ char *word = NULL, *k = NULL;
+ _cleanup_free_ void *d = NULL;
ExecContext *context = data;
ExecSetCredential *old;
Unit *u = userdata;
- const char *p;
- int r, l;
+ bool encrypted = ltype;
+ const char *p = rvalue;
+ size_t size;
+ int r;
assert(filename);
assert(lvalue);
@@ -4487,7 +4490,6 @@ int config_parse_set_credential(
return 0;
}
- p = rvalue;
r = extract_first_word(&p, &word, ":", EXTRACT_DONT_COALESCE_SEPARATORS);
if (r == -ENOMEM)
return log_oom();
@@ -4506,33 +4508,51 @@ int config_parse_set_credential(
return 0;
}
- /* We support escape codes here, so that users can insert trailing \n if they like */
- l = cunescape(p, UNESCAPE_ACCEPT_NUL, &unescaped);
- if (l < 0) {
- log_syntax(unit, LOG_WARNING, filename, line, l, "Can't unescape \"%s\", ignoring: %m", p);
- return 0;
+ if (encrypted) {
+ r = unbase64mem_full(p, SIZE_MAX, true, &d, &size);
+ if (r < 0) {
+ log_syntax(unit, LOG_WARNING, filename, line, r, "Encrypted credential data not valid Base64 data, ignoring.");
+ return 0;
+ }
+ } else {
+ char *unescaped = NULL;
+ int l;
+
+ /* We support escape codes here, so that users can insert trailing \n if they like */
+ l = cunescape(p, UNESCAPE_ACCEPT_NUL, &unescaped);
+ if (l < 0) {
+ log_syntax(unit, LOG_WARNING, filename, line, l, "Can't unescape \"%s\", ignoring: %m", p);
+ return 0;
+ }
+
+ d = unescaped;
+ size = l;
}
old = hashmap_get(context->set_credentials, k);
if (old) {
- free_and_replace(old->data, unescaped);
- old->size = l;
+ free_and_replace(old->data, d);
+ old->size = size;
+ old->encrypted = encrypted;
} else {
_cleanup_(exec_set_credential_freep) ExecSetCredential *sc = NULL;
- sc = new0(ExecSetCredential, 1);
+ sc = new(ExecSetCredential, 1);
if (!sc)
return log_oom();
- sc->id = TAKE_PTR(k);
- sc->data = TAKE_PTR(unescaped);
- sc->size = l;
+ *sc = (ExecSetCredential) {
+ .id = TAKE_PTR(k),
+ .data = TAKE_PTR(d),
+ .size = size,
+ .encrypted = encrypted,
+ };
r = hashmap_ensure_put(&context->set_credentials, &exec_set_credential_hash_ops, sc->id, sc);
if (r == -ENOMEM)
return log_oom();
if (r < 0) {
- log_syntax(unit, LOG_WARNING, filename, line, l,
+ log_syntax(unit, LOG_WARNING, filename, line, r,
"Duplicated credential value '%s', ignoring assignment: %s", sc->id, rvalue);
return 0;
}
@@ -4557,6 +4577,8 @@ int config_parse_load_credential(
_cleanup_free_ char *word = NULL, *k = NULL, *q = NULL;
ExecContext *context = data;
+ ExecLoadCredential *old;
+ bool encrypted = ltype;
Unit *u = userdata;
const char *p;
int r;
@@ -4568,7 +4590,7 @@ int config_parse_load_credential(
if (isempty(rvalue)) {
/* Empty assignment resets the list */
- context->load_credentials = strv_free(context->load_credentials);
+ context->load_credentials = hashmap_free(context->load_credentials);
return 0;
}
@@ -4604,14 +4626,39 @@ int config_parse_load_credential(
return 0;
}
if (path_is_absolute(q) ? !path_is_normalized(q) : !credential_name_valid(q)) {
- log_syntax(unit, LOG_WARNING, filename, line, r, "Credential source \"%s\" not valid, ignoring.", q);
+ log_syntax(unit, LOG_WARNING, filename, line, 0, "Credential source \"%s\" not valid, ignoring.", q);
return 0;
}
}
- r = strv_consume_pair(&context->load_credentials, TAKE_PTR(k), TAKE_PTR(q));
- if (r < 0)
- return log_oom();
+ old = hashmap_get(context->load_credentials, k);
+ if (old) {
+ free_and_replace(old->path, q);
+ old->encrypted = encrypted;
+ } else {
+ _cleanup_(exec_load_credential_freep) ExecLoadCredential *lc = NULL;
+
+ lc = new(ExecLoadCredential, 1);
+ if (!lc)
+ return log_oom();
+
+ *lc = (ExecLoadCredential) {
+ .id = TAKE_PTR(k),
+ .path = TAKE_PTR(q),
+ .encrypted = encrypted,
+ };
+
+ r = hashmap_ensure_put(&context->load_credentials, &exec_load_credential_hash_ops, lc->id, lc);
+ if (r == -ENOMEM)
+ return log_oom();
+ if (r < 0) {
+ log_syntax(unit, LOG_WARNING, filename, line, r,
+ "Duplicated credential value '%s', ignoring assignment: %s", lc->id, rvalue);
+ return 0;
+ }
+
+ TAKE_PTR(lc);
+ }
return 0;
}