From 6082ccf792abe3ed423e3f818f50f024f36e7637 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 15 Nov 2024 14:31:09 +0100 Subject: [PATCH 1/7] pam_systemd: normalize parsing of XDG_VTNR Let's make it more like the parsing of the "incomplete" boolean env var, to streamline things. --- src/login/pam_systemd.c | 30 +++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/src/login/pam_systemd.c b/src/login/pam_systemd.c index 7cc64144eb..dd7ba64fb0 100644 --- a/src/login/pam_systemd.c +++ b/src/login/pam_systemd.c @@ -542,13 +542,33 @@ static bool getenv_harder_bool(pam_handle_t *handle, const char *key, bool fallb r = parse_boolean(v); if (r < 0) { - pam_syslog(handle, LOG_ERR, "Boolean environment variable value of '%s' is not valid: %s", key, v); + pam_syslog(handle, LOG_WARNING, "Failed to parse environment variable value '%s' of '%s', falling back to using '%s'.", v, key, true_false(fallback)); return fallback; } return r; } +static uint32_t getenv_harder_uint32(pam_handle_t *handle, const char *key, uint32_t fallback) { + int r; + + assert(handle); + assert(key); + + const char *v = getenv_harder(handle, key, NULL); + if (isempty(v)) + return fallback; + + uint32_t u; + r = safe_atou32(v, &u); + if (r < 0) { + pam_syslog(handle, LOG_WARNING, "Failed to parse environment variable value '%s' of '%s' as unsigned integer, falling back to using %" PRIu32 ".", v, key, fallback); + return fallback; + } + + return u; +} + static int update_environment(pam_handle_t *handle, const char *key, const char *value) { int r; @@ -946,7 +966,7 @@ _public_ PAM_EXTERN int pam_sm_open_session( *remote_user = NULL, *remote_host = NULL, *seat = NULL, *type = NULL, *class = NULL, - *class_pam = NULL, *type_pam = NULL, *cvtnr = NULL, *desktop = NULL, *desktop_pam = 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; @@ -991,7 +1011,7 @@ _public_ PAM_EXTERN int pam_sm_open_session( return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to get PAM items: @PAMERR@"); seat = getenv_harder(handle, "XDG_SEAT", NULL); - cvtnr = getenv_harder(handle, "XDG_VTNR", 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); @@ -1036,10 +1056,6 @@ _public_ PAM_EXTERN int pam_sm_open_session( /* Chop off leading /dev prefix that some clients specify, but others do not. */ tty = skip_dev_prefix(tty); - /* If this fails vtnr will be 0, that's intended */ - if (!isempty(cvtnr)) - (void) safe_atou32(cvtnr, &vtnr); - if (!isempty(display) && !vtnr) { if (isempty(seat)) (void) get_seat_from_display(display, &seat, &vtnr); From 014d23c3955ac2c2c6fc7c8e235daccae201c8fd Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 15 Nov 2024 14:36:22 +0100 Subject: [PATCH 2/7] pam_systemd: drop "pid" field from SessionContext We never use the field and this is not going to change... This addresses a weird asymmetry, as create_session_message() always went to the process' own PID when doing pidfds but otherwise (i.e. without pidfds) would honour the PID specified as function parameter. --- src/login/pam_systemd.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/login/pam_systemd.c b/src/login/pam_systemd.c index dd7ba64fb0..fd27d0ff81 100644 --- a/src/login/pam_systemd.c +++ b/src/login/pam_systemd.c @@ -847,7 +847,6 @@ static uint64_t pick_default_capability_ambient_set( typedef struct SessionContext { const uid_t uid; - const pid_t pid; const char *service; const char *type; const char *class; @@ -895,7 +894,7 @@ static int create_session_message( r = sd_bus_message_append(m, pidfd >= 0 ? "uhsssssussbss" : "uusssssussbss", (uint32_t) context->uid, - pidfd >= 0 ? pidfd : context->pid, + pidfd >= 0 ? pidfd : 0, context->service, context->type, context->class, @@ -1122,7 +1121,6 @@ _public_ PAM_EXTERN int pam_sm_open_session( const SessionContext context = { .uid = ur->uid, - .pid = 0, .service = service, .type = type, .class = class, From 5e782e4de3bd1991549c443101cf21bda8b9c692 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 15 Nov 2024 14:39:11 +0100 Subject: [PATCH 3/7] pam_systemd: drop "uid" field from SessionContext Let's instead just pass over the UserRecord, it's a much more useful object with lots more information we'll sooner or later need (preparation for later commits). --- src/login/pam_systemd.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/login/pam_systemd.c b/src/login/pam_systemd.c index fd27d0ff81..e218fdbc0c 100644 --- a/src/login/pam_systemd.c +++ b/src/login/pam_systemd.c @@ -846,7 +846,6 @@ static uint64_t pick_default_capability_ambient_set( } typedef struct SessionContext { - const uid_t uid; const char *service; const char *type; const char *class; @@ -868,6 +867,7 @@ typedef struct SessionContext { static int create_session_message( sd_bus *bus, pam_handle_t *handle, + UserRecord *ur, const SessionContext *context, bool avoid_pidfd, sd_bus_message **ret) { @@ -878,6 +878,7 @@ static int create_session_message( assert(bus); assert(handle); + assert(ur); assert(context); assert(ret); @@ -893,7 +894,7 @@ static int create_session_message( r = sd_bus_message_append(m, pidfd >= 0 ? "uhsssssussbss" : "uusssssussbss", - (uint32_t) context->uid, + (uint32_t) ur->uid, pidfd >= 0 ? pidfd : 0, context->service, context->type, @@ -1120,7 +1121,6 @@ _public_ PAM_EXTERN int pam_sm_open_session( strna(memory_max), strna(tasks_max), strna(cpu_weight), strna(io_weight), strna(runtime_max_sec)); const SessionContext context = { - .uid = ur->uid, .service = service, .type = type, .class = class, @@ -1141,6 +1141,7 @@ _public_ PAM_EXTERN int pam_sm_open_session( r = create_session_message(bus, handle, + ur, &context, /* avoid_pidfd = */ false, &m); @@ -1156,6 +1157,7 @@ _public_ PAM_EXTERN int pam_sm_open_session( m = sd_bus_message_unref(m); r = create_session_message(bus, handle, + ur, &context, /* avoid_pidfd = */ true, &m); From 166a678fea3e2301a9be4ba72c5b7f7183615065 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 15 Nov 2024 15:08:15 +0100 Subject: [PATCH 4/7] pam_systemd: split out setting of shell env vars from credentials and move it later Let's shorten the code of pam_sm_open_session() a bit, and also make sure the importing of the env vars from the creds also happens if the session registration with logind is skipped. --- src/login/pam_systemd.c | 38 +++++++++++++++++++++++++------------- 1 file changed, 25 insertions(+), 13 deletions(-) diff --git a/src/login/pam_systemd.c b/src/login/pam_systemd.c index e218fdbc0c..b9b257df00 100644 --- a/src/login/pam_systemd.c +++ b/src/login/pam_systemd.c @@ -948,6 +948,27 @@ 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( pam_handle_t *handle, int flags, @@ -1234,19 +1255,6 @@ _public_ PAM_EXTERN int pam_sm_open_session( if (r != PAM_SUCCESS) return r; - static const char *const propagate[] = { - "shell.prompt.prefix", "SHELL_PROMPT_PREFIX", - "shell.prompt.suffix", "SHELL_PROMPT_SUFFIX", - "shell.welcome", "SHELL_WELCOME", - NULL - }; - - STRV_FOREACH_PAIR(k, v, propagate) { - r = propagate_credential_to_environment(handle, *k, *v); - if (r != PAM_SUCCESS) - return r; - } - if (vtnr > 0) { char buf[DECIMAL_STR_MAX(vtnr)]; sprintf(buf, "%u", vtnr); @@ -1272,6 +1280,10 @@ _public_ PAM_EXTERN int pam_sm_open_session( } success: + 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); From 32580792df9f07b7b922eba9ea02cbf60c1b07e4 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 15 Nov 2024 14:25:59 +0100 Subject: [PATCH 5/7] pam_systemd: split pam_sm_open_session() into more digestable blocks Let's separate four different parts of pam_sm_open_session(): 1. Acquiring of our various parameters from pam env, pam data, pam items 2. Mangling of that data to clean it up 3. Registering of the service with logind 4. Importing shell credentials into environment variables 5. Enforcement of user record data This makes the code a lot more readable, and gets rid of an ugly goto label. It also corrects things: if step 3 doesnt work because logind is not around, we'll now still do step 4, which we previously erroneously skipped. Besides that no real code changes. --- src/login/pam_systemd.c | 562 +++++++++++++++++++++------------------- 1 file changed, 294 insertions(+), 268 deletions(-) diff --git a/src/login/pam_systemd.c b/src/login/pam_systemd.c index b9b257df00..247167a573 100644 --- a/src/login/pam_systemd.c +++ b/src/login/pam_systemd.c @@ -851,10 +851,10 @@ typedef struct SessionContext { 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; @@ -862,6 +862,7 @@ typedef struct SessionContext { const char *cpu_weight; const char *io_weight; const char *runtime_max_sec; + bool incomplete; } SessionContext; static int create_session_message( @@ -948,6 +949,270 @@ static int create_session_message( return 0; } +static void session_context_mangle( + pam_handle_t *handle, + SessionContext *c, + UserRecord *ur, + bool debug) { + + assert(handle); + assert(c); + assert(ur); + + 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. */ + c->type = "unspecified"; + c->class = IN_SET(user_record_disposition(ur), USER_INTRINSIC, USER_SYSTEM, USER_DYNAMIC) ? + "manager-early" : "manager"; + c->tty = NULL; + + } 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(c->display)) + c->display = c->tty; + c->tty = NULL; + + } 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.) */ + c->type = "unspecified"; + c->class = "background"; + c->tty = NULL; + + } 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). */ + 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. */ + c->tty = skip_dev_prefix(c->tty); + + 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 (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(c->type)) + c->type = !isempty(c->display) ? "x11" : + !isempty(c->tty) ? "tty" : "unspecified"; + + if (isempty(c->class)) + c->class = streq(c->type, "unspecified") ? "background" : + ((IN_SET(user_record_disposition(ur), USER_INTRINSIC, USER_SYSTEM, USER_DYNAMIC) && + streq(c->type, "tty")) ? "user-early" : "user"); + + 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.", c->class); + } + + c->remote = !isempty(c->remote_host) && !is_localhost(c->remote_host); +} + +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); + if (r != PAM_SUCCESS) + return r; + + pam_debug_syslog(handle, debug, + "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(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(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, + c, + /* avoid_pidfd = */ false, + &m); + if (r < 0) + return pam_bus_log_create_error(handle, r); + + r = sd_bus_call(bus, m, LOGIN_SLOW_BUS_CALL_TIMEOUT_USEC, &error, &reply); + if (r < 0 && sd_bus_error_has_name(&error, SD_BUS_ERROR_UNKNOWN_METHOD)) { + sd_bus_error_free(&error); + pam_debug_syslog(handle, debug, + "CreateSessionWithPIDFD() API is not available, retrying with CreateSession()."); + + m = sd_bus_message_unref(m); + r = create_session_message(bus, + handle, + ur, + c, + /* avoid_pidfd = */ true, + &m); + if (r < 0) + return pam_bus_log_create_error(handle, r); + + r = sd_bus_call(bus, m, LOGIN_SLOW_BUS_CALL_TIMEOUT_USEC, &error, &reply); + } + if (r < 0) { + if (sd_bus_error_has_name(&error, BUS_ERROR_SESSION_BUSY)) { + /* 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 skip; + } + + pam_syslog(handle, LOG_ERR, + "Failed to create session: %s", bus_error_message(&error, r)); + return PAM_SESSION_ERR; + } + + 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, real_seat, real_vtnr, original_uid); + + /* Please update manager_default_environment() in core/manager.c accordingly if more session envvars + * shall be added. */ + + r = update_environment(handle, "XDG_SESSION_ID", id); + if (r != PAM_SUCCESS) + return r; + + if (original_uid == ur->uid) { + /* Don't set $XDG_RUNTIME_DIR if the user we now authenticated for does not match the + * original user of the session. We do this in order not to result in privileged apps + * clobbering the runtime directory unnecessarily. */ + + r = configure_runtime_directory(handle, ur, runtime_path); + if (r != PAM_SUCCESS) + return r; + } + + /* Most likely we got the session/type/class from environment variables, but might have gotten the data + * 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", c->type); + if (r != PAM_SUCCESS) + return r; + + r = update_environment(handle, "XDG_SESSION_CLASS", c->class); + if (r != PAM_SUCCESS) + return r; + + r = update_environment(handle, "XDG_SESSION_DESKTOP", c->desktop); + if (r != PAM_SUCCESS) + return r; + + r = update_environment(handle, "XDG_SEAT", real_seat); + if (r != PAM_SUCCESS) + return r; + + 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) + return r; + } + + r = pam_set_data(handle, "systemd.existing", INT_TO_PTR(!!existing), NULL); + if (r != PAM_SUCCESS) + return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to install existing flag: @PAMERR@"); + + if (session_fd >= 0) { + _cleanup_close_ int fd = fcntl(session_fd, F_DUPFD_CLOEXEC, 3); + if (fd < 0) + return pam_syslog_errno(handle, LOG_ERR, errno, "Failed to dup session fd: %m"); + + r = pam_set_data(handle, "systemd.session-fd", FD_TO_PTR(fd), NULL); + if (r != PAM_SUCCESS) + return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to install session fd: @PAMERR@"); + TAKE_FD(fd); + } + + /* 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[] = { @@ -974,33 +1239,15 @@ _public_ PAM_EXTERN int pam_sm_open_session( 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; + 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, @@ -1013,279 +1260,58 @@ _public_ PAM_EXTERN int pam_sm_open_session( 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; - /* Make most of this a NOP on non-logind systems */ - if (!logind_running()) - goto success; - + SessionContext c = {}; r = pam_get_item_many( handle, - PAM_SERVICE, &service, - PAM_XDISPLAY, &display, - PAM_TTY, &tty, - PAM_RUSER, &remote_user, - PAM_RHOST, &remote_host); + 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@"); - 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); + 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); - if (streq_ptr(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) ? - "manager-early" : "manager"; - tty = NULL; - - } else if (tty && strchr(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; - - } else if (streq_ptr(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; - - } else if (streq_ptr(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) - /* Chop off leading /dev prefix that some clients specify, but others do not. */ - tty = skip_dev_prefix(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 (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 (isempty(type)) - type = !isempty(display) ? "x11" : - !isempty(tty) ? "tty" : "unspecified"; - - if (isempty(class)) - class = streq(type, "unspecified") ? "background" : - ((IN_SET(user_record_disposition(ur), USER_INTRINSIC, USER_SYSTEM, USER_DYNAMIC) && - streq(type, "tty")) ? "user-early" : "user"); - - if (incomplete) { - if (streq(class, "user")) - 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); - } - - remote = !isempty(remote_host) && !is_localhost(remote_host); - - r = pam_get_data(handle, "systemd.memory_max", (const void **)&memory_max); + 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 **)&tasks_max); + 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 **)&cpu_weight); + 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 **)&io_weight); + 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 **)&runtime_max_sec); + 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@"); - /* Talk to logind over the message bus */ - r = pam_acquire_bus_connection(handle, "pam-systemd", debug, &bus, &d); + 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; - pam_debug_syslog(handle, debug, - "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)); - 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, - }; - - r = create_session_message(bus, - handle, - ur, - &context, - /* avoid_pidfd = */ false, - &m); - if (r < 0) - return pam_bus_log_create_error(handle, r); - - r = sd_bus_call(bus, m, LOGIN_SLOW_BUS_CALL_TIMEOUT_USEC, &error, &reply); - if (r < 0 && sd_bus_error_has_name(&error, SD_BUS_ERROR_UNKNOWN_METHOD)) { - sd_bus_error_free(&error); - pam_debug_syslog(handle, debug, - "CreateSessionWithPIDFD() API is not available, retrying with CreateSession()."); - - m = sd_bus_message_unref(m); - r = create_session_message(bus, - handle, - ur, - &context, - /* avoid_pidfd = */ true, - &m); - if (r < 0) - return pam_bus_log_create_error(handle, r); - - r = sd_bus_call(bus, m, LOGIN_SLOW_BUS_CALL_TIMEOUT_USEC, &error, &reply); - } - if (r < 0) { - if (sd_bus_error_has_name(&error, BUS_ERROR_SESSION_BUSY)) { - /* 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; - } - - pam_syslog(handle, LOG_ERR, - "Failed to create session: %s", bus_error_message(&error, r)); - return PAM_SESSION_ERR; - } - - r = sd_bus_message_read(reply, - "soshusub", - &id, - &object_path, - &runtime_path, - &session_fd, - &original_uid, - &seat, - &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); - - /* Please update manager_default_environment() in core/manager.c accordingly if more session envvars - * shall be added. */ - - r = update_environment(handle, "XDG_SESSION_ID", id); - if (r != PAM_SUCCESS) - return r; - - if (original_uid == ur->uid) { - /* Don't set $XDG_RUNTIME_DIR if the user we now authenticated for does not match the - * original user of the session. We do this in order not to result in privileged apps - * clobbering the runtime directory unnecessarily. */ - - r = configure_runtime_directory(handle, ur, runtime_path); - if (r != PAM_SUCCESS) - return r; - } - - /* Most likely we got the session/type/class from environment variables, but might have gotten the data - * 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); - if (r != PAM_SUCCESS) - return r; - - r = update_environment(handle, "XDG_SESSION_CLASS", class); - if (r != PAM_SUCCESS) - return r; - - r = update_environment(handle, "XDG_SESSION_DESKTOP", desktop); - if (r != PAM_SUCCESS) - return r; - - r = update_environment(handle, "XDG_SEAT", seat); - if (r != PAM_SUCCESS) - return r; - - if (vtnr > 0) { - char buf[DECIMAL_STR_MAX(vtnr)]; - sprintf(buf, "%u", vtnr); - - r = update_environment(handle, "XDG_VTNR", buf); - if (r != PAM_SUCCESS) - return r; - } - - r = pam_set_data(handle, "systemd.existing", INT_TO_PTR(!!existing), NULL); - if (r != PAM_SUCCESS) - return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to install existing flag: @PAMERR@"); - - if (session_fd >= 0) { - _cleanup_close_ int fd = fcntl(session_fd, F_DUPFD_CLOEXEC, 3); - if (fd < 0) - return pam_syslog_errno(handle, LOG_ERR, errno, "Failed to dup session fd: %m"); - - r = pam_set_data(handle, "systemd.session-fd", FD_TO_PTR(fd), NULL); - if (r != PAM_SUCCESS) - return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to install session fd: @PAMERR@"); - TAKE_FD(fd); - } - -success: 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); } From 7f471bd3b213c0c15bf8e7c0e0a7097ebabbdb64 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 15 Nov 2024 15:03:51 +0100 Subject: [PATCH 6/7] pam_systemd: fix error code confusion when prepping D-Bus message We got confused by the error codes here, and sometimes return PAM errors where the caller propagated them unconverted as negative errno errors. Fix that. --- src/login/pam_systemd.c | 146 +++++++++++++++++++--------------------- 1 file changed, 69 insertions(+), 77 deletions(-) diff --git a/src/login/pam_systemd.c b/src/login/pam_systemd.c index 247167a573..684ec967e1 100644 --- a/src/login/pam_systemd.c +++ b/src/login/pam_systemd.c @@ -390,116 +390,107 @@ static int export_legacy_dbus_address( } static int append_session_memory_max(pam_handle_t *handle, sd_bus_message *m, const char *limit) { - uint64_t val; int r; + assert(handle); + assert(m); + if (isempty(limit)) - return PAM_SUCCESS; + return 0; - if (streq(limit, "infinity")) { - r = sd_bus_message_append(m, "(sv)", "MemoryMax", "t", UINT64_MAX); - if (r < 0) - return pam_bus_log_create_error(handle, r); - - return PAM_SUCCESS; - } + if (streq(limit, "infinity")) + return sd_bus_message_append(m, "(sv)", "MemoryMax", "t", UINT64_MAX); r = parse_permyriad(limit); - if (r >= 0) { - r = sd_bus_message_append(m, "(sv)", "MemoryMaxScale", "u", UINT32_SCALE_FROM_PERMYRIAD(r)); - if (r < 0) - return pam_bus_log_create_error(handle, r); - - return PAM_SUCCESS; - } + if (r >= 0) + return sd_bus_message_append(m, "(sv)", "MemoryMaxScale", "u", UINT32_SCALE_FROM_PERMYRIAD(r)); + uint64_t val; r = parse_size(limit, 1024, &val); - if (r >= 0) { - r = sd_bus_message_append(m, "(sv)", "MemoryMax", "t", val); - if (r < 0) - return pam_bus_log_create_error(handle, r); - + if (r < 0) { + pam_syslog(handle, LOG_WARNING, "Failed to parse systemd.memory_max, ignoring: %s", limit); return PAM_SUCCESS; } - pam_syslog(handle, LOG_WARNING, "Failed to parse systemd.memory_max, ignoring: %s", limit); - return PAM_SUCCESS; + return sd_bus_message_append(m, "(sv)", "MemoryMax", "t", val); } static int append_session_runtime_max_sec(pam_handle_t *handle, sd_bus_message *m, const char *limit) { - usec_t val; int r; + assert(handle); + assert(m); + /* No need to parse "infinity" here, it will be set by default later in scope_init() */ if (isempty(limit) || streq(limit, "infinity")) - return PAM_SUCCESS; + return 0; + usec_t val; r = parse_sec(limit, &val); - if (r >= 0) { - r = sd_bus_message_append(m, "(sv)", "RuntimeMaxUSec", "t", (uint64_t) val); - if (r < 0) - return pam_bus_log_create_error(handle, r); - } else + if (r < 0) { pam_syslog(handle, LOG_WARNING, "Failed to parse systemd.runtime_max_sec: %s, ignoring.", limit); + return 0; + } - return PAM_SUCCESS; + return sd_bus_message_append(m, "(sv)", "RuntimeMaxUSec", "t", (uint64_t) val); } static int append_session_tasks_max(pam_handle_t *handle, sd_bus_message *m, const char *limit) { - uint64_t val; int r; + assert(handle); + assert(m); + /* No need to parse "infinity" here, it will be set unconditionally later in manager_start_scope() */ if (isempty(limit) || streq(limit, "infinity")) - return PAM_SUCCESS; + return 0; + uint64_t val; r = safe_atou64(limit, &val); - if (r >= 0) { - r = sd_bus_message_append(m, "(sv)", "TasksMax", "t", val); - if (r < 0) - return pam_bus_log_create_error(handle, r); - } else + if (r < 0) { pam_syslog(handle, LOG_WARNING, "Failed to parse systemd.tasks_max, ignoring: %s", limit); + return 0; + } - return PAM_SUCCESS; + return sd_bus_message_append(m, "(sv)", "TasksMax", "t", val); } static int append_session_cpu_weight(pam_handle_t *handle, sd_bus_message *m, const char *limit) { - uint64_t val; int r; - if (isempty(limit)) - return PAM_SUCCESS; + assert(handle); + assert(m); + if (isempty(limit)) + return 0; + + uint64_t val; r = cg_cpu_weight_parse(limit, &val); - if (r < 0) + if (r < 0) { pam_syslog(handle, LOG_WARNING, "Failed to parse systemd.cpu_weight, ignoring: %s", limit); - else { - r = sd_bus_message_append(m, "(sv)", "CPUWeight", "t", val); - if (r < 0) - return pam_bus_log_create_error(handle, r); + return 0; } - return PAM_SUCCESS; + return sd_bus_message_append(m, "(sv)", "CPUWeight", "t", val); } static int append_session_io_weight(pam_handle_t *handle, sd_bus_message *m, const char *limit) { - uint64_t val; int r; - if (isempty(limit)) - return PAM_SUCCESS; + assert(handle); + assert(m); + if (isempty(limit)) + return 0; + + uint64_t val; r = cg_weight_parse(limit, &val); - if (r < 0) + if (r < 0) { pam_syslog(handle, LOG_WARNING, "Failed to parse systemd.io_weight, ignoring: %s", limit); - else { - r = sd_bus_message_append(m, "(sv)", "IOWeight", "t", val); - if (r < 0) - return pam_bus_log_create_error(handle, r); + return 0; } - return PAM_SUCCESS; + return sd_bus_message_append(m, "(sv)", "IOWeight", "t", val); } static const char* getenv_harder(pam_handle_t *handle, const char *key, const char *fallback) { @@ -893,21 +884,22 @@ static int create_session_message( if (r < 0) return r; - r = sd_bus_message_append(m, - pidfd >= 0 ? "uhsssssussbss" : "uusssssussbss", - (uint32_t) ur->uid, - pidfd >= 0 ? pidfd : 0, - context->service, - context->type, - context->class, - context->desktop, - context->seat, - context->vtnr, - context->tty, - context->display, - context->remote, - context->remote_user, - context->remote_host); + r = sd_bus_message_append( + m, + pidfd >= 0 ? "uhsssssussbss" : "uusssssussbss", + (uint32_t) ur->uid, + pidfd >= 0 ? pidfd : 0, + context->service, + context->type, + context->class, + context->desktop, + context->seat, + context->vtnr, + context->tty, + context->display, + context->remote, + context->remote_user, + context->remote_host); if (r < 0) return r; @@ -922,23 +914,23 @@ static int create_session_message( return r; r = append_session_memory_max(handle, m, context->memory_max); - if (r != PAM_SUCCESS) + if (r < 0) return r; r = append_session_runtime_max_sec(handle, m, context->runtime_max_sec); - if (r != PAM_SUCCESS) + if (r < 0) return r; r = append_session_tasks_max(handle, m, context->tasks_max); - if (r != PAM_SUCCESS) + if (r < 0) return r; r = append_session_cpu_weight(handle, m, context->cpu_weight); - if (r != PAM_SUCCESS) + if (r < 0) return r; r = append_session_io_weight(handle, m, context->io_weight); - if (r != PAM_SUCCESS) + if (r < 0) return r; r = sd_bus_message_close_container(m); From f07fe275d502e2553a08ca420cdf45fd37509e6b Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 15 Nov 2024 15:23:41 +0100 Subject: [PATCH 7/7] pam_systemd: introduce pam_get_data_many() helper and make use of it This is to pam_get_data() what pam_get_item() is to pam_get_item_many(). --- src/login/pam_systemd.c | 32 +++++++++++++------------------- src/shared/pam-util.c | 28 ++++++++++++++++++++++++++-- src/shared/pam-util.h | 4 +++- 3 files changed, 42 insertions(+), 22 deletions(-) diff --git a/src/login/pam_systemd.c b/src/login/pam_systemd.c index 684ec967e1..9d96c91539 100644 --- a/src/login/pam_systemd.c +++ b/src/login/pam_systemd.c @@ -1260,11 +1260,11 @@ _public_ PAM_EXTERN int pam_sm_open_session( SessionContext c = {}; r = pam_get_item_many( handle, - PAM_SERVICE, &c.service, + PAM_SERVICE, &c.service, PAM_XDISPLAY, &c.display, - PAM_TTY, &c.tty, - PAM_RUSER, &c.remote_user, - PAM_RHOST, &c.remote_host); + 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@"); @@ -1275,21 +1275,15 @@ _public_ PAM_EXTERN int pam_sm_open_session( 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@"); + r = pam_get_data_many( + handle, + "systemd.memory_max", &c.memory_max, + "systemd.tasks_max", &c.tasks_max, + "systemd.cpu_weight", &c.cpu_weight, + "systemd.io_weight", &c.io_weight, + "systemd.runtime_max_sec", &c.runtime_max_sec); + if (r != PAM_SUCCESS) + return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to get PAM data: @PAMERR@"); session_context_mangle(handle, &c, ur, debug); diff --git a/src/shared/pam-util.c b/src/shared/pam-util.c index af97adb2d0..0b97e81ab8 100644 --- a/src/shared/pam-util.c +++ b/src/shared/pam-util.c @@ -253,17 +253,17 @@ int pam_get_item_many_internal(pam_handle_t *handle, ...) { va_list ap; int r; + assert(handle); + va_start(ap, handle); for (;;) { int item_type = va_arg(ap, int); - if (item_type <= 0) { r = PAM_SUCCESS; break; } const void **value = ASSERT_PTR(va_arg(ap, const void **)); - r = pam_get_item(handle, item_type, value); if (!IN_SET(r, PAM_BAD_ITEM, PAM_SUCCESS)) break; @@ -273,6 +273,30 @@ int pam_get_item_many_internal(pam_handle_t *handle, ...) { return r; } +int pam_get_data_many_internal(pam_handle_t *handle, ...) { + va_list ap; + int r; + + assert(handle); + + va_start(ap, handle); + for (;;) { + const char *data_name = va_arg(ap, const char *); + if (!data_name) { + r = PAM_SUCCESS; + break; + } + + const void **value = ASSERT_PTR(va_arg(ap, const void **)); + r = pam_get_data(handle, data_name, value); + if (!IN_SET(r, PAM_NO_MODULE_DATA, PAM_SUCCESS)) + break; + } + va_end(ap); + + return r; +} + int pam_prompt_graceful(pam_handle_t *handle, int style, char **ret_response, const char *fmt, ...) { va_list args; int r; diff --git a/src/shared/pam-util.h b/src/shared/pam-util.h index 34a83294b0..2e19516042 100644 --- a/src/shared/pam-util.h +++ b/src/shared/pam-util.h @@ -44,7 +44,9 @@ int pam_get_bus_data(pam_handle_t *handle, const char *module_name, PamBusData * void pam_cleanup_free(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) +int pam_get_data_many_internal(pam_handle_t *handle, ...) _sentinel_; +#define pam_get_data_many(handle, ...) pam_get_data_many_internal(handle, __VA_ARGS__, NULL) + int pam_prompt_graceful(pam_handle_t *handle, int style, char **ret_response, const char *fmt, ...) _printf_(4,5);