mirror of
https://github.com/systemd/systemd.git
synced 2025-01-09 01:18:19 +03:00
homework: Implement offline updates
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
This commit is contained in:
parent
5ec87d577f
commit
d94c7eef12
@ -169,6 +169,16 @@
|
||||
<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" />
|
||||
|
||||
|
@ -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) << 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>
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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:
|
||||
|
@ -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:
|
||||
|
@ -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 */
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user