/*
SSSD
proxy_auth.c
Authors:
Stephen Gallagher <sgallagh@redhat.com>
Copyright (C) 2010 Red Hat
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "providers/proxy/proxy.h"
#include "providers/proxy/proxy_iface_generated.h"
struct pc_init_ctx;
static int proxy_child_destructor(TALLOC_CTX *ctx)
{
struct proxy_child_ctx *child_ctx =
talloc_get_type(ctx, struct proxy_child_ctx);
hash_key_t key;
int hret;
DEBUG(SSSDBG_TRACE_INTERNAL,
"Removing proxy child id [%d]\n", child_ctx->id);
key.type = HASH_KEY_ULONG;
key.ul = child_ctx->id;
hret = hash_delete(child_ctx->auth_ctx->request_table, &key);
if (!(hret == HASH_SUCCESS ||
hret == HASH_ERROR_KEY_NOT_FOUND)) {
DEBUG(SSSDBG_CRIT_FAILURE,
"Hash error [%d][%s]\n", hret, hash_error_string(hret));
/* Nothing we can do about this, so just continue */
}
return 0;
}
static struct tevent_req *proxy_child_init_send(TALLOC_CTX *mem_ctx,
struct proxy_child_ctx *child_ctx,
struct proxy_auth_ctx *auth_ctx);
static void proxy_child_init_done(struct tevent_req *subreq);
static struct tevent_req *proxy_child_send(TALLOC_CTX *mem_ctx,
struct proxy_auth_ctx *auth_ctx,
struct pam_data *pd)
{
struct tevent_req *req;
struct tevent_req *subreq;
struct proxy_child_ctx *state;
int hret;
hash_key_t key;
hash_value_t value;
uint32_t first;
req = tevent_req_create(mem_ctx, &state, struct proxy_child_ctx);
if (req == NULL) {
DEBUG(SSSDBG_CRIT_FAILURE, "Could not send PAM request to child\n");
return NULL;
}
state->auth_ctx = auth_ctx;
state->pd = pd;
/* Find an available key */
key.type = HASH_KEY_ULONG;
key.ul = auth_ctx->next_id;
first = auth_ctx->next_id;
while (auth_ctx->next_id == 0 ||
hash_has_key(auth_ctx->request_table, &key)) {
/* Handle overflow, zero is a reserved value
* Also handle the unlikely case where the next ID
* is still awaiting being run
*/
auth_ctx->next_id++;
key.ul = auth_ctx->next_id;
if (auth_ctx->next_id == first) {
/* We've looped through all possible integers! */
DEBUG(SSSDBG_FATAL_FAILURE, "Serious error: queue is too long!\n");
talloc_zfree(req);
return NULL;
}
}
state->id = auth_ctx->next_id;
auth_ctx->next_id++;
value.type = HASH_VALUE_PTR;
value.ptr = req;
DEBUG(SSSDBG_TRACE_INTERNAL, "Queueing request [%lu]\n", key.ul);
hret = hash_enter(auth_ctx->request_table,
&key, &value);
if (hret != HASH_SUCCESS) {
DEBUG(SSSDBG_CRIT_FAILURE, "Could not add request to the queue\n");
talloc_zfree(req);
return NULL;
}
talloc_set_destructor((TALLOC_CTX *) state,
proxy_child_destructor);
if (auth_ctx->running < auth_ctx->max_children) {
/* There's an available slot; start a child
* to handle the request
*/
auth_ctx->running++;
subreq = proxy_child_init_send(auth_ctx, state, auth_ctx);
if (!subreq) {
DEBUG(SSSDBG_CRIT_FAILURE, "Could not fork child process\n");
auth_ctx->running--;
talloc_zfree(req);
return NULL;
}
tevent_req_set_callback(subreq, proxy_child_init_done, req);
state->running = true;
}
else {
/* If there was no available slot, it will be queued
* until a slot is available
*/
DEBUG(SSSDBG_TRACE_INTERNAL,
"All available child slots are full, queuing request\n");
}
return req;
}
static int pc_init_destructor (TALLOC_CTX *ctx)
{
struct pc_init_ctx *init_ctx =
talloc_get_type(ctx, struct pc_init_ctx);
/* If the init request has died, forcibly kill the child */
kill(init_ctx->pid, SIGKILL);
return 0;
}
static void pc_init_sig_handler(struct tevent_context *ev,
struct tevent_signal *sige, int signum,
int count, void *__siginfo, void *pvt);
static void pc_init_timeout(struct tevent_context *ev,
struct tevent_timer *te,
struct timeval t, void *ptr);
static struct tevent_req *proxy_child_init_send(TALLOC_CTX *mem_ctx,
struct proxy_child_ctx *child_ctx,
struct proxy_auth_ctx *auth_ctx)
{
struct tevent_req *req;
struct pc_init_ctx *state;
char **proxy_child_args;
struct timeval tv;
errno_t ret;
pid_t pid;
req = tevent_req_create(mem_ctx, &state, struct pc_init_ctx);
if (req == NULL) {
DEBUG(SSSDBG_CRIT_FAILURE, "Could not create tevent_req\n");
return NULL;
}
state->child_ctx = child_ctx;
state->command = talloc_asprintf(req,
"%s/proxy_child -d %#.4x --debug-timestamps=%d "
"--debug-microseconds=%d%s --domain %s --id %d",
SSSD_LIBEXEC_PATH, debug_level, debug_timestamps,
debug_microseconds, (debug_to_file ? " --debug-to-files" : ""),
auth_ctx->be->domain->name,
child_ctx->id);
if (state->command == NULL) {
DEBUG(SSSDBG_CRIT_FAILURE, "talloc_asprintf failed.\n");
return NULL;
}
DEBUG(SSSDBG_TRACE_LIBS,
"Starting proxy child with args [%s]\n", state->command);
pid = fork();
if (pid < 0) {
ret = errno;
DEBUG(SSSDBG_CRIT_FAILURE,
"fork failed [%d][%s].\n", ret, strerror(ret));
talloc_zfree(req);
return NULL;
}
if (pid == 0) { /* child */
proxy_child_args = parse_args(state->command);
execvp(proxy_child_args[0], proxy_child_args);
ret = errno;
DEBUG(SSSDBG_FATAL_FAILURE,
"Could not start proxy child [%s]: [%d][%s].\n",
state->command, ret, strerror(ret));
_exit(1);
}
else { /* parent */
state->pid = pid;
/* Make sure to kill the child process if we abort */
talloc_set_destructor((TALLOC_CTX *)state, pc_init_destructor);
state->sige = tevent_add_signal(auth_ctx->be->ev, req,
SIGCHLD, 0,
pc_init_sig_handler, req);
if (state->sige == NULL) {
DEBUG(SSSDBG_CRIT_FAILURE, "tevent_add_signal failed.\n");
talloc_zfree(req);
return NULL;
}
/* Save the init request to the child context.
* This is technically a layering violation,
* but it's the only sane way to be able to
* identify which client is which when it
* connects to the backend in
* client_registration()
*/
child_ctx->init_req = req;
/* Wait six seconds for the child to connect
* This is because the connection handler will add
* its own five-second timeout, and we don't want to
* be faster here.
*/
tv = tevent_timeval_current_ofs(6, 0);
state->timeout = tevent_add_timer(auth_ctx->be->ev, req,
tv, pc_init_timeout, req);
/* processing will continue once the connection is received
* in proxy_client_init()
*/
return req;
}
}
static void pc_init_sig_handler(struct tevent_context *ev,
struct tevent_signal *sige, int signum,
int count, void *__siginfo, void *pvt)
{
int ret;
int child_status;
struct tevent_req *req;
struct pc_init_ctx *init_ctx;
if (count <= 0) {
DEBUG(SSSDBG_FATAL_FAILURE,
"SIGCHLD handler called with invalid child count\n");
return;
}
req = talloc_get_type(pvt, struct tevent_req);
init_ctx = tevent_req_data(req, struct pc_init_ctx);
DEBUG(SSSDBG_TRACE_LIBS, "Waiting for child [%d].\n", init_ctx->pid);
errno = 0;
ret = waitpid(init_ctx->pid, &child_status, WNOHANG);
if (ret == -1) {
ret = errno;
DEBUG(SSSDBG_CRIT_FAILURE,
"waitpid failed [%d][%s].\n", ret, strerror(ret));
} else if (ret == 0) {
DEBUG(SSSDBG_CRIT_FAILURE,
"waitpid did not find a child with changed status.\n");
} else {
if (WIFEXITED(child_status)) {
DEBUG(SSSDBG_CONF_SETTINGS,
"child [%d] exited with status [%d].\n", ret,
WEXITSTATUS(child_status));
tevent_req_error(req, EIO);
} else if (WIFSIGNALED(child_status)) {
DEBUG(SSSDBG_CONF_SETTINGS,
"child [%d] was terminate by signal [%d].\n", ret,
WTERMSIG(child_status));
tevent_req_error(req, EIO);
} else {
if (WIFSTOPPED(child_status)) {
DEBUG(SSSDBG_CRIT_FAILURE,
"child [%d] was stopped by signal [%d].\n", ret,
WSTOPSIG(child_status));
}
if (WIFCONTINUED(child_status) == true) {
DEBUG(SSSDBG_CRIT_FAILURE,
"child [%d] was resumed by delivery of SIGCONT.\n",
ret);
}
DEBUG(SSSDBG_CRIT_FAILURE,
"Child is still running, no new child is started.\n");
return;
}
}
}
static void pc_init_timeout(struct tevent_context *ev,
struct tevent_timer *te,
struct timeval t, void *ptr)
{
struct tevent_req *req;
DEBUG(SSSDBG_OP_FAILURE, "Client timed out before Identification!\n");
req = talloc_get_type(ptr, struct tevent_req);
tevent_req_error(req, ETIMEDOUT);
}
static errno_t proxy_child_init_recv(struct tevent_req *req,
pid_t *pid,
struct sbus_connection **conn)
{
struct pc_init_ctx *state;
TEVENT_REQ_RETURN_ON_ERROR(req);
state = tevent_req_data(req, struct pc_init_ctx);
/* Unset the destructor since we initialized successfully.
* We don't want to kill the child now that it's properly
* set up.
*/
talloc_set_destructor((TALLOC_CTX *)state, NULL);
*pid = state->pid;
*conn = state->conn;
return EOK;
}
struct proxy_child_sig_ctx {
struct proxy_auth_ctx *auth_ctx;
pid_t pid;
};
static void proxy_child_sig_handler(struct tevent_context *ev,
struct tevent_signal *sige, int signum,
int count, void *__siginfo, void *pvt);
static struct tevent_req *proxy_pam_conv_send(TALLOC_CTX *mem_ctx,
struct proxy_auth_ctx *auth_ctx,
struct sbus_connection *conn,
struct pam_data *pd,
pid_t pid);
static void proxy_pam_conv_done(struct tevent_req *subreq);
static void proxy_child_init_done(struct tevent_req *subreq) {
int ret;
struct tevent_signal *sige;
struct tevent_req *req =
tevent_req_callback_data(subreq, struct tevent_req);
struct proxy_child_ctx *child_ctx =
tevent_req_data(req, struct proxy_child_ctx);
struct proxy_child_sig_ctx *sig_ctx;
ret = proxy_child_init_recv(subreq, &child_ctx->pid, &child_ctx->conn);
talloc_zfree(subreq);
if (ret != EOK) {
DEBUG(SSSDBG_TRACE_FUNC, "Proxy child init failed [%d]\n", ret);
tevent_req_error(req, ret);
return;
}
/* An initialized child is available, awaiting the PAM command */
subreq = proxy_pam_conv_send(req, child_ctx->auth_ctx,
child_ctx->conn, child_ctx->pd,
child_ctx->pid);
if (!subreq) {
DEBUG(SSSDBG_CRIT_FAILURE,"Could not start PAM conversation\n");
tevent_req_error(req, EIO);
return;
}
tevent_req_set_callback(subreq, proxy_pam_conv_done, req);
/* Add a signal handler for the child under the auth_ctx,
* that way if the child exits after completion of the
* request, it will still be handled.
*/
sig_ctx = talloc_zero(child_ctx->auth_ctx, struct proxy_child_sig_ctx);
if(sig_ctx == NULL) {
DEBUG(SSSDBG_CRIT_FAILURE, "tevent_add_signal failed.\n");
tevent_req_error(req, ENOMEM);
return;
}
sig_ctx->auth_ctx = child_ctx->auth_ctx;
sig_ctx->pid = child_ctx->pid;
sige = tevent_add_signal(child_ctx->auth_ctx->be->ev,
child_ctx->auth_ctx,
SIGCHLD, 0,
proxy_child_sig_handler,
sig_ctx);
if (sige == NULL) {
DEBUG(SSSDBG_CRIT_FAILURE, "tevent_add_signal failed.\n");
tevent_req_error(req, ENOMEM);
return;
}
/* Steal the signal context onto the signal event
* so that when the signal is freed, the context
* will go with it.
*/
talloc_steal(sige, sig_ctx);
}
static void remove_sige(struct tevent_context *ev,
struct tevent_immediate *imm,
void *pvt);
static void run_proxy_child_queue(struct tevent_context *ev,
struct tevent_immediate *imm,
void *pvt);
static void proxy_child_sig_handler(struct tevent_context *ev,
struct tevent_signal *sige, int signum,
int count, void *__siginfo, void *pvt)
{
int ret;
int child_status;
struct proxy_child_sig_ctx *sig_ctx;
struct tevent_immediate *imm;
struct tevent_immediate *imm2;
if (count <= 0) {
DEBUG(SSSDBG_FATAL_FAILURE,
"SIGCHLD handler called with invalid child count\n");
return;
}
sig_ctx = talloc_get_type(pvt, struct proxy_child_sig_ctx);
DEBUG(SSSDBG_TRACE_LIBS, "Waiting for child [%d].\n", sig_ctx->pid);
errno = 0;
ret = waitpid(sig_ctx->pid, &child_status, WNOHANG);
if (ret == -1) {
ret = errno;
DEBUG(SSSDBG_CRIT_FAILURE,
"waitpid failed [%d][%s].\n", ret, strerror(ret));
} else if (ret == 0) {
DEBUG(SSSDBG_CRIT_FAILURE,
"waitpid did not found a child with changed status.\n");
} else {
if (WIFEXITED(child_status)) {
DEBUG(SSSDBG_CONF_SETTINGS,
"child [%d] exited with status [%d].\n", ret,
WEXITSTATUS(child_status));
} else if (WIFSIGNALED(child_status) == true) {
DEBUG(SSSDBG_CONF_SETTINGS,
"child [%d] was terminated by signal [%d].\n", ret,
WTERMSIG(child_status));
} else {
if (WIFSTOPPED(child_status)) {
DEBUG(SSSDBG_CRIT_FAILURE,
"child [%d] was stopped by signal [%d].\n", ret,
WSTOPSIG(child_status));
}
if (WIFCONTINUED(child_status) == true) {
DEBUG(SSSDBG_CRIT_FAILURE,
"child [%d] was resumed by delivery of SIGCONT.\n",
ret);
}
DEBUG(SSSDBG_CRIT_FAILURE,
"Child is still running, no new child is started.\n");
return;
}
imm = tevent_create_immediate(ev);
if (imm == NULL) {
DEBUG(SSSDBG_CRIT_FAILURE, "tevent_create_immediate failed.\n");
return;
}
tevent_schedule_immediate(imm, ev, run_proxy_child_queue,
sig_ctx->auth_ctx);
/* schedule another immediate timer to delete the sigchld handler */
imm2 = tevent_create_immediate(ev);
if (imm2 == NULL) {
DEBUG(SSSDBG_CRIT_FAILURE, "tevent_create_immediate failed.\n");
return;
}
tevent_schedule_immediate(imm2, ev, remove_sige, sige);
}
return;
}
static void remove_sige(struct tevent_context *ev,
struct tevent_immediate *imm,
void *pvt)
{
talloc_free(pvt);
}
struct proxy_conv_ctx {
struct proxy_auth_ctx *auth_ctx;
struct sbus_connection *conn;
struct pam_data *pd;
pid_t pid;
};
static void proxy_pam_conv_reply(DBusPendingCall *pending, void *ptr);
static struct tevent_req *proxy_pam_conv_send(TALLOC_CTX *mem_ctx,
struct proxy_auth_ctx *auth_ctx,
struct sbus_connection *conn,
struct pam_data *pd,
pid_t pid)
{
errno_t ret;
bool dp_ret;
DBusMessage *msg;
struct tevent_req *req;
struct proxy_conv_ctx *state;
req = tevent_req_create(mem_ctx, &state, struct proxy_conv_ctx);
if (req == NULL) {
return NULL;
}
state->auth_ctx = auth_ctx;
state->conn = conn;
state->pd = pd;
state->pid = pid;
msg = dbus_message_new_method_call(NULL,
PROXY_CHILD_PATH,
IFACE_PROXY_AUTH,
IFACE_PROXY_AUTH_PAM);
if (msg == NULL) {
DEBUG(SSSDBG_CRIT_FAILURE, "dbus_message_new_method_call failed.\n");
talloc_zfree(req);
return NULL;
}
DEBUG(SSSDBG_CONF_SETTINGS, "Sending request with the following data:\n");
DEBUG_PAM_DATA(SSSDBG_CONF_SETTINGS, pd);
dp_ret = dp_pack_pam_request(msg, pd);
if (!dp_ret) {
DEBUG(SSSDBG_CRIT_FAILURE, "Failed to build message\n");
dbus_message_unref(msg);
talloc_zfree(req);
return NULL;
}
ret = sbus_conn_send(state->conn, msg, state->auth_ctx->timeout_ms,
proxy_pam_conv_reply, req, NULL);
if (ret != EOK) {
dbus_message_unref(msg);
talloc_zfree(req);
return NULL;
}
dbus_message_unref(msg);
return req;
}
static void proxy_pam_conv_reply(DBusPendingCall *pending, void *ptr)
{
struct tevent_req *req;
struct proxy_conv_ctx *state;
DBusError dbus_error;
DBusMessage *reply;
int type;
int ret;
DEBUG(SSSDBG_TRACE_INTERNAL, "Handling pam conversation reply\n");
req = talloc_get_type(ptr, struct tevent_req);
state = tevent_req_data(req, struct proxy_conv_ctx);
dbus_error_init(&dbus_error);
reply = dbus_pending_call_steal_reply(pending);
dbus_pending_call_unref(pending);
if (reply == NULL) {
DEBUG(SSSDBG_FATAL_FAILURE,
"Severe error. A reply callback was called but no reply was"
"received and no timeout occurred\n");
state->pd->pam_status = PAM_SYSTEM_ERR;
tevent_req_error(req, EIO);
}
type = dbus_message_get_type(reply);
switch (type) {
case DBUS_MESSAGE_TYPE_METHOD_RETURN:
ret = dp_unpack_pam_response(reply, state->pd, &dbus_error);
if (!ret) {
DEBUG(SSSDBG_FATAL_FAILURE, "Failed to parse reply.\n");
state->pd->pam_status = PAM_SYSTEM_ERR;
dbus_message_unref(reply);
tevent_req_error(req, EIO);
return;
}
DEBUG(SSSDBG_CONF_SETTINGS, "received: [%d][%s]\n",
state->pd->pam_status,
state->pd->domain);
break;
case DBUS_MESSAGE_TYPE_ERROR:
DEBUG(SSSDBG_FATAL_FAILURE, "Reply error [%s].\n",
dbus_message_get_error_name(reply));
state->pd->pam_status = PAM_SYSTEM_ERR;
break;
default:
DEBUG(SSSDBG_FATAL_FAILURE, "Default... what now?.\n");
state->pd->pam_status = PAM_SYSTEM_ERR;
}
dbus_message_unref(reply);
/* Kill the child */
kill(state->pid, SIGKILL);
/* Conversation is finished */
tevent_req_done(req);
}
static errno_t proxy_pam_conv_recv(struct tevent_req *req)
{
TEVENT_REQ_RETURN_ON_ERROR(req);
return EOK;
}
static void proxy_pam_conv_done(struct tevent_req *subreq)
{
struct tevent_req *req;
int ret;
req = tevent_req_callback_data(subreq, struct tevent_req);
ret = proxy_pam_conv_recv(subreq);
talloc_zfree(subreq);
if (ret != EOK) {
DEBUG(SSSDBG_TRACE_FUNC, "Proxy PAM conversation failed [%d]\n", ret);
tevent_req_error(req, ret);
return;
}
tevent_req_done(req);
}
static int proxy_child_recv(struct tevent_req *req,
TALLOC_CTX *mem_ctx,
struct pam_data **pd)
{
struct proxy_child_ctx *ctx;
TEVENT_REQ_RETURN_ON_ERROR(req);
ctx = tevent_req_data(req, struct proxy_child_ctx);
*pd = talloc_steal(mem_ctx, ctx->pd);
return EOK;
}
static void run_proxy_child_queue(struct tevent_context *ev,
struct tevent_immediate *imm,
void *pvt)
{
struct proxy_auth_ctx *auth_ctx;
struct hash_iter_context_t *iter;
struct hash_entry_t *entry;
struct tevent_req *req;
struct tevent_req *subreq;
struct proxy_child_ctx *state;
auth_ctx = talloc_get_type(pvt, struct proxy_auth_ctx);
/* Launch next queued request */
iter = new_hash_iter_context(auth_ctx->request_table);
while ((entry = iter->next(iter)) != NULL) {
req = talloc_get_type(entry->value.ptr, struct tevent_req);
state = tevent_req_data(req, struct proxy_child_ctx);
if (!state->running) {
break;
}
}
free(iter);
if (!entry) {
/* Nothing pending on the queue */
return;
}
if (auth_ctx->running < auth_ctx->max_children) {
/* There's an available slot; start a child
* to handle the request
*/
auth_ctx->running++;
subreq = proxy_child_init_send(auth_ctx, state, auth_ctx);
if (!subreq) {
DEBUG(SSSDBG_CRIT_FAILURE, "Could not fork child process\n");
auth_ctx->running--;
talloc_zfree(req);
return;
}
tevent_req_set_callback(subreq, proxy_child_init_done, req);
state->running = true;
}
}
struct proxy_pam_handler_state {
struct pam_data *pd;
struct proxy_auth_ctx *auth_ctx;
struct be_ctx *be_ctx;
};
static void proxy_pam_handler_done(struct tevent_req *subreq);
struct tevent_req *
proxy_pam_handler_send(TALLOC_CTX *mem_ctx,
struct proxy_auth_ctx *proxy_auth_ctx,
struct pam_data *pd,
struct dp_req_params *params)
{
struct proxy_pam_handler_state *state;
struct tevent_req *subreq;
struct tevent_req *req;
req = tevent_req_create(mem_ctx, &state, struct proxy_pam_handler_state);
if (req == NULL) {
DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n");
return NULL;
}
state->pd = pd;
state->auth_ctx = proxy_auth_ctx;
state->be_ctx = params->be_ctx;
switch (pd->cmd) {
case SSS_PAM_AUTHENTICATE:
case SSS_PAM_CHAUTHTOK:
case SSS_PAM_CHAUTHTOK_PRELIM:
case SSS_PAM_ACCT_MGMT:
/* Queue the request and spawn a child if there is an available slot. */
subreq = proxy_child_send(state, proxy_auth_ctx, state->pd);
if (subreq == NULL) {
pd->pam_status = PAM_SYSTEM_ERR;
goto immediately;
}
tevent_req_set_callback(subreq, proxy_pam_handler_done, req);
break;
case SSS_PAM_SETCRED:
case SSS_PAM_OPEN_SESSION:
case SSS_PAM_CLOSE_SESSION:
pd->pam_status = PAM_SUCCESS;
goto immediately;
default:
DEBUG(SSSDBG_CRIT_FAILURE, "Unsupported PAM task.\n");
pd->pam_status = PAM_MODULE_UNKNOWN;
goto immediately;
}
return req;
immediately:
/* TODO For backward compatibility we always return EOK to DP now. */
tevent_req_done(req);
tevent_req_post(req, params->ev);
return req;
}
static void proxy_pam_handler_done(struct tevent_req *subreq)
{
struct proxy_pam_handler_state *state;
struct tevent_immediate *imm;
struct tevent_req *req;
const char *password;
errno_t ret;
req = tevent_req_callback_data(subreq, struct tevent_req);
state = tevent_req_data(req, struct proxy_pam_handler_state);
ret = proxy_child_recv(subreq, state, &state->pd);
talloc_zfree(subreq);
if (ret != EOK) {
state->pd->pam_status = PAM_SYSTEM_ERR;
goto done;
}
/* Start the next auth in the queue, if any */
state->auth_ctx->running--;
imm = tevent_create_immediate(state->be_ctx->ev);
if (imm == NULL) {
DEBUG(SSSDBG_CRIT_FAILURE, "tevent_create_immediate failed.\n");
/* We'll still finish the current request, but we're
* likely to have problems if there are queued events
* if we've gotten into this state.
* Hopefully this is impossible, since freeing req
* above should guarantee that we have enough memory
* to create this immediate event.
*/
} else {
tevent_schedule_immediate(imm, state->be_ctx->ev,
run_proxy_child_queue,
state->auth_ctx);
}
/* Check if we need to save the cached credentials */
if ((state->pd->cmd == SSS_PAM_AUTHENTICATE || state->pd->cmd == SSS_PAM_CHAUTHTOK)
&& (state->pd->pam_status == PAM_SUCCESS) && state->be_ctx->domain->cache_credentials) {
ret = sss_authtok_get_password(state->pd->authtok, &password, NULL);
if (ret) {
/* password caching failures are not fatal errors */
DEBUG(SSSDBG_OP_FAILURE, "Failed to cache password\n");
goto done;
}
ret = sysdb_cache_password(state->be_ctx->domain, state->pd->user, password);
/* password caching failures are not fatal errors */
/* so we just log it any return */
if (ret != EOK) {
DEBUG(SSSDBG_OP_FAILURE, "Failed to cache password (%d)[%s]!?\n",
ret, sss_strerror(ret));
}
}
done:
/* TODO For backward compatibility we always return EOK to DP now. */
tevent_req_done(req);
}
errno_t
proxy_pam_handler_recv(TALLOC_CTX *mem_ctx,
struct tevent_req *req,
struct pam_data **_data)
{
struct proxy_pam_handler_state *state = NULL;
state = tevent_req_data(req, struct proxy_pam_handler_state);
TEVENT_REQ_RETURN_ON_ERROR(req);
*_data = talloc_steal(mem_ctx, state->pd);
return EOK;
}