/* SPDX-License-Identifier: LGPL-2.1-or-later */

#include <linux/capability.h>

#include "bus-common-errors.h"
#include "bus-polkit.h"
#include "fd-util.h"
#include "format-util.h"
#include "home-util.h"
#include "homed-bus.h"
#include "homed-home-bus.h"
#include "homed-home.h"
#include "strv.h"
#include "user-record-util.h"
#include "user-util.h"

static int property_get_unix_record(
                sd_bus *bus,
                const char *path,
                const char *interface,
                const char *property,
                sd_bus_message *reply,
                void *userdata,
                sd_bus_error *error) {

        Home *h = ASSERT_PTR(userdata);

        assert(bus);
        assert(reply);

        return sd_bus_message_append(
                        reply, "(suusss)",
                        h->user_name,
                        (uint32_t) h->uid,
                        h->record ? (uint32_t) user_record_gid(h->record) : GID_INVALID,
                        h->record ? user_record_real_name(h->record) : NULL,
                        h->record ? user_record_home_directory(h->record) : NULL,
                        h->record ? user_record_shell(h->record) : NULL);
}

static int property_get_state(
                sd_bus *bus,
                const char *path,
                const char *interface,
                const char *property,
                sd_bus_message *reply,
                void *userdata,
                sd_bus_error *error) {

        Home *h = ASSERT_PTR(userdata);

        assert(bus);
        assert(reply);

        return sd_bus_message_append(reply, "s", home_state_to_string(home_get_state(h)));
}

static int bus_home_client_is_trusted(Home *h, sd_bus_message *message, bool strict) {
        _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL;
        uid_t euid;
        int r;

        assert(h);

        if (!message)
                return -EINVAL;

        r = sd_bus_query_sender_creds(message, SD_BUS_CREDS_EUID, &creds);
        if (r < 0)
                return r;

        r = sd_bus_creds_get_euid(creds, &euid);
        if (r < 0)
                return r;

        return (!strict && euid == 0) || h->uid == euid;
}

static int home_verify_polkit_async(
                Home *h,
                sd_bus_message *message,
                const char *action,
                uid_t good_uid,
                sd_bus_error *error) {

        assert(h);
        assert(message);
        assert(action);
        assert(error);

        const char *details[] = {
                "uid", FORMAT_UID(h->uid),
                "username", h->user_name,
                NULL
        };

        return bus_verify_polkit_async_full(
                        message,
                        action,
                        details,
                        good_uid,
                        /* flags= */ 0,
                        &h->manager->polkit_registry,
                        error);
}

int bus_home_get_record_json(
                Home *h,
                sd_bus_message *message,
                char **ret,
                bool *ret_incomplete) {

        _cleanup_(user_record_unrefp) UserRecord *augmented = NULL;
        UserRecordLoadFlags flags;
        int r, trusted;

        assert(h);
        assert(ret);

        trusted = bus_home_client_is_trusted(h, message, /* strict= */ false);
        if (trusted < 0) {
                log_warning_errno(trusted, "Failed to determine whether client is trusted, assuming untrusted.");
                trusted = false;
        }

        flags = USER_RECORD_REQUIRE_REGULAR|USER_RECORD_ALLOW_PER_MACHINE|USER_RECORD_ALLOW_BINDING|USER_RECORD_STRIP_SECRET|USER_RECORD_ALLOW_STATUS|USER_RECORD_ALLOW_SIGNATURE|USER_RECORD_PERMISSIVE;
        if (trusted)
                flags |= USER_RECORD_ALLOW_PRIVILEGED;
        else
                flags |= USER_RECORD_STRIP_PRIVILEGED;

        r = home_augment_status(h, flags, &augmented);
        if (r < 0)
                return r;

        r = sd_json_variant_format(augmented->json, 0, ret);
        if (r < 0)
                return r;

        if (ret_incomplete)
                *ret_incomplete = augmented->incomplete;

        return 0;
}

