From f750219f1ee65217974b9ed192a745fb086b757b Mon Sep 17 00:00:00 2001
From: Jan Zeleny <jzeleny@redhat.com>
Date: Tue, 29 Mar 2011 02:50:28 -0400
Subject: [PATCH] Extend and move function for finding principal in keytab

The function now supports finding principal in keytab not only based on
realm, but based on both realm and primary/instance parts. The function
also supports * wildcard at the beginning or at the end of primary
principal part. The function for finding principal has been moved to
util/sss_krb5.c, so it can be used in other parts of the code.
---
 src/providers/krb5/krb5_child.c |   82 +--------------------
 src/util/sss_krb5.c             |  159 +++++++++++++++++++++++++++++++++++++++
 src/util/sss_krb5.h             |    6 ++
 3 files changed, 167 insertions(+), 80 deletions(-)

diff --git a/src/providers/krb5/krb5_child.c b/src/providers/krb5/krb5_child.c
index 335da423c1752bf113ef1dae6a324cff1b964976..1ed63f6b2583c2fad267ad04ab0e32bd7f6d327b 100644
--- a/src/providers/krb5/krb5_child.c
+++ b/src/providers/krb5/krb5_child.c
@@ -30,6 +30,7 @@
 #include <security/pam_modules.h>
 
 #include "util/util.h"
+#include "util/sss_krb5.h"
 #include "util/user_info_msg.h"
 #include "providers/child_common.h"
 #include "providers/dp_backend.h"
@@ -492,85 +493,6 @@ static errno_t add_ticket_times_to_response(struct krb5_req *kr)
     return ret;
 }
 
