diff --git a/src/login/pam_systemd.c b/src/login/pam_systemd.c index 6dbf22d7b3..4c0f1a58b8 100644 --- a/src/login/pam_systemd.c +++ b/src/login/pam_systemd.c @@ -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; diff --git a/src/shared/pam-util.c b/src/shared/pam-util.c index 0b97e81ab8..95e646b460 100644 --- a/src/shared/pam-util.c +++ b/src/shared/pam-util.c @@ -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; diff --git a/src/shared/pam-util.h b/src/shared/pam-util.h index 2e19516042..7dae9dd31d 100644 --- a/src/shared/pam-util.h +++ b/src/shared/pam-util.h @@ -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)