static int property_get_user_record(
                sd_bus *bus,
                const char *path,
                const char *interface,
                const char *property,
                sd_bus_message *reply,
                void *userdata,
                sd_bus_error *error) {

        _cleanup_free_ char *json = NULL;
        Home *h = ASSERT_PTR(userdata);
        bool incomplete;
        int r;

        assert(bus);
        assert(reply);

        r = bus_home_get_record_json(h, sd_bus_get_current_message(bus), &json, &incomplete);
        if (r < 0)
                return r;

        return sd_bus_message_append(reply, "(sb)", json, incomplete);
}

int bus_home_method_activate(
                sd_bus_message *message,
                void *userdata,
                sd_bus_error *error) {

        _cleanup_(user_record_unrefp) UserRecord *secret = NULL;
        Home *h = ASSERT_PTR(userdata);
        bool if_referenced;
        int r;

        assert(message);

        if_referenced = endswith(sd_bus_message_get_member(message), "IfReferenced");

        r = bus_verify_polkit_async_full(
                        message,
                        "org.freedesktop.home1.activate-home",
                        /* details= */ NULL,
                        h->uid,
                        /* flags= */ 0,
                        &h->manager->polkit_registry,
                        error);
        if (r < 0)
                return r;
        if (r == 0)
                return 1; /* Will call us back */

        r = bus_message_read_secret(message, &secret, error);
        if (r < 0)
                return r;

        r = home_activate(h, if_referenced, secret, error);
        if (r < 0)
                return r;

        assert(r == 0);
        assert(!h->current_operation);

        /* The operation is now in process, keep track of this message so that we can later reply to it. */
        r = home_set_current_message(h, message);
        if (r < 0)
                return r;

        return 1;
}

int bus_home_method_deactivate(
                sd_bus_message *message,
                void *userdata,
                sd_bus_error *error) {

        Home *h = ASSERT_PTR(userdata);
        int r;

        assert(message);

        r = home_deactivate(h, false, error);
        if (r < 0)
                return r;

        assert(r == 0);
        assert(!h->current_operation);

        r = home_set_current_message(h, message);
        if (r < 0)
                return r;

        return 1;
}

int bus_home_method_unregister(
                sd_bus_message *message,
                void *userdata,
                sd_bus_error *error) {

        Home *h = ASSERT_PTR(userdata);
        int r;

        assert(message);

        r = home_verify_polkit_async(
                        h,
                        message,
                        "org.freedesktop.home1.remove-home",
                        UID_INVALID,
                        error);
        if (r < 0)
                return r;
        if (r == 0)
                return 1; /* Will call us back */

        r = home_unregister(h, error);
        if (r < 0)
                return r;

        assert(r > 0);

        /* Note that home_unregister() destroyed 'h' here, so no more accesses */

        return sd_bus_reply_method_return(message, NULL);
}

int bus_home_method_realize(
                sd_bus_message *message,
                void *userdata,
                sd_bus_error *error) {

        _cleanup_(user_record_unrefp) UserRecord *secret = NULL;
        Home *h = ASSERT_PTR(userdata);
        int r;

        assert(message);

        r = bus_message_read_secret(message, &secret, error);
        if (r < 0)
                return r;

        r = home_verify_polkit_async(
                        h,
                        message,
                        "org.freedesktop.home1.create-home",
                        UID_INVALID,
                        error);
        if (r < 0)
                return r;
        if (r == 0)
                return 1; /* Will call us back */

        r = home_create(h, secret, NULL, 0, error);
        if (r < 0)
                return r;

        assert(r == 0);
        assert(!h->current_operation);

        h->unregister_on_failure = false;

        r = home_set_current_message(h, message);
        if (r < 0)
                return r;

        return 1;
}

int bus_home_method_remove(
                sd_bus_message *message,
                void *userdata,
                sd_bus_error *error) {

        Home *h = ASSERT_PTR(userdata);
        int r;

        assert(message);

        r = home_verify_polkit_async(
                        h,
                        message,
                        "org.freedesktop.home1.remove-home",
                        UID_INVALID,
                        error);
        if (r < 0)
                return r;
        if (r == 0)
                return 1; /* Will call us back */

        r = home_remove(h, error);
        if (r < 0)
                return r;
        if (r > 0) /* Done already. Note that home_remove() destroyed 'h' here, so no more accesses */
                return sd_bus_reply_method_return(message, NULL);

        assert(!h->current_operation);

        r = home_set_current_message(h, message);
        if (r < 0)
                return r;

        return 1;
}

