From 1a401d7162639281c961379e4c53469c7def5f27 Mon Sep 17 00:00:00 2001
From: Greg Hudson <ghudson@mit.edu>
Date: Mon, 7 Feb 2011 18:40:00 +0000
Subject: [PATCH 120/120] Improve acceptor name flexibility

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
---
 doc/admin.texinfo                        |   9 ++
 src/config-files/krb5.conf.M             |   9 ++
 src/include/k5-int.h                     |   2 +
 src/include/krb5/krb5.hin                |  16 +++
 src/lib/gssapi/krb5/accept_sec_context.c |  24 ++--
 src/lib/gssapi/krb5/acquire_cred.c       |  83 ++++++++++---
 src/lib/gssapi/krb5/gssapiP_krb5.h       |  18 +--
 src/lib/gssapi/krb5/import_name.c        |  67 ++++++++---
 src/lib/gssapi/krb5/naming_exts.c        |  66 +++++++++-
 src/lib/gssapi/krb5/s4u_gss_glue.c       |   3 +-
 src/lib/gssapi/krb5/ser_sctx.c           |   4 +-
 src/lib/krb5/krb/Makefile.in             |   3 +
 src/lib/krb5/krb/init_ctx.c              |   7 ++
 src/lib/krb5/krb/rd_req_dec.c            | 200 +++++++++++++++++--------------
 src/lib/krb5/krb/sname_match.c           |  54 +++++++++
 src/lib/krb5/libkrb5.exports             |   1 +
 src/lib/krb5_32.def                      |   1 +
 src/tests/gssapi/Makefile.in             |   8 +-
 src/tests/gssapi/t_accname.c             | 172 ++++++++++++++++++++++++++
 19 files changed, 598 insertions(+), 149 deletions(-)
 create mode 100644 src/lib/krb5/krb/sname_match.c
 create mode 100644 src/tests/gssapi/t_accname.c

diff --git a/doc/admin.texinfo b/doc/admin.texinfo
index fbdc592..fe673b1 100644
--- a/doc/admin.texinfo
+++ b/doc/admin.texinfo
@@ -467,6 +467,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 kdc_timesync
 If this is set to 1 (for true), then client machines will compute the
 difference between their time and the time returned by the KDC in the
diff --git a/src/config-files/krb5.conf.M b/src/config-files/krb5.conf.M
index 412156b..e253f4b 100644
--- a/src/config-files/krb5.conf.M
+++ b/src/config-files/krb5.conf.M
@@ -141,6 +141,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 kdc_timesync 
 If the value of this relation is non-zero (the default), the library
 will compute the difference between the system clock and the time
diff --git a/src/include/k5-int.h b/src/include/k5-int.h
index 96c7d77..05e1d80 100644
--- a/src/include/k5-int.h
+++ b/src/include/k5-int.h
@@ -209,6 +209,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"
@@ -1486,6 +1487,7 @@ struct _krb5_context {
     struct _kdb_log_context *kdblog_context;
 
     krb5_boolean allow_weak_crypto;
+    krb5_boolean ignore_acceptor_hostname;
 };
 
 /* could be used in a table to find an etype and initialize a block */
diff --git a/src/include/krb5/krb5.hin b/src/include/krb5/krb5.hin
index 1c89f9e..bf87ca1 100644
--- a/src/include/krb5/krb5.hin
+++ b/src/include/krb5/krb5.hin
@@ -1929,6 +1929,22 @@ krb5_error_code KRB5_CALLCONV
 krb5_sname_to_principal(krb5_context, const char *, const char *,  krb5_int32,
                         krb5_principal *);
 
+/** 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);
+
 krb5_error_code KRB5_CALLCONV
 krb5_change_password(krb5_context context, krb5_creds *creds, char *newpw,
                      int *result_code, krb5_data *result_code_string,
diff --git a/src/lib/gssapi/krb5/accept_sec_context.c b/src/lib/gssapi/krb5/accept_sec_context.c
index 974804d..eb191fc 100644
--- a/src/lib/gssapi/krb5/accept_sec_context.c
+++ b/src/lib/gssapi/krb5/accept_sec_context.c
@@ -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 */
@@ -471,6 +472,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) {
@@ -625,11 +627,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;
     }
