/*
    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 %#.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(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 = NULL;
    char *password;
    int ret;
    struct tevent_immediate *imm;

    ret = proxy_child_recv(req, client_ctx, &pd);
    talloc_zfree(req);

    /* Start the next auth in the queue, if any */
    client_ctx->auth_ctx->running--;
    imm = tevent_create_immediate(client_ctx->be_req->be_ctx->ev);
    if (imm == NULL) {
        DEBUG(1, ("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,
                                  client_ctx->be_req->be_ctx->ev,
                                  run_proxy_child_queue,
                                  client_ctx->auth_ctx);
    }

    if (ret != EOK) {
        /* Pam child failed */
        proxy_reply(client_ctx->be_req, DP_ERR_FATAL, ret,
                    "PAM child failed");
        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->be_req->be_ctx->sysdb,
                                   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;
        }
    }
    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(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;
    }
}