int bus_home_method_fixate(
                sd_bus_message *message,
                void *userdata,
                sd_bus_error *error) {

        _cleanup_(user_record_unrefp) UserRecord *secret = NULL;
        Home *h = ASSERT_PTR(userdata);
        int r;

        assert(message);

        r = bus_message_read_secret(message, &secret, error);
        if (r < 0)
                return r;

        r = home_fixate(h, secret, error);
        if (r < 0)
                return r;

        assert(r == 0);
        assert(!h->current_operation);

        r = home_set_current_message(h, message);
        if (r < 0)
                return r;

        return 1;
}

int bus_home_method_authenticate(
                sd_bus_message *message,
                void *userdata,
                sd_bus_error *error) {

        _cleanup_(user_record_unrefp) UserRecord *secret = NULL;
        Home *h = ASSERT_PTR(userdata);
        int r;

        assert(message);

        r = bus_message_read_secret(message, &secret, error);
        if (r < 0)
                return r;

        r = home_verify_polkit_async(
                        h,
                        message,
                        "org.freedesktop.home1.authenticate-home",
                        h->uid,
                        error);
        if (r < 0)
                return r;
        if (r == 0)
                return 1; /* Will call us back */

        r = home_authenticate(h, secret, error);
        if (r < 0)
                return r;

        assert(r == 0);
        assert(!h->current_operation);

        r = home_set_current_message(h, message);
        if (r < 0)
                return r;

        return 1;
}

int bus_home_update_record(
                Home *h,
                sd_bus_message *message,
                UserRecord *hr,
                Hashmap *blobs,
                uint64_t flags,
                sd_bus_error *error) {
        int r, relax_access;

        assert(h);
        assert(message);
        assert(hr);

        r = user_record_is_supported(hr, error);
        if (r < 0)
                return r;

        if ((flags & ~SD_HOMED_UPDATE_FLAGS_ALL) != 0)
                return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid flags provided.");

        if (blobs) {
                const char *failed = NULL;
                r = user_record_ensure_blob_manifest(hr, blobs, &failed);
                if (r == -EINVAL)
                        return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "Provided blob files do not correspond to blob manifest.");
                if (r < 0)
                        return sd_bus_error_set_errnof(error, r, "Failed to generate hash for blob %s: %m", strnull(failed));
        }

        relax_access = user_record_self_changes_allowed(h->record, hr);
        if (relax_access < 0) {
                log_warning_errno(relax_access, "Failed to determine if changes to user record are permitted, assuming not: %m");
                relax_access = false;
        } else if (relax_access) {
                relax_access = bus_home_client_is_trusted(h, message, /* strict= */ true);
                if (relax_access < 0) {
                        log_warning_errno(relax_access, "Failed to determine whether client is trusted, assuming not: %m");
                        relax_access = false;
                }
        }

        r = home_verify_polkit_async(
                        h,
                        message,
                        relax_access ? "org.freedesktop.home1.update-home-by-owner"
                                     : "org.freedesktop.home1.update-home",
                        UID_INVALID,
                        error);
        if (r < 0)
                return r;
        if (r == 0)
                return 1; /* Will call us back */

        r = home_update(h, hr, blobs, flags, error);
        if (r < 0)
                return r;

        assert(r == 0);
        assert(!h->current_operation);

        r = home_set_current_message(h, message);
        if (r < 0)
                return r;

        h->current_operation->call_flags = flags;

        return 1;
}

int bus_home_method_update(
                sd_bus_message *message,
                void *userdata,
                sd_bus_error *error) {

        _cleanup_(user_record_unrefp) UserRecord *hr = NULL;
        _cleanup_hashmap_free_ Hashmap *blobs = NULL;
        uint64_t flags = 0;
        Home *h = ASSERT_PTR(userdata);
        int r;

        assert(message);

        r = bus_message_read_home_record(message, USER_RECORD_REQUIRE_REGULAR|USER_RECORD_ALLOW_SECRET|USER_RECORD_ALLOW_PRIVILEGED|USER_RECORD_ALLOW_PER_MACHINE|USER_RECORD_ALLOW_SIGNATURE|USER_RECORD_PERMISSIVE, &hr, error);
        if (r < 0)
                return r;

        if (endswith(sd_bus_message_get_member(message), "Ex")) {
                r = bus_message_read_blobs(message, &blobs, error);
                if (r < 0)
                        return r;

                r = sd_bus_message_read(message, "t", &flags);
                if (r < 0)
                        return r;
        }

        return bus_home_update_record(h, message, hr, blobs, flags, error);
}

