logind: move verify_shutdown_creds() to logind-shutdown.c

Move verify_shutdown_creds() and its helper have_multiple_sessions()
from logind-dbus.c to logind-shutdown.c so that they can be reused
by the varlink transport. No functional changes.

Also prefix both with `manager_` now that they are public.
This commit is contained in:
Michael Vogt
2026-04-07 20:38:50 +02:00
parent d1d72563e0
commit c1b928c810
3 changed files with 153 additions and 136 deletions
+3 -136
View File
@@ -1863,24 +1863,6 @@ static int method_flush_devices(sd_bus_message *message, void *userdata, sd_bus_
return sd_bus_reply_method_return(message, NULL);
}
static int have_multiple_sessions(
Manager *m,
uid_t uid) {
Session *session;
assert(m);
/* Check for other users' sessions. Greeter sessions do not
* count, and non-login sessions do not count either. */
HASHMAP_FOREACH(session, m->sessions)
if (SESSION_CLASS_IS_INHIBITOR_LIKE(session->class) &&
session->user->user_record->uid != uid)
return true;
return false;
}
static int bus_manager_log_shutdown(
Manager *m,
const HandleActionData *a) {
@@ -2186,121 +2168,6 @@ int bus_manager_shutdown_or_sleep_now_or_later(
return r;
}
static int verify_shutdown_creds(
Manager *m,
sd_bus_message *message,
const HandleActionData *a,
uint64_t flags,
sd_bus_error *error) {
_cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL;
bool multiple_sessions, blocked, interactive;
_unused_ bool error_or_denial = false;
Inhibitor *offending = NULL;
uid_t uid;
int r;
assert(m);
assert(a);
assert(message);
r = sd_bus_query_sender_creds(message, SD_BUS_CREDS_EUID, &creds);
if (r < 0)
return r;
r = sd_bus_creds_get_euid(creds, &uid);
if (r < 0)
return r;
r = have_multiple_sessions(m, uid);
if (r < 0)
return r;
multiple_sessions = r > 0;
blocked = manager_is_inhibited(m, a->inhibit_what, NULL, /* flags= */ 0, uid, &offending);
interactive = flags & SD_LOGIND_INTERACTIVE;
if (multiple_sessions) {
r = bus_verify_polkit_async_full(
message,
a->polkit_action_multiple_sessions,
/* details= */ NULL,
/* good_user= */ UID_INVALID,
interactive ? POLKIT_ALLOW_INTERACTIVE : 0,
&m->polkit_registry,
error);
if (r < 0) {
/* If we get -EBUSY, it means a polkit decision was made, but not for
* this action in particular. Assuming we are blocked on inhibitors,
* ignore that error and allow the decision to be revealed below. */
if (blocked && r == -EBUSY)
error_or_denial = true;
else
return r;
}
if (r == 0)
return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
}
if (blocked) {
PolkitFlags polkit_flags = 0;
/* With a strong inhibitor, if the skip flag is not set, reject outright.
* With a weak inhibitor, if root is asking and the root flag is set, reject outright.
* All else, check polkit first. */
if (!FLAGS_SET(flags, SD_LOGIND_SKIP_INHIBITORS) &&
(offending->mode != INHIBIT_BLOCK_WEAK ||
(uid == 0 && FLAGS_SET(flags, SD_LOGIND_ROOT_CHECK_INHIBITORS))))
return sd_bus_error_set(error, BUS_ERROR_BLOCKED_BY_INHIBITOR_LOCK,
"Operation denied due to active block inhibitor");
/* We want to always ask here, even for root, to only allow bypassing if explicitly allowed
* by polkit, unless a weak blocker is used, in which case it will be authorized. */
if (offending->mode != INHIBIT_BLOCK_WEAK)
polkit_flags |= POLKIT_ALWAYS_QUERY;
if (interactive)
polkit_flags |= POLKIT_ALLOW_INTERACTIVE;
r = bus_verify_polkit_async_full(
message,
a->polkit_action_ignore_inhibit,
/* details= */ NULL,
/* good_user= */ UID_INVALID,
polkit_flags,
&m->polkit_registry,
error);
if (r < 0)
return r;
if (r == 0)
return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
}
if (!multiple_sessions && !blocked) {
r = bus_verify_polkit_async_full(
message,
a->polkit_action,
/* details= */ NULL,
/* good_user= */ UID_INVALID,
interactive ? POLKIT_ALLOW_INTERACTIVE : 0,
&m->polkit_registry,
error);
if (r < 0)
return r;
if (r == 0)
return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
}
/* If error_or_denial was set above, it means that a polkit denial or
* error was deferred for a future call to bus_verify_polkit_async_full()
* to catch. In any case, it also means that the payload guarded by
* these polkit calls should never be executed, and hence we should
* never reach this point. */
assert(!error_or_denial);
return 0;
}
static int setup_wall_message_timer(Manager *m, sd_bus_message* message) {
_cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL;
int r;
@@ -2439,7 +2306,7 @@ static int method_do_shutdown_or_sleep(
} else if (!a)
assert_se(a = handle_action_lookup(action));
r = verify_shutdown_creds(m, message, a, flags, error);
r = manager_verify_shutdown_creds(m, message, a, flags, error);
if (r != 0)
return r;
@@ -2793,7 +2660,7 @@ static int method_schedule_shutdown(sd_bus_message *message, void *userdata, sd_
assert_se(a = handle_action_lookup(handle));
assert(a->polkit_action);
r = verify_shutdown_creds(m, message, a, 0, error);
r = manager_verify_shutdown_creds(m, message, a, 0, error);
if (r != 0)
return r;
@@ -2943,7 +2810,7 @@ static int method_can_shutdown_or_sleep(
if (r < 0)
return r;
r = have_multiple_sessions(m, uid);
r = manager_have_multiple_sessions(m, uid);
if (r < 0)
return r;
+148
View File
@@ -1,12 +1,160 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <unistd.h>
#include "sd-bus.h"
#include "sd-event.h"
#include "alloc-util.h"
#include "bus-common-errors.h"
#include "bus-polkit.h"
#include "fs-util.h"
#include "hashmap.h"
#include "login-util.h"
#include "logind.h"
#include "logind-dbus.h"
#include "logind-inhibit.h"
#include "logind-session.h"
#include "logind-shutdown.h"
#include "logind-user.h"
#include "user-record.h"
int manager_have_multiple_sessions(
Manager *m,
uid_t uid) {
Session *session;
assert(m);
/* Check for other users' sessions. Greeter sessions do not
* count, and non-login sessions do not count either. */
HASHMAP_FOREACH(session, m->sessions)
if (SESSION_CLASS_IS_INHIBITOR_LIKE(session->class) &&
session->user->user_record->uid != uid)
return true;
return false;
}
int manager_verify_shutdown_creds(
Manager *m,
sd_bus_message *message,
const HandleActionData *a,
uint64_t flags,
sd_bus_error *error) {
_cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL;
bool multiple_sessions, blocked, interactive;
_unused_ bool error_or_denial = false;
Inhibitor *offending = NULL;
uid_t uid;
int r;
assert(m);
assert(a);
assert(message);
r = sd_bus_query_sender_creds(message, SD_BUS_CREDS_EUID, &creds);
if (r < 0)
return r;
r = sd_bus_creds_get_euid(creds, &uid);
if (r < 0)
return r;
r = manager_have_multiple_sessions(m, uid);
if (r < 0)
return r;
multiple_sessions = r > 0;
blocked = manager_is_inhibited(m, a->inhibit_what, NULL, /* flags= */ 0, uid, &offending);
interactive = flags & SD_LOGIND_INTERACTIVE;
if (multiple_sessions) {
r = bus_verify_polkit_async_full(
message,
a->polkit_action_multiple_sessions,
/* details= */ NULL,
/* good_user= */ UID_INVALID,
interactive ? POLKIT_ALLOW_INTERACTIVE : 0,
&m->polkit_registry,
error);
if (r < 0) {
/* If we get -EBUSY, it means a polkit decision was made, but not for
* this action in particular. Assuming we are blocked on inhibitors,
* ignore that error and allow the decision to be revealed below. */
if (blocked && r == -EBUSY)
error_or_denial = true;
else
return r;
}
if (r == 0)
return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
}
if (blocked) {
PolkitFlags polkit_flags = 0;
/* With a strong inhibitor, if the skip flag is not set, reject outright.
* With a weak inhibitor, if root is asking and the root flag is set, reject outright.
* All else, check polkit first. */
if (!FLAGS_SET(flags, SD_LOGIND_SKIP_INHIBITORS) &&
(offending->mode != INHIBIT_BLOCK_WEAK ||
(uid == 0 && FLAGS_SET(flags, SD_LOGIND_ROOT_CHECK_INHIBITORS)))) {
if (error)
return sd_bus_error_set(error, BUS_ERROR_BLOCKED_BY_INHIBITOR_LOCK,
"Operation denied due to active block inhibitor");
else
return -EACCES;
}
/* We want to always ask here, even for root, to only allow bypassing if explicitly allowed
* by polkit, unless a weak blocker is used, in which case it will be authorized. */
if (offending->mode != INHIBIT_BLOCK_WEAK)
polkit_flags |= POLKIT_ALWAYS_QUERY;
if (interactive)
polkit_flags |= POLKIT_ALLOW_INTERACTIVE;
r = bus_verify_polkit_async_full(
message,
a->polkit_action_ignore_inhibit,
/* details= */ NULL,
/* good_user= */ UID_INVALID,
polkit_flags,
&m->polkit_registry,
error);
if (r < 0)
return r;
if (r == 0)
return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
}
if (!multiple_sessions && !blocked) {
r = bus_verify_polkit_async_full(
message,
a->polkit_action,
/* details= */ NULL,
/* good_user= */ UID_INVALID,
interactive ? POLKIT_ALLOW_INTERACTIVE : 0,
&m->polkit_registry,
error);
if (r < 0)
return r;
if (r == 0)
return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
}
/* If error_or_denial was set above, it means that a polkit denial or
* error was deferred for a future call to bus_verify_polkit_async_full()
* to catch. In any case, it also means that the payload guarded by
* these polkit calls should never be executed, and hence we should
* never reach this point. */
assert(!error_or_denial);
return 0;
}
void manager_reset_scheduled_shutdown(Manager *m) {
assert(m);
+2
View File
@@ -5,4 +5,6 @@
#define SHUTDOWN_SCHEDULE_FILE "/run/systemd/shutdown/scheduled"
int manager_have_multiple_sessions(Manager *m, uid_t uid);
int manager_verify_shutdown_creds(Manager *m, sd_bus_message *message, const HandleActionData *a, uint64_t flags, sd_bus_error *error);
void manager_reset_scheduled_shutdown(Manager *m);