diff --git a/Add-dns_canonicalize_hostname-fallback-support.patch b/Add-dns_canonicalize_hostname-fallback-support.patch new file mode 100644 index 0000000..07eb422 --- /dev/null +++ b/Add-dns_canonicalize_hostname-fallback-support.patch @@ -0,0 +1,409 @@ +From 18d45e4b48c363f631b1acd7dac5902351bf1a0e Mon Sep 17 00:00:00 2001 +From: Simo Sorce +Date: Tue, 4 Dec 2018 15:22:55 -0500 +Subject: [PATCH] Add dns_canonicalize_hostname=fallback support + +Turn dns_canonicalize_hostname into a tristate variable, allowing the +value "fallback" as well as the true/false booleans. If it is set to +fallback, delay DNS canonicalization and attempt it only in +krb5_get_credentials() if the KDC responds that the requested server +principal name is unknown. + +[ghudson@mit.edu: added TGS tests; refactored code; edited commit +message and documentation] + +ticket: 8765 (new) +(cherry picked from commit 6c20cb1c89acaa03db897182a3b28d5f8f284907) +--- + doc/admin/conf_files/krb5_conf.rst | 4 ++ + src/include/k5-int.h | 8 ++- + src/include/k5-trace.h | 3 ++ + src/lib/krb5/krb/get_creds.c | 79 ++++++++++++++++++++++++++---- + src/lib/krb5/krb/init_ctx.c | 27 +++++++++- + src/lib/krb5/krb/t_copy_context.c | 2 +- + src/lib/krb5/os/os-proto.h | 4 ++ + src/lib/krb5/os/sn2princ.c | 19 +++++-- + src/tests/gcred.c | 5 +- + src/tests/t_sn2princ.py | 34 ++++++++++++- + 10 files changed, 167 insertions(+), 18 deletions(-) + +diff --git a/doc/admin/conf_files/krb5_conf.rst b/doc/admin/conf_files/krb5_conf.rst +index 7b4389f6b..e9f7e8c59 100644 +--- a/doc/admin/conf_files/krb5_conf.rst ++++ b/doc/admin/conf_files/krb5_conf.rst +@@ -201,6 +201,10 @@ The libdefaults section may contain any of the following relations: + means that short hostnames will not be canonicalized to + fully-qualified hostnames. The default value is true. + ++ If this option is set to ``fallback`` (new in release 1.18), DNS ++ canonicalization will only be performed the server hostname is not ++ found with the original name when requesting credentials. ++ + **dns_lookup_kdc** + Indicate whether DNS SRV records should be used to locate the KDCs + and other servers for a realm, if they are not listed in the +diff --git a/src/include/k5-int.h b/src/include/k5-int.h +index 255cee822..1e6a739e9 100644 +--- a/src/include/k5-int.h ++++ b/src/include/k5-int.h +@@ -1159,6 +1159,12 @@ k5_plugin_register_dyn(krb5_context context, int interface_id, + void + k5_plugin_free_context(krb5_context context); + ++enum dns_canonhost { ++ CANONHOST_FALSE = 0, ++ CANONHOST_TRUE = 1, ++ CANONHOST_FALLBACK = 2 ++}; ++ + struct _kdb5_dal_handle; /* private, in kdb5.h */ + typedef struct _kdb5_dal_handle kdb5_dal_handle; + struct _kdb_log_context; +@@ -1222,7 +1228,7 @@ struct _krb5_context { + + krb5_boolean allow_weak_crypto; + krb5_boolean ignore_acceptor_hostname; +- krb5_boolean dns_canonicalize_hostname; ++ enum dns_canonhost dns_canonicalize_hostname; + + krb5_trace_callback trace_callback; + void *trace_callback_data; +diff --git a/src/include/k5-trace.h b/src/include/k5-trace.h +index 2aa379b76..f3ed6a45d 100644 +--- a/src/include/k5-trace.h ++++ b/src/include/k5-trace.h +@@ -191,6 +191,9 @@ void krb5int_trace(krb5_context context, const char *fmt, ...); + #define TRACE_FAST_REQUIRED(c) \ + TRACE(c, "Using FAST due to KRB5_FAST_REQUIRED flag") + ++#define TRACE_GET_CREDS_FALLBACK(c, hostname) \ ++ TRACE(c, "Falling back to canonicalized server hostname {str}", hostname) ++ + #define TRACE_GIC_PWD_CHANGED(c) \ + TRACE(c, "Getting initial TGT with changed password") + #define TRACE_GIC_PWD_CHANGEPW(c, tries) \ +diff --git a/src/lib/krb5/krb/get_creds.c b/src/lib/krb5/krb/get_creds.c +index 69900adfa..0a04d68b9 100644 +--- a/src/lib/krb5/krb/get_creds.c ++++ b/src/lib/krb5/krb/get_creds.c +@@ -39,6 +39,7 @@ + + #include "k5-int.h" + #include "int-proto.h" ++#include "os-proto.h" + #include "fast.h" + + /* +@@ -1249,6 +1250,26 @@ krb5_tkt_creds_step(krb5_context context, krb5_tkt_creds_context ctx, + return EINVAL; + } + ++static krb5_error_code ++try_get_creds(krb5_context context, krb5_flags options, krb5_ccache ccache, ++ krb5_creds *in_creds, krb5_creds *creds_out) ++{ ++ krb5_error_code code; ++ krb5_tkt_creds_context ctx = NULL; ++ ++ code = krb5_tkt_creds_init(context, ccache, in_creds, options, &ctx); ++ if (code) ++ goto cleanup; ++ code = krb5_tkt_creds_get(context, ctx); ++ if (code) ++ goto cleanup; ++ code = krb5_tkt_creds_get_creds(context, ctx, creds_out); ++ ++cleanup: ++ krb5_tkt_creds_free(context, ctx); ++ return code; ++} ++ + krb5_error_code KRB5_CALLCONV + krb5_get_credentials(krb5_context context, krb5_flags options, + krb5_ccache ccache, krb5_creds *in_creds, +@@ -1256,7 +1277,10 @@ krb5_get_credentials(krb5_context context, krb5_flags options, + { + krb5_error_code code; + krb5_creds *ncreds = NULL; +- krb5_tkt_creds_context ctx = NULL; ++ krb5_creds canon_creds, store_creds; ++ krb5_principal_data canon_server; ++ krb5_data canon_components[2]; ++ char *hostname = NULL, *canon_hostname = NULL; + + *out_creds = NULL; + +@@ -1265,22 +1289,59 @@ krb5_get_credentials(krb5_context context, krb5_flags options, + if (ncreds == NULL) + goto cleanup; + +- /* Make and execute a krb5_tkt_creds context to get the credential. */ +- code = krb5_tkt_creds_init(context, ccache, in_creds, options, &ctx); +- if (code != 0) ++ code = try_get_creds(context, options, ccache, in_creds, ncreds); ++ if (!code) { ++ *out_creds = ncreds; ++ return 0; ++ } ++ ++ /* Possibly try again with the canonicalized hostname, if the server is ++ * host-based and we are configured for fallback canonicalization. */ ++ if (code != KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN) + goto cleanup; +- code = krb5_tkt_creds_get(context, ctx); +- if (code != 0) ++ if (context->dns_canonicalize_hostname != CANONHOST_FALLBACK) + goto cleanup; +- code = krb5_tkt_creds_get_creds(context, ctx, ncreds); +- if (code != 0) ++ if (in_creds->server->type != KRB5_NT_SRV_HST || ++ in_creds->server->length != 2) + goto cleanup; + ++ hostname = k5memdup0(in_creds->server->data[1].data, ++ in_creds->server->data[1].length, &code); ++ if (hostname == NULL) ++ goto cleanup; ++ code = k5_expand_hostname(context, hostname, TRUE, &canon_hostname); ++ if (code) ++ goto cleanup; ++ ++ TRACE_GET_CREDS_FALLBACK(context, canon_hostname); ++ ++ /* Make shallow copies of in_creds and its server to alter the hostname. */ ++ canon_components[0] = in_creds->server->data[0]; ++ canon_components[1] = string2data(canon_hostname); ++ canon_server = *in_creds->server; ++ canon_server.data = canon_components; ++ canon_creds = *in_creds; ++ canon_creds.server = &canon_server; ++ ++ code = try_get_creds(context, options | KRB5_GC_NO_STORE, ccache, ++ &canon_creds, ncreds); ++ if (code) ++ goto cleanup; ++ ++ if (!(options & KRB5_GC_NO_STORE)) { ++ /* Store the creds under the originally requested server name. The ++ * ccache layer will also store them under the ticket server name. */ ++ store_creds = *ncreds; ++ store_creds.server = in_creds->server; ++ (void)krb5_cc_store_cred(context, ccache, &store_creds); ++ } ++ + *out_creds = ncreds; + ncreds = NULL; + + cleanup: ++ free(hostname); ++ free(canon_hostname); + krb5_free_creds(context, ncreds); +- krb5_tkt_creds_free(context, ctx); + return code; + } +diff --git a/src/lib/krb5/krb/init_ctx.c b/src/lib/krb5/krb/init_ctx.c +index 947e50400..d263d5cc5 100644 +--- a/src/lib/krb5/krb/init_ctx.c ++++ b/src/lib/krb5/krb/init_ctx.c +@@ -101,6 +101,30 @@ get_boolean(krb5_context ctx, const char *name, int def_val, int *boolean_out) + return retval; + } + ++static krb5_error_code ++get_tristate(krb5_context ctx, const char *name, const char *third_option, ++ int third_option_val, int def_val, int *val_out) ++{ ++ krb5_error_code retval; ++ char *str; ++ int match; ++ ++ retval = profile_get_boolean(ctx->profile, KRB5_CONF_LIBDEFAULTS, name, ++ NULL, def_val, val_out); ++ if (retval != PROF_BAD_BOOLEAN) ++ return retval; ++ retval = profile_get_string(ctx->profile, KRB5_CONF_LIBDEFAULTS, name, ++ NULL, NULL, &str); ++ if (retval) ++ return retval; ++ match = (strcasecmp(third_option, str) == 0); ++ free(str); ++ if (!match) ++ return EINVAL; ++ *val_out = third_option_val; ++ return 0; ++} ++ + krb5_error_code KRB5_CALLCONV + krb5_init_context(krb5_context *context) + { +@@ -213,7 +237,8 @@ krb5_init_context_profile(profile_t profile, krb5_flags flags, + goto cleanup; + ctx->ignore_acceptor_hostname = tmp; + +- retval = get_boolean(ctx, KRB5_CONF_DNS_CANONICALIZE_HOSTNAME, 1, &tmp); ++ retval = get_tristate(ctx, KRB5_CONF_DNS_CANONICALIZE_HOSTNAME, "fallback", ++ CANONHOST_FALLBACK, 1, &tmp); + if (retval) + goto cleanup; + ctx->dns_canonicalize_hostname = tmp; +diff --git a/src/lib/krb5/krb/t_copy_context.c b/src/lib/krb5/krb/t_copy_context.c +index fa810be8a..a6e48cd25 100644 +--- a/src/lib/krb5/krb/t_copy_context.c ++++ b/src/lib/krb5/krb/t_copy_context.c +@@ -145,7 +145,7 @@ main(int argc, char **argv) + ctx->udp_pref_limit = 2345; + ctx->use_conf_ktypes = TRUE; + ctx->ignore_acceptor_hostname = TRUE; +- ctx->dns_canonicalize_hostname = FALSE; ++ ctx->dns_canonicalize_hostname = CANONHOST_FALSE; + free(ctx->plugin_base_dir); + check((ctx->plugin_base_dir = strdup("/a/b/c/d")) != NULL); + +diff --git a/src/lib/krb5/os/os-proto.h b/src/lib/krb5/os/os-proto.h +index 634e82d70..066d30221 100644 +--- a/src/lib/krb5/os/os-proto.h ++++ b/src/lib/krb5/os/os-proto.h +@@ -83,6 +83,10 @@ struct sendto_callback_info { + void *data; + }; + ++krb5_error_code k5_expand_hostname(krb5_context context, const char *host, ++ krb5_boolean is_fallback, ++ char **canonhost_out); ++ + krb5_error_code k5_locate_server(krb5_context, const krb5_data *realm, + struct serverlist *serverlist, + enum locate_service_type svc, +diff --git a/src/lib/krb5/os/sn2princ.c b/src/lib/krb5/os/sn2princ.c +index 5932fd9b3..98d2600aa 100644 +--- a/src/lib/krb5/os/sn2princ.c ++++ b/src/lib/krb5/os/sn2princ.c +@@ -53,19 +53,23 @@ use_reverse_dns(krb5_context context) + return value; + } + +-krb5_error_code KRB5_CALLCONV +-krb5_expand_hostname(krb5_context context, const char *host, +- char **canonhost_out) ++krb5_error_code ++k5_expand_hostname(krb5_context context, const char *host, ++ krb5_boolean is_fallback, char **canonhost_out) + { + struct addrinfo *ai = NULL, hint; + char namebuf[NI_MAXHOST], *copy, *p; + int err; + const char *canonhost; ++ krb5_boolean use_dns; + + *canonhost_out = NULL; + + canonhost = host; +- if (context->dns_canonicalize_hostname) { ++ use_dns = (context->dns_canonicalize_hostname == CANONHOST_TRUE || ++ (is_fallback && ++ context->dns_canonicalize_hostname == CANONHOST_FALLBACK)); ++ if (use_dns) { + /* Try a forward lookup of the hostname. */ + memset(&hint, 0, sizeof(hint)); + hint.ai_flags = AI_CANONNAME; +@@ -112,6 +116,13 @@ cleanup: + return (*canonhost_out == NULL) ? ENOMEM : 0; + } + ++krb5_error_code KRB5_CALLCONV ++krb5_expand_hostname(krb5_context context, const char *host, ++ char **canonhost_out) ++{ ++ return k5_expand_hostname(context, host, FALSE, canonhost_out); ++} ++ + /* If hostname appears to have a :port or :instance trailer (used in MSSQLSvc + * principals), return a pointer to the separator. Otherwise return NULL. */ + static const char * +diff --git a/src/tests/gcred.c b/src/tests/gcred.c +index b14e4fc9a..cac524c51 100644 +--- a/src/tests/gcred.c ++++ b/src/tests/gcred.c +@@ -66,6 +66,7 @@ main(int argc, char **argv) + krb5_principal client, server; + krb5_ccache ccache; + krb5_creds in_creds, *creds; ++ krb5_ticket *ticket; + krb5_flags options = 0; + char *name; + int c; +@@ -102,9 +103,11 @@ main(int argc, char **argv) + in_creds.client = client; + in_creds.server = server; + check(krb5_get_credentials(ctx, options, ccache, &in_creds, &creds)); +- check(krb5_unparse_name(ctx, creds->server, &name)); ++ check(krb5_decode_ticket(&creds->ticket, &ticket)); ++ check(krb5_unparse_name(ctx, ticket->server, &name)); + printf("%s\n", name); + ++ krb5_free_ticket(ctx, ticket); + krb5_free_unparsed_name(ctx, name); + krb5_free_creds(ctx, creds); + krb5_free_principal(ctx, client); +diff --git a/src/tests/t_sn2princ.py b/src/tests/t_sn2princ.py +index 1ffda51f4..fe435a2d5 100755 +--- a/src/tests/t_sn2princ.py ++++ b/src/tests/t_sn2princ.py +@@ -7,10 +7,15 @@ conf = {'domain_realm': {'kerberos.org': 'R1', + 'mit.edu': 'R3'}} + no_rdns_conf = {'libdefaults': {'rdns': 'false'}} + no_canon_conf = {'libdefaults': {'dns_canonicalize_hostname': 'false'}} ++fallback_canon_conf = {'libdefaults': ++ {'rdns': 'false', ++ 'dns_canonicalize_hostname': 'fallback'}} + +-realm = K5Realm(create_kdb=False, krb5_conf=conf) ++realm = K5Realm(realm='R1', create_host=False, krb5_conf=conf) + no_rdns = realm.special_env('no_rdns', False, krb5_conf=no_rdns_conf) + no_canon = realm.special_env('no_canon', False, krb5_conf=no_canon_conf) ++fallback_canon = realm.special_env('fallback_canon', False, ++ krb5_conf=fallback_canon_conf) + + def testbase(host, nametype, princhost, princrealm, env=None): + # Run the sn2princ harness with a specified host and name type and +@@ -37,6 +42,10 @@ def testu(host, princhost, princrealm): + # Test with the unknown name type. + testbase(host, 'unknown', princhost, princrealm) + ++def testfc(host, princhost, princrealm): ++ # Test with the host-based name type with canonicalization fallback. ++ testbase(host, 'srv-hst', princhost, princrealm, env=fallback_canon) ++ + # With the unknown principal type, we do not canonicalize or downcase, + # but we do remove a trailing period and look up the realm. + mark('unknown type') +@@ -71,6 +80,29 @@ if offline: + oname = 'ptr-mismatch.kerberos.org' + fname = 'www.kerberos.org' + ++# Test fallback canonicalization krb5_sname_to_principal() results ++# (same as dns_canonicalize_hostname=false). ++mark('dns_canonicalize_host=fallback') ++testfc(oname, oname, 'R1') ++ ++# Test fallback canonicalization in krb5_get_credentials(). ++oprinc = 'host/' + oname ++fprinc = 'host/' + fname ++shutil.copy(realm.ccache, realm.ccache + '.save') ++realm.addprinc(fprinc) ++# oprinc doesn't exist, so we get the canonicalized fprinc as a fallback. ++msgs = ('Falling back to canonicalized server hostname ' + fname,) ++realm.run(['./gcred', 'srv-hst', oprinc], env=fallback_canon, ++ expected_msg=fprinc, expected_trace=msgs) ++realm.addprinc(oprinc) ++# oprinc now exists, but we still get the fprinc ticket from the cache. ++realm.run(['./gcred', 'srv-hst', oprinc], env=fallback_canon, ++ expected_msg=fprinc) ++# Without the cached result, we sould get oprinc in preference to fprinc. ++os.rename(realm.ccache + '.save', realm.ccache) ++realm.run(['./gcred', 'srv-hst', oprinc], env=fallback_canon, ++ expected_msg=oprinc) ++ + # Verify forward resolution before testing for it. + try: + ai = socket.getaddrinfo(oname, None, 0, 0, 0, socket.AI_CANONNAME) diff --git a/Clear-forwardable-flag-instead-of-denying-request.patch b/Clear-forwardable-flag-instead-of-denying-request.patch new file mode 100644 index 0000000..4b29e0f --- /dev/null +++ b/Clear-forwardable-flag-instead-of-denying-request.patch @@ -0,0 +1,484 @@ +From 297ad5039231e655eaae7c142991326fd863e70a Mon Sep 17 00:00:00 2001 +From: Greg Hudson +Date: Thu, 15 Nov 2018 13:40:43 -0500 +Subject: [PATCH] Clear forwardable flag instead of denying request + +If the client requests a forwardable or proxiable ticket and the +option cannot be honored by policy, issue a non-forwardable or +non-proxiable ticket rather than denying the request. + +Add a test script for testing KDC request options and populate it with +tests for the forwardable and proxiable flags. + +ticket: 7871 +(cherry picked from commit 08e948cce2c79a3604066fcf7a64fc527456f83d) +--- + src/kdc/do_as_req.c | 19 ++------ + src/kdc/do_tgs_req.c | 56 ++++----------------- + src/kdc/kdc_util.c | 82 ++++++++++++++++++------------- + src/kdc/kdc_util.h | 9 ++-- + src/kdc/tgs_policy.c | 8 +-- + src/tests/Makefile.in | 1 + + src/tests/gcred.c | 28 ++++++++--- + src/tests/t_kdcoptions.py | 100 ++++++++++++++++++++++++++++++++++++++ + 8 files changed, 189 insertions(+), 114 deletions(-) + create mode 100644 src/tests/t_kdcoptions.py + +diff --git a/src/kdc/do_as_req.c b/src/kdc/do_as_req.c +index 588c1375a..8a96c12a9 100644 +--- a/src/kdc/do_as_req.c ++++ b/src/kdc/do_as_req.c +@@ -192,13 +192,6 @@ finish_process_as_req(struct as_req_state *state, krb5_error_code errcode) + + au_state->stage = ENCR_REP; + +- if ((errcode = validate_forwardable(state->request, *state->client, +- *state->server, state->kdc_time, +- &state->status))) { +- errcode += ERROR_TABLE_BASE_krb5; +- goto egress; +- } +- + errcode = check_indicators(kdc_context, state->server, + state->auth_indicators); + if (errcode) { +@@ -708,12 +701,11 @@ process_as_req(krb5_kdc_req *request, krb5_data *req_pkt, + } + + /* Copy options that request the corresponding ticket flags. */ +- state->enc_tkt_reply.flags = OPTS2FLAGS(state->request->kdc_options); ++ state->enc_tkt_reply.flags = get_ticket_flags(state->request->kdc_options, ++ state->client, state->server, ++ NULL); + state->enc_tkt_reply.times.authtime = state->authtime; + +- setflag(state->enc_tkt_reply.flags, TKT_FLG_INITIAL); +- setflag(state->enc_tkt_reply.flags, TKT_FLG_ENC_PA_REP); +- + /* + * It should be noted that local policy may affect the + * processing of any of these flags. For example, some +@@ -732,10 +724,9 @@ process_as_req(krb5_kdc_req *request, krb5_data *req_pkt, + state->enc_tkt_reply.transited.tr_type = KRB5_DOMAIN_X500_COMPRESS; + state->enc_tkt_reply.transited.tr_contents = empty_string; + +- if (isflagset(state->request->kdc_options, KDC_OPT_POSTDATED)) { +- setflag(state->enc_tkt_reply.flags, TKT_FLG_INVALID); ++ if (isflagset(state->request->kdc_options, KDC_OPT_POSTDATED)) + state->enc_tkt_reply.times.starttime = state->request->from; +- } else ++ else + state->enc_tkt_reply.times.starttime = state->kdc_time; + + kdc_get_ticket_endtime(kdc_active_realm, +diff --git a/src/kdc/do_tgs_req.c b/src/kdc/do_tgs_req.c +index 587342a6c..1da099318 100644 +--- a/src/kdc/do_tgs_req.c ++++ b/src/kdc/do_tgs_req.c +@@ -378,15 +378,16 @@ process_tgs_req(krb5_kdc_req *request, krb5_data *pkt, + else + ticket_reply.server = request->server; /* XXX careful for realm... */ + +- enc_tkt_reply.flags = OPTS2FLAGS(request->kdc_options); +- enc_tkt_reply.flags |= COPY_TKT_FLAGS(header_enc_tkt->flags); ++ enc_tkt_reply.flags = get_ticket_flags(request->kdc_options, client, ++ server, header_enc_tkt); + enc_tkt_reply.times.starttime = 0; + +- if (isflagset(server->attributes, KRB5_KDB_OK_AS_DELEGATE)) +- setflag(enc_tkt_reply.flags, TKT_FLG_OK_AS_DELEGATE); +- +- /* Indicate support for encrypted padata (RFC 6806). */ +- setflag(enc_tkt_reply.flags, TKT_FLG_ENC_PA_REP); ++ /* OK_TO_AUTH_AS_DELEGATE must be set on the service requesting S4U2Self ++ * for forwardable tickets to be issued. */ ++ if (isflagset(c_flags, KRB5_KDB_FLAG_PROTOCOL_TRANSITION) && ++ !is_referral && ++ !isflagset(server->attributes, KRB5_KDB_OK_TO_AUTH_AS_DELEGATE)) ++ clear(enc_tkt_reply.flags, TKT_FLG_FORWARDABLE); + + /* don't use new addresses unless forwarded, see below */ + +@@ -401,37 +402,6 @@ process_tgs_req(krb5_kdc_req *request, krb5_data *pkt, + * realms may refuse to issue renewable tickets + */ + +- if (isflagset(request->kdc_options, KDC_OPT_FORWARDABLE)) { +- +- if (isflagset(c_flags, KRB5_KDB_FLAG_PROTOCOL_TRANSITION)) { +- /* +- * If S4U2Self principal is not forwardable, then mark ticket as +- * unforwardable. This behaviour matches Windows, but it is +- * different to the MIT AS-REQ path, which returns an error +- * (KDC_ERR_POLICY) if forwardable tickets cannot be issued. +- * +- * Consider this block the S4U2Self equivalent to +- * validate_forwardable(). +- */ +- if (client != NULL && +- isflagset(client->attributes, KRB5_KDB_DISALLOW_FORWARDABLE)) +- clear(enc_tkt_reply.flags, TKT_FLG_FORWARDABLE); +- /* +- * Forwardable flag is propagated along referral path. +- */ +- else if (!isflagset(header_enc_tkt->flags, TKT_FLG_FORWARDABLE)) +- clear(enc_tkt_reply.flags, TKT_FLG_FORWARDABLE); +- /* +- * OK_TO_AUTH_AS_DELEGATE must be set on the service requesting +- * S4U2Self in order for forwardable tickets to be returned. +- */ +- else if (!is_referral && +- !isflagset(server->attributes, +- KRB5_KDB_OK_TO_AUTH_AS_DELEGATE)) +- clear(enc_tkt_reply.flags, TKT_FLG_FORWARDABLE); +- } +- } +- + if (isflagset(request->kdc_options, KDC_OPT_FORWARDED) || + isflagset(request->kdc_options, KDC_OPT_PROXY)) { + +@@ -440,16 +410,10 @@ process_tgs_req(krb5_kdc_req *request, krb5_data *pkt, + enc_tkt_reply.caddrs = request->addresses; + reply_encpart.caddrs = request->addresses; + } +- /* We don't currently handle issuing anonymous tickets based on +- * non-anonymous ones, so just ignore the option. */ +- if (isflagset(request->kdc_options, KDC_OPT_REQUEST_ANONYMOUS) && +- !isflagset(header_enc_tkt->flags, TKT_FLG_ANONYMOUS)) +- clear(enc_tkt_reply.flags, TKT_FLG_ANONYMOUS); + +- if (isflagset(request->kdc_options, KDC_OPT_POSTDATED)) { +- setflag(enc_tkt_reply.flags, TKT_FLG_INVALID); ++ if (isflagset(request->kdc_options, KDC_OPT_POSTDATED)) + enc_tkt_reply.times.starttime = request->from; +- } else ++ else + enc_tkt_reply.times.starttime = kdc_time; + + if (isflagset(request->kdc_options, KDC_OPT_VALIDATE)) { +diff --git a/src/kdc/kdc_util.c b/src/kdc/kdc_util.c +index 96c88edc1..f2741090e 100644 +--- a/src/kdc/kdc_util.c ++++ b/src/kdc/kdc_util.c +@@ -697,29 +697,6 @@ validate_as_request(kdc_realm_t *kdc_active_realm, + return(KDC_ERR_CANNOT_POSTDATE); + } + +- /* +- * A Windows KDC will return KDC_ERR_PREAUTH_REQUIRED instead of +- * KDC_ERR_POLICY in the following case: +- * +- * - KDC_OPT_FORWARDABLE is set in KDCOptions but local +- * policy has KRB5_KDB_DISALLOW_FORWARDABLE set for the +- * client, and; +- * - KRB5_KDB_REQUIRES_PRE_AUTH is set for the client but +- * preauthentication data is absent in the request. +- * +- * Hence, this check most be done after the check for preauth +- * data, and is now performed by validate_forwardable() (the +- * contents of which were previously below). +- */ +- +- /* Client and server must allow proxiable tickets */ +- if (isflagset(request->kdc_options, KDC_OPT_PROXIABLE) && +- (isflagset(client.attributes, KRB5_KDB_DISALLOW_PROXIABLE) || +- isflagset(server.attributes, KRB5_KDB_DISALLOW_PROXIABLE))) { +- *status = "PROXIABLE NOT ALLOWED"; +- return(KDC_ERR_POLICY); +- } +- + /* Check to see if client is locked out */ + if (isflagset(client.attributes, KRB5_KDB_DISALLOW_ALL_TIX)) { + *status = "CLIENT LOCKED OUT"; +@@ -752,19 +729,54 @@ validate_as_request(kdc_realm_t *kdc_active_realm, + return 0; + } + +-int +-validate_forwardable(krb5_kdc_req *request, krb5_db_entry client, +- krb5_db_entry server, krb5_timestamp kdc_time, +- const char **status) ++/* ++ * Compute ticket flags based on the request, the client and server DB entry ++ * (which may prohibit forwardable or proxiable tickets), and the header ++ * ticket. client may be NULL for a TGS request (although it may be set, such ++ * as for an S4U2Self request). header_enc may be NULL for an AS request. ++ */ ++krb5_flags ++get_ticket_flags(krb5_flags reqflags, krb5_db_entry *client, ++ krb5_db_entry *server, krb5_enc_tkt_part *header_enc) + { +- *status = NULL; +- if (isflagset(request->kdc_options, KDC_OPT_FORWARDABLE) && +- (isflagset(client.attributes, KRB5_KDB_DISALLOW_FORWARDABLE) || +- isflagset(server.attributes, KRB5_KDB_DISALLOW_FORWARDABLE))) { +- *status = "FORWARDABLE NOT ALLOWED"; +- return(KDC_ERR_POLICY); +- } else +- return 0; ++ krb5_flags flags; ++ ++ /* Indicate support for encrypted padata (RFC 6806), and set flags based on ++ * request options and the header ticket. */ ++ flags = OPTS2FLAGS(reqflags) | TKT_FLG_ENC_PA_REP; ++ if (reqflags & KDC_OPT_POSTDATED) ++ flags |= TKT_FLG_INVALID; ++ if (header_enc != NULL) ++ flags |= COPY_TKT_FLAGS(header_enc->flags); ++ if (header_enc == NULL) ++ flags |= TKT_FLG_INITIAL; ++ ++ /* For TGS requests, indicate if the service is marked ok-as-delegate. */ ++ if (header_enc != NULL && (server->attributes & KRB5_KDB_OK_AS_DELEGATE)) ++ flags |= TKT_FLG_OK_AS_DELEGATE; ++ ++ /* Unset PROXIABLE if it is disallowed. */ ++ if (client != NULL && (client->attributes & KRB5_KDB_DISALLOW_PROXIABLE)) ++ flags &= ~TKT_FLG_PROXIABLE; ++ if (server->attributes & KRB5_KDB_DISALLOW_PROXIABLE) ++ flags &= ~TKT_FLG_PROXIABLE; ++ if (header_enc != NULL && !(header_enc->flags & TKT_FLG_PROXIABLE)) ++ flags &= ~TKT_FLG_PROXIABLE; ++ ++ /* Unset FORWARDABLE if it is disallowed. */ ++ if (client != NULL && (client->attributes & KRB5_KDB_DISALLOW_FORWARDABLE)) ++ flags &= ~TKT_FLG_FORWARDABLE; ++ if (server->attributes & KRB5_KDB_DISALLOW_FORWARDABLE) ++ flags &= ~TKT_FLG_FORWARDABLE; ++ if (header_enc != NULL && !(header_enc->flags & TKT_FLG_FORWARDABLE)) ++ flags &= ~TKT_FLG_FORWARDABLE; ++ ++ /* We don't currently handle issuing anonymous tickets based on ++ * non-anonymous ones. */ ++ if (header_enc != NULL && !(header_enc->flags & TKT_FLG_ANONYMOUS)) ++ flags &= ~TKT_FLG_ANONYMOUS; ++ ++ return flags; + } + + /* Return KRB5KDC_ERR_POLICY if indicators does not contain the required auth +diff --git a/src/kdc/kdc_util.h b/src/kdc/kdc_util.h +index 25077cbf5..1314bdd58 100644 +--- a/src/kdc/kdc_util.h ++++ b/src/kdc/kdc_util.h +@@ -85,16 +85,15 @@ validate_as_request (kdc_realm_t *, krb5_kdc_req *, krb5_db_entry, + krb5_db_entry, krb5_timestamp, + const char **, krb5_pa_data ***); + +-int +-validate_forwardable(krb5_kdc_req *, krb5_db_entry, +- krb5_db_entry, krb5_timestamp, +- const char **); +- + int + validate_tgs_request (kdc_realm_t *, krb5_kdc_req *, krb5_db_entry, + krb5_ticket *, krb5_timestamp, + const char **, krb5_pa_data ***); + ++krb5_flags ++get_ticket_flags(krb5_flags reqflags, krb5_db_entry *client, ++ krb5_db_entry *server, krb5_enc_tkt_part *header_enc); ++ + krb5_error_code + check_indicators(krb5_context context, krb5_db_entry *server, + krb5_data *const *indicators); +diff --git a/src/kdc/tgs_policy.c b/src/kdc/tgs_policy.c +index 907fcd330..554345ba5 100644 +--- a/src/kdc/tgs_policy.c ++++ b/src/kdc/tgs_policy.c +@@ -63,9 +63,9 @@ static check_tgs_svc_pol_fn * const svc_pol_fns[] = { + }; + + static const struct tgsflagrule tgsflagrules[] = { +- { (KDC_OPT_FORWARDED | KDC_OPT_FORWARDABLE), TKT_FLG_FORWARDABLE, ++ { KDC_OPT_FORWARDED, TKT_FLG_FORWARDABLE, + "TGT NOT FORWARDABLE", KDC_ERR_BADOPTION }, +- { (KDC_OPT_PROXY | KDC_OPT_PROXIABLE), TKT_FLG_PROXIABLE, ++ { KDC_OPT_PROXY, TKT_FLG_PROXIABLE, + "TGT NOT PROXIABLE", KDC_ERR_BADOPTION }, + { (KDC_OPT_ALLOW_POSTDATE | KDC_OPT_POSTDATED), TKT_FLG_MAY_POSTDATE, + "TGT NOT POSTDATABLE", KDC_ERR_BADOPTION }, +@@ -98,12 +98,8 @@ check_tgs_opts(krb5_kdc_req *req, krb5_ticket *tkt, const char **status) + } + + static const struct tgsflagrule svcdenyrules[] = { +- { KDC_OPT_FORWARDABLE, KRB5_KDB_DISALLOW_FORWARDABLE, +- "NON-FORWARDABLE TICKET", KDC_ERR_POLICY }, + { KDC_OPT_RENEWABLE, KRB5_KDB_DISALLOW_RENEWABLE, + "NON-RENEWABLE TICKET", KDC_ERR_POLICY }, +- { KDC_OPT_PROXIABLE, KRB5_KDB_DISALLOW_PROXIABLE, +- "NON-PROXIABLE TICKET", KDC_ERR_POLICY }, + { KDC_OPT_ALLOW_POSTDATE, KRB5_KDB_DISALLOW_POSTDATED, + "NON-POSTDATABLE TICKET", KDC_ERR_CANNOT_POSTDATE }, + { KDC_OPT_ENC_TKT_IN_SKEY, KRB5_KDB_DISALLOW_DUP_SKEY, +diff --git a/src/tests/Makefile.in b/src/tests/Makefile.in +index c96c5d6b7..d2a37c616 100644 +--- a/src/tests/Makefile.in ++++ b/src/tests/Makefile.in +@@ -171,6 +171,7 @@ check-pytests: unlockiter + $(RUNPYTEST) $(srcdir)/t_y2038.py $(PYTESTFLAGS) + $(RUNPYTEST) $(srcdir)/t_kdcpolicy.py $(PYTESTFLAGS) + $(RUNPYTEST) $(srcdir)/t_u2u.py $(PYTESTFLAGS) ++ $(RUNPYTEST) $(srcdir)/t_kdcoptions.py $(PYTESTFLAGS) + + clean: + $(RM) adata etinfo forward gcred hist hooks hrealm icinterleave icred +diff --git a/src/tests/gcred.c b/src/tests/gcred.c +index cb0ae6af5..b14e4fc9a 100644 +--- a/src/tests/gcred.c ++++ b/src/tests/gcred.c +@@ -66,20 +66,32 @@ main(int argc, char **argv) + krb5_principal client, server; + krb5_ccache ccache; + krb5_creds in_creds, *creds; ++ krb5_flags options = 0; + char *name; ++ int c; + + check(krb5_init_context(&ctx)); + +- /* Parse arguments. */ +- assert(argc == 3); +- check(krb5_parse_name(ctx, argv[2], &server)); +- if (strcmp(argv[1], "unknown") == 0) ++ while ((c = getopt(argc, argv, "f")) != -1) { ++ switch (c) { ++ case 'f': ++ options |= KRB5_GC_FORWARDABLE; ++ break; ++ default: ++ abort(); ++ } ++ } ++ argc -= optind; ++ argv += optind; ++ assert(argc == 2); ++ check(krb5_parse_name(ctx, argv[1], &server)); ++ if (strcmp(argv[0], "unknown") == 0) + server->type = KRB5_NT_UNKNOWN; +- else if (strcmp(argv[1], "principal") == 0) ++ else if (strcmp(argv[0], "principal") == 0) + server->type = KRB5_NT_PRINCIPAL; +- else if (strcmp(argv[1], "srv-inst") == 0) ++ else if (strcmp(argv[0], "srv-inst") == 0) + server->type = KRB5_NT_SRV_INST; +- else if (strcmp(argv[1], "srv-hst") == 0) ++ else if (strcmp(argv[0], "srv-hst") == 0) + server->type = KRB5_NT_SRV_HST; + else + abort(); +@@ -89,7 +101,7 @@ main(int argc, char **argv) + memset(&in_creds, 0, sizeof(in_creds)); + in_creds.client = client; + in_creds.server = server; +- check(krb5_get_credentials(ctx, 0, ccache, &in_creds, &creds)); ++ check(krb5_get_credentials(ctx, options, ccache, &in_creds, &creds)); + check(krb5_unparse_name(ctx, creds->server, &name)); + printf("%s\n", name); + +diff --git a/src/tests/t_kdcoptions.py b/src/tests/t_kdcoptions.py +new file mode 100644 +index 000000000..7ec57508c +--- /dev/null ++++ b/src/tests/t_kdcoptions.py +@@ -0,0 +1,100 @@ ++from k5test import * ++import re ++ ++# KDC option test coverage notes: ++# ++# FORWARDABLE here ++# FORWARDED no test ++# PROXIABLE here ++# PROXY no test ++# ALLOW_POSTDATE no test ++# POSTDATED no test ++# RENEWABLE t_renew.py ++# CNAME_IN_ADDL_TKT gssapi/t_s4u.py ++# CANONICALIZE t_kdb.py and various other tests ++# REQUEST_ANONYMOUS t_pkinit.py ++# DISABLE_TRANSITED_CHECK no test ++# RENEWABLE_OK t_renew.py ++# ENC_TKT_IN_SKEY t_u2u.py ++# RENEW t_renew.py ++# VALIDATE no test ++ ++# Run klist -f and return the flags on the ticket for svcprinc. ++def get_flags(realm, svcprinc): ++ grab_flags = False ++ for line in realm.run([klist, '-f']).splitlines(): ++ if grab_flags: ++ return re.findall(r'Flags: ([a-zA-Z]*)', line)[0] ++ grab_flags = line.endswith(svcprinc) ++ ++ ++# Get the flags on the ticket for svcprinc, and check for an expected ++# element and an expected-absent element, either of which can be None. ++def check_flags(realm, svcprinc, expected_flag, expected_noflag): ++ flags = get_flags(realm, svcprinc) ++ if expected_flag is not None and not expected_flag in flags: ++ fail('expected flag ' + expected_flag) ++ if expected_noflag is not None and expected_noflag in flags: ++ fail('did not expect flag ' + expected_noflag) ++ ++ ++# Run kinit with the given flags, and check the flags on the resulting ++# TGT. ++def kinit_check_flags(realm, flags, expected_flag, expected_noflag): ++ realm.kinit(realm.user_princ, password('user'), flags) ++ check_flags(realm, realm.krbtgt_princ, expected_flag, expected_noflag) ++ ++ ++# Run kinit with kflags. Then get credentials for the host principal ++# with gflags, and check the flags on the resulting ticket. ++def gcred_check_flags(realm, kflags, gflags, expected_flag, expected_noflag): ++ realm.kinit(realm.user_princ, password('user'), kflags) ++ realm.run(['./gcred'] + gflags + ['unknown', realm.host_princ]) ++ check_flags(realm, realm.host_princ, expected_flag, expected_noflag) ++ ++ ++realm = K5Realm() ++ ++mark('proxiable (AS)') ++kinit_check_flags(realm, [], None, 'P') ++kinit_check_flags(realm, ['-p'], 'P', None) ++realm.run([kadminl, 'modprinc', '-allow_proxiable', realm.user_princ]) ++kinit_check_flags(realm, ['-p'], None, 'P') ++realm.run([kadminl, 'modprinc', '+allow_proxiable', realm.user_princ]) ++realm.run([kadminl, 'modprinc', '-allow_proxiable', realm.krbtgt_princ]) ++kinit_check_flags(realm, ['-p'], None, 'P') ++realm.run([kadminl, 'modprinc', '+allow_proxiable', realm.krbtgt_princ]) ++ ++mark('proxiable (TGS)') ++gcred_check_flags(realm, [], [], None, 'P') ++gcred_check_flags(realm, ['-p'], [], 'P', None) ++ ++# Not tested: PROXIABLE option set with a non-proxiable TGT (because ++# there is no krb5_get_credentials() flag to request this; would ++# expect a non-proxiable ticket). ++ ++# Not tested: proxiable TGT but PROXIABLE flag not set (because we ++# internally set the PROXIABLE option when using a proxiable TGT; ++# would expect a non-proxiable ticket). ++ ++mark('forwardable (AS)') ++kinit_check_flags(realm, [], None, 'F') ++kinit_check_flags(realm, ['-f'], 'F', None) ++realm.run([kadminl, 'modprinc', '-allow_forwardable', realm.user_princ]) ++kinit_check_flags(realm, ['-f'], None, 'F') ++realm.run([kadminl, 'modprinc', '+allow_forwardable', realm.user_princ]) ++realm.run([kadminl, 'modprinc', '-allow_forwardable', realm.krbtgt_princ]) ++kinit_check_flags(realm, ['-f'], None, 'F') ++realm.run([kadminl, 'modprinc', '+allow_forwardable', realm.krbtgt_princ]) ++ ++mark('forwardable (TGS)') ++realm.kinit(realm.user_princ, password('user')) ++gcred_check_flags(realm, [], [], None, 'F') ++gcred_check_flags(realm, [], ['-f'], None, 'F') ++gcred_check_flags(realm, ['-f'], [], 'F', None) ++ ++# Not tested: forwardable TGT but FORWARDABLE flag not set (because we ++# internally set the FORWARDABLE option when using a forwardable TGT; ++# would expect a non-proxiable ticket). ++ ++success('KDC option tests') diff --git a/krb5.spec b/krb5.spec index b4f962b..99818a0 100644 --- a/krb5.spec +++ b/krb5.spec @@ -18,7 +18,7 @@ Summary: The Kerberos network authentication system Name: krb5 Version: 1.17 # for prerelease, should be e.g., 0.% {prerelease}.1% { ?dist } (without spaces) -Release: 13%{?dist} +Release: 14%{?dist} # lookaside-cached sources; two downloads and a build artifact Source0: https://web.mit.edu/kerberos/dist/krb5/1.16/krb5-%{version}%{prerelease}.tar.gz @@ -85,6 +85,8 @@ Patch112: Remove-confvalidator-utility.patch Patch113: Remove-ovsec_adm_export-dump-format-support.patch Patch114: Fix-potential-close-1-in-cc_file.c.patch Patch115: Check-more-errors-in-OpenSSL-crypto-backend.patch +Patch116: Clear-forwardable-flag-instead-of-denying-request.patch +Patch117: Add-dns_canonicalize_hostname-fallback-support.patch License: MIT URL: http://web.mit.edu/kerberos/www/ @@ -721,6 +723,9 @@ exit 0 %{_libdir}/libkadm5srv_mit.so.* %changelog +* Wed Apr 24 2019 Robbie Harwood - 1.17-14 +- Add dns_canonicalize_hostname=fallback support + * Wed Apr 24 2019 Robbie Harwood - 1.17-13 - Check more errors in OpenSSL crypto backend