]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
homework: Handle Update & Create w/ blob dir
authorAdrian Vovk <adrianvovk@gmail.com>
Wed, 10 Jan 2024 03:06:35 +0000 (22:06 -0500)
committerLuca Boccassi <bluca@debian.org>
Mon, 19 Feb 2024 11:18:11 +0000 (11:18 +0000)
Introduces new extended variants of the various incarnations of
Create and Update, which take a map of filenames to FDs. This map is
then used to populate the bulk directory.

FDs are used to prevent the client from abusing homed's blob directory
permissions (everything is made world-readable by homed) to open files
that they normally aren't allowed to open. Passing along an FD ensures
that the client has read access to the file it wants homed to make
world-readable.

Internally, homework uses the map to overwrite the system blob dir.
Later, homework's existing blob dir reconciliation logic will propagate
the new contents from the system blob dir into the embedded blob
dir

16 files changed:
man/org.freedesktop.home1.xml
src/home/home-util.c
src/home/home-util.h
src/home/homed-bus.c
src/home/homed-bus.h
src/home/homed-home-bus.c
src/home/homed-home-bus.h
src/home/homed-home.c
src/home/homed-home.h
src/home/homed-manager-bus.c
src/home/homework-blob.c
src/home/homework-blob.h
src/home/homework.c
src/home/org.freedesktop.home1.conf
src/home/user-record-util.c
src/home/user-record-util.h