@@ -911,7 +917,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;
     }
@@ -920,7 +927,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;
@@ -1260,7 +1267,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)
diff --git a/src/lib/gssapi/krb5/acquire_cred.c b/src/lib/gssapi/krb5/acquire_cred.c
index 029d535..c1382cb 100644
--- a/src/lib/gssapi/krb5/acquire_cred.c
+++ b/src/lib/gssapi/krb5/acquire_cred.c
@@ -130,6 +130,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
 */
@@ -137,13 +190,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);
 
@@ -176,8 +228,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) {
@@ -189,18 +241,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;
@@ -385,7 +435,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);
@@ -520,9 +570,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;
@@ -545,7 +595,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 */
@@ -566,18 +616,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;
     }
@@ -589,7 +635,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;
     }
diff --git a/src/lib/gssapi/krb5/gssapiP_krb5.h b/src/lib/gssapi/krb5/gssapiP_krb5.h
index fc74ff1..86fe76c 100644
--- a/src/lib/gssapi/krb5/gssapiP_krb5.h
+++ b/src/lib/gssapi/krb5/gssapiP_krb5.h
@@ -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;
 
@@ -886,11 +888,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,
@@ -908,6 +908,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,
diff --git a/src/lib/gssapi/krb5/import_name.c b/src/lib/gssapi/krb5/import_name.c
index 0f36721..2ba178a 100644
--- a/src/lib/gssapi/krb5/import_name.c
+++ b/src/lib/gssapi/krb5/import_name.c
@@ -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;
 }
diff --git a/src/lib/gssapi/krb5/naming_exts.c b/src/lib/gssapi/krb5/naming_exts.c
index 8cb21bf..d194012 100644
--- a/src/lib/gssapi/krb5/naming_exts.c
+++ b/src/lib/gssapi/krb5/naming_exts.c
@@ -33,11 +33,9 @@
 #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)
 {
diff --git a/src/lib/gssapi/krb5/s4u_gss_glue.c b/src/lib/gssapi/krb5/s4u_gss_glue.c
index cecbbb0..4ed5816 100644
--- a/src/lib/gssapi/krb5/s4u_gss_glue.c
+++ b/src/lib/gssapi/krb5/s4u_gss_glue.c
@@ -219,7 +219,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;
 
diff --git a/src/lib/gssapi/krb5/ser_sctx.c b/src/lib/gssapi/krb5/ser_sctx.c
index 63a7bd8..e0f49ee 100644
--- a/src/lib/gssapi/krb5/ser_sctx.c
+++ b/src/lib/gssapi/krb5/ser_sctx.c
@@ -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);
diff --git a/src/lib/krb5/krb/Makefile.in b/src/lib/krb5/krb/Makefile.in
index 54337da..6305bcd 100644
--- a/src/lib/krb5/krb/Makefile.in
+++ b/src/lib/krb5/krb/Makefile.in
@@ -95,6 +95,7 @@ STLIBOBJS= \
 	ser_princ.o	\
 	serialize.o	\
 	set_realm.o	\
+	sname_match.o	\
 	srv_dec_tkt.o	\
 	srv_rcache.o	\
 	str_conv.o	\
@@ -189,6 +190,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)	\
@@ -284,6 +286,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	\
diff --git a/src/lib/krb5/krb/init_ctx.c b/src/lib/krb5/krb/init_ctx.c
index e72534c..1e5b1cf 100644
--- a/src/lib/krb5/krb/init_ctx.c
+++ b/src/lib/krb5/krb/init_ctx.c
@@ -170,6 +170,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;
diff --git a/src/lib/krb5/krb/rd_req_dec.c b/src/lib/krb5/krb/rd_req_dec.c
index 3a9b9ee..af31d16 100644
--- a/src/lib/krb5/krb/rd_req_dec.c
+++ b/src/lib/krb5/krb/rd_req_dec.c
@@ -79,97 +79,136 @@ 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 && key != NULL)
-                retval = krb5_copy_keyblock_contents(context, &ktent.key, key);
-
-            (void) krb5_free_keytab_entry_contents(context, &ktent);
-        }
-    } else {
-        krb5_error_code code;
-        krb5_kt_cursor cursor;
+    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 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;
+
+    return 0;
+}
 
