#include <errno.h>
#include <pwd.h>
#include <string.h>
+#include <sys/stat.h>
#include <unistd.h>
#include "sd-device.h"
#include "fileio.h"
#include "format-util.h"
#include "fs-util.h"
+#include "logind-dbus.h"
+#include "logind-seat-dbus.h"
+#include "logind-session-dbus.h"
+#include "logind-user-dbus.h"
#include "logind.h"
#include "missing_capability.h"
#include "mkdir.h"
#include "parse-util.h"
#include "path-util.h"
#include "process-util.h"
+#include "reboot-util.h"
#include "selinux-util.h"
#include "sleep-config.h"
#include "special.h"
#include "unit-name.h"
#include "user-util.h"
#include "utmp-wtmp.h"
+#include "virt.h"
-static int get_sender_session(Manager *m, sd_bus_message *message, sd_bus_error *error, Session **ret) {
+static int get_sender_session(
+ Manager *m,
+ sd_bus_message *message,
+ bool consult_display,
+ sd_bus_error *error,
+ Session **ret) {
_cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL;
+ Session *session = NULL;
const char *name;
- Session *session;
int r;
- /* Get client login session. This is not what you are looking for these days,
- * as apps may instead belong to a user service unit. This includes terminal
- * emulators and hence command-line apps. */
- r = sd_bus_query_sender_creds(message, SD_BUS_CREDS_SESSION|SD_BUS_CREDS_AUGMENT, &creds);
+ /* Acquire the sender's session. This first checks if the sending process is inside a session itself,
+ * and returns that. If not and 'consult_display' is true, this returns the display session of the
+ * owning user of the caller. */
+
+ r = sd_bus_query_sender_creds(message,
+ SD_BUS_CREDS_SESSION|SD_BUS_CREDS_AUGMENT|
+ (consult_display ? SD_BUS_CREDS_OWNER_UID : 0), &creds);
if (r < 0)
return r;
r = sd_bus_creds_get_session(creds, &name);
- if (r == -ENXIO)
- goto err_no_session;
- if (r < 0)
- return r;
+ if (r < 0) {
+ if (r != -ENXIO)
+ return r;
+
+ if (consult_display) {
+ uid_t uid;
+
+ r = sd_bus_creds_get_owner_uid(creds, &uid);
+ if (r < 0) {
+ if (r != -ENXIO)
+ return r;
+ } else {
+ User *user;
+
+ user = hashmap_get(m->users, UID_TO_PTR(uid));
+ if (user)
+ session = user->display;
+ }
+ }
+ } else
+ session = hashmap_get(m->sessions, name);
- session = hashmap_get(m->sessions, name);
if (!session)
- goto err_no_session;
+ return sd_bus_error_setf(error, BUS_ERROR_NO_SESSION_FOR_PID,
+ consult_display ?
+ "Caller does not belong to any known session and doesn't own any suitable session." :
+ "Caller does not belong to any known session.");
*ret = session;
return 0;
-
-err_no_session:
- return sd_bus_error_setf(error, BUS_ERROR_NO_SESSION_FOR_PID,
- "Caller does not belong to any known session");
}
-int manager_get_session_from_creds(Manager *m, sd_bus_message *message, const char *name, sd_bus_error *error, Session **ret) {
+int manager_get_session_from_creds(
+ Manager *m,
+ sd_bus_message *message,
+ const char *name,
+ sd_bus_error *error,
+ Session **ret) {
+
Session *session;
assert(m);
assert(message);
assert(ret);
- if (isempty(name))
- return get_sender_session(m, message, error, ret);
+ if (SEAT_IS_SELF(name)) /* the caller's own session */
+ return get_sender_session(m, message, false, error, ret);
+ if (SEAT_IS_AUTO(name)) /* The caller's own session if they have one, otherwise their user's display session */
+ return get_sender_session(m, message, true, error, ret);
session = hashmap_get(m->sessions, name);
if (!session)
}
static int get_sender_user(Manager *m, sd_bus_message *message, sd_bus_error *error, User **ret) {
-
_cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL;
uid_t uid;
User *user;
return r;
r = sd_bus_creds_get_owner_uid(creds, &uid);
- if (r == -ENXIO)
- goto err_no_user;
- if (r < 0)
- return r;
+ if (r < 0) {
+ if (r != -ENXIO)
+ return r;
+
+ user = NULL;
+ } else
+ user = hashmap_get(m->users, UID_TO_PTR(uid));
- user = hashmap_get(m->users, UID_TO_PTR(uid));
if (!user)
- goto err_no_user;
+ return sd_bus_error_setf(error, BUS_ERROR_NO_USER_FOR_PID,
+ "Caller does not belong to any logged in or lingering user");
*ret = user;
return 0;
-
-err_no_user:
- return sd_bus_error_setf(error, BUS_ERROR_NO_USER_FOR_PID, "Caller does not belong to any logged in user or lingering user");
}
int manager_get_user_from_creds(Manager *m, sd_bus_message *message, uid_t uid, sd_bus_error *error, User **ret) {
user = hashmap_get(m->users, UID_TO_PTR(uid));
if (!user)
- return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_USER, "User ID "UID_FMT" is not logged in or lingering", uid);
+ return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_USER,
+ "User ID "UID_FMT" is not logged in or lingering", uid);
*ret = user;
return 0;
}
-int manager_get_seat_from_creds(Manager *m, sd_bus_message *message, const char *name, sd_bus_error *error, Seat **ret) {
+int manager_get_seat_from_creds(
+ Manager *m,
+ sd_bus_message *message,
+ const char *name,
+ sd_bus_error *error,
+ Seat **ret) {
+
Seat *seat;
int r;
assert(message);
assert(ret);
- if (isempty(name)) {
+ if (SEAT_IS_SELF(name) || SEAT_IS_AUTO(name)) {
Session *session;
- r = manager_get_session_from_creds(m, message, NULL, error, &session);
+ /* Use these special seat names as session names */
+ r = manager_get_session_from_creds(m, message, name, error, &session);
if (r < 0)
return r;
seat = session->seat;
if (!seat)
- return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_SEAT, "Session has no seat.");
+ return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_SEAT, "Session '%s' has no seat.", session->id);
} else {
seat = hashmap_get(m->seats, name);
if (!seat)
return r;
if (!session)
- return sd_bus_error_setf(error, BUS_ERROR_NO_SESSION_FOR_PID, "PID "PID_FMT" does not belong to any known session", pid);
+ return sd_bus_error_setf(error, BUS_ERROR_NO_SESSION_FOR_PID,
+ "PID "PID_FMT" does not belong to any known session", pid);
}
p = session_bus_path(session);
assert_cc(sizeof(pid_t) == sizeof(uint32_t));
assert_cc(sizeof(uid_t) == sizeof(uint32_t));
- r = sd_bus_message_read(message, "uusssssussbss", &uid, &leader, &service, &type, &class, &desktop, &cseat, &vtnr, &tty, &display, &remote, &remote_user, &remote_host);
+ r = sd_bus_message_read(message, "uusssssussbss",
+ &uid, &leader, &service, &type, &class, &desktop, &cseat,
+ &vtnr, &tty, &display, &remote, &remote_user, &remote_host);
if (r < 0)
return r;
else {
t = session_type_from_string(type);
if (t < 0)
- return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid session type %s", type);
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS,
+ "Invalid session type %s", type);
}
if (isempty(class))
else {
c = session_class_from_string(class);
if (c < 0)
- return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid session class %s", class);
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS,
+ "Invalid session class %s", class);
}
if (isempty(desktop))
desktop = NULL;
else {
if (!string_is_safe(desktop))
- return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid desktop string %s", desktop);
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS,
+ "Invalid desktop string %s", desktop);
}
if (isempty(cseat))
else {
seat = hashmap_get(m->seats, cseat);
if (!seat)
- return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_SEAT, "No seat '%s' known", cseat);
+ return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_SEAT,
+ "No seat '%s' known", cseat);
}
if (tty_is_vc(tty)) {
if (!seat)
seat = m->seat0;
else if (seat != m->seat0)
- return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "TTY %s is virtual console but seat %s is not seat0", tty, seat->id);
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS,
+ "TTY %s is virtual console but seat %s is not seat0", tty, seat->id);
v = vtnr_from_tty(tty);
if (v <= 0)
- return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Cannot determine VT number from virtual console TTY %s", tty);
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS,
+ "Cannot determine VT number from virtual console TTY %s", tty);
if (vtnr == 0)
vtnr = (uint32_t) v;
else if (vtnr != (uint32_t) v)
- return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Specified TTY and VT number do not match");
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS,
+ "Specified TTY and VT number do not match");
} else if (tty_is_console(tty)) {
if (!seat)
seat = m->seat0;
else if (seat != m->seat0)
- return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Console TTY specified but seat is not seat0");
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS,
+ "Console TTY specified but seat is not seat0");
if (vtnr != 0)
- return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Console TTY specified but VT number is not 0");
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS,
+ "Console TTY specified but VT number is not 0");
}
if (seat) {
if (seat_has_vts(seat)) {
if (vtnr <= 0 || vtnr > 63)
- return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "VT number out of range");
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS,
+ "VT number out of range");
} else {
if (vtnr != 0)
- return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Seat has no VTs but VT number not 0");
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS,
+ "Seat has no VTs but VT number not 0");
}
}
return r;
}
- /* Check if we are already in a logind session. Or if we are in user@.service which is a special PAM session
- * that avoids creating a logind session. */
+ /* Check if we are already in a logind session. Or if we are in user@.service
+ * which is a special PAM session that avoids creating a logind session. */
r = manager_get_user_by_pid(m, leader, NULL);
if (r < 0)
return r;
if (r > 0)
- return sd_bus_error_setf(error, BUS_ERROR_SESSION_BUSY, "Already running in a session or user slice");
+ return sd_bus_error_setf(error, BUS_ERROR_SESSION_BUSY,
+ "Already running in a session or user slice");
/*
* Old gdm and lightdm start the user-session on the same VT as
return sd_bus_error_setf(error, BUS_ERROR_SESSION_BUSY, "Already occupied by a session");
if (hashmap_size(m->sessions) >= m->sessions_max)
- return sd_bus_error_setf(error, SD_BUS_ERROR_LIMITS_EXCEEDED, "Maximum number of sessions (%" PRIu64 ") reached, refusing further sessions.", m->sessions_max);
+ return sd_bus_error_setf(error, SD_BUS_ERROR_LIMITS_EXCEEDED,
+ "Maximum number of sessions (%" PRIu64 ") reached, refusing further sessions.",
+ m->sessions_max);
(void) audit_session_from_pid(leader, &audit_id);
if (audit_session_is_valid(audit_id)) {
if (asprintf(&id, "%"PRIu32, audit_id) < 0)
return -ENOMEM;
- /* Wut? There's already a session by this name and we
- * didn't find it above? Weird, then let's not trust
- * the audit data and let's better register a new
- * ID */
- if (hashmap_get(m->sessions, id)) {
+ /* Wut? There's already a session by this name and we didn't find it above? Weird, then let's
+ * not trust the audit data and let's better register a new ID */
+ if (hashmap_contains(m->sessions, id)) {
log_warning("Existing logind session ID %s used by new audit session, ignoring.", id);
audit_id = AUDIT_SESSION_INVALID;
id = mfree(id);
if (asprintf(&id, "c%lu", ++m->session_counter) < 0)
return -ENOMEM;
- } while (hashmap_get(m->sessions, id));
+ } while (hashmap_contains(m->sessions, id));
}
- /* If we are not watching utmp aleady, try again */
+ /* The generated names should not clash with 'auto' or 'self' */
+ assert(!SESSION_IS_SELF(id));
+ assert(!SESSION_IS_AUTO(id));
+
+ /* If we are not watching utmp already, try again */
manager_reconnect_utmp(m);
r = manager_add_user_by_uid(m, uid, &user);
assert(message);
assert(m);
- /* Same as ActivateSession() but refuses to work if
- * the seat doesn't match */
+ /* Same as ActivateSession() but refuses to work if the seat doesn't match */
r = sd_bus_message_read(message, "ss", &session_name, &seat_name);
if (r < 0)
return r;
if (session->seat != seat)
- return sd_bus_error_setf(error, BUS_ERROR_SESSION_NOT_ON_SEAT, "Session %s not on seat %s", session_name, seat_name);
+ return sd_bus_error_setf(error, BUS_ERROR_SESSION_NOT_ON_SEAT,
+ "Session %s not on seat %s", session_name, seat_name);
r = session_activate(session);
if (r < 0)
if (r == 0)
return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
- mkdir_p_label("/var/lib/systemd", 0755);
-
+ (void) mkdir_p_label("/var/lib/systemd", 0755);
r = mkdir_safe_label("/var/lib/systemd/linger", 0755, 0, 0, MKDIR_WARN_MODE);
if (r < 0)
return r;
if (r < 0)
return r;
+ if (!path_is_normalized(sysfs))
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Path %s is not normalized", sysfs);
if (!path_startswith(sysfs, "/sys"))
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Path %s is not in /sys", sysfs);
- if (!seat_name_is_valid(seat))
- return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Seat %s is not valid", seat);
+ if (SEAT_IS_SELF(seat) || SEAT_IS_AUTO(seat)) {
+ Seat *found;
+
+ r = manager_get_seat_from_creds(m, message, seat, error, &found);
+ if (r < 0)
+ return r;
+
+ seat = found->id;
+
+ } else if (!seat_name_is_valid(seat)) /* Note that a seat does not have to exist yet for this operation to succeed */
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Seat name %s is not valid", seat);
r = bus_verify_polkit_async(
message,
/* Don't allow multiple jobs being executed at the same time */
if (m->action_what > 0)
- return sd_bus_error_setf(error, BUS_ERROR_OPERATION_IN_PROGRESS, "There's already a shutdown or sleep operation in progress");
+ return sd_bus_error_setf(error, BUS_ERROR_OPERATION_IN_PROGRESS,
+ "There's already a shutdown or sleep operation in progress");
if (sleep_verb) {
r = can_sleep(sleep_verb);
error);
}
+static int property_get_reboot_parameter(
+ 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 *parameter = NULL;
+ int r;
+
+ assert(bus);
+ assert(reply);
+ assert(userdata);
+
+ r = read_reboot_parameter(¶meter);
+ if (r < 0)
+ return r;
+
+ return sd_bus_message_append(reply, "s", parameter);
+}
+
+static int method_set_reboot_parameter(
+ sd_bus_message *message,
+ void *userdata,
+ sd_bus_error *error) {
+
+ Manager *m = userdata;
+ const char *arg;
+ int r;
+
+ assert(message);
+ assert(m);
+
+ r = sd_bus_message_read(message, "s", &arg);
+ if (r < 0)
+ return r;
+
+ r = detect_container();
+ if (r < 0)
+ return r;
+ if (r > 0)
+ return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED,
+ "Reboot parameter not supported in containers, refusing.");
+
+ r = bus_verify_polkit_async(message,
+ CAP_SYS_ADMIN,
+ "org.freedesktop.login1.set-reboot-parameter",
+ NULL,
+ false,
+ UID_INVALID,
+ &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 */
+
+ r = update_reboot_parameter_and_warn(arg, false);
+ if (r < 0)
+ return r;
+
+ return sd_bus_reply_method_return(message, NULL);
+}
+
+static int method_can_reboot_parameter(
+ sd_bus_message *message,
+ void *userdata,
+ sd_bus_error *error) {
+
+ Manager *m = userdata;
+ int r;
+
+ assert(message);
+ assert(m);
+
+ r = detect_container();
+ if (r < 0)
+ return r;
+ if (r > 0) /* Inside containers, specifying a reboot parameter, doesn't make much sense */
+ return sd_bus_reply_method_return(message, "s", "na");
+
+ return return_test_polkit(
+ message,
+ CAP_SYS_ADMIN,
+ "org.freedesktop.login1.set-reboot-parameter",
+ NULL,
+ UID_INVALID,
+ error);
+}
+
static int property_get_reboot_to_firmware_setup(
sd_bus *bus,
const char *path,
w = inhibit_what_from_string(what);
if (w <= 0)
- return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid what specification %s", what);
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS,
+ "Invalid what specification %s", what);
mm = inhibit_mode_from_string(mode);
if (mm < 0)
- return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid mode specification %s", mode);
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS,
+ "Invalid mode specification %s", mode);
/* Delay is only supported for shutdown/sleep */
if (mm == INHIBIT_DELAY && (w & ~(INHIBIT_SHUTDOWN|INHIBIT_SLEEP)))
- return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Delay inhibitors only supported for shutdown and sleep");
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS,
+ "Delay inhibitors only supported for shutdown and sleep");
/* Don't allow taking delay locks while we are already
* executing the operation. We shouldn't create the impression
* that the lock was successful if the machine is about to go
* down/suspend any moment. */
if (m->action_what & w)
- return sd_bus_error_setf(error, BUS_ERROR_OPERATION_IN_PROGRESS, "The operation inhibition has been requested for is already running");
+ return sd_bus_error_setf(error, BUS_ERROR_OPERATION_IN_PROGRESS,
+ "The operation inhibition has been requested for is already running");
r = bus_verify_polkit_async(
message,
return r;
if (hashmap_size(m->inhibitors) >= m->inhibitors_max)
- return sd_bus_error_setf(error, SD_BUS_ERROR_LIMITS_EXCEEDED, "Maximum number of inhibitors (%" PRIu64 ") reached, refusing further inhibitors.", m->inhibitors_max);
+ return sd_bus_error_setf(error, SD_BUS_ERROR_LIMITS_EXCEEDED,
+ "Maximum number of inhibitors (%" PRIu64 ") reached, refusing further inhibitors.",
+ m->inhibitors_max);
do {
id = mfree(id);
SD_BUS_PROPERTY("KillOnlyUsers", "as", NULL, offsetof(Manager, kill_only_users), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("KillExcludeUsers", "as", NULL, offsetof(Manager, kill_exclude_users), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("KillUserProcesses", "b", NULL, offsetof(Manager, kill_user_processes), SD_BUS_VTABLE_PROPERTY_CONST),
+ SD_BUS_PROPERTY("RebootParameter", "s", property_get_reboot_parameter, 0, 0),
SD_BUS_PROPERTY("RebootToFirmwareSetup", "b", property_get_reboot_to_firmware_setup, 0, 0),
SD_BUS_PROPERTY("RebootToBootLoaderMenu", "t", property_get_reboot_to_boot_loader_menu, 0, 0),
SD_BUS_PROPERTY("RebootToBootLoaderEntry", "s", property_get_reboot_to_boot_loader_entry, 0, 0),
SD_BUS_METHOD("ScheduleShutdown", "st", NULL, method_schedule_shutdown, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("CancelScheduledShutdown", NULL, "b", method_cancel_scheduled_shutdown, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("Inhibit", "ssss", "h", method_inhibit, SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD("CanRebootParameter", NULL, "s", method_can_reboot_parameter, SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD("SetRebootParameter", "s", NULL, method_set_reboot_parameter, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("CanRebootToFirmwareSetup", NULL, "s", method_can_reboot_to_firmware_setup, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("SetRebootToFirmwareSetup", "b", NULL, method_set_reboot_to_firmware_setup, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("CanRebootToBootLoaderMenu", NULL, "s", method_can_reboot_to_boot_loader_menu, SD_BUS_VTABLE_UNPRIVILEGED),
if (result && !streq(result, "done")) {
_cleanup_(sd_bus_error_free) sd_bus_error e = SD_BUS_ERROR_NULL;
- sd_bus_error_setf(&e, BUS_ERROR_JOB_FAILED, "Start job for unit '%s' failed with '%s'", unit, result);
+ sd_bus_error_setf(&e, BUS_ERROR_JOB_FAILED,
+ "Start job for unit '%s' failed with '%s'", unit, result);
return session_send_create_reply(s, &e);
}