s3:gse: MIT krb5 1.8.1 has a bug in gss_wrap_iov()
[samba.git] / source3 / librpc / crypto / gse.c
index ca99f9b03ac5bf0e284d252f761145040966f426..9eaef5aa44d4cc6db8070a6490ba769c80e1a837 100644 (file)
 
 #include "includes.h"
 #include "gse.h"
+#include "libads/kerberos_proto.h"
 
 #if defined(HAVE_KRB5) && defined(HAVE_GSS_WRAP_IOV)
 
 #include "smb_krb5.h"
 #include "gse_krb5.h"
 
-#include <gssapi/gssapi.h>
-#include <gssapi/gssapi_krb5.h>
-#ifdef HAVE_GSSAPI_GSSAPI_EXT_H
-#include <gssapi/gssapi_ext.h>
-#endif
-
 #ifndef GSS_KRB5_INQ_SSPI_SESSION_KEY_OID
 #define GSS_KRB5_INQ_SSPI_SESSION_KEY_OID_LENGTH 11
 #define GSS_KRB5_INQ_SSPI_SESSION_KEY_OID "\x2a\x86\x48\x86\xf7\x12\x01\x02\x02\x05\x05"
@@ -253,15 +248,22 @@ NTSTATUS gse_init_client(TALLOC_CTX *mem_ctx,
                return NT_STATUS_NO_MEMORY;
        }
 
