From 532c599d75cdce127f65c4f4a8e3fdc29956a357 Mon Sep 17 00:00:00 2001
From: Jan Zeleny <jzeleny@redhat.com>
Date: Fri, 15 Oct 2010 13:59:59 +0200
Subject: [PATCH] Serialize authentication requests

Request for user in particular domain is inserted into a hash table in PAM
responder. In case another request for the same use arrives to PAM responder
before the first one is resolved, it is connected to the first one and the
response for both of them is send to PAM once the domain returns the result
for the first one. The result is that data provider receives only one request
from PAM responder which subsequentially leads to all login attempts receive
the same kerberos cache.
Ticket: #533
---
 src/responder/pam/pam_LOCAL_domain.c |    9 +-
 src/responder/pam/pamsrv.h           |   20 +++-
 src/responder/pam/pamsrv_cmd.c       |  219 ++++++++++++++++++++++++++++++++--
 src/responder/pam/pamsrv_dp.c        |   20 +++-
 4 files changed, 253 insertions(+), 15 deletions(-)

diff --git a/src/responder/pam/pam_LOCAL_domain.c b/src/responder/pam/pam_LOCAL_domain.c
index 0df9499..b6dec2b 100644
--- a/src/responder/pam/pam_LOCAL_domain.c
+++ b/src/responder/pam/pam_LOCAL_domain.c
@@ -60,13 +60,18 @@ struct LOCAL_request {
 static void prepare_reply(struct LOCAL_request *lreq)
 {
     struct pam_data *pd;
+    struct pam_auth_req *preq = lreq->preq;
+    struct timeval tv;
+    struct tevent_timer *te;
 
-    pd = lreq->preq->pd;
+    pd = preq->pd;
 
     if (lreq->error != EOK && pd->pam_status == PAM_SUCCESS)
         pd->pam_status = PAM_SYSTEM_ERR;
 
-    lreq->preq->callback(lreq->preq);
+    tv = tevent_timeval_current();
+    te = tevent_add_timer(preq->cctx->ev, preq, tv,
+                          pam_auth_invoke_callback, preq);
 }
 
 static void do_successful_login(struct LOCAL_request *lreq)
diff --git a/src/responder/pam/pamsrv.h b/src/responder/pam/pamsrv.h
index 3ada4cf..d01f36f 100644
--- a/src/responder/pam/pamsrv.h
+++ b/src/responder/pam/pamsrv.h
@@ -37,13 +37,27 @@ struct pam_ctx {
     int neg_timeout;
 };
 
+struct pam_auth_callback {
+    struct pam_auth_callback *prev;
+    struct pam_auth_callback *next;
+
+    struct pam_auth_req *preq;
+
+    pam_dp_callback_t *callback;
+    struct pam_auth_req *callback_preq;
+};
+
 struct pam_auth_req {
     struct cli_ctx *cctx;
     struct sss_domain_info *domain;
 
+    int reply_pam_status;
+    struct response_data *reply_resp_list;
     struct pam_data *pd;
 
-    pam_dp_callback_t *callback;
+    char *key;
+    struct pam_auth_callback *cb_list;
+    struct pam_auth_callback *current_cb;
 
     struct ldb_result *res;
     bool check_provider;
@@ -56,4 +70,8 @@ int pam_dp_send_req(struct pam_auth_req *preq, int timeout);
 
 int LOCAL_pam_handler(struct pam_auth_req *preq);
 
+void pam_auth_invoke_callback(struct tevent_context *ev,
+                              struct tevent_timer *te,
+                              struct timeval t, void *ptr);
+
 #endif /* __PAMSRV_H__ */
diff --git a/src/responder/pam/pamsrv_cmd.c b/src/responder/pam/pamsrv_cmd.c
index 7bfd0f2..7d0d7a8 100644
--- a/src/responder/pam/pamsrv_cmd.c
+++ b/src/responder/pam/pamsrv_cmd.c
@@ -33,6 +33,86 @@
 
 static void pam_reply(struct pam_auth_req *preq);
 
+static int pam_auth_callback_destructor(void *ptr)
+{
+    struct pam_auth_callback *cb = talloc_get_type(ptr, struct pam_auth_callback);
+
+    DLIST_REMOVE(cb->preq->cb_list, cb);
+    if (cb->preq != cb->callback_preq) {
+        talloc_zfree(cb->callback_preq);
+    }
+
+    return EOK;
+}
+
+static int pam_auth_req_destructor(void *ptr)
+{
+    struct pam_auth_req *preq = talloc_get_type(ptr, struct pam_auth_req);
+    struct pam_auth_callback *cb;
+    struct pam_auth_callback *next;
+    hash_key_t key;
+
+    key.type = HASH_KEY_STRING;
+    key.str = preq->key;
+    int hret = hash_delete(dp_requests, &key);
+    if (hret != HASH_SUCCESS) {
+        DEBUG(0, ("Could not clear entry from request queue\n"));
+    }
+
+    cb = preq->cb_list;
+    while (cb) {
+        cb->callback(cb->callback_preq);
+        next = cb->next;
+        talloc_free(cb);
+        cb = next;
+    }
+
+    return EOK;
+}
+
+void pam_auth_invoke_callback(struct tevent_context *ev,
+                              struct tevent_timer *te,
+                              struct timeval t, void *ptr)
+{
+    struct pam_auth_req *preq = talloc_get_type(ptr, struct pam_auth_req);
+    struct pam_auth_callback *cb = NULL;
+    struct timeval tv;
+    struct tevent_timer *tev = NULL;
+
+    cb = preq->cb_list;
+
+    if (cb == NULL) {
+        DEBUG(1, ("No callback to invoke.\n"));
+        return;
+    }
+
+    cb->callback_preq->current_cb = cb;
+    cb->callback(cb->callback_preq);
+    cb->callback_preq->current_cb = NULL;
+    talloc_free(cb);
+
+    /* Call the next callback if needed */
+    if (cb->preq->cb_list != NULL) {
+        cb = cb->preq->cb_list;
+        tv = tevent_timeval_current();
+        tev = tevent_add_timer(preq->cctx->ev, cb->callback_preq, tv,
+                               pam_auth_invoke_callback, cb->callback_preq);
+        if (!tev) {
+            goto done;
+        }
+
+        return;
+    }
+
+    /* No more callbacks to invoke. Destroy the request */
+done:
+    /* steal te on NULL because it will be freed as soon as the handler
+     * returns. Causing a double free if we don't, as te is allocated on
+     * preq and we are just going to free it */
+    talloc_steal(NULL, te);
+    talloc_zfree(cb->preq);
+}
+
 static int extract_authtok(uint32_t *type, uint32_t *size, uint8_t **tok, uint8_t *body, size_t blen, size_t *c) {
     uint32_t data_size;
 
@@ -311,7 +391,7 @@ static errno_t set_last_login(struct pam_auth_req *preq)
     } else {
         preq->pd->last_auth_saved = true;
     }
-    preq->callback(preq);
+    preq->current_cb->callback(preq->current_cb->callback_preq);
 
     return EOK;
 
@@ -331,6 +411,33 @@ static void pam_reply_delay(struct tevent_context *ev, struct tevent_timer *te,
     pam_reply(preq);
 }
 
+static errno_t pam_copy_resp(TALLOC_CTX *memctx, struct response_data *src, struct response_data **dst)
+{
+    struct response_data *new = NULL;
+    struct response_data *new_dst = NULL;
+
+    while (src) {
+        new = talloc(memctx, struct response_data);
+        if (new == NULL) return ENOMEM;
+
+        new->next = NULL;
+        new->type = src->type;
+        new->len = src->len;
+        new->data = talloc_memdup(memctx, src->data, src->len);
+        if (new->data == NULL) return ENOMEM;
+
+        if (*dst == NULL) {
+            *dst = new;
+        } else {
+            new_dst->next = new;
+        }
+        new_dst = new;
+        src = src->next;
+    }
+
+    return EOK;
+}
+
 static void pam_cache_auth_done(struct pam_auth_req *preq, int ret,
                                 time_t expire_date, time_t delayed_until);
 
@@ -358,6 +465,12 @@ static void pam_reply(struct pam_auth_req *preq)
 
     DEBUG(4, ("pam_reply get called.\n"));
 
+    /* First fill reply data from dp response */
+    if (!pd->pam_status && !pd->resp_list) {
+        pd->pam_status = preq->current_cb->preq->reply_pam_status;
+        ret = pam_copy_resp(pd, preq->current_cb->preq->reply_resp_list, &(preq->pd->resp_list));
+    }
+
     if (pd->pam_status == PAM_AUTHINFO_UNAVAIL) {
         switch(pd->cmd) {
         case SSS_PAM_AUTHENTICATE:
@@ -498,7 +611,7 @@ static void pam_reply(struct pam_auth_req *preq)
     }
 
 done:
-    sss_cmd_done(cctx, preq);
+    sss_cmd_done(cctx, NULL);
 }
 
 static void pam_cache_auth_done(struct pam_auth_req *preq, int ret,
@@ -841,21 +954,109 @@ static void pam_check_user_dp_callback(uint16_t err_maj, uint32_t err_min,
 static void pam_dom_forwarder(struct pam_auth_req *preq)
 {
     int ret;
+    struct pam_auth_callback *cb = talloc_zero(preq, struct pam_auth_callback);
+    TALLOC_CTX *tmp_ctx = NULL;
+    hash_key_t key;
+    hash_value_t value;
+    char *authtok = NULL;
+    int hret = 0;
+
+    tmp_ctx = talloc_new(NULL);
+    if (!tmp_ctx) {
+        ret = ENOMEM;
+        goto done;
+    }
+
+    if (dp_requests == NULL) {
+        ret = hash_create(10, &dp_requests, NULL, NULL);
+        if (ret != HASH_SUCCESS) {
+            DEBUG(1, ("Cannot create hash table (%s)\n", hash_error_string(ret)));
+            ret = EIO;
+            goto done;
+        }
+    }
 
     if (!preq->pd->domain) {
         preq->pd->domain = preq->domain->name;
     }
 
-    if (!NEED_CHECK_PROVIDER(preq->domain->provider)) {
-        preq->callback = pam_reply;
-        ret = LOCAL_pam_handler(preq);
+    key.type = HASH_KEY_STRING;
+    key.str = NULL;
+
+    if (preq->pd->authtok_type == SSS_AUTHTOK_TYPE_EMPTY) {
+        key.str = talloc_asprintf(tmp_ctx, "%d%s:%d@%s", preq->pd->cmd,
+                                  preq->pd->user, preq->pd->authtok_type,
+                                  preq->pd->domain);
+    } else {
+        authtok = talloc_zero_array(tmp_ctx, char, preq->pd->authtok_size+1);
+        if (authtok == NULL) {
+            DEBUG(0,("Out of memory?!\n"));
+            ret = ENOMEM;
+            goto done;
+        }
+        memcpy(authtok, preq->pd->authtok, preq->pd->authtok_size);
+        key.str = talloc_asprintf(tmp_ctx, "%d%s:%d%s@%s", preq->pd->cmd,
+                                  preq->pd->user, preq->pd->authtok_type,
+                                  authtok, preq->pd->domain);
     }
-    else {
-        preq->callback = pam_reply;
-        ret = pam_dp_send_req(preq, SSS_CLI_SOCKET_TIMEOUT/2);
-        DEBUG(4, ("pam_dp_send_req returned %d\n", ret));
+
+    if (!key.str) {
+        DEBUG(0,("Out of memory?!\n"));
+        ret = ENOMEM;
+        goto done;
     }
 
+    cb->callback = pam_reply;
+    cb->callback_preq = preq;
+    DLIST_ADD_END(preq->cb_list, cb, struct pam_auth_callback *);
+    talloc_set_destructor((TALLOC_CTX *)cb, pam_auth_callback_destructor);
+
+    hret = hash_lookup(dp_requests, &key, &value);
+    switch (hret) {
+    case HASH_SUCCESS:
+        DEBUG(7, ("Trace: corresponding auth request found, adding callback.\n"));
+        cb->preq = talloc_get_type(value.ptr, struct pam_auth_req);
+        if (!cb->preq) {
+            DEBUG(0, ("Could not retrieve auth request context\n"));
+            ret = EIO;
+            goto done;
+        }
+        /* we need this pointer, it is retrieved in pam_auth_invoke_callback()*/
+        DLIST_ADD_END(cb->preq->cb_list, cb, struct pam_auth_callback *);
+        break;
+
+    case HASH_ERROR_KEY_NOT_FOUND:
+        DEBUG(7, ("Trace: inserting the request to the hash table.\n"));
+        talloc_set_destructor((TALLOC_CTX *)preq, pam_auth_req_destructor);
+        cb->preq = preq;
+
+        value.type = HASH_VALUE_PTR;
+        value.ptr = preq;
+        hret = hash_enter(dp_requests, &key, &value);
+        if (hret != HASH_SUCCESS) {
+            DEBUG(1, ("Could not store request query (%s)\n",
+                      hash_error_string(hret)));
+            ret = EIO;
+            goto done;
+        }
+        preq->key = talloc_steal(preq, key.str);
+
+        if (!NEED_CHECK_PROVIDER(preq->domain->provider)) {
+            ret = LOCAL_pam_handler(preq);
+        }
+        else {
+            ret = pam_dp_send_req(preq, SSS_CLI_SOCKET_TIMEOUT/2);
+            DEBUG(4, ("pam_dp_send_req returned %d\n", ret));
+        }
+        break;
+
+    default:
+        break;
+    }
+
+
+done:
+    talloc_free(tmp_ctx);
     if (ret != EOK) {
         preq->pd->pam_status = PAM_SYSTEM_ERR;
         pam_reply(preq);
diff --git a/src/responder/pam/pamsrv_dp.c b/src/responder/pam/pamsrv_dp.c
index 40d4f30..4bb245c 100644
--- a/src/responder/pam/pamsrv_dp.c
+++ b/src/responder/pam/pamsrv_dp.c
@@ -38,6 +38,9 @@ static void pam_dp_process_reply(DBusPendingCall *pending, void *ptr)
     int ret;
     int type;
     struct pam_auth_req *preq;
+    struct timeval tv;
+    struct tevent_timer *te;
+    struct response_data *resp;
 
     preq = talloc_get_type(ptr, struct pam_auth_req);
 
@@ -51,7 +54,6 @@ static void pam_dp_process_reply(DBusPendingCall *pending, void *ptr)
         goto done;
     }
 
-
     type = dbus_message_get_type(msg);
     switch (type) {
         case DBUS_MESSAGE_TYPE_METHOD_RETURN:
@@ -72,11 +74,23 @@ static void pam_dp_process_reply(DBusPendingCall *pending, void *ptr)
             preq->pd->pam_status = PAM_SYSTEM_ERR;
     }
 
-
 done:
+    /* Now we need to make the reply data available to all callbacks */
+    preq->reply_pam_status = preq->pd->pam_status;
+    preq->reply_resp_list = preq->pd->resp_list;
+    preq->pd->resp_list = NULL;
+
+    resp = preq->reply_resp_list;
+    while (resp) {
+        talloc_steal(preq, resp);
+        resp = resp->next;
+    }
+
     dbus_pending_call_unref(pending);
     dbus_message_unref(msg);
-    preq->callback(preq);
+    tv = tevent_timeval_current();
+    te = tevent_add_timer(preq->cctx->ev, preq, tv,
+                          pam_auth_invoke_callback, preq);
 }
 
 int pam_dp_send_req(struct pam_auth_req *preq, int timeout)
-- 
1.7.3.1

