Systemd/src/home/homed-home.c
Lennart Poettering d1f6e01e47 homed: explicitly deactivate all home directories on shutdown
Let's explicitly deactivate all home dirs on shutdown, in order to
properly synchronizing unmounting and avoiding blocking devices.

Previously, we'd rely on automatic deactivation when home directories
become unused. However, that scheme is asynchronous, and ongoing
deactviations might conflicts with attempts to unmount /home. Let's fix
that by providing an explicit service systemd-homed-activate.service
whose only job is to have a ExecStop= line that explicitly deactivates
all home directories on shutdown. This service can the be ordered after
home.mount and similar, ensuring that we'll first deactivate all homes
before deactivating /home itself during shutdown.

This is kept separate from systemd-homed.service so that it is possible
to restart systemd-homed.service without deactivating all home
directories.

Fixes: #16842
2020-09-30 14:37:52 +02:00

2837 lines
101 KiB
C

/* SPDX-License-Identifier: LGPL-2.1+ */
#if HAVE_LINUX_MEMFD_H
#include <linux/memfd.h>
#endif
#include <sys/mman.h>
#include <sys/quota.h>
#include <sys/vfs.h>
#include "blockdev-util.h"
#include "btrfs-util.h"
#include "bus-common-errors.h"
#include "env-util.h"
#include "errno-list.h"
#include "errno-util.h"
#include "fd-util.h"
#include "fileio.h"
#include "home-util.h"
#include "homed-home-bus.h"
#include "homed-home.h"
#include "missing_syscall.h"
#include "mkdir.h"
#include "path-util.h"
#include "process-util.h"
#include "pwquality-util.h"
#include "quota-util.h"
#include "resize-fs.h"
#include "set.h"
#include "signal-util.h"
#include "stat-util.h"
#include "string-table.h"
#include "strv.h"
#include "user-record-sign.h"
#include "user-record-util.h"
#include "user-record-pwquality.h"
#include "user-record.h"
#include "user-util.h"
#define HOME_USERS_MAX 500
#define PENDING_OPERATIONS_MAX 100
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);
DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR(operation_hash_ops, void, trivial_hash_func, trivial_compare_func, Operation, operation_unref);
static int suitable_home_record(UserRecord *hr) {
int r;
assert(hr);
if (!hr->user_name)
return -EUNATCH;
/* We are a bit more restrictive with what we accept as homed-managed user than what we accept in
* home records in general. Let's enforce the stricter rule here. */
if (!suitable_user_name(hr->user_name))
return -EINVAL;
if (!uid_is_valid(hr->uid))
return -EINVAL;
/* Insist we are outside of the dynamic and system range */
if (uid_is_system(hr->uid) || gid_is_system(user_record_gid(hr)) ||
uid_is_dynamic(hr->uid) || gid_is_dynamic(user_record_gid(hr)))
return -EADDRNOTAVAIL;
/* Insist that GID and UID match */
if (user_record_gid(hr) != (gid_t) hr->uid)
return -EBADSLT;
/* Similar for the realm */
if (hr->realm) {
r = suitable_realm(hr->realm);
if (r < 0)
return r;
if (r == 0)
return -EINVAL;
}
return 0;
}
int home_new(Manager *m, UserRecord *hr, const char *sysfs, Home **ret) {
_cleanup_(home_freep) Home *home = NULL;
_cleanup_free_ char *nm = NULL, *ns = NULL;
int r;
assert(m);
assert(hr);
r = suitable_home_record(hr);
if (r < 0)
return r;
if (hashmap_contains(m->homes_by_name, hr->user_name))
return -EBUSY;
if (hashmap_contains(m->homes_by_uid, UID_TO_PTR(hr->uid)))
return -EBUSY;
if (sysfs && hashmap_contains(m->homes_by_sysfs, sysfs))
return -EBUSY;
if (hashmap_size(m->homes_by_name) >= HOME_USERS_MAX)
return -EUSERS;
nm = strdup(hr->user_name);
if (!nm)
return -ENOMEM;
if (sysfs) {
ns = strdup(sysfs);
if (!ns)
return -ENOMEM;
}
home = new(Home, 1);
if (!home)
return -ENOMEM;
*home = (Home) {
.manager = m,
.user_name = TAKE_PTR(nm),
.uid = hr->uid,
.state = _HOME_STATE_INVALID,
.worker_stdout_fd = -1,
.sysfs = TAKE_PTR(ns),
.signed_locally = -1,
};
r = hashmap_put(m->homes_by_name, home->user_name, home);
if (r < 0)
return r;
r = hashmap_put(m->homes_by_uid, UID_TO_PTR(home->uid), home);
if (r < 0)
return r;
if (home->sysfs) {
r = hashmap_put(m->homes_by_sysfs, home->sysfs, home);
if (r < 0)
return r;
}
r = user_record_clone(hr, USER_RECORD_LOAD_MASK_SECRET, &home->record);
if (r < 0)
return r;
(void) bus_manager_emit_auto_login_changed(m);
(void) bus_home_emit_change(home);
if (ret)
*ret = TAKE_PTR(home);
else
TAKE_PTR(home);
return 0;
}
Home *home_free(Home *h) {
if (!h)
return NULL;
if (h->manager) {
(void) bus_home_emit_remove(h);
(void) bus_manager_emit_auto_login_changed(h->manager);
if (h->user_name)
(void) hashmap_remove_value(h->manager->homes_by_name, h->user_name, h);
if (uid_is_valid(h->uid))
(void) hashmap_remove_value(h->manager->homes_by_uid, UID_TO_PTR(h->uid), h);
if (h->sysfs)
(void) hashmap_remove_value(h->manager->homes_by_sysfs, h->sysfs, h);
if (h->worker_pid > 0)
(void) hashmap_remove_value(h->manager->homes_by_worker_pid, PID_TO_PTR(h->worker_pid), h);
if (h->manager->gc_focus == h)
h->manager->gc_focus = NULL;
}
user_record_unref(h->record);
user_record_unref(h->secret);
h->worker_event_source = sd_event_source_unref(h->worker_event_source);
safe_close(h->worker_stdout_fd);
free(h->user_name);
free(h->sysfs);
h->ref_event_source_please_suspend = sd_event_source_unref(h->ref_event_source_please_suspend);
h->ref_event_source_dont_suspend = sd_event_source_unref(h->ref_event_source_dont_suspend);
h->pending_operations = ordered_set_free(h->pending_operations);
h->pending_event_source = sd_event_source_unref(h->pending_event_source);
h->deferred_change_event_source = sd_event_source_unref(h->deferred_change_event_source);
h->current_operation = operation_unref(h->current_operation);
return mfree(h);
}
int home_set_record(Home *h, UserRecord *hr) {
_cleanup_(user_record_unrefp) UserRecord *new_hr = NULL;
Home *other;
int r;
assert(h);
assert(h->user_name);
assert(h->record);
assert(hr);
if (user_record_equal(h->record, hr))
return 0;
r = suitable_home_record(hr);
if (r < 0)
return r;
if (!user_record_compatible(h->record, hr))
return -EREMCHG;
if (!FLAGS_SET(hr->mask, USER_RECORD_REGULAR) ||
FLAGS_SET(hr->mask, USER_RECORD_SECRET))
return -EINVAL;
if (FLAGS_SET(h->record->mask, USER_RECORD_STATUS)) {
_cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
/* Hmm, the existing record has status fields? If so, copy them over */
v = json_variant_ref(hr->json);
r = json_variant_set_field(&v, "status", json_variant_by_key(h->record->json, "status"));
if (r < 0)
return r;
new_hr = user_record_new();
if (!new_hr)
return -ENOMEM;
r = user_record_load(new_hr, v, USER_RECORD_LOAD_REFUSE_SECRET);
if (r < 0)
return r;
hr = new_hr;
}
other = hashmap_get(h->manager->homes_by_uid, UID_TO_PTR(hr->uid));
if (other && other != h)
return -EBUSY;
if (h->uid != hr->uid) {
r = hashmap_remove_and_replace(h->manager->homes_by_uid, UID_TO_PTR(h->uid), UID_TO_PTR(hr->uid), h);
if (r < 0)
return r;
}
user_record_unref(h->record);
h->record = user_record_ref(hr);
h->uid = h->record->uid;
/* The updated record might have a different autologin setting, trigger a PropertiesChanged event for it */
(void) bus_manager_emit_auto_login_changed(h->manager);
(void) bus_home_emit_change(h);
return 0;
}
int home_save_record(Home *h) {
_cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
_cleanup_free_ char *text = NULL;
const char *fn;
int r;
assert(h);
v = json_variant_ref(h->record->json);
r = json_variant_normalize(&v);
if (r < 0)
log_warning_errno(r, "User record could not be normalized.");
r = json_variant_format(v, JSON_FORMAT_PRETTY|JSON_FORMAT_NEWLINE, &text);
if (r < 0)
return r;
(void) mkdir("/var/lib/systemd/", 0755);
(void) mkdir("/var/lib/systemd/home/", 0700);
fn = strjoina("/var/lib/systemd/home/", h->user_name, ".identity");
r = write_string_file(fn, text, WRITE_STRING_FILE_ATOMIC|WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_MODE_0600|WRITE_STRING_FILE_SYNC);
if (r < 0)
return r;
return 0;
}
int home_unlink_record(Home *h) {
const char *fn;
assert(h);
fn = strjoina("/var/lib/systemd/home/", h->user_name, ".identity");
if (unlink(fn) < 0 && errno != ENOENT)
return -errno;
fn = strjoina("/run/systemd/home/", h->user_name, ".ref");
if (unlink(fn) < 0 && errno != ENOENT)
return -errno;
return 0;
}
static void home_set_state(Home *h, HomeState state) {
HomeState old_state, new_state;
assert(h);
old_state = home_get_state(h);
h->state = state;
new_state = home_get_state(h); /* Query the new state, since the 'state' variable might be set to -1,
* in which case we synthesize an high-level state on demand */
log_info("%s: changing state %s → %s", h->user_name,
home_state_to_string(old_state),
home_state_to_string(new_state));
if (HOME_STATE_IS_EXECUTING_OPERATION(old_state) && !HOME_STATE_IS_EXECUTING_OPERATION(new_state)) {
/* If we just finished executing some operation, process the queue of pending operations. And
* enqueue it for GC too. */
home_schedule_operation(h, NULL, NULL);
manager_enqueue_gc(h->manager, h);
}
}
static int home_parse_worker_stdout(int _fd, UserRecord **ret) {
_cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
_cleanup_close_ int fd = _fd; /* take possession, even on failure */
_cleanup_(user_record_unrefp) UserRecord *hr = NULL;
_cleanup_fclose_ FILE *f = NULL;
unsigned line, column;
struct stat st;
int r;
if (fstat(fd, &st) < 0)
return log_error_errno(errno, "Failed to stat stdout fd: %m");
assert(S_ISREG(st.st_mode));
if (st.st_size == 0) { /* empty record */
*ret = NULL;
return 0;
}
if (lseek(fd, SEEK_SET, 0) == (off_t) -1)
return log_error_errno(errno, "Failed to seek to beginning of memfd: %m");
f = take_fdopen(&fd, "r");
if (!f)
return log_error_errno(errno, "Failed to reopen memfd: %m");
if (DEBUG_LOGGING) {
_cleanup_free_ char *text = NULL;
r = read_full_stream(f, &text, NULL);
if (r < 0)
return log_error_errno(r, "Failed to read from client: %m");
log_debug("Got from worker: %s", text);
rewind(f);
}
r = json_parse_file(f, "stdout", JSON_PARSE_SENSITIVE, &v, &line, &column);
if (r < 0)
return log_error_errno(r, "Failed to parse identity at %u:%u: %m", line, column);
hr = user_record_new();
if (!hr)
return log_oom();
r = user_record_load(hr, v, USER_RECORD_LOAD_REFUSE_SECRET);
if (r < 0)
return log_error_errno(r, "Failed to load home record identity: %m");
*ret = TAKE_PTR(hr);
return 1;
}
static int home_verify_user_record(Home *h, UserRecord *hr, bool *ret_signed_locally, sd_bus_error *ret_error) {
int is_signed;
assert(h);
assert(hr);
assert(ret_signed_locally);
is_signed = manager_verify_user_record(h->manager, hr);
switch (is_signed) {
case USER_RECORD_SIGNED_EXCLUSIVE:
log_info("Home %s is signed exclusively by our key, accepting.", hr->user_name);
*ret_signed_locally = true;
return 0;
case USER_RECORD_SIGNED:
log_info("Home %s is signed by our key (and others), accepting.", hr->user_name);
*ret_signed_locally = false;
return 0;
case USER_RECORD_FOREIGN:
log_info("Home %s is signed by foreign key we like, accepting.", hr->user_name);
*ret_signed_locally = false;
return 0;
case USER_RECORD_UNSIGNED:
sd_bus_error_setf(ret_error, BUS_ERROR_BAD_SIGNATURE, "User record %s is not signed at all, refusing.", hr->user_name);
return log_error_errno(SYNTHETIC_ERRNO(EPERM), "Home %s contains user record that is not signed at all, refusing.", hr->user_name);
case -ENOKEY:
sd_bus_error_setf(ret_error, BUS_ERROR_BAD_SIGNATURE, "User record %s is not signed by any known key, refusing.", hr->user_name);
return log_error_errno(is_signed, "Home %s contains user record that is not signed by any known key, refusing.", hr->user_name);
default:
assert(is_signed < 0);
return log_error_errno(is_signed, "Failed to verify signature on user record for %s, refusing fixation: %m", hr->user_name);
}
}
static int convert_worker_errno(Home *h, int e, sd_bus_error *error) {
/* Converts the error numbers the worker process returned into somewhat sensible dbus errors */
switch (e) {
case -EMSGSIZE:
return sd_bus_error_setf(error, BUS_ERROR_BAD_HOME_SIZE, "File systems of this type cannot be shrunk");
case -ETXTBSY:
return sd_bus_error_setf(error, BUS_ERROR_BAD_HOME_SIZE, "File systems of this type can only be shrunk offline");
case -ERANGE:
return sd_bus_error_setf(error, BUS_ERROR_BAD_HOME_SIZE, "File system size too small");
case -ENOLINK:
return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "System does not support selected storage backend");
case -EPROTONOSUPPORT:
return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "System does not support selected file system");
case -ENOTTY:
return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Operation not supported on storage backend");
case -ESOCKTNOSUPPORT:
return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Operation not supported on file system");
case -ENOKEY:
return sd_bus_error_setf(error, BUS_ERROR_BAD_PASSWORD, "Password for home %s is incorrect or not sufficient for authentication.", h->user_name);
case -EBADSLT:
return sd_bus_error_setf(error, BUS_ERROR_BAD_PASSWORD_AND_NO_TOKEN, "Password for home %s is incorrect or not sufficient, and configured security token not found either.", h->user_name);
case -EREMOTEIO:
return sd_bus_error_setf(error, BUS_ERROR_BAD_RECOVERY_KEY, "Recovery key for home %s is incorrect or not sufficient for authentication.", h->user_name);
case -ENOANO:
return sd_bus_error_setf(error, BUS_ERROR_TOKEN_PIN_NEEDED, "PIN for security token required.");
case -ERFKILL:
return sd_bus_error_setf(error, BUS_ERROR_TOKEN_PROTECTED_AUTHENTICATION_PATH_NEEDED, "Security token requires protected authentication path.");
case -EMEDIUMTYPE:
return sd_bus_error_setf(error, BUS_ERROR_TOKEN_USER_PRESENCE_NEEDED, "Security token requires user presence.");
case -ENOSTR:
return sd_bus_error_setf(error, BUS_ERROR_TOKEN_ACTION_TIMEOUT, "Token action timeout. (User was supposed to verify presence or similar, by interacting with the token, and didn't do that in time.)");
case -EOWNERDEAD:
return sd_bus_error_setf(error, BUS_ERROR_TOKEN_PIN_LOCKED, "PIN of security token locked.");
case -ENOLCK:
return sd_bus_error_setf(error, BUS_ERROR_TOKEN_BAD_PIN, "Bad PIN of security token.");
case -ETOOMANYREFS:
return sd_bus_error_setf(error, BUS_ERROR_TOKEN_BAD_PIN_FEW_TRIES_LEFT, "Bad PIN of security token, and only a few tries left.");
case -EUCLEAN:
return sd_bus_error_setf(error, BUS_ERROR_TOKEN_BAD_PIN_ONE_TRY_LEFT, "Bad PIN of security token, and only one try left.");
case -EBUSY:
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);
case -ENOEXEC:
return sd_bus_error_setf(error, BUS_ERROR_HOME_NOT_ACTIVE, "Home %s is currently not active", h->user_name);
case -ENOSPC:
return sd_bus_error_setf(error, BUS_ERROR_NO_DISK_SPACE, "Not enough disk space for home %s", h->user_name);
case -EKEYREVOKED:
return sd_bus_error_setf(error, BUS_ERROR_HOME_CANT_AUTHENTICATE, "Home %s has no password or other authentication mechanism defined.", h->user_name);
}
return 0;
}
static void home_count_bad_authentication(Home *h, bool save) {
int r;
assert(h);
r = user_record_bad_authentication(h->record);
if (r < 0) {
log_warning_errno(r, "Failed to increase bad authentication counter, ignoring: %m");
return;
}
if (save) {
r = home_save_record(h);
if (r < 0)
log_warning_errno(r, "Failed to write home record to disk, ignoring: %m");
}
}
static void home_fixate_finish(Home *h, int ret, UserRecord *hr) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_(user_record_unrefp) UserRecord *secret = NULL;
bool signed_locally;
int r;
assert(h);
assert(IN_SET(h->state, HOME_FIXATING, HOME_FIXATING_FOR_ACTIVATION, HOME_FIXATING_FOR_ACQUIRE));
secret = TAKE_PTR(h->secret); /* Take possession */
if (ret < 0) {
if (ret == -ENOKEY)
(void) home_count_bad_authentication(h, false);
(void) convert_worker_errno(h, ret, &error);
r = log_error_errno(ret, "Fixation failed: %m");
goto fail;
}
if (!hr) {
r = log_error_errno(SYNTHETIC_ERRNO(EIO), "Did not receive user record from worker process, fixation failed.");
goto fail;
}
r = home_verify_user_record(h, hr, &signed_locally, &error);
if (r < 0)
goto fail;
r = home_set_record(h, hr);
if (r < 0) {
log_error_errno(r, "Failed to update home record: %m");
goto fail;
}
h->signed_locally = signed_locally;
/* When we finished fixating (and don't follow-up with activation), let's count this as good authentication */
if (h->state == HOME_FIXATING) {
r = user_record_good_authentication(h->record);
if (r < 0)
log_warning_errno(r, "Failed to increase good authentication counter, ignoring: %m");
}
r = home_save_record(h);
if (r < 0)
log_warning_errno(r, "Failed to write home record to disk, ignoring: %m");
if (IN_SET(h->state, HOME_FIXATING_FOR_ACTIVATION, HOME_FIXATING_FOR_ACQUIRE)) {
r = home_start_work(h, "activate", h->record, secret);
if (r < 0) {
h->current_operation = operation_result_unref(h->current_operation, r, NULL);
home_set_state(h, _HOME_STATE_INVALID);
} else
home_set_state(h, h->state == HOME_FIXATING_FOR_ACTIVATION ? HOME_ACTIVATING : HOME_ACTIVATING_FOR_ACQUIRE);
return;
}
log_debug("Fixation of %s completed.", h->user_name);
h->current_operation = operation_result_unref(h->current_operation, 0, NULL);
/* Reset the state to "invalid", which makes home_get_state() test if the image exists and returns
* HOME_ABSENT vs. HOME_INACTIVE as necessary. */
home_set_state(h, _HOME_STATE_INVALID);
return;
fail:
/* If fixation fails, we stay in unfixated state! */
h->current_operation = operation_result_unref(h->current_operation, r, &error);
home_set_state(h, HOME_UNFIXATED);
}
static void home_activate_finish(Home *h, int ret, UserRecord *hr) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
int r;
assert(h);
assert(IN_SET(h->state, HOME_ACTIVATING, HOME_ACTIVATING_FOR_ACQUIRE));
if (ret < 0) {
if (ret == -ENOKEY)
home_count_bad_authentication(h, true);
(void) convert_worker_errno(h, ret, &error);
r = log_error_errno(ret, "Activation failed: %m");
goto finish;
}
if (hr) {
bool signed_locally;
r = home_verify_user_record(h, hr, &signed_locally, &error);
if (r < 0)
goto finish;
r = home_set_record(h, hr);
if (r < 0) {
log_error_errno(r, "Failed to update home record, ignoring: %m");
goto finish;
}
h->signed_locally = signed_locally;
r = user_record_good_authentication(h->record);
if (r < 0)
log_warning_errno(r, "Failed to increase good authentication counter, ignoring: %m");
r = home_save_record(h);
if (r < 0)
log_warning_errno(r, "Failed to write home record to disk, ignoring: %m");
}
log_debug("Activation of %s completed.", h->user_name);
r = 0;
finish:
h->current_operation = operation_result_unref(h->current_operation, r, &error);
home_set_state(h, _HOME_STATE_INVALID);
}
static void home_deactivate_finish(Home *h, int ret, UserRecord *hr) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
int r;
assert(h);
assert(h->state == HOME_DEACTIVATING);
assert(!hr); /* We don't expect a record on this operation */
if (ret < 0) {
(void) convert_worker_errno(h, ret, &error);
r = log_error_errno(ret, "Deactivation of %s failed: %m", h->user_name);
goto finish;
}
log_debug("Deactivation of %s completed.", h->user_name);
r = 0;
finish:
h->current_operation = operation_result_unref(h->current_operation, r, &error);
home_set_state(h, _HOME_STATE_INVALID);
}
static void home_remove_finish(Home *h, int ret, UserRecord *hr) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
Manager *m;
int r;
assert(h);
assert(h->state == HOME_REMOVING);
assert(!hr); /* We don't expect a record on this operation */
m = h->manager;
if (ret < 0 && ret != -EALREADY) {
(void) convert_worker_errno(h, ret, &error);
r = log_error_errno(ret, "Removing %s failed: %m", h->user_name);
goto fail;
}
/* For a couple of storage types we can't delete the actual data storage when called (such as LUKS on
* partitions like USB sticks, or so). Sometimes these storage locations are among those we normally
* automatically discover in /home or in udev. When such a home is deleted let's hence issue a rescan
* after completion, so that "unfixated" entries are rediscovered. */
if (!IN_SET(user_record_test_image_path(h->record), USER_TEST_UNDEFINED, USER_TEST_ABSENT))
manager_enqueue_rescan(m);
/* The image is now removed from disk. Now also remove our stored record */
r = home_unlink_record(h);
if (r < 0) {
log_error_errno(r, "Removing record file failed: %m");
goto fail;
}
log_debug("Removal of %s completed.", h->user_name);
h->current_operation = operation_result_unref(h->current_operation, 0, NULL);
/* Unload this record from memory too now. */
h = home_free(h);
return;
fail:
h->current_operation = operation_result_unref(h->current_operation, r, &error);
home_set_state(h, _HOME_STATE_INVALID);
}
static void home_create_finish(Home *h, int ret, UserRecord *hr) {
int r;
assert(h);
assert(h->state == HOME_CREATING);
if (ret < 0) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
(void) convert_worker_errno(h, ret, &error);
log_error_errno(ret, "Operation on %s failed: %m", h->user_name);
h->current_operation = operation_result_unref(h->current_operation, ret, &error);
if (h->unregister_on_failure) {
(void) home_unlink_record(h);
h = home_free(h);
return;
}
home_set_state(h, _HOME_STATE_INVALID);
return;
}
if (hr) {
r = home_set_record(h, hr);
if (r < 0)
log_warning_errno(r, "Failed to update home record, ignoring: %m");
}
r = home_save_record(h);
if (r < 0)
log_warning_errno(r, "Failed to save record to disk, ignoring: %m");
log_debug("Creation of %s completed.", h->user_name);
h->current_operation = operation_result_unref(h->current_operation, 0, NULL);
home_set_state(h, _HOME_STATE_INVALID);
}
static void home_change_finish(Home *h, int ret, UserRecord *hr) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
int r;
assert(h);
if (ret < 0) {
if (ret == -ENOKEY)
(void) home_count_bad_authentication(h, true);
(void) convert_worker_errno(h, ret, &error);
r = log_error_errno(ret, "Change operation failed: %m");
goto finish;
}
if (hr) {
r = home_set_record(h, hr);
if (r < 0)
log_warning_errno(r, "Failed to update home record, ignoring: %m");
else {
r = user_record_good_authentication(h->record);
if (r < 0)
log_warning_errno(r, "Failed to increase good authentication counter, ignoring: %m");
r = home_save_record(h);
if (r < 0)
log_warning_errno(r, "Failed to write home record to disk, ignoring: %m");
}
}
log_debug("Change operation of %s completed.", h->user_name);
r = 0;
finish:
h->current_operation = operation_result_unref(h->current_operation, r, &error);
home_set_state(h, _HOME_STATE_INVALID);
}
static void home_locking_finish(Home *h, int ret, UserRecord *hr) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
int r;
assert(h);
assert(h->state == HOME_LOCKING);
if (ret < 0) {
(void) convert_worker_errno(h, ret, &error);
r = log_error_errno(ret, "Locking operation failed: %m");
goto finish;
}
log_debug("Locking operation of %s completed.", h->user_name);
h->current_operation = operation_result_unref(h->current_operation, 0, NULL);
home_set_state(h, HOME_LOCKED);
return;
finish:
/* If a specific home doesn't know the concept of locking, then that's totally OK, don't propagate
* the error if we are executing a LockAllHomes() operation. */
if (h->current_operation->type == OPERATION_LOCK_ALL && r == -ENOTTY)
h->current_operation = operation_result_unref(h->current_operation, 0, NULL);
else
h->current_operation = operation_result_unref(h->current_operation, r, &error);
home_set_state(h, _HOME_STATE_INVALID);
}
static void home_unlocking_finish(Home *h, int ret, UserRecord *hr) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
int r;
assert(h);
assert(IN_SET(h->state, HOME_UNLOCKING, HOME_UNLOCKING_FOR_ACQUIRE));
if (ret < 0) {
if (ret == -ENOKEY)
(void) home_count_bad_authentication(h, true);
(void) convert_worker_errno(h, ret, &error);
r = log_error_errno(ret, "Unlocking operation failed: %m");
/* Revert to locked state */
home_set_state(h, HOME_LOCKED);
h->current_operation = operation_result_unref(h->current_operation, r, &error);
return;
}
r = user_record_good_authentication(h->record);
if (r < 0)
log_warning_errno(r, "Failed to increase good authentication counter, ignoring: %m");
else {
r = home_save_record(h);
if (r < 0)
log_warning_errno(r, "Failed to write home record to disk, ignoring: %m");
}
log_debug("Unlocking operation of %s completed.", h->user_name);
h->current_operation = operation_result_unref(h->current_operation, r, &error);
home_set_state(h, _HOME_STATE_INVALID);
return;
}
static void home_authenticating_finish(Home *h, int ret, UserRecord *hr) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
int r;
assert(h);
assert(IN_SET(h->state, HOME_AUTHENTICATING, HOME_AUTHENTICATING_WHILE_ACTIVE, HOME_AUTHENTICATING_FOR_ACQUIRE));
if (ret < 0) {
if (ret == -ENOKEY)
(void) home_count_bad_authentication(h, true);
(void) convert_worker_errno(h, ret, &error);
r = log_error_errno(ret, "Authentication failed: %m");
goto finish;
}
if (hr) {
r = home_set_record(h, hr);
if (r < 0)
log_warning_errno(r, "Failed to update home record, ignoring: %m");
else {
r = user_record_good_authentication(h->record);
if (r < 0)
log_warning_errno(r, "Failed to increase good authentication counter, ignoring: %m");
r = home_save_record(h);
if (r < 0)
log_warning_errno(r, "Failed to write home record to disk, ignoring: %m");
}
}
log_debug("Authentication of %s completed.", h->user_name);
r = 0;
finish:
h->current_operation = operation_result_unref(h->current_operation, r, &error);
home_set_state(h, _HOME_STATE_INVALID);
}
static int home_on_worker_process(sd_event_source *s, const siginfo_t *si, void *userdata) {
_cleanup_(user_record_unrefp) UserRecord *hr = NULL;
Home *h = userdata;
int ret;
assert(s);
assert(si);
assert(h);
assert(h->worker_pid == si->si_pid);
assert(h->worker_event_source);
assert(h->worker_stdout_fd >= 0);
(void) hashmap_remove_value(h->manager->homes_by_worker_pid, PID_TO_PTR(h->worker_pid), h);
h->worker_pid = 0;
h->worker_event_source = sd_event_source_unref(h->worker_event_source);
if (si->si_code != CLD_EXITED) {
assert(IN_SET(si->si_code, CLD_KILLED, CLD_DUMPED));
ret = log_debug_errno(SYNTHETIC_ERRNO(EPROTO), "Worker process died abnormally with signal %s.", signal_to_string(si->si_status));
} else if (si->si_status != EXIT_SUCCESS) {
/* If we received an error code via sd_notify(), use it */
if (h->worker_error_code != 0)
ret = log_debug_errno(h->worker_error_code, "Worker reported error code %s.", errno_to_name(h->worker_error_code));
else
ret = log_debug_errno(SYNTHETIC_ERRNO(EPROTO), "Worker exited with exit code %i.", si->si_status);
} else
ret = home_parse_worker_stdout(TAKE_FD(h->worker_stdout_fd), &hr);
h->worker_stdout_fd = safe_close(h->worker_stdout_fd);
switch (h->state) {
case HOME_FIXATING:
case HOME_FIXATING_FOR_ACTIVATION:
case HOME_FIXATING_FOR_ACQUIRE:
home_fixate_finish(h, ret, hr);
break;
case HOME_ACTIVATING:
case HOME_ACTIVATING_FOR_ACQUIRE:
home_activate_finish(h, ret, hr);
break;
case HOME_DEACTIVATING:
home_deactivate_finish(h, ret, hr);
break;
case HOME_LOCKING:
home_locking_finish(h, ret, hr);
break;
case HOME_UNLOCKING:
case HOME_UNLOCKING_FOR_ACQUIRE:
home_unlocking_finish(h, ret, hr);
break;
case HOME_CREATING:
home_create_finish(h, ret, hr);
break;
case HOME_REMOVING:
home_remove_finish(h, ret, hr);
break;
case HOME_UPDATING:
case HOME_UPDATING_WHILE_ACTIVE:
case HOME_RESIZING:
case HOME_RESIZING_WHILE_ACTIVE:
case HOME_PASSWD:
case HOME_PASSWD_WHILE_ACTIVE:
home_change_finish(h, ret, hr);
break;
case HOME_AUTHENTICATING:
case HOME_AUTHENTICATING_WHILE_ACTIVE:
case HOME_AUTHENTICATING_FOR_ACQUIRE:
home_authenticating_finish(h, ret, hr);
break;
default:
assert_not_reached("Unexpected state after worker exited");
}
return 0;
}
static int home_start_work(Home *h, const char *verb, UserRecord *hr, UserRecord *secret) {
_cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
_cleanup_(erase_and_freep) char *formatted = NULL;
_cleanup_close_ int stdin_fd = -1, stdout_fd = -1;
pid_t pid = 0;
int r;
assert(h);
assert(verb);
assert(hr);
if (h->worker_pid != 0)
return -EBUSY;
assert(h->worker_stdout_fd < 0);
assert(!h->worker_event_source);
v = json_variant_ref(hr->json);
if (secret) {
JsonVariant *sub = NULL;
sub = json_variant_by_key(secret->json, "secret");
if (!sub)
return -ENOKEY;
r = json_variant_set_field(&v, "secret", sub);
if (r < 0)
return r;
}
r = json_variant_format(v, 0, &formatted);
if (r < 0)
return r;
stdin_fd = acquire_data_fd(formatted, strlen(formatted), 0);
if (stdin_fd < 0)
return stdin_fd;
log_debug("Sending to worker: %s", formatted);
stdout_fd = memfd_create("homework-stdout", MFD_CLOEXEC);
if (stdout_fd < 0)
return -errno;
r = safe_fork_full("(sd-homework)",
(int[]) { stdin_fd, stdout_fd }, 2,
FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG|FORK_LOG, &pid);
if (r < 0)
return r;
if (r == 0) {
const char *homework, *suffix, *unix_path;
/* Child */
suffix = getenv("SYSTEMD_HOME_DEBUG_SUFFIX");
if (suffix)
unix_path = strjoina("/run/systemd/home/notify.", suffix);
else
unix_path = "/run/systemd/home/notify";
if (setenv("NOTIFY_SOCKET", unix_path, 1) < 0) {
log_error_errno(errno, "Failed to set $NOTIFY_SOCKET: %m");
_exit(EXIT_FAILURE);
}
if (h->manager->default_storage >= 0)
if (setenv("SYSTEMD_HOME_DEFAULT_STORAGE", user_storage_to_string(h->manager->default_storage), 1) < 0) {
log_error_errno(errno, "Failed to set $SYSTEMD_HOME_DEFAULT_STORAGE: %m");
_exit(EXIT_FAILURE);
}
if (h->manager->default_file_system_type)
if (setenv("SYSTEMD_HOME_DEFAULT_FILE_SYSTEM_TYPE", h->manager->default_file_system_type, 1) < 0) {
log_error_errno(errno, "Failed to set $SYSTEMD_HOME_DEFAULT_FILE_SYSTEM_TYPE: %m");
_exit(EXIT_FAILURE);
}
r = rearrange_stdio(stdin_fd, stdout_fd, STDERR_FILENO);
if (r < 0) {
log_error_errno(r, "Failed to rearrange stdin/stdout/stderr: %m");
_exit(EXIT_FAILURE);
}
stdin_fd = stdout_fd = -1; /* have been invalidated by rearrange_stdio() */
/* Allow overriding the homework path via an environment variable, to make debugging
* easier. */
homework = getenv("SYSTEMD_HOMEWORK_PATH") ?: SYSTEMD_HOMEWORK_PATH;
execl(homework, homework, verb, NULL);
log_error_errno(errno, "Failed to invoke %s: %m", homework);
_exit(EXIT_FAILURE);
}
r = sd_event_add_child(h->manager->event, &h->worker_event_source, pid, WEXITED, home_on_worker_process, h);
if (r < 0)
return r;
(void) sd_event_source_set_description(h->worker_event_source, "worker");
r = hashmap_put(h->manager->homes_by_worker_pid, PID_TO_PTR(pid), h);
if (r < 0) {
h->worker_event_source = sd_event_source_unref(h->worker_event_source);
return r;
}
h->worker_stdout_fd = TAKE_FD(stdout_fd);
h->worker_pid = pid;
h->worker_error_code = 0;
return 0;
}
static int home_ratelimit(Home *h, sd_bus_error *error) {
int r, ret;
assert(h);
ret = user_record_ratelimit(h->record);
if (ret < 0)
return ret;
if (h->state != HOME_UNFIXATED) {
r = home_save_record(h);
if (r < 0)
log_warning_errno(r, "Failed to save updated record, ignoring: %m");
}
if (ret == 0) {
char buf[FORMAT_TIMESPAN_MAX];
usec_t t, n;
n = now(CLOCK_REALTIME);
t = user_record_ratelimit_next_try(h->record);
if (t != USEC_INFINITY && t > n)
return sd_bus_error_setf(error, BUS_ERROR_AUTHENTICATION_LIMIT_HIT, "Too many login attempts, please try again in %s!",
format_timespan(buf, sizeof(buf), t - n, USEC_PER_SEC));
return sd_bus_error_setf(error, BUS_ERROR_AUTHENTICATION_LIMIT_HIT, "Too many login attempts, please try again later.");
}
return 0;
}
static int home_fixate_internal(
Home *h,
UserRecord *secret,
HomeState for_state,
sd_bus_error *error) {
int r;
assert(h);
assert(IN_SET(for_state, HOME_FIXATING, HOME_FIXATING_FOR_ACTIVATION, HOME_FIXATING_FOR_ACQUIRE));
r = home_start_work(h, "inspect", h->record, secret);
if (r < 0)
return r;
if (IN_SET(for_state, HOME_FIXATING_FOR_ACTIVATION, HOME_FIXATING_FOR_ACQUIRE)) {
/* Remember the secret data, since we need it for the activation again, later on. */
user_record_unref(h->secret);
h->secret = user_record_ref(secret);
}
home_set_state(h, for_state);
return 0;
}
int home_fixate(Home *h, UserRecord *secret, sd_bus_error *error) {
int r;
assert(h);
switch (home_get_state(h)) {
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);
case HOME_INACTIVE:
case HOME_DIRTY:
case HOME_ACTIVE:
case HOME_LOCKED:
return sd_bus_error_setf(error, BUS_ERROR_HOME_ALREADY_FIXATED, "Home %s is already fixated.", h->user_name);
case HOME_UNFIXATED:
break;
default:
return sd_bus_error_setf(error, BUS_ERROR_HOME_BUSY, "An operation on home %s is currently being executed.", h->user_name);
}
r = home_ratelimit(h, error);
if (r < 0)
return r;
return home_fixate_internal(h, secret, HOME_FIXATING, error);
}
static int home_activate_internal(Home *h, UserRecord *secret, HomeState for_state, sd_bus_error *error) {
int r;
assert(h);
assert(IN_SET(for_state, HOME_ACTIVATING, HOME_ACTIVATING_FOR_ACQUIRE));
r = home_start_work(h, "activate", h->record, secret);
if (r < 0)
return r;
home_set_state(h, for_state);
return 0;
}
int home_activate(Home *h, UserRecord *secret, sd_bus_error *error) {
int r;
assert(h);
switch (home_get_state(h)) {
case HOME_UNFIXATED:
return home_fixate_internal(h, secret, HOME_FIXATING_FOR_ACTIVATION, error);
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);
case HOME_ACTIVE:
return sd_bus_error_setf(error, BUS_ERROR_HOME_ALREADY_ACTIVE, "Home %s is already active.", h->user_name);
case HOME_LOCKED:
return sd_bus_error_setf(error, BUS_ERROR_HOME_LOCKED, "Home %s is currently locked.", h->user_name);
case HOME_INACTIVE:
case HOME_DIRTY:
break;
default:
return sd_bus_error_setf(error, BUS_ERROR_HOME_BUSY, "An operation on home %s is currently being executed.", h->user_name);
}
r = home_ratelimit(h, error);
if (r < 0)
return r;
return home_activate_internal(h, secret, HOME_ACTIVATING, error);
}
static int home_authenticate_internal(Home *h, UserRecord *secret, HomeState for_state, sd_bus_error *error) {
int r;
assert(h);
assert(IN_SET(for_state, HOME_AUTHENTICATING, HOME_AUTHENTICATING_WHILE_ACTIVE, HOME_AUTHENTICATING_FOR_ACQUIRE));
r = home_start_work(h, "inspect", h->record, secret);
if (r < 0)
return r;
home_set_state(h, for_state);
return 0;
}
int home_authenticate(Home *h, UserRecord *secret, sd_bus_error *error) {
HomeState state;
int r;
assert(h);
state = home_get_state(h);
switch (state) {
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);
case HOME_LOCKED:
return sd_bus_error_setf(error, BUS_ERROR_HOME_LOCKED, "Home %s is currently locked.", h->user_name);
case HOME_UNFIXATED:
case HOME_INACTIVE:
case HOME_DIRTY:
case HOME_ACTIVE:
break;
default:
return sd_bus_error_setf(error, BUS_ERROR_HOME_BUSY, "An operation on home %s is currently being executed.", h->user_name);
}
r = home_ratelimit(h, error);
if (r < 0)
return r;
return home_authenticate_internal(h, secret, state == HOME_ACTIVE ? HOME_AUTHENTICATING_WHILE_ACTIVE : HOME_AUTHENTICATING, error);
}
static int home_deactivate_internal(Home *h, bool force, sd_bus_error *error) {
int r;
assert(h);
r = home_start_work(h, force ? "deactivate-force" : "deactivate", h->record, NULL);
if (r < 0)
return r;
home_set_state(h, HOME_DEACTIVATING);
return 0;
}
int home_deactivate(Home *h, bool force, sd_bus_error *error) {
assert(h);
switch (home_get_state(h)) {
case HOME_UNFIXATED:
case HOME_ABSENT:
case HOME_INACTIVE:
case HOME_DIRTY:
return sd_bus_error_setf(error, BUS_ERROR_HOME_NOT_ACTIVE, "Home %s not active.", h->user_name);
case HOME_LOCKED:
return sd_bus_error_setf(error, BUS_ERROR_HOME_LOCKED, "Home %s is currently locked.", h->user_name);
case HOME_ACTIVE:
break;
default:
return sd_bus_error_setf(error, BUS_ERROR_HOME_BUSY, "An operation on home %s is currently being executed.", h->user_name);
}
return home_deactivate_internal(h, force, error);
}
int home_create(Home *h, UserRecord *secret, sd_bus_error *error) {
int r;
assert(h);
switch (home_get_state(h)) {
case HOME_INACTIVE: {
int t;
if (h->record->storage < 0)
break; /* if no storage is defined we don't know what precisely to look for, hence
* HOME_INACTIVE is OK in that case too. */
t = user_record_test_image_path(h->record);
if (IN_SET(t, USER_TEST_MAYBE, USER_TEST_UNDEFINED))
break; /* And if the image path test isn't conclusive, let's also go on */
if (IN_SET(t, -EBADFD, -ENOTDIR))
return sd_bus_error_setf(error, BUS_ERROR_HOME_EXISTS, "Selected home image of user %s already exists or has wrong inode type.", h->user_name);
return sd_bus_error_setf(error, BUS_ERROR_HOME_EXISTS, "Selected home image of user %s already exists.", h->user_name);
}
case HOME_UNFIXATED:
case HOME_DIRTY:
return sd_bus_error_setf(error, BUS_ERROR_HOME_EXISTS, "Home of user %s already exists.", h->user_name);
case HOME_ABSENT:
break;
case HOME_ACTIVE:
case HOME_LOCKED:
default:
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);
}
if (h->record->enforce_password_policy == false)
log_debug("Password quality check turned off for account, skipping.");
else {
r = user_record_quality_check_password(h->record, secret, error);
if (r < 0)
return r;
}
r = home_start_work(h, "create", h->record, secret);
if (r < 0)
return r;
home_set_state(h, HOME_CREATING);
return 0;
}
int home_remove(Home *h, sd_bus_error *error) {
HomeState state;
int r;
assert(h);
state = home_get_state(h);
switch (state) {
case HOME_ABSENT: /* If the home directory is absent, then this is just like unregistering */
return home_unregister(h, error);
case HOME_LOCKED:
return sd_bus_error_setf(error, BUS_ERROR_HOME_LOCKED, "Home %s is currently locked.", h->user_name);
case HOME_UNFIXATED:
case HOME_INACTIVE:
case HOME_DIRTY:
break;
case HOME_ACTIVE:
default:
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);
if (r < 0)
return r;
home_set_state(h, HOME_REMOVING);
return 0;
}
static int user_record_extend_with_binding(UserRecord *hr, UserRecord *with_binding, UserRecordLoadFlags flags, UserRecord **ret) {
_cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
_cleanup_(user_record_unrefp) UserRecord *nr = NULL;
JsonVariant *binding;
int r;
assert(hr);
assert(with_binding);
assert(ret);
assert_se(v = json_variant_ref(hr->json));
binding = json_variant_by_key(with_binding->json, "binding");
if (binding) {
r = json_variant_set_field(&v, "binding", binding);
if (r < 0)
return r;
}
nr = user_record_new();
if (!nr)
return -ENOMEM;
r = user_record_load(nr, v, flags);
if (r < 0)
return r;
*ret = TAKE_PTR(nr);
return 0;
}
static int home_update_internal(
Home *h,
const char *verb,
UserRecord *hr,
UserRecord *secret,
sd_bus_error *error) {
_cleanup_(user_record_unrefp) UserRecord *new_hr = NULL, *saved_secret = NULL, *signed_hr = NULL;
int r, c;
assert(h);
assert(verb);
assert(hr);
if (!user_record_compatible(hr, h->record))
return sd_bus_error_setf(error, BUS_ERROR_HOME_RECORD_MISMATCH, "Updated user record is not compatible with existing one.");
c = user_record_compare_last_change(hr, h->record); /* refuse downgrades */
if (c < 0)
return sd_bus_error_setf(error, BUS_ERROR_HOME_RECORD_DOWNGRADE, "Refusing to update to older home record.");
if (!secret && FLAGS_SET(hr->mask, USER_RECORD_SECRET)) {
r = user_record_clone(hr, USER_RECORD_EXTRACT_SECRET, &saved_secret);
if (r < 0)
return r;
secret = saved_secret;
}
r = manager_verify_user_record(h->manager, hr);
switch (r) {
case USER_RECORD_UNSIGNED:
if (h->signed_locally <= 0) /* If the existing record is not owned by us, don't accept an
* unsigned new record. i.e. only implicitly sign new records
* that where previously signed by us too. */
return sd_bus_error_setf(error, BUS_ERROR_HOME_RECORD_SIGNED, "Home %s is signed and cannot be modified locally.", h->user_name);
/* The updated record is not signed, then do so now */
r = manager_sign_user_record(h->manager, hr, &signed_hr, error);
if (r < 0)
return r;
hr = signed_hr;
break;
case USER_RECORD_SIGNED_EXCLUSIVE:
case USER_RECORD_SIGNED:
case USER_RECORD_FOREIGN:
/* Has already been signed. Great! */
break;
case -ENOKEY:
default:
return r;
}
r = user_record_extend_with_binding(hr, h->record, USER_RECORD_LOAD_MASK_SECRET, &new_hr);
if (r < 0)
return r;
if (c == 0) {
/* different payload but same lastChangeUSec field? That's not cool! */
r = user_record_masked_equal(new_hr, h->record, USER_RECORD_REGULAR|USER_RECORD_PRIVILEGED|USER_RECORD_PER_MACHINE);
if (r < 0)
return r;
if (r == 0)
return sd_bus_error_setf(error, BUS_ERROR_HOME_RECORD_MISMATCH, "Home record different but timestamp remained the same, refusing.");
}
r = home_start_work(h, verb, new_hr, secret);
if (r < 0)
return r;
return 0;
}
int home_update(Home *h, UserRecord *hr, sd_bus_error *error) {
HomeState state;
int r;
assert(h);
assert(hr);
state = home_get_state(h);
switch (state) {
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);
case HOME_LOCKED:
return sd_bus_error_setf(error, BUS_ERROR_HOME_LOCKED, "Home %s is currently locked.", h->user_name);
case HOME_INACTIVE:
case HOME_DIRTY:
case HOME_ACTIVE:
break;
default:
return sd_bus_error_setf(error, BUS_ERROR_HOME_BUSY, "An operation on home %s is currently being executed.", h->user_name);
}
r = home_ratelimit(h, error);
if (r < 0)
return r;
r = home_update_internal(h, "update", hr, NULL, error);
if (r < 0)
return r;
home_set_state(h, state == HOME_ACTIVE ? HOME_UPDATING_WHILE_ACTIVE : HOME_UPDATING);
return 0;
}
int home_resize(Home *h, uint64_t disk_size, UserRecord *secret, sd_bus_error *error) {
_cleanup_(user_record_unrefp) UserRecord *c = NULL;
HomeState state;
int r;
assert(h);
state = home_get_state(h);
switch (state) {
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);
case HOME_LOCKED:
return sd_bus_error_setf(error, BUS_ERROR_HOME_LOCKED, "Home %s is currently locked.", h->user_name);
case HOME_INACTIVE:
case HOME_DIRTY:
case HOME_ACTIVE:
break;
default:
return sd_bus_error_setf(error, BUS_ERROR_HOME_BUSY, "An operation on home %s is currently being executed.", h->user_name);
}
r = home_ratelimit(h, error);
if (r < 0)
return r;
if (disk_size == UINT64_MAX || disk_size == h->record->disk_size) {
if (h->record->disk_size == UINT64_MAX)
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "No disk size to resize to specified.");
c = user_record_ref(h->record); /* Shortcut if size is unspecified or matches the record */
} else {
_cleanup_(user_record_unrefp) UserRecord *signed_c = NULL;
if (h->signed_locally <= 0) /* Don't allow changing of records not signed only by us */
return sd_bus_error_setf(error, BUS_ERROR_HOME_RECORD_SIGNED, "Home %s is signed and cannot be modified locally.", h->user_name);
r = user_record_clone(h->record, USER_RECORD_LOAD_REFUSE_SECRET, &c);
if (r < 0)
return r;
r = user_record_set_disk_size(c, disk_size);
if (r == -ERANGE)
return sd_bus_error_setf(error, BUS_ERROR_BAD_HOME_SIZE, "Requested size for home %s out of acceptable range.", h->user_name);
if (r < 0)
return r;
r = user_record_update_last_changed(c, false);
if (r == -ECHRNG)
return sd_bus_error_setf(error, BUS_ERROR_HOME_RECORD_MISMATCH, "Record last change time of %s is newer than current time, cannot update.", h->user_name);
if (r < 0)
return r;
r = manager_sign_user_record(h->manager, c, &signed_c, error);
if (r < 0)
return r;
user_record_unref(c);
c = TAKE_PTR(signed_c);
}
r = home_update_internal(h, "resize", c, secret, error);
if (r < 0)
return r;
home_set_state(h, state == HOME_ACTIVE ? HOME_RESIZING_WHILE_ACTIVE : HOME_RESIZING);
return 0;
}
static int home_may_change_password(
Home *h,
sd_bus_error *error) {
int r;
assert(h);
r = user_record_test_password_change_required(h->record);
if (IN_SET(r, -EKEYREVOKED, -EOWNERDEAD, -EKEYEXPIRED, -ESTALE))
return 0; /* expired in some form, but changing is allowed */
if (IN_SET(r, -EKEYREJECTED, -EROFS))
return sd_bus_error_setf(error, SD_BUS_ERROR_ACCESS_DENIED, "Expiration settings of account %s do not allow changing of password.", h->user_name);
if (r < 0)
return log_error_errno(r, "Failed to test password expiry: %m");
return 0; /* not expired */
}
int home_passwd(Home *h,
UserRecord *new_secret,
UserRecord *old_secret,
sd_bus_error *error) {
_cleanup_(user_record_unrefp) UserRecord *c = NULL, *merged_secret = NULL, *signed_c = NULL;
HomeState state;
int r;
assert(h);
if (h->signed_locally <= 0) /* Don't allow changing of records not signed only by us */
return sd_bus_error_setf(error, BUS_ERROR_HOME_RECORD_SIGNED, "Home %s is signed and cannot be modified locally.", h->user_name);
state = home_get_state(h);
switch (state) {
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);
case HOME_LOCKED:
return sd_bus_error_setf(error, BUS_ERROR_HOME_LOCKED, "Home %s is currently locked.", h->user_name);
case HOME_INACTIVE:
case HOME_DIRTY:
case HOME_ACTIVE:
break;
default:
return sd_bus_error_setf(error, BUS_ERROR_HOME_BUSY, "An operation on home %s is currently being executed.", h->user_name);
}
r = home_ratelimit(h, error);
if (r < 0)
return r;
r = home_may_change_password(h, error);
if (r < 0)
return r;
r = user_record_clone(h->record, USER_RECORD_LOAD_REFUSE_SECRET, &c);
if (r < 0)
return r;
merged_secret = user_record_new();
if (!merged_secret)
return -ENOMEM;
r = user_record_merge_secret(merged_secret, old_secret);
if (r < 0)
return r;
r = user_record_merge_secret(merged_secret, new_secret);
if (r < 0)
return r;
if (!strv_isempty(new_secret->password)) {
/* Update the password only if one is specified, otherwise let's just reuse the old password
* data. This is useful as a way to propagate updated user records into the LUKS backends
* properly. */
r = user_record_make_hashed_password(c, new_secret->password, /* extend = */ false);
if (r < 0)
return r;
r = user_record_set_password_change_now(c, -1 /* remove */);
if (r < 0)
return r;
}
r = user_record_update_last_changed(c, true);
if (r == -ECHRNG)
return sd_bus_error_setf(error, BUS_ERROR_HOME_RECORD_MISMATCH, "Record last change time of %s is newer than current time, cannot update.", h->user_name);
if (r < 0)
return r;
r = manager_sign_user_record(h->manager, c, &signed_c, error);
if (r < 0)
return r;
if (c->enforce_password_policy == false)
log_debug("Password quality check turned off for account, skipping.");
else {
r = user_record_quality_check_password(c, merged_secret, error);
if (r < 0)
return r;
}
r = home_update_internal(h, "passwd", signed_c, merged_secret, error);
if (r < 0)
return r;
home_set_state(h, state == HOME_ACTIVE ? HOME_PASSWD_WHILE_ACTIVE : HOME_PASSWD);
return 0;
}
int home_unregister(Home *h, sd_bus_error *error) {
int r;
assert(h);
switch (home_get_state(h)) {
case HOME_UNFIXATED:
return sd_bus_error_setf(error, BUS_ERROR_HOME_UNFIXATED, "Home %s is not registered.", h->user_name);
case HOME_LOCKED:
return sd_bus_error_setf(error, BUS_ERROR_HOME_LOCKED, "Home %s is currently locked.", h->user_name);
case HOME_ABSENT:
case HOME_INACTIVE:
case HOME_DIRTY:
break;
case HOME_ACTIVE:
default:
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_unlink_record(h);
if (r < 0)
return r;
/* And destroy the whole entry. The caller needs to be prepared for that. */
h = home_free(h);
return 1;
}
int home_lock(Home *h, sd_bus_error *error) {
int r;
assert(h);
switch (home_get_state(h)) {
case HOME_UNFIXATED:
case HOME_ABSENT:
case HOME_INACTIVE:
case HOME_DIRTY:
return sd_bus_error_setf(error, BUS_ERROR_HOME_NOT_ACTIVE, "Home %s is not active.", h->user_name);
case HOME_LOCKED:
return sd_bus_error_setf(error, BUS_ERROR_HOME_LOCKED, "Home %s is already locked.", h->user_name);
case HOME_ACTIVE:
break;
default:
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);
if (r < 0)
return r;
home_set_state(h, HOME_LOCKING);
return 0;
}
static int home_unlock_internal(Home *h, UserRecord *secret, HomeState for_state, sd_bus_error *error) {
int r;
assert(h);
assert(IN_SET(for_state, HOME_UNLOCKING, HOME_UNLOCKING_FOR_ACQUIRE));
r = home_start_work(h, "unlock", h->record, secret);
if (r < 0)
return r;
home_set_state(h, for_state);
return 0;
}
int home_unlock(Home *h, UserRecord *secret, sd_bus_error *error) {
int r;
assert(h);
r = home_ratelimit(h, error);
if (r < 0)
return r;
switch (home_get_state(h)) {
case HOME_UNFIXATED:
case HOME_ABSENT:
case HOME_INACTIVE:
case HOME_ACTIVE:
case HOME_DIRTY:
return sd_bus_error_setf(error, BUS_ERROR_HOME_NOT_LOCKED, "Home %s is not locked.", h->user_name);
case HOME_LOCKED:
break;
default:
return sd_bus_error_setf(error, BUS_ERROR_HOME_BUSY, "An operation on home %s is currently being executed.", h->user_name);
}
return home_unlock_internal(h, secret, HOME_UNLOCKING, error);
}
HomeState home_get_state(Home *h) {
int r;
assert(h);
/* When the state field is initialized, it counts. */
if (h->state >= 0)
return h->state;
/* Otherwise, let's see if the home directory is mounted. If so, we assume for sure the home
* directory is active */
if (user_record_test_home_directory(h->record) == USER_TEST_MOUNTED)
return HOME_ACTIVE;
/* And if we see the image being gone, we report this as absent */
r = user_record_test_image_path(h->record);
if (r == USER_TEST_ABSENT)
return HOME_ABSENT;
if (r == USER_TEST_DIRTY)
return HOME_DIRTY;
/* And for all other cases we return "inactive". */
return HOME_INACTIVE;
}
void home_process_notify(Home *h, char **l) {
const char *e;
int error;
int r;
assert(h);
e = strv_env_get(l, "ERRNO");
if (!e) {
log_debug("Got notify message lacking ERRNO= field, ignoring.");
return;
}
r = safe_atoi(e, &error);
if (r < 0) {
log_debug_errno(r, "Failed to parse received error number, ignoring: %s", e);
return;
}
if (error <= 0) {
log_debug("Error number is out of range: %i", error);
return;
}
h->worker_error_code = error;
}
int home_killall(Home *h) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_free_ char *unit = NULL;
int r;
assert(h);
if (!uid_is_valid(h->uid))
return 0;
assert(h->uid > 0); /* We never should be UID 0 */
/* Let's kill everything matching the specified UID */
r = safe_fork("(sd-killer)", FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG|FORK_WAIT|FORK_LOG, NULL);
if (r < 0)
return r;
if (r == 0) {
gid_t gid;
/* Child */
gid = user_record_gid(h->record);
if (setresgid(gid, gid, gid) < 0) {
log_error_errno(errno, "Failed to change GID to " GID_FMT ": %m", gid);
_exit(EXIT_FAILURE);
}
if (setgroups(0, NULL) < 0) {
log_error_errno(errno, "Failed to reset auxiliary groups list: %m");
_exit(EXIT_FAILURE);
}
if (setresuid(h->uid, h->uid, h->uid) < 0) {
log_error_errno(errno, "Failed to change UID to " UID_FMT ": %m", h->uid);
_exit(EXIT_FAILURE);
}
if (kill(-1, SIGKILL) < 0) {
log_error_errno(errno, "Failed to kill all processes of UID " UID_FMT ": %m", h->uid);
_exit(EXIT_FAILURE);
}
_exit(EXIT_SUCCESS);
}
/* Let's also kill everything in the user's slice */
if (asprintf(&unit, "user-" UID_FMT ".slice", h->uid) < 0)
return log_oom();
r = sd_bus_call_method(
h->manager->bus,
"org.freedesktop.systemd1",
"/org/freedesktop/systemd1",
"org.freedesktop.systemd1.Manager",
"KillUnit",
&error,
NULL,
"ssi", unit, "all", SIGKILL);
if (r < 0)
log_full_errno(sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_UNIT) ? LOG_DEBUG : LOG_WARNING,
r, "Failed to kill login processes of user, ignoring: %s", bus_error_message(&error, r));
return 1;
}
static int home_get_disk_status_luks(
Home *h,
HomeState state,
uint64_t *ret_disk_size,
uint64_t *ret_disk_usage,
uint64_t *ret_disk_free,
uint64_t *ret_disk_ceiling,
uint64_t *ret_disk_floor) {
uint64_t disk_size = UINT64_MAX, disk_usage = UINT64_MAX, disk_free = UINT64_MAX,
disk_ceiling = UINT64_MAX, disk_floor = UINT64_MAX,
stat_used = UINT64_MAX, fs_size = UINT64_MAX, header_size = 0;
struct statfs sfs;
const char *hd;
int r;
assert(h);
assert(ret_disk_size);
assert(ret_disk_usage);
assert(ret_disk_free);
assert(ret_disk_ceiling);
if (state != HOME_ABSENT) {
const char *ip;
ip = user_record_image_path(h->record);
if (ip) {
struct stat st;
if (stat(ip, &st) < 0)
log_debug_errno(errno, "Failed to stat() %s, ignoring: %m", ip);
else if (S_ISREG(st.st_mode)) {
_cleanup_free_ char *parent = NULL;
disk_size = st.st_size;
stat_used = st.st_blocks * 512;
parent = dirname_malloc(ip);
if (!parent)
return log_oom();
if (statfs(parent, &sfs) < 0)
log_debug_errno(errno, "Failed to statfs() %s, ignoring: %m", parent);
else
disk_ceiling = stat_used + sfs.f_bsize * sfs.f_bavail;
} else if (S_ISBLK(st.st_mode)) {
_cleanup_free_ char *szbuf = NULL;
char p[SYS_BLOCK_PATH_MAX("/size")];
/* Let's read the size off sysfs, so that we don't have to open the device */
xsprintf_sys_block_path(p, "/size", st.st_rdev);
r = read_one_line_file(p, &szbuf);
if (r < 0)
log_debug_errno(r, "Failed to read %s, ignoring: %m", p);
else {
uint64_t sz;
r = safe_atou64(szbuf, &sz);
if (r < 0)
log_debug_errno(r, "Failed to parse %s, ignoring: %s", p, szbuf);
else
disk_size = sz * 512;
}
} else
log_debug("Image path is not a block device or regular file, not able to acquire size.");
}
}
if (!HOME_STATE_IS_ACTIVE(state))
goto finish;
hd = user_record_home_directory(h->record);
if (!hd)
goto finish;
if (statfs(hd, &sfs) < 0) {
log_debug_errno(errno, "Failed to statfs() %s, ignoring: %m", hd);
goto finish;
}
disk_free = sfs.f_bsize * sfs.f_bavail;
fs_size = sfs.f_bsize * sfs.f_blocks;
if (disk_size != UINT64_MAX && disk_size > fs_size)
header_size = disk_size - fs_size;
/* We take a perspective from the user here (as opposed to from the host): the used disk space is the
* difference from the limit and what's free. This makes a difference if sparse mode is not used: in
* that case the image is pre-allocated and thus appears all used from the host PoV but is not used
* up at all yet from the user's PoV.
*
* That said, we use use the stat() reported loopback file size as upper boundary: our footprint can
* never be larger than what we take up on the lowest layers. */
if (disk_size != UINT64_MAX && disk_size > disk_free) {
disk_usage = disk_size - disk_free;
if (stat_used != UINT64_MAX && disk_usage > stat_used)
disk_usage = stat_used;
} else
disk_usage = stat_used;
/* If we have the magic, determine floor preferably by magic */
disk_floor = minimal_size_by_fs_magic(sfs.f_type) + header_size;
finish:
/* If we don't know the magic, go by file system name */
if (disk_floor == UINT64_MAX)
disk_floor = minimal_size_by_fs_name(user_record_file_system_type(h->record));
*ret_disk_size = disk_size;
*ret_disk_usage = disk_usage;
*ret_disk_free = disk_free;
*ret_disk_ceiling = disk_ceiling;
*ret_disk_floor = disk_floor;
return 0;
}
static int home_get_disk_status_directory(
Home *h,
HomeState state,
uint64_t *ret_disk_size,
uint64_t *ret_disk_usage,
uint64_t *ret_disk_free,
uint64_t *ret_disk_ceiling,
uint64_t *ret_disk_floor) {
uint64_t disk_size = UINT64_MAX, disk_usage = UINT64_MAX, disk_free = UINT64_MAX,
disk_ceiling = UINT64_MAX, disk_floor = UINT64_MAX;
struct statfs sfs;
struct dqblk req;
const char *path = NULL;
int r;
assert(ret_disk_size);
assert(ret_disk_usage);
assert(ret_disk_free);
assert(ret_disk_ceiling);
assert(ret_disk_floor);
if (HOME_STATE_IS_ACTIVE(state))
path = user_record_home_directory(h->record);
if (!path) {
if (state == HOME_ABSENT)
goto finish;
path = user_record_image_path(h->record);
}
if (!path)
goto finish;
if (statfs(path, &sfs) < 0)
log_debug_errno(errno, "Failed to statfs() %s, ignoring: %m", path);
else {
disk_free = sfs.f_bsize * sfs.f_bavail;
disk_size = sfs.f_bsize * sfs.f_blocks;
/* We don't initialize disk_usage from statfs() data here, since the device is likely not used
* by us alone, and disk_usage should only reflect our own use. */
}
if (IN_SET(h->record->storage, USER_CLASSIC, USER_DIRECTORY, USER_SUBVOLUME)) {
r = btrfs_is_subvol(path);
if (r < 0)
log_debug_errno(r, "Failed to determine whether %s is a btrfs subvolume: %m", path);
else if (r > 0) {
BtrfsQuotaInfo qi;
r = btrfs_subvol_get_subtree_quota(path, 0, &qi);
if (r < 0)
log_debug_errno(r, "Failed to query btrfs subtree quota, ignoring: %m");
else {
disk_usage = qi.referenced;
if (disk_free != UINT64_MAX) {
disk_ceiling = qi.referenced + disk_free;
if (disk_size != UINT64_MAX && disk_ceiling > disk_size)
disk_ceiling = disk_size;
}
if (qi.referenced_max != UINT64_MAX) {
if (disk_size != UINT64_MAX)
disk_size = MIN(qi.referenced_max, disk_size);
else
disk_size = qi.referenced_max;
}
if (disk_size != UINT64_MAX) {
if (disk_size > disk_usage)
disk_free = disk_size - disk_usage;
else
disk_free = 0;
}
}
goto finish;
}
}
if (IN_SET(h->record->storage, USER_CLASSIC, USER_DIRECTORY, USER_FSCRYPT)) {
r = quotactl_path(QCMD_FIXED(Q_GETQUOTA, USRQUOTA), path, h->uid, &req);
if (r < 0) {
if (ERRNO_IS_NOT_SUPPORTED(r)) {
log_debug_errno(r, "No UID quota support on %s.", path);
goto finish;
}
if (r != -ESRCH) {
log_debug_errno(r, "Failed to query disk quota for UID " UID_FMT ": %m", h->uid);
goto finish;
}
disk_usage = 0; /* No record of this user? then nothing was used */
} else {
if (FLAGS_SET(req.dqb_valid, QIF_SPACE) && disk_free != UINT64_MAX) {
disk_ceiling = req.dqb_curspace + disk_free;
if (disk_size != UINT64_MAX && disk_ceiling > disk_size)
disk_ceiling = disk_size;
}
if (FLAGS_SET(req.dqb_valid, QIF_BLIMITS)) {
uint64_t q;
/* Take the minimum of the quota and the available disk space here */
q = req.dqb_bhardlimit * QIF_DQBLKSIZE;
if (disk_size != UINT64_MAX)
disk_size = MIN(disk_size, q);
else
disk_size = q;
}
if (FLAGS_SET(req.dqb_valid, QIF_SPACE)) {
disk_usage = req.dqb_curspace;
if (disk_size != UINT64_MAX) {
if (disk_size > disk_usage)
disk_free = disk_size - disk_usage;
else
disk_free = 0;
}
}
}
}
finish:
*ret_disk_size = disk_size;
*ret_disk_usage = disk_usage;
*ret_disk_free = disk_free;
*ret_disk_ceiling = disk_ceiling;
*ret_disk_floor = disk_floor;
return 0;
}
int home_augment_status(
Home *h,
UserRecordLoadFlags flags,
UserRecord **ret) {
uint64_t disk_size = UINT64_MAX, disk_usage = UINT64_MAX, disk_free = UINT64_MAX, disk_ceiling = UINT64_MAX, disk_floor = UINT64_MAX;
_cleanup_(json_variant_unrefp) JsonVariant *j = NULL, *v = NULL, *m = NULL, *status = NULL;
_cleanup_(user_record_unrefp) UserRecord *ur = NULL;
char ids[SD_ID128_STRING_MAX];
HomeState state;
sd_id128_t id;
int r;
assert(h);
assert(ret);
/* We are supposed to add this, this can't be on hence. */
assert(!FLAGS_SET(flags, USER_RECORD_STRIP_STATUS));
r = sd_id128_get_machine(&id);
if (r < 0)
return r;
state = home_get_state(h);
switch (h->record->storage) {
case USER_LUKS:
r = home_get_disk_status_luks(h, state, &disk_size, &disk_usage, &disk_free, &disk_ceiling, &disk_floor);
if (r < 0)
return r;
break;
case USER_CLASSIC:
case USER_DIRECTORY:
case USER_SUBVOLUME:
case USER_FSCRYPT:
case USER_CIFS:
r = home_get_disk_status_directory(h, state, &disk_size, &disk_usage, &disk_free, &disk_ceiling, &disk_floor);
if (r < 0)
return r;
break;
default:
; /* unset */
}
if (disk_floor == UINT64_MAX || (disk_usage != UINT64_MAX && disk_floor < disk_usage))
disk_floor = disk_usage;
if (disk_floor == UINT64_MAX || disk_floor < USER_DISK_SIZE_MIN)
disk_floor = USER_DISK_SIZE_MIN;
if (disk_ceiling == UINT64_MAX || disk_ceiling > USER_DISK_SIZE_MAX)
disk_ceiling = USER_DISK_SIZE_MAX;
r = json_build(&status,
JSON_BUILD_OBJECT(
JSON_BUILD_PAIR("state", JSON_BUILD_STRING(home_state_to_string(state))),
JSON_BUILD_PAIR("service", JSON_BUILD_STRING("io.systemd.Home")),
JSON_BUILD_PAIR_CONDITION(disk_size != UINT64_MAX, "diskSize", JSON_BUILD_UNSIGNED(disk_size)),
JSON_BUILD_PAIR_CONDITION(disk_usage != UINT64_MAX, "diskUsage", JSON_BUILD_UNSIGNED(disk_usage)),
JSON_BUILD_PAIR_CONDITION(disk_free != UINT64_MAX, "diskFree", JSON_BUILD_UNSIGNED(disk_free)),
JSON_BUILD_PAIR_CONDITION(disk_ceiling != UINT64_MAX, "diskCeiling", JSON_BUILD_UNSIGNED(disk_ceiling)),
JSON_BUILD_PAIR_CONDITION(disk_floor != UINT64_MAX, "diskFloor", JSON_BUILD_UNSIGNED(disk_floor)),
JSON_BUILD_PAIR_CONDITION(h->signed_locally >= 0, "signedLocally", JSON_BUILD_BOOLEAN(h->signed_locally))
));
if (r < 0)
return r;
j = json_variant_ref(h->record->json);
v = json_variant_ref(json_variant_by_key(j, "status"));
m = json_variant_ref(json_variant_by_key(v, sd_id128_to_string(id, ids)));
r = json_variant_filter(&m, STRV_MAKE("diskSize", "diskUsage", "diskFree", "diskCeiling", "diskFloor", "signedLocally"));
if (r < 0)
return r;
r = json_variant_merge(&m, status);
if (r < 0)
return r;
r = json_variant_set_field(&v, ids, m);
if (r < 0)
return r;
r = json_variant_set_field(&j, "status", v);
if (r < 0)
return r;
ur = user_record_new();
if (!ur)
return -ENOMEM;
r = user_record_load(ur, j, flags);
if (r < 0)
return r;
ur->incomplete =
FLAGS_SET(h->record->mask, USER_RECORD_PRIVILEGED) &&
!FLAGS_SET(ur->mask, USER_RECORD_PRIVILEGED);
*ret = TAKE_PTR(ur);
return 0;
}
static int on_home_ref_eof(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
_cleanup_(operation_unrefp) Operation *o = NULL;
Home *h = userdata;
assert(s);
assert(h);
if (h->ref_event_source_please_suspend == s)
h->ref_event_source_please_suspend = sd_event_source_disable_unref(h->ref_event_source_please_suspend);
if (h->ref_event_source_dont_suspend == s)
h->ref_event_source_dont_suspend = sd_event_source_disable_unref(h->ref_event_source_dont_suspend);
if (h->ref_event_source_dont_suspend || h->ref_event_source_please_suspend)
return 0;
log_info("Got notification that all sessions of user %s ended, deactivating automatically.", h->user_name);
o = operation_new(OPERATION_PIPE_EOF, NULL);
if (!o) {
log_oom();
return 0;
}
home_schedule_operation(h, o, NULL);
return 0;
}
int home_create_fifo(Home *h, bool please_suspend) {
_cleanup_close_ int ret_fd = -1;
sd_event_source **ss;
const char *fn, *suffix;
int r;
assert(h);
if (please_suspend) {
suffix = ".please-suspend";
ss = &h->ref_event_source_please_suspend;
} else {
suffix = ".dont-suspend";
ss = &h->ref_event_source_dont_suspend;
}
fn = strjoina("/run/systemd/home/", h->user_name, suffix);
if (!*ss) {
_cleanup_close_ int ref_fd = -1;
(void) mkdir("/run/systemd/home/", 0755);
if (mkfifo(fn, 0600) < 0 && errno != EEXIST)
return log_error_errno(errno, "Failed to create FIFO %s: %m", fn);
ref_fd = open(fn, O_RDONLY|O_CLOEXEC|O_NONBLOCK);
if (ref_fd < 0)
return log_error_errno(errno, "Failed to open FIFO %s for reading: %m", fn);
r = sd_event_add_io(h->manager->event, ss, ref_fd, 0, on_home_ref_eof, h);
if (r < 0)
return log_error_errno(r, "Failed to allocate reference FIFO event source: %m");
(void) sd_event_source_set_description(*ss, "acquire-ref");
r = sd_event_source_set_priority(*ss, SD_EVENT_PRIORITY_IDLE-1);
if (r < 0)
return r;
r = sd_event_source_set_io_fd_own(*ss, true);
if (r < 0)
return log_error_errno(r, "Failed to pass ownership of FIFO event fd to event source: %m");
TAKE_FD(ref_fd);
}
ret_fd = open(fn, O_WRONLY|O_CLOEXEC|O_NONBLOCK);
if (ret_fd < 0)
return log_error_errno(errno, "Failed to open FIFO %s for writing: %m", fn);
return TAKE_FD(ret_fd);
}
static int home_dispatch_acquire(Home *h, Operation *o) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
int (*call)(Home *h, UserRecord *secret, HomeState for_state, sd_bus_error *error) = NULL;
HomeState for_state;
int r;
assert(h);
assert(o);
assert(o->type == OPERATION_ACQUIRE);
switch (home_get_state(h)) {
case HOME_UNFIXATED:
for_state = HOME_FIXATING_FOR_ACQUIRE;
call = home_fixate_internal;
break;
case HOME_ABSENT:
r = sd_bus_error_setf(&error, BUS_ERROR_HOME_ABSENT, "Home %s is currently missing or not plugged in.", h->user_name);
break;
case HOME_INACTIVE:
case HOME_DIRTY:
for_state = HOME_ACTIVATING_FOR_ACQUIRE;
call = home_activate_internal;
break;
case HOME_ACTIVE:
for_state = HOME_AUTHENTICATING_FOR_ACQUIRE;
call = home_authenticate_internal;
break;
case HOME_LOCKED:
for_state = HOME_UNLOCKING_FOR_ACQUIRE;
call = home_unlock_internal;
break;
default:
/* All other cases means we are currently executing an operation, which means the job remains
* pending. */
return 0;
}
assert(!h->current_operation);
if (call) {
r = home_ratelimit(h, &error);
if (r >= 0)
r = call(h, o->secret, for_state, &error);
}
if (r != 0) /* failure or completed */
operation_result(o, r, &error);
else /* ongoing */
h->current_operation = operation_ref(o);
return 1;
}
static int home_dispatch_release(Home *h, Operation *o) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
int r;
assert(h);
assert(o);
assert(o->type == OPERATION_RELEASE);
if (h->ref_event_source_dont_suspend || h->ref_event_source_please_suspend)
/* If there's now a reference again, then let's abort the release attempt */
r = sd_bus_error_setf(&error, BUS_ERROR_HOME_BUSY, "Home %s is currently referenced.", h->user_name);
else {
switch (home_get_state(h)) {
case HOME_UNFIXATED:
case HOME_ABSENT:
case HOME_INACTIVE:
case HOME_DIRTY:
r = 1; /* done */
break;
case HOME_LOCKED:
r = sd_bus_error_setf(&error, BUS_ERROR_HOME_LOCKED, "Home %s is currently locked.", h->user_name);
break;
case HOME_ACTIVE:
r = home_deactivate_internal(h, false, &error);
break;
default:
/* All other cases means we are currently executing an operation, which means the job remains
* pending. */
return 0;
}
}
assert(!h->current_operation);
if (r != 0) /* failure or completed */
operation_result(o, r, &error);
else /* ongoing */
h->current_operation = operation_ref(o);
return 1;
}
static int home_dispatch_lock_all(Home *h, Operation *o) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
int r;
assert(h);
assert(o);
assert(o->type == OPERATION_LOCK_ALL);
switch (home_get_state(h)) {
case HOME_UNFIXATED:
case HOME_ABSENT:
case HOME_INACTIVE:
case HOME_DIRTY:
log_info("Home %s is not active, no locking necessary.", h->user_name);
r = 1; /* done */
break;
case HOME_LOCKED:
log_info("Home %s is already locked.", h->user_name);
r = 1; /* done */
break;
case HOME_ACTIVE:
log_info("Locking home %s.", h->user_name);
r = home_lock(h, &error);
break;
default:
/* All other cases means we are currently executing an operation, which means the job remains
* pending. */
return 0;
}
assert(!h->current_operation);
if (r != 0) /* failure or completed */
operation_result(o, r, &error);
else /* ongoing */
h->current_operation = operation_ref(o);
return 1;
}
static int home_dispatch_deactivate_all(Home *h, Operation *o) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
int r;
assert(h);
assert(o);
assert(o->type == OPERATION_DEACTIVATE_ALL);
switch (home_get_state(h)) {
case HOME_UNFIXATED:
case HOME_ABSENT:
case HOME_INACTIVE:
case HOME_DIRTY:
log_info("Home %s is already deactivated.", h->user_name);
r = 1; /* done */
break;
case HOME_LOCKED:
log_info("Home %s is currently locked, not deactivating.", h->user_name);
r = 1; /* done */
break;
case HOME_ACTIVE:
log_info("Deactivating home %s.", h->user_name);
r = home_deactivate_internal(h, false, &error);
break;
default:
/* All other cases means we are currently executing an operation, which means the job remains
* pending. */
return 0;
}
assert(!h->current_operation);
if (r != 0) /* failure or completed */
operation_result(o, r, &error);
else /* ongoing */
h->current_operation = operation_ref(o);
return 1;
}
static int home_dispatch_pipe_eof(Home *h, Operation *o) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
int r;
assert(h);
assert(o);
assert(o->type == OPERATION_PIPE_EOF);
if (h->ref_event_source_please_suspend || h->ref_event_source_dont_suspend)
return 1; /* Hmm, there's a reference again, let's cancel this */
switch (home_get_state(h)) {
case HOME_UNFIXATED:
case HOME_ABSENT:
case HOME_INACTIVE:
case HOME_DIRTY:
log_info("Home %s already deactivated, no automatic deactivation needed.", h->user_name);
break;
case HOME_DEACTIVATING:
log_info("Home %s is already being deactivated, automatic deactivated unnecessary.", h->user_name);
break;
case HOME_ACTIVE:
r = home_deactivate_internal(h, false, &error);
if (r < 0)
log_warning_errno(r, "Failed to deactivate %s, ignoring: %s", h->user_name, bus_error_message(&error, r));
break;
case HOME_LOCKED:
default:
/* If the device is locked or any operation is being executed, let's leave this pending */
return 0;
}
/* Note that we don't call operation_fail() or operation_success() here, because this kind of
* operation has no message associated with it, and thus there's no need to propagate success. */
assert(!o->message);
return 1;
}
static int home_dispatch_deactivate_force(Home *h, Operation *o) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
int r;
assert(h);
assert(o);
assert(o->type == OPERATION_DEACTIVATE_FORCE);
switch (home_get_state(h)) {
case HOME_UNFIXATED:
case HOME_ABSENT:
case HOME_INACTIVE:
case HOME_DIRTY:
log_debug("Home %s already deactivated, no forced deactivation due to unplug needed.", h->user_name);
break;
case HOME_DEACTIVATING:
log_debug("Home %s is already being deactivated, forced deactivation due to unplug unnecessary.", h->user_name);
break;
case HOME_ACTIVE:
case HOME_LOCKED:
r = home_deactivate_internal(h, true, &error);
if (r < 0)
log_warning_errno(r, "Failed to forcibly deactivate %s, ignoring: %s", h->user_name, bus_error_message(&error, r));
break;
default:
/* If any operation is being executed, let's leave this pending */
return 0;
}
/* Note that we don't call operation_fail() or operation_success() here, because this kind of
* operation has no message associated with it, and thus there's no need to propagate success. */
assert(!o->message);
return 1;
}
static int on_pending(sd_event_source *s, void *userdata) {
Home *h = userdata;
Operation *o;
int r;
assert(s);
assert(h);
o = ordered_set_first(h->pending_operations);
if (o) {
static int (* const operation_table[_OPERATION_MAX])(Home *h, Operation *o) = {
[OPERATION_ACQUIRE] = home_dispatch_acquire,
[OPERATION_RELEASE] = home_dispatch_release,
[OPERATION_LOCK_ALL] = home_dispatch_lock_all,
[OPERATION_DEACTIVATE_ALL] = home_dispatch_deactivate_all,
[OPERATION_PIPE_EOF] = home_dispatch_pipe_eof,
[OPERATION_DEACTIVATE_FORCE] = home_dispatch_deactivate_force,
};
assert(operation_table[o->type]);
r = operation_table[o->type](h, o);
if (r != 0) {
/* The operation completed, let's remove it from the pending list, and exit while
* leaving the event source enabled as it is. */
assert_se(ordered_set_remove(h->pending_operations, o) == o);
operation_unref(o);
return 0;
}
}
/* Nothing to do anymore, let's turn off this event source */
r = sd_event_source_set_enabled(s, SD_EVENT_OFF);
if (r < 0)
return log_error_errno(r, "Failed to disable event source: %m");
return 0;
}
int home_schedule_operation(Home *h, Operation *o, sd_bus_error *error) {
int r;
assert(h);
if (o) {
if (ordered_set_size(h->pending_operations) >= PENDING_OPERATIONS_MAX)
return sd_bus_error_setf(error, BUS_ERROR_TOO_MANY_OPERATIONS, "Too many client operations requested");
r = ordered_set_ensure_allocated(&h->pending_operations, &operation_hash_ops);
if (r < 0)
return r;
r = ordered_set_put(h->pending_operations, o);
if (r < 0)
return r;
operation_ref(o);
}
if (!h->pending_event_source) {
r = sd_event_add_defer(h->manager->event, &h->pending_event_source, on_pending, h);
if (r < 0)
return log_error_errno(r, "Failed to allocate pending defer event source: %m");
(void) sd_event_source_set_description(h->pending_event_source, "pending");
r = sd_event_source_set_priority(h->pending_event_source, SD_EVENT_PRIORITY_IDLE);
if (r < 0)
return r;
}
r = sd_event_source_set_enabled(h->pending_event_source, SD_EVENT_ON);
if (r < 0)
return log_error_errno(r, "Failed to trigger pending event source: %m");
return 0;
}
static int home_get_image_path_seat(Home *h, char **ret) {
_cleanup_(sd_device_unrefp) sd_device *d = NULL;
_cleanup_free_ char *c = NULL;
const char *ip, *seat;
struct stat st;
int r;
assert(h);
if (user_record_storage(h->record) != USER_LUKS)
return -ENXIO;
ip = user_record_image_path(h->record);
if (!ip)
return -ENXIO;
if (!path_startswith(ip, "/dev/"))
return -ENXIO;
if (stat(ip, &st) < 0)
return -errno;
if (!S_ISBLK(st.st_mode))
return -ENOTBLK;
r = sd_device_new_from_devnum(&d, 'b', st.st_rdev);
if (r < 0)
return r;
r = sd_device_get_property_value(d, "ID_SEAT", &seat);
if (r == -ENOENT) /* no property means seat0 */
seat = "seat0";
else if (r < 0)
return r;
c = strdup(seat);
if (!c)
return -ENOMEM;
*ret = TAKE_PTR(c);
return 0;
}
int home_auto_login(Home *h, char ***ret_seats) {
_cleanup_free_ char *seat = NULL, *seat2 = NULL;
assert(h);
assert(ret_seats);
(void) home_get_image_path_seat(h, &seat);
if (h->record->auto_login > 0 && !streq_ptr(seat, "seat0")) {
/* For now, when the auto-login boolean is set for a user, let's make it mean
* "seat0". Eventually we can extend the concept and allow configuration of any kind of seat,
* but let's keep simple initially, most likely the feature is interesting on single-user
* systems anyway, only.
*
* We filter out users marked for auto-login in we know for sure their home directory is
* absent. */
if (user_record_test_image_path(h->record) != USER_TEST_ABSENT) {
seat2 = strdup("seat0");
if (!seat2)
return -ENOMEM;
}
}
if (seat || seat2) {
_cleanup_strv_free_ char **list = NULL;
size_t i = 0;
list = new(char*, 3);
if (!list)
return -ENOMEM;
if (seat)
list[i++] = TAKE_PTR(seat);
if (seat2)
list[i++] = TAKE_PTR(seat2);
list[i] = NULL;
*ret_seats = TAKE_PTR(list);
return 1;
}
*ret_seats = NULL;
return 0;
}
int home_set_current_message(Home *h, sd_bus_message *m) {
assert(h);
if (!m)
return 0;
if (h->current_operation)
return -EBUSY;
h->current_operation = operation_new(OPERATION_IMMEDIATE, m);
if (!h->current_operation)
return -ENOMEM;
return 1;
}
int home_wait_for_worker(Home *h) {
assert(h);
if (h->worker_pid <= 0)
return 0;
log_info("Worker process for home %s is still running while exiting. Waiting for it to finish.", h->user_name);
(void) wait_for_terminate(h->worker_pid, NULL);
(void) hashmap_remove_value(h->manager->homes_by_worker_pid, PID_TO_PTR(h->worker_pid), h);
h->worker_pid = 0;
return 1;
}
static const char* const home_state_table[_HOME_STATE_MAX] = {
[HOME_UNFIXATED] = "unfixated",
[HOME_ABSENT] = "absent",
[HOME_INACTIVE] = "inactive",
[HOME_DIRTY] = "dirty",
[HOME_FIXATING] = "fixating",
[HOME_FIXATING_FOR_ACTIVATION] = "fixating-for-activation",
[HOME_FIXATING_FOR_ACQUIRE] = "fixating-for-acquire",
[HOME_ACTIVATING] = "activating",
[HOME_ACTIVATING_FOR_ACQUIRE] = "activating-for-acquire",
[HOME_DEACTIVATING] = "deactivating",
[HOME_ACTIVE] = "active",
[HOME_LOCKING] = "locking",
[HOME_LOCKED] = "locked",
[HOME_UNLOCKING] = "unlocking",
[HOME_UNLOCKING_FOR_ACQUIRE] = "unlocking-for-acquire",
[HOME_CREATING] = "creating",
[HOME_REMOVING] = "removing",
[HOME_UPDATING] = "updating",
[HOME_UPDATING_WHILE_ACTIVE] = "updating-while-active",
[HOME_RESIZING] = "resizing",
[HOME_RESIZING_WHILE_ACTIVE] = "resizing-while-active",
[HOME_PASSWD] = "passwd",
[HOME_PASSWD_WHILE_ACTIVE] = "passwd-while-active",
[HOME_AUTHENTICATING] = "authenticating",
[HOME_AUTHENTICATING_WHILE_ACTIVE] = "authenticating-while-active",
[HOME_AUTHENTICATING_FOR_ACQUIRE] = "authenticating-for-acquire",
};
DEFINE_STRING_TABLE_LOOKUP(home_state, HomeState);