Improve acceptor name flexibility
authorGreg Hudson <ghudson@mit.edu>
Mon, 7 Feb 2011 18:40:00 +0000 (18:40 +0000)
committerGreg Hudson <ghudson@mit.edu>
Mon, 7 Feb 2011 18:40:00 +0000 (18:40 +0000)
Be more flexible about the principal names we will accept for a given
GSS acceptor name.  Also add support for a new libdefaults profile
variable ignore_acceptor_hostname, which causes the hostnames of
host-based service principals to be ignored when passed by server
applications as acceptor names.

Note that we still always invoke krb5_sname_to_principal() when
importing a gss-krb5 mechanism name, even though we won't always use
the result.  This is an unfortunate waste of getaddrinfo/getnameinfo
queries in some situations, but the code surgery necessary to defer
it appears too risky at this time.

The project proposal for this change is at:

http://k5wiki.kerberos.org/wiki/Projects/Acceptor_Names

ticket: 6855

git-svn-id: svn://anonsvn.mit.edu/krb5/trunk@24616 dc483132-0cff-0310-8789-dd5450dbe970

20 files changed:
doc/admin.texinfo
src/config-files/krb5.conf.M
src/include/k5-int.h
src/include/krb5/krb5.hin
src/lib/gssapi/krb5/accept_sec_context.c
src/lib/gssapi/krb5/acquire_cred.c
src/lib/gssapi/krb5/gssapiP_krb5.h
src/lib/gssapi/krb5/import_name.c
src/lib/gssapi/krb5/naming_exts.c
src/lib/gssapi/krb5/s4u_gss_glue.c
src/lib/gssapi/krb5/ser_sctx.c
src/lib/krb5/krb/Makefile.in
src/lib/krb5/krb/init_ctx.c
src/lib/krb5/krb/rd_req_dec.c
src/lib/krb5/krb/sname_match.c [new file with mode: 0644]
src/lib/krb5/libkrb5.exports
src/lib/krb5_32.def
src/tests/gssapi/Makefile.in
src/tests/gssapi/t_accname.c [new file with mode: 0644]
src/tests/gssapi/t_gssapi.py

index 5051b5d3f794dd69331ddc070a6c5fc2d3db72d3..427f64eca300278e8ab2df30c73f95d0e9488a07 100644 (file)
@@ -470,6 +470,15 @@ Sets the maximum allowable amount of clockskew in seconds that the
 library  will tolerate before assuming that a Kerberos message is
 invalid.  The default value is @value{DefaultClockskew}.
 
+@itemx ignore_acceptor_hostname
+When accepting GSSAPI or krb5 security contexts for host-based service
+principals, ignore any hostname passed by the calling application and
+allow any service principal present in the keytab which matches the
+service name and realm name (if given).  This option can improve
+the administrative flexibility of server applications multi-homed hosts,
+but can compromise the security of virtual hosting environments.  The
+default value is false.
+
 @itemx k5login_authoritative
 If the value of this relation is true (the default), principals must
 be listed in a local user's k5login file to be granted login access,
index df62d4d524b454838575a34db997cb6ac0512013..b04d6efd69f7b875a0fc54691f96d7ab0dbc1e31 100644 (file)
@@ -155,6 +155,15 @@ This relation sets the maximum allowable amount of clockskew in seconds
 that the library will tolerate before assuming that a Kerberos message
 is invalid.  The default value is 300 seconds, or five minutes.
 
+.IP ignore_acceptor_hostname
+When accepting GSSAPI or krb5 security contexts for host-based service
+principals, ignore any hostname passed by the calling application and
+allow any service principal present in the keytab which matches the
+service name and realm name (if given).  This option can improve the
+administrative flexibility of server applications on multi-homed
+hosts, but can compromise the security of virtual hosting
+environments.  The default value is false.
+
 .IP k5login_authoritative
 If the value of this relation is true (the default), principals must
 be listed in a local user's k5login file to be granted login access,
index d188f8e9d842d0ad176814fa5ace4139e8d46cd1..92ff1f0435923fc6b8a3d5bc424115c3a7a18e8d 100644 (file)
@@ -217,6 +217,7 @@ typedef INT64_TYPE krb5_int64;
 #define KRB5_CONF_EXTRA_ADDRESSES             "extra_addresses"
 #define KRB5_CONF_FORWARDABLE                 "forwardable"
 #define KRB5_CONF_HOST_BASED_SERVICES         "host_based_services"
+#define KRB5_CONF_IGNORE_ACCEPTOR_HOSTNAME    "ignore_acceptor_hostname"
 #define KRB5_CONF_IPROP_ENABLE                "iprop_enable"
 #define KRB5_CONF_IPROP_MASTER_ULOGSIZE       "iprop_master_ulogsize"
 #define KRB5_CONF_IPROP_PORT                  "iprop_port"
