From: Greg Hudson Date: Mon, 7 Feb 2011 18:40:00 +0000 (+0000) Subject: Improve acceptor name flexibility X-Git-Tag: krb5-1.10-alpha1~602 X-Git-Url: http://git.tremily.us/?a=commitdiff_plain;h=66587fcd6380eac2c53674df4f64a827d337aee5;p=krb5.git 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 --- diff --git a/doc/admin.texinfo b/doc/admin.texinfo index 5051b5d3f..427f64eca 100644 --- a/doc/admin.texinfo +++ b/doc/admin.texinfo @@ -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, diff --git a/src/config-files/krb5.conf.M b/src/config-files/krb5.conf.M index df62d4d52..b04d6efd6 100644 --- a/src/config-files/krb5.conf.M +++ b/src/config-files/krb5.conf.M @@ -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, diff --git a/src/include/k5-int.h b/src/include/k5-int.h index d188f8e9d..92ff1f043 100644 --- a/src/include/k5-int.h +++ b/src/include/k5-int.h @@ -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; diff --git a/src/include/krb5/krb5.hin b/src/include/krb5/krb5.hin index adf670082..14c4a9e41 100644 --- a/src/include/krb5/krb5.hin +++ b/src/include/krb5/krb5.hin @@ -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. * diff --git a/src/lib/gssapi/krb5/accept_sec_context.c b/src/lib/gssapi/krb5/accept_sec_context.c index c3cb2f19e..0344ebd10 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 */ @@ -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) diff --git a/src/lib/gssapi/krb5/acquire_cred.c b/src/lib/gssapi/krb5/acquire_cred.c index 4fe9ebcea..ae34f95cc 100644 --- a/src/lib/gssapi/krb5/acquire_cred.c +++ b/src/lib/gssapi/krb5/acquire_cred.c @@ -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; } diff --git a/src/lib/gssapi/krb5/gssapiP_krb5.h b/src/lib/gssapi/krb5/gssapiP_krb5.h index a0e60be5e..6649331e7 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; @@ -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, diff --git a/src/lib/gssapi/krb5/import_name.c b/src/lib/gssapi/krb5/import_name.c index 0f36721c1..2ba178a04 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 8cb21bf60..d1940123f 100644 --- a/src/lib/gssapi/krb5/naming_exts.c +++ b/src/lib/gssapi/krb5/naming_exts.c @@ -33,11 +33,9 @@ #include 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 ac07dad5d..4cebf7fa6 100644 --- a/src/lib/gssapi/krb5/s4u_gss_glue.c +++ b/src/lib/gssapi/krb5/s4u_gss_glue.c @@ -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; diff --git a/src/lib/gssapi/krb5/ser_sctx.c b/src/lib/gssapi/krb5/ser_sctx.c index ae6350cce..dc679c9d8 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 b96229986..b1af2e747 100644 --- a/src/lib/krb5/krb/Makefile.in +++ b/src/lib/krb5/krb/Makefile.in @@ -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 \ diff --git a/src/lib/krb5/krb/init_ctx.c b/src/lib/krb5/krb/init_ctx.c index 4f0075031..4eae4d743 100644 --- a/src/lib/krb5/krb/init_ctx.c +++ b/src/lib/krb5/krb/init_ctx.c @@ -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; diff --git a/src/lib/krb5/krb/rd_req_dec.c b/src/lib/krb5/krb/rd_req_dec.c index 9bc7c42f9..e065e2157 100644 --- a/src/lib/krb5/krb/rd_req_dec.c +++ b/src/lib/krb5/krb/rd_req_dec.c @@ -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 index 000000000..0c7bd39e8 --- /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 c8918dccd..686681397 100644 --- a/src/lib/krb5/libkrb5.exports +++ b/src/lib/krb5/libkrb5.exports @@ -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 diff --git a/src/lib/krb5_32.def b/src/lib/krb5_32.def index e74943700..8c8a8f503 100644 --- a/src/lib/krb5_32.def +++ b/src/lib/krb5_32.def @@ -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 diff --git a/src/tests/gssapi/Makefile.in b/src/tests/gssapi/Makefile.in index a0e2e47d4..36b541732 100644 --- a/src/tests/gssapi/Makefile.in +++ b/src/tests/gssapi/Makefile.in @@ -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 index 000000000..ab6bc83bd --- /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 +#include +#include + +#include + +/* + * 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; +} diff --git a/src/tests/gssapi/t_gssapi.py b/src/tests/gssapi/t_gssapi.py index d88f7949b..f4dd9e3cb 100644 --- a/src/tests/gssapi/t_gssapi.py +++ b/src/tests/gssapi/t_gssapi.py @@ -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.')