pam_systemd: issue context OSC sequences when allocating new TTY session
note: this also adds making a copy of the session type string after registering the session. That's because we need to check the session type we settled on later to condition out the OSC sequence (because it should only be issued on TTY sessions). However, the session type string originally quite likely points into the PAM environment block, which we update in the meantime, invalidating that pointer. hence, make an explicit copy first, and use that.
This commit is contained in:
parent
575922c914
commit
d8069b8add
src
|
@ -39,11 +39,13 @@
|
|||
#include "format-util.h"
|
||||
#include "fs-util.h"
|
||||
#include "hostname-util.h"
|
||||
#include "io-util.h"
|
||||
#include "json-util.h"
|
||||
#include "locale-util.h"
|
||||
#include "login-util.h"
|
||||
#include "macro.h"
|
||||
#include "missing_syscall.h"
|
||||
#include "osc-context.h"
|
||||
#include "pam-util.h"
|
||||
#include "parse-util.h"
|
||||
#include "path-util.h"
|
||||
|
@ -1081,6 +1083,7 @@ static int register_session(
|
|||
UserRecord *ur,
|
||||
bool debug,
|
||||
char **ret_seat,
|
||||
char **ret_type,
|
||||
char **ret_runtime_dir) {
|
||||
|
||||
int r;
|
||||
|
@ -1089,19 +1092,20 @@ static int register_session(
|
|||
assert(c);
|
||||
assert(ur);
|
||||
assert(ret_seat);
|
||||
assert(ret_type);
|
||||
assert(ret_runtime_dir);
|
||||
|
||||
/* We don't register session class none with logind */
|
||||
if (streq(c->class, "none")) {
|
||||
pam_debug_syslog(handle, debug, "Skipping logind registration for session class none.");
|
||||
*ret_seat = *ret_runtime_dir = NULL;
|
||||
*ret_seat = *ret_type = *ret_runtime_dir = NULL;
|
||||
return PAM_SUCCESS;
|
||||
}
|
||||
|
||||
/* Make most of this a NOP on non-logind systems */
|
||||
if (!logind_running()) {
|
||||
pam_debug_syslog(handle, debug, "Skipping logind registration as logind is not running.");
|
||||
*ret_seat = *ret_runtime_dir = NULL;
|
||||
*ret_seat = *ret_type = *ret_runtime_dir = NULL;
|
||||
return PAM_SUCCESS;
|
||||
}
|
||||
|
||||
|
@ -1174,7 +1178,7 @@ static int register_session(
|
|||
if (streq_ptr(error_id, "io.systemd.Login.AlreadySessionMember")) {
|
||||
/* We are already in a session, don't do anything */
|
||||
pam_debug_syslog(handle, debug, "Not creating session: %s", error_id);
|
||||
*ret_seat = *ret_runtime_dir = NULL;
|
||||
*ret_seat = *ret_type= *ret_runtime_dir = NULL;
|
||||
return PAM_SUCCESS;
|
||||
}
|
||||
if (error_id)
|
||||
|
@ -1271,7 +1275,7 @@ static int register_session(
|
|||
/* We are already in a session, don't do anything */
|
||||
pam_debug_syslog(handle, debug,
|
||||
"Not creating session: %s", bus_error_message(&error, r));
|
||||
*ret_seat = *ret_runtime_dir = NULL;
|
||||
*ret_seat = *ret_type = *ret_runtime_dir = NULL;
|
||||
return PAM_SUCCESS;
|
||||
}
|
||||
|
||||
|
@ -1311,6 +1315,10 @@ static int register_session(
|
|||
* 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. */
|
||||
|
||||
_cleanup_free_ char *real_type = strdup(c->type); /* make copy because this might point to env block, which we are going to update shortly */
|
||||
if (!real_type)
|
||||
return pam_log_oom(handle);
|
||||
|
||||
r = update_environment(handle, "XDG_SESSION_TYPE", c->type);
|
||||
if (r != PAM_SUCCESS)
|
||||
return r;
|
||||
|
@ -1367,6 +1375,7 @@ static int register_session(
|
|||
|
||||
c->vtnr = real_vtnr;
|
||||
c->seat = *ret_seat = TAKE_PTR(rs);
|
||||
c->type = *ret_type = TAKE_PTR(real_type);
|
||||
*ret_runtime_dir = TAKE_PTR(rt);
|
||||
|
||||
return PAM_SUCCESS;
|
||||
|
@ -1574,6 +1583,126 @@ static int setup_environment(
|
|||
return export_legacy_dbus_address(handle, runtime_directory);
|
||||
}
|
||||
|
||||
static int open_osc_context(pam_handle_t *handle, const char *session_type, UserRecord *ur) {
|
||||
int r;
|
||||
|
||||
assert(handle);
|
||||
assert(ur);
|
||||
|
||||
/* If this is a TTY session, then output the session start OSC sequence */
|
||||
|
||||
if (!streq_ptr(session_type, "tty"))
|
||||
return PAM_SUCCESS;
|
||||
|
||||
const char *e = pam_getenv(handle, "TERM");
|
||||
if (!e)
|
||||
e = getenv("TERM");
|
||||
if (streq_ptr(e, "dumb"))
|
||||
return PAM_SUCCESS;
|
||||
|
||||
/* NB: we output directly to stdout, instead of going via pam_info() or so, because that's too
|
||||
* high-level for us, as it suffixes the output with a newline, expecting a full blown text message
|
||||
* as prompt string, not just an ANSI sequence. Note that PAM's conv_misc() actually goes to stdout
|
||||
* anyway, hence let's do so here too, but only after careful validation. */
|
||||
if (!isatty(STDOUT_FILENO))
|
||||
return PAM_SUCCESS;
|
||||
|
||||
/* Keep a reference to the TTY we are operating on, so that we can issue the OSC close sequence also
|
||||
* if the TTY is already closed. We use an O_PATH reference here, rather than a properly opened fd,
|
||||
* so that we don't delay tty hang-up. */
|
||||
_cleanup_close_ int tty_opath_fd = fd_reopen(STDOUT_FILENO, O_PATH|O_CLOEXEC);
|
||||
if (tty_opath_fd < 0)
|
||||
pam_syslog_errno(handle, LOG_DEBUG, tty_opath_fd, "Failed to pin TTY, ignoring: %m");
|
||||
else
|
||||
tty_opath_fd = fd_move_above_stdio(tty_opath_fd);
|
||||
|
||||
_cleanup_free_ char *osc = NULL;
|
||||
sd_id128_t osc_id;
|
||||
r = osc_context_open_session(
|
||||
ur->user_name,
|
||||
pam_getenv(handle, "XDG_SESSION_ID"),
|
||||
&osc,
|
||||
&osc_id);
|
||||
if (r < 0)
|
||||
return pam_syslog_errno(handle, LOG_ERR, r, "Failed to prepare OSC sequence: %m");
|
||||
|
||||
r = loop_write(STDOUT_FILENO, osc, SIZE_MAX);
|
||||
if (r < 0)
|
||||
return pam_syslog_errno(handle, LOG_ERR, r, "Failed to write OSC sequence to TTY: %m");
|
||||
|
||||
/* Remember the OSC context id, so that we can close it cleanly later */
|
||||
_cleanup_free_ sd_id128_t *osc_id_copy = newdup(sd_id128_t, &osc_id, 1);
|
||||
if (!osc_id_copy)
|
||||
return pam_log_oom(handle);
|
||||
|
||||
r = pam_set_data(handle, "systemd.osc-context-id", osc_id_copy, pam_cleanup_free);
|
||||
if (r != PAM_SUCCESS)
|
||||
return pam_syslog_pam_error(handle, LOG_ERR, r,
|
||||
"Failed to set PAM OSC sequence ID data: @PAMERR@");
|
||||
|
||||
TAKE_PTR(osc_id_copy);
|
||||
|
||||
if (tty_opath_fd >= 0) {
|
||||
r = pam_set_data(handle, "systemd.osc-context-fd", FD_TO_PTR(tty_opath_fd), pam_cleanup_close);
|
||||
if (r != PAM_SUCCESS)
|
||||
return pam_syslog_pam_error(handle, LOG_ERR, r,
|
||||
"Failed to set PAM OSC sequence fd data: @PAMERR@");
|
||||
|
||||
TAKE_FD(tty_opath_fd);
|
||||
}
|
||||
|
||||
return PAM_SUCCESS;
|
||||
}
|
||||
|
||||
static int close_osc_context(pam_handle_t *handle) {
|
||||
int r;
|
||||
|
||||
assert(handle);
|
||||
|
||||
const void *p;
|
||||
int tty_opath_fd = -EBADF;
|
||||
r = pam_get_data(handle, "systemd.osc-context-fd", &p);
|
||||
if (r == PAM_SUCCESS)
|
||||
tty_opath_fd = PTR_TO_FD(p);
|
||||
else if (r != PAM_NO_MODULE_DATA)
|
||||
return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to get PAM OSC context fd: @PAMERR@");
|
||||
if (tty_opath_fd < 0)
|
||||
return PAM_SUCCESS;
|
||||
|
||||
const sd_id128_t *osc_id = NULL;
|
||||
r = pam_get_data(handle, "systemd.osc-context-id", (const void**) &osc_id);
|
||||
if (!IN_SET(r, PAM_SUCCESS, PAM_NO_MODULE_DATA))
|
||||
return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to get PAM OSC context id data: @PAMERR@");
|
||||
if (!osc_id)
|
||||
return PAM_SUCCESS;
|
||||
|
||||
/* Now open the original TTY again, so that we can write on it */
|
||||
_cleanup_close_ int fd = fd_reopen(tty_opath_fd, O_WRONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY);
|
||||
if (fd < 0) {
|
||||
pam_syslog_errno(handle, LOG_DEBUG, fd, "Failed to reopen TTY, ignoring: %m");
|
||||
return PAM_SUCCESS;
|
||||
}
|
||||
|
||||
/* /bin/login calls us with fds 0, 1, 2 closed, which is just weird. Let's step outside of that
|
||||
* range, just in case pam_syslog() or so logs to stderr */
|
||||
fd = fd_move_above_stdio(fd);
|
||||
|
||||
/* Safety check, let's verify this is a valid TTY we just opened */
|
||||
if (!isatty(fd))
|
||||
return PAM_SUCCESS;
|
||||
|
||||
_cleanup_free_ char *osc = NULL;
|
||||
r = osc_context_close(*osc_id, &osc);
|
||||
if (r < 0)
|
||||
return pam_syslog_errno(handle, LOG_ERR, r, "Failed to prepare OSC sequence: %m");
|
||||
|
||||
r = loop_write(fd, osc, SIZE_MAX);
|
||||
if (r < 0)
|
||||
return pam_syslog_errno(handle, LOG_ERR, r, "Failed to write OSC sequence to TTY: %m");
|
||||
|
||||
return PAM_SUCCESS;
|
||||
}
|
||||
|
||||
_public_ PAM_EXTERN int pam_sm_open_session(
|
||||
pam_handle_t *handle,
|
||||
int flags,
|
||||
|
@ -1637,8 +1766,8 @@ _public_ PAM_EXTERN int pam_sm_open_session(
|
|||
|
||||
session_context_mangle(handle, &c, ur, debug);
|
||||
|
||||
_cleanup_free_ char *seat_buffer = NULL, *runtime_dir = NULL;
|
||||
r = register_session(handle, &c, ur, debug, &seat_buffer, &runtime_dir);
|
||||
_cleanup_free_ char *seat_buffer = NULL, *type_buffer = NULL, *runtime_dir = NULL;
|
||||
r = register_session(handle, &c, ur, debug, &seat_buffer, &type_buffer, &runtime_dir);
|
||||
if (r != PAM_SUCCESS)
|
||||
return r;
|
||||
|
||||
|
@ -1653,7 +1782,11 @@ _public_ PAM_EXTERN int pam_sm_open_session(
|
|||
if (default_capability_ambient_set == UINT64_MAX)
|
||||
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);
|
||||
r = apply_user_record_settings(handle, ur, debug, default_capability_bounding_set, default_capability_ambient_set);
|
||||
if (r != PAM_SUCCESS)
|
||||
return r;
|
||||
|
||||
return open_osc_context(handle, c.type, ur);
|
||||
}
|
||||
|
||||
_public_ PAM_EXTERN int pam_sm_close_session(
|
||||
|
@ -1690,6 +1823,8 @@ _public_ PAM_EXTERN int pam_sm_close_session(
|
|||
return pam_syslog_pam_error(handle, LOG_ERR, r,
|
||||
"Failed to get PAM systemd.existing data: @PAMERR@");
|
||||
|
||||
(void) close_osc_context(handle);
|
||||
|
||||
id = pam_getenv(handle, "XDG_SESSION_ID");
|
||||
if (id && !existing) {
|
||||
_cleanup_(sd_varlink_unrefp) sd_varlink *vl = NULL;
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
#include "alloc-util.h"
|
||||
#include "bus-internal.h"
|
||||
#include "errno-util.h"
|
||||
#include "fd-util.h"
|
||||
#include "format-util.h"
|
||||
#include "macro.h"
|
||||
#include "pam-util.h"
|
||||
|
@ -249,6 +250,20 @@ void pam_cleanup_free(pam_handle_t *handle, void *data, int error_status) {
|
|||
free(data);
|
||||
}
|
||||
|
||||
void pam_cleanup_close(pam_handle_t *handle, void *data, int error_status) {
|
||||
|
||||
/* A generic destructor for pam_set_data() that just closes the specified fd.
|
||||
*
|
||||
* As per pam_set_data() docs: the PAM_DATA_SILENT indicates whether we are called in the forked off
|
||||
* payload child of the new session. However, all file descriptors are most likely already closed
|
||||
* there (that's what /bin/login does after all), hence let's simply turn this into a NOP in the
|
||||
* child, and only close the fd in the parent. */
|
||||
if (FLAGS_SET(error_status, PAM_DATA_SILENT))
|
||||
return;
|
||||
|
||||
safe_close(PTR_TO_FD(data));
|
||||
}
|
||||
|
||||
int pam_get_item_many_internal(pam_handle_t *handle, ...) {
|
||||
va_list ap;
|
||||
int r;
|
||||
|
|
|
@ -42,6 +42,7 @@ int pam_release_bus_connection(pam_handle_t *handle, const char *module_name);
|
|||
int pam_get_bus_data(pam_handle_t *handle, const char *module_name, PamBusData **ret);
|
||||
|
||||
void pam_cleanup_free(pam_handle_t *handle, void *data, int error_status);
|
||||
void pam_cleanup_close(pam_handle_t *handle, void *data, int error_status);
|
||||
|
||||
int pam_get_item_many_internal(pam_handle_t *handle, ...);
|
||||
#define pam_get_item_many(handle, ...) pam_get_item_many_internal(handle, __VA_ARGS__, -1)
|
||||
|
|
Loading…
Reference in New Issue