summaryrefslogtreecommitdiffstats
path: root/src/providers/proxy/proxy_auth.c
diff options
context:
space:
mode:
authorStephen Gallagher <sgallagh@redhat.com>2010-06-25 11:28:20 -0400
committerStephen Gallagher <sgallagh@redhat.com>2010-06-30 07:28:26 -0400
commit2dd3faebcd3cfd00efda38ffd2585d675e696b12 (patch)
tree76b74b05e63ef3fffeae6318a44a62d78b7c0545 /src/providers/proxy/proxy_auth.c
parentb19739caff3b15bbf79855f7f0339add73e64cce (diff)
downloadsssd-2dd3faebcd3cfd00efda38ffd2585d675e696b12.tar.gz
sssd-2dd3faebcd3cfd00efda38ffd2585d675e696b12.tar.xz
sssd-2dd3faebcd3cfd00efda38ffd2585d675e696b12.zip
Split proxy.c into smaller files
proxy.c was growing too large to manage (and some graphical development tools could no longer open it because of memory limitations). This patch splits proxy.c into the following files: proxy_init.c: Setup routines for the plugin proxy_id.c: Functions to handle user and group lookups proxy_auth.c: Functions to handle PAM interactions proxy_common.c: Common utility routines
Diffstat (limited to 'src/providers/proxy/proxy_auth.c')
-rw-r--r--src/providers/proxy/proxy_auth.c816
1 files changed, 816 insertions, 0 deletions
diff --git a/src/providers/proxy/proxy_auth.c b/src/providers/proxy/proxy_auth.c
new file mode 100644
index 000000000..a78de3126
--- /dev/null
+++ b/src/providers/proxy/proxy_auth.c
@@ -0,0 +1,816 @@
+/*
+ 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"
+
+struct proxy_client_ctx {
+ struct be_req *be_req;
+ struct proxy_auth_ctx *auth_ctx;
+};
+
+static struct tevent_req *proxy_child_send(TALLOC_CTX *mem_ctx,
+ struct proxy_auth_ctx *ctx,
+ struct be_req *be_req);
+static void proxy_child_done(struct tevent_req *child_req);
+void proxy_pam_handler(struct be_req *req)
+{
+ struct pam_data *pd;
+ struct proxy_auth_ctx *ctx;
+ struct tevent_req *child_req = NULL;
+ struct proxy_client_ctx *client_ctx;
+
+ pd = talloc_get_type(req->req_data, struct pam_data);
+
+ switch (pd->cmd) {
+ case SSS_PAM_AUTHENTICATE:
+ ctx = talloc_get_type(req->be_ctx->bet_info[BET_AUTH].pvt_bet_data,
+ struct proxy_auth_ctx);
+ break;
+ case SSS_PAM_CHAUTHTOK:
+ case SSS_PAM_CHAUTHTOK_PRELIM:
+ ctx = talloc_get_type(req->be_ctx->bet_info[BET_CHPASS].pvt_bet_data,
+ struct proxy_auth_ctx);
+ break;
+ case SSS_PAM_ACCT_MGMT:
+ ctx = talloc_get_type(req->be_ctx->bet_info[BET_ACCESS].pvt_bet_data,
+ struct proxy_auth_ctx);
+ break;
+ case SSS_PAM_SETCRED:
+ case SSS_PAM_OPEN_SESSION:
+ case SSS_PAM_CLOSE_SESSION:
+ pd->pam_status = PAM_SUCCESS;
+ proxy_reply(req, DP_ERR_OK, EOK, NULL);
+ return;
+ default:
+ DEBUG(1, ("Unsupported PAM task.\n"));
+ pd->pam_status = PAM_MODULE_UNKNOWN;
+ proxy_reply(req, DP_ERR_OK, EINVAL, "Unsupported PAM task");
+ return;
+ }
+
+ client_ctx = talloc(req, struct proxy_client_ctx);
+ if (client_ctx == NULL) {
+ proxy_reply(req, DP_ERR_FATAL, ENOMEM, NULL);
+ return;
+ }
+ client_ctx->auth_ctx = ctx;
+ client_ctx->be_req = req;
+
+ /* Queue the request and spawn a child if there
+ * is an available slot.
+ */
+ child_req = proxy_child_send(req, ctx, req);
+ if (child_req == NULL) {
+ /* Could not queue request
+ * Return an error
+ */
+ proxy_reply(req, DP_ERR_FATAL, EINVAL, "Could not queue request\n");
+ return;
+ }
+ tevent_req_set_callback(child_req, proxy_child_done, client_ctx);
+ return;
+}
+
+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(8, ("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(1, ("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 be_req *be_req)
+{
+ 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(1, ("Could not send PAM request to child\n"));
+ return NULL;
+ }
+
+ state->be_req = be_req;
+ state->auth_ctx = auth_ctx;
+ state->pd = talloc_get_type(be_req->req_data, struct pam_data);
+
+ /* 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(0, ("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(8, ("Queueing request [%d]\n", key.ul));
+ hret = hash_enter(auth_ctx->request_table,
+ &key, &value);
+ if (hret != HASH_SUCCESS) {
+ DEBUG(1, ("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(1, ("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(8, ("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(1, ("Could not create tevent_req\n"));
+ return NULL;
+ }
+
+ state->child_ctx = child_ctx;
+
+ state->command = talloc_asprintf(req,
+ "%s/proxy_child -d %d%s%s --domain %s --id %d",
+ SSSD_LIBEXEC_PATH, debug_level,
+ (debug_timestamps ? "" : " --debug-timestamps=0"),
+ (debug_to_file ? " --debug-to-files" : ""),
+ auth_ctx->be->domain->name,
+ child_ctx->id);
+ if (state->command == NULL) {
+ DEBUG(1, ("talloc_asprintf failed.\n"));
+ return NULL;
+ }
+
+ DEBUG(7, ("Starting proxy child with args [%s]\n", state->command));
+
+ pid = fork();
+ if (pid < 0) {
+ ret = errno;
+ DEBUG(1, ("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(0, ("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, SA_SIGINFO,
+ pc_init_sig_handler, req);
+ if (state->sige == NULL) {
+ DEBUG(1, ("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(0, ("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(7, ("Waiting for child [%d].\n", init_ctx->pid));
+
+ errno = 0;
+ ret = waitpid(init_ctx->pid, &child_status, WNOHANG);
+
+ if (ret == -1) {
+ ret = errno;
+ DEBUG(1, ("waitpid failed [%d][%s].\n", ret, strerror(ret)));
+ } else if (ret == 0) {
+ DEBUG(1, ("waitpid did not find a child with changed status.\n"));
+ } else {
+ if (WIFEXITED(child_status)) {
+ DEBUG(4, ("child [%d] exited with status [%d].\n", ret,
+ WEXITSTATUS(child_status)));
+ tevent_req_error(req, EIO);
+ } else if (WIFSIGNALED(child_status)) {
+ DEBUG(4, ("child [%d] was terminate by signal [%d].\n", ret,
+ WTERMSIG(child_status)));
+ tevent_req_error(req, EIO);
+ } else {
+ if (WIFSTOPPED(child_status)) {
+ DEBUG(1, ("child [%d] was stopped by signal [%d].\n", ret,
+ WSTOPSIG(child_status)));
+ }
+ if (WIFCONTINUED(child_status)) {
+ DEBUG(1, ("child [%d] was resumed by delivery of SIGCONT.\n",
+ ret));
+ }
+ DEBUG(1, ("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(2, ("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(6, ("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(1,("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(1, ("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, SA_SIGINFO,
+ proxy_child_sig_handler,
+ sig_ctx);
+ if (sige == NULL) {
+ DEBUG(1, ("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(0, ("SIGCHLD handler called with invalid child count\n"));
+ return;
+ }
+
+ sig_ctx = talloc_get_type(pvt, struct proxy_child_sig_ctx);
+ DEBUG(7, ("Waiting for child [%d].\n", sig_ctx->pid));
+
+ errno = 0;
+ ret = waitpid(sig_ctx->pid, &child_status, WNOHANG);
+
+ if (ret == -1) {
+ ret = errno;
+ DEBUG(1, ("waitpid failed [%d][%s].\n", ret, strerror(ret)));
+ } else if (ret == 0) {
+ DEBUG(1, ("waitpid did not found a child with changed status.\n"));
+ } else {
+ if (WIFEXITED(child_status)) {
+ DEBUG(4, ("child [%d] exited with status [%d].\n", ret,
+ WEXITSTATUS(child_status)));
+ } else if (WIFSIGNALED(child_status)) {
+ DEBUG(4, ("child [%d] was terminated by signal [%d].\n", ret,
+ WTERMSIG(child_status)));
+ } else {
+ if (WIFSTOPPED(child_status)) {
+ DEBUG(1, ("child [%d] was stopped by signal [%d].\n", ret,
+ WSTOPSIG(child_status)));
+ }
+ if (WIFCONTINUED(child_status)) {
+ DEBUG(1, ("child [%d] was resumed by delivery of SIGCONT.\n",
+ ret));
+ }
+ DEBUG(1, ("Child is still running, no new child is started.\n"));
+ return;
+ }
+
+ imm = tevent_create_immediate(ev);
+ if (imm == NULL) {
+ DEBUG(1, ("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 (imm == NULL) {
+ DEBUG(1, ("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,
+ DP_PATH,
+ DP_INTERFACE,
+ DP_METHOD_PAMHANDLER);
+ if (msg == NULL) {
+ DEBUG(1, ("dbus_message_new_method_call failed.\n"));
+ talloc_zfree(req);
+ return NULL;
+ }
+
+ DEBUG(4, ("Sending request with the following data:\n"));
+ DEBUG_PAM_DATA(4, pd);
+
+ dp_ret = dp_pack_pam_request(msg, pd);
+ if (!dp_ret) {
+ DEBUG(1, ("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(8, ("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(0, ("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(0, ("Failed to parse reply.\n"));
+ state->pd->pam_status = PAM_SYSTEM_ERR;
+ dbus_message_unref(reply);
+ tevent_req_error(req, EIO);
+ return;
+ }
+ DEBUG(4, ("received: [%d][%s]\n",
+ state->pd->pam_status,
+ state->pd->domain));
+ break;
+ case DBUS_MESSAGE_TYPE_ERROR:
+ DEBUG(0, ("Reply error [%s].\n",
+ dbus_message_get_error_name(reply)));
+ state->pd->pam_status = PAM_SYSTEM_ERR;
+ break;
+ default:
+ DEBUG(0, ("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(6, ("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 proxy_child_done(struct tevent_req *req)
+{
+ struct proxy_client_ctx *client_ctx =
+ tevent_req_callback_data(req, struct proxy_client_ctx);
+ struct pam_data *pd;
+ char *password;
+ int ret;
+ struct tevent_immediate *imm;
+
+ ret = proxy_child_recv(req, client_ctx, &pd);
+ talloc_zfree(req);
+ if (ret != EOK) {
+ /* Pam child failed */
+ client_ctx->auth_ctx->running--;
+ proxy_reply(client_ctx->be_req, DP_ERR_FATAL, ret,
+ "PAM child failed");
+
+ /* Start the next auth in the queue, if any */
+ imm = tevent_create_immediate(client_ctx->be_req->be_ctx->ev);
+ if (imm == NULL) {
+ DEBUG(1, ("tevent_create_immediate failed.\n"));
+ return;
+ }
+
+ tevent_schedule_immediate(imm,
+ client_ctx->be_req->be_ctx->ev,
+ run_proxy_child_queue,
+ client_ctx->auth_ctx);
+ return;
+ }
+
+ /* Check if we need to save the cached credentials */
+ if ((pd->cmd == SSS_PAM_AUTHENTICATE || pd->cmd == SSS_PAM_CHAUTHTOK) &&
+ pd->pam_status == PAM_SUCCESS &&
+ client_ctx->be_req->be_ctx->domain->cache_credentials) {
+ password = talloc_strndup(client_ctx->be_req,
+ (char *) pd->authtok,
+ pd->authtok_size);
+ if (!password) {
+ /* password caching failures are not fatal errors */
+ DEBUG(2, ("Failed to cache password\n"));
+ goto done;
+ }
+ talloc_set_destructor((TALLOC_CTX *)password, password_destructor);
+
+ ret = sysdb_cache_password(client_ctx,
+ client_ctx->be_req->be_ctx->sysdb,
+ client_ctx->be_req->be_ctx->domain,
+ pd->user, password);
+
+ /* password caching failures are not fatal errors */
+ /* so we just log it any return */
+ if (ret != EOK) {
+ DEBUG(2, ("Failed to cache password (%d)[%s]!?\n",
+ ret, strerror(ret)));
+ }
+ }
+
+done:
+ proxy_reply(client_ctx->be_req, DP_ERR_OK, EOK, NULL);
+}
+
+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;
+ }
+ }
+
+ 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(1, ("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;
+ }
+}