diff --git a/man/homectl.xml b/man/homectl.xml index 1a0535cd4a6..f1bade20534 100644 --- a/man/homectl.xml +++ b/man/homectl.xml @@ -169,6 +169,16 @@ + + + + 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. + + + + diff --git a/man/org.freedesktop.home1.xml b/man/org.freedesktop.home1.xml index 726d9d98325..9334f1a596e 100644 --- a/man/org.freedesktop.home1.xml +++ b/man/org.freedesktop.home1.xml @@ -334,8 +334,16 @@ node /org/freedesktop/home1 { Directories for more info). The blobs argument works in the same way as CreateHomeEx(), so check there for details. The new blob directory contents passed into this method will completely replace the user's existing blob directory. The flags argument - may be used for future expansion, but for now pass 0. This method is equivalent to UpdateEx() - on the org.freedesktop.home1.Home interface. + can be used to further customize the behavior of this method via flags defined as follows: + +#define SD_HOMED_UPDATE_OFFLINE (UINT64_C(1) << 0) + + When SD_HOMED_UPDATE_OFFLINE (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 + UpdateEx() on the org.freedesktop.home1.Home interface. ResizeHome() 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 secret diff --git a/src/home/home-util.h b/src/home/home-util.h index f2e5787a546..42131b9f41d 100644 --- a/src/home/home-util.h +++ b/src/home/home-util.h @@ -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) diff --git a/src/home/homectl.c b/src/home/homectl.c index 11a138070b1..2a7917d7f9f 100644 --- a/src/home/homectl.c +++ b/src/home/homectl.c @@ -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; diff --git a/src/home/homed-home-bus.c b/src/home/homed-home-bus.c index dd3603efa7f..23578fe314b 100644 --- a/src/home/homed-home-bus.c +++ b/src/home/homed-home-bus.c @@ -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; } diff --git a/src/home/homed-home.c b/src/home/homed-home.c index a2892d00a37..447e8c597ca 100644 --- a/src/home/homed-home.c +++ b/src/home/homed-home.c @@ -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: diff --git a/src/home/homed-manager-bus.c b/src/home/homed-manager-bus.c index 403a7d0213b..58cd0371057 100644 --- a/src/home/homed-manager-bus.c +++ b/src/home/homed-manager-bus.c @@ -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: diff --git a/src/home/homed-operation.h b/src/home/homed-operation.h index 004246a4e64..af165bb4a52 100644 --- a/src/home/homed-operation.h +++ b/src/home/homed-operation.h @@ -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 */ diff --git a/src/home/homework.c b/src/home/homework.c index d97e8fd9f09..afc11422986 100644 --- a/src/home/homework.c +++ b/src/home/homework.c @@ -1551,6 +1551,21 @@ static int home_remove(UserRecord *h) { return 0; } +static int home_basic_validate_update(UserRecord *h) { + assert(h); + + 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; @@ -1558,12 +1573,9 @@ static int home_validate_update(UserRecord *h, HomeSetup *setup, HomeSetupFlags 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))); + r = home_basic_validate_update(h); + if (r < 0) + return r; r = user_record_test_home_directory_and_warn(h); if (r < 0) @@ -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; diff --git a/test/units/testsuite-46.sh b/test/units/testsuite-46.sh index 5aef06bf228..f8275106405 100755 --- a/test/units/testsuite-46.sh +++ b/test/units/testsuite-46.sh @@ -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