summaryrefslogtreecommitdiffstats
path: root/src/home/homework.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-06-12 03:50:40 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-06-12 03:50:40 +0000
commitfc53809803cd2bc2434e312b19a18fa36776da12 (patch)
treeb4b43bd6538f51965ce32856e9c053d0f90919c8 /src/home/homework.c
parentAdding upstream version 255.5. (diff)
downloadsystemd-fc53809803cd2bc2434e312b19a18fa36776da12.tar.xz
systemd-fc53809803cd2bc2434e312b19a18fa36776da12.zip
Adding upstream version 256.upstream/256
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/home/homework.c')
-rw-r--r--src/home/homework.c340
1 files changed, 255 insertions, 85 deletions
diff --git a/src/home/homework.c b/src/home/homework.c
index 066483e..482db23 100644
--- a/src/home/homework.c
+++ b/src/home/homework.c
@@ -4,13 +4,17 @@
#include <sys/mount.h>
#include "blockdev-util.h"
+#include "bus-unit-util.h"
#include "chown-recursive.h"
#include "copy.h"
+#include "env-util.h"
#include "fd-util.h"
#include "fileio.h"
#include "filesystems.h"
+#include "format-util.h"
#include "fs-util.h"
#include "home-util.h"
+#include "homework-blob.h"
#include "homework-cifs.h"
#include "homework-directory.h"
#include "homework-fido2.h"
@@ -24,6 +28,7 @@
#include "memory-util.h"
#include "missing_magic.h"
#include "mount-util.h"
+#include "parse-util.h"
#include "path-util.h"
#include "recovery-key.h"
#include "rm-rf.h"
@@ -51,6 +56,7 @@ int user_record_authenticate(
assert(h);
assert(secret);
+ assert(cache);
/* Tries to authenticate a user record with the supplied secrets. i.e. checks whether at least one
* supplied plaintext passwords matches a hashed password field of the user record. Or if a
@@ -61,9 +67,25 @@ int user_record_authenticate(
* times over the course of an operation (think: on login we authenticate the host user record, the
* record embedded in the LUKS record and the one embedded in $HOME). Hence we keep a list of
* passwords we already decrypted, so that we don't have to do the (slow and potentially interactive)
- * PKCS#11/FIDO2 dance for the relevant token again and again. */
+ * PKCS#11/FIDO2 dance for the relevant token again and again.
+ *
+ * The 'cache' parameter might also contain the LUKS volume key, loaded from the kernel keyring.
+ * In this case, authentication becomes optional - if a secret section is provided it will be
+ * verified, but if missing then authentication is skipped entirely. Thus, callers should
+ * consider carefully whether it is safe to load the volume key into 'cache' before doing so.
+ * Note that most of the time this is safe, because the home area must be active for the key
+ * to exist in the keyring, and the user would have had to authenticate when activating their
+ * home area; however, for some methods (i.e. ChangePassword, Authenticate) it makes more sense
+ * to force re-authentication. */
+
+ /* First, let's see if we already have a volume key from the keyring */
+ if (cache->volume_key &&
+ json_variant_is_blank_object(json_variant_by_key(secret->json, "secret"))) {
+ log_info("LUKS volume key from keyring unlocks user record.");
+ return 1;
+ }
- /* First, let's see if the supplied plain-text passwords work? */
+ /* Next, let's see if the supplied plain-text passwords work? */
r = user_record_test_password(h, secret);
if (r == -ENOKEY)
need_password = true;
@@ -96,10 +118,10 @@ int user_record_authenticate(
else
log_info("None of the supplied plaintext passwords unlock the user record's hashed recovery keys.");
- /* Second, test cached PKCS#11 passwords */
- for (size_t n = 0; n < h->n_pkcs11_encrypted_key; n++)
+ /* Next, test cached PKCS#11 passwords */
+ FOREACH_ARRAY(i, h->pkcs11_encrypted_key, h->n_pkcs11_encrypted_key)
STRV_FOREACH(pp, cache->pkcs11_passwords) {
- r = test_password_one(h->pkcs11_encrypted_key[n].hashed_password, *pp);
+ r = test_password_one(i->hashed_password, *pp);
if (r < 0)
return log_error_errno(r, "Failed to check supplied PKCS#11 password: %m");
if (r > 0) {
@@ -108,11 +130,11 @@ int user_record_authenticate(
}
}
- /* Third, test cached FIDO2 passwords */
- for (size_t n = 0; n < h->n_fido2_hmac_salt; n++)
+ /* Next, test cached FIDO2 passwords */
+ FOREACH_ARRAY(i, h->fido2_hmac_salt, h->n_fido2_hmac_salt)
/* See if any of the previously calculated passwords work */
STRV_FOREACH(pp, cache->fido2_passwords) {
- r = test_password_one(h->fido2_hmac_salt[n].hashed_password, *pp);
+ r = test_password_one(i->hashed_password, *pp);
if (r < 0)
return log_error_errno(r, "Failed to check supplied FIDO2 password: %m");
if (r > 0) {
@@ -121,13 +143,13 @@ int user_record_authenticate(
}
}
- /* Fourth, let's see if any of the PKCS#11 security tokens are plugged in and help us */
- for (size_t n = 0; n < h->n_pkcs11_encrypted_key; n++) {
+ /* Next, let's see if any of the PKCS#11 security tokens are plugged in and help us */
+ FOREACH_ARRAY(i, h->pkcs11_encrypted_key, h->n_pkcs11_encrypted_key) {
#if HAVE_P11KIT
_cleanup_(pkcs11_callback_data_release) struct pkcs11_callback_data data = {
.user_record = h,
.secret = secret,
- .encrypted_key = h->pkcs11_encrypted_key + n,
+ .encrypted_key = i,
};
r = pkcs11_find_token(data.encrypted_key->uri, pkcs11_callback, &data);
@@ -161,7 +183,9 @@ int user_record_authenticate(
if (r < 0)
return log_error_errno(r, "Failed to test PKCS#11 password: %m");
if (r == 0)
- return log_error_errno(SYNTHETIC_ERRNO(EPERM), "Configured PKCS#11 security token %s does not decrypt encrypted key correctly.", data.encrypted_key->uri);
+ return log_error_errno(SYNTHETIC_ERRNO(EPERM),
+ "Configured PKCS#11 security token %s does not decrypt encrypted key correctly.",
+ data.encrypted_key->uri);
log_info("Decrypted password from PKCS#11 security token %s unlocks user record.", data.encrypted_key->uri);
@@ -177,12 +201,12 @@ int user_record_authenticate(
#endif
}
- /* Fifth, let's see if any of the FIDO2 security tokens are plugged in and help us */
- for (size_t n = 0; n < h->n_fido2_hmac_salt; n++) {
+ /* Next, let's see if any of the FIDO2 security tokens are plugged in and help us */
+ FOREACH_ARRAY(i, h->fido2_hmac_salt, h->n_fido2_hmac_salt) {
#if HAVE_LIBFIDO2
_cleanup_(erase_and_freep) char *decrypted_password = NULL;
- r = fido2_use_token(h, secret, h->fido2_hmac_salt + n, &decrypted_password);
+ r = fido2_use_token(h, secret, i, &decrypted_password);
switch (r) {
case -EAGAIN:
need_token = true;
@@ -209,11 +233,12 @@ int user_record_authenticate(
if (r < 0)
return r;
- r = test_password_one(h->fido2_hmac_salt[n].hashed_password, decrypted_password);
+ r = test_password_one(i->hashed_password, decrypted_password);
if (r < 0)
return log_error_errno(r, "Failed to test FIDO2 password: %m");
if (r == 0)
- return log_error_errno(SYNTHETIC_ERRNO(EPERM), "Configured FIDO2 security token does not decrypt encrypted key correctly.");
+ return log_error_errno(SYNTHETIC_ERRNO(EPERM),
+ "Configured FIDO2 security token does not decrypt encrypted key correctly.");
log_info("Decrypted password from FIDO2 security token unlocks user record.");
@@ -275,10 +300,10 @@ static void drop_caches_now(void) {
int r;
/* Drop file system caches now. See https://docs.kernel.org/admin-guide/sysctl/vm.html
- * for details. We write "2" into /proc/sys/vm/drop_caches to ensure dentries/inodes are flushed, but
+ * for details. We write "3" into /proc/sys/vm/drop_caches to ensure dentries/inodes are flushed, but
* not more. */
- r = write_string_file("/proc/sys/vm/drop_caches", "2\n", WRITE_STRING_FILE_DISABLE_BUFFER);
+ r = write_string_file("/proc/sys/vm/drop_caches", "3\n", WRITE_STRING_FILE_DISABLE_BUFFER);
if (r < 0)
log_warning_errno(r, "Failed to drop caches, ignoring: %m");
else
@@ -354,6 +379,9 @@ static int keyring_flush(UserRecord *h) {
assert(h);
+ if (user_record_storage(h) == USER_FSCRYPT)
+ (void) home_flush_keyring_fscrypt(h);
+
name = strjoin("homework-user-", h->user_name);
if (!name)
return log_oom();
@@ -638,7 +666,7 @@ int home_load_embedded_identity(
*
* · The record we got passed from the host
* · The record included in the LUKS header (only if LUKS is used)
- * · The record in the home directory itself (~.identity)
+ * · The record in the home directory itself (~/.identity)
*
* Now we have to reconcile all three, and let the newest one win. */
@@ -695,16 +723,15 @@ int home_load_embedded_identity(
if (ret_new_home)
*ret_new_home = TAKE_PTR(new_home);
- return 0;
+ return r; /* We pass along who won the reconciliation */
}
-int home_store_embedded_identity(UserRecord *h, int root_fd, uid_t uid, UserRecord *old_home) {
+int home_store_embedded_identity(UserRecord *h, int root_fd, UserRecord *old_home) {
_cleanup_(user_record_unrefp) UserRecord *embedded = NULL;
int r;
assert(h);
assert(root_fd >= 0);
- assert(uid_is_valid(uid));
r = user_record_clone(h, USER_RECORD_EXTRACT_EMBEDDED|USER_RECORD_PERMISSIVE, &embedded);
if (r < 0)
@@ -827,7 +854,7 @@ int home_refresh(
UserRecord **ret_new_home) {
_cleanup_(user_record_unrefp) UserRecord *embedded_home = NULL, *new_home = NULL;
- int r;
+ int r, reconciled;
assert(h);
assert(setup);
@@ -836,9 +863,9 @@ int home_refresh(
/* When activating a home directory, does the identity work: loads the identity from the $HOME
* directory, reconciles it with our idea, chown()s everything. */
- r = home_load_embedded_identity(h, setup->root_fd, header_home, USER_RECONCILE_ANY, cache, &embedded_home, &new_home);
- if (r < 0)
- return r;
+ reconciled = home_load_embedded_identity(h, setup->root_fd, header_home, USER_RECONCILE_ANY, cache, &embedded_home, &new_home);
+ if (reconciled < 0)
+ return reconciled;
r = home_maybe_shift_uid(h, flags, setup);
if (r < 0)
@@ -848,7 +875,11 @@ int home_refresh(
if (r < 0)
return r;
- r = home_store_embedded_identity(new_home, setup->root_fd, h->uid, embedded_home);
+ r = home_store_embedded_identity(new_home, setup->root_fd, embedded_home);
+ if (r < 0)
+ return r;
+
+ r = home_reconcile_blob_dirs(new_home, setup->root_fd, reconciled);
if (r < 0)
return r;
@@ -1031,12 +1062,13 @@ static int home_deactivate(UserRecord *h, bool force) {
return 0;
}
-static int copy_skel(int root_fd, const char *skel) {
+static int copy_skel(UserRecord *h, int root_fd, const char *skel) {
int r;
+ assert(h);
assert(root_fd >= 0);
- r = copy_tree_at(AT_FDCWD, skel, root_fd, ".", UID_INVALID, GID_INVALID, COPY_MERGE|COPY_REPLACE, NULL, NULL);
+ r = copy_tree_at(AT_FDCWD, skel, root_fd, ".", h->uid, h->gid, COPY_MERGE|COPY_REPLACE, NULL, NULL);
if (r == -ENOENT) {
log_info("Skeleton directory %s missing, ignoring.", skel);
return 0;
@@ -1064,11 +1096,15 @@ int home_populate(UserRecord *h, int dir_fd) {
assert(h);
assert(dir_fd >= 0);
- r = copy_skel(dir_fd, user_record_skeleton_directory(h));
+ r = copy_skel(h, dir_fd, user_record_skeleton_directory(h));
+ if (r < 0)
+ return r;
+
+ r = home_store_embedded_identity(h, dir_fd, NULL);
if (r < 0)
return r;
- r = home_store_embedded_identity(h, dir_fd, h->uid, NULL);
+ r = home_reconcile_blob_dirs(h, dir_fd, USER_RECONCILE_HOST_WON);
if (r < 0)
return r;
@@ -1089,7 +1125,6 @@ static int user_record_compile_effective_passwords(
char ***ret_effective_passwords) {
_cleanup_strv_free_erase_ char **effective = NULL;
- size_t n;
int r;
assert(h);
@@ -1134,17 +1169,16 @@ static int user_record_compile_effective_passwords(
return log_error_errno(SYNTHETIC_ERRNO(ENOKEY), "Missing plaintext password for defined hashed password");
}
- for (n = 0; n < h->n_recovery_key; n++) {
+ FOREACH_ARRAY(i, h->recovery_key, h->n_recovery_key) {
bool found = false;
- log_debug("Looking for plaintext recovery key for: %s", h->recovery_key[n].hashed_password);
+ log_debug("Looking for plaintext recovery key for: %s", i->hashed_password);
STRV_FOREACH(j, h->password) {
_cleanup_(erase_and_freep) char *mangled = NULL;
const char *p;
- if (streq(h->recovery_key[n].type, "modhex64")) {
-
+ if (streq(i->type, "modhex64")) {
r = normalize_recovery_key(*j, &mangled);
if (r == -EINVAL) /* Not properly formatted, probably a regular password. */
continue;
@@ -1155,7 +1189,7 @@ static int user_record_compile_effective_passwords(
} else
p = *j;
- r = test_password_one(h->recovery_key[n].hashed_password, p);
+ r = test_password_one(i->hashed_password, p);
if (r < 0)
return log_error_errno(r, "Failed to test plaintext recovery key: %m");
if (r > 0) {
@@ -1172,15 +1206,16 @@ static int user_record_compile_effective_passwords(
}
if (!found)
- return log_error_errno(SYNTHETIC_ERRNO(EREMOTEIO), "Missing plaintext recovery key for defined recovery key");
+ return log_error_errno(SYNTHETIC_ERRNO(EREMOTEIO),
+ "Missing plaintext recovery key for defined recovery key.");
}
- for (n = 0; n < h->n_pkcs11_encrypted_key; n++) {
+ FOREACH_ARRAY(i, h->pkcs11_encrypted_key, h->n_pkcs11_encrypted_key) {
#if HAVE_P11KIT
_cleanup_(pkcs11_callback_data_release) struct pkcs11_callback_data data = {
.user_record = h,
.secret = h,
- .encrypted_key = h->pkcs11_encrypted_key + n,
+ .encrypted_key = i,
};
r = pkcs11_find_token(data.encrypted_key->uri, pkcs11_callback, &data);
@@ -1209,19 +1244,20 @@ static int user_record_compile_effective_passwords(
#endif
}
- for (n = 0; n < h->n_fido2_hmac_salt; n++) {
+ FOREACH_ARRAY(i, h->fido2_hmac_salt, h->n_fido2_hmac_salt) {
#if HAVE_LIBFIDO2
_cleanup_(erase_and_freep) char *decrypted_password = NULL;
- r = fido2_use_token(h, h, h->fido2_hmac_salt + n, &decrypted_password);
+ r = fido2_use_token(h, h, i, &decrypted_password);
if (r < 0)
return r;
- r = test_password_one(h->fido2_hmac_salt[n].hashed_password, decrypted_password);
+ r = test_password_one(i->hashed_password, decrypted_password);
if (r < 0)
return log_error_errno(r, "Failed to test FIDO2 password: %m");
if (r == 0)
- return log_error_errno(SYNTHETIC_ERRNO(EPERM), "Decrypted password from token is not correct, refusing.");
+ return log_error_errno(SYNTHETIC_ERRNO(EPERM),
+ "Decrypted password from token is not correct, refusing.");
if (ret_effective_passwords) {
r = strv_extend(&effective, decrypted_password);
@@ -1306,7 +1342,7 @@ static int determine_default_storage(UserStorage *ret) {
return 0;
}
-static int home_create(UserRecord *h, UserRecord **ret_home) {
+static int home_create(UserRecord *h, Hashmap *blobs, UserRecord **ret_home) {
_cleanup_strv_free_erase_ char **effective_passwords = NULL;
_cleanup_(home_setup_done) HomeSetup setup = HOME_SETUP_INIT;
_cleanup_(user_record_unrefp) UserRecord *new_home = NULL;
@@ -1368,6 +1404,10 @@ static int home_create(UserRecord *h, UserRecord **ret_home) {
if (!IN_SET(r, USER_TEST_ABSENT, USER_TEST_UNDEFINED, USER_TEST_MAYBE))
return log_error_errno(SYNTHETIC_ERRNO(EEXIST), "Image path %s already exists, refusing.", user_record_image_path(h));
+ r = home_apply_new_blob_dir(h, blobs);
+ if (r < 0)
+ return r;
+
switch (user_record_storage(h)) {
case USER_LUKS:
@@ -1519,20 +1559,32 @@ static int home_remove(UserRecord *h) {
return 0;
}
-static int home_validate_update(UserRecord *h, HomeSetup *setup, HomeSetupFlags *flags) {
- bool has_mount = false;
- int r;
-
+static int home_basic_validate_update(UserRecord *h) {
assert(h);
- assert(setup);
if (!h->user_name)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "User record lacks user name, refusing.");
+
if (!uid_is_valid(h->uid))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "User record lacks UID, refusing.");
+
if (!IN_SET(user_record_storage(h), USER_LUKS, USER_DIRECTORY, USER_SUBVOLUME, USER_FSCRYPT, USER_CIFS))
return log_error_errno(SYNTHETIC_ERRNO(ENOTTY), "Processing home directories of type '%s' currently not supported.", user_storage_to_string(user_record_storage(h)));
+ return 0;
+}
+
+static int home_validate_update(UserRecord *h, HomeSetup *setup, HomeSetupFlags *flags) {
+ bool has_mount = false;
+ int r;
+
+ assert(h);
+ assert(setup);
+
+ r = home_basic_validate_update(h);
+ if (r < 0)
+ return r;
+
r = user_record_test_home_directory_and_warn(h);
if (r < 0)
return r;
@@ -1573,25 +1625,48 @@ static int home_validate_update(UserRecord *h, HomeSetup *setup, HomeSetupFlags
return has_mount; /* return true if the home record is already active */
}
-static int home_update(UserRecord *h, UserRecord **ret) {
+static int home_update(UserRecord *h, Hashmap *blobs, UserRecord **ret) {
_cleanup_(user_record_unrefp) UserRecord *new_home = NULL, *header_home = NULL, *embedded_home = NULL;
_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);
- r = user_record_authenticate(h, h, &cache, /* strict_verify= */ true);
+ offline = getenv_bool("SYSTEMD_HOMEWORK_UPDATE_OFFLINE") > 0;
+
+ if (!offline) {
+ password_cache_load_keyring(h, &cache);
+
+ 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;
- assert(r > 0); /* Insist that a password was verified */
- r = home_validate_update(h, &setup, &flags);
+ r = home_apply_new_blob_dir(h, blobs);
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;
@@ -1608,7 +1683,11 @@ static int home_update(UserRecord *h, UserRecord **ret) {
if (r < 0)
return r;
- r = home_store_embedded_identity(new_home, setup.root_fd, h->uid, embedded_home);
+ r = home_store_embedded_identity(new_home, setup.root_fd, embedded_home);
+ if (r < 0)
+ return r;
+
+ r = home_reconcile_blob_dirs(new_home, setup.root_fd, USER_RECONCILE_HOST_WON);
if (r < 0)
return r;
@@ -1630,7 +1709,7 @@ static int home_update(UserRecord *h, UserRecord **ret) {
return 0;
}
-static int home_resize(UserRecord *h, bool automatic, UserRecord **ret) {
+static int home_resize(UserRecord *h, UserRecord **ret) {
_cleanup_(home_setup_done) HomeSetup setup = HOME_SETUP_INIT;
_cleanup_(password_cache_free) PasswordCache cache = {};
HomeSetupFlags flags = 0;
@@ -1642,25 +1721,16 @@ static int home_resize(UserRecord *h, bool automatic, UserRecord **ret) {
if (h->disk_size == UINT64_MAX)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No target size specified, refusing.");
- if (automatic)
- /* In automatic mode don't want to ask the user for the password, hence load it from the kernel keyring */
- password_cache_load_keyring(h, &cache);
- else {
- /* In manual mode let's ensure the user is fully authenticated */
- r = user_record_authenticate(h, h, &cache, /* strict_verify= */ true);
- if (r < 0)
- return r;
- assert(r > 0); /* Insist that a password was verified */
- }
+ 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 */
- /* In automatic mode let's skip syncing identities, because we can't validate them, since we can't
- * ask the user for reauthentication */
- if (automatic)
- flags |= HOME_SETUP_RESIZE_DONT_SYNC_IDENTITIES;
+ r = home_validate_update(h, &setup, &flags);
+ if (r < 0)
+ return r;
switch (user_record_storage(h)) {
@@ -1683,7 +1753,7 @@ static int home_passwd(UserRecord *h, UserRecord **ret_home) {
_cleanup_(home_setup_done) HomeSetup setup = HOME_SETUP_INIT;
_cleanup_(password_cache_free) PasswordCache cache = {};
HomeSetupFlags flags = 0;
- int r;
+ int r, reconciled;
assert(h);
assert(ret_home);
@@ -1703,9 +1773,9 @@ static int home_passwd(UserRecord *h, UserRecord **ret_home) {
if (r < 0)
return r;
- r = home_load_embedded_identity(h, setup.root_fd, header_home, USER_RECONCILE_REQUIRE_NEWER_OR_EQUAL, &cache, &embedded_home, &new_home);
- if (r < 0)
- return r;
+ reconciled = home_load_embedded_identity(h, setup.root_fd, header_home, USER_RECONCILE_REQUIRE_NEWER_OR_EQUAL, &cache, &embedded_home, &new_home);
+ if (reconciled < 0)
+ return reconciled;
r = home_maybe_shift_uid(h, flags, &setup);
if (r < 0)
@@ -1733,7 +1803,11 @@ static int home_passwd(UserRecord *h, UserRecord **ret_home) {
if (r < 0)
return r;
- r = home_store_embedded_identity(new_home, setup.root_fd, h->uid, embedded_home);
+ r = home_store_embedded_identity(new_home, setup.root_fd, embedded_home);
+ if (r < 0)
+ return r;
+
+ r = home_reconcile_blob_dirs(new_home, setup.root_fd, reconciled);
if (r < 0)
return r;
@@ -1795,6 +1869,38 @@ static int home_inspect(UserRecord *h, UserRecord **ret_home) {
return 1;
}
+static int user_session_freezer(uid_t uid, bool freeze_now, UnitFreezer **ret) {
+ _cleanup_free_ char *unit = NULL;
+ int r;
+
+ assert(uid_is_valid(uid));
+ assert(ret);
+
+ r = getenv_bool("SYSTEMD_HOME_LOCK_FREEZE_SESSION");
+ if (r < 0 && r != -ENXIO)
+ log_warning_errno(r, "Cannot parse value of $SYSTEMD_HOME_LOCK_FREEZE_SESSION, ignoring: %m");
+ else if (r == 0) {
+ if (freeze_now)
+ log_notice("Session remains unfrozen on explicit request ($SYSTEMD_HOME_LOCK_FREEZE_SESSION=0).\n"
+ "This is not recommended, and might result in unexpected behavior including data loss!");
+
+ *ret = NULL;
+ return 0;
+ }
+
+ if (asprintf(&unit, "user-" UID_FMT ".slice", uid) < 0)
+ return log_oom();
+
+ if (freeze_now)
+ r = unit_freezer_new_freeze(unit, ret);
+ else
+ r = unit_freezer_new(unit, ret);
+ if (r < 0)
+ return r;
+
+ return 1;
+}
+
static int home_lock(UserRecord *h) {
_cleanup_(home_setup_done) HomeSetup setup = HOME_SETUP_INIT;
int r;
@@ -1812,10 +1918,23 @@ static int home_lock(UserRecord *h) {
if (r != USER_TEST_MOUNTED)
return log_error_errno(SYNTHETIC_ERRNO(ENOEXEC), "Home directory of %s is not mounted, can't lock.", h->user_name);
- r = home_lock_luks(h, &setup);
+ _cleanup_(unit_freezer_freep) UnitFreezer *f = NULL;
+
+ r = user_session_freezer(h->uid, /* freeze_now= */ true, &f);
if (r < 0)
return r;
+ r = home_lock_luks(h, &setup);
+ if (r < 0) {
+ if (f)
+ (void) unit_freezer_thaw(f);
+
+ return r;
+ }
+
+ /* Explicitly flush any per-user key from the keyring */
+ (void) keyring_flush(h);
+
log_info("Everything completed.");
return 1;
}
@@ -1843,6 +1962,15 @@ static int home_unlock(UserRecord *h) {
if (r < 0)
return r;
+ _cleanup_(unit_freezer_freep) UnitFreezer *f = NULL;
+
+ /* We want to thaw the session only after it's safe to access $HOME */
+ r = user_session_freezer(h->uid, /* freeze_now= */ false, &f);
+ if (r > 0)
+ r = unit_freezer_thaw(f);
+ if (r < 0)
+ return r;
+
log_info("Everything completed.");
return 1;
}
@@ -1851,10 +1979,12 @@ static int run(int argc, char *argv[]) {
_cleanup_(user_record_unrefp) UserRecord *home = NULL, *new_home = NULL;
_cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
_cleanup_fclose_ FILE *opened_file = NULL;
+ _cleanup_hashmap_free_ Hashmap *blobs = NULL;
unsigned line = 0, column = 0;
- const char *json_path = NULL;
+ const char *json_path = NULL, *blob_filename;
FILE *json_file;
usec_t start;
+ JsonVariant *fdmap, *blob_fd_variant;
int r;
start = now(CLOCK_MONOTONIC);
@@ -1885,6 +2015,48 @@ static int run(int argc, char *argv[]) {
if (r < 0)
return log_error_errno(r, "[%s:%u:%u] Failed to parse JSON data: %m", json_path, line, column);
+ fdmap = json_variant_by_key(v, HOMEWORK_BLOB_FDMAP_FIELD);
+ if (fdmap) {
+ r = hashmap_ensure_allocated(&blobs, &blob_fd_hash_ops);
+ if (r < 0)
+ return log_oom();
+
+ JSON_VARIANT_OBJECT_FOREACH(blob_filename, blob_fd_variant, fdmap) {
+ _cleanup_free_ char *filename = NULL;
+ _cleanup_close_ int fd = -EBADF;
+
+ assert(json_variant_is_integer(blob_fd_variant));
+ assert(json_variant_integer(blob_fd_variant) >= 0);
+ assert(json_variant_integer(blob_fd_variant) <= INT_MAX - SD_LISTEN_FDS_START);
+ fd = SD_LISTEN_FDS_START + (int) json_variant_integer(blob_fd_variant);
+
+ if (DEBUG_LOGGING) {
+ _cleanup_free_ char *resolved = NULL;
+ r = fd_get_path(fd, &resolved);
+ log_debug("Got blob from daemon: %s (%d) → %s",
+ blob_filename, fd, resolved ?: STRERROR(r));
+ }
+
+ filename = strdup(blob_filename);
+ if (!filename)
+ return log_oom();
+
+ r = fd_cloexec(fd, true);
+ if (r < 0)
+ return log_error_errno(r, "Failed to enable O_CLOEXEC on blob %s: %m", filename);
+
+ r = hashmap_put(blobs, filename, FD_TO_PTR(fd));
+ if (r < 0)
+ return log_error_errno(r, "Failed to insert blob %s into map: %m", filename);
+ TAKE_PTR(filename); /* Ownership transfers to hashmap */
+ TAKE_FD(fd);
+ }
+
+ r = json_variant_filter(&v, STRV_MAKE(HOMEWORK_BLOB_FDMAP_FIELD));
+ if (r < 0)
+ return log_error_errno(r, "Failed to strip internal fdmap from JSON: %m");
+ }
+
home = user_record_new();
if (!home)
return log_oom();
@@ -1928,15 +2100,13 @@ static int run(int argc, char *argv[]) {
else if (streq(argv[1], "deactivate-force"))
r = home_deactivate(home, true);
else if (streq(argv[1], "create"))
- r = home_create(home, &new_home);
+ r = home_create(home, blobs, &new_home);
else if (streq(argv[1], "remove"))
r = home_remove(home);
else if (streq(argv[1], "update"))
- r = home_update(home, &new_home);
- else if (streq(argv[1], "resize")) /* Resize on user request */
- r = home_resize(home, false, &new_home);
- else if (streq(argv[1], "resize-auto")) /* Automatic resize */
- r = home_resize(home, true, &new_home);
+ r = home_update(home, blobs, &new_home);
+ else if (streq(argv[1], "resize"))
+ r = home_resize(home, &new_home);
else if (streq(argv[1], "passwd"))
r = home_passwd(home, &new_home);
else if (streq(argv[1], "inspect"))