]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
homework: Implement offline updates
authorAdrian Vovk <adrianvovk@gmail.com>
Thu, 1 Feb 2024 18:35:03 +0000 (13:35 -0500)
committerLuca Boccassi <bluca@debian.org>
Sat, 23 Mar 2024 01:05:13 +0000 (01:05 +0000)
This makes it possible to update a home record (and blob directory) of a
home area that's either completely absent (i.e. on a USB stick that's
unplugged) or just inaccessible due to lack of authentication

man/homectl.xml
man/org.freedesktop.home1.xml
src/home/home-util.h
src/home/homectl.c
src/home/homed-home-bus.c
src/home/homed-home.c
src/home/homed-manager-bus.c
src/home/homed-operation.h
src/home/homework.c
test/units/testsuite-46.sh

index 1a0535cd4a66089dc9d5aa4bf1394010930e2ee5..f1bade205343244717b0a750679a29fa814bef1c 100644 (file)
         <xi:include href="version-info.xml" xpointer="v245"/></listitem>
       </varlistentry>
 
+      <varlistentry>
+        <term><option>--offline</option></term>
+
+        <listitem><para>Do not attempt to update the copy of the user record and blob directory that is embedded inside
+        of the home area. This allows for operation on home areas that are absent, or without needing to authenticate as
+        the user being modified.</para>
+
+        <xi:include href="version-info.xml" xpointer="v256"/></listitem>
+      </varlistentry>
+
       <xi:include href="user-system-options.xml" xpointer="host" />
       <xi:include href="user-system-options.xml" xpointer="machine" />
 