-        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;
-            }
+/*
+ * 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;
 
-            retval = krb5_decrypt_tkt_part(context, &ktent.key,
-                                           req->ticket);
-
-            if (retval == 0) {
-                krb5_principal tmp = NULL;
-
-                /*
-                 * 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);
+#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);
+    }
+
+    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) {
+                (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
@@ -203,7 +242,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;
@@ -213,19 +251,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 ?     */
@@ -243,9 +269,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;
     }
 
@@ -529,8 +555,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
index 0000000..0c7bd39
--- /dev/null
+++ b/src/lib/krb5/krb/sname_match.c
@@ -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;
+}
diff --git a/src/lib/krb5/libkrb5.exports b/src/lib/krb5/libkrb5.exports
index e59acfe..957e0bf 100644
--- a/src/lib/krb5/libkrb5.exports
+++ b/src/lib/krb5/libkrb5.exports
@@ -548,6 +548,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
diff --git a/src/lib/krb5_32.def b/src/lib/krb5_32.def
index b79fbda..312d954 100644
--- a/src/lib/krb5_32.def
+++ b/src/lib/krb5_32.def
@@ -309,3 +309,4 @@ EXPORTS
 	krb5_c_decrypt_iov
 	krb5_c_make_checksum_iov
 	krb5_c_verify_checksum_iov
+	krb5_sname_match		@384
diff --git a/src/tests/gssapi/Makefile.in b/src/tests/gssapi/Makefile.in
index 6a15414..264ef1b 100644
--- a/src/tests/gssapi/Makefile.in
+++ b/src/tests/gssapi/Makefile.in
@@ -4,11 +4,11 @@ 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
+SRCS= $(srcdir)/t_imp_name.c $(srcdir)/t_s4u.c $(srcdir)/t_namingexts.c $(srcdir)/t_gssexts.c $(srcdir)/t_accname.c
 
-OBJS= t_imp_name.o t_s4u.o t_namingexts.o t_gssexts.o
+OBJS= t_imp_name.o t_s4u.o t_namingexts.o t_gssexts.o t_accname.o
 
-all:: t_imp_name t_s4u t_namingexts t_gssexts t_spnego
+all:: t_imp_name t_s4u t_namingexts t_gssexts t_spnego t_accname
 
 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)
@@ -20,6 +20,8 @@ t_gssexts: t_gssexts.o $(GSS_DEPLIBS) $(KRB5_BASE_DEPLIBS)
 	$(CC_LINK) -o t_gssexts t_gssexts.o $(GSS_LIBS) $(KRB5_BASE_LIBS)
 t_spnego: t_spnego.o $(GSS_DEPLIBS) $(KRB5_BASE_DEPLIBS)
 	$(CC_LINK) -o t_spnego t_spnego.o $(GSS_LIBS) $(KRB5_BASE_LIBS)
+t_accname: t_accname.o $(GSS_DEPLIBS) $(KRB5_BASE_DEPLIBS)
+	$(CC_LINK) -o t_accname t_accname.o $(GSS_LIBS) $(KRB5_BASE_LIBS)
 
 clean::
 	$(RM) t_imp_name t_s4u t_namingexts t_gssexts t_spnego
diff --git a/src/tests/gssapi/t_accname.c b/src/tests/gssapi/t_accname.c
new file mode 100644
index 0000000..ab6bc83
--- /dev/null
+++ b/src/tests/gssapi/t_accname.c
@@ -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;
+}
-- 
2.6.6

