diff options
Diffstat (limited to 'src/home/homework.c')
-rw-r--r-- | src/home/homework.c | 340 |
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")) |