| /* 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, 0)) |
| 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, 0)) |
| 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, 0)) |
| 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 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); |
| } |
| |
| static 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_WITH_NAMES("GetHomeByName", |
| "s", |
| SD_BUS_PARAM(user_name), |
| "usussso", |
| SD_BUS_PARAM(uid) |
| SD_BUS_PARAM(home_state) |
| SD_BUS_PARAM(gid) |
| SD_BUS_PARAM(real_name) |
| SD_BUS_PARAM(home_directory) |
| SD_BUS_PARAM(shell) |
| SD_BUS_PARAM(bus_path), |
| method_get_home_by_name, |
| SD_BUS_VTABLE_UNPRIVILEGED), |
| SD_BUS_METHOD_WITH_NAMES("GetHomeByUID", |
| "u", |
| SD_BUS_PARAM(uid), |
| "ssussso", |
| SD_BUS_PARAM(user_name) |
| SD_BUS_PARAM(home_state) |
| SD_BUS_PARAM(gid) |
| SD_BUS_PARAM(real_name) |
| SD_BUS_PARAM(home_directory) |
| SD_BUS_PARAM(shell) |
| SD_BUS_PARAM(bus_path), |
| method_get_home_by_uid, |
| SD_BUS_VTABLE_UNPRIVILEGED), |
| SD_BUS_METHOD_WITH_NAMES("GetUserRecordByName", |
| "s", |
| SD_BUS_PARAM(user_name), |
| "sbo", |
| SD_BUS_PARAM(user_record) |
| SD_BUS_PARAM(incomplete) |
| SD_BUS_PARAM(bus_path), |
| method_get_user_record_by_name, |
| SD_BUS_VTABLE_UNPRIVILEGED|SD_BUS_VTABLE_SENSITIVE), |
| SD_BUS_METHOD_WITH_NAMES("GetUserRecordByUID", |
| "u", |
| SD_BUS_PARAM(uid), |
| "sbo", |
| SD_BUS_PARAM(user_record) |
| SD_BUS_PARAM(incomplete) |
| SD_BUS_PARAM(bus_path), |
| method_get_user_record_by_uid, |
| SD_BUS_VTABLE_UNPRIVILEGED|SD_BUS_VTABLE_SENSITIVE), |
| SD_BUS_METHOD_WITH_NAMES("ListHomes", |
| NULL,, |
| "a(susussso)", |
| SD_BUS_PARAM(home_areas), |
| method_list_homes, |
| SD_BUS_VTABLE_UNPRIVILEGED), |
| |
| /* The following methods directly execute an operation on a home area, without ref-counting, queueing |
| * or anything, and are accessible through homectl. */ |
| SD_BUS_METHOD_WITH_NAMES("ActivateHome", |
| "ss", |
| SD_BUS_PARAM(user_name) |
| SD_BUS_PARAM(secret), |
| NULL,, |
| method_activate_home, |
| SD_BUS_VTABLE_SENSITIVE), |
| SD_BUS_METHOD_WITH_NAMES("DeactivateHome", |
| "s", |
| SD_BUS_PARAM(user_name), |
| NULL,, |
| method_deactivate_home, |
| 0), |
| |
| /* Add the JSON record to homed, but don't create actual $HOME */ |
| SD_BUS_METHOD_WITH_NAMES("RegisterHome", |
| "s", |
| SD_BUS_PARAM(user_record), |
| NULL,, |
| method_register_home, |
| SD_BUS_VTABLE_UNPRIVILEGED), |
| |
| /* Remove the JSON record from homed, but don't remove actual $HOME */ |
| SD_BUS_METHOD_WITH_NAMES("UnregisterHome", |
| "s", |
| SD_BUS_PARAM(user_name), |
| NULL,, |
| method_unregister_home, |
| SD_BUS_VTABLE_UNPRIVILEGED), |
| |
| /* Add JSON record, and create $HOME for it */ |
| SD_BUS_METHOD_WITH_NAMES("CreateHome", |
| "s", |
| SD_BUS_PARAM(user_record), |
| NULL,, |
| method_create_home, |
| SD_BUS_VTABLE_UNPRIVILEGED|SD_BUS_VTABLE_SENSITIVE), |
| |
| /* Create $HOME for already registered JSON entry */ |
| SD_BUS_METHOD_WITH_NAMES("RealizeHome", |
| "ss", |
| SD_BUS_PARAM(user_name) |
| SD_BUS_PARAM(secret), |
| NULL,, |
| method_realize_home, |
| SD_BUS_VTABLE_UNPRIVILEGED|SD_BUS_VTABLE_SENSITIVE), |
| |
| /* Remove the JSON record and remove $HOME */ |
| SD_BUS_METHOD_WITH_NAMES("RemoveHome", |
| "s", |
| SD_BUS_PARAM(user_name), |
| NULL,, |
| method_remove_home, |
| SD_BUS_VTABLE_UNPRIVILEGED), |
| |
| /* Investigate $HOME and propagate contained JSON record into our database */ |
| SD_BUS_METHOD_WITH_NAMES("FixateHome", |
| "ss", |
| SD_BUS_PARAM(user_name) |
| SD_BUS_PARAM(secret), |
| NULL,, |
| method_fixate_home, |
| SD_BUS_VTABLE_SENSITIVE), |
| |
| /* Just check credentials */ |
| SD_BUS_METHOD_WITH_NAMES("AuthenticateHome", |
| "ss", |
| SD_BUS_PARAM(user_name) |
| SD_BUS_PARAM(secret), |
| NULL,, |
| method_authenticate_home, |
| SD_BUS_VTABLE_UNPRIVILEGED|SD_BUS_VTABLE_SENSITIVE), |
| |
| /* Update the JSON record of existing user */ |
| SD_BUS_METHOD_WITH_NAMES("UpdateHome", |
| "s", |
| SD_BUS_PARAM(user_record), |
| NULL,, |
| method_update_home, |
| SD_BUS_VTABLE_UNPRIVILEGED|SD_BUS_VTABLE_SENSITIVE), |
| |
| SD_BUS_METHOD_WITH_NAMES("ResizeHome", |
| "sts", |
| SD_BUS_PARAM(user_name) |
| SD_BUS_PARAM(size) |
| SD_BUS_PARAM(secret), |
| NULL,, |
| method_resize_home, |
| SD_BUS_VTABLE_UNPRIVILEGED|SD_BUS_VTABLE_SENSITIVE), |
| |
| SD_BUS_METHOD_WITH_NAMES("ChangePasswordHome", |
| "sss", |
| SD_BUS_PARAM(user_name) |
| SD_BUS_PARAM(new_secret) |
| SD_BUS_PARAM(old_secret), |
| NULL,, |
| method_change_password_home, |
| SD_BUS_VTABLE_UNPRIVILEGED|SD_BUS_VTABLE_SENSITIVE), |
| |
| /* Prepare active home for system suspend: flush out passwords, suspend access */ |
| SD_BUS_METHOD_WITH_NAMES("LockHome", |
| "s", |
| SD_BUS_PARAM(user_name), |
| NULL,, |
| method_lock_home, |
| 0), |
| |
| /* Make $HOME usable after system resume again */ |
| SD_BUS_METHOD_WITH_NAMES("UnlockHome", |
| "ss", |
| SD_BUS_PARAM(user_name) |
| SD_BUS_PARAM(secret), |
| NULL,, |
| method_unlock_home, |
| SD_BUS_VTABLE_SENSITIVE), |
| |
| /* The following methods implement ref-counted activation, and are what the PAM module and "homectl |
| * with" use. 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 area, 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 the home area is already active. */ |
| SD_BUS_METHOD_WITH_NAMES("AcquireHome", |
| "ssb", |
| SD_BUS_PARAM(user_name) |
| SD_BUS_PARAM(secret) |
| SD_BUS_PARAM(please_suspend), |
| "h", |
| SD_BUS_PARAM(send_fd), |
| method_acquire_home, |
| SD_BUS_VTABLE_UNPRIVILEGED|SD_BUS_VTABLE_SENSITIVE), |
| SD_BUS_METHOD_WITH_NAMES("RefHome", |
| "sb", |
| SD_BUS_PARAM(user_name) |
| SD_BUS_PARAM(please_suspend), |
| "h", |
| SD_BUS_PARAM(send_fd), |
| method_ref_home, |
| 0), |
| SD_BUS_METHOD_WITH_NAMES("ReleaseHome", |
| "s", |
| SD_BUS_PARAM(user_name), |
| 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 |
| }; |
| |
| const BusObjectImplementation manager_object = { |
| "/org/freedesktop/home1", |
| "org.freedesktop.home1.Manager", |
| .vtables = BUS_VTABLES(manager_vtable), |
| .children = BUS_IMPLEMENTATIONS(&home_object), |
| }; |
| |
| 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; |
| } |