From 1f2b11113b14c4ed9f1dd0c54bf522fe3e25b2b7 Mon Sep 17 00:00:00 2001
From: Jan Zeleny <jzeleny@redhat.com>
Date: Mon, 6 Dec 2010 09:15:38 +0100
Subject: [PATCH] Generate ccache name at the beginning of auth

In order to avoid situation when user logging to multiple
terminals simultaneously gets different ccache files in each
terminal (if random ccache files are used), the ccache file
names are determined at the very beginning of authorization
process.

Ticket: #533
---
 src/providers/krb5/krb5_auth.c |  233 ++++++++++++++++++++++++++++++---------
 src/providers/krb5/krb5_auth.h |    3 +
 2 files changed, 182 insertions(+), 54 deletions(-)

diff --git a/src/providers/krb5/krb5_auth.c b/src/providers/krb5/krb5_auth.c
index 9dc7a2c..19c41d9 100644
--- a/src/providers/krb5/krb5_auth.c
+++ b/src/providers/krb5/krb5_auth.c
@@ -24,6 +24,7 @@
 
 #include <errno.h>
 #include <sys/time.h>
+#include <unistd.h>
 
 #include <sys/types.h>
 #include <sys/wait.h>
@@ -38,6 +39,9 @@
 #include "providers/child_common.h"
 #include "providers/krb5/krb5_auth.h"
 #include "providers/krb5/krb5_utils.h"
+#include "dhash.h"
+
+static hash_table_t *ccache_names = NULL;
 
 static errno_t safe_remove_old_ccache_file(const char *old_ccache_file,
                                            const char *new_ccache_file)
@@ -199,6 +203,71 @@ done:
     return ret;
 }
 