index 726d9d9832519de0298d58de985dab88d2caedf4..9334f1a596ec82cabcc2c43e279b8da18a0d1ffb 100644 (file)
@@ -334,8 +334,16 @@ node /org/freedesktop/home1 {
       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>
+      can be used to further customize the behavior of this method via flags defined as follows:</para>
+      <programlisting>
+#define SD_HOMED_UPDATE_OFFLINE (UINT64_C(1) &lt;&lt; 0)
+      </programlisting>
+      <para>When <constant>SD_HOMED_UPDATE_OFFLINE</constant> (0x01) is set, no attempt is made to update the copies
+      of the user record and blob directory that are embedded into the home directory. Changes will be stored, however,
+      and may be propagated into the home directory the next time it is reconciled (most likely when the user next logs in).
+      Note that any changes made with this flag set may be lost if the home area has a newer record, which can happen
+      if the home area is updated on another machine after this method call. 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 optionally a user record consisting only of the <literal>secret</literal>
index f2e5787a546f578ff51460b97f5b2e70a014213f..42131b9f41ddeee4c2405fd4ccc9f091f5967c94 100644 (file)
@@ -9,6 +9,13 @@
 #include "time-util.h"
 #include "user-record.h"
 
+/* Flags supported by UpdateEx() */
+#define SD_HOMED_UPDATE_OFFLINE (UINT64_C(1) << 0)
+#define SD_HOMED_UPDATE_FLAGS_ALL (SD_HOMED_UPDATE_OFFLINE)
+
+/* Flags supported by CreateHomeEx() */
+#define SD_HOMED_CREATE_FLAGS_ALL (0)
+
 /* Put some limits on disk sizes: not less than 5M, not more than 5T */
 #define USER_DISK_SIZE_MIN (UINT64_C(5)*1024*1024)
 #define USER_DISK_SIZE_MAX (UINT64_C(5)*1024*1024*1024*1024)
index 11a138070b1fd09b2dc28bb4a9cdb88e36bd3b9d..2a7917d7f9f846e53ee90a640d345f14a24f54b4 100644 (file)
@@ -61,6 +61,7 @@ static bool arg_legend = true;
 static bool arg_ask_password = true;
 static BusTransport arg_transport = BUS_TRANSPORT_LOCAL;
 static const char *arg_host = NULL;
+static bool arg_offline = false;
 static const char *arg_identity = NULL;
 static JsonVariant *arg_identity_extra = NULL;
 static JsonVariant *arg_identity_extra_privileged = NULL;
@@ -1712,6 +1713,7 @@ static int update_home(int argc, char *argv[], void *userdata) {
         _cleanup_free_ char *buffer = NULL;
         _cleanup_hashmap_free_ Hashmap *blobs = NULL;
         const char *username;
+        uint64_t flags = 0;
         int r;
 
         if (argc >= 2)
@@ -1754,6 +1756,9 @@ static int update_home(int argc, char *argv[], void *userdata) {
         if (arg_and_resize || arg_and_change_password)
                 log_info("Updating home directory.");
 
+        if (arg_offline)
+                flags |= SD_HOMED_UPDATE_OFFLINE;
+
         for (;;) {
                 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
                 _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
@@ -1777,7 +1782,7 @@ static int update_home(int argc, char *argv[], void *userdata) {
                 if (r < 0)
                         return bus_log_create_error(r);
 
-                r = sd_bus_message_append(m, "t", UINT64_C(0));
+                r = sd_bus_message_append(m, "t", flags);
                 if (r < 0)
                         return bus_log_create_error(r);
 
@@ -2564,6 +2569,7 @@ static int help(int argc, char *argv[], void *userdata) {
                "     --no-pager                Do not pipe output into a pager\n"
                "     --no-legend               Do not show the headers and footers\n"
                "     --no-ask-password         Do not ask for system passwords\n"
+               "     --offline                 Don't update record embedded in home directory\n"
                "  -H --host=[USER@]HOST        Operate on remote host\n"
                "  -M --machine=CONTAINER       Operate on local container\n"
                "     --identity=PATH           Read JSON identity from file\n"
@@ -2723,6 +2729,7 @@ static int parse_argv(int argc, char *argv[]) {
                 ARG_NO_PAGER,
                 ARG_NO_LEGEND,
                 ARG_NO_ASK_PASSWORD,
+                ARG_OFFLINE,
                 ARG_REALM,
                 ARG_EMAIL_ADDRESS,
                 ARG_DISK_SIZE,
@@ -2808,6 +2815,7 @@ static int parse_argv(int argc, char *argv[]) {
                 { "no-pager",                    no_argument,       NULL, ARG_NO_PAGER                    },
                 { "no-legend",                   no_argument,       NULL, ARG_NO_LEGEND                   },
                 { "no-ask-password",             no_argument,       NULL, ARG_NO_ASK_PASSWORD             },
+                { "offline",                     no_argument,       NULL, ARG_OFFLINE                     },
                 { "host",                        required_argument, NULL, 'H'                             },
                 { "machine",                     required_argument, NULL, 'M'                             },
                 { "identity",                    required_argument, NULL, 'I'                             },
@@ -2933,6 +2941,10 @@ static int parse_argv(int argc, char *argv[]) {
                         arg_ask_password = false;
                         break;
 
+                case ARG_OFFLINE:
+                        arg_offline = true;
+                        break;
+
                 case 'H':
                         arg_transport = BUS_TRANSPORT_REMOTE;
                         arg_host = optarg;
index dd3603efa7f50deff49540aae520b141a1dae91b..23578fe314be5cbbd961e634460c5c971fa36ddc 100644 (file)
@@ -6,6 +6,7 @@
 #include "bus-polkit.h"
 #include "fd-util.h"
 #include "format-util.h"
+#include "home-util.h"
 #include "homed-bus.h"
 #include "homed-home-bus.h"
 #include "homed-home.h"
@@ -432,8 +433,8 @@ int bus_home_update_record(
         if (r < 0)
                 return r;
 
-        if (flags != 0)
-                return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Provided flags are unsupported.");
+        if ((flags & ~SD_HOMED_UPDATE_FLAGS_ALL) != 0)
+                return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid flags provided.");
 
         r = home_verify_polkit_async(
                         h,
@@ -457,6 +458,8 @@ int bus_home_update_record(
         if (r < 0)
                 return r;
 
+        h->current_operation->call_flags = flags;
+
         return 1;
 }
 
index a2892d00a375301ce2ba7b304f90b48adbeb3b7c..447e8c597cadd12ca9ce33b75157d1355432fce3 100644 (file)
@@ -955,10 +955,13 @@ static void home_create_finish(Home *h, int ret, UserRecord *hr) {
 
 static void home_change_finish(Home *h, int ret, UserRecord *hr) {
         _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+        uint64_t flags;
         int r;
 
         assert(h);
 
+        flags = h->current_operation ? h->current_operation->call_flags : 0;
+
         if (ret < 0) {
                 (void) home_count_bad_authentication(h, ret, /* save= */ true);
 
@@ -969,17 +972,22 @@ static void home_change_finish(Home *h, int ret, UserRecord *hr) {
         }
 
         if (hr) {
-                r = home_set_record(h, hr);
-                if (r < 0)
-                        log_warning_errno(r, "Failed to update home record, ignoring: %m");
-                else {
+                if (!FLAGS_SET(flags, SD_HOMED_UPDATE_OFFLINE)) {
                         r = user_record_good_authentication(h->record);
                         if (r < 0)
                                 log_warning_errno(r, "Failed to increase good authentication counter, ignoring: %m");
+                }
 
+                r = home_set_record(h, hr);
+                if (r >= 0)
                         r = home_save_record(h);
-                        if (r < 0)
-                                log_warning_errno(r, "Failed to write home record to disk, ignoring: %m");
+                if (r < 0) {
+                        if (FLAGS_SET(flags, SD_HOMED_UPDATE_OFFLINE)) {
+                                log_error_errno(r, "Failed to update home record and write it to disk: %m");
+                                sd_bus_error_set(&error, SD_BUS_ERROR_FAILED, "Failed to cache changes to home record");
+                                goto finish;
+                        } else
+                                log_warning_errno(r, "Failed to update home record, ignoring: %m");
                 }
         }
 
@@ -1312,6 +1320,11 @@ static int home_start_work(
                                 _exit(EXIT_FAILURE);
                         }
 
+                if (setenv("SYSTEMD_HOMEWORK_UPDATE_OFFLINE", one_zero(FLAGS_SET(flags, SD_HOMED_UPDATE_OFFLINE)), 1) < 0) {
+                        log_error_errno(errno, "Failed to set $SYSTEMD_HOMEWORK_UPDATE_OFFLINE: %m");
+                        _exit(EXIT_FAILURE);
+                }
+
                 r = setenv_systemd_exec_pid(true);
                 if (r < 0)
                         log_warning_errno(r, "Failed to update $SYSTEMD_EXEC_PID, ignoring: %m");
@@ -1783,7 +1796,9 @@ int home_update(Home *h, UserRecord *hr, Hashmap *blobs, uint64_t flags, sd_bus_
         case HOME_UNFIXATED:
                 return sd_bus_error_setf(error, BUS_ERROR_HOME_UNFIXATED, "Home %s has not been fixated yet.", h->user_name);
         case HOME_ABSENT:
-                return sd_bus_error_setf(error, BUS_ERROR_HOME_ABSENT, "Home %s is currently missing or not plugged in.", h->user_name);
+                if (!FLAGS_SET(flags, SD_HOMED_UPDATE_OFFLINE))
+                        return sd_bus_error_setf(error, BUS_ERROR_HOME_ABSENT, "Home %s is currently missing or not plugged in.", h->user_name);
+                break; /* offline updates are compatible w/ an absent home area */
         case HOME_LOCKED:
                 return sd_bus_error_setf(error, BUS_ERROR_HOME_LOCKED, "Home %s is currently locked.", h->user_name);
         case HOME_INACTIVE:
index 403a7d0213b40d18353482dac95d0a4cb81df0ca..58cd0371057575d6559f25c3884650a1fbd77d87 100644 (file)
@@ -6,6 +6,7 @@
 #include "bus-common-errors.h"
 #include "bus-polkit.h"
 #include "format-util.h"
+#include "home-util.h"
 #include "homed-bus.h"
 #include "homed-home-bus.h"
 #include "homed-manager-bus.h"
@@ -507,8 +508,8 @@ static int method_create_home(sd_bus_message *message, void *userdata, sd_bus_er
                 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.");
+                if ((flags & ~SD_HOMED_CREATE_FLAGS_ALL) != 0)
+                        return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid flags provided.");
         }
 
         r = bus_verify_polkit_async(
@@ -538,6 +539,8 @@ static int method_create_home(sd_bus_message *message, void *userdata, sd_bus_er
         if (r < 0)
                 return r;
 
+        h->current_operation->call_flags = flags;
+
         return 1;
 
 fail:
index 004246a4e642a9711c5f13e1d40420409937d3ac..af165bb4a5213fe231a35341328e3b231d795baf 100644 (file)
@@ -39,6 +39,7 @@ typedef struct Operation {
         sd_bus_message *message;
 
         UserRecord *secret;
+        uint64_t call_flags; /* flags passed into UpdateEx() or CreateHomeEx() */
         int send_fd;   /* pipe fd for AcquireHome() which is taken already when we start the operation */
 
         int result;    /* < 0 if not completed yet, == 0 on failure, > 0 on success */
index d97e8fd9f0993060886cafb77dc0a3ac1a793560..afc114229869a89fee06ae26ab1a1a709f9bf6a0 100644 (file)
@@ -1551,20 +1551,32 @@ static int home_remove(UserRecord *h) {
         return 0;
 }
 
-static int home_validate_update(UserRecord *h, HomeSetup *setup, HomeSetupFlags *flags) {
-        bool has_mount = false;
-        int r;
-
+static int home_basic_validate_update(UserRecord *h) {
         assert(h);
-        assert(setup);
 
         if (!h->user_name)
                 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "User record lacks user name, refusing.");
+
         if (!uid_is_valid(h->uid))
                 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "User record lacks UID, refusing.");
+
         if (!IN_SET(user_record_storage(h), USER_LUKS, USER_DIRECTORY, USER_SUBVOLUME, USER_FSCRYPT, USER_CIFS))
                 return log_error_errno(SYNTHETIC_ERRNO(ENOTTY), "Processing home directories of type '%s' currently not supported.", user_storage_to_string(user_record_storage(h)));
 
+        return 0;
+}
+
+static int home_validate_update(UserRecord *h, HomeSetup *setup, HomeSetupFlags *flags) {
+        bool has_mount = false;
+        int r;
+
+        assert(h);
+        assert(setup);
+
+        r = home_basic_validate_update(h);
+        if (r < 0)
+                return r;
+
         r = user_record_test_home_directory_and_warn(h);
         if (r < 0)
                 return r;
@@ -1610,19 +1622,31 @@ static int home_update(UserRecord *h, Hashmap *blobs, UserRecord **ret) {
         _cleanup_(home_setup_done) HomeSetup setup = HOME_SETUP_INIT;
         _cleanup_(password_cache_free) PasswordCache cache = {};
         HomeSetupFlags flags = 0;
+        bool offline;
         int r;
 
         assert(h);
         assert(ret);
 
-        password_cache_load_keyring(h, &cache);
+        offline = getenv_bool("SYSTEMD_HOMEWORK_UPDATE_OFFLINE") > 0;
 
-        r = user_record_authenticate(h, h, &cache, /* strict_verify= */ true);
-        if (r < 0)
-                return r;
-        assert(r > 0); /* Insist that a password was verified */
+        if (!offline) {
+                password_cache_load_keyring(h, &cache);
 
-        r = home_validate_update(h, &setup, &flags);
+                r = user_record_authenticate(h, h, &cache, /* strict_verify= */ true);
+                if (r < 0)
+                        return r;
+                assert(r > 0); /* Insist that a password was verified */
+
+                r = home_validate_update(h, &setup, &flags);
+        } else {
+                /* In offline mode we skip all authentication, since we're
+                 * not propagating anything into the home area. The new home
+                 * records's authentication will still be checked when the user
+                 * next logs in, so this is fine */
+
+                r = home_basic_validate_update(h);
+        }
         if (r < 0)
                 return r;
 
@@ -1630,6 +1654,11 @@ static int home_update(UserRecord *h, Hashmap *blobs, UserRecord **ret) {
         if (r < 0)
                 return r;
 
+        if (offline) {
+                log_info("Offline update requested. Not touching embedded records.");
+                return user_record_clone(h, USER_RECORD_LOAD_MASK_SECRET|USER_RECORD_PERMISSIVE, ret);
+        }
+
         r = home_setup(h, flags, &setup, &cache, &header_home);
         if (r < 0)
                 return r;
index 5aef06bf228de2acb403a91cca2914ef32444bdc..f82751064057a0f1011e51e6f1a8d6c89af169d4 100755 (executable)
@@ -74,13 +74,19 @@ inspect test-user
 homectl deactivate test-user
 inspect test-user
 
+homectl update test-user --real-name "Offline test" --offline
+inspect test-user
+
 PASSWORD=xEhErW0ndafV4s homectl activate test-user
 inspect test-user
 
+# Ensure that the offline changes were propagated in
+grep "Offline test" /home/test-user/.identity
+
 homectl deactivate test-user
 inspect test-user
 
-PASSWORD=xEhErW0ndafV4s homectl update test-user --real-name="Offline test"
+PASSWORD=xEhErW0ndafV4s homectl update test-user --real-name="Inactive test"
 inspect test-user
 
 PASSWORD=xEhErW0ndafV4s homectl activate test-user
@@ -326,6 +332,16 @@ checkblob barely-fits /tmp/external-barely-fits
 (! PASSWORD=EMJuc3zQaMibJo homectl update blob-user -b файл=/tmp/external-test3 )
 (! PASSWORD=EMJuc3zQaMibJo homectl update blob-user -b special@chars=/tmp/external-test3 )
 
+# Make sure offline updates to blobs get propagated in
+homectl deactivate blob-user
+inspect blob-user
+homectl update blob-user --offline -b barely-fits= -b propagated=/tmp/external-test3
+inspect blob-user
+PASSWORD=EMJuc3zQaMibJo homectl activate blob-user
+inspect blob-user
+(! checkblob barely-fits /tmp/external-barely-fits )
+checkblob propagated /tmp/external-test3
+
 homectl deactivate blob-user
 wait_for_state blob-user inactive
 homectl remove blob-user