#include "path-util.h"
#include "process-util.h"
#include "random-util.h"
+#include "recurse-dir.h"
#include "rlimit-util.h"
#include "rm-rf.h"
#if HAVE_SECCOMP
return 0;
}
+static int load_credential(
+ const ExecContext *context,
+ const ExecParameters *params,
+ ExecLoadCredential *lc,
+ const char *unit,
+ int read_dfd,
+ int write_dfd,
+ uid_t uid,
+ bool ownership_ok,
+ uint64_t *left) {
+
+ assert(context);
+ assert(lc);
+ assert(unit);
+ assert(write_dfd >= 0);
+ assert(left);
+
+ 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;
+ int r;
+
+ if (path_is_absolute(lc->path) || read_dfd >= 0) {
+ /* If this is an absolute path, read the data directly from it, and support AF_UNIX sockets */
+ 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, lc->id) < 0)
+ return -ENOMEM;
+
+ missing_ok = false;
+
+ } else if (params->received_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, lc->path);
+ if (!j)
+ return -ENOMEM;
+
+ source = j;
+ } else
+ source = NULL;
+
+ if (source)
+ r = read_full_file_full(
+ read_dfd, 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 || 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 a fallback is set via SetCredentials=
+ * we are fine, too. */
+ log_debug_errno(r, "Couldn't read inherited credential '%s', skipping: %m", lc->path);
+ return 0;
+ }
+ if (r < 0)
+ 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;
+
+ free_and_replace(data, plaintext);
+ size = plaintext_size;
+ }
+
+ add = strlen(lc->id) + size;
+ if (add > *left)
+ return -E2BIG;
+
+ r = write_credential(write_dfd, lc->id, data, size, uid, ownership_ok);
+ if (r < 0)
+ return r;
+
+ *left -= add;
+ return 0;
+}
+
+struct load_cred_args {
+ Set *seen_creds;
+
+ const ExecContext *context;
+ const ExecParameters *params;
+ ExecLoadCredential *parent_local_credential;
+ const char *unit;
+ int dfd;
+ uid_t uid;
+ bool ownership_ok;
+ uint64_t *left;
+};
+
+static int load_cred_recurse_dir_cb(
+ RecurseDirEvent event,
+ const char *path,
+ int dir_fd,
+ int inode_fd,
+ const struct dirent *de,
+ const struct statx *sx,
+ void *userdata) {
+
+ _cleanup_free_ char *credname = NULL, *sub_id = NULL;
+ struct load_cred_args *args = userdata;
+ int r;
+
+ if (event != RECURSE_DIR_ENTRY)
+ return RECURSE_DIR_CONTINUE;
+
+ if (!IN_SET(de->d_type, DT_REG, DT_SOCK))
+ return RECURSE_DIR_CONTINUE;
+
+ credname = strreplace(path, "/", "_");
+ if (!credname)
+ return -ENOMEM;
+
+ sub_id = strjoin(args->parent_local_credential->id, "_", credname);
+ if (!sub_id)
+ return -ENOMEM;
+
+ if (!credential_name_valid(sub_id))
+ return -EINVAL;
+
+ if (set_contains(args->seen_creds, sub_id)) {
+ log_debug("Skipping credential with duplicated ID %s at %s", sub_id, path);
+ return RECURSE_DIR_CONTINUE;
+ }
+
+ r = set_put_strdup(&args->seen_creds, sub_id);
+ if (r < 0)
+ return r;
+
+ r = load_credential(args->context, args->params,
+ &(ExecLoadCredential) {
+ .id = sub_id,
+ .path = (char *) de->d_name,
+ .encrypted = args->parent_local_credential->encrypted,
+ }, args->unit, dir_fd, args->dfd, args->uid, args->ownership_ok, args->left);
+ if (r < 0)
+ return r;
+
+ return RECURSE_DIR_CONTINUE;
+}
+
static int acquire_credentials(
const ExecContext *context,
const ExecParameters *params,
uint64_t left = CREDENTIALS_TOTAL_SIZE_MAX;
_cleanup_close_ int dfd = -1;
+ _cleanup_set_free_ Set *seen_creds = NULL;
ExecLoadCredential *lc;
ExecSetCredential *sc;
int r;
if (dfd < 0)
return -errno;
+ seen_creds = set_new(&string_hash_ops_free);
+ if (!seen_creds)
+ return -ENOMEM;
+
/* 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(lc->path)) {
- /* If this is an absolute path, read the data directly from it, and support AF_UNIX sockets */
- 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, lc->id) < 0)
- return -ENOMEM;
-
- missing_ok = false;
+ _cleanup_close_ int sub_fd = -1;
- } else if (params->received_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, lc->path);
- if (!j)
- return -ENOMEM;
-
- source = j;
- } else
- source = NULL;
-
- if (source)
- 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 || 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 a fallback is set via SetCredentials=
- * we are fine, too. */
- log_debug_errno(r, "Couldn't read inherited credential '%s', skipping: %m", lc->path);
+ /* Skip over credentials with unspecified paths. These are received by the
+ * service manager via the $CREDENTIALS_DIRECTORY environment variable. */
+ if (!is_path(lc->path) && streq(lc->id, lc->path))
continue;
- }
- if (r < 0)
- 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;
+ sub_fd = open(lc->path, O_DIRECTORY|O_CLOEXEC|O_RDONLY);
+ if (sub_fd < 0 && errno != ENOTDIR)
+ return -errno;
- r = decrypt_credential_and_warn(lc->id, now(CLOCK_REALTIME), NULL, data, size, &plaintext, &plaintext_size);
+ if (sub_fd < 0) {
+ r = set_put_strdup(&seen_creds, lc->id);
+ if (r < 0)
+ return r;
+ r = load_credential(context, params, lc, unit, -1, dfd, uid, ownership_ok, &left);
if (r < 0)
return r;
- free_and_replace(data, plaintext);
- size = plaintext_size;
+ } else {
+ r = recurse_dir(
+ sub_fd,
+ /* path= */ "",
+ /* statx_mask= */ 0,
+ /* n_depth_max= */ UINT_MAX,
+ RECURSE_DIR_IGNORE_DOT|RECURSE_DIR_ENSURE_TYPE,
+ load_cred_recurse_dir_cb,
+ &(struct load_cred_args) {
+ .seen_creds = seen_creds,
+ .context = context,
+ .params = params,
+ .parent_local_credential = lc,
+ .unit = unit,
+ .dfd = dfd,
+ .uid = uid,
+ .ownership_ok = ownership_ok,
+ .left = &left,
+ });
+ if (r < 0)
+ return r;
}
-
- add = strlen(lc->id) + size;
- if (add > left)
- return -E2BIG;
-
- 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,