+static errno_t krb5_parse_ccname_hash(TALLOC_CTX *mem_ctx, const char *hash,
+                               char **new_ccname, int *refcount)
+{
+    char *colon = strrchr(hash,':');
+    char *end = NULL;
+
+    if (colon == NULL) {
+        return EINVAL;
+    }
+
+    *refcount = strtol(colon+1, &end, 10);
+    if (end == NULL || end == colon+1) {
+        return EINVAL;
+    }
+
+    *new_ccname = talloc_strndup(mem_ctx, hash, colon-hash);
+    if (*new_ccname == NULL) {
+        return ENOMEM;
+    }
+    return EOK;
+}
+
+/**
+ * Decrease reference counter of the potential new ccname
+ *
+ * When the counter is 0, no auth request for given user should be in progress
+ * and the new ccname candidate will be generated during the next auth request
+ */
+static errno_t krb5_ccname_decref(char *user)
+{
+    hash_key_t key;
+    hash_value_t value;
+    errno_t ret = EOK;
+    int ccache_file_new_ref = 0;
+    char *ccache_file_new = NULL;
+    TALLOC_CTX *tmp_ctx = talloc_new(NULL);
+
+    key.type = HASH_KEY_STRING;
+    key.str = user;
+    DEBUG(8, ("Decrementing ccname reference counter.\n"));
+
+    ret = hash_lookup(ccache_names, &key, &value);
+    if (ret == HASH_SUCCESS) {
+        ret = krb5_parse_ccname_hash(tmp_ctx, value.ptr, &ccache_file_new,
+                                     &ccache_file_new_ref);
+        if (ret != EOK) {
+           goto done;
+        }
+        ccache_file_new_ref--;
+        if (ccache_file_new_ref == 0) {
+            ret = hash_delete(ccache_names, &key);
+        } else {
+            value.ptr = talloc_asprintf(tmp_ctx, "%s:%d", ccache_file_new,
+                                        ccache_file_new_ref);
+            ret = hash_enter(ccache_names, &key, &value);
+        }
+    } else {
+        ret = EINVAL;
+    }
+
+done:
+    talloc_free(tmp_ctx);
+    return ret;
+}
+
 static struct krb5_ctx *get_krb5_ctx(struct be_req *be_req)
 {
     struct pam_data *pd;
@@ -301,12 +370,20 @@ struct tevent_req *krb5_auth_send(TALLOC_CTX *mem_ctx,
     struct krb5_auth_state *state;
     struct ldb_result *res;
     struct krb5child_req *kr = NULL;
+    hash_key_t key;
+    hash_value_t value;
     const char *ccache_file = NULL;
+    const char *ccache_file_new = NULL;
+    int ccache_file_new_ref = 0;
+    bool private_path = false;
+    char *cc_file_name = NULL;
+    size_t ccname_len = 0;
     const char *realm;
     krb5_error_code kerr;
     struct tevent_req *req;
     struct tevent_req *subreq;
     int ret;
+    int fd;
 
     req = tevent_req_create(mem_ctx, &state, struct krb5_auth_state);
     if (req == NULL) {
@@ -322,6 +399,15 @@ struct tevent_req *krb5_auth_send(TALLOC_CTX *mem_ctx,
     state->pam_status = PAM_SYSTEM_ERR;
     state->dp_err = DP_ERR_FATAL;
 
+    if (ccache_names == NULL) {
+        ret = hash_create(CCACHE_HTABLE_SIZE, &ccache_names, NULL, NULL);
+        if (ret != HASH_SUCCESS) {
+            DEBUG(1, ("Creating ccache hash table failed.\n"));
+            ret = EIO;
+            goto done;
+        }
+    }
+
     switch (pd->cmd) {
         case SSS_PAM_AUTHENTICATE:
         case SSS_PAM_CHAUTHTOK:
@@ -425,6 +511,85 @@ struct tevent_req *krb5_auth_send(TALLOC_CTX *mem_ctx,
             goto done;
         }
 
+        key.type = HASH_KEY_STRING;
+        key.str = kr->pd->user;
+        ret = hash_lookup(ccache_names, &key, &value);
+        if (ret == HASH_SUCCESS) {
+            ret = krb5_parse_ccname_hash(kr, value.ptr, &cc_file_name,
+                                         &ccache_file_new_ref);
+            if (ret != EOK) {
+                goto done;
+            }
+            ccache_file_new = cc_file_name;
+            ccache_file_new_ref++;
+        } else if (ret == HASH_ERROR_KEY_NOT_FOUND) {
+            cc_file_name = expand_ccname_template(kr, kr,
+                                                  dp_opt_get_cstring(kr->krb5_ctx->opts,
+                                                                     KRB5_CCNAME_TMPL),
+                                                  true, &private_path);
+            if (cc_file_name == NULL) {
+                DEBUG(1, ("expand_ccname_template failed.\n"));
+                ret = ENOMEM;
+                goto done;
+            }
+
+            ret = create_ccache_dir(kr, cc_file_name,
+                                    kr->krb5_ctx->illegal_path_re,
+                                    kr->uid, kr->gid, private_path);
+            if (ret != EOK) {
+                DEBUG(1, ("create_ccache_dir failed.\n"));
+                goto done;
+            }
+
+            /* A little hack here - assigning to const char *
+             * even though we know that we will be modifiyng the content
+             * a few lines below
+             */
+            ccache_file_new = cc_file_name;
+            if (strncmp(cc_file_name, "FILE:", 5) == 0) {
+                cc_file_name = cc_file_name + 5;
+            }
+
+            if (cc_file_name[0] != '/') {
+                DEBUG(1, ("Ccache filename is not an absolute path.\n"));
+                ret = EINVAL;
+                goto done;
+            }
+
+            ccname_len = strlen(cc_file_name);
+            if (ccname_len >= 6 && strcmp(cc_file_name + (ccname_len-6), "XXXXXX")==0 ) {
+                fd = mkstemp(cc_file_name);
+                if (fd == -1) {
+                    DEBUG(1, ("mkstemp failed [%d][%s].\n", errno, strerror(errno)));
+                    ret = errno;
+                    goto done;
+                }
+                close(fd);
+                unlink(cc_file_name);
+            }
+
+            ccache_file_new_ref = 1;
+            value.type = HASH_VALUE_PTR;
+        } else {
+            DEBUG(0,("Could not query request list (%s)\n",
+                      hash_error_string(ret)));
+            ret = EIO;
+            goto done;
+        }
+        DEBUG(8, ("Storing new ccache file: %s (%d references)\n",
+                  ccache_file_new, ccache_file_new_ref));
+        kr->new_ccname = ccache_file_new;
+        kr->new_ccname_ref = ccache_file_new_ref;
+        value.ptr = talloc_asprintf(kr,"%s:%d", ccache_file_new, ccache_file_new_ref);
+        if (value.ptr != NULL) {
+            ret = hash_enter(ccache_names, &key, &value);
+            if (ret != EOK) {
+                DEBUG(5, ("Error: Couldn't store new ccache file info in hash table. Trying to continue.\n"));
+            }
+        } else {
+            DEBUG(5, ("Not enough memory to store new ccache file info. Trying to continue.\n"));
+        }
+
         ccache_file = ldb_msg_find_attr_as_string(res->msgs[0],
                                                   SYSDB_CCACHE_FILE,
                                                   NULL);
@@ -566,7 +731,6 @@ static void krb5_find_ccache_step(struct tevent_req *req)
     struct krb5child_req *kr = state->kr;
     struct pam_data *pd = kr->pd;
     char *msg;
-    bool private_path = false;
     struct tevent_req *subreq = NULL;
 
     if (!kr->is_offline) {
@@ -588,24 +752,8 @@ static void krb5_find_ccache_step(struct tevent_req *req)
         (kr->is_offline && !kr->active_ccache_present &&
             !kr->valid_tgt_present) ||
         (!kr->is_offline && !kr->active_ccache_present)) {
-            DEBUG(9, ("Recreating  ccache file.\n"));
-            kr->ccname = expand_ccname_template(kr, kr,
-                                          dp_opt_get_cstring(kr->krb5_ctx->opts,
-                                                             KRB5_CCNAME_TMPL),
-                                                true, &private_path);
-            if (kr->ccname == NULL) {
-                DEBUG(1, ("expand_ccname_template failed.\n"));
-                ret = ENOMEM;
-                goto done;
-            }
-
-            ret = create_ccache_dir(kr, kr->ccname,
-                                    kr->krb5_ctx->illegal_path_re,
-                                    kr->uid, kr->gid, private_path);
-            if (ret != EOK) {
-                DEBUG(1, ("create_ccache_dir failed.\n"));
-                goto done;
-            }
+            DEBUG(9, ("Using new ccache file (%s).\n", kr->new_ccname));
+            kr->ccname = kr->new_ccname;
     }
 
     if (kr->is_offline) {
@@ -615,6 +763,11 @@ static void krb5_find_ccache_step(struct tevent_req *req)
             DEBUG(9, ("Valid TGT available or "
                       "ccache file is already in use.\n"));
             kr->ccname = kr->old_ccname;
+            kr->new_ccname = NULL;
+            kr->new_ccname_ref = 0;
+            ret = krb5_save_ccname(kr, state->be_ctx->sysdb, state->be_ctx->domain,
+                                   kr->pd->user, kr->new_ccname);
+
             msg = talloc_asprintf(pd, "%s=%s", CCACHE_ENV_NAME, kr->ccname);
             if (msg == NULL) {
                 DEBUG(1, ("talloc_asprintf failed.\n"));
@@ -683,7 +836,6 @@ static void krb5_child_done(struct tevent_req *subreq)
     int ret;
     uint8_t *buf = NULL;
     ssize_t len = -1;
-    ssize_t pref_len;
     size_t p;
     int32_t msg_status;
     int32_t msg_type;
@@ -735,22 +887,6 @@ static void krb5_child_done(struct tevent_req *subreq)
             goto done;
         }
 
-        /* We need to save the name of the credential cache file. To find it
-         * we check if the data part of a message starts with
-         * CCACHE_ENV_NAME"=". pref_len also counts the trailing '=' because
-         * sizeof() counts the trailing '\0' of a string. */
-        pref_len = sizeof(CCACHE_ENV_NAME);
-        if (msg_len > pref_len &&
-            strncmp((const char *) &buf[p], CCACHE_ENV_NAME"=", pref_len) == 0) {
-            kr->ccname = talloc_strndup(kr, (char *) &buf[p+pref_len],
-                                        msg_len-pref_len);
-            if (kr->ccname == NULL) {
-                DEBUG(1, ("talloc_strndup failed.\n"));
-                ret = ENOMEM;
-                goto done;
-            }
-        }
-
         ret = pam_add_response(pd, msg_type, msg_len, &buf[p]);
         if (ret != EOK) {
             /* This is not a fatal error */
@@ -818,16 +954,7 @@ static void krb5_child_done(struct tevent_req *subreq)
         fo_set_port_status(kr->srv, PORT_WORKING);
     }
 
-    /* Now only a successful authentication or password change is left.
-     *
-     * We expect that one of the messages in the received buffer contains
-     * the name of the credential cache file. */
-    if (kr->ccname == NULL) {
-        DEBUG(1, ("Missing ccache name in child response.\n"));
-        ret = EINVAL;
-        goto done;
-    }
-
+    /* Now only a successful authentication or password change is left. */
     if (kr->old_ccname != NULL) {
         ret = safe_remove_old_ccache_file(kr->old_ccname, kr->ccname);
         if (ret != EOK) {
@@ -835,14 +962,6 @@ static void krb5_child_done(struct tevent_req *subreq)
         }
     }
 
-    struct sysdb_attrs *attrs;
-    attrs = sysdb_new_attrs(state);
-    ret = sysdb_attrs_add_string(attrs, SYSDB_CCACHE_FILE, kr->ccname);
-    if (ret != EOK) {
-        DEBUG(1, ("sysdb_attrs_add_string failed.\n"));
-        goto done;
-    }
-
     ret = krb5_save_ccname(state, state->be_ctx->sysdb,
                            state->be_ctx->domain,
                            pd->user, kr->ccname);
@@ -1116,6 +1235,12 @@ void krb5_auth_done(struct tevent_req *req)
         pd->pam_status = pam_status;
     }
 
+    ret = krb5_ccname_decref(pd->user);
+    if (ret) {
+        DEBUG(3, ("krb5_ccname_decref failed.\n"));
+    }
+
+
     krb_reply(be_req, dp_err, pd->pam_status);
 }
 
diff --git a/src/providers/krb5/krb5_auth.h b/src/providers/krb5/krb5_auth.h
index 130e85d..9353e9a 100644
--- a/src/providers/krb5/krb5_auth.h
+++ b/src/providers/krb5/krb5_auth.h
@@ -34,6 +34,7 @@
 #include "providers/krb5/krb5_common.h"
 
 #define CCACHE_ENV_NAME "KRB5CCNAME"
+#define CCACHE_HTABLE_SIZE 10
 
 #define ILLEGAL_PATH_PATTERN "//|/\\./|/\\.\\./"
 
@@ -43,6 +44,8 @@ struct krb5child_req {
 
     const char *ccname;
     const char *old_ccname;
+    const char *new_ccname;
+    int new_ccname_ref;
     const char *homedir;
     const char *upn;
     uid_t uid;
-- 
1.7.3.1