-       name_buffer.value = talloc_asprintf(gse_ctx,
-                                           "%s@%s", service, server);
+       /* Guess the realm based on the supplied service, and avoid the GSS libs
+          doing DNS lookups which may fail.
+
+          TODO: Loop with the KDC on some more combinations (local
+          realm in particular), possibly falling back to
+          GSS_C_NT_HOSTBASED_SERVICE
+       */
+       name_buffer.value = kerberos_get_principal_from_service_hostname(gse_ctx,
+                                                                        service, server);
        if (!name_buffer.value) {
                status = NT_STATUS_NO_MEMORY;
                goto err_out;
        }
        name_buffer.length = strlen((char *)name_buffer.value);
        gss_maj = gss_import_name(&gss_min, &name_buffer,
-                                 GSS_C_NT_HOSTBASED_SERVICE,
+                                 GSS_C_NT_USER_NAME,
                                  &gse_ctx->server_name);
        if (gss_maj) {
                DEBUG(0, ("gss_import_name failed for %s, with [%s]\n",
@@ -324,7 +326,7 @@ NTSTATUS gse_get_client_auth_token(TALLOC_CTX *mem_ctx,
                                        gse_ctx->gss_c_flags,
                                        0, GSS_C_NO_CHANNEL_BINDINGS,
                                        &in_data, NULL, &out_data,
-                                       NULL, NULL);
+                                       &gse_ctx->ret_flags, NULL);
        switch (gss_maj) {
        case GSS_S_COMPLETE:
                /* we are done with it */
@@ -365,8 +367,6 @@ NTSTATUS gse_init_server(TALLOC_CTX *mem_ctx,
        OM_uint32 gss_maj, gss_min;
        krb5_error_code ret;
        NTSTATUS status;
-       const char *ktname;
-       gss_OID_set_desc mech_set;
 
        status = gse_context_init(mem_ctx, do_sign, do_seal,
                                  NULL, add_gss_c_flags, &gse_ctx);
@@ -382,38 +382,51 @@ NTSTATUS gse_init_server(TALLOC_CTX *mem_ctx,
        }
 
 #ifdef HAVE_GSS_KRB5_IMPORT_CRED
-       /* This creates a GSSAPI cred_id_t with the principal and keytab set */
+
+       /* This creates a GSSAPI cred_id_t with the keytab set */
        gss_maj = gss_krb5_import_cred(&gss_min, NULL, NULL, gse_ctx->keytab, 
-                                       &gse_ctx->creds);
-       if (gss_maj) {
+                                      &gse_ctx->creds);
+
+       if (gss_maj != 0
+           && gss_maj != (GSS_S_CALL_BAD_STRUCTURE|GSS_S_BAD_NAME)) {
                DEBUG(0, ("gss_krb5_import_cred failed with [%s]\n",
                          gse_errstr(gse_ctx, gss_maj, gss_min)));
                status = NT_STATUS_INTERNAL_ERROR;
                goto done;
-       }
-#else
+
+               /* This is the error the MIT krb5 1.9 gives when it
+                * implements the function, but we do not specify the
+                * principal.  However, when we specify the principal
+                * as host$@REALM the GSS acceptor fails with 'wrong
+                * principal in request'.  Work around the issue by
+                * falling back to the alternate approach below. */
+       } else if (gss_maj == (GSS_S_CALL_BAD_STRUCTURE|GSS_S_BAD_NAME))
+#endif
        /* FIXME!!!
         * This call sets the default keytab for the whole server, not
         * just for this context. Need to find a way that does not alter
         * the state of the whole server ... */
+       {
+               const char *ktname;
+               gss_OID_set_desc mech_set;
 
-       ret = smb_krb5_keytab_name(gse_ctx, gse_ctx->k5ctx,
+               ret = smb_krb5_keytab_name(gse_ctx, gse_ctx->k5ctx,
                                   gse_ctx->keytab, &ktname);
-       if (ret) {
-               status = NT_STATUS_INTERNAL_ERROR;
-               goto done;
-       }
+               if (ret) {
+                       status = NT_STATUS_INTERNAL_ERROR;
+                       goto done;
+               }
 
-       ret = gsskrb5_register_acceptor_identity(ktname);
-       if (ret) {
-               status = NT_STATUS_INTERNAL_ERROR;
-               goto done;
-       }
+               ret = gsskrb5_register_acceptor_identity(ktname);
+               if (ret) {
+                       status = NT_STATUS_INTERNAL_ERROR;
+                       goto done;
+               }
 
-       mech_set.count = 1;
-       mech_set.elements = &gse_ctx->gss_mech;
-       
-       gss_maj = gss_acquire_cred(&gss_min,
+               mech_set.count = 1;
+               mech_set.elements = &gse_ctx->gss_mech;
+
+               gss_maj = gss_acquire_cred(&gss_min,
                                   GSS_C_NO_NAME,
                                   GSS_C_INDEFINITE,
                                   &mech_set,
@@ -421,13 +434,14 @@ NTSTATUS gse_init_server(TALLOC_CTX *mem_ctx,
                                   &gse_ctx->creds,
                                   NULL, NULL);
 
-       if (gss_maj) {
-               DEBUG(0, ("gss_acquire_creds failed with [%s]\n",
-                         gse_errstr(gse_ctx, gss_maj, gss_min)));
-               status = NT_STATUS_INTERNAL_ERROR;
-               goto done;
+               if (gss_maj) {
+                       DEBUG(0, ("gss_acquire_creds failed with [%s]\n",
+                                 gse_errstr(gse_ctx, gss_maj, gss_min)));
+                       status = NT_STATUS_INTERNAL_ERROR;
+                       goto done;
+               }
        }
-#endif
+
        status = NT_STATUS_OK;
 
 done:
@@ -562,6 +576,12 @@ static char *gse_errstr(TALLOC_CTX *mem_ctx, OM_uint32 maj, OM_uint32 min)
        if (gss_maj) {
                goto done;
        }
+       errstr = talloc_strndup(mem_ctx,
+                               (char *)msg_maj.value,
+                                       msg_maj.length);
+       if (!errstr) {
+               goto done;
+       }
        gss_maj = gss_display_status(&gss_min, min, GSS_C_MECH_CODE,
                                     (gss_OID)discard_const(gss_mech_krb5),
                                     &msg_ctx, &msg_min);
@@ -569,12 +589,6 @@ static char *gse_errstr(TALLOC_CTX *mem_ctx, OM_uint32 maj, OM_uint32 min)
                goto done;
        }
 
-       errstr = talloc_strndup(mem_ctx,
-                               (char *)msg_maj.value,
-                                       msg_maj.length);
-       if (!errstr) {
-               goto done;
-       }
        errstr = talloc_strdup_append_buffer(errstr, ": ");
        if (!errstr) {
                goto done;
@@ -622,11 +636,26 @@ DATA_BLOB gse_get_session_key(TALLOC_CTX *mem_ctx,
            (memcmp(set->elements[1].value,
                    gse_sesskeytype_oid.elements,
                    gse_sesskeytype_oid.length) != 0)) {
+#ifdef HAVE_GSSKRB5_GET_SUBKEY
+               krb5_keyblock *subkey;
+               gss_maj = gsskrb5_get_subkey(&gss_min,
+                                            gse_ctx->gss_ctx,
+                                            &subkey);
+               if (gss_maj != 0) {
+                       DEBUG(1, ("NO session key for this mech\n"));
+                       return data_blob_null;
+               }
+               ret = data_blob_talloc(mem_ctx,
+                                      KRB5_KEY_DATA(subkey), KRB5_KEY_LENGTH(subkey));
+               krb5_free_keyblock(NULL /* should be krb5_context */, subkey);
+               return ret;
+#else
                DEBUG(0, ("gss_inquire_sec_context_by_oid returned unknown "
                          "OID for data in results:\n"));
                dump_data(1, (uint8_t *)set->elements[1].value,
                             set->elements[1].length);
                return data_blob_null;
+#endif
        }
 
        ret = data_blob_talloc(mem_ctx, set->elements[0].value,
@@ -753,7 +782,7 @@ NTSTATUS gse_seal(TALLOC_CTX *mem_ctx, struct gse_context *gse_ctx,
        OM_uint32 gss_min, gss_maj;
        gss_iov_buffer_desc iov[2];
        int req_seal = 1; /* setting to 1 means we request sign+seal */
-       int sealed;
+       int sealed = 1;
        NTSTATUS status;
 
        /* allocate the memory ourselves so we do not need to talloc_memdup */