Imported Upstream version 1.15.1
[platform/upstream/krb5.git] / src / lib / krb5 / krb / gic_keytab.c
index 88de6a8..f20af53 100644 (file)
@@ -26,6 +26,7 @@
 #ifndef LEAN_CLIENT
 
 #include "k5-int.h"
+#include "int-proto.h"
 #include "init_creds_ctx.h"
 
 static krb5_error_code
@@ -37,13 +38,18 @@ get_as_key_keytab(krb5_context context,
                   krb5_data *salt,
                   krb5_data *params,
                   krb5_keyblock *as_key,
-                  void *gak_data)
+                  void *gak_data,
+                  k5_response_items *ritems)
 {
     krb5_keytab keytab = (krb5_keytab) gak_data;
     krb5_error_code ret;
     krb5_keytab_entry kt_ent;
     krb5_keyblock *kt_key;
 
+    /* We don't need the password from the responder to create the AS key. */
+    if (as_key == NULL)
+        return 0;
+
     /* if there's already a key of the correct etype, we're done.
        if the etype is wrong, free the existing key, and make
        a new one. */
@@ -76,15 +82,177 @@ get_as_key_keytab(krb5_context context,
     return(ret);
 }
 
+/* Return the list of etypes available for client in keytab. */
+static krb5_error_code
+lookup_etypes_for_keytab(krb5_context context, krb5_keytab keytab,
+                         krb5_principal client, krb5_enctype **etypes_out)
+{
+    krb5_kt_cursor cursor;
+    krb5_keytab_entry entry;
+    krb5_enctype *p, *etypes = NULL, etype;
+    krb5_kvno max_kvno = 0, vno;
+    krb5_error_code ret;
+    krb5_boolean match;
+    size_t count = 0;
+
+    *etypes_out = NULL;
+
+    if (keytab->ops->start_seq_get == NULL)
+        return EINVAL;
+    ret = krb5_kt_start_seq_get(context, keytab, &cursor);
+    if (ret != 0)
+        return ret;
+
+    while (!(ret = krb5_kt_next_entry(context, keytab, &entry, &cursor))) {
+        /* Extract what we need from the entry and free it. */
+        etype = entry.key.enctype;
+        vno = entry.vno;
+        match = krb5_principal_compare(context, entry.principal, client);
+        krb5_free_keytab_entry_contents(context, &entry);
+
+        /* Filter out old or non-matching entries and invalid enctypes. */
+        if (vno < max_kvno || !match || !krb5_c_valid_enctype(etype))
+            continue;
+
+        /* Update max_kvno and reset the list if we find a newer kvno. */
+        if (vno > max_kvno) {
+            max_kvno = vno;
+            free(etypes);
+            etypes = NULL;
+            count = 0;
+        }
+
+        /* Leave room for the terminator and possibly a second entry. */
+        p = realloc(etypes, (count + 3) * sizeof(*etypes));
+        if (p == NULL) {
+            ret = ENOMEM;
+            goto cleanup;
+        }
+        etypes = p;
+        etypes[count++] = etype;
+        /* All DES key types work with des-cbc-crc, which is more likely to be
+         * accepted by the KDC (since MIT KDCs refuse des-cbc-md5). */
+        if (etype == ENCTYPE_DES_CBC_MD5 || etype == ENCTYPE_DES_CBC_MD4)
+            etypes[count++] = ENCTYPE_DES_CBC_CRC;
+        etypes[count] = 0;
+    }
+    if (ret != KRB5_KT_END)
+        goto cleanup;
+    ret = 0;
+
+    *etypes_out = etypes;
+    etypes = NULL;
+
+cleanup:
+    krb5_kt_end_seq_get(context, keytab, &cursor);
+    free(etypes);
+    return ret;
+}
+
+/* Move the entries in keytab_list (zero-terminated) to the front of req_list
+ * (of length req_len), preserving order otherwise. */
+static krb5_error_code
+sort_enctypes(krb5_enctype *req_list, int req_len, krb5_enctype *keytab_list)
+{
+    krb5_enctype *save_list;
+    int save_pos, req_pos, i;
+
+    save_list = malloc(req_len * sizeof(*save_list));
+    if (save_list == NULL)
+        return ENOMEM;
+
+    /* Sort req_list entries into the front of req_list or into save_list. */
+    req_pos = save_pos = 0;
+    for (i = 0; i < req_len; i++) {
+        if (k5_etypes_contains(keytab_list, req_list[i]))
+            req_list[req_pos++] = req_list[i];
+        else
+            save_list[save_pos++] = req_list[i];
+    }
+
+    /* Put the entries we saved back in at the end, in order. */
+    for (i = 0; i < save_pos; i++)
+        req_list[req_pos++] = save_list[i];
+    assert(req_pos == req_len);
+
+    free(save_list);
+    return 0;
+}
+
 krb5_error_code KRB5_CALLCONV
 krb5_init_creds_set_keytab(krb5_context context,
                            krb5_init_creds_context ctx,
                            krb5_keytab keytab)
 {
+    krb5_enctype *etype_list;
+    krb5_error_code ret;
+    char *name;
+
     ctx->gak_fct = get_as_key_keytab;
     ctx->gak_data = keytab;
 
-    return 0;
+    ret = lookup_etypes_for_keytab(context, keytab, ctx->request->client,
+                                   &etype_list);
+    if (ret) {
+        TRACE_INIT_CREDS_KEYTAB_LOOKUP_FAILED(context, ret);
+        return 0;
+    }
+    TRACE_INIT_CREDS_KEYTAB_LOOKUP(context, etype_list);
+
+    /* Error out if we have no keys for the client principal. */
+    if (etype_list == NULL) {
+        ret = krb5_unparse_name(context, ctx->request->client, &name);
+        if (ret == 0) {
+            k5_setmsg(context, KRB5_KT_NOTFOUND,
+                      _("Keytab contains no suitable keys for %s"), name);
+        }
+        krb5_free_unparsed_name(context, name);
+        return KRB5_KT_NOTFOUND;
+    }
+
+    /* Sort the request enctypes so the ones in the keytab appear first. */
+    ret = sort_enctypes(ctx->request->ktype, ctx->request->nktypes,
+                        etype_list);
+    free(etype_list);
+    return ret;
+}
+
+static krb5_error_code
+get_init_creds_keytab(krb5_context context, krb5_creds *creds,
+                      krb5_principal client, krb5_keytab keytab,
+                      krb5_deltat start_time, const char *in_tkt_service,
+                      krb5_get_init_creds_opt *options, int *use_master)
+{
+    krb5_error_code ret;
+    krb5_init_creds_context ctx = NULL;
+
+    ret = krb5_init_creds_init(context, client, NULL, NULL, start_time,
+                               options, &ctx);
+    if (ret != 0)
+        goto cleanup;
+
+    if (in_tkt_service) {
+        ret = krb5_init_creds_set_service(context, ctx, in_tkt_service);
+        if (ret != 0)
+            goto cleanup;
+    }
+
+    ret = krb5_init_creds_set_keytab(context, ctx, keytab);
+    if (ret != 0)
+        goto cleanup;
+
+    ret = k5_init_creds_get(context, ctx, use_master);
+    if (ret != 0)
+        goto cleanup;
+
+    ret = krb5_init_creds_get_creds(context, ctx, creds);
+    if (ret != 0)
+        goto cleanup;
+
+cleanup:
+    krb5_init_creds_free(context, ctx);
+
+    return ret;
 }
 
 krb5_error_code KRB5_CALLCONV
@@ -93,12 +261,13 @@ krb5_get_init_creds_keytab(krb5_context context,
                            krb5_principal client,
                            krb5_keytab arg_keytab,
                            krb5_deltat start_time,
-                           char *in_tkt_service,
+                           const char *in_tkt_service,
                            krb5_get_init_creds_opt *options)
 {
-    krb5_error_code ret, ret2;
+    krb5_error_code ret;
     int use_master;
     krb5_keytab keytab;
+    struct errinfo errsave = EMPTY_ERRINFO;
 
     if (arg_keytab == NULL) {
         if ((ret = krb5_kt_default(context, &keytab)))
@@ -111,10 +280,8 @@ krb5_get_init_creds_keytab(krb5_context context,
 
     /* first try: get the requested tkt from any kdc */
 
-    ret = krb5int_get_init_creds(context, creds, client, NULL, NULL,
-                                 start_time, in_tkt_service, options,
-                                 get_as_key_keytab, (void *) keytab,
-                                 &use_master,NULL);
+    ret = get_init_creds_keytab(context, creds, client, keytab, start_time,
+                                in_tkt_service, options, &use_master);
 
     /* check for success */
 
@@ -132,25 +299,18 @@ krb5_get_init_creds_keytab(krb5_context context,
     if (!use_master) {
         use_master = 1;
 
-        ret2 = krb5int_get_init_creds(context, creds, client, NULL, NULL,
-                                      start_time, in_tkt_service, options,
-                                      get_as_key_keytab, (void *) keytab,
-                                      &use_master, NULL);
-
-        if (ret2 == 0) {
-            ret = 0;
-            goto cleanup;
-        }
-
-        /* if the master is unreachable, return the error from the
-           slave we were able to contact */
-
-        if ((ret2 == KRB5_KDC_UNREACH) ||
-            (ret2 == KRB5_REALM_CANT_RESOLVE) ||
-            (ret2 == KRB5_REALM_UNKNOWN))
+        k5_save_ctx_error(context, ret, &errsave);
+        ret = get_init_creds_keytab(context, creds, client, keytab,
+                                    start_time, in_tkt_service, options,
+                                    &use_master);
+        if (ret == 0)
             goto cleanup;
 
-        ret = ret2;
+        /* If the master is unreachable, return the error from the slave we
+         * were able to contact. */
+        if (ret == KRB5_KDC_UNREACH || ret == KRB5_REALM_CANT_RESOLVE ||
+            ret == KRB5_REALM_UNKNOWN)
+            ret = k5_restore_ctx_error(context, &errsave);
     }
 
     /* at this point, we have a response from the master.  Since we don't
@@ -159,6 +319,7 @@ krb5_get_init_creds_keytab(krb5_context context,
 cleanup:
     if (arg_keytab == NULL)
         krb5_kt_close(context, keytab);
+    k5_clear_error(&errsave);
 
     return(ret);
 }
@@ -176,9 +337,8 @@ krb5_get_in_tkt_with_keytab(krb5_context context, krb5_flags options,
     krb5_principal client_princ, server_princ;
     int use_master = 0;
 
-    retval = krb5int_populate_gic_opt(context, &opts,
-                                      options, addrs, ktypes,
-                                      pre_auth_types, creds);
+    retval = k5_populate_gic_opt(context, &opts, options, addrs, ktypes,
+                                 pre_auth_types, creds);
     if (retval)
         return retval;
 
@@ -194,11 +354,10 @@ krb5_get_in_tkt_with_keytab(krb5_context context, krb5_flags options,
         goto cleanup;
     server_princ = creds->server;
     client_princ = creds->client;
-    retval = krb5int_get_init_creds(context, creds, creds->client,
-                                    krb5_prompter_posix,  NULL,
-                                    0, server, opts,
-                                    get_as_key_keytab, (void *)keytab,
-                                    &use_master, ret_as_reply);
+    retval = k5_get_init_creds(context, creds, creds->client,
+                               krb5_prompter_posix,  NULL, 0, server, opts,
+                               get_as_key_keytab, (void *)keytab, &use_master,
+                               ret_as_reply);
     krb5_free_unparsed_name( context, server);
     if (retval) {
         goto cleanup;