const char *class;
const char *desktop;
const char *seat;
- const uint32_t vtnr;
+ uint32_t vtnr;
const char *tty;
const char *display;
- const bool remote;
+ bool remote;
const char *remote_user;
const char *remote_host;
const char *memory_max;
const char *cpu_weight;
const char *io_weight;
const char *runtime_max_sec;
+ bool incomplete;
} SessionContext;
static int create_session_message(
return 0;
}
-static int import_shell_credentials(pam_handle_t *handle) {
-
- static const char *const propagate[] = {
- "shell.prompt.prefix", "SHELL_PROMPT_PREFIX",
- "shell.prompt.suffix", "SHELL_PROMPT_SUFFIX",
- "shell.welcome", "SHELL_WELCOME",
- NULL
- };
- int r;
-
- assert(handle);
-
- STRV_FOREACH_PAIR(k, v, propagate) {
- r = propagate_credential_to_environment(handle, *k, *v);
- if (r != PAM_SUCCESS)
- return r;
- }
-
- return PAM_SUCCESS;
-}
-
-_public_ PAM_EXTERN int pam_sm_open_session(
+static void session_context_mangle(
pam_handle_t *handle,
- int flags,
- int argc, const char **argv) {
-
- /* Let's release the D-Bus connection once this function exits, after all the session might live
- * quite a long time, and we are not going to process the bus connection in that time, so let's
- * better close before the daemon kicks us off because we are not processing anything. */
- _cleanup_(pam_bus_data_disconnectp) PamBusData *d = NULL;
- _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
- _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL;
- const char
- *id, *object_path, *runtime_path,
- *service = NULL,
- *tty = NULL, *display = NULL,
- *remote_user = NULL, *remote_host = NULL,
- *seat = NULL,
- *type = NULL, *class = NULL,
- *class_pam = NULL, *type_pam = NULL, *desktop = NULL, *desktop_pam = NULL,
- *memory_max = NULL, *tasks_max = NULL, *cpu_weight = NULL, *io_weight = NULL, *runtime_max_sec = NULL;
- uint64_t default_capability_bounding_set = UINT64_MAX, default_capability_ambient_set = UINT64_MAX;
- _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
- _cleanup_(user_record_unrefp) UserRecord *ur = NULL;
- int session_fd = -EBADF, existing, r;
- bool debug = false, remote, incomplete;
- uint32_t vtnr = 0;
- uid_t original_uid;
+ SessionContext *c,
+ UserRecord *ur,
+ bool debug) {
assert(handle);
+ assert(c);
+ assert(ur);
- pam_log_setup();
-
- if (parse_argv(handle,
- argc, argv,
- &class_pam,
- &type_pam,
- &desktop_pam,
- &debug,
- &default_capability_bounding_set,
- &default_capability_ambient_set) < 0)
- return PAM_SESSION_ERR;
-
- pam_debug_syslog(handle, debug, "pam-systemd initializing");
-
- r = acquire_user_record(handle, &ur);
- if (r != PAM_SUCCESS)
- return r;
-
- /* Make most of this a NOP on non-logind systems */
- if (!logind_running())
- goto success;
-
- r = pam_get_item_many(
- handle,
- PAM_SERVICE, &service,
- PAM_XDISPLAY, &display,
- PAM_TTY, &tty,
- PAM_RUSER, &remote_user,
- PAM_RHOST, &remote_host);
- if (r != PAM_SUCCESS)
- return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to get PAM items: @PAMERR@");
-
- seat = getenv_harder(handle, "XDG_SEAT", NULL);
- vtnr = getenv_harder_uint32(handle, "XDG_VTNR", 0);
- type = getenv_harder(handle, "XDG_SESSION_TYPE", type_pam);
- class = getenv_harder(handle, "XDG_SESSION_CLASS", class_pam);
- desktop = getenv_harder(handle, "XDG_SESSION_DESKTOP", desktop_pam);
- incomplete = getenv_harder_bool(handle, "XDG_SESSION_INCOMPLETE", false);
-
- if (streq_ptr(service, "systemd-user")) {
+ if (streq_ptr(c->service, "systemd-user")) {
/* If we detect that we are running in the "systemd-user" PAM stack, then let's patch the class to
* 'manager' if not set, simply for robustness reasons. */
- type = "unspecified";
- class = IN_SET(user_record_disposition(ur), USER_INTRINSIC, USER_SYSTEM, USER_DYNAMIC) ?
+ c->type = "unspecified";
+ c->class = IN_SET(user_record_disposition(ur), USER_INTRINSIC, USER_SYSTEM, USER_DYNAMIC) ?
"manager-early" : "manager";
- tty = NULL;
+ c->tty = NULL;
- } else if (tty && strchr(tty, ':')) {
+ } else if (c->tty && strchr(c->tty, ':')) {
/* A tty with a colon is usually an X11 display, placed there to show up in utmp. We rearrange things
* and don't pretend that an X display was a tty. */
- if (isempty(display))
- display = tty;
- tty = NULL;
+ if (isempty(c->display))
+ c->display = c->tty;
+ c->tty = NULL;
- } else if (streq_ptr(tty, "cron")) {
+ } else if (streq_ptr(c->tty, "cron")) {
/* cron is setting PAM_TTY to "cron" for some reason (the commit carries no information why, but
* probably because it wants to set it to something as pam_time/pam_access/… require PAM_TTY to be set
* (as they otherwise even try to update it!) — but cron doesn't actually allocate a TTY for its forked
* off processes.) */
- type = "unspecified";
- class = "background";
- tty = NULL;
+ c->type = "unspecified";
+ c->class = "background";
+ c->tty = NULL;
- } else if (streq_ptr(tty, "ssh")) {
+ } else if (streq_ptr(c->tty, "ssh")) {
/* ssh has been setting PAM_TTY to "ssh" (for the same reason as cron does this, see above. For further
* details look for "PAM_TTY_KLUDGE" in the openssh sources). */
- type = "tty";
- class = "user";
- tty = NULL; /* This one is particularly sad, as this means that ssh sessions — even though usually
- * associated with a pty — won't be tracked by their tty in logind. This is because ssh
- * does the PAM session registration early for new connections, and registers a pty only
- * much later (this is because it doesn't know yet if it needs one at all, as whether to
- * register a pty or not is negotiated much later in the protocol). */
-
- } else if (tty)
+ c->type = "tty";
+ c->class = "user";
+ c->tty = NULL; /* This one is particularly sad, as this means that ssh sessions — even though
+ * usually associated with a pty — won't be tracked by their tty in
+ * logind. This is because ssh does the PAM session registration early for new
+ * connections, and registers a pty only much later (this is because it doesn't
+ * know yet if it needs one at all, as whether to register a pty or not is
+ * negotiated much later in the protocol). */
+
+ } else if (c->tty)
/* Chop off leading /dev prefix that some clients specify, but others do not. */
- tty = skip_dev_prefix(tty);
+ c->tty = skip_dev_prefix(c->tty);
- if (!isempty(display) && !vtnr) {
- if (isempty(seat))
- (void) get_seat_from_display(display, &seat, &vtnr);
- else if (streq(seat, "seat0"))
- (void) get_seat_from_display(display, NULL, &vtnr);
+ if (!isempty(c->display) && !c->vtnr) {
+ if (isempty(c->seat))
+ (void) get_seat_from_display(c->display, &c->seat, &c->vtnr);
+ else if (streq(c->seat, "seat0"))
+ (void) get_seat_from_display(c->display, /* seat= */ NULL, &c->vtnr);
}
- if (seat && !streq(seat, "seat0") && vtnr != 0) {
- pam_debug_syslog(handle, debug, "Ignoring vtnr %"PRIu32" for %s which is not seat0", vtnr, seat);
- vtnr = 0;
+ if (c->seat && !streq(c->seat, "seat0") && c->vtnr != 0) {
+ pam_debug_syslog(handle, debug, "Ignoring vtnr %"PRIu32" for %s which is not seat0", c->vtnr, c->seat);
+ c->vtnr = 0;
}
- if (isempty(type))
- type = !isempty(display) ? "x11" :
- !isempty(tty) ? "tty" : "unspecified";
+ if (isempty(c->type))
+ c->type = !isempty(c->display) ? "x11" :
+ !isempty(c->tty) ? "tty" : "unspecified";
- if (isempty(class))
- class = streq(type, "unspecified") ? "background" :
+ if (isempty(c->class))
+ c->class = streq(c->type, "unspecified") ? "background" :
((IN_SET(user_record_disposition(ur), USER_INTRINSIC, USER_SYSTEM, USER_DYNAMIC) &&
- streq(type, "tty")) ? "user-early" : "user");
+ streq(c->type, "tty")) ? "user-early" : "user");
- if (incomplete) {
- if (streq(class, "user"))
- class = "user-incomplete";
+ if (c->incomplete) {
+ if (streq(c->class, "user"))
+ c->class = "user-incomplete";
else
- pam_syslog_pam_error(handle, LOG_WARNING, 0, "PAM session of class '%s' is incomplete, which is not supported, ignoring.", class);
+ pam_syslog_pam_error(handle, LOG_WARNING, 0, "PAM session of class '%s' is incomplete, which is not supported, ignoring.", c->class);
}
- remote = !isempty(remote_host) && !is_localhost(remote_host);
+ c->remote = !isempty(c->remote_host) && !is_localhost(c->remote_host);
+}
- r = pam_get_data(handle, "systemd.memory_max", (const void **)&memory_max);
- if (!IN_SET(r, PAM_SUCCESS, PAM_NO_MODULE_DATA))
- return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to get PAM systemd.memory_max data: @PAMERR@");
- r = pam_get_data(handle, "systemd.tasks_max", (const void **)&tasks_max);
- if (!IN_SET(r, PAM_SUCCESS, PAM_NO_MODULE_DATA))
- return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to get PAM systemd.tasks_max data: @PAMERR@");
- r = pam_get_data(handle, "systemd.cpu_weight", (const void **)&cpu_weight);
- if (!IN_SET(r, PAM_SUCCESS, PAM_NO_MODULE_DATA))
- return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to get PAM systemd.cpu_weight data: @PAMERR@");
- r = pam_get_data(handle, "systemd.io_weight", (const void **)&io_weight);
- if (!IN_SET(r, PAM_SUCCESS, PAM_NO_MODULE_DATA))
- return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to get PAM systemd.io_weight data: @PAMERR@");
- r = pam_get_data(handle, "systemd.runtime_max_sec", (const void **)&runtime_max_sec);
- if (!IN_SET(r, PAM_SUCCESS, PAM_NO_MODULE_DATA))
- return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to get PAM systemd.runtime_max_sec data: @PAMERR@");
+static int register_session(
+ pam_handle_t *handle,
+ SessionContext *c,
+ UserRecord *ur,
+ bool debug,
+ char **ret_seat) {
+
+ /* Let's release the D-Bus connection once this function exits, after all the session might live
+ * quite a long time, and we are not going to process the bus connection in that time, so let's
+ * better close before the daemon kicks us off because we are not processing anything. */
+ _cleanup_(pam_bus_data_disconnectp) PamBusData *d = NULL;
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL;
+ _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
+ int r;
+
+ assert(handle);
+ assert(c);
+ assert(ur);
+ assert(ret_seat);
+
+ /* Make most of this a NOP on non-logind systems */
+ if (!logind_running())
+ goto skip;
/* Talk to logind over the message bus */
r = pam_acquire_bus_connection(handle, "pam-systemd", debug, &bus, &d);
"Asking logind to create session: "
"uid="UID_FMT" pid="PID_FMT" service=%s type=%s class=%s desktop=%s seat=%s vtnr=%"PRIu32" tty=%s display=%s remote=%s remote_user=%s remote_host=%s",
ur->uid, getpid_cached(),
- strempty(service),
- type, class, strempty(desktop),
- strempty(seat), vtnr, strempty(tty), strempty(display),
- yes_no(remote), strempty(remote_user), strempty(remote_host));
+ strempty(c->service),
+ c->type, c->class, strempty(c->desktop),
+ strempty(c->seat), c->vtnr, strempty(c->tty), strempty(c->display),
+ yes_no(c->remote), strempty(c->remote_user), strempty(c->remote_host));
pam_debug_syslog(handle, debug,
"Session limits: "
"memory_max=%s tasks_max=%s cpu_weight=%s io_weight=%s runtime_max_sec=%s",
- strna(memory_max), strna(tasks_max), strna(cpu_weight), strna(io_weight), strna(runtime_max_sec));
-
- const SessionContext context = {
- .service = service,
- .type = type,
- .class = class,
- .desktop = desktop,
- .seat = seat,
- .vtnr = vtnr,
- .tty = tty,
- .display = display,
- .remote = remote,
- .remote_user = remote_user,
- .remote_host = remote_host,
- .memory_max = memory_max,
- .tasks_max = tasks_max,
- .cpu_weight = cpu_weight,
- .io_weight = io_weight,
- .runtime_max_sec = runtime_max_sec,
- };
+ strna(c->memory_max), strna(c->tasks_max), strna(c->cpu_weight), strna(c->io_weight), strna(c->runtime_max_sec));
- r = create_session_message(bus,
- handle,
- ur,
- &context,
- /* avoid_pidfd = */ false,
- &m);
+ r = create_session_message(
+ bus,
+ handle,
+ ur,
+ c,
+ /* avoid_pidfd = */ false,
+ &m);
if (r < 0)
return pam_bus_log_create_error(handle, r);
r = create_session_message(bus,
handle,
ur,
- &context,
+ c,
/* avoid_pidfd = */ true,
&m);
if (r < 0)
/* We are already in a session, don't do anything */
pam_debug_syslog(handle, debug,
"Not creating session: %s", bus_error_message(&error, r));
- goto success;
+ goto skip;
}
pam_syslog(handle, LOG_ERR,
return PAM_SESSION_ERR;
}
- r = sd_bus_message_read(reply,
- "soshusub",
- &id,
- &object_path,
- &runtime_path,
- &session_fd,
- &original_uid,
- &seat,
- &vtnr,
- &existing);
+ const char *id, *object_path, *runtime_path, *real_seat;
+ int session_fd = -EBADF, existing;
+ uint32_t original_uid, real_vtnr;
+ r = sd_bus_message_read(
+ reply,
+ "soshusub",
+ &id,
+ &object_path,
+ &runtime_path,
+ &session_fd,
+ &original_uid,
+ &real_seat,
+ &real_vtnr,
+ &existing);
if (r < 0)
return pam_bus_log_parse_error(handle, r);
pam_debug_syslog(handle, debug,
"Reply from logind: "
"id=%s object_path=%s runtime_path=%s session_fd=%d seat=%s vtnr=%u original_uid=%u",
- id, object_path, runtime_path, session_fd, seat, vtnr, original_uid);
+ id, object_path, runtime_path, session_fd, real_seat, real_vtnr, original_uid);
/* Please update manager_default_environment() in core/manager.c accordingly if more session envvars
* shall be added. */
* somewhere else (for example PAM module parameters). Let's now update the environment variables, so that this
* data is inherited into the session processes, and programs can rely on them to be initialized. */
- r = update_environment(handle, "XDG_SESSION_TYPE", type);
+ r = update_environment(handle, "XDG_SESSION_TYPE", c->type);
if (r != PAM_SUCCESS)
return r;
- r = update_environment(handle, "XDG_SESSION_CLASS", class);
+ r = update_environment(handle, "XDG_SESSION_CLASS", c->class);
if (r != PAM_SUCCESS)
return r;
- r = update_environment(handle, "XDG_SESSION_DESKTOP", desktop);
+ r = update_environment(handle, "XDG_SESSION_DESKTOP", c->desktop);
if (r != PAM_SUCCESS)
return r;
- r = update_environment(handle, "XDG_SEAT", seat);
+ r = update_environment(handle, "XDG_SEAT", real_seat);
if (r != PAM_SUCCESS)
return r;
- if (vtnr > 0) {
- char buf[DECIMAL_STR_MAX(vtnr)];
- sprintf(buf, "%u", vtnr);
+ if (real_vtnr > 0) {
+ char buf[DECIMAL_STR_MAX(real_vtnr)];
+ xsprintf(buf, "%u", real_vtnr);
r = update_environment(handle, "XDG_VTNR", buf);
if (r != PAM_SUCCESS)
TAKE_FD(fd);
}
-success:
+ /* Everything worked, hence let's patch in the data we learned. Since 'real_set' points into the
+ * D-Bus message, let's copy it and return it as a buffer */
+ char *rs = strdup(real_seat);
+ if (!rs)
+ return pam_log_oom(handle);
+
+ c->seat = *ret_seat = rs;
+ c->vtnr = real_vtnr;
+
+ return PAM_SUCCESS;
+
+skip:
+ *ret_seat = NULL;
+ return PAM_SUCCESS;
+}
+
+static int import_shell_credentials(pam_handle_t *handle) {
+
+ static const char *const propagate[] = {
+ "shell.prompt.prefix", "SHELL_PROMPT_PREFIX",
+ "shell.prompt.suffix", "SHELL_PROMPT_SUFFIX",
+ "shell.welcome", "SHELL_WELCOME",
+ NULL
+ };
+ int r;
+
+ assert(handle);
+
+ STRV_FOREACH_PAIR(k, v, propagate) {
+ r = propagate_credential_to_environment(handle, *k, *v);
+ if (r != PAM_SUCCESS)
+ return r;
+ }
+
+ return PAM_SUCCESS;
+}
+
+_public_ PAM_EXTERN int pam_sm_open_session(
+ pam_handle_t *handle,
+ int flags,
+ int argc, const char **argv) {
+
+ int r;
+
+ assert(handle);
+
+ pam_log_setup();
+
+ uint64_t default_capability_bounding_set = UINT64_MAX, default_capability_ambient_set = UINT64_MAX;
+ const char *class_pam = NULL, *type_pam = NULL, *desktop_pam = NULL;
+ bool debug = false;
+ if (parse_argv(handle,
+ argc, argv,
+ &class_pam,
+ &type_pam,
+ &desktop_pam,
+ &debug,
+ &default_capability_bounding_set,
+ &default_capability_ambient_set) < 0)
+ return PAM_SESSION_ERR;
+
+ pam_debug_syslog(handle, debug, "pam-systemd initializing");
+
+ _cleanup_(user_record_unrefp) UserRecord *ur = NULL;
+ r = acquire_user_record(handle, &ur);
+ if (r != PAM_SUCCESS)
+ return r;
+
+ SessionContext c = {};
+ r = pam_get_item_many(
+ handle,
+ PAM_SERVICE, &c.service,
+ PAM_XDISPLAY, &c.display,
+ PAM_TTY, &c.tty,
+ PAM_RUSER, &c.remote_user,
+ PAM_RHOST, &c.remote_host);
+ if (r != PAM_SUCCESS)
+ return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to get PAM items: @PAMERR@");
+
+ c.seat = getenv_harder(handle, "XDG_SEAT", NULL);
+ c.vtnr = getenv_harder_uint32(handle, "XDG_VTNR", 0);
+ c.type = getenv_harder(handle, "XDG_SESSION_TYPE", type_pam);
+ c.class = getenv_harder(handle, "XDG_SESSION_CLASS", class_pam);
+ c.desktop = getenv_harder(handle, "XDG_SESSION_DESKTOP", desktop_pam);
+ c.incomplete = getenv_harder_bool(handle, "XDG_SESSION_INCOMPLETE", false);
+
+ r = pam_get_data(handle, "systemd.memory_max", (const void **)&c.memory_max);
+ if (!IN_SET(r, PAM_SUCCESS, PAM_NO_MODULE_DATA))
+ return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to get PAM systemd.memory_max data: @PAMERR@");
+ r = pam_get_data(handle, "systemd.tasks_max", (const void **)&c.tasks_max);
+ if (!IN_SET(r, PAM_SUCCESS, PAM_NO_MODULE_DATA))
+ return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to get PAM systemd.tasks_max data: @PAMERR@");
+ r = pam_get_data(handle, "systemd.cpu_weight", (const void **)&c.cpu_weight);
+ if (!IN_SET(r, PAM_SUCCESS, PAM_NO_MODULE_DATA))
+ return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to get PAM systemd.cpu_weight data: @PAMERR@");
+ r = pam_get_data(handle, "systemd.io_weight", (const void **)&c.io_weight);
+ if (!IN_SET(r, PAM_SUCCESS, PAM_NO_MODULE_DATA))
+ return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to get PAM systemd.io_weight data: @PAMERR@");
+ r = pam_get_data(handle, "systemd.runtime_max_sec", (const void **)&c.runtime_max_sec);
+ if (!IN_SET(r, PAM_SUCCESS, PAM_NO_MODULE_DATA))
+ return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to get PAM systemd.runtime_max_sec data: @PAMERR@");
+
+ session_context_mangle(handle, &c, ur, debug);
+
+ _cleanup_free_ char *seat_buffer = NULL;
+ r = register_session(handle, &c, ur, debug, &seat_buffer);
+ if (r != PAM_SUCCESS)
+ return r;
+
r = import_shell_credentials(handle);
if (r != PAM_SUCCESS)
return r;
if (default_capability_ambient_set == UINT64_MAX)
- default_capability_ambient_set = pick_default_capability_ambient_set(ur, service, seat);
+ default_capability_ambient_set = pick_default_capability_ambient_set(ur, c.service, c.seat);
return apply_user_record_settings(handle, ur, debug, default_capability_bounding_set, default_capability_ambient_set);
}