<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" />
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) << 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>
#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)
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;
_cleanup_free_ char *buffer = NULL;
_cleanup_hashmap_free_ Hashmap *blobs = NULL;
const char *username;
+ uint64_t flags = 0;
int r;
if (argc >= 2)
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;
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);
" --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"
ARG_NO_PAGER,
ARG_NO_LEGEND,
ARG_NO_ASK_PASSWORD,
+ ARG_OFFLINE,
ARG_REALM,
ARG_EMAIL_ADDRESS,
ARG_DISK_SIZE,
{ "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' },
arg_ask_password = false;
break;
+ case ARG_OFFLINE:
+ arg_offline = true;
+ break;
+
case 'H':
arg_transport = BUS_TRANSPORT_REMOTE;
arg_host = optarg;
#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"
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,
if (r < 0)
return r;
+ h->current_operation->call_flags = flags;
+
return 1;
}
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);
}
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");
}
}
_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");
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:
#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"
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(
if (r < 0)
return r;
+ h->current_operation->call_flags = flags;
+
return 1;
fail:
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 */
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;
_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;
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;
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
(! 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