]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
pid1: when a password is requested during PAMName= processing, query it via the ask...
authorLennart Poettering <lennart@poettering.net>
Tue, 14 Jan 2025 23:24:29 +0000 (00:24 +0100)
committerLuca Boccassi <luca.boccassi@gmail.com>
Sat, 18 Jan 2025 11:45:44 +0000 (11:45 +0000)
man/systemd.exec.xml
src/core/exec-invoke.c

index 695049db97592cdffea8476f84601c080c7d357f..8a2fa448dc7cc79c438ae7b6ca0bab693ac1347d 100644 (file)
         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 <varname>PAMName=</varname> in
         combination with <varname>NotifyAccess=</varname><option>all</option>.</para>
+
+        <para>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 <varname>SetCredential=</varname>,
+        <varname>ImportCredential=</varname> and related calls) under the name
+        <varname>pam.authtok.<replaceable>pamservice</replaceable></varname>, where
+        <replaceable>pamservice</replaceable> is replaced by the PAM service name as configured with
+        <varname>PAMName=</varname>. (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 <ulink
+        url="https://systemd.io/PASSWORD_AGENTS">Password Agent</ulink> logic.</para>
         </listitem>
       </varlistentry>
 
index c58698e564957eee322ce4e4e0edb46fe4328c5c..7f79ae4962c1b4e88f6288ae310fc9daa23ae915 100644 (file)
@@ -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");