#include "apparmor-util.h"
#endif
#include "argv-util.h"
+#include "ask-password-api.h"
#include "barrier.h"
#include "bitfield.h"
#include "bpf-dlopen.h"
#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) {
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;
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
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;
* 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");