diff options
Diffstat (limited to 'src/home/homed-manager-bus.c')
-rw-r--r-- | src/home/homed-manager-bus.c | 690 |
1 files changed, 690 insertions, 0 deletions
diff --git a/src/home/homed-manager-bus.c b/src/home/homed-manager-bus.c new file mode 100644 index 0000000000..e1d6a996ec --- /dev/null +++ b/src/home/homed-manager-bus.c @@ -0,0 +1,690 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ + +#include <linux/capability.h> + +#include "alloc-util.h" +#include "bus-common-errors.h" +#include "bus-polkit.h" +#include "format-util.h" +#include "homed-bus.h" +#include "homed-home-bus.h" +#include "homed-manager-bus.h" +#include "homed-manager.h" +#include "strv.h" +#include "user-record-sign.h" +#include "user-record-util.h" +#include "user-util.h" + +static int property_get_auto_login( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + Manager *m = userdata; + Iterator i; + Home *h; + int r; + + assert(bus); + assert(reply); + assert(m); + + r = sd_bus_message_open_container(reply, 'a', "(sso)"); + if (r < 0) + return r; + + HASHMAP_FOREACH(h, m->homes_by_name, i) { + _cleanup_(strv_freep) char **seats = NULL; + _cleanup_free_ char *home_path = NULL; + char **s; + + r = home_auto_login(h, &seats); + if (r < 0) { + log_debug_errno(r, "Failed to determine whether home '%s' is candidate for auto-login, ignoring: %m", h->user_name); + continue; + } + if (!r) + continue; + + r = bus_home_path(h, &home_path); + if (r < 0) + return log_error_errno(r, "Failed to generate home bus path: %m"); + + STRV_FOREACH(s, seats) { + r = sd_bus_message_append(reply, "(sso)", h->user_name, *s, home_path); + if (r < 0) + return r; + } + } + + return sd_bus_message_close_container(reply); +} + +static int method_get_home_by_name( + sd_bus_message *message, + void *userdata, + sd_bus_error *error) { + + _cleanup_free_ char *path = NULL; + const char *user_name; + Manager *m = userdata; + Home *h; + int r; + + assert(message); + assert(m); + + r = sd_bus_message_read(message, "s", &user_name); + if (r < 0) + return r; + if (!valid_user_group_name(user_name)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "User name %s is not valid", user_name); + + h = hashmap_get(m->homes_by_name, user_name); + if (!h) + return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_HOME, "No home for user %s known", user_name); + + r = bus_home_path(h, &path); + if (r < 0) + return r; + + return sd_bus_reply_method_return( + message, "usussso", + (uint32_t) h->uid, + home_state_to_string(home_get_state(h)), + 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, + path); +} + +static int method_get_home_by_uid( + sd_bus_message *message, + void *userdata, + sd_bus_error *error) { + + _cleanup_free_ char *path = NULL; + Manager *m = userdata; + uint32_t uid; + int r; + Home *h; + + assert(message); + assert(m); + + r = sd_bus_message_read(message, "u", &uid); + if (r < 0) + return r; + if (!uid_is_valid(uid)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "UID " UID_FMT " is not valid", uid); + + h = hashmap_get(m->homes_by_uid, UID_TO_PTR(uid)); + if (!h) + return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_HOME, "No home for UID " UID_FMT " known", uid); + + /* Note that we don't use bus_home_path() here, but build the path manually, since if we are queried + * for a UID we should also generate the bus path with a UID, and bus_home_path() uses our more + * typical bus path by name. */ + if (asprintf(&path, "/org/freedesktop/home1/home/" UID_FMT, h->uid) < 0) + return -ENOMEM; + + return sd_bus_reply_method_return( + message, "ssussso", + h->user_name, + home_state_to_string(home_get_state(h)), + 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, + path); +} + +static int method_list_homes( + sd_bus_message *message, + void *userdata, + sd_bus_error *error) { + + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + Manager *m = userdata; + Iterator i; + Home *h; + int r; + + assert(message); + assert(m); + + r = sd_bus_message_new_method_return(message, &reply); + if (r < 0) + return r; + + r = sd_bus_message_open_container(reply, 'a', "(susussso)"); + if (r < 0) + return r; + + HASHMAP_FOREACH(h, m->homes_by_uid, i) { + _cleanup_free_ char *path = NULL; + + r = bus_home_path(h, &path); + if (r < 0) + return r; + + r = sd_bus_message_append( + reply, "(susussso)", + h->user_name, + (uint32_t) h->uid, + home_state_to_string(home_get_state(h)), + 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, + path); + if (r < 0) + return r; + } + + r = sd_bus_message_close_container(reply); + if (r < 0) + return r; + + return sd_bus_send(NULL, reply, NULL); +} + +static int method_get_user_record_by_name( + sd_bus_message *message, + void *userdata, + sd_bus_error *error) { + + _cleanup_free_ char *json = NULL, *path = NULL; + Manager *m = userdata; + const char *user_name; + bool incomplete; + Home *h; + int r; + + assert(message); + assert(m); + + r = sd_bus_message_read(message, "s", &user_name); + if (r < 0) + return r; + if (!valid_user_group_name(user_name)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "User name %s is not valid", user_name); + + h = hashmap_get(m->homes_by_name, user_name); + if (!h) + return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_HOME, "No home for user %s known", user_name); + + r = bus_home_get_record_json(h, message, &json, &incomplete); + if (r < 0) + return r; + + r = bus_home_path(h, &path); + if (r < 0) + return r; + + return sd_bus_reply_method_return( + message, "sbo", + json, + incomplete, + path); +} + +static int method_get_user_record_by_uid( + sd_bus_message *message, + void *userdata, + sd_bus_error *error) { + + _cleanup_free_ char *json = NULL, *path = NULL; + Manager *m = userdata; + bool incomplete; + uint32_t uid; + Home *h; + int r; + + assert(message); + assert(m); + + r = sd_bus_message_read(message, "u", &uid); + if (r < 0) + return r; + if (!uid_is_valid(uid)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "UID " UID_FMT " is not valid", uid); + + h = hashmap_get(m->homes_by_uid, UID_TO_PTR(uid)); + if (!h) + return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_HOME, "No home for UID " UID_FMT " known", uid); + + r = bus_home_get_record_json(h, message, &json, &incomplete); + if (r < 0) + return r; + + if (asprintf(&path, "/org/freedesktop/home1/home/" UID_FMT, h->uid) < 0) + return -ENOMEM; + + return sd_bus_reply_method_return( + message, "sbo", + json, + incomplete, + path); +} + +static int generic_home_method( + Manager *m, + sd_bus_message *message, + sd_bus_message_handler_t handler, + sd_bus_error *error) { + + const char *user_name; + Home *h; + int r; + + r = sd_bus_message_read(message, "s", &user_name); + if (r < 0) + return r; + + if (!valid_user_group_name(user_name)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "User name %s is not valid", user_name); + + h = hashmap_get(m->homes_by_name, user_name); + if (!h) + return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_HOME, "No home for user %s known", user_name); + + return handler(message, h, error); +} + +static int method_activate_home(sd_bus_message *message, void *userdata, sd_bus_error *error) { + return generic_home_method(userdata, message, bus_home_method_activate, error); +} + +static int method_deactivate_home(sd_bus_message *message, void *userdata, sd_bus_error *error) { + return generic_home_method(userdata, message, bus_home_method_deactivate, error); +} + +static int validate_and_allocate_home(Manager *m, UserRecord *hr, Home **ret, sd_bus_error *error) { + _cleanup_(user_record_unrefp) UserRecord *signed_hr = NULL; + struct passwd *pw; + struct group *gr; + bool signed_locally; + Home *other; + int r; + + assert(m); + assert(hr); + assert(ret); + + r = user_record_is_supported(hr, error); + if (r < 0) + return r; + + other = hashmap_get(m->homes_by_name, hr->user_name); + if (other) + return sd_bus_error_setf(error, BUS_ERROR_USER_NAME_EXISTS, "Specified user name %s exists already, refusing.", hr->user_name); + + pw = getpwnam(hr->user_name); + if (pw) + return sd_bus_error_setf(error, BUS_ERROR_USER_NAME_EXISTS, "Specified user name %s exists in the NSS user database, refusing.", hr->user_name); + + gr = getgrnam(hr->user_name); + if (gr) + return sd_bus_error_setf(error, BUS_ERROR_USER_NAME_EXISTS, "Specified user name %s conflicts with an NSS group by the same name, refusing.", hr->user_name); + + r = manager_verify_user_record(m, hr); + switch (r) { + + case USER_RECORD_UNSIGNED: + /* If the record is unsigned, then let's sign it with our own key */ + r = manager_sign_user_record(m, hr, &signed_hr, error); + if (r < 0) + return r; + + hr = signed_hr; + _fallthrough_; + + case USER_RECORD_SIGNED_EXCLUSIVE: + signed_locally = true; + break; + + case USER_RECORD_SIGNED: + case USER_RECORD_FOREIGN: + signed_locally = false; + break; + + case -ENOKEY: + return sd_bus_error_setf(error, BUS_ERROR_BAD_SIGNATURE, "Specified user record for %s is signed by a key we don't recognize, refusing.", hr->user_name); + + default: + return sd_bus_error_set_errnof(error, r, "Failed to validate signature for '%s': %m", hr->user_name); + } + + if (uid_is_valid(hr->uid)) { + other = hashmap_get(m->homes_by_uid, UID_TO_PTR(hr->uid)); + if (other) + return sd_bus_error_setf(error, BUS_ERROR_UID_IN_USE, "Specified UID " UID_FMT " already in use by home %s, refusing.", hr->uid, other->user_name); + + pw = getpwuid(hr->uid); + if (pw) + return sd_bus_error_setf(error, BUS_ERROR_UID_IN_USE, "Specified UID " UID_FMT " already in use by NSS user %s, refusing.", hr->uid, pw->pw_name); + + gr = getgrgid(hr->uid); + if (gr) + return sd_bus_error_setf(error, BUS_ERROR_UID_IN_USE, "Specified UID " UID_FMT " already in use as GID by NSS group %s, refusing.", hr->uid, gr->gr_name); + } else { + r = manager_augment_record_with_uid(m, hr); + if (r < 0) + return sd_bus_error_set_errnof(error, r, "Failed to acquire UID for '%s': %m", hr->user_name); + } + + r = home_new(m, hr, NULL, ret); + if (r < 0) + return r; + + (*ret)->signed_locally = signed_locally; + return r; +} + +static int method_register_home( + sd_bus_message *message, + void *userdata, + sd_bus_error *error) { + + _cleanup_(user_record_unrefp) UserRecord *hr = NULL; + Manager *m = userdata; + Home *h; + int r; + + assert(message); + assert(m); + + r = bus_message_read_home_record(message, USER_RECORD_LOAD_EMBEDDED, &hr, error); + if (r < 0) + return r; + + r = bus_verify_polkit_async( + message, + CAP_SYS_ADMIN, + "org.freedesktop.home1.create-home", + NULL, + true, + UID_INVALID, + &m->polkit_registry, + error); + if (r < 0) + return r; + if (r == 0) + return 1; /* Will call us back */ + + r = validate_and_allocate_home(m, hr, &h, error); + if (r < 0) + return r; + + r = home_save_record(h); + if (r < 0) { + home_free(h); + return r; + } + + return sd_bus_reply_method_return(message, NULL); +} + +static int method_unregister_home(sd_bus_message *message, void *userdata, sd_bus_error *error) { + return generic_home_method(userdata, message, bus_home_method_unregister, error); +} + +static int method_create_home( + sd_bus_message *message, + void *userdata, + sd_bus_error *error) { + + _cleanup_(user_record_unrefp) UserRecord *hr = NULL; + Manager *m = userdata; + Home *h; + int r; + + assert(message); + assert(m); + + 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, &hr, error); + if (r < 0) + return r; + + r = bus_verify_polkit_async( + message, + CAP_SYS_ADMIN, + "org.freedesktop.home1.create-home", + NULL, + true, + UID_INVALID, + &m->polkit_registry, + error); + if (r < 0) + return r; + if (r == 0) + return 1; /* Will call us back */ + + r = validate_and_allocate_home(m, hr, &h, error); + if (r < 0) + return r; + + r = home_create(h, hr, error); + if (r < 0) + goto fail; + + assert(r == 0); + h->unregister_on_failure = true; + assert(!h->current_operation); + + r = home_set_current_message(h, message); + if (r < 0) + return r; + + return 1; + +fail: + (void) home_unlink_record(h); + h = home_free(h); + return r; +} + +static int method_realize_home(sd_bus_message *message, void *userdata, sd_bus_error *error) { + return generic_home_method(userdata, message, bus_home_method_realize, error); +} + +static int method_remove_home(sd_bus_message *message, void *userdata, sd_bus_error *error) { + return generic_home_method(userdata, message, bus_home_method_remove, error); +} + +static int method_fixate_home(sd_bus_message *message, void *userdata, sd_bus_error *error) { + return generic_home_method(userdata, message, bus_home_method_fixate, error); +} + +static int method_authenticate_home(sd_bus_message *message, void *userdata, sd_bus_error *error) { + return generic_home_method(userdata, message, bus_home_method_authenticate, error); +} + +static int method_update_home(sd_bus_message *message, void *userdata, sd_bus_error *error) { + _cleanup_(user_record_unrefp) UserRecord *hr = NULL; + Manager *m = userdata; + Home *h; + int r; + + assert(message); + assert(m); + + 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, &hr, error); + if (r < 0) + return r; + + assert(hr->user_name); + + h = hashmap_get(m->homes_by_name, hr->user_name); + if (!h) + return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_HOME, "No home for user %s known", hr->user_name); + + return bus_home_method_update_record(h, message, hr, error); +} + +static int method_resize_home(sd_bus_message *message, void *userdata, sd_bus_error *error) { + return generic_home_method(userdata, message, bus_home_method_resize, error); +} + +static int method_change_password_home(sd_bus_message *message, void *userdata, sd_bus_error *error) { + return generic_home_method(userdata, message, bus_home_method_change_password, error); +} + +static int method_lock_home(sd_bus_message *message, void *userdata, sd_bus_error *error) { + return generic_home_method(userdata, message, bus_home_method_lock, error); +} + +static int method_unlock_home(sd_bus_message *message, void *userdata, sd_bus_error *error) { + return generic_home_method(userdata, message, bus_home_method_unlock, error); +} + +static int method_acquire_home(sd_bus_message *message, void *userdata, sd_bus_error *error) { + return generic_home_method(userdata, message, bus_home_method_acquire, error); +} + +static int method_ref_home(sd_bus_message *message, void *userdata, sd_bus_error *error) { + return generic_home_method(userdata, message, bus_home_method_ref, error); +} + +static int method_release_home(sd_bus_message *message, void *userdata, sd_bus_error *error) { + return generic_home_method(userdata, message, bus_home_method_release, error); +} + +static int method_lock_all_homes(sd_bus_message *message, void *userdata, sd_bus_error *error) { + _cleanup_(operation_unrefp) Operation *o = NULL; + bool waiting = false; + Manager *m = userdata; + Iterator i; + Home *h; + int r; + + assert(m); + + /* This is called from logind when we are preparing for system suspend. We enqueue a lock operation + * for every suitable home we have and only when all of them completed we send a reply indicating + * completion. */ + + HASHMAP_FOREACH(h, m->homes_by_name, i) { + + /* Automatically suspend all homes that have at least one client referencing it that asked + * for "please suspend", and no client that asked for "please do not suspend". */ + if (h->ref_event_source_dont_suspend || + !h->ref_event_source_please_suspend) + continue; + + if (!o) { + o = operation_new(OPERATION_LOCK_ALL, message); + if (!o) + return -ENOMEM; + } + + log_info("Automatically locking of home of user %s.", h->user_name); + + r = home_schedule_operation(h, o, error); + if (r < 0) + return r; + + waiting = true; + } + + if (waiting) /* At least one lock operation was enqeued, let's leave here without a reply: it will + * be sent as soon as the last of the lock operations completed. */ + return 1; + + return sd_bus_reply_method_return(message, NULL); +} + +const sd_bus_vtable manager_vtable[] = { + SD_BUS_VTABLE_START(0), + + SD_BUS_PROPERTY("AutoLogin", "a(sso)", property_get_auto_login, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + + SD_BUS_METHOD("GetHomeByName", "s", "usussso", method_get_home_by_name, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("GetHomeByUID", "u", "ssussso", method_get_home_by_uid, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("GetUserRecordByName", "s", "sbo", method_get_user_record_by_name, SD_BUS_VTABLE_UNPRIVILEGED|SD_BUS_VTABLE_SENSITIVE), + SD_BUS_METHOD("GetUserRecordByUID", "u", "sbo", method_get_user_record_by_uid, SD_BUS_VTABLE_UNPRIVILEGED|SD_BUS_VTABLE_SENSITIVE), + SD_BUS_METHOD("ListHomes", NULL, "a(susussso)", method_list_homes, SD_BUS_VTABLE_UNPRIVILEGED), + + /* The following methods directly execute an operation on a home, without ref-counting, queing or + * anything, and are accessible through homectl. */ + SD_BUS_METHOD("ActivateHome", "ss", NULL, method_activate_home, SD_BUS_VTABLE_SENSITIVE), + SD_BUS_METHOD("DeactivateHome", "s", NULL, method_deactivate_home, 0), + SD_BUS_METHOD("RegisterHome", "s", NULL, method_register_home, SD_BUS_VTABLE_UNPRIVILEGED), /* Add JSON record to homed, but don't create actual $HOME */ + SD_BUS_METHOD("UnregisterHome", "s", NULL, method_unregister_home, SD_BUS_VTABLE_UNPRIVILEGED), /* Remove JSON record from homed, but don't remove actual $HOME */ + SD_BUS_METHOD("CreateHome", "s", NULL, method_create_home, SD_BUS_VTABLE_UNPRIVILEGED|SD_BUS_VTABLE_SENSITIVE), /* Add JSON record, and create $HOME for it */ + SD_BUS_METHOD("RealizeHome", "ss", NULL, method_realize_home, SD_BUS_VTABLE_UNPRIVILEGED|SD_BUS_VTABLE_SENSITIVE), /* Create $HOME for already registered JSON entry */ + SD_BUS_METHOD("RemoveHome", "s", NULL, method_remove_home, SD_BUS_VTABLE_UNPRIVILEGED), /* Remove JSON record and remove $HOME */ + SD_BUS_METHOD("FixateHome", "ss", NULL, method_fixate_home, SD_BUS_VTABLE_SENSITIVE), /* Investigate $HOME and propagate contained JSON record into our database */ + SD_BUS_METHOD("AuthenticateHome", "ss", NULL, method_authenticate_home, SD_BUS_VTABLE_UNPRIVILEGED|SD_BUS_VTABLE_SENSITIVE), /* Just check credentials */ + SD_BUS_METHOD("UpdateHome", "s", NULL, method_update_home, SD_BUS_VTABLE_UNPRIVILEGED|SD_BUS_VTABLE_SENSITIVE), /* Update JSON record of existing user */ + SD_BUS_METHOD("ResizeHome", "sts", NULL, method_resize_home, SD_BUS_VTABLE_UNPRIVILEGED|SD_BUS_VTABLE_SENSITIVE), + SD_BUS_METHOD("ChangePasswordHome", "sss", NULL, method_change_password_home, SD_BUS_VTABLE_UNPRIVILEGED|SD_BUS_VTABLE_SENSITIVE), + SD_BUS_METHOD("LockHome", "s", NULL, method_lock_home, 0), /* Prepare active home for system suspend: flush out passwords, suspend access */ + SD_BUS_METHOD("UnlockHome", "ss", NULL, method_unlock_home, SD_BUS_VTABLE_SENSITIVE), /* Make $HOME usable after system resume again */ + + /* The following methods implement ref-counted activation, and are what the PAM module calls (and + * what "homectl with" runs). In contrast to the methods above which fail if an operation is already + * being executed on a home directory, these ones will queue the request, and are thus more + * reliable. Moreover, they are a bit smarter: AcquireHome() will fixate, activate, unlock, or + * authenticate depending on the state of the home, so that the end result is always the same + * (i.e. the home directory is accessible), and we always validate the specified passwords. RefHome() + * will not authenticate, and thus only works if home is already active. */ + SD_BUS_METHOD("AcquireHome", "ssb", "h", method_acquire_home, SD_BUS_VTABLE_SENSITIVE), + SD_BUS_METHOD("RefHome", "sb", "h", method_ref_home, 0), + SD_BUS_METHOD("ReleaseHome", "s", NULL, method_release_home, 0), + + /* An operation that acts on all homes that allow it */ + SD_BUS_METHOD("LockAllHomes", NULL, NULL, method_lock_all_homes, 0), + + SD_BUS_VTABLE_END +}; + +static int on_deferred_auto_login(sd_event_source *s, void *userdata) { + Manager *m = userdata; + int r; + + assert(m); + + m->deferred_auto_login_event_source = sd_event_source_unref(m->deferred_auto_login_event_source); + + r = sd_bus_emit_properties_changed( + m->bus, + "/org/freedesktop/home1", + "org.freedesktop.home1.Manager", + "AutoLogin", NULL); + if (r < 0) + log_warning_errno(r, "Failed to send AutoLogin property change event, ignoring: %m"); + + return 0; +} + +int bus_manager_emit_auto_login_changed(Manager *m) { + int r; + assert(m); + + if (m->deferred_auto_login_event_source) + return 0; + + if (!m->event) + return 0; + + if (IN_SET(sd_event_get_state(m->event), SD_EVENT_FINISHED, SD_EVENT_EXITING)) + return 0; + + r = sd_event_add_defer(m->event, &m->deferred_auto_login_event_source, on_deferred_auto_login, m); + if (r < 0) + return log_error_errno(r, "Failed to allocate auto login event source: %m"); + + r = sd_event_source_set_priority(m->deferred_auto_login_event_source, SD_EVENT_PRIORITY_IDLE+10); + if (r < 0) + log_warning_errno(r, "Failed to tweak priority of event source, ignoring: %m"); + + (void) sd_event_source_set_description(m->deferred_auto_login_event_source, "deferred-auto-login"); + return 1; +} |