-static krb5_error_code find_principal_in_keytab(krb5_context ctx,
-                                                krb5_keytab keytab,
-                                                const char *realm,
-                                                krb5_principal *princ)
-{
-    krb5_error_code kerr;
-    krb5_error_code kt_err;
-    krb5_error_code kerr_d;
-    krb5_kt_cursor cursor;
-    krb5_keytab_entry entry;
-    bool principal_found = false;
-
-    memset(&cursor, 0, sizeof(cursor));
-    kerr = krb5_kt_start_seq_get(ctx, keytab, &cursor);
-    if (kerr != 0) {
-        DEBUG(1, ("krb5_kt_start_seq_get failed.\n"));
-        KRB5_DEBUG(1, kerr);
-        return kerr;
-    }
-
-    /* We look for the first entry from our realm or take the last one */
-    memset(&entry, 0, sizeof(entry));
-    while ((kt_err = krb5_kt_next_entry(ctx, keytab, &entry, &cursor)) == 0) {
-        if (krb5_princ_realm(ctx, entry.principal)->length == strlen(realm) &&
-            strncmp(krb5_princ_realm(ctx, entry.principal)->data, realm,
-                    krb5_princ_realm(ctx, entry.principal)->length) == 0) {
-            DEBUG(9, ("Found keytab entry with the realm of the credential.\n"));
-            principal_found = true;
-            break;
-        }
-
-        kerr = krb5_free_keytab_entry_contents(ctx, &entry);
-        if (kerr != 0) {
-            DEBUG(1, ("Failed to free keytab entry.\n"));
-        }
-        memset(&entry, 0, sizeof(entry));
-    }
-
-    /* Close the keytab here.  Even though we're using cursors, the file
-     * handle is stored in the krb5_keytab structure, and it gets
-     * overwritten by other keytab calls, creating a leak. */
-    kerr = krb5_kt_end_seq_get(ctx, keytab, &cursor);
-    if (kerr != 0) {
-        DEBUG(1, ("krb5_kt_end_seq_get failed.\n"));
-        goto done;
-    }
-
-    if (!principal_found) {
-        kerr = KRB5_KT_NOTFOUND;
-        DEBUG(1, ("No principal from realm [%s] found in keytab.\n", realm));
-        goto done;
-    }
-
-    /* check if we got any errors from krb5_kt_next_entry */
-    if (kt_err != 0 && kt_err != KRB5_KT_END) {
-        DEBUG(1, ("Error while reading keytab.\n"));
-        KRB5_DEBUG(1, kerr);
-        goto done;
-    }
-
-    kerr = krb5_copy_principal(ctx, entry.principal, princ);
-    if (kerr != 0) {
-        DEBUG(1, ("krb5_copy_principal failed.\n"));
-        KRB5_DEBUG(1, kerr);
-        goto done;
-    }
-
-    kerr = 0;
-
-done:
-    kerr_d = krb5_free_keytab_entry_contents(ctx, &entry);
-    if (kerr_d != 0) {
-        DEBUG(1, ("Failed to free keytab entry.\n"));
-        KRB5_DEBUG(1, kerr_d);
-    }
-
-    return kerr;
-}
-
 static krb5_error_code validate_tgt(struct krb5_req *kr)
 {
     krb5_error_code kerr;
@@ -1338,7 +1260,7 @@ static krb5_error_code check_fast_ccache(krb5_context ctx, const char *realm,
         goto done;
     }
 
-    kerr = find_principal_in_keytab(ctx, keytab, realm, &client_princ);
+    kerr = find_principal_in_keytab(ctx, keytab, NULL, realm, &client_princ);
     if (kerr != 0) {
         DEBUG(1, ("find_principal_in_keytab failed.\n"));
         goto done;
diff --git a/src/util/sss_krb5.c b/src/util/sss_krb5.c
index 894dd443d02f4f0549f4e50d1aff87be3f1c74e8..4c78cc0911d9369e0dafb920cbd276c2b79a56ad 100644
--- a/src/util/sss_krb5.c
+++ b/src/util/sss_krb5.c
@@ -188,6 +188,165 @@ int sss_krb5_verify_keytab_ex(const char *principal, const char *keytab_name,
     return EOK;
 }
 
+
+enum matching_mode {MODE_NORMAL, MODE_PREFIX, MODE_POSTFIX};
+/**
+ * We only have primary and instances stored separately, we need to
+ * join them to one string and compare that string.
+ *
+ * @param ctx kerberos context
+ * @param principal principal we want to match
+ * @param pattern_primary primary part of the principal we want to
+ *        perform matching against. It is possible to use * wildcard
+ *        at the beginning or at the end of the string. If NULL, it
+ *        will act as "*"
+ * @param pattern_realm realm part of the principal we want to perform
+ *        the matching against. If NULL, it will act as "*"
+ */
+static bool match_principal(krb5_context ctx,
+                     krb5_principal principal,
+                     const char *pattern_primary,
+                     const char *pattern_realm)
+{
+    krb5_data *realm_data;
+    char *primary = NULL;
+    char *primary_str = NULL;
+    int primary_str_len = 0;
+    int tmp_len;
+    int len_diff;
+
+    int mode = MODE_NORMAL;
+    TALLOC_CTX *tmp_ctx;
+    bool ret;
+    char *tmp_ptr;
+
+    realm_data = krb5_princ_realm(ctx, principal);
+
+    tmp_ctx = talloc_new(NULL);
+    if (!tmp_ctx) {
+        DEBUG(1, ("talloc_new failed\n"));
+        return false;
+    }
+
+    if (pattern_primary) {
+        tmp_len = strlen(pattern_primary);
+        if (pattern_primary[tmp_len] == '*') {
+            mode = MODE_PREFIX;
+            primary_str = talloc_strdup(tmp_ctx, pattern_primary);
+            primary_str[tmp_len] = '\0';
+            primary_str_len = tmp_len-1;
+        } else if (pattern_primary[0] == '*') {
+            mode = MODE_POSTFIX;
+            primary_str = talloc_strdup(tmp_ctx, pattern_primary+1);
+            primary_str_len = tmp_len-1;
+        }
+
+        krb5_unparse_name(ctx, principal, &primary);
+        tmp_ptr = strchr(primary, '@');
+        if (tmp_ptr) {
+            *tmp_ptr = '\0';
+        }
+
+        len_diff = strlen(primary)-primary_str_len;
+
+        if ((mode == MODE_NORMAL &&
+                strcmp(primary, pattern_primary) != 0) ||
+            (mode == MODE_PREFIX &&
+                strncmp(primary, primary_str, primary_str_len) != 0) ||
+            (mode == MODE_POSTFIX &&
+                strcmp(primary+len_diff, primary_str) != 0)) {
+            ret = false;
+            goto done;
+        }
+    }
+
+    if (!pattern_realm || (realm_data->length == strlen(pattern_realm) &&
+        strncmp(realm_data->data, pattern_realm, realm_data->length) == 0)) {
+        DEBUG(7, ("Principal matched to the sample (%s@%s).\n", pattern_primary,
+                                                                pattern_realm));
+        ret = true;
+    }
+
+done:
+    free(primary);
+    talloc_free(tmp_ctx);
+    return ret;
+}
+
+krb5_error_code find_principal_in_keytab(krb5_context ctx,
+                                         krb5_keytab keytab,
+                                         const char *pattern_primary,
+                                         const char *pattern_realm,
+                                         krb5_principal *princ)
+{
+    krb5_error_code kerr;
+    krb5_error_code kt_err;
+    krb5_error_code kerr_d;
+    krb5_kt_cursor cursor;
+    krb5_keytab_entry entry;
+    bool principal_found = false;
+
+    memset(&cursor, 0, sizeof(cursor));
+    kerr = krb5_kt_start_seq_get(ctx, keytab, &cursor);
+    if (kerr != 0) {
+        DEBUG(1, ("krb5_kt_start_seq_get failed.\n"));
+        return kerr;
+    }
+
+    DEBUG(9, ("Trying to find principal %s@%s in keytab.\n", pattern_primary, pattern_realm));
+    memset(&entry, 0, sizeof(entry));
+    while ((kt_err = krb5_kt_next_entry(ctx, keytab, &entry, &cursor)) == 0) {
+        principal_found = match_principal(ctx, entry.principal, pattern_primary, pattern_realm);
+        if (principal_found) {
+            break;
+        }
+
+        kerr = krb5_free_keytab_entry_contents(ctx, &entry);
+        if (kerr != 0) {
+            DEBUG(1, ("Failed to free keytab entry.\n"));
+        }
+        memset(&entry, 0, sizeof(entry));
+    }
+
+    /* Close the keytab here.  Even though we're using cursors, the file
+     * handle is stored in the krb5_keytab structure, and it gets
+     * overwritten by other keytab calls, creating a leak. */
+    kerr = krb5_kt_end_seq_get(ctx, keytab, &cursor);
+    if (kerr != 0) {
+        DEBUG(1, ("krb5_kt_end_seq_get failed.\n"));
+        goto done;
+    }
+
+    if (!principal_found) {
+        kerr = KRB5_KT_NOTFOUND;
+        DEBUG(1, ("No principal matching %s@%s found in keytab.\n",
+                  pattern_primary, pattern_realm));
+        goto done;
+    }
+
+    /* check if we got any errors from krb5_kt_next_entry */
+    if (kt_err != 0 && kt_err != KRB5_KT_END) {
+        DEBUG(1, ("Error while reading keytab.\n"));
+        goto done;
+    }
+
+    kerr = krb5_copy_principal(ctx, entry.principal, princ);
+    if (kerr != 0) {
+        DEBUG(1, ("krb5_copy_principal failed.\n"));
+        goto done;
+    }
+
+    kerr = 0;
+
+done:
+    kerr_d = krb5_free_keytab_entry_contents(ctx, &entry);
+    if (kerr_d != 0) {
+        DEBUG(1, ("Failed to free keytab entry.\n"));
+    }
+
+    return kerr;
+}
+
 const char *KRB5_CALLCONV sss_krb5_get_error_message(krb5_context ctx,
                                                krb5_error_code ec)
 {
diff --git a/src/util/sss_krb5.h b/src/util/sss_krb5.h
index 0a82315cca48b5712a9f0225efbb374185144ec7..f25f3285b2cac8b37fde73985775c276c6ea589d 100644
--- a/src/util/sss_krb5.h
+++ b/src/util/sss_krb5.h
@@ -58,6 +58,12 @@ int sss_krb5_verify_keytab(const char *principal,
 int sss_krb5_verify_keytab_ex(const char *principal, const char *keytab_name,
                               krb5_context context, krb5_keytab keytab);
 
+krb5_error_code find_principal_in_keytab(krb5_context ctx,
+                                         krb5_keytab keytab,
+                                         const char *pattern_primary,
+                                         const char *pattern_realm,
+                                         krb5_principal *princ);
+
 #ifndef HAVE_KRB5_GET_INIT_CREDS_OPT_SET_EXPIRE_CALLBACK
 typedef void krb5_expire_callback_func(krb5_context context, void *data,
                                              krb5_timestamp password_expiration,
-- 
1.7.4.1

