From: Lennart Poettering Date: Tue, 14 Jan 2025 23:24:29 +0000 (+0100) Subject: pid1: when a password is requested during PAMName= processing, query it via the ask... X-Git-Tag: v258-rc1~1567 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=8af1b296cb2cec8ddbb2cb47f4194269eb6cee2b;p=thirdparty%2Fsystemd.git pid1: when a password is requested during PAMName= processing, query it via the ask-password logic --- diff --git a/man/systemd.exec.xml b/man/systemd.exec.xml index 695049db975..8a2fa448dc7 100644 --- a/man/systemd.exec.xml +++ b/man/systemd.exec.xml @@ -770,6 +770,15 @@ changes in the original unit through notification messages. These messages will be considered belonging to the session scope unit and not the original unit. It is hence not recommended to use PAMName= in combination with NotifyAccess=. + + If a PAM module interactively requests input (a password or suchlike) it will be attempted to + be read from a service credential (as configured via SetCredential=, + ImportCredential= and related calls) under the name + pam.authtok.pamservice, where + pamservice is replaced by the PAM service name as configured with + PAMName=. (Note that the credential remains accessible for the runtime of the + service!) If no matching credential is set, the user is prompted for it interactively via the Password Agent logic. diff --git a/src/core/exec-invoke.c b/src/core/exec-invoke.c index c58698e5649..7f79ae4962c 100644 --- a/src/core/exec-invoke.c +++ b/src/core/exec-invoke.c @@ -21,6 +21,7 @@ #include "apparmor-util.h" #endif #include "argv-util.h" +#include "ask-password-api.h" #include "barrier.h" #include "bitfield.h" #include "bpf-dlopen.h" @@ -1048,15 +1049,114 @@ static int enforce_user( #if HAVE_PAM -static int null_conv( +static void pam_response_free_array(struct pam_response *responses, size_t n_responses) { + assert(responses || n_responses == 0); + + FOREACH_ARRAY(resp, responses, n_responses) + erase_and_free(resp->resp); + + free(responses); +} + +typedef struct AskPasswordConvData { + const ExecContext *context; + const ExecParameters *params; +} AskPasswordConvData; + +static int ask_password_conv( int num_msg, - const struct pam_message **msg, - struct pam_response **resp, - void *appdata_ptr) { + const struct pam_message *msg[], + struct pam_response **ret, + void *userdata) { + + AskPasswordConvData *data = ASSERT_PTR(userdata); + bool set_credential_env_var = false; + int r; + + assert(num_msg >= 0); + assert(msg); + assert(data->context); + assert(data->params); + + size_t n = num_msg; + struct pam_response *responses = new0(struct pam_response, n); + if (!responses) + return PAM_BUF_ERR; + CLEANUP_ARRAY(responses, n, pam_response_free_array); + + for (size_t i = 0; i < n; i++) { + const struct pam_message *mi = *msg + i; + + switch (mi->msg_style) { + + case PAM_PROMPT_ECHO_ON: + case PAM_PROMPT_ECHO_OFF: { + + /* Locally set the $CREDENTIALS_DIRECTORY to the credentials directory we just populated */ + if (!set_credential_env_var) { + _cleanup_free_ char *creds_dir = NULL; + r = exec_context_get_credential_directory(data->context, data->params, data->params->unit_id, &creds_dir); + if (r < 0) + return log_exec_error_errno(data->context, data->params, r, "Failed to determine credentials directory: %m"); + + if (creds_dir) { + if (setenv("CREDENTIALS_DIRECTORY", creds_dir, /* overwrite= */ true) < 0) + return log_exec_error_errno(data->context, data->params, r, "Failed to set $CREDENTIALS_DIRECTORY: %m"); + } else + (void) unsetenv("CREDENTIALS_DIRECTORY"); + + set_credential_env_var = true; + } + + _cleanup_free_ char *credential_name = strjoin("pam.authtok.", data->context->pam_name); + if (!credential_name) + return log_oom(); + + AskPasswordRequest req = { + .message = mi->msg, + .credential = credential_name, + .tty_fd = -EBADF, + .hup_fd = -EBADF, + .until = now(CLOCK_MONOTONIC) + 15 * USEC_PER_SEC, + }; - /* We don't support conversations */ + _cleanup_(strv_free_erasep) char **acquired = NULL; + r = ask_password_auto( + &req, + ASK_PASSWORD_ACCEPT_CACHED| + ASK_PASSWORD_NO_TTY| + (mi->msg_style == PAM_PROMPT_ECHO_ON ? ASK_PASSWORD_ECHO : 0), + &acquired); + if (r < 0) { + log_exec_error_errno(data->context, data->params, r, "Failed to query for password: %m"); + return PAM_CONV_ERR; + } + + responses[i].resp = strdup(ASSERT_PTR(acquired[0])); + if (!responses[i].resp) { + log_oom(); + return PAM_BUF_ERR; + } + break; + } + + case PAM_ERROR_MSG: + log_exec_error(data->context, data->params, "PAM: %s", mi->msg); + break; + + case PAM_TEXT_INFO: + log_exec_info(data->context, data->params, "PAM: %s", mi->msg); + break; + + default: + return PAM_CONV_ERR; + } + } - return PAM_CONV_ERR; + *ret = TAKE_PTR(responses); + n = 0; + + return PAM_SUCCESS; } static int pam_close_session_and_delete_credentials(pam_handle_t *handle, int flags) { @@ -1074,24 +1174,27 @@ static int pam_close_session_and_delete_credentials(pam_handle_t *handle, int fl return r != PAM_SUCCESS ? r : s; } - #endif static int setup_pam( - const char *name, + const ExecContext *context, + ExecParameters *params, const char *user, uid_t uid, gid_t gid, - const char *tty, char ***env, /* updated on success */ const int fds[], size_t n_fds, int exec_fd) { #if HAVE_PAM + AskPasswordConvData conv_data = { + .context = context, + .params = params, + }; - static const struct pam_conv conv = { - .conv = null_conv, - .appdata_ptr = NULL + const struct pam_conv conv = { + .conv = ask_password_conv, + .appdata_ptr = &conv_data, }; _cleanup_(barrier_destroy) Barrier barrier = BARRIER_NULL; @@ -1103,8 +1206,12 @@ static int setup_pam( pid_t parent_pid; int flags = 0; - assert(name); + assert(context); + assert(params); assert(user); + assert(uid_is_valid(uid)); + assert(gid_is_valid(gid)); + assert(fds || n_fds == 0); assert(env); /* We set up PAM in the parent process, then fork. The child @@ -1121,12 +1228,13 @@ static int setup_pam( if (log_get_max_level() < LOG_DEBUG) flags |= PAM_SILENT; - pam_code = pam_start(name, user, &conv, &handle); + pam_code = pam_start(context->pam_name, user, &conv, &handle); if (pam_code != PAM_SUCCESS) { handle = NULL; goto fail; } + const char *tty = context->tty_path; if (!tty) { _cleanup_free_ char *q = NULL; @@ -4976,7 +5084,7 @@ int exec_invoke( * wins here. (See above.) */ /* All fds passed in the fds array will be closed in the pam child process. */ - r = setup_pam(context->pam_name, username, uid, gid, context->tty_path, &accum_env, params->fds, n_fds, params->exec_fd); + r = setup_pam(context, params, username, uid, gid, &accum_env, params->fds, n_fds, params->exec_fd); if (r < 0) { *exit_status = EXIT_PAM; return log_exec_error_errno(context, params, r, "Failed to set up PAM session: %m");