index b1bb6587dc449a881d280587e744c518500596e9..8ac3d96086c291af6ebec9313caf2f505e059276 100644 (file)
@@ -72,6 +72,9 @@ node /org/freedesktop/home1 {
       RegisterHome(in  s user_record);
       UnregisterHome(in  s user_name);
       CreateHome(in  s user_record);
+      CreateHomeEx(in  s user_record,
+                   in  a{sh} blobs,
+                   in  t flags);
       RealizeHome(in  s user_name,
                   in  s secret);
       RemoveHome(in  s user_name);
@@ -81,6 +84,9 @@ node /org/freedesktop/home1 {
       AuthenticateHome(in  s user_name,
                        in  s secret);
       UpdateHome(in  s user_record);
+      UpdateHomeEx(in  s user_record,
+                   in  a{sh} blobs,
+                   in  t flags);
       ResizeHome(in  s user_name,
                  in  t size,
                  in  s secret);
@@ -151,6 +157,8 @@ node /org/freedesktop/home1 {
 
     <variablelist class="dbus-method" generated="True" extra-ref="CreateHome()"/>
 
+    <variablelist class="dbus-method" generated="True" extra-ref="CreateHomeEx()"/>
+
     <variablelist class="dbus-method" generated="True" extra-ref="RealizeHome()"/>
 
     <variablelist class="dbus-method" generated="True" extra-ref="RemoveHome()"/>
@@ -161,6 +169,8 @@ node /org/freedesktop/home1 {
 
     <variablelist class="dbus-method" generated="True" extra-ref="UpdateHome()"/>
 
+    <variablelist class="dbus-method" generated="True" extra-ref="UpdateHomeEx()"/>
+
     <variablelist class="dbus-method" generated="True" extra-ref="ResizeHome()"/>
 
     <variablelist class="dbus-method" generated="True" extra-ref="ChangePasswordHome()"/>
@@ -265,6 +275,19 @@ node /org/freedesktop/home1 {
       the user record locally and creates a home directory matching it, depending on the settings specified
       in the record in combination with local configuration.</para>
 
+      <para><function>CreateHomeEx()</function> is like <function>CreateHome()</function>, but it allows the
+      home directory to be created with a pre-populated blob directory (see
+      <ulink url="https://systemd.io/USER_RECORD_BLOB_DIRS">User Record Blob Directories</ulink> for more info).
+      This can be done via the dictionary passed as the <varname>blobs</varname> argument to this method: the values
+      are open file descriptors to regular files, and the keys are the filenames that should contain their respective
+      file's data in the blob directory. Note that for security reasons, the file descriptors passed into this method
+      must have enough privileges to read their target file and thus cannot be <literal>O_PATH</literal>; this
+      is done to ensure the caller is actually permitted to read the file they are asking to publish in the
+      blob directories. If the user record passed as the first argument contains a <literal>blobManifest</literal>
+      field it will be enforced; otherwise, a <literal>blobManifest</literal> field will be generated and inserted
+      into the record. The <varname>flags</varname> argument may be used for future expansion, but for now
+      pass 0.</para>
+
       <para><function>RealizeHome()</function> creates a home directory whose user record is already
       registered locally. This takes a user name plus a user record consisting only of the
       <literal>secret</literal> section. Invoking <function>RegisterHome()</function> followed by
@@ -308,6 +331,14 @@ node /org/freedesktop/home1 {
       as the storage resized using <function>ResizeHome()</function>. This method is equivalent to
       <function>Update()</function> on the <classname>org.freedesktop.home1.Home</classname> interface.</para>
 
+      <para><function>UpdateHomeEx()</function> is like <function>UpdateHome()</function>, but it allows for
+      changes to the blob directory (see <ulink url="https://systemd.io/USER_RECORD_BLOB_DIRS">User Record Blob
+      Directories</ulink> for more info). The <varname>blobs</varname> argument works in the same way as
+      <function>CreateHomeEx()</function>, so check there for details. The new blob directory contents passed into
+      this method will completely replace the user's existing blob directory. The <varname>flags</varname> argument
+      may be used for future expansion, but for now pass 0. This method is equivalent to <function>UpdateEx()</function>
+      on the <classname>org.freedesktop.home1.Home</classname> interface.</para>
+
       <para><function>ResizeHome()</function> resizes the storage associated with a user record. Takes a user
       name, a disk size in bytes and a user record consisting only of the <literal>secret</literal> section
       as argument. If the size is specified as <constant>UINT64_MAX</constant> the storage is resized to the
@@ -438,6 +469,9 @@ node /org/freedesktop/home1/home {
       Fixate(in  s secret);
       Authenticate(in  s secret);
       Update(in  s user_record);
+      UpdateEx(in  s user_record,
+               in  a{sh} blobs,
+               in  t flags);
       Resize(in  t size,
              in  s secret);
       ChangePassword(in  s new_secret,
@@ -504,6 +538,8 @@ node /org/freedesktop/home1/home {
 
     <variablelist class="dbus-method" generated="True" extra-ref="Update()"/>
 
+    <variablelist class="dbus-method" generated="True" extra-ref="UpdateEx()"/>
+
     <variablelist class="dbus-method" generated="True" extra-ref="Resize()"/>
 
     <variablelist class="dbus-method" generated="True" extra-ref="ChangePassword()"/>
@@ -540,11 +576,11 @@ node /org/freedesktop/home1/home {
       <para><function>Activate()</function>, <function>ActivateIfReferenced()</function>,
       <function>Deactivate()</function>, <function>Unregister()</function>, <function>Realize()</function>,
       <function>Remove()</function>, <function>Fixate()</function>, <function>Authenticate()</function>,
-      <function>Update()</function>, <function>Resize()</function>, <function>ChangePassword()</function>,
-      <function>Lock()</function>, <function>Unlock()</function>, <function>Acquire()</function>,
-      <function>Ref()</function>, <function>RefUnrestricted()</function>, <function>Release()</function>,
-      <function>InhibitSuspend()</function> operate like their matching counterparts on the
-      <classname>org.freedesktop.home1.Manager</classname> interface (see above). The main difference is that
+      <function>Update()</function>, <function>UpdateEx()</function>, <function>Resize()</function>,
+      <function>ChangePassword()</function>, <function>Lock()</function>, <function>Unlock()</function>,
+      <function>Acquire()</function>, <function>Ref()</function>, <function>RefUnrestricted()</function>,
+      <function>Release()</function>, <function>InhibitSuspend()</function> operate like their matching counterparts
+      on the <classname>org.freedesktop.home1.Manager</classname> interface (see above). The main difference is that
       they are methods of the home directory objects, and hence carry no additional user name
       parameter. Which of the two flavors of methods to call depends on the handles to the user known on the
       client side: if only the user name is known, it's preferable to use the methods on the manager object
@@ -575,11 +611,13 @@ node /org/freedesktop/home1/home {
     <title>History</title>
     <refsect2>
       <title>The Manager Object</title>
-      <para><function>InhibitSuspendHome()</function>, <function>ActivateHomeIfReferenced()</function>, <function>RefHomeUnrestricted()</function> wer added in version 256.</para>
+      <para><function>InhibitSuspendHome()</function>, <function>ActivateHomeIfReferenced()</function>, <function>RefHomeUnrestricted()</function>,
+      <function>CreateHomeEx()</function>, and <function>UpdateHomeEx()</function> were added in version 256.</para>
     </refsect2>
     <refsect2>
       <title>Home Objects</title>
-      <para><function>InhibitSuspend()</function>, <function>ActivateIfReferenced()</function> and <function>RefUnrestricted()</function> were added in version 256.</para>
+      <para><function>InhibitSuspend()</function>, <function>ActivateIfReferenced()</function>, <function>RefUnrestricted()</function>, and
+      <function>UpdateEx()</function> were added in version 256.</para>
     </refsect2>
   </refsect1>
 
index 9c9c0ff78aaace5a8bff9af3f1c29fd2aa7629fb..973523652dcac6c6eca482b3138df172f8210dff 100644 (file)
@@ -1,6 +1,7 @@
 /* SPDX-License-Identifier: LGPL-2.1-or-later */
 
 #include "dns-domain.h"
+#include "fd-util.h"
 #include "home-util.h"
 #include "libcrypt-util.h"
 #include "memory-util.h"
@@ -9,6 +10,8 @@
 #include "strv.h"
 #include "user-util.h"
 
+DEFINE_HASH_OPS_FULL(blob_fd_hash_ops, char, path_hash_func, path_compare, free, void, close_fd_ptr);
+
 bool suitable_user_name(const char *name) {
 
         /* Checks whether the specified name is suitable for management via homed. Note that client-side
index ead8f5637ee521f07bcd2a256eef54d9ee1317aa..f2e5787a546f578ff51460b97f5b2e70a014213f 100644 (file)
@@ -5,6 +5,7 @@
 
 #include "sd-bus.h"
 
+#include "hash-funcs.h"
 #include "time-util.h"
 #include "user-record.h"
 
@@ -20,6 +21,8 @@
 /* This should be 83% right now, i.e. 100 of (100 + 20). Let's protect us against accidental changes. */
 assert_cc(USER_DISK_SIZE_DEFAULT_PERCENT == 83U);
 
+extern const struct hash_ops blob_fd_hash_ops;
+
 bool suitable_user_name(const char *name);
 int suitable_realm(const char *realm);
 int suitable_image_path(const char *path);
index 24b421a58c076aa6c28febb2248380117285ab4d..cbd84c231c7ba86e78ba9f6fd6d1c4780089e5c9 100644 (file)
@@ -1,6 +1,10 @@
 /* SPDX-License-Identifier: LGPL-2.1-or-later */
 
+#include "fd-util.h"
+#include "home-util.h"
 #include "homed-bus.h"
+#include "missing_fcntl.h"
+#include "stat-util.h"
 #include "strv.h"
 
 int bus_message_read_secret(sd_bus_message *m, UserRecord **ret, sd_bus_error *error) {
@@ -64,3 +68,72 @@ int bus_message_read_home_record(sd_bus_message *m, UserRecordLoadFlags flags, U
         *ret = TAKE_PTR(hr);
         return 0;
 }
+
+int bus_message_read_blobs(sd_bus_message *m, Hashmap **ret, sd_bus_error *error) {
+        _cleanup_hashmap_free_ Hashmap *blobs = NULL;
+        int r;
+
+        assert(m);
+        assert(ret);
+
+        /* We want to differentiate between blobs being NULL (not passed at all)
+         * and empty (passed from dbus, but it was empty) */
+        r = hashmap_ensure_allocated(&blobs, &blob_fd_hash_ops);
+        if (r < 0)
+                return r;
+
+        r = sd_bus_message_enter_container(m, 'a', "{sh}");
+        if (r < 0)
+                return r;
+
+        for (;;) {
+                _cleanup_free_ char *filename = NULL;
+                _cleanup_close_ int fd = -EBADF;
+                const char *_filename = NULL;
+                int _fd, flags;
+
+                r = sd_bus_message_read(m, "{sh}", &_filename, &_fd);
+                if (r < 0)
+                        return r;
+                if (r == 0)
+                        break;
+
+                filename = strdup(_filename);
+                if (!filename)
+                        return -ENOMEM;
+
+                fd = fcntl(_fd, F_DUPFD_CLOEXEC, 3);
+                if (fd < 0)
+                        return -errno;
+
+                r = suitable_blob_filename(filename);
+                if (r < 0)
+                        return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid blob directory filename: %s", filename);
+
+                r = fd_verify_regular(fd);
+                if (r < 0)
+                        return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "FD for %s is not a regular file", filename);
+
+                flags = fcntl(fd, F_GETFL);
+                if (flags < 0)
+                        return -errno;
+
+                /* Refuse fds w/ unexpected flags set. In particular, we don't want to permit O_PATH FDs, since
+                 * those don't actually guarentee that the client has access to the file. */
+                if ((flags & ~(O_ACCMODE|RAW_O_LARGEFILE)) != 0)
+                        return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "FD for %s has unexpected flags set", filename);
+
+                r = hashmap_put(blobs, filename, FD_TO_PTR(fd));
+                if (r < 0)
+                        return r;
+                TAKE_PTR(filename); /* Ownership transferred to hashmap */
+                TAKE_FD(fd);
+        }
+
+        r = sd_bus_message_exit_container(m);
+        if (r < 0)
+                return r;
+
+        *ret = TAKE_PTR(blobs);
+        return 0;
+}
index 977679b10ad3f85297c98e6c943c207f3ad94445..0660a5936c2d342f767ac228e550d8efb510d28c 100644 (file)
@@ -3,8 +3,10 @@
 
 #include "sd-bus.h"
 
-#include "user-record.h"
+#include "hashmap.h"
 #include "json.h"
+#include "user-record.h"
 
 int bus_message_read_secret(sd_bus_message *m, UserRecord **ret, sd_bus_error *error);
 int bus_message_read_home_record(sd_bus_message *m, UserRecordLoadFlags flags, UserRecord **ret, sd_bus_error *error);
+int bus_message_read_blobs(sd_bus_message *m, Hashmap **ret, sd_bus_error *error);
index 81f0df4c956c2bfde77ce01c40f5a39d63327b6b..f54de1f581a01f7182cb1e434fcab40a8c2a334f 100644 (file)
@@ -295,7 +295,7 @@ int bus_home_method_realize(
         if (r == 0)
                 return 1; /* Will call us back */
 
-        r = home_create(h, secret, error);
+        r = home_create(h, secret, NULL, 0, error);
         if (r < 0)
                 return r;
 
@@ -416,7 +416,13 @@ int bus_home_method_authenticate(
         return 1;
 }
 
-int bus_home_method_update_record(Home *h, sd_bus_message *message, UserRecord *hr, sd_bus_error *error) {
+int bus_home_method_update_record(
+                Home *h,
+                sd_bus_message *message,
+                UserRecord *hr,
+                Hashmap *blobs,
+                uint64_t flags,
+                sd_bus_error *error) {
         int r;
 
         assert(h);
@@ -427,6 +433,9 @@ int bus_home_method_update_record(Home *h, sd_bus_message *message, UserRecord *
         if (r < 0)
                 return r;
 
+        if (flags != 0)
+                return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Provided flags are unsupported.");
+
         r = home_verify_polkit_async(
                         h,
                         message,
@@ -438,7 +447,7 @@ int bus_home_method_update_record(Home *h, sd_bus_message *message, UserRecord *
         if (r == 0)
                 return 1; /* Will call us back */
 
-        r = home_update(h, hr, error);
+        r = home_update(h, hr, blobs, flags, error);
         if (r < 0)
                 return r;
 
@@ -458,6 +467,8 @@ int bus_home_method_update(
                 sd_bus_error *error) {
 
         _cleanup_(user_record_unrefp) UserRecord *hr = NULL;
+        _cleanup_hashmap_free_ Hashmap *blobs = NULL;
+        uint64_t flags = 0;
         Home *h = ASSERT_PTR(userdata);
         int r;
 
@@ -467,7 +478,17 @@ int bus_home_method_update(
         if (r < 0)
                 return r;
 
-        return bus_home_method_update_record(h, message, hr, error);
+        if (endswith(sd_bus_message_get_member(message), "Ex")) {
+                r = bus_message_read_blobs(message, &blobs, error);
+                if (r < 0)
+                        return r;
+
+                r = sd_bus_message_read(message, "t", &flags);
+                if (r < 0)
+                        return r;
+        }
+
+        return bus_home_method_update_record(h, message, hr, blobs, flags, error);
 }
 
 int bus_home_method_resize(
@@ -892,6 +913,11 @@ const sd_bus_vtable home_vtable[] = {
                                 SD_BUS_NO_RESULT,
                                 bus_home_method_update,
                                 SD_BUS_VTABLE_UNPRIVILEGED|SD_BUS_VTABLE_SENSITIVE),
+        SD_BUS_METHOD_WITH_ARGS("UpdateEx",
+                                SD_BUS_ARGS("s", user_record, "a{sh}", blobs, "t", flags),
+                                SD_BUS_NO_RESULT,
+                                bus_home_method_update,
+                                SD_BUS_VTABLE_UNPRIVILEGED|SD_BUS_VTABLE_SENSITIVE),
         SD_BUS_METHOD_WITH_ARGS("Resize",
                                 SD_BUS_ARGS("t", size, "s", secret),
                                 SD_BUS_NO_RESULT,
index a791c763126203f300232474f7aa223c52d11ebc..9ba2cf1252504ec49d4aebd66acd6e8cb39e3686 100644 (file)
@@ -17,7 +17,7 @@ int bus_home_method_remove(sd_bus_message *message, void *userdata, sd_bus_error
 int bus_home_method_fixate(sd_bus_message *message, void *userdata, sd_bus_error *error);
 int bus_home_method_authenticate(sd_bus_message *message, void *userdata, sd_bus_error *error);
 int bus_home_method_update(sd_bus_message *message, void *userdata, sd_bus_error *error);
-int bus_home_method_update_record(Home *home, sd_bus_message *message, UserRecord *hr, sd_bus_error *error);
+int bus_home_method_update_record(Home *home, sd_bus_message *message, UserRecord *hr, Hashmap *blobs, uint64_t flags, sd_bus_error *error);
 int bus_home_method_resize(sd_bus_message *message, void *userdata, sd_bus_error *error);
 int bus_home_method_change_password(sd_bus_message *message, void *userdata, sd_bus_error *error);
 int bus_home_method_lock(sd_bus_message *message, void *userdata, sd_bus_error *error);
index 7bb078ee2c8f2d13bbc09e11ccdb0e2d26eefad8..a487eb1fd1ab7806127e5cc7e49eef96383c2f9c 100644 (file)
 assert_cc(HOME_UID_MIN <= HOME_UID_MAX);
 assert_cc(HOME_USERS_MAX <= (HOME_UID_MAX - HOME_UID_MIN + 1));
 
-static int home_start_work(Home *h, const char *verb, UserRecord *hr, UserRecord *secret);
+static int home_start_work(
+                Home *h,
+                const char *verb,
+                UserRecord *hr,
+                UserRecord *secret,
+                Hashmap *blobs,
+                uint64_t flags);
 
 DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR(operation_hash_ops, void, trivial_hash_func, trivial_compare_func, Operation, operation_unref);
 
@@ -738,7 +744,7 @@ static void home_fixate_finish(Home *h, int ret, UserRecord *hr) {
 
         if (IN_SET(h->state, HOME_FIXATING_FOR_ACTIVATION, HOME_FIXATING_FOR_ACQUIRE)) {
 
-                r = home_start_work(h, "activate", h->record, secret);
+                r = home_start_work(h, "activate", h->record, secret, NULL, 0);
                 if (r < 0) {
                         h->current_operation = operation_result_unref(h->current_operation, r, NULL);
                         home_set_state(h, _HOME_STATE_INVALID);
@@ -1178,10 +1184,17 @@ static int home_on_worker_process(sd_event_source *s, const siginfo_t *si, void
         return 0;
 }
 
-static int home_start_work(Home *h, const char *verb, UserRecord *hr, UserRecord *secret) {
-        _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
+static int home_start_work(
+                Home *h,
+                const char *verb,
+                UserRecord *hr,
+                UserRecord *secret,
+                Hashmap *blobs,
+                uint64_t flags) {
+        _cleanup_(json_variant_unrefp) JsonVariant *v = NULL, *fdmap = NULL;
         _cleanup_(erase_and_freep) char *formatted = NULL;
         _cleanup_close_ int stdin_fd = -EBADF, stdout_fd = -EBADF;
+        _cleanup_free_ int *blob_fds = NULL;
         pid_t pid = 0;
         int r;
 
@@ -1209,6 +1222,37 @@ static int home_start_work(Home *h, const char *verb, UserRecord *hr, UserRecord
                         return r;
         }
 
+        if (blobs) {
+                const char *blob_filename = NULL;
+                void *fd_ptr;
+                size_t i = 0;
+
+                blob_fds = new(int, hashmap_size(blobs));
+                if (!blob_fds)
+                        return -ENOMEM;
+
+                /* homework needs to be able to tell the difference between blobs being null
+                 * (the fdmap field is completely missing) and it being empty (the field is an
+                 * empty object) */
+                r = json_variant_new_object(&fdmap, NULL, 0);
+                if (r < 0)
+                        return r;
+
+                HASHMAP_FOREACH_KEY(fd_ptr, blob_filename, blobs) {
+                        blob_fds[i] = PTR_TO_FD(fd_ptr);
+
+                        r = json_variant_set_field_integer(&fdmap, blob_filename, i);
+                        if (r < 0)
+                                return r;
+
+                        i++;
+                }
+
+                r = json_variant_set_field(&v, HOMEWORK_BLOB_FDMAP_FIELD, fdmap);
+                if (r < 0)
+                        return r;
+        }
+
         r = json_variant_format(v, 0, &formatted);
         if (r < 0)
                 return r;
@@ -1225,8 +1269,9 @@ static int home_start_work(Home *h, const char *verb, UserRecord *hr, UserRecord
 
         r = safe_fork_full("(sd-homework)",
                            (int[]) { stdin_fd, stdout_fd, STDERR_FILENO },
-                           NULL, 0,
-                           FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG_SIGTERM|FORK_REARRANGE_STDIO|FORK_LOG|FORK_REOPEN_LOG, &pid);
+                           blob_fds, hashmap_size(blobs),
+                           FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_CLOEXEC_OFF|FORK_PACK_FDS|FORK_DEATHSIG_SIGTERM|
+                           FORK_REARRANGE_STDIO|FORK_LOG|FORK_REOPEN_LOG, &pid);
         if (r < 0)
                 return r;
         if (r == 0) {
@@ -1271,6 +1316,10 @@ static int home_start_work(Home *h, const char *verb, UserRecord *hr, UserRecord
                 if (r < 0)
                         log_warning_errno(r, "Failed to update $SYSTEMD_EXEC_PID, ignoring: %m");
 
+                r = setenv_systemd_log_level();
+                if (r < 0)
+                        log_warning_errno(r, "Failed to update $SYSTEMD_LOG_LEVEL, ignoring: %m");
+
                 /* Allow overriding the homework path via an environment variable, to make debugging
                  * easier. */
                 homework = getenv("SYSTEMD_HOMEWORK_PATH") ?: SYSTEMD_HOMEWORK_PATH;
@@ -1343,7 +1392,7 @@ static int home_fixate_internal(
         assert(secret);
         assert(IN_SET(for_state, HOME_FIXATING, HOME_FIXATING_FOR_ACTIVATION, HOME_FIXATING_FOR_ACQUIRE));
 
-        r = home_start_work(h, "inspect", h->record, secret);
+        r = home_start_work(h, "inspect", h->record, secret, NULL, 0);
         if (r < 0)
                 return r;
 
@@ -1392,7 +1441,7 @@ static int home_activate_internal(Home *h, UserRecord *secret, HomeState for_sta
         assert(secret);
         assert(IN_SET(for_state, HOME_ACTIVATING, HOME_ACTIVATING_FOR_ACQUIRE));
 
-        r = home_start_work(h, "activate", h->record, secret);
+        r = home_start_work(h, "activate", h->record, secret, NULL, 0);
         if (r < 0)
                 return r;
 
@@ -1444,7 +1493,7 @@ static int home_authenticate_internal(Home *h, UserRecord *secret, HomeState for
         assert(secret);
         assert(IN_SET(for_state, HOME_AUTHENTICATING, HOME_AUTHENTICATING_WHILE_ACTIVE, HOME_AUTHENTICATING_FOR_ACQUIRE));
 
-        r = home_start_work(h, "inspect", h->record, secret);
+        r = home_start_work(h, "inspect", h->record, secret, NULL, 0);
         if (r < 0)
                 return r;
 
@@ -1489,7 +1538,7 @@ static int home_deactivate_internal(Home *h, bool force, sd_bus_error *error) {
 
         home_unpin(h); /* unpin so that we can deactivate */
 
-        r = home_start_work(h, force ? "deactivate-force" : "deactivate", h->record, NULL);
+        r = home_start_work(h, force ? "deactivate-force" : "deactivate", h->record, NULL, NULL, 0);
         if (r < 0)
                 /* Operation failed before it even started, reacquire pin fd, if state still dictates so */
                 home_update_pin_fd(h, _HOME_STATE_INVALID);
@@ -1526,7 +1575,7 @@ int home_deactivate(Home *h, bool force, sd_bus_error *error) {
         return home_deactivate_internal(h, force, error);
 }
 
-int home_create(Home *h, UserRecord *secret, sd_bus_error *error) {
+int home_create(Home *h, UserRecord *secret, Hashmap *blobs, uint64_t flags, sd_bus_error *error) {
         int r;
 
         assert(h);
@@ -1569,7 +1618,7 @@ int home_create(Home *h, UserRecord *secret, sd_bus_error *error) {
                         return r;
         }
 
-        r = home_start_work(h, "create", h->record, secret);
+        r = home_start_work(h, "create", h->record, secret, blobs, flags);
         if (r < 0)
                 return r;
 
@@ -1599,7 +1648,7 @@ int home_remove(Home *h, sd_bus_error *error) {
                 return sd_bus_error_setf(error, BUS_ERROR_HOME_BUSY, "Home %s is currently being used, or an operation on home %s is currently being executed.", h->user_name, h->user_name);
         }
 
-        r = home_start_work(h, "remove", h->record, NULL);
+        r = home_start_work(h, "remove", h->record, NULL, NULL, 0);
         if (r < 0)
                 return r;
 
@@ -1643,6 +1692,8 @@ static int home_update_internal(
                 const char *verb,
                 UserRecord *hr,
                 UserRecord *secret,
+                Hashmap *blobs,
+                uint64_t flags,
                 sd_bus_error *error) {
 
         _cleanup_(user_record_unrefp) UserRecord *new_hr = NULL, *saved_secret = NULL, *signed_hr = NULL;
@@ -1666,6 +1717,15 @@ static int home_update_internal(
                 secret = saved_secret;
         }
 
+        if (blobs) {
+                const char *failed = NULL;
+                r = user_record_ensure_blob_manifest(hr, blobs, &failed);
+                if (r == -EINVAL)
+                        return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "Provided blob files do not correspond to blob manifest.");
+                if (r < 0)
+                        return sd_bus_error_set_errnof(error, r, "Failed to generate hash for blob %s: %m", strnull(failed));
+        }
+
         r = manager_verify_user_record(h->manager, hr);
         switch (r) {
 
@@ -1708,14 +1768,14 @@ static int home_update_internal(
                         return sd_bus_error_set(error, BUS_ERROR_HOME_RECORD_MISMATCH, "Home record different but timestamp remained the same, refusing.");
         }
 
-        r = home_start_work(h, verb, new_hr, secret);
+        r = home_start_work(h, verb, new_hr, secret, blobs, flags);
         if (r < 0)
                 return r;
 
         return 0;
 }
 
-int home_update(Home *h, UserRecord *hr, sd_bus_error *error) {
+int home_update(Home *h, UserRecord *hr, Hashmap *blobs, uint64_t flags, sd_bus_error *error) {
         HomeState state;
         int r;
 
@@ -1743,7 +1803,7 @@ int home_update(Home *h, UserRecord *hr, sd_bus_error *error) {
         if (r < 0)
                 return r;
 
-        r = home_update_internal(h, "update", hr, NULL, error);
+        r = home_update_internal(h, "update", hr, NULL, blobs, flags, error);
         if (r < 0)
                 return r;
 
@@ -1830,7 +1890,7 @@ int home_resize(Home *h,
                 c = TAKE_PTR(signed_c);
         }
 
-        r = home_update_internal(h, automatic ? "resize-auto" : "resize", c, secret, error);
+        r = home_update_internal(h, automatic ? "resize-auto" : "resize", c, secret, NULL, 0, error);
         if (r < 0)
                 return r;
 
@@ -1946,7 +2006,7 @@ int home_passwd(Home *h,
                         return r;
         }
 
-        r = home_update_internal(h, "passwd", signed_c, merged_secret, error);
+        r = home_update_internal(h, "passwd", signed_c, merged_secret, NULL, 0, error);
         if (r < 0)
                 return r;
 
@@ -2003,7 +2063,7 @@ int home_lock(Home *h, sd_bus_error *error) {
                 return sd_bus_error_setf(error, BUS_ERROR_HOME_BUSY, "An operation on home %s is currently being executed.", h->user_name);
         }
 
-        r = home_start_work(h, "lock", h->record, NULL);
+        r = home_start_work(h, "lock", h->record, NULL, NULL, 0);
         if (r < 0)
                 return r;
 
@@ -2018,7 +2078,7 @@ static int home_unlock_internal(Home *h, UserRecord *secret, HomeState for_state
         assert(secret);
         assert(IN_SET(for_state, HOME_UNLOCKING, HOME_UNLOCKING_FOR_ACQUIRE));
 
-        r = home_start_work(h, "unlock", h->record, secret);
+        r = home_start_work(h, "unlock", h->record, secret, NULL, 0);
         if (r < 0)
                 return r;
 
index 6c069ab5f0269d48c9186a2d97a81af0a2151a61..0f3d1ce3251b4fb49c4a730b52be616cc0f3d118 100644 (file)
@@ -3,6 +3,7 @@
 
 typedef struct Home Home;
 
+#include "hashmap.h"
 #include "homed-manager.h"
 #include "homed-operation.h"
 #include "list.h"
@@ -193,9 +194,9 @@ int home_fixate(Home *h, UserRecord *secret, sd_bus_error *error);
 int home_activate(Home *h, bool if_referenced, UserRecord *secret, sd_bus_error *error);
 int home_authenticate(Home *h, UserRecord *secret, sd_bus_error *error);
 int home_deactivate(Home *h, bool force, sd_bus_error *error);
-int home_create(Home *h, UserRecord *secret, sd_bus_error *error);
+int home_create(Home *h, UserRecord *secret, Hashmap *blobs, uint64_t flags, sd_bus_error *error);
 int home_remove(Home *h, sd_bus_error *error);
-int home_update(Home *h, UserRecord *new_record, sd_bus_error *error);
+int home_update(Home *h, UserRecord *new_record, Hashmap *blobs, uint64_t flags, sd_bus_error *error);
 int home_resize(Home *h, uint64_t disk_size, UserRecord *secret, bool automatic, sd_bus_error *error);
 int home_passwd(Home *h, UserRecord *new_secret, UserRecord *old_secret, sd_bus_error *error);
 int home_unregister(Home *h, sd_bus_error *error);
index c8e232f4257c91846c0e92b8e028f4cd5f72e463..1e63012a06f4accc822ad6b4d9e6d8df221a46b9 100644 (file)
@@ -340,7 +340,7 @@ static int method_deactivate_home(sd_bus_message *message, void *userdata, sd_bu
         return generic_home_method(userdata, message, bus_home_method_deactivate, error);
 }
 
-static int validate_and_allocate_home(Manager *m, UserRecord *hr, Home **ret, sd_bus_error *error) {
+static int validate_and_allocate_home(Manager *m, UserRecord *hr, Hashmap *blobs, Home **ret, sd_bus_error *error) {
         _cleanup_(user_record_unrefp) UserRecord *signed_hr = NULL;
         bool signed_locally;
         Home *other;
@@ -370,6 +370,15 @@ static int validate_and_allocate_home(Manager *m, UserRecord *hr, Home **ret, sd
         if (r != -ESRCH)
                 return r;
 
+        if (blobs) {
+                const char *failed = NULL;
+                r = user_record_ensure_blob_manifest(hr, blobs, &failed);
+                if (r == -EINVAL)
+                        return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "Provided blob files do not correspond to blob manifest.");
+                if (r < 0)
+                        return sd_bus_error_set_errnof(error, r, "Failed to generate hash for blob %s: %m", strnull(failed));
+        }
+
         r = manager_verify_user_record(m, hr);
         switch (r) {
 
@@ -458,7 +467,7 @@ static int method_register_home(
         if (r == 0)
                 return 1; /* Will call us back */
 
-        r = validate_and_allocate_home(m, hr, &h, error);
+        r = validate_and_allocate_home(m, hr, NULL, &h, error);
         if (r < 0)
                 return r;
 
@@ -475,12 +484,11 @@ static int method_unregister_home(sd_bus_message *message, void *userdata, sd_bu
         return generic_home_method(userdata, message, bus_home_method_unregister, error);
 }
 
-static int method_create_home(
-                sd_bus_message *message,
-                void *userdata,
-                sd_bus_error *error) {
 
+static int method_create_home(sd_bus_message *message, void *userdata, sd_bus_error *error) {
         _cleanup_(user_record_unrefp) UserRecord *hr = NULL;
+        _cleanup_hashmap_free_ Hashmap *blobs = NULL;
+        uint64_t flags = 0;
         Manager *m = ASSERT_PTR(userdata);
         Home *h;
         int r;
@@ -491,6 +499,18 @@ static int method_create_home(
         if (r < 0)
                 return r;
 
+        if (endswith(sd_bus_message_get_member(message), "Ex")) {
+                r = bus_message_read_blobs(message, &blobs, error);
+                if (r < 0)
+                        return r;
+
+                r = sd_bus_message_read(message, "t", &flags);
+                if (r < 0)
+                        return r;
+                if (flags != 0)
+                        return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Provided flags are unsupported.");
+        }
+
         r = bus_verify_polkit_async(
                         message,
                         "org.freedesktop.home1.create-home",
@@ -502,11 +522,11 @@ static int method_create_home(
         if (r == 0)
                 return 1; /* Will call us back */
 
-        r = validate_and_allocate_home(m, hr, &h, error);
+        r = validate_and_allocate_home(m, hr, blobs, &h, error);
         if (r < 0)
                 return r;
 
-        r = home_create(h, hr, error);
+        r = home_create(h, hr, blobs, flags, error);
         if (r < 0)
                 goto fail;
 
@@ -544,6 +564,8 @@ static int method_authenticate_home(sd_bus_message *message, void *userdata, sd_
 
 static int method_update_home(sd_bus_message *message, void *userdata, sd_bus_error *error) {
         _cleanup_(user_record_unrefp) UserRecord *hr = NULL;
+        _cleanup_hashmap_free_ Hashmap *blobs = NULL;
+        uint64_t flags = 0;
         Manager *m = ASSERT_PTR(userdata);
         Home *h;
         int r;
@@ -554,13 +576,23 @@ static int method_update_home(sd_bus_message *message, void *userdata, sd_bus_er
         if (r < 0)
                 return r;
 
+        if (endswith(sd_bus_message_get_member(message), "Ex")) {
+                r = bus_message_read_blobs(message, &blobs, error);
+                if (r < 0)
+                        return r;
+
+                r = sd_bus_message_read(message, "t", &flags);
+                if (r < 0)
+                        return r;
+        }
+
         assert(hr->user_name);
 
         h = hashmap_get(m->homes_by_name, hr->user_name);
         if (!h)
                 return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_HOME, "No home for user %s known", hr->user_name);
 
-        return bus_home_method_update_record(h, message, hr, error);
+        return bus_home_method_update_record(h, message, hr, blobs, flags, error);
 }
 
 static int method_resize_home(sd_bus_message *message, void *userdata, sd_bus_error *error) {
@@ -769,6 +801,11 @@ static const sd_bus_vtable manager_vtable[] = {
                                 SD_BUS_NO_RESULT,
                                 method_create_home,
                                 SD_BUS_VTABLE_UNPRIVILEGED|SD_BUS_VTABLE_SENSITIVE),
+        SD_BUS_METHOD_WITH_ARGS("CreateHomeEx",
+                                SD_BUS_ARGS("s", user_record, "a{sh}", blobs, "t", flags),
+                                SD_BUS_NO_RESULT,
+                                method_create_home,
+                                SD_BUS_VTABLE_UNPRIVILEGED|SD_BUS_VTABLE_SENSITIVE),
 
         /* Create $HOME for already registered JSON entry */
         SD_BUS_METHOD_WITH_ARGS("RealizeHome",
@@ -804,6 +841,11 @@ static const sd_bus_vtable manager_vtable[] = {
                                 SD_BUS_NO_RESULT,
                                 method_update_home,
                                 SD_BUS_VTABLE_UNPRIVILEGED|SD_BUS_VTABLE_SENSITIVE),
+        SD_BUS_METHOD_WITH_ARGS("UpdateHomeEx",
+                                SD_BUS_ARGS("s", user_record, "a{sh}", blobs, "t", flags),
+                                SD_BUS_NO_RESULT,
+                                method_update_home,
+                                SD_BUS_VTABLE_UNPRIVILEGED|SD_BUS_VTABLE_SENSITIVE),
 
         SD_BUS_METHOD_WITH_ARGS("ResizeHome",
                                 SD_BUS_ARGS("s", user_name, "t", size, "s", secret),
index b95a0dc7091c0e9a6a239352adea81c6e2d9d201..17cb7d6ce3c45b7297e047dad450b20a12adf67b 100644 (file)
@@ -241,3 +241,61 @@ int home_reconcile_blob_dirs(UserRecord *h, int root_fd, int reconciled) {
         }
         return 0;
 }
+
+int home_apply_new_blob_dir(UserRecord *h, Hashmap *blobs) {
+        _cleanup_free_ char *fn = NULL;
+        _cleanup_close_ int base_dfd = -EBADF, dfd = -EBADF;
+        uint64_t total_size = 0;
+        const char *filename;
+        const void *v;
+        int r;
+
+        assert(h);
+
+        if (!blobs) /* Shortcut: If no blobs are passed from dbus, we have nothing to do. */
+                return 0;
+
+        base_dfd = open(home_system_blob_dir(), O_RDONLY|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW);
+        if (base_dfd < 0)
+                return log_error_errno(errno, "Failed to open system blob base dir: %m");
+
+        if (hashmap_isempty(blobs)) {
+                /* Shortcut: If blobs was passed but empty, we can simply delete the contents
+                 * of the directory. */
+                r = rm_rf_at(base_dfd, h->user_name, REMOVE_PHYSICAL|REMOVE_MISSING_OK);
+                if (r < 0)
+                        return log_error_errno(errno, "Failed to empty out system blob dir: %m");
+                return 0;
+        }
+
+        r = tempfn_random(h->user_name, NULL, &fn);
+        if (r < 0)
+                return r;
+
+        dfd = open_mkdir_at(base_dfd, fn, O_EXCL|O_CLOEXEC, 0755);
+        if (dfd < 0)
+                return log_error_errno(errno, "Failed to create system blob dir: %m");
+
+        HASHMAP_FOREACH_KEY(v, filename, blobs) {
+                r = copy_one_blob(PTR_TO_FD(v), dfd, filename, &total_size, 0, h->blob_manifest);
+                if (r == -EFBIG)
+                        break;
+                if (r < 0) {
+                        log_error_errno(r, "Failed to copy %s into system blob dir: %m", filename);
+                        goto fail;
+                }
+        }
+
+        r = install_file(base_dfd, fn, base_dfd, h->user_name, INSTALL_REPLACE);
+        if (r < 0) {
+                log_error_errno(r, "Failed to move system blob dir into place: %m");
+                goto fail;
+        }
+
+        log_info("Replaced system blob directory.");
+        return 0;
+
+fail:
+        (void) rm_rf_at(base_dfd, fn, REMOVE_ROOT|REMOVE_PHYSICAL|REMOVE_MISSING_OK);
+        return r;
+}
index 7bf4f514b9fc3f21721d2afc38fa2e7b44917b1c..fbe6c82cd4f9a89d341e9adddf70b328f3cb2b6c 100644 (file)
@@ -5,3 +5,5 @@
 #include "user-record.h"
 
 int home_reconcile_blob_dirs(UserRecord *h, int root_fd, int reconciled);
+
+int home_apply_new_blob_dir(UserRecord *h, Hashmap *blobs);
index 4575471041ffc676109b05a8d0c02b8facd0caed..39e00514865c40b45f5d6913286e15156143e822 100644 (file)
@@ -25,6 +25,7 @@
 #include "memory-util.h"
 #include "missing_magic.h"
 #include "mount-util.h"
+#include "parse-util.h"
 #include "path-util.h"
 #include "recovery-key.h"
 #include "rm-rf.h"
@@ -1314,7 +1315,7 @@ static int determine_default_storage(UserStorage *ret) {
         return 0;
 }
 
-static int home_create(UserRecord *h, UserRecord **ret_home) {
+static int home_create(UserRecord *h, Hashmap *blobs, UserRecord **ret_home) {
         _cleanup_strv_free_erase_ char **effective_passwords = NULL;
         _cleanup_(home_setup_done) HomeSetup setup = HOME_SETUP_INIT;
         _cleanup_(user_record_unrefp) UserRecord *new_home = NULL;
@@ -1376,6 +1377,10 @@ static int home_create(UserRecord *h, UserRecord **ret_home) {
         if (!IN_SET(r, USER_TEST_ABSENT, USER_TEST_UNDEFINED, USER_TEST_MAYBE))
                 return log_error_errno(SYNTHETIC_ERRNO(EEXIST), "Image path %s already exists, refusing.", user_record_image_path(h));
 
+        r = home_apply_new_blob_dir(h, blobs);
+        if (r < 0)
+                return r;
+
         switch (user_record_storage(h)) {
 
         case USER_LUKS:
@@ -1581,7 +1586,7 @@ static int home_validate_update(UserRecord *h, HomeSetup *setup, HomeSetupFlags
         return has_mount; /* return true if the home record is already active */
 }
 
-static int home_update(UserRecord *h, UserRecord **ret) {
+static int home_update(UserRecord *h, Hashmap *blobs, UserRecord **ret) {
         _cleanup_(user_record_unrefp) UserRecord *new_home = NULL, *header_home = NULL, *embedded_home = NULL;
         _cleanup_(home_setup_done) HomeSetup setup = HOME_SETUP_INIT;
         _cleanup_(password_cache_free) PasswordCache cache = {};
@@ -1600,6 +1605,10 @@ static int home_update(UserRecord *h, UserRecord **ret) {
         if (r < 0)
                 return r;
 
+        r = home_apply_new_blob_dir(h, blobs);
+        if (r < 0)
+                return r;
+
         r = home_setup(h, flags, &setup, &cache, &header_home);
         if (r < 0)
                 return r;
@@ -1867,10 +1876,12 @@ static int run(int argc, char *argv[]) {
         _cleanup_(user_record_unrefp) UserRecord *home = NULL, *new_home = NULL;
         _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
         _cleanup_fclose_ FILE *opened_file = NULL;
+        _cleanup_hashmap_free_ Hashmap *blobs = NULL;
         unsigned line = 0, column = 0;
-        const char *json_path = NULL;
+        const char *json_path = NULL, *blob_filename;
         FILE *json_file;
         usec_t start;
+        JsonVariant *fdmap, *blob_fd_variant;
         int r;
 
         start = now(CLOCK_MONOTONIC);
@@ -1901,6 +1912,48 @@ static int run(int argc, char *argv[]) {
         if (r < 0)
                 return log_error_errno(r, "[%s:%u:%u] Failed to parse JSON data: %m", json_path, line, column);
 
+        fdmap = json_variant_by_key(v, HOMEWORK_BLOB_FDMAP_FIELD);
+        if (fdmap) {
+                r = hashmap_ensure_allocated(&blobs, &blob_fd_hash_ops);
+                if (r < 0)
+                        return log_oom();
+
+                JSON_VARIANT_OBJECT_FOREACH(blob_filename, blob_fd_variant, fdmap) {
+                        _cleanup_free_ char *filename = NULL;
+                        _cleanup_close_ int fd = -EBADF;
+
+                        assert(json_variant_is_integer(blob_fd_variant));
+                        assert(json_variant_integer(blob_fd_variant) >= 0);
+                        assert(json_variant_integer(blob_fd_variant) <= INT_MAX - SD_LISTEN_FDS_START);
+                        fd = SD_LISTEN_FDS_START + (int) json_variant_integer(blob_fd_variant);
+
+                        if (DEBUG_LOGGING) {
+                                _cleanup_free_ char *resolved = NULL;
+                                r = fd_get_path(fd, &resolved);
+                                log_debug("Got blob from daemon: %s (%d) → %s",
+                                          blob_filename, fd, resolved ?: STRERROR(r));
+                        }
+
+                        filename = strdup(blob_filename);
+                        if (!filename)
+                                return log_oom();
+
+                        r = fd_cloexec(fd, true);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to enable O_CLOEXEC on blob %s: %m", filename);
+
+                        r = hashmap_put(blobs, filename, FD_TO_PTR(fd));
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to insert blob %s into map: %m", filename);
+                        TAKE_PTR(filename); /* Ownership transfers to hashmap */
+                        TAKE_FD(fd);
+                }
+
+                r = json_variant_filter(&v, STRV_MAKE(HOMEWORK_BLOB_FDMAP_FIELD));
+                if (r < 0)
+                        return log_error_errno(r, "Failed to strip internal fdmap from JSON: %m");
+        }
+
         home = user_record_new();
         if (!home)
                 return log_oom();
@@ -1944,11 +1997,11 @@ static int run(int argc, char *argv[]) {
         else if (streq(argv[1], "deactivate-force"))
                 r = home_deactivate(home, true);
         else if (streq(argv[1], "create"))
-                r = home_create(home, &new_home);
+                r = home_create(home, blobs, &new_home);
         else if (streq(argv[1], "remove"))
                 r = home_remove(home);
         else if (streq(argv[1], "update"))
-                r = home_update(home, &new_home);
+                r = home_update(home, blobs, &new_home);
         else if (streq(argv[1], "resize")) /* Resize on user request */
                 r = home_resize(home, false, &new_home);
         else if (streq(argv[1], "resize-auto")) /* Automatic resize */
index d2c4b9dd290d12af9311eecb5c9e685df0c8807a..9ac34aea55f67227af4fd5b2008feedf1b1667dc 100644 (file)
                        send_interface="org.freedesktop.home1.Manager"
                        send_member="CreateHome"/>
 
+                <allow send_destination="org.freedesktop.home1"
+                       send_interface="org.freedesktop.home1.Manager"
+                       send_member="CreateHomeEx"/>
+
                 <allow send_destination="org.freedesktop.home1"
                        send_interface="org.freedesktop.home1.Manager"
                        send_member="RealizeHome"/>
                        send_interface="org.freedesktop.home1.Manager"
                        send_member="UpdateHome"/>
 
+                <allow send_destination="org.freedesktop.home1"
+                       send_interface="org.freedesktop.home1.Manager"
+                       send_member="UpdateHomeEx"/>
+
                 <allow send_destination="org.freedesktop.home1"
                        send_interface="org.freedesktop.home1.Manager"
                        send_member="ResizeHome"/>
                        send_interface="org.freedesktop.home1.Home"
                        send_member="Update"/>
 
+                <allow send_destination="org.freedesktop.home1"
+                       send_interface="org.freedesktop.home1.Home"
+                       send_member="UpdateEx"/>
+
                 <allow send_destination="org.freedesktop.home1"
                        send_interface="org.freedesktop.home1.Home"
                        send_member="Resize"/>
index b18a77d7407e98b1b44acc1224d06ef67c0eb644..3ae0883cb0688f0e1a0e97c0f90abb9fb8000cf9 100644 (file)
@@ -3,6 +3,7 @@
 #include <sys/xattr.h>
 
 #include "errno-util.h"
+#include "fd-util.h"
 #include "home-util.h"
 #include "id128-util.h"
 #include "libcrypt-util.h"
@@ -10,6 +11,7 @@
 #include "recovery-key.h"
 #include "mountpoint-util.h"
 #include "path-util.h"
+#include "sha256.h"
 #include "stat-util.h"
 #include "user-record-util.h"
 #include "user-util.h"
@@ -1396,6 +1398,9 @@ int user_record_is_supported(UserRecord *hr, sd_bus_error *error) {
                 return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "Cannot manage custom blob directories.");
         }
 
+        if (json_variant_by_key(hr->json, HOMEWORK_BLOB_FDMAP_FIELD))
+                return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "User record contains unsafe internal fields.");
+
         return 0;
 }
 
@@ -1524,3 +1529,103 @@ int user_record_set_rebalance_weight(UserRecord *h, uint64_t weight) {
         h->mask |= USER_RECORD_PER_MACHINE;
         return 0;
 }
+
+int user_record_ensure_blob_manifest(UserRecord *h, Hashmap *blobs, const char **ret_failed) {
+        _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
+        _cleanup_hashmap_free_ Hashmap *manifest = NULL;
+        const char *filename;
+        void *key, *value;
+        uint64_t total_size = 0;
+        int r;
+
+        assert(h);
+        assert(h->json);
+        assert(blobs);
+        assert(ret_failed);
+
+        /* Ensures that blobManifest exists (possibly creating it using the
+         * contents of blobs), and that the set of keys in both hashmaps are
+         * exactly the same. If it fails to handle one blob file, the filename
+         * is put it ret_failed for nicer error reporting. ret_failed is a pointer
+         * to the same memory blobs uses to store its keys, so it is valid for
+         * as long as blobs is valid and the corresponding key isn't removed! */
+
+        if (h->blob_manifest) {
+                /* blobManifest already exists. In this case we verify
+                 * that the sets of keys are equal and that's it */
+
+                HASHMAP_FOREACH_KEY(value, key, h->blob_manifest)
+                        if (!hashmap_contains(blobs, key))
+                                return -EINVAL;
+                HASHMAP_FOREACH_KEY(value, key, blobs)
+                        if (!hashmap_contains(h->blob_manifest, key))
+                                return -EINVAL;
+
+                return 0;
+        }
+
+        /* blobManifest doesn't exist, so we need to create it */
+
+        HASHMAP_FOREACH_KEY(value, filename, blobs) {
+                _cleanup_free_ char *filename_dup = NULL;
+                _cleanup_free_ uint8_t *hash = NULL;
+                _cleanup_(json_variant_unrefp) JsonVariant *hash_json = NULL;
+                int fd = PTR_TO_FD(value);
+                off_t initial, size;
+
+                *ret_failed = filename;
+
+                filename_dup = strdup(filename);
+                if (!filename_dup)
+                        return -ENOMEM;
+
+                hash = malloc(SHA256_DIGEST_SIZE);
+                if (!hash)
+                        return -ENOMEM;
+
+                initial = lseek(fd, 0, SEEK_CUR);
+                if (initial < 0)
+                        return -errno;
+
+                r = sha256_fd(fd, BLOB_DIR_MAX_SIZE, hash);
+                if (r < 0)
+                        return r;
+
+                size = lseek(fd, 0, SEEK_CUR);
+                if (size < 0)
+                        return -errno;
+                if (!DEC_SAFE(&size, initial))
+                        return -EOVERFLOW;
+
+                if (!INC_SAFE(&total_size, size))
+                        total_size = UINT64_MAX;
+                if (total_size > BLOB_DIR_MAX_SIZE)
+                        return -EFBIG;
+
+                if (lseek(fd, initial, SEEK_SET) < 0)
+                        return -errno;
+
+                r = json_variant_new_hex(&hash_json, hash, SHA256_DIGEST_SIZE);
+                if (r < 0)
+                        return r;
+
+                r = hashmap_ensure_put(&manifest, &path_hash_ops_free_free, filename_dup, hash);
+                if (r < 0)
+                        return r;
+                TAKE_PTR(filename_dup); /* Ownership transfers to hashmap */
+                TAKE_PTR(hash);
+
+                r = json_variant_set_field(&v, filename, hash_json);
+                if (r < 0)
+                        return r;
+
+                *ret_failed = NULL;
+        }
+
+        r = json_variant_set_field_non_null(&h->json, "blobManifest", v);
+        if (r < 0)
+                return r;
+
+        h->blob_manifest = TAKE_PTR(manifest);
+        return 0;
+}
index 508e2bdb8d9dbfd9eeb23f001203b70d7bfd8988..1295a8e09a94eb505682967da98fa4bba77a93e4 100644 (file)
@@ -6,6 +6,11 @@
 #include "user-record.h"
 #include "group-record.h"
 
+/* We intentionally use snake_case instead of the usual camelCase here to further
+ * reduce the chance of collision with a field any legitimate user record may ever
+ * want to set. */
+#define HOMEWORK_BLOB_FDMAP_FIELD "__systemd_homework_internal_blob_fdmap"
+
 int user_record_synthesize(UserRecord *h, const char *user_name, const char *realm, const char *image_path, UserStorage storage, uid_t uid, gid_t gid);
 int group_record_synthesize(GroupRecord *g, UserRecord *u);
 
@@ -63,3 +68,5 @@ int user_record_is_supported(UserRecord *hr, sd_bus_error *error);
 
 bool user_record_shall_rebalance(UserRecord *h);
 int user_record_set_rebalance_weight(UserRecord *h, uint64_t weight);
+
+int user_record_ensure_blob_manifest(UserRecord *h, Hashmap *blobs, const char **ret_failed);