@@ -1600,6 +1601,7 @@ struct _krb5_context {
     struct _kdb_log_context *kdblog_context;
 
     krb5_boolean allow_weak_crypto;
+    krb5_boolean ignore_acceptor_hostname;
 
     krb5_trace_callback trace_callback;
     void *trace_callback_data;
index adf67008231070bce90d82c1050484174d49171c..14c4a9e41edf01a7436be0cf1d8b41bd4c953598 100644 (file)
@@ -4284,6 +4284,22 @@ krb5_error_code KRB5_CALLCONV
 krb5_sname_to_principal(krb5_context context, const char *hostname, const char *sname,
                         krb5_int32 type, krb5_principal *ret_princ);
 
+/** Return true if @a princ matches @a matching, false otherwise.
+ *
+ * A matching principal is a host-based principal with an empty realm and/or
+ * second data component (hostname).  Profile configuration may cause the
+ * hostname to be ignored even if it is present.  A principal matches a
+ * matching principal if the principal has the same non-empty (and non-ignored)
+ * components of the matching principal.
+ *
+ * If @a matching is NULL, return true.  If @a matching is not a matching
+ * principal, return the value of krb5_principal_compare(context, matching,
+ * princ).
+ */
+krb5_boolean KRB5_CALLCONV
+krb5_sname_match(krb5_context context, krb5_const_principal matching,
+                 krb5_const_principal princ);
+
 /**
  * @brief Change the password for an existing Kerberos account.
  *
index c3cb2f19e76fb2a6e45d8ce8164802116b61026f..0344ebd10f46564ead63dd702d7e3a6e0c7ae589 100644 (file)
@@ -240,7 +240,8 @@ rd_and_store_for_creds(context, auth_context, inbuf, out_cred)
 
         /* copy the client principle into it... */
         if ((retval =
-             kg_init_name(context, creds[0]->client, NULL, 0, &cred->name))) {
+             kg_init_name(context, creds[0]->client, NULL, NULL, NULL, 0,
+                          &cred->name))) {
             k5_mutex_destroy(&cred->lock);
             retval = ENOMEM; /* out of memory? */
             xfree(cred); /* clean up memory on failure */
@@ -472,6 +473,7 @@ kg_accept_krb5(minor_status, context_handle,
     krb5_flags ap_req_options = 0;
     krb5_enctype negotiated_etype;
     krb5_authdata_context ad_context = NULL;
+    krb5_principal accprinc = NULL;
 
     code = krb5int_accessor (&kaccess, KRB5INT_ACCESS_VERSION);
     if (code) {
@@ -632,11 +634,15 @@ kg_accept_krb5(minor_status, context_handle,
         }
     }
 
-    if ((code = krb5_rd_req(context, &auth_context, &ap_req,
-                            cred->default_identity ? NULL : cred->name->princ,
-                            cred->keytab,
-                            &ap_req_options,
-                            &ticket))) {
+    if (!cred->default_identity) {
+        if ((code = kg_acceptor_princ(context, cred->name, &accprinc))) {
+            major_status = GSS_S_FAILURE;
+            goto fail;
+        }
+    }
+
+    if ((code = krb5_rd_req(context, &auth_context, &ap_req, accprinc,
+                            cred->keytab, &ap_req_options, &ticket))) {
         major_status = GSS_S_FAILURE;
         goto fail;
     }
@@ -918,7 +924,8 @@ kg_accept_krb5(minor_status, context_handle,
         major_status = GSS_S_FAILURE;
         goto fail;
     }
-    if ((code = kg_init_name(context, ticket->server, NULL, 0, &ctx->here))) {
+    if ((code = kg_init_name(context, ticket->server, NULL, NULL, NULL, 0,
+                             &ctx->here))) {
         major_status = GSS_S_FAILURE;
         goto fail;
     }
@@ -927,7 +934,7 @@ kg_accept_krb5(minor_status, context_handle,
         major_status = GSS_S_FAILURE;
         goto fail;
     }
-    if ((code = kg_init_name(context, authdat->client,
+    if ((code = kg_init_name(context, authdat->client, NULL, NULL,
                              ad_context, KG_INIT_NAME_NO_COPY, &ctx->there))) {
         major_status = GSS_S_FAILURE;
         goto fail;
@@ -1269,7 +1276,6 @@ fail:
         krb_error_data.error = code;
         (void) krb5_us_timeofday(context, &krb_error_data.stime,
                                  &krb_error_data.susec);
-        krb_error_data.server = cred->name ? cred->name->princ : NULL;
 
         code = krb5_mk_error(context, &krb_error_data, &scratch);
         if (code)
index 4fe9ebceab0325f61c3f795b1adb08ed79a4d527..ae34f95cce383b04fac6d1b68c53a1349ca12d30 100644 (file)
@@ -128,6 +128,59 @@ gss_krb5int_register_acceptor_identity(OM_uint32 *minor_status,
     return GSS_S_COMPLETE;
 }
 
+/* Try to verify that keytab contains at least one entry for name.  Return 0 if
+ * it does, KRB5_KT_NOTFOUND if it doesn't, or another error as appropriate. */
+static krb5_error_code
+check_keytab(krb5_context context, krb5_keytab kt, krb5_gss_name_t name)
+{
+    krb5_error_code code;
+    krb5_keytab_entry ent;
+    krb5_kt_cursor cursor;
+    krb5_principal accprinc = NULL;
+    krb5_boolean match;
+    char *princname;
+
+    if (name->service == NULL) {
+        code = krb5_kt_get_entry(context, kt, name->princ, 0, 0, &ent);
+        if (code == 0)
+            krb5_kt_free_entry(context, &ent);
+        return code;
+    }
+
+    /* If we can't iterate through the keytab, skip this check. */
+    if (kt->ops->start_seq_get == NULL)
+        return 0;
+
+    /* Get the partial principal for the acceptor name. */
+    code = kg_acceptor_princ(context, name, &accprinc);
+    if (code)
+        return code;
+
+    /* Scan the keytab for host-based entries matching accprinc. */
+    code = krb5_kt_start_seq_get(context, kt, &cursor);
+    if (code)
+        goto cleanup;
+    while ((code = krb5_kt_next_entry(context, kt, &ent, &cursor)) == 0) {
+        match = krb5_sname_match(context, accprinc, ent.principal);
+        (void)krb5_free_keytab_entry_contents(context, &ent);
+        if (match)
+            break;
+    }
+    (void)krb5_kt_end_seq_get(context, kt, &cursor);
+    if (code == KRB5_KT_END) {
+        code = KRB5_KT_NOTFOUND;
+        if (krb5_unparse_name(context, accprinc, &princname) == 0) {
+            krb5_set_error_message(context, code, "No key table entry "
+                                   "found matching %s", princname);
+            free(princname);
+        }
+    }
+
+cleanup:
+    krb5_free_principal(context, accprinc);
+    return code;
+}
+
 /* get credentials corresponding to a key in the krb5 keytab.
    If successful, set the keytab-specific fields in cred
 */
@@ -135,13 +188,12 @@ gss_krb5int_register_acceptor_identity(OM_uint32 *minor_status,
 static OM_uint32
 acquire_accept_cred(krb5_context context,
                     OM_uint32 *minor_status,
-                    krb5_principal desired_princ,
+                    krb5_gss_name_t desired_name,
                     krb5_keytab req_keytab,
                     krb5_gss_cred_id_rec *cred)
 {
     krb5_error_code code;
     krb5_keytab kt;
-    krb5_keytab_entry entry;
 
     assert(cred->keytab == NULL);
 
@@ -174,8 +226,8 @@ acquire_accept_cred(krb5_context context,
         return GSS_S_CRED_UNAVAIL;
     }
 
-    if (desired_princ != NULL) {
-        code = krb5_kt_get_entry(context, kt, desired_princ, 0, 0, &entry);
+    if (desired_name != NULL) {
+        code = check_keytab(context, kt, desired_name);
         if (code) {
             krb5_kt_close(context, kt);
             if (code == KRB5_KT_NOTFOUND) {
@@ -187,18 +239,16 @@ acquire_accept_cred(krb5_context context,
                 *minor_status = code;
             return GSS_S_CRED_UNAVAIL;
         }
-        krb5_kt_free_entry(context, &entry);
 
         assert(cred->name == NULL);
-        code = kg_init_name(context, desired_princ, NULL, 0, &cred->name);
+        code = kg_duplicate_name(context, desired_name, 0, &cred->name);
         if (code) {
             *minor_status = code;
             return GSS_S_FAILURE;
         }
 
         /* Open the replay cache for this principal. */
-        code = krb5_get_server_rcache(context,
-                                      krb5_princ_component(context, desired_princ, 0),
+        code = krb5_get_server_rcache(context, &desired_name->princ->data[0],
                                       &cred->rcache);
         if (code) {
             *minor_status = code;
@@ -376,7 +426,7 @@ acquire_init_cred(krb5_context context,
      * cred->name to the credentials cache principal name.
      */
     if (cred->name == NULL) {
-        if ((code = kg_init_name(context, ccache_princ, NULL,
+        if ((code = kg_init_name(context, ccache_princ, NULL, NULL, NULL,
                                  KG_INIT_NAME_NO_COPY, &cred->name))) {
             krb5_free_principal(context, ccache_princ);
             krb5_cc_close(context, ccache);
@@ -511,9 +561,9 @@ acquire_cred(OM_uint32 *minor_status,
 {
     krb5_context context = NULL;
     krb5_gss_cred_id_t cred = NULL;
+    krb5_gss_name_t name = (krb5_gss_name_t)args->desired_name;
     OM_uint32 ret;
     krb5_error_code code = 0;
-    krb5_principal desired_princ = NULL;
 
     /* make sure all outputs are valid */
     *output_cred_handle = GSS_C_NO_CREDENTIAL;
@@ -536,7 +586,7 @@ acquire_cred(OM_uint32 *minor_status,
     cred->usage = args->cred_usage;
     cred->name = NULL;
     cred->iakerb_mech = args->iakerb;
-    cred->default_identity = (args->desired_name == GSS_C_NO_NAME);
+    cred->default_identity = (name == NULL);
 #ifndef LEAN_CLIENT
     cred->keytab = NULL;
 #endif /* LEAN_CLIENT */
@@ -558,18 +608,14 @@ acquire_cred(OM_uint32 *minor_status,
         goto error_out;
     }
 
-    if (args->desired_name != GSS_C_NO_NAME)
-        desired_princ = ((krb5_gss_name_t)args->desired_name)->princ;
-
 #ifndef LEAN_CLIENT
     /*
      * If requested, acquire credentials for accepting. This will fill
      * in cred->name if desired_princ is specified.
      */
     if (args->cred_usage == GSS_C_ACCEPT || args->cred_usage == GSS_C_BOTH) {
-        ret = acquire_accept_cred(context, minor_status,
-                                  desired_princ,
-                                  args->keytab, cred);
+        ret = acquire_accept_cred(context, minor_status, name, args->keytab,
+                                  cred);
         if (ret != GSS_S_COMPLETE)
             goto error_out;
     }
@@ -581,7 +627,8 @@ acquire_cred(OM_uint32 *minor_status,
      */
     if (args->cred_usage == GSS_C_INITIATE || args->cred_usage == GSS_C_BOTH) {
         ret = acquire_init_cred(context, minor_status, args->ccache,
-                                desired_princ, args->password, cred);
+                                name ? name->princ : NULL, args->password,
+                                cred);
         if (ret != GSS_S_COMPLETE)
             goto error_out;
     }
index a0e60be5e631fcf9b0b556be0fbde0cddea9c109..6649331e726c02501153786bbbb79763b728cfb8 100644 (file)
@@ -158,8 +158,10 @@ enum qop {
 /** internal types **/
 
 typedef struct _krb5_gss_name_rec {
-    krb5_principal princ; /* immutable */
-    k5_mutex_t lock; /* protects ad_context only for now */
+    krb5_principal princ;       /* immutable */
+    char *service;              /* immutable */
+    char *host;                 /* immutable */
+    k5_mutex_t lock;            /* protects ad_context only for now */
     krb5_authdata_context ad_context;
 } krb5_gss_name_rec, *krb5_gss_name_t;
 
@@ -893,11 +895,9 @@ int gss_krb5int_rotate_left (void *ptr, size_t bufsiz, size_t rc);
 #define KG_INIT_NAME_NO_COPY 0x2
 
 krb5_error_code
-kg_init_name(krb5_context context,
-             krb5_principal principal,
-             krb5_authdata_context ad_context,
-             krb5_flags flags,
-             krb5_gss_name_t *name);
+kg_init_name(krb5_context context, krb5_principal principal,
+             char *service, char *host, krb5_authdata_context ad_context,
+             krb5_flags flags, krb5_gss_name_t *name);
 
 krb5_error_code
 kg_release_name(krb5_context context,
@@ -915,6 +915,10 @@ kg_compare_name(krb5_context context,
                 krb5_gss_name_t name1,
                 krb5_gss_name_t name2);
 
+krb5_boolean
+kg_acceptor_princ(krb5_context context, krb5_gss_name_t name,
+                  krb5_principal *princ_out);
+
 OM_uint32
 krb5_gss_display_name_ext(OM_uint32 *minor_status,
                           gss_name_t name,
index 0f36721c1496c3d5b93b93d507745a3467d6e5ae..2ba178a04031d3d7883243a9d7807fff821576e9 100644 (file)
@@ -78,6 +78,44 @@ import_name_composite(krb5_context context,
     return 0;
 }
 
+/* Split a host-based name "service[@host]" into allocated strings
+ * placed in *service_out and *host_out (possibly NULL). */
+static krb5_error_code
+parse_hostbased(const char *str, size_t len,
+                char **service_out, char **host_out)
+{
+    const char *at;
+    size_t servicelen, hostlen;
+    char *service, *host = NULL;
+
+    *service_out = *host_out = NULL;
+
+    /* Find the bound of the service name and copy it. */
+    at = memchr(str, '@', len);
+    servicelen = (at == NULL) ? len : (size_t)(at - str);
+    service = xmalloc(servicelen + 1);
+    if (service == NULL)
+        return ENOMEM;
+    memcpy(service, str, servicelen);
+    service[servicelen] = '\0';
+
+    /* If present, copy the hostname. */
+    if (at != NULL) {
+        hostlen = len - servicelen - 1;
+        host = malloc(hostlen + 1);
+        if (host == NULL) {
+            free(service);
+            return ENOMEM;
+        }
+        memcpy(host, at + 1, hostlen);
+        host[hostlen] = '\0';
+    }
+
+    *service_out = service;
+    *host_out = host;
+    return 0;
+}
+
 OM_uint32
 krb5_gss_import_name(minor_status, input_name_buffer,
                      input_name_type, output_name)
@@ -90,7 +128,7 @@ krb5_gss_import_name(minor_status, input_name_buffer,
     krb5_principal princ = NULL;
     krb5_error_code code;
     unsigned char *cp, *end;
-    char *tmp = NULL, *tmp2 = NULL, *stringrep;
+    char *tmp = NULL, *tmp2 = NULL, *service = NULL, *host = NULL, *stringrep;
     ssize_t    length;
 #ifndef NO_PASSWORD
     struct passwd *pw;
@@ -110,21 +148,17 @@ krb5_gss_import_name(minor_status, input_name_buffer,
     if ((input_name_type != GSS_C_NULL_OID) &&
         (g_OID_equal(input_name_type, gss_nt_service_name) ||
          g_OID_equal(input_name_type, gss_nt_service_name_v2))) {
-        char *service, *host;
-
-        tmp = k5alloc(input_name_buffer->length + 1, &code);
-        if (tmp == NULL)
+        /* Split the name into service and host (or NULL). */
+        code = parse_hostbased(input_name_buffer->value,
+                               input_name_buffer->length, &service, &host);
+        if (code)
             goto cleanup;
 
-        memcpy(tmp, input_name_buffer->value, input_name_buffer->length);
-        tmp[input_name_buffer->length] = '\0';
-
-        service = tmp;
-        if ((host = strchr(tmp, '@'))) {
-            *host = '\0';
-            host++;
-        }
-
+        /*
+         * Compute the initiator target name.  In some cases this is a waste of
+         * getaddrinfo/getnameinfo queries, but computing the name when we need
+         * it would require a lot of code changes.
+         */
         code = krb5_sname_to_principal(context, host, service, KRB5_NT_SRV_HST,
                                        &princ);
         if (code)
@@ -271,12 +305,13 @@ krb5_gss_import_name(minor_status, input_name_buffer,
     }
 
     /* Create a name and save it in the validation database. */
-    code = kg_init_name(context, princ, ad_context,
+    code = kg_init_name(context, princ, service, host, ad_context,
                         KG_INIT_NAME_INTERN | KG_INIT_NAME_NO_COPY, &name);
     if (code)
         goto cleanup;
     princ = NULL;
     ad_context = NULL;
+    service = host = NULL;
     *output_name = (gss_name_t)name;
     status = GSS_S_COMPLETE;
 
@@ -289,5 +324,7 @@ cleanup:
     krb5_free_context(context);
     free(tmp);
     free(tmp2);
+    free(service);
+    free(host);
     return status;
 }
index 8cb21bf602fa21e28f3f1102beb5c35549dba934..d1940123fee2af088391dcbf06b7f244cb986460 100644 (file)
 #include <stdarg.h>
 
 krb5_error_code
-kg_init_name(krb5_context context,
-             krb5_principal principal,
-             krb5_authdata_context ad_context,
-             krb5_flags flags,
-             krb5_gss_name_t *ret_name)
+kg_init_name(krb5_context context, krb5_principal principal,
+             char *service, char *host, krb5_authdata_context ad_context,
+             krb5_flags flags, krb5_gss_name_t *ret_name)
 {
     krb5_error_code code;
     krb5_gss_name_t name;
@@ -71,8 +69,23 @@ kg_init_name(krb5_context context,
             if (code != 0)
                 goto cleanup;
         }
+
+        code = ENOMEM;
+        if (service != NULL) {
+            name->service = strdup(service);
+            if (name->service == NULL)
+                goto cleanup;
+        }
+        if (host != NULL) {
+            name->host = strdup(host);
+            if (name->host == NULL)
+                goto cleanup;
+        }
+        code = 0;
     } else {
         name->princ = principal;
+        name->service = service;
+        name->host = host;
         name->ad_context = ad_context;
     }
 
@@ -100,6 +113,8 @@ kg_release_name(krb5_context context,
         if (flags & KG_INIT_NAME_INTERN)
             kg_delete_name((gss_name_t)*name);
         krb5_free_principal(context, (*name)->princ);
+        free((*name)->service);
+        free((*name)->host);
         krb5_authdata_context_free(context, (*name)->ad_context);
         k5_mutex_destroy(&(*name)->lock);
         free(*name);
@@ -121,7 +136,7 @@ kg_duplicate_name(krb5_context context,
     if (code != 0)
         return code;
 
-    code = kg_init_name(context, src->princ,
+    code = kg_init_name(context, src->princ, src->service, src->host,
                         src->ad_context, flags, dst);
 
     k5_mutex_unlock(&src->lock);
@@ -138,6 +153,45 @@ kg_compare_name(krb5_context context,
     return krb5_principal_compare(context, name1->princ, name2->princ);
 }
 
+/* Determine the principal to use for an acceptor name, which is different from
+ * name->princ for host-based names. */
+krb5_boolean
+kg_acceptor_princ(krb5_context context, krb5_gss_name_t name,
+                  krb5_principal *princ_out)
+{
+    krb5_error_code code;
+    const char *host;
+    char *tmp = NULL;
+
+    *princ_out = NULL;
+    if (name == NULL)
+        return 0;
+
+    /* If it's not a host-based name, just copy name->princ. */
+    if (name->service == NULL)
+        return krb5_copy_principal(context, name->princ, princ_out);
+
+    if (name->host != NULL && name->princ->length == 2) {
+        /* If a host was given, we have to use the canonicalized form of it (as
+         * given by krb5_sname_to_principal) for backward compatibility. */
+        const krb5_data *d = &name->princ->data[1];
+        tmp = k5alloc(d->length + 1, &code);
+        if (tmp == NULL)
+            return ENOMEM;
+        memcpy(tmp, d->data, d->length);
+        tmp[d->length] = '\0';
+        host = tmp;
+    } else                      /* No host was given; use an empty string. */
+        host = "";
+
+    code = krb5_build_principal(context, princ_out, 0, "", name->service, host,
+                                (char *)NULL);
+    if (code == 0)
+        (*princ_out)->type = KRB5_NT_SRV_HST;
+    free(tmp);
+    return code;
+}
+
 static OM_uint32
 kg_map_name_error(OM_uint32 *minor_status, krb5_error_code code)
 {
index ac07dad5d7a43f88b29635bcd7a96dd16fab0c06..4cebf7fa66b5126a56e3dcda28319f41b10642a9 100644 (file)
@@ -221,7 +221,8 @@ kg_compose_deleg_cred(OM_uint32 *minor_status,
 
     cred->tgt_expire = impersonator_cred->tgt_expire;
 
-    code = kg_init_name(context, subject_creds->client, NULL, 0, &cred->name);
+    code = kg_init_name(context, subject_creds->client, NULL, NULL, NULL, 0,
+                        &cred->name);
     if (code != 0)
         goto cleanup;
 
index ae6350cced854abb98b7566c79f19b10910e7191..dc679c9d8f32550a4531cb120ed6cb3463c058ae 100644 (file)
@@ -669,7 +669,7 @@ kg_ctx_internalize(kcontext, argp, buffer, lenremain)
                                            (krb5_pointer *) &princ,
                                            &bp, &remain);
             if (kret == 0) {
-                kret = kg_init_name(kcontext, princ, NULL,
+                kret = kg_init_name(kcontext, princ, NULL, NULL, NULL,
                                     KG_INIT_NAME_NO_COPY, &ctx->here);
                 if (kret)
                     krb5_free_principal(kcontext, princ);
@@ -681,7 +681,7 @@ kg_ctx_internalize(kcontext, argp, buffer, lenremain)
                                                (krb5_pointer *) &princ,
                                                &bp, &remain);
                 if (kret == 0) {
-                    kret = kg_init_name(kcontext, princ, NULL,
+                    kret = kg_init_name(kcontext, princ, NULL, NULL, NULL,
                                         KG_INIT_NAME_NO_COPY, &ctx->there);
                     if (kret)
                         krb5_free_principal(kcontext, princ);
index b96229986baec4734889e3a0db343542d23f8469..b1af2e747c528c7b598ce094aa9b647867f1d043 100644 (file)
@@ -101,6 +101,7 @@ STLIBOBJS= \
        ser_princ.o     \
        serialize.o     \
        set_realm.o     \
+       sname_match.o   \
        srv_dec_tkt.o   \
        srv_rcache.o    \
        str_conv.o      \
@@ -202,6 +203,7 @@ OBJS=       $(OUTPRE)addr_comp.$(OBJEXT)    \
        $(OUTPRE)ser_princ.$(OBJEXT)    \
        $(OUTPRE)serialize.$(OBJEXT)    \
        $(OUTPRE)set_realm.$(OBJEXT)    \
+       $(OUTPRE)sname_match.$(OBJEXT)  \
        $(OUTPRE)srv_dec_tkt.$(OBJEXT)  \
        $(OUTPRE)srv_rcache.$(OBJEXT)   \
        $(OUTPRE)str_conv.$(OBJEXT)     \
@@ -303,6 +305,7 @@ SRCS=       $(srcdir)/addr_comp.c   \
        $(srcdir)/ser_princ.c   \
        $(srcdir)/serialize.c   \
        $(srcdir)/set_realm.c   \
+       $(srcdir)/sname_match.c \
        $(srcdir)/srv_dec_tkt.c \
        $(srcdir)/srv_rcache.c  \
        $(srcdir)/str_conv.c    \
index 4f007503116f566b310425d2e33dd440ea234409..4eae4d743c2d1395f1018b76afca06c0a293b18d 100644 (file)
@@ -180,6 +180,13 @@ init_common (krb5_context *context, krb5_boolean secure, krb5_boolean kdc)
         goto cleanup;
     ctx->allow_weak_crypto = tmp;
 
+    retval = profile_get_boolean(ctx->profile, KRB5_CONF_LIBDEFAULTS,
+                                 KRB5_CONF_IGNORE_ACCEPTOR_HOSTNAME, NULL, 0,
+                                 &tmp);
+    if (retval)
+        goto cleanup;
+    ctx->ignore_acceptor_hostname = tmp;
+
     /* initialize the prng (not well, but passable) */
     if ((retval = krb5_c_random_os_entropy( ctx, 0, NULL)) !=0)
         goto cleanup;
index 9bc7c42f902f7df510a4264430bbba1dbd9c1ba1..e065e2157f853a4fe47569c3aea09653e058935e 100644 (file)
@@ -86,102 +86,138 @@ negotiate_etype(krb5_context context,
                 int permitted_etypes_len,
                 krb5_enctype *negotiated_etype);
 
+/* Return true if princ might match multiple principals. */
+static inline krb5_boolean
+is_matching(krb5_context context, krb5_const_principal princ)
+{
+    if (princ == NULL)
+        return TRUE;
+    return (princ->type == KRB5_NT_SRV_HST && princ->length == 2
+            && (princ->realm.length == 0 || princ->data[1].length == 0 ||
+                context->ignore_acceptor_hostname));
+}
+
+/* Decrypt the ticket in req using the key in ent. */
 static krb5_error_code
-rd_req_decrypt_tkt_part(krb5_context context, const krb5_ap_req *req,
-                        krb5_const_principal server, krb5_keytab keytab,
-                        krb5_keyblock *key)
+try_one_entry(krb5_context context, const krb5_ap_req *req,
+              krb5_keytab_entry *ent, krb5_keyblock *keyblock_out)
 {
-    krb5_error_code       retval;
-    krb5_keytab_entry     ktent;
+    krb5_error_code ret;
+    krb5_principal tmp = NULL;
+
+    /* Try decrypting the ticket with this entry's key. */
+    ret = krb5_decrypt_tkt_part(context, &ent->key, req->ticket);
+    if (ret)
+        return ret;
+
+    /* Make a copy of the principal for the ticket server field. */
+    ret = krb5_copy_principal(context, ent->principal, &tmp);
+    if (ret)
+        return ret;
+
+    /* Make a copy of the decrypting key if requested by the caller. */
+    if (keyblock_out != NULL) {
+        ret = krb5_copy_keyblock_contents(context, &ent->key, keyblock_out);
+        if (ret) {
+            krb5_free_principal(context, tmp);
+            return ret;
+        }
+    }
 
-    retval = KRB5_KT_NOTFOUND;
+    /* Make req->ticket->server indicate the actual server principal. */
+    krb5_free_principal(context, req->ticket->server);
+    req->ticket->server = tmp;
 
-#ifndef LEAN_CLIENT
-    if (server != NULL || keytab->ops->start_seq_get == NULL) {
-        retval = krb5_kt_get_entry(context, keytab,
-                                   server != NULL ? server : req->ticket->server,
-                                   req->ticket->enc_part.kvno,
-                                   req->ticket->enc_part.enctype, &ktent);
-        if (retval == 0) {
-            retval = krb5_decrypt_tkt_part(context, &ktent.key, req->ticket);
-            if (retval == 0) {
-                TRACE_RD_REQ_DECRYPT_SPECIFIC(context, ktent.principal,
-                                              &ktent.key);
-            }
-            if (retval == 0 && key != NULL)
-                retval = krb5_copy_keyblock_contents(context, &ktent.key, key);
+    return 0;
+}
 
-            (void) krb5_free_keytab_entry_contents(context, &ktent);
-        }
-    } else {
-        krb5_error_code code;
-        krb5_kt_cursor cursor;
+/* Decrypt the ticket in req using a principal looked up from keytab. */
+static krb5_error_code
+try_one_princ(krb5_context context, const krb5_ap_req *req,
+              krb5_const_principal princ, krb5_keytab keytab,
+              krb5_keyblock *keyblock_out)
+{
+    krb5_error_code ret;
+    krb5_keytab_entry ent;
+
+    ret = krb5_kt_get_entry(context, keytab, princ,
+                            req->ticket->enc_part.kvno,
+                            req->ticket->enc_part.enctype, &ent);
+    if (ret)
+        return ret;
+    ret = try_one_entry(context, req, &ent, keyblock_out);
+    (void)krb5_free_keytab_entry_contents(context, &ent);
+    if (ret)
+        return ret;
+
+    TRACE_RD_REQ_DECRYPT_SPECIFIC(context, ent.principal, &ent.key);
+    return 0;
+}
 
-        code = krb5_kt_start_seq_get(context, keytab, &cursor);
-        if (code != 0) {
-            retval = code;
-            goto map_error;
-        }
+/*
+ * Decrypt the ticket in req using an entry in keytab matching server (if
+ * given).  Set req->ticket->server to the principal of the keytab entry used.
+ * Store the decrypting key in *keyblock_out if it is not NULL.
+ */
+static krb5_error_code
+decrypt_ticket(krb5_context context, const krb5_ap_req *req,
+               krb5_const_principal server, krb5_keytab keytab,
+               krb5_keyblock *keyblock_out)
+{
+    krb5_error_code ret;
+    krb5_keytab_entry ent;
+    krb5_kt_cursor cursor;
 
-        while ((code = krb5_kt_next_entry(context, keytab,
-                                          &ktent, &cursor)) == 0) {
-            if (ktent.key.enctype != req->ticket->enc_part.enctype) {
-                (void) krb5_free_keytab_entry_contents(context, &ktent);
-                continue;
-            }
+#ifdef LEAN_CLIENT
+    return KRB5KRB_AP_WRONG_PRINC;
+#else
+    /* If we have an explicit server principal, try just that one. */
+    if (!is_matching(context, server))
+        return try_one_princ(context, req, server, keytab, keyblock_out);
+
+    if (keytab->ops->start_seq_get == NULL) {
+        /* We can't iterate over the keytab.  Try the principal asserted by the
+         * client if it's allowed by the server parameter. */
+        if (!krb5_sname_match(context, server, req->ticket->server))
+            return KRB5KRB_AP_WRONG_PRINC;
+        return try_one_princ(context, req, req->ticket->server, keytab,
+                             keyblock_out);
+    }
 
-            retval = krb5_decrypt_tkt_part(context, &ktent.key,
-                                           req->ticket);
-
-            if (retval == 0) {
-                krb5_principal tmp = NULL;
-
-                TRACE_RD_REQ_DECRYPT_ANY(context, ktent.principal, &ktent.key);
-                /*
-                 * We overwrite ticket->server to be the principal
-                 * that we match in the keytab.  The reason for doing
-                 * this is that GSS-API and other consumers look at
-                 * that principal to make authorization decisions
-                 * about whether the appropriate server is contacted.
-                 * It might be cleaner to create a new API and store
-                 * the server in the auth_context, but doing so would
-                 * probably miss existing uses of the server. Instead,
-                 * perhaps an API should be created to retrieve the
-                 * server as it appeared in the ticket.
-                 */
-                retval = krb5_copy_principal(context, ktent.principal, &tmp);
-                if (retval == 0 && key != NULL)
-                    retval = krb5_copy_keyblock_contents(context, &ktent.key, key);
-                if (retval == 0) {
-                    krb5_free_principal(context, req->ticket->server);
-                    req->ticket->server = tmp;
-                } else {
-                    krb5_free_principal(context, tmp);
-                }
-                (void) krb5_free_keytab_entry_contents(context, &ktent);
+    ret = krb5_kt_start_seq_get(context, keytab, &cursor);
+    if (ret)
+        goto cleanup;
+
+    while ((ret = krb5_kt_next_entry(context, keytab, &ent, &cursor)) == 0) {
+        if (ent.key.enctype == req->ticket->enc_part.enctype &&
+            krb5_sname_match(context, server, ent.principal)) {
+            ret = try_one_entry(context, req, &ent, keyblock_out);
+            if (ret == 0) {
+                TRACE_RD_REQ_DECRYPT_ANY(context, ent.principal, &ent.key);
+                (void)krb5_free_keytab_entry_contents(context, &ent);
                 break;
             }
-            (void) krb5_free_keytab_entry_contents(context, &ktent);
         }
 
-        code = krb5_kt_end_seq_get(context, keytab, &cursor);
-        if (code != 0)
-            retval = code;
+        (void)krb5_free_keytab_entry_contents(context, &ent);
     }
-#endif /* LEAN_CLIENT */
 
-map_error:
-    switch (retval) {
+    (void)krb5_kt_end_seq_get(context, keytab, &cursor);
+
+cleanup:
+    switch (ret) {
     case KRB5_KT_KVNONOTFOUND:
     case KRB5_KT_NOTFOUND:
+    case KRB5_KT_END:
     case KRB5KRB_AP_ERR_BAD_INTEGRITY:
-        retval = KRB5KRB_AP_WRONG_PRINC;
+        ret = KRB5KRB_AP_WRONG_PRINC;
         break;
     default:
         break;
     }
 
-    return retval;
+    return ret;
+#endif /* LEAN_CLIENT */
 }
 
 #if 0
@@ -215,7 +251,6 @@ rd_req_decoded_opt(krb5_context context, krb5_auth_context *auth_context,
                    krb5_ticket **ticket, int check_valid_flag)
 {
     krb5_error_code       retval = 0;
-    krb5_principal_data   princ_data;
     krb5_enctype         *desired_etypes = NULL;
     int                   desired_etypes_len = 0;
     int                   rfc4537_etypes_len = 0;
@@ -225,19 +260,7 @@ rd_req_decoded_opt(krb5_context context, krb5_auth_context *auth_context,
 
     decrypt_key.enctype = ENCTYPE_NULL;
     decrypt_key.contents = NULL;
-
     req->ticket->enc_part2 = NULL;
-    if (server && krb5_is_referral_realm(&server->realm)) {
-        char *realm;
-        princ_data = *server;
-        server = &princ_data;
-        retval = krb5_get_default_realm(context, &realm);
-        if (retval)
-            return retval;
-        princ_data.realm.data = realm;
-        princ_data.realm.length = strlen(realm);
-    }
-
 
     /* if (req->ap_options & AP_OPTS_USE_SESSION_KEY)
        do we need special processing here ?     */
@@ -255,9 +278,9 @@ rd_req_decoded_opt(krb5_context context, krb5_auth_context *auth_context,
         krb5_k_free_key(context, (*auth_context)->key);
         (*auth_context)->key = NULL;
     } else {
-        if ((retval = rd_req_decrypt_tkt_part(context, req,
-                                              server, keytab,
-                                              check_valid_flag ? &decrypt_key : NULL)))
+        retval = decrypt_ticket(context, req, server, keytab,
+                                check_valid_flag ? &decrypt_key : NULL);
+        if (retval)
             goto cleanup;
     }
     TRACE_RD_REQ_TICKET(context, req->ticket->enc_part2->client,
@@ -545,8 +568,6 @@ cleanup:
     if (permitted_etypes != NULL &&
         permitted_etypes != (*auth_context)->permitted_etypes)
         free(permitted_etypes);
-    if (server == &princ_data)
-        krb5_free_default_realm(context, princ_data.realm.data);
     if (retval) {
         /* only free if we're erroring out...otherwise some
            applications will need the output. */
diff --git a/src/lib/krb5/krb/sname_match.c b/src/lib/krb5/krb/sname_match.c
new file mode 100644 (file)
index 0000000..0c7bd39
--- /dev/null
@@ -0,0 +1,54 @@
+/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/* lib/krb5/krb/sname_match.c - krb5_sname_match API function */
+/*
+ * Copyright (C) 2011 by the Massachusetts Institute of Technology.
+ * All rights reserved.
+ *
+ * Export of this software from the United States of America may
+ *   require a specific license from the United States Government.
+ *   It is the responsibility of any person or organization contemplating
+ *   export to obtain such a license before exporting.
+ *
+ * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
+ * distribute this software and its documentation for any purpose and
+ * without fee is hereby granted, provided that the above copyright
+ * notice appear in all copies and that both that copyright notice and
+ * this permission notice appear in supporting documentation, and that
+ * the name of M.I.T. not be used in advertising or publicity pertaining
+ * to distribution of the software without specific, written prior
+ * permission.  Furthermore if you modify this software you must label
+ * your software as modified software and not distribute it in such a
+ * fashion that it might be confused with the original M.I.T. software.
+ * M.I.T. makes no representations about the suitability of
+ * this software for any purpose.  It is provided "as is" without express
+ * or implied warranty.
+ */
+
+#include "k5-int.h"
+
+krb5_boolean KRB5_CALLCONV
+krb5_sname_match(krb5_context context, krb5_const_principal matching,
+                 krb5_const_principal princ)
+{
+    if (matching == NULL)
+        return TRUE;
+
+    if (matching->type != KRB5_NT_SRV_HST || matching->length != 2)
+        return krb5_principal_compare(context, matching, princ);
+
+    /* Check the realm if present in matching. */
+    if (matching->realm.length != 0 && !data_eq(matching->realm, princ->realm))
+        return FALSE;
+
+    /* Check the service name (must be present in matching). */
+    if (!data_eq(matching->data[0], princ->data[0]))
+        return FALSE;
+
+    /* Check the hostname if present in matching and not ignored. */
+    if (matching->data[1].length != 0 && !context->ignore_acceptor_hostname &&
+        !data_eq(matching->data[1], princ->data[1]))
+        return FALSE;
+
+    /* All elements match. */
+    return TRUE;
+}
index c8918dccdbd7da212d6d41175a69648a8c2a652d..6866813975d96ddbbf934e5f88662eab7b2a4e2c 100644 (file)
@@ -556,6 +556,7 @@ krb5_set_time_offsets
 krb5_size_opaque
 krb5_skdc_timeout_1
 krb5_skdc_timeout_shift
+krb5_sname_match
 krb5_sname_to_principal
 krb5_string_to_deltat
 krb5_string_to_salttype
index e74943700cead4a63b648912521c05070d519dd9..8c8a8f5033b62a1ebaaaf68cc46e0081cb92d735 100644 (file)
@@ -405,3 +405,6 @@ EXPORTS
        krb5_sendto_kdc                                 @381 ; PRIVATE GSSAPI
        krb5int_copy_data_contents_add0                 @382 ; PRIVATE GSSAPI
        krb5int_free_data_list                          @383 ; PRIVATE GSSAPI
+
+; new in 1.10
+       krb5_sname_match                                @384
index a0e2e47d4ecb9d1d46ae8b006e9fc8e211476ada..36b541732b848654cb3493ce09cff426a4528f12 100644 (file)
@@ -4,15 +4,19 @@ DEFINES = -DUSE_AUTOCONF_H
 PROG_LIBPATH=-L$(TOPLIBD)
 PROG_RPATH=$(KRB5_LIBDIR)
 
-SRCS= $(srcdir)/t_imp_name.c $(srcdir)/t_s4u.c $(srcdir)/t_namingexts.c $(srcdir)/t_gssexts.c $(srcdir)/t_saslname.c
+SRCS=  $(srcdir)/t_accname.c $(srcdir)/t_imp_name.c $(srcdir)/t_s4u.c \
+       $(srcdir)/t_namingexts.c $(srcdir)/t_gssexts.c $(srcdir)/t_saslname.c
 
-OBJS= t_imp_name.o t_s4u.o t_namingexts.o t_gssexts.o t_spnego.o t_saslname.o
+OBJS=  t_accname.o t_imp_name.o t_s4u.o t_namingexts.o t_gssexts.o \
+       t_spnego.o t_saslname.o
 
-all:: t_imp_name t_s4u t_namingexts t_gssexts t_spnego t_saslname
+all:: t_accname t_imp_name t_s4u t_namingexts t_gssexts t_spnego t_saslname
 
-check-pytests:: t_spnego
+check-pytests:: t_accname t_spnego
        $(RUNPYTEST) $(srcdir)/t_gssapi.py $(PYTESTFLAGS)
 
+t_accname: t_accname.o $(GSS_DEPLIBS) $(KRB5_BASE_DEPLIBS)
+       $(CC_LINK) -o t_accname t_accname.o $(GSS_LIBS) $(KRB5_BASE_LIBS)
 t_imp_name: t_imp_name.o $(GSS_DEPLIBS) $(KRB5_BASE_DEPLIBS)
        $(CC_LINK) -o t_imp_name t_imp_name.o $(GSS_LIBS) $(KRB5_BASE_LIBS)
 t_s4u: t_s4u.o $(GSS_DEPLIBS) $(KRB5_BASE_DEPLIBS)
diff --git a/src/tests/gssapi/t_accname.c b/src/tests/gssapi/t_accname.c
new file mode 100644 (file)
index 0000000..ab6bc83
--- /dev/null
@@ -0,0 +1,172 @@
+/* -*- mode: c; indent-tabs-mode: nil -*- */
+/*
+ * Copyright 2011 by the Massachusetts Institute of Technology.
+ * All Rights Reserved.
+ *
+ * Export of this software from the United States of America may
+ *   require a specific license from the United States Government.
+ *   It is the responsibility of any person or organization contemplating
+ *   export to obtain such a license before exporting.
+ *
+ * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
+ * distribute this software and its documentation for any purpose and
+ * without fee is hereby granted, provided that the above copyright
+ * notice appear in all copies and that both that copyright notice and
+ * this permission notice appear in supporting documentation, and that
+ * the name of M.I.T. not be used in advertising or publicity pertaining
+ * to distribution of the software without specific, written prior
+ * permission.  Furthermore if you modify this software you must label
+ * your software as modified software and not distribute it in such a
+ * fashion that it might be confused with the original M.I.T. software.
+ * M.I.T. makes no representations about the suitability of
+ * this software for any purpose.  It is provided "as is" without express
+ * or implied warranty.
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <gssapi/gssapi_krb5.h>
+
+/*
+ * Test program for acceptor names, intended to be run from a Python test
+ * script.  Performs a one-token gss_init_sec_context/gss_accept_sec_context
+ * exchange with the default initiator name, a specified principal name as
+ * target name, and a specified host-based name as acceptor name (or
+ * GSS_C_NO_NAME if no acceptor name is given).  If the exchange is successful,
+ * queries the context for the acceptor name and prints it.  If any call is
+ * unsuccessful, displays an error message.  Exits with status 0 if all
+ * operations are successful, or 1 if not.
+ *
+ * Usage: ./t_accname targetname [acceptorname]
+ */
+
+static void
+display_status_1(const char *m, OM_uint32 code, int type)
+{
+    OM_uint32 maj_stat, min_stat;
+    gss_buffer_desc msg;
+    OM_uint32 msg_ctx;
+
+    msg_ctx = 0;
+    while (1) {
+        maj_stat = gss_display_status(&min_stat, code,
+                                      type, GSS_C_NULL_OID,
+                                      &msg_ctx, &msg);
+        fprintf(stderr, "%s: %s\n", m, (char *)msg.value);
+        (void) gss_release_buffer(&min_stat, &msg);
+
+        if (!msg_ctx)
+            break;
+     }
+}
+
+static void
+display_status(const char *msg, OM_uint32 maj_stat, OM_uint32 min_stat)
+{
+     display_status_1(msg, maj_stat, GSS_C_GSS_CODE);
+     display_status_1(msg, min_stat, GSS_C_MECH_CODE);
+}
+
+int
+main(int argc, char *argv[])
+{
+    OM_uint32 minor, major;
+    gss_cred_id_t acceptor_cred;
+    gss_buffer_desc buf;
+    gss_name_t target_name, acceptor_name = GSS_C_NO_NAME, real_acceptor_name;
+    gss_buffer_desc token, tmp, namebuf;
+    gss_ctx_id_t initiator_context = GSS_C_NO_CONTEXT;
+    gss_ctx_id_t acceptor_context = GSS_C_NO_CONTEXT;
+
+    if (argc < 2 || argc > 3) {
+        fprintf(stderr, "Usage: %s targetname [acceptorname]\n", argv[0]);
+        return 1;
+    }
+
+    /* Import the target name as a krb5 principal name. */
+    buf.value = argv[1];
+    buf.length = strlen((char *)buf.value);
+    major = gss_import_name(&minor, &buf, (gss_OID)GSS_KRB5_NT_PRINCIPAL_NAME,
+                            &target_name);
+    if (GSS_ERROR(major)) {
+        display_status("gss_import_name(target_name)", major, minor);
+        return 1;
+    }
+
+    /* Import the acceptor name as a host-based name. */
+    if (argc >= 3) {
+        buf.value = argv[2];
+        buf.length = strlen((char *)buf.value);
+        major = gss_import_name(&minor, &buf,
+                                (gss_OID)GSS_C_NT_HOSTBASED_SERVICE,
+                                &acceptor_name);
+        if (GSS_ERROR(major)) {
+            display_status("gss_import_name(acceptor_name)", major, minor);
+            return 1;
+        }
+    }
+
+    /* Get acceptor cred. */
+    major = gss_acquire_cred(&minor, acceptor_name, GSS_C_INDEFINITE,
+                             GSS_C_NO_OID_SET, GSS_C_ACCEPT,
+                             &acceptor_cred, NULL, NULL);
+    if (GSS_ERROR(major)) {
+        display_status("gss_acquire_cred", major, minor);
+        return 1;
+    }
+
+    /* Create krb5 initiator context and get the first token. */
+    token.value = NULL;
+    token.length = 0;
+    major = gss_init_sec_context(&minor, GSS_C_NO_CREDENTIAL,
+                                 &initiator_context, target_name,
+                                 (gss_OID)gss_mech_krb5,
+                                 GSS_C_REPLAY_FLAG | GSS_C_SEQUENCE_FLAG,
+                                 GSS_C_INDEFINITE, GSS_C_NO_CHANNEL_BINDINGS,
+                                 GSS_C_NO_BUFFER, NULL, &token, NULL, NULL);
+    if (GSS_ERROR(major)) {
+        display_status("gss_init_sec_context", major, minor);
+        return 1;
+    }
+
+    /* Pass the token to gss_accept_sec_context. */
+    tmp.value = NULL;
+    tmp.length = 0;
+    major = gss_accept_sec_context(&minor, &acceptor_context, acceptor_cred,
+                                   &token, GSS_C_NO_CHANNEL_BINDINGS,
+                                   NULL, NULL, &tmp, NULL, NULL, NULL);
+    if (major != GSS_S_COMPLETE) {
+        display_status("gss_accept_sec_context", major, minor);
+        return 1;
+    }
+
+    major = gss_inquire_context(&minor, acceptor_context, NULL,
+                                &real_acceptor_name, NULL, NULL, NULL, NULL,
+                                NULL);
+    if (GSS_ERROR(major)) {
+        display_status("gss_inquire_context", major, minor);
+        return 1;
+    }
+
+    namebuf.value = NULL;
+    namebuf.length = 0;
+    major = gss_display_name(&minor, real_acceptor_name, &namebuf, NULL);
+    if (GSS_ERROR(major)) {
+        display_status("gss_display_name", major, minor);
+        return 1;
+    }
+
+    printf("%.*s\n", (int)namebuf.length, (char *)namebuf.value);
+
+    (void)gss_release_name(&minor, &target_name);
+    (void)gss_release_name(&minor, &acceptor_name);
+    (void)gss_release_cred(&minor, &acceptor_cred);
+    (void)gss_delete_sec_context(&minor, &initiator_context, NULL);
+    (void)gss_delete_sec_context(&minor, &acceptor_context, NULL);
+    (void)gss_release_buffer(&minor, &token);
+    (void)gss_release_buffer(&minor, &tmp);
+    return 0;
+}
index d88f7949b8eec06eb674e4b20c3df77f4b97cad3..f4dd9e3cb85ffeeb516907eeff44dc482ad36167 100644 (file)
@@ -1,7 +1,86 @@
 #!/usr/bin/python
 from k5test import *
 
+# Test krb5 negotiation under SPNEGO for all enctype configurations.
 for realm in multipass_realms():
     realm.run_as_client(['./t_spnego', realm.host_princ, realm.keytab])
 
-success('GSSAPI test programs (SPNEGO only).')
+### Test acceptor name behavior.
+
+realm = K5Realm(start_kadmind=False)
+
+# Create some host-based principals and put most of them into the
+# keytab.  Rename one principal so that the keytab name matches the
+# key but not the client name.
+realm.run_kadminl('addprinc -randkey service1/abraham')
+realm.run_kadminl('addprinc -randkey service1/barack')
+realm.run_kadminl('addprinc -randkey service2/calvin')
+realm.run_kadminl('addprinc -randkey service2/dwight')
+realm.run_kadminl('addprinc -randkey host/-nomatch-')
+realm.run_kadminl('xst service1/abraham')
+realm.run_kadminl('xst service1/barack')
+realm.run_kadminl('xst service2/calvin')
+realm.run_kadminl('renprinc -force service1/abraham service1/andrew')
+
+# Test with no acceptor name, including client/keytab principal
+# mismatch (non-fatal) and missing keytab entry (fatal).
+output = realm.run_as_client(['./t_accname', 'service1/andrew'])
+if 'service1/abraham' not in output:
+    fail('Expected service1/abraham in t_accname output')
+output = realm.run_as_client(['./t_accname', 'service1/barack'])
+if 'service1/barack' not in output:
+    fail('Expected service1/barack in t_accname output')
+output = realm.run_as_client(['./t_accname', 'service2/calvin'])
+if 'service2/calvin' not in output:
+    fail('Expected service1/barack in t_accname output')
+output = realm.run_as_client(['./t_accname', 'service2/dwight'],
+                             expected_code=1)
+if 'Wrong principal in request' not in output:
+    fail('Expected error message not seen in t_accname output')
+
+# Test with acceptor name containing service only, including
+# client/keytab hostname mismatch (non-fatal) and service name
+# mismatch (fatal).
+output = realm.run_as_client(['./t_accname', 'service1/andrew', 'service1'])
+if 'service1/abraham' not in output:
+    fail('Expected service1/abraham in t_accname output')
+output = realm.run_as_client(['./t_accname', 'service1/andrew', 'service2'],
+                             expected_code=1)
+if 'Wrong principal in request' not in output:
+    fail('Expected error message not seen in t_accname output')
+output = realm.run_as_client(['./t_accname', 'service2/calvin', 'service2'])
+if 'service2/calvin' not in output:
+    fail('Expected service2/calvin in t_accname output')
+output = realm.run_as_client(['./t_accname', 'service2/calvin', 'service1'],
+                             expected_code=1)
+if 'Wrong principal in request' not in output:
+    fail('Expected error message not seen in t_accname output')
+
+# Test with acceptor name containing service and host.  Use the
+# client's un-canonicalized hostname as acceptor input to mirror what
+# many servers do.
+output = realm.run_as_client(['./t_accname', realm.host_princ,
+                              'host@%s' % socket.gethostname()])
+if realm.host_princ not in output:
+    fail('Expected %s in t_accname output' % realm.host_princ)
+output = realm.run_as_client(['./t_accname', 'host/-nomatch-',
+                              'host@%s' % socket.gethostname()],
+                             expected_code=1)
+if 'Wrong principal in request' not in output:
+    fail('Expected error message not seen in t_accname output')
+
+realm.stop()
+
+# Re-run that last test with ignore_acceptor_hostname set and the
+# principal for the mismatching hostname in the keytab.
+ignore_conf = { 'all' : { 'libdefaults' : {
+            'ignore_acceptor_hostname' : 'true' } } }
+realm = K5Realm(krb5_conf=ignore_conf, start_kadmind=False)
+realm.run_kadminl('addprinc -randkey host/-nomatch-')
+realm.run_kadminl('xst host/-nomatch-')
+output = realm.run_as_client(['./t_accname', 'host/-nomatch-',
+                              'host@%s' % socket.gethostname()])
+if 'host/-nomatch-' not in output:
+    fail('Expected error message not seen in t_accname output')
+
+success('GSSAPI tests.')