Systemd/src/home/homework.c
Lennart Poettering c8f145adbb homed: don't insist on authentication against host-copy user record
homed maintains two or three copies of the user's identity record per
home directory: one on the host, one inside the LUKS header, and one
embedded in the home directory.

Previously we'd insist that if a user logs in they have to authenticate
against all three, as a safety feature. This broke logging into
unfixated records however, since in that case the host version is
synthetic and thus does not carry any authentication data.

Let's hence losen the strictness here: accept authentication against
host records that carry no auth data. This should be safe as we know
after all that the second/third record will catch invalid accesses.

Fixes: #15178
2020-05-21 23:39:30 +02:00

1564 lines
58 KiB
C

/* SPDX-License-Identifier: LGPL-2.1+ */
#include <stddef.h>
#include <sys/mount.h>
#include "chown-recursive.h"
#include "copy.h"
#include "fd-util.h"
#include "fileio.h"
#include "fs-util.h"
#include "home-util.h"
#include "homework-cifs.h"
#include "homework-directory.h"
#include "homework-fscrypt.h"
#include "homework-luks.h"
#include "homework-mount.h"
#include "homework-pkcs11.h"
#include "homework.h"
#include "main-func.h"
#include "memory-util.h"
#include "missing_magic.h"
#include "mount-util.h"
#include "path-util.h"
#include "pkcs11-util.h"
#include "rm-rf.h"
#include "stat-util.h"
#include "strv.h"
#include "tmpfile-util.h"
#include "user-util.h"
#include "virt.h"
/* Make sure a bad password always results in a 3s delay, no matter what */
#define BAD_PASSWORD_DELAY_USEC (3 * USEC_PER_SEC)
int user_record_authenticate(
UserRecord *h,
UserRecord *secret,
char ***pkcs11_decrypted_passwords,
bool strict_verify) {
bool need_password = false, need_token = false, need_pin = false, need_protected_authentication_path_permitted = false,
pin_locked = false, pin_incorrect = false, pin_incorrect_few_tries_left = false, pin_incorrect_one_try_left = false;
int r;
assert(h);
assert(secret);
/* 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
* configured PKCS#11 token is around and can unlock the record.
*
* Note that the pkcs11_decrypted_passwords parameter is both an input and and output parameter: it
* is a list of configured, decrypted PKCS#11 passwords. We typically have to call this function
* multiple 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 an potentially
* interactive) PKCS#11 dance for the relevant token again and again. */
/* First, let's see if the supplied plain-text passwords work? */
r = user_record_test_secret(h, secret);
if (r == -ENOKEY) {
log_info_errno(r, "None of the supplied plaintext passwords unlocks the user record's hashed passwords.");
need_password = true;
} else if (r == -ENXIO)
log_debug_errno(r, "User record has no hashed passwords, plaintext passwords not tested.");
else if (r < 0)
return log_error_errno(r, "Failed to validate password of record: %m");
else {
log_info("Provided password unlocks user record.");
return 1;
}
/* Second, 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++) {
#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,
};
char **pp;
/* See if any of the previously calculated passwords work */
STRV_FOREACH(pp, *pkcs11_decrypted_passwords) {
r = test_password_one(data.encrypted_key->hashed_password, *pp);
if (r < 0)
return log_error_errno(r, "Failed to check supplied PKCS#11 password: %m");
if (r > 0) {
log_info("Previously acquired PKCS#11 password unlocks user record.");
return 1;
}
}
r = pkcs11_find_token(data.encrypted_key->uri, pkcs11_callback, &data);
switch (r) {
case -EAGAIN:
need_token = true;
break;
case -ENOANO:
need_pin = true;
break;
case -ERFKILL:
need_protected_authentication_path_permitted = true;
break;
case -EOWNERDEAD:
pin_locked = true;
break;
case -ENOLCK:
pin_incorrect = true;
break;
case -ETOOMANYREFS:
pin_incorrect = pin_incorrect_few_tries_left = true;
break;
case -EUCLEAN:
pin_incorrect = pin_incorrect_few_tries_left = pin_incorrect_one_try_left = true;
break;
default:
if (r < 0)
return r;
r = test_password_one(data.encrypted_key->hashed_password, data.decrypted_password);
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);
log_info("Decrypted password from PKCS#11 security token %s unlocks user record.", data.encrypted_key->uri);
r = strv_extend(pkcs11_decrypted_passwords, data.decrypted_password);
if (r < 0)
return log_oom();
return 1;
}
#else
need_token = true;
break;
#endif
}
/* Ordered by "relevance", i.e. the most "important" or "interesting" error condition is returned. */
if (pin_incorrect_one_try_left)
return -EUCLEAN;
if (pin_incorrect_few_tries_left)
return -ETOOMANYREFS;
if (pin_incorrect)
return -ENOLCK;
if (pin_locked)
return -EOWNERDEAD;
if (need_protected_authentication_path_permitted)
return -ERFKILL;
if (need_pin)
return -ENOANO;
if (need_token)
return -EBADSLT;
if (need_password)
return -ENOKEY;
/* Hmm, this means neither PCKS#11 nor classic hashed passwords were supplied, we cannot authenticate this reasonably */
if (strict_verify)
return log_debug_errno(SYNTHETIC_ERRNO(EKEYREVOKED),
"No hashed passwords and no PKCS#11 tokens defined, cannot authenticate user record, refusing.");
/* If strict verification is off this means we are possibly in the case where we encountered an
* unfixated record, i.e. a synthetic one that accordingly lacks any authentication data. In this
* case, allow the authentication to pass for now, so that the second (or third) authentication level
* (the ones of the user record in the LUKS header or inside the home directory) will then catch
* invalid passwords. The second/third authentication always runs in strict verification mode. */
log_debug("No hashed passwords and no PKCS#11 tokens defined in record, cannot authenticate user record. "
"Deferring to embedded user record.");
return 0;
}
int home_setup_undo(HomeSetup *setup) {
int r = 0, q;
assert(setup);
if (setup->root_fd >= 0) {
if (setup->do_offline_fitrim) {
q = run_fitrim(setup->root_fd);
if (q < 0)
r = q;
}
setup->root_fd = safe_close(setup->root_fd);
}
if (setup->undo_mount) {
q = umount_verbose("/run/systemd/user-home-mount");
if (q < 0)
r = q;
}
if (setup->undo_dm && setup->crypt_device && setup->dm_name) {
q = crypt_deactivate(setup->crypt_device, setup->dm_name);
if (q < 0)
r = q;
}
if (setup->image_fd >= 0) {
if (setup->do_offline_fallocate) {
q = run_fallocate(setup->image_fd, NULL);
if (q < 0)
r = q;
}
setup->image_fd = safe_close(setup->image_fd);
}
setup->undo_mount = false;
setup->undo_dm = false;
setup->do_offline_fitrim = false;
setup->do_offline_fallocate = false;
setup->dm_name = mfree(setup->dm_name);
setup->dm_node = mfree(setup->dm_node);
setup->loop = loop_device_unref(setup->loop);
crypt_free(setup->crypt_device);
setup->crypt_device = NULL;
explicit_bzero_safe(setup->volume_key, setup->volume_key_size);
setup->volume_key = mfree(setup->volume_key);
setup->volume_key_size = 0;
return r;
}
int home_prepare(
UserRecord *h,
bool already_activated,
char ***pkcs11_decrypted_passwords,
HomeSetup *setup,
UserRecord **ret_header_home) {
int r;
assert(h);
assert(setup);
assert(!setup->loop);
assert(!setup->crypt_device);
assert(setup->root_fd < 0);
assert(!setup->undo_dm);
assert(!setup->undo_mount);
/* Makes a home directory accessible (through the root_fd file descriptor, not by path!). */
switch (user_record_storage(h)) {
case USER_LUKS:
return home_prepare_luks(h, already_activated, NULL, pkcs11_decrypted_passwords, setup, ret_header_home);
case USER_SUBVOLUME:
case USER_DIRECTORY:
r = home_prepare_directory(h, already_activated, setup);
break;
case USER_FSCRYPT:
r = home_prepare_fscrypt(h, already_activated, pkcs11_decrypted_passwords, setup);
break;
case USER_CIFS:
r = home_prepare_cifs(h, already_activated, setup);
break;
default:
return log_error_errno(SYNTHETIC_ERRNO(ENOLINK), "Processing home directories of type '%s' currently not supported.", user_storage_to_string(user_record_storage(h)));
}
if (r < 0)
return r;
if (ret_header_home)
*ret_header_home = NULL;
return r;
}
int home_sync_and_statfs(int root_fd, struct statfs *ret) {
assert(root_fd >= 0);
/* Let's sync this to disk, so that the disk space reported by fstatfs() below is accurate (for file
* systems such as btrfs where this is determined lazily). */
if (syncfs(root_fd) < 0)
return log_error_errno(errno, "Failed to synchronize file system: %m");
if (ret)
if (fstatfs(root_fd, ret) < 0)
return log_error_errno(errno, "Failed to statfs() file system: %m");
log_info("Synchronized disk.");
return 0;
}
static int read_identity_file(int root_fd, JsonVariant **ret) {
_cleanup_(fclosep) FILE *identity_file = NULL;
_cleanup_close_ int identity_fd = -1;
unsigned line, column;
int r;
assert(root_fd >= 0);
assert(ret);
identity_fd = openat(root_fd, ".identity", O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW|O_NONBLOCK);
if (identity_fd < 0)
return log_error_errno(errno, "Failed to open .identity file in home directory: %m");
r = fd_verify_regular(identity_fd);
if (r < 0)
return log_error_errno(r, "Embedded identity file is not a regular file, refusing: %m");
identity_file = take_fdopen(&identity_fd, "r");
if (!identity_file)
return log_oom();
r = json_parse_file(identity_file, ".identity", JSON_PARSE_SENSITIVE, ret, &line, &column);
if (r < 0)
return log_error_errno(r, "[.identity:%u:%u] Failed to parse JSON data: %m", line, column);
log_info("Read embedded .identity file.");
return 0;
}
static int write_identity_file(int root_fd, JsonVariant *v, uid_t uid) {
_cleanup_(json_variant_unrefp) JsonVariant *normalized = NULL;
_cleanup_(fclosep) FILE *identity_file = NULL;
_cleanup_close_ int identity_fd = -1;
_cleanup_free_ char *fn = NULL;
int r;
assert(root_fd >= 0);
assert(v);
normalized = json_variant_ref(v);
r = json_variant_normalize(&normalized);
if (r < 0)
log_warning_errno(r, "Failed to normalize user record, ignoring: %m");
r = tempfn_random(".identity", NULL, &fn);
if (r < 0)
return r;
identity_fd = openat(root_fd, fn, O_WRONLY|O_CREAT|O_EXCL|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW, 0600);
if (identity_fd < 0)
return log_error_errno(errno, "Failed to create .identity file in home directory: %m");
identity_file = take_fdopen(&identity_fd, "w");
if (!identity_file) {
r = log_oom();
goto fail;
}
json_variant_dump(normalized, JSON_FORMAT_PRETTY, identity_file, NULL);
r = fflush_and_check(identity_file);
if (r < 0) {
log_error_errno(r, "Failed to write .identity file: %m");
goto fail;
}
if (fchown(fileno(identity_file), uid, uid) < 0) {
log_error_errno(r, "Failed to change ownership of identity file: %m");
goto fail;
}
if (renameat(root_fd, fn, root_fd, ".identity") < 0) {
r = log_error_errno(errno, "Failed to move identity file into place: %m");
goto fail;
}
log_info("Wrote embedded .identity file.");
return 0;
fail:
(void) unlinkat(root_fd, fn, 0);
return r;
}
int home_load_embedded_identity(
UserRecord *h,
int root_fd,
UserRecord *header_home,
UserReconcileMode mode,
char ***pkcs11_decrypted_passwords,
UserRecord **ret_embedded_home,
UserRecord **ret_new_home) {
_cleanup_(user_record_unrefp) UserRecord *embedded_home = NULL, *intermediate_home = NULL, *new_home = NULL;
_cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
int r;
assert(h);
assert(root_fd >= 0);
r = read_identity_file(root_fd, &v);
if (r < 0)
return r;
embedded_home = user_record_new();
if (!embedded_home)
return log_oom();
r = user_record_load(embedded_home, v, USER_RECORD_LOAD_EMBEDDED);
if (r < 0)
return r;
if (!user_record_compatible(h, embedded_home))
return log_error_errno(SYNTHETIC_ERRNO(EREMCHG), "Embedded home record not compatible with host record, refusing.");
/* Insist that credentials the user supplies also unlocks any embedded records. */
r = user_record_authenticate(embedded_home, h, pkcs11_decrypted_passwords, /* strict_verify= */ true);
if (r < 0)
return r;
assert(r > 0); /* Insist that a password was verified */
/* At this point we have three records to deal with:
*
* · 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)
*
* Now we have to reconcile all three, and let the newest one win. */
if (header_home) {
/* Note we relax the requirements here. Instead of insisting that the host record is strictly
* newer, let's also be OK if its equally new. If it is, we'll however insist that the
* embedded record must be newer, so that we update at least one of the two. */
r = user_record_reconcile(h, header_home, mode == USER_RECONCILE_REQUIRE_NEWER ? USER_RECONCILE_REQUIRE_NEWER_OR_EQUAL : mode, &intermediate_home);
if (r == -EREMCHG) /* this was supposed to be checked earlier already, but let's check this again */
return log_error_errno(r, "Identity stored on host and in header don't match, refusing.");
if (r == -ESTALE)
return log_error_errno(r, "Embedded identity record is newer than supplied record, refusing.");
if (r < 0)
return log_error_errno(r, "Failed to reconcile host and header identities: %m");
if (r == USER_RECONCILE_EMBEDDED_WON)
log_info("Reconciling header user identity completed (header version was newer).");
else if (r == USER_RECONCILE_HOST_WON) {
log_info("Reconciling header user identity completed (host version was newer).");
if (mode == USER_RECONCILE_REQUIRE_NEWER) /* Host version is newer than the header
* version, hence we'll update
* something. This means we can relax the
* requirements on the embedded
* identity. */
mode = USER_RECONCILE_REQUIRE_NEWER_OR_EQUAL;
} else {
assert(r == USER_RECONCILE_IDENTICAL);
log_info("Reconciling user identities completed (host and header version were identical).");
}
h = intermediate_home;
}
r = user_record_reconcile(h, embedded_home, mode, &new_home);
if (r == -EREMCHG)
return log_error_errno(r, "Identity stored on host and in home don't match, refusing.");
if (r == -ESTALE)
return log_error_errno(r, "Embedded identity record is equally new or newer than supplied record, refusing.");
if (r < 0)
return log_error_errno(r, "Failed to reconcile host and embedded identities: %m");
if (r == USER_RECONCILE_EMBEDDED_WON)
log_info("Reconciling embedded user identity completed (embedded version was newer).");
else if (r == USER_RECONCILE_HOST_WON)
log_info("Reconciling embedded user identity completed (host version was newer).");
else {
assert(r == USER_RECONCILE_IDENTICAL);
log_info("Reconciling embedded user identity completed (host and embedded version were identical).");
}
if (ret_embedded_home)
*ret_embedded_home = TAKE_PTR(embedded_home);
if (ret_new_home)
*ret_new_home = TAKE_PTR(new_home);
return 0;
}
int home_store_embedded_identity(UserRecord *h, int root_fd, uid_t uid, 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, &embedded);
if (r < 0)
return log_error_errno(r, "Failed to determine new embedded record: %m");
if (old_home && user_record_equal(old_home, embedded)) {
log_debug("Not updating embedded home record.");
return 0;
}
/* The identity has changed, let's update it in the image */
r = write_identity_file(root_fd, embedded->json, h->uid);
if (r < 0)
return r;
return 1;
}
static const char *file_system_type_fd(int fd) {
struct statfs sfs;
assert(fd >= 0);
if (fstatfs(fd, &sfs) < 0) {
log_debug_errno(errno, "Failed to statfs(): %m");
return NULL;
}
if (is_fs_type(&sfs, XFS_SB_MAGIC))
return "xfs";
if (is_fs_type(&sfs, EXT4_SUPER_MAGIC))
return "ext4";
if (is_fs_type(&sfs, BTRFS_SUPER_MAGIC))
return "btrfs";
return NULL;
}
int home_extend_embedded_identity(UserRecord *h, UserRecord *used, HomeSetup *setup) {
int r;
assert(h);
assert(used);
assert(setup);
r = user_record_add_binding(
h,
user_record_storage(used),
user_record_image_path(used),
setup->found_partition_uuid,
setup->found_luks_uuid,
setup->found_fs_uuid,
setup->crypt_device ? crypt_get_cipher(setup->crypt_device) : NULL,
setup->crypt_device ? crypt_get_cipher_mode(setup->crypt_device) : NULL,
setup->crypt_device ? luks_volume_key_size_convert(setup->crypt_device) : UINT64_MAX,
file_system_type_fd(setup->root_fd),
user_record_home_directory(used),
used->uid,
(gid_t) used->uid);
if (r < 0)
return log_error_errno(r, "Failed to update binding in record: %m");
return 0;
}
static int chown_recursive_directory(int root_fd, uid_t uid) {
int r;
assert(root_fd >= 0);
assert(uid_is_valid(uid));
r = fd_chown_recursive(root_fd, uid, (gid_t) uid, 0777);
if (r < 0)
return log_error_errno(r, "Failed to change ownership of files and directories: %m");
if (r == 0)
log_info("Recursive changing of ownership not necessary, skipped.");
else
log_info("Recursive changing of ownership completed.");
return 0;
}
int home_refresh(
UserRecord *h,
HomeSetup *setup,
UserRecord *header_home,
char ***pkcs11_decrypted_passwords,
struct statfs *ret_statfs,
UserRecord **ret_new_home) {
_cleanup_(user_record_unrefp) UserRecord *embedded_home = NULL, *new_home = NULL;
int r;
assert(h);
assert(setup);
assert(ret_new_home);
/* 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, pkcs11_decrypted_passwords, &embedded_home, &new_home);
if (r < 0)
return r;
r = home_store_header_identity_luks(new_home, setup, header_home);
if (r < 0)
return r;
r = home_store_embedded_identity(new_home, setup->root_fd, h->uid, embedded_home);
if (r < 0)
return r;
r = chown_recursive_directory(setup->root_fd, h->uid);
if (r < 0)
return r;
r = home_sync_and_statfs(setup->root_fd, ret_statfs);
if (r < 0)
return r;
*ret_new_home = TAKE_PTR(new_home);
return 0;
}
static int home_activate(UserRecord *h, UserRecord **ret_home) {
_cleanup_(strv_free_erasep) char **pkcs11_decrypted_passwords = NULL;
_cleanup_(user_record_unrefp) UserRecord *new_home = NULL;
int r;
assert(h);
if (!h->user_name)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "User record lacks user name, refusing.");
if (!uid_is_valid(h->uid))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "User record lacks UID, refusing.");
if (!IN_SET(user_record_storage(h), USER_LUKS, USER_DIRECTORY, USER_SUBVOLUME, USER_FSCRYPT, USER_CIFS))
return log_error_errno(SYNTHETIC_ERRNO(ENOTTY), "Activating home directories of type '%s' currently not supported.", user_storage_to_string(user_record_storage(h)));
r = user_record_authenticate(h, h, &pkcs11_decrypted_passwords, /* strict_verify= */ false);
if (r < 0)
return r;
r = user_record_test_home_directory_and_warn(h);
if (r < 0)
return r;
if (r == USER_TEST_MOUNTED)
return log_error_errno(SYNTHETIC_ERRNO(EALREADY), "Home directory %s is already mounted, refusing.", user_record_home_directory(h));
r = user_record_test_image_path_and_warn(h);
if (r < 0)
return r;
if (r == USER_TEST_ABSENT)
return log_error_errno(SYNTHETIC_ERRNO(ENOENT), "Image path %s is missing, refusing.", user_record_image_path(h));
switch (user_record_storage(h)) {
case USER_LUKS:
r = home_activate_luks(h, &pkcs11_decrypted_passwords, &new_home);
if (r < 0)
return r;
break;
case USER_SUBVOLUME:
case USER_DIRECTORY:
case USER_FSCRYPT:
r = home_activate_directory(h, &pkcs11_decrypted_passwords, &new_home);
if (r < 0)
return r;
break;
case USER_CIFS:
r = home_activate_cifs(h, &pkcs11_decrypted_passwords, &new_home);
if (r < 0)
return r;
break;
default:
assert_not_reached("unexpected type");
}
/* Note that the returned object might either be a reference to an updated version of the existing
* home object, or a reference to a newly allocated home object. The caller has to be able to deal
* with both, and consider the old object out-of-date. */
if (user_record_equal(h, new_home)) {
*ret_home = NULL;
return 0; /* no identity change */
}
*ret_home = TAKE_PTR(new_home);
return 1; /* identity updated */
}
static int home_deactivate(UserRecord *h, bool force) {
bool done = false;
int r;
assert(h);
if (!h->user_name)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "User record incomplete, 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), "Deactivating home directories of type '%s' currently not supported.", user_storage_to_string(user_record_storage(h)));
r = user_record_test_home_directory_and_warn(h);
if (r < 0)
return r;
if (r == USER_TEST_MOUNTED) {
if (user_record_storage(h) == USER_LUKS) {
r = home_trim_luks(h);
if (r < 0)
return r;
}
if (umount2(user_record_home_directory(h), UMOUNT_NOFOLLOW | (force ? MNT_FORCE|MNT_DETACH : 0)) < 0)
return log_error_errno(errno, "Failed to unmount %s: %m", user_record_home_directory(h));
log_info("Unmounting completed.");
done = true;
} else
log_info("Directory %s is already unmounted.", user_record_home_directory(h));
if (user_record_storage(h) == USER_LUKS) {
r = home_deactivate_luks(h);
if (r < 0)
return r;
if (r > 0)
done = true;
}
if (!done)
return log_error_errno(SYNTHETIC_ERRNO(ENOEXEC), "Home is not active.");
log_info("Everything completed.");
return 0;
}
static int copy_skel(int root_fd, const char *skel) {
int r;
assert(root_fd >= 0);
r = copy_tree_at(AT_FDCWD, skel, root_fd, ".", UID_INVALID, GID_INVALID, COPY_MERGE|COPY_REPLACE);
if (r == -ENOENT) {
log_info("Skeleton directory %s missing, ignoring.", skel);
return 0;
}
if (r < 0)
return log_error_errno(r, "Failed to copy in %s: %m", skel);
log_info("Copying in %s completed.", skel);
return 0;
}
static int change_access_mode(int root_fd, mode_t m) {
assert(root_fd >= 0);
if (fchmod(root_fd, m) < 0)
return log_error_errno(errno, "Failed to change access mode of top-level directory: %m");
log_info("Changed top-level directory access mode to 0%o.", m);
return 0;
}
int home_populate(UserRecord *h, int dir_fd) {
int r;
assert(h);
assert(dir_fd >= 0);
r = copy_skel(dir_fd, user_record_skeleton_directory(h));
if (r < 0)
return r;
r = home_store_embedded_identity(h, dir_fd, h->uid, NULL);
if (r < 0)
return r;
r = chown_recursive_directory(dir_fd, h->uid);
if (r < 0)
return r;
r = change_access_mode(dir_fd, user_record_access_mode(h));
if (r < 0)
return r;
return 0;
}
static int user_record_compile_effective_passwords(
UserRecord *h,
char ***ret_effective_passwords,
char ***ret_pkcs11_decrypted_passwords) {
_cleanup_(strv_free_erasep) char **effective = NULL, **pkcs11_passwords = NULL;
size_t n;
char **i;
int r;
assert(h);
/* We insist on at least one classic hashed password to be defined in addition to any PKCS#11 one, as
* a safe fallback, but also to simplify the password changing algorithm: there we require providing
* the old literal password only (and do not care for the old PKCS#11 token) */
if (strv_isempty(h->hashed_password))
return log_error_errno(EINVAL, "User record has no hashed passwords, refusing.");
/* Generates the list of plaintext passwords to propagate to LUKS/fscrypt devices, and checks whether
* we have a plaintext password for each hashed one. If we are missing one we'll fail, since we
* couldn't sync fscrypt/LUKS to the login account properly. */
STRV_FOREACH(i, h->hashed_password) {
bool found = false;
char **j;
log_debug("Looking for plaintext password for: %s", *i);
/* Let's scan all provided plaintext passwords */
STRV_FOREACH(j, h->password) {
r = test_password_one(*i, *j);
if (r < 0)
return log_error_errno(r, "Failed to test plain text password: %m");
if (r > 0) {
if (ret_effective_passwords) {
r = strv_extend(&effective, *j);
if (r < 0)
return log_oom();
}
log_debug("Found literal plaintext password.");
found = true;
break;
}
}
if (!found)
return log_error_errno(SYNTHETIC_ERRNO(ENOKEY), "Missing plaintext password for defined hashed password");
}
for (n = 0; n < h->n_pkcs11_encrypted_key; n++) {
#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,
};
r = pkcs11_find_token(data.encrypted_key->uri, pkcs11_callback, &data);
if (r == -EAGAIN)
return -EBADSLT;
if (r < 0)
return r;
r = test_password_one(data.encrypted_key->hashed_password, data.decrypted_password);
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), "Decrypted password from token is not correct, refusing.");
if (ret_effective_passwords) {
r = strv_extend(&effective, data.decrypted_password);
if (r < 0)
return log_oom();
}
if (ret_pkcs11_decrypted_passwords) {
r = strv_extend(&pkcs11_passwords, data.decrypted_password);
if (r < 0)
return log_oom();
}
#else
return -EBADSLT;
#endif
}
if (ret_effective_passwords)
*ret_effective_passwords = TAKE_PTR(effective);
if (ret_pkcs11_decrypted_passwords)
*ret_pkcs11_decrypted_passwords = TAKE_PTR(pkcs11_passwords);
return 0;
}
static int determine_default_storage(UserStorage *ret) {
UserStorage storage = _USER_STORAGE_INVALID;
const char *e;
int r;
assert(ret);
/* homed tells us via an environment variable which default storage to use */
e = getenv("SYSTEMD_HOME_DEFAULT_STORAGE");
if (e) {
storage = user_storage_from_string(e);
if (storage < 0)
log_warning("$SYSTEMD_HOME_DEFAULT_STORAGE set to invalid storage type, ignoring: %s", e);
else {
log_info("Using configured default storage '%s'.", user_storage_to_string(storage));
*ret = storage;
return 0;
}
}
/* When neither user nor admin specified the storage type to use, fix it to be LUKS — unless we run
* in a container where loopback devices and LUKS/DM are not available. Also, if /home is encrypted
* anyway, let's avoid duplicate encryption. Note that we typically default to the assumption of
* "classic" storage for most operations. However, if we create a new home, then let's user LUKS if
* nothing is specified. */
r = detect_container();
if (r < 0)
return log_error_errno(r, "Failed to determine whether we are in a container: %m");
if (r == 0) {
r = path_is_encrypted("/home");
if (r < 0)
log_warning_errno(r, "Failed to determine if /home is encrypted, ignoring: %m");
if (r <= 0) {
log_info("Using automatic default storage of '%s'.", user_storage_to_string(USER_LUKS));
*ret = USER_LUKS;
return 0;
}
log_info("/home is encrypted, not using '%s' storage, in order to avoid double encryption.", user_storage_to_string(USER_LUKS));
} else
log_info("Running in container, not using '%s' storage.", user_storage_to_string(USER_LUKS));
r = path_is_fs_type("/home", BTRFS_SUPER_MAGIC);
if (r < 0)
log_warning_errno(r, "Failed to determine file system of /home, ignoring: %m");
if (r > 0) {
log_info("/home is on btrfs, using '%s' as storage.", user_storage_to_string(USER_SUBVOLUME));
*ret = USER_SUBVOLUME;
} else {
log_info("/home is on simple file system, using '%s' as storage.", user_storage_to_string(USER_DIRECTORY));
*ret = USER_DIRECTORY;
}
return 0;
}
static int home_create(UserRecord *h, UserRecord **ret_home) {
_cleanup_(strv_free_erasep) char **effective_passwords = NULL, **pkcs11_decrypted_passwords = NULL;
_cleanup_(user_record_unrefp) UserRecord *new_home = NULL;
UserStorage new_storage = _USER_STORAGE_INVALID;
const char *new_fs = NULL;
int r;
assert(h);
if (!h->user_name)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "User record lacks name, refusing.");
if (!uid_is_valid(h->uid))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "User record lacks UID, refusing.");
r = user_record_compile_effective_passwords(h, &effective_passwords, &pkcs11_decrypted_passwords);
if (r < 0)
return r;
r = user_record_test_home_directory_and_warn(h);
if (r < 0)
return r;
if (r != USER_TEST_ABSENT)
return log_error_errno(SYNTHETIC_ERRNO(EEXIST), "Home directory %s already exists, refusing.", user_record_home_directory(h));
if (h->storage < 0) {
r = determine_default_storage(&new_storage);
if (r < 0)
return r;
}
if ((h->storage == USER_LUKS ||
(h->storage < 0 && new_storage == USER_LUKS)) &&
!h->file_system_type)
new_fs = getenv("SYSTEMD_HOME_DEFAULT_FILE_SYSTEM_TYPE");
if (new_storage >= 0 || new_fs) {
r = user_record_add_binding(
h,
new_storage,
NULL,
SD_ID128_NULL,
SD_ID128_NULL,
SD_ID128_NULL,
NULL,
NULL,
UINT64_MAX,
new_fs,
NULL,
UID_INVALID,
GID_INVALID);
if (r < 0)
return log_error_errno(r, "Failed to change storage type to LUKS: %m");
}
r = user_record_test_image_path_and_warn(h);
if (r < 0)
return r;
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));
switch (user_record_storage(h)) {
case USER_LUKS:
r = home_create_luks(h, pkcs11_decrypted_passwords, effective_passwords, &new_home);
break;
case USER_DIRECTORY:
case USER_SUBVOLUME:
r = home_create_directory_or_subvolume(h, &new_home);
break;
case USER_FSCRYPT:
r = home_create_fscrypt(h, effective_passwords, &new_home);
break;
case USER_CIFS:
r = home_create_cifs(h, &new_home);
break;
default:
return log_error_errno(SYNTHETIC_ERRNO(ENOTTY),
"Creating home directories of type '%s' currently not supported.", user_storage_to_string(user_record_storage(h)));
}
if (r < 0)
return r;
if (user_record_equal(h, new_home)) {
*ret_home = NULL;
return 0;
}
*ret_home = TAKE_PTR(new_home);
return 1;
}
static int home_remove(UserRecord *h) {
bool deleted = false;
const char *ip, *hd;
int r;
assert(h);
if (!h->user_name)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "User record lacks user name, 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), "Removing home directories of type '%s' currently not supported.", user_storage_to_string(user_record_storage(h)));
hd = user_record_home_directory(h);
r = user_record_test_home_directory_and_warn(h);
if (r < 0)
return r;
if (r == USER_TEST_MOUNTED)
return log_error_errno(SYNTHETIC_ERRNO(EBUSY), "Directory %s is still mounted, refusing.", hd);
assert(hd);
r = user_record_test_image_path_and_warn(h);
if (r < 0)
return r;
ip = user_record_image_path(h);
switch (user_record_storage(h)) {
case USER_LUKS: {
struct stat st;
assert(ip);
if (stat(ip, &st) < 0) {
if (errno != ENOENT)
return log_error_errno(errno, "Failed to stat() %s: %m", ip);
} else {
if (S_ISREG(st.st_mode)) {
if (unlink(ip) < 0) {
if (errno != ENOENT)
return log_error_errno(errno, "Failed to remove %s: %m", ip);
} else
deleted = true;
} else if (S_ISBLK(st.st_mode))
log_info("Not removing file system on block device %s.", ip);
else
return log_error_errno(SYNTHETIC_ERRNO(ENOTBLK), "Image file %s is neither block device, nor regular, refusing removal.", ip);
}
break;
}
case USER_SUBVOLUME:
case USER_DIRECTORY:
case USER_FSCRYPT:
assert(ip);
r = rm_rf(ip, REMOVE_ROOT|REMOVE_PHYSICAL|REMOVE_SUBVOLUME);
if (r < 0) {
if (r != -ENOENT)
return log_warning_errno(r, "Failed to remove %s: %m", ip);
} else
deleted = true;
/* If the image path and the home directory are the same invalidate the home directory, so
* that we don't remove it anymore */
if (path_equal(ip, hd))
hd = NULL;
break;
case USER_CIFS:
/* Nothing else to do here: we won't remove remote stuff. */
log_info("Not removing home directory on remote server.");
break;
default:
assert_not_reached("unknown storage type");
}
if (hd) {
if (rmdir(hd) < 0) {
if (errno != ENOENT)
return log_error_errno(errno, "Failed to remove %s, ignoring: %m", hd);
} else
deleted = true;
}
if (deleted)
log_info("Everything completed.");
else
return log_notice_errno(SYNTHETIC_ERRNO(EALREADY),
"Nothing to remove.");
return 0;
}
static int home_validate_update(UserRecord *h, HomeSetup *setup) {
bool has_mount = false;
int r;
assert(h);
assert(setup);
if (!h->user_name)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "User record lacks user name, refusing.");
if (!uid_is_valid(h->uid))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "User record lacks UID, refusing.");
if (!IN_SET(user_record_storage(h), USER_LUKS, USER_DIRECTORY, USER_SUBVOLUME, USER_FSCRYPT, USER_CIFS))
return log_error_errno(SYNTHETIC_ERRNO(ENOTTY), "Processing home directories of type '%s' currently not supported.", user_storage_to_string(user_record_storage(h)));
r = user_record_test_home_directory_and_warn(h);
if (r < 0)
return r;
has_mount = r == USER_TEST_MOUNTED;
r = user_record_test_image_path_and_warn(h);
if (r < 0)
return r;
if (r == USER_TEST_ABSENT)
return log_error_errno(SYNTHETIC_ERRNO(ENOENT), "Image path %s does not exist", user_record_image_path(h));
switch (user_record_storage(h)) {
case USER_DIRECTORY:
case USER_SUBVOLUME:
case USER_FSCRYPT:
case USER_CIFS:
break;
case USER_LUKS: {
r = home_validate_update_luks(h, setup);
if (r < 0)
return r;
if ((r > 0) != has_mount)
return log_error_errno(SYNTHETIC_ERRNO(EBUSY), "Home mount incompletely set up.");
break;
}
default:
assert_not_reached("unexpected storage type");
}
return has_mount; /* return true if the home record is already active */
}
static int home_update(UserRecord *h, UserRecord **ret) {
_cleanup_(user_record_unrefp) UserRecord *new_home = NULL, *header_home = NULL, *embedded_home = NULL;
_cleanup_(strv_free_erasep) char **pkcs11_decrypted_passwords = NULL;
_cleanup_(home_setup_undo) HomeSetup setup = HOME_SETUP_INIT;
bool already_activated = false;
int r;
assert(h);
assert(ret);
r = user_record_authenticate(h, h, &pkcs11_decrypted_passwords, /* strict_verify= */ true);
if (r < 0)
return r;
assert(r > 0); /* Insist that a password was verified */
r = home_validate_update(h, &setup);
if (r < 0)
return r;
already_activated = r > 0;
r = home_prepare(h, already_activated, &pkcs11_decrypted_passwords, &setup, &header_home);
if (r < 0)
return r;
r = home_load_embedded_identity(h, setup.root_fd, header_home, USER_RECONCILE_REQUIRE_NEWER, &pkcs11_decrypted_passwords, &embedded_home, &new_home);
if (r < 0)
return r;
r = home_store_header_identity_luks(new_home, &setup, header_home);
if (r < 0)
return r;
r = home_store_embedded_identity(new_home, setup.root_fd, h->uid, embedded_home);
if (r < 0)
return r;
r = home_extend_embedded_identity(new_home, h, &setup);
if (r < 0)
return r;
r = home_sync_and_statfs(setup.root_fd, NULL);
if (r < 0)
return r;
r = home_setup_undo(&setup);
if (r < 0)
return r;
log_info("Everything completed.");
*ret = TAKE_PTR(new_home);
return 0;
}
static int home_resize(UserRecord *h, UserRecord **ret) {
_cleanup_(home_setup_undo) HomeSetup setup = HOME_SETUP_INIT;
_cleanup_(strv_free_erasep) char **pkcs11_decrypted_passwords = NULL;
bool already_activated = false;
int r;
assert(h);
assert(ret);
if (h->disk_size == UINT64_MAX)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No target size specified, refusing.");
r = user_record_authenticate(h, h, &pkcs11_decrypted_passwords, /* strict_verify= */ true);
if (r < 0)
return r;
assert(r > 0); /* Insist that a password was verified */
r = home_validate_update(h, &setup);
if (r < 0)
return r;
already_activated = r > 0;
switch (user_record_storage(h)) {
case USER_LUKS:
return home_resize_luks(h, already_activated, &pkcs11_decrypted_passwords, &setup, ret);
case USER_DIRECTORY:
case USER_SUBVOLUME:
case USER_FSCRYPT:
return home_resize_directory(h, already_activated, &pkcs11_decrypted_passwords, &setup, ret);
default:
return log_error_errno(SYNTHETIC_ERRNO(ENOTTY), "Resizing home directories of type '%s' currently not supported.", user_storage_to_string(user_record_storage(h)));
}
}
static int home_passwd(UserRecord *h, UserRecord **ret_home) {
_cleanup_(user_record_unrefp) UserRecord *header_home = NULL, *embedded_home = NULL, *new_home = NULL;
_cleanup_(strv_free_erasep) char **effective_passwords = NULL, **pkcs11_decrypted_passwords = NULL;
_cleanup_(home_setup_undo) HomeSetup setup = HOME_SETUP_INIT;
bool already_activated = false;
int r;
assert(h);
assert(ret_home);
if (!IN_SET(user_record_storage(h), USER_LUKS, USER_DIRECTORY, USER_SUBVOLUME, USER_FSCRYPT))
return log_error_errno(SYNTHETIC_ERRNO(ENOTTY), "Changing password of home directories of type '%s' currently not supported.", user_storage_to_string(user_record_storage(h)));
r = user_record_compile_effective_passwords(h, &effective_passwords, &pkcs11_decrypted_passwords);
if (r < 0)
return r;
r = home_validate_update(h, &setup);
if (r < 0)
return r;
already_activated = r > 0;
r = home_prepare(h, already_activated, &pkcs11_decrypted_passwords, &setup, &header_home);
if (r < 0)
return r;
r = home_load_embedded_identity(h, setup.root_fd, header_home, USER_RECONCILE_REQUIRE_NEWER_OR_EQUAL, &pkcs11_decrypted_passwords, &embedded_home, &new_home);
if (r < 0)
return r;
switch (user_record_storage(h)) {
case USER_LUKS:
r = home_passwd_luks(h, &setup, pkcs11_decrypted_passwords, effective_passwords);
if (r < 0)
return r;
break;
case USER_FSCRYPT:
r = home_passwd_fscrypt(h, &setup, pkcs11_decrypted_passwords, effective_passwords);
if (r < 0)
return r;
break;
default:
break;
}
r = home_store_header_identity_luks(new_home, &setup, header_home);
if (r < 0)
return r;
r = home_store_embedded_identity(new_home, setup.root_fd, h->uid, embedded_home);
if (r < 0)
return r;
r = home_extend_embedded_identity(new_home, h, &setup);
if (r < 0)
return r;
r = home_sync_and_statfs(setup.root_fd, NULL);
if (r < 0)
return r;
r = home_setup_undo(&setup);
if (r < 0)
return r;
log_info("Everything completed.");
*ret_home = TAKE_PTR(new_home);
return 1;
}
static int home_inspect(UserRecord *h, UserRecord **ret_home) {
_cleanup_(user_record_unrefp) UserRecord *header_home = NULL, *new_home = NULL;
_cleanup_(home_setup_undo) HomeSetup setup = HOME_SETUP_INIT;
_cleanup_(strv_free_erasep) char **pkcs11_decrypted_passwords = NULL;
bool already_activated = false;
int r;
assert(h);
assert(ret_home);
r = user_record_authenticate(h, h, &pkcs11_decrypted_passwords, /* strict_verify= */ false);
if (r < 0)
return r;
r = home_validate_update(h, &setup);
if (r < 0)
return r;
already_activated = r > 0;
r = home_prepare(h, already_activated, &pkcs11_decrypted_passwords, &setup, &header_home);
if (r < 0)
return r;
r = home_load_embedded_identity(h, setup.root_fd, header_home, USER_RECONCILE_ANY, &pkcs11_decrypted_passwords, NULL, &new_home);
if (r < 0)
return r;
r = home_extend_embedded_identity(new_home, h, &setup);
if (r < 0)
return r;
r = home_setup_undo(&setup);
if (r < 0)
return r;
log_info("Everything completed.");
*ret_home = TAKE_PTR(new_home);
return 1;
}
static int home_lock(UserRecord *h) {
int r;
assert(h);
if (!h->user_name)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "User record incomplete, refusing.");
if (user_record_storage(h) != USER_LUKS)
return log_error_errno(SYNTHETIC_ERRNO(ENOTTY), "Locking home directories of type '%s' currently not supported.", user_storage_to_string(user_record_storage(h)));
r = user_record_test_home_directory_and_warn(h);
if (r < 0)
return r;
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);
if (r < 0)
return r;
log_info("Everything completed.");
return 1;
}
static int home_unlock(UserRecord *h) {
_cleanup_(strv_free_erasep) char **pkcs11_decrypted_passwords = NULL;
int r;
assert(h);
if (!h->user_name)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "User record incomplete, refusing.");
if (user_record_storage(h) != USER_LUKS)
return log_error_errno(SYNTHETIC_ERRNO(ENOTTY), "Unlocking home directories of type '%s' currently not supported.", user_storage_to_string(user_record_storage(h)));
/* Note that we don't check if $HOME is actually mounted, since we want to avoid disk accesses on
* that mount until we have resumed the device. */
r = user_record_authenticate(h, h, &pkcs11_decrypted_passwords, /* strict_verify= */ false);
if (r < 0)
return r;
r = home_unlock_luks(h, &pkcs11_decrypted_passwords);
if (r < 0)
return r;
log_info("Everything completed.");
return 1;
}
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_(fclosep) FILE *opened_file = NULL;
unsigned line = 0, column = 0;
const char *json_path = NULL;
FILE *json_file;
usec_t start;
int r;
start = now(CLOCK_MONOTONIC);
log_setup_service();
umask(0022);
if (argc < 2 || argc > 3)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "This program takes one or two arguments.");
if (argc > 2) {
json_path = argv[2];
opened_file = fopen(json_path, "re");
if (!opened_file)
return log_error_errno(errno, "Failed to open %s: %m", json_path);
json_file = opened_file;
} else {
json_path = "<stdin>";
json_file = stdin;
}
r = json_parse_file(json_file, json_path, JSON_PARSE_SENSITIVE, &v, &line, &column);
if (r < 0)
return log_error_errno(r, "[%s:%u:%u] Failed to parse JSON data: %m", json_path, line, column);
home = user_record_new();
if (!home)
return log_oom();
r = user_record_load(home, v, USER_RECORD_LOAD_FULL|USER_RECORD_LOG);
if (r < 0)
return r;
/* Well known return values of these operations, that systemd-homed knows and converts to proper D-Bus errors:
*
* EMSGSIZE → file systems of this type cannot be shrunk
* ETXTBSY → file systems of this type can only be shrunk offline
* ERANGE → file system size too small
* ENOLINK → system does not support selected storage backend
* EPROTONOSUPPORT → system does not support selected file system
* ENOTTY → operation not support on this storage
* ESOCKTNOSUPPORT → operation not support on this file system
* ENOKEY → password incorrect (or not sufficient, or not supplied)
* EBADSLT → similar, but PKCS#11 device is defined and might be able to provide password, if it was plugged in which it is not
* ENOANO → suitable PKCS#11 device found, but PIN is missing to unlock it
* ERFKILL → suitable PKCS#11 device found, but OK to ask for on-device interactive authentication not given
* EOWNERDEAD → suitable PKCS#11 device found, but its PIN is locked
* ENOLCK → suitable PKCS#11 device found, but PIN incorrect
* ETOOMANYREFS → suitable PKCS#11 device found, but PIN incorrect, and only few tries left
* EUCLEAN → suitable PKCS#11 device found, but PIN incorrect, and only one try left
* EBUSY → file system is currently active
* ENOEXEC → file system is currently not active
* ENOSPC → not enough disk space for operation
* EKEYREVOKED → user record has not suitable hashed password or pkcs#11 entry, we cannot authenticate
*/
if (streq(argv[1], "activate"))
r = home_activate(home, &new_home);
else if (streq(argv[1], "deactivate"))
r = home_deactivate(home, false);
else if (streq(argv[1], "deactivate-force"))
r = home_deactivate(home, true);
else if (streq(argv[1], "create"))
r = home_create(home, &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"))
r = home_resize(home, &new_home);
else if (streq(argv[1], "passwd"))
r = home_passwd(home, &new_home);
else if (streq(argv[1], "inspect"))
r = home_inspect(home, &new_home);
else if (streq(argv[1], "lock"))
r = home_lock(home);
else if (streq(argv[1], "unlock"))
r = home_unlock(home);
else
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown verb '%s'.", argv[1]);
if (r == -ENOKEY && !strv_isempty(home->password)) { /* There were passwords specified but they were incorrect */
usec_t end, n, d;
/* Make sure bad password replies always take at least 3s, and if longer multiples of 3s, so
* that it's not clear how long we actually needed for our calculations. */
n = now(CLOCK_MONOTONIC);
assert(n >= start);
d = usec_sub_unsigned(n, start);
if (d > BAD_PASSWORD_DELAY_USEC)
end = start + DIV_ROUND_UP(d, BAD_PASSWORD_DELAY_USEC) * BAD_PASSWORD_DELAY_USEC;
else
end = start + BAD_PASSWORD_DELAY_USEC;
if (n < end)
(void) usleep(usec_sub_unsigned(end, n));
}
if (r < 0)
return r;
/* We always pass the new record back, regardless if it changed or not. This allows our caller to
* prepare a fresh record, send to us, and only if it works use it without having to keep a local
* copy. */
if (new_home)
json_variant_dump(new_home->json, JSON_FORMAT_NEWLINE, stdout, NULL);
return 0;
}
DEFINE_MAIN_FUNCTION(run);