int bus_home_method_resize(
                sd_bus_message *message,
                void *userdata,
                sd_bus_error *error) {

        _cleanup_(user_record_unrefp) UserRecord *secret = NULL;
        Home *h = ASSERT_PTR(userdata);
        uint64_t sz;
        int r;

        assert(message);

        r = sd_bus_message_read(message, "t", &sz);
        if (r < 0)
                return r;

        r = bus_message_read_secret(message, &secret, error);
        if (r < 0)
                return r;

        r = home_verify_polkit_async(
                        h,
                        message,
                        "org.freedesktop.home1.resize-home",
                        UID_INVALID,
                        error);
        if (r < 0)
                return r;
        if (r == 0)
                return 1; /* Will call us back */

        r = home_resize(h, sz, secret, error);
        if (r < 0)
                return r;

        assert(r == 0);
        assert(!h->current_operation);

        r = home_set_current_message(h, message);
        if (r < 0)
                return r;

        return 1;
}

int bus_home_method_change_password(
                sd_bus_message *message,
                void *userdata,
                sd_bus_error *error) {

        _cleanup_(user_record_unrefp) UserRecord *new_secret = NULL, *old_secret = NULL;
        Home *h = ASSERT_PTR(userdata);
        int r;

        assert(message);

        r = bus_message_read_secret(message, &new_secret, error);
        if (r < 0)
                return r;

        r = bus_message_read_secret(message, &old_secret, error);
        if (r < 0)
                return r;

        r = home_verify_polkit_async(
                        h,
                        message,
                        "org.freedesktop.home1.passwd-home",
                        h->uid, /* Always let a user change their own password. Safe b/c homework will always re-check password */
                        error);
        if (r < 0)
                return r;
        if (r == 0)
                return 1; /* Will call us back */

        r = home_passwd(h, new_secret, old_secret, error);
        if (r < 0)
                return r;

        assert(r == 0);
        assert(!h->current_operation);

        r = home_set_current_message(h, message);
        if (r < 0)
                return r;

        return 1;
}

int bus_home_method_lock(
                sd_bus_message *message,
                void *userdata,
                sd_bus_error *error) {

        Home *h = ASSERT_PTR(userdata);
        int r;

        assert(message);

        r = home_lock(h, error);
        if (r < 0)
                return r;
        if (r > 0) /* Done */
                return sd_bus_reply_method_return(message, NULL);

        /* The operation is now in process, keep track of this message so that we can later reply to it. */
        assert(!h->current_operation);

        r = home_set_current_message(h, message);
        if (r < 0)
                return r;

        return 1;
}

int bus_home_method_unlock(
                sd_bus_message *message,
                void *userdata,
                sd_bus_error *error) {

        _cleanup_(user_record_unrefp) UserRecord *secret = NULL;
        Home *h = ASSERT_PTR(userdata);
        int r;

        assert(message);

        r = bus_message_read_secret(message, &secret, error);
        if (r < 0)
                return r;

        r = home_unlock(h, secret, error);
        if (r < 0)
                return r;

        assert(r == 0);
        assert(!h->current_operation);

        /* The operation is now in process, keep track of this message so that we can later reply to it. */
        r = home_set_current_message(h, message);
        if (r < 0)
                return r;

        return 1;
}

int bus_home_method_acquire(
                sd_bus_message *message,
                void *userdata,
                sd_bus_error *error) {

        _cleanup_(user_record_unrefp) UserRecord *secret = NULL;
        _cleanup_(operation_unrefp) Operation *o = NULL;
        _cleanup_close_ int fd = -EBADF;
        int r, please_suspend;
        Home *h = ASSERT_PTR(userdata);

        assert(message);

        r = bus_message_read_secret(message, &secret, error);
        if (r < 0)
                return r;

        r = sd_bus_message_read(message, "b", &please_suspend);
        if (r < 0)
                return r;

        /* This operation might not be something we can executed immediately, hence queue it */
        fd = home_create_fifo(h, please_suspend);
        if (fd < 0)
                return sd_bus_reply_method_errnof(message, fd, "Failed to allocate FIFO for %s: %m", h->user_name);

        o = operation_new(OPERATION_ACQUIRE, message);
        if (!o)
                return -ENOMEM;

        o->secret = TAKE_PTR(secret);
        o->send_fd = TAKE_FD(fd);

        r = home_schedule_operation(h, o, error);
        if (r < 0)
                return r;

        return 1;
}

int bus_home_method_ref(
                sd_bus_message *message,
                void *userdata,
                sd_bus_error *error) {

        _cleanup_close_ int fd = -EBADF;
        Home *h = ASSERT_PTR(userdata);
        int please_suspend, r;
        bool unrestricted;

        assert(message);

        /* In unrestricted mode we'll add a reference to the home even if it's not active */
        unrestricted = strstr(sd_bus_message_get_member(message), "Unrestricted");

        r = sd_bus_message_read(message, "b", &please_suspend);
        if (r < 0)
                return r;

        if (!unrestricted) {
                HomeState state;

                state = home_get_state(h);

                switch (state) {
                case HOME_ABSENT:
                        return sd_bus_error_setf(error, BUS_ERROR_HOME_ABSENT, "Home %s is currently missing or not plugged in.", h->user_name);
                case HOME_UNFIXATED:
                case HOME_INACTIVE:
                case HOME_DIRTY:
                        return sd_bus_error_setf(error, BUS_ERROR_HOME_NOT_ACTIVE, "Home %s not active.", h->user_name);
                case HOME_LOCKED:
                        return sd_bus_error_setf(error, BUS_ERROR_HOME_LOCKED, "Home %s is currently locked.", h->user_name);
                default:
                        if (HOME_STATE_IS_ACTIVE(state))
                                break;

                        return sd_bus_error_setf(error, BUS_ERROR_HOME_BUSY, "An operation on home %s is currently being executed.", h->user_name);
                }
        }

        fd = home_create_fifo(h, please_suspend);
        if (fd < 0)
                return sd_bus_reply_method_errnof(message, fd, "Failed to allocate FIFO for %s: %m", h->user_name);

        return sd_bus_reply_method_return(message, "h", fd);
}

int bus_home_method_release(
                sd_bus_message *message,
                void *userdata,
                sd_bus_error *error) {

        _cleanup_(operation_unrefp) Operation *o = NULL;
        Home *h = ASSERT_PTR(userdata);
        int r;

        assert(message);

        o = operation_new(OPERATION_RELEASE, message);
        if (!o)
                return -ENOMEM;

        r = home_schedule_operation(h, o, error);
        if (r < 0)
                return r;

        return 1;
}

/* We map a uid_t as uint32_t bus property, let's ensure this is safe. */
assert_cc(sizeof(uid_t) == sizeof(uint32_t));

int bus_home_path(Home *h, char **ret) {
        assert(ret);

        return sd_bus_path_encode("/org/freedesktop/home1/home", h->user_name, ret);
}

static int bus_home_object_find(
                sd_bus *bus,
                const char *path,
                const char *interface,
                void *userdata,
                void **found,
                sd_bus_error *error) {

        _cleanup_free_ char *e = NULL;
        Manager *m = userdata;
        uid_t uid;
        Home *h;
        int r;

        r = sd_bus_path_decode(path, "/org/freedesktop/home1/home", &e);
        if (r <= 0)
                return 0;

        if (parse_uid(e, &uid) >= 0)
                h = hashmap_get(m->homes_by_uid, UID_TO_PTR(uid));
        else
                h = hashmap_get(m->homes_by_name, e);
        if (!h)
                return 0;

        *found = h;
        return 1;
}

static int bus_home_node_enumerator(
                sd_bus *bus,
                const char *path,
                void *userdata,
                char ***nodes,
                sd_bus_error *error) {

        _cleanup_strv_free_ char **l = NULL;
        Manager *m = userdata;
        size_t k = 0;
        Home *h;
        int r;

        assert(nodes);

        l = new0(char*, hashmap_size(m->homes_by_uid) + 1);
        if (!l)
                return -ENOMEM;

        HASHMAP_FOREACH(h, m->homes_by_uid) {
                r = bus_home_path(h, l + k);
                if (r < 0)
                        return r;

                k++;
        }

        *nodes = TAKE_PTR(l);
        return 1;
}

const sd_bus_vtable home_vtable[] = {
        SD_BUS_VTABLE_START(0),

        SD_BUS_PROPERTY("UserName", "s",
                        NULL, offsetof(Home, user_name),
                        SD_BUS_VTABLE_PROPERTY_CONST),
        SD_BUS_PROPERTY("UID", "u",
                        NULL, offsetof(Home, uid),
                        SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
        SD_BUS_PROPERTY("UnixRecord", "(suusss)",
                        property_get_unix_record, 0,
                        SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
        SD_BUS_PROPERTY("State", "s",
                        property_get_state, 0,
                        0),
        SD_BUS_PROPERTY("UserRecord", "(sb)",
                        property_get_user_record, 0,
                        SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION|SD_BUS_VTABLE_SENSITIVE),

        SD_BUS_METHOD_WITH_ARGS("Activate",
                                SD_BUS_ARGS("s", secret),
                                SD_BUS_NO_RESULT,
                                bus_home_method_activate,
                                SD_BUS_VTABLE_UNPRIVILEGED|SD_BUS_VTABLE_SENSITIVE),
        SD_BUS_METHOD_WITH_ARGS("ActivateIfReferenced",
                                SD_BUS_ARGS("s", secret),
                                SD_BUS_NO_RESULT,
                                bus_home_method_activate,
                                SD_BUS_VTABLE_UNPRIVILEGED|SD_BUS_VTABLE_SENSITIVE),
        SD_BUS_METHOD("Deactivate", NULL, NULL, bus_home_method_deactivate, 0),
        SD_BUS_METHOD("Unregister", NULL, NULL, bus_home_method_unregister, SD_BUS_VTABLE_UNPRIVILEGED),
        SD_BUS_METHOD_WITH_ARGS("Realize",
                                SD_BUS_ARGS("s", secret),
                                SD_BUS_NO_RESULT,
                                bus_home_method_realize,
                                SD_BUS_VTABLE_UNPRIVILEGED|SD_BUS_VTABLE_SENSITIVE),

        SD_BUS_METHOD("Remove", NULL, NULL, bus_home_method_remove, SD_BUS_VTABLE_UNPRIVILEGED),
        SD_BUS_METHOD_WITH_ARGS("Fixate",
                                SD_BUS_ARGS("s", secret),
                                SD_BUS_NO_RESULT,
                                bus_home_method_fixate,
                                SD_BUS_VTABLE_SENSITIVE),
        SD_BUS_METHOD_WITH_ARGS("Authenticate",
                                SD_BUS_ARGS("s", secret),
                                SD_BUS_NO_RESULT,
                                bus_home_method_authenticate,
                                SD_BUS_VTABLE_UNPRIVILEGED|SD_BUS_VTABLE_SENSITIVE),
        SD_BUS_METHOD_WITH_ARGS("Update",
                                SD_BUS_ARGS("s", user_record),
                                SD_BUS_NO_RESULT,
                                bus_home_method_update,
                                SD_BUS_VTABLE_UNPRIVILEGED|SD_BUS_VTABLE_SENSITIVE),
        SD_BUS_METHOD_WITH_ARGS("UpdateEx",
                                SD_BUS_ARGS("s", user_record, "a{sh}", blobs, "t", flags),
                                SD_BUS_NO_RESULT,
                                bus_home_method_update,
                                SD_BUS_VTABLE_UNPRIVILEGED|SD_BUS_VTABLE_SENSITIVE),
        SD_BUS_METHOD_WITH_ARGS("Resize",
                                SD_BUS_ARGS("t", size, "s", secret),
                                SD_BUS_NO_RESULT,
                                bus_home_method_resize,
                                SD_BUS_VTABLE_UNPRIVILEGED|SD_BUS_VTABLE_SENSITIVE),
        SD_BUS_METHOD_WITH_ARGS("ChangePassword",
                                SD_BUS_ARGS("s", new_secret, "s", old_secret),
                                SD_BUS_NO_RESULT,
                                bus_home_method_change_password,
                                SD_BUS_VTABLE_UNPRIVILEGED|SD_BUS_VTABLE_SENSITIVE),
        SD_BUS_METHOD("Lock", NULL, NULL, bus_home_method_lock, 0),
        SD_BUS_METHOD_WITH_ARGS("Unlock",
                                SD_BUS_ARGS("s", secret),
                                SD_BUS_NO_RESULT,
                                bus_home_method_unlock,
                                SD_BUS_VTABLE_SENSITIVE),
        SD_BUS_METHOD_WITH_ARGS("Acquire",
                                SD_BUS_ARGS("s", secret, "b", please_suspend),
                                SD_BUS_RESULT("h", send_fd),
                                bus_home_method_acquire,
                                SD_BUS_VTABLE_SENSITIVE),
        SD_BUS_METHOD_WITH_ARGS("Ref",
                                SD_BUS_ARGS("b", please_suspend),
                                SD_BUS_RESULT("h", send_fd),
                                bus_home_method_ref,
                                0),
        SD_BUS_METHOD_WITH_ARGS("RefUnrestricted",
                                SD_BUS_ARGS("b", please_suspend),
                                SD_BUS_RESULT("h", send_fd),
                                bus_home_method_ref,
                                0),
        SD_BUS_METHOD("Release", NULL, NULL, bus_home_method_release, 0),
        SD_BUS_VTABLE_END
};

const BusObjectImplementation home_object = {
        "/org/freedesktop/home1/home",
        "org.freedesktop.home1.Home",
        .fallback_vtables = BUS_FALLBACK_VTABLES({home_vtable, bus_home_object_find}),
        .node_enumerator = bus_home_node_enumerator,
        .manager = true,
};

static int on_deferred_change(sd_event_source *s, void *userdata) {
        _cleanup_free_ char *path = NULL;
        Home *h = ASSERT_PTR(userdata);
        int r;

        h->deferred_change_event_source = sd_event_source_disable_unref(h->deferred_change_event_source);

        r = bus_home_path(h, &path);
        if (r < 0) {
                log_warning_errno(r, "Failed to generate home bus path, ignoring: %m");
                return 0;
        }

        if (h->announced)
                r = sd_bus_emit_properties_changed_strv(h->manager->bus, path, "org.freedesktop.home1.Home", NULL);
        else
                r = sd_bus_emit_object_added(h->manager->bus, path);
        if (r < 0)
                log_warning_errno(r, "Failed to send home change event, ignoring: %m");
        else
                h->announced = true;

        return 0;
}

int bus_home_emit_change(Home *h) {
        int r;

        assert(h);

        if (h->deferred_change_event_source)
                return 1;

        if (!h->manager->event)
                return 0;

        if (IN_SET(sd_event_get_state(h->manager->event), SD_EVENT_FINISHED, SD_EVENT_EXITING))
                return 0;

        r = sd_event_add_defer(h->manager->event, &h->deferred_change_event_source, on_deferred_change, h);
        if (r < 0)
                return log_error_errno(r, "Failed to allocate deferred change event source: %m");

        r = sd_event_source_set_priority(h->deferred_change_event_source, SD_EVENT_PRIORITY_IDLE+5);
        if (r < 0)
                log_warning_errno(r, "Failed to tweak priority of event source, ignoring: %m");

        (void) sd_event_source_set_description(h->deferred_change_event_source, "deferred-change-event");
        return 1;
}

int bus_home_emit_remove(Home *h) {
        _cleanup_free_ char *path = NULL;
        int r;

        assert(h);

        if (!h->announced)
                return 0;

        if (!h->manager)
                return 0;

        if (!h->manager->bus)
                return 0;

        r = bus_home_path(h, &path);
        if (r < 0)
                return r;

        r = sd_bus_emit_object_removed(h->manager->bus, path);
        if (r < 0)
                return r;

        h->announced = false;
        return 1;
}
