From 25860eac35980ab0d8d786fe1d06ced26a04d2db Mon Sep 17 00:00:00 2001 From: Sam Hartman Date: Thu, 21 Sep 2006 01:48:50 +0000 Subject: [PATCH] Set the canonicalize flag in TGS requests and accept cross-realm referral tickets. We do not yet accept tickets in which the server name changes. * krb5_sname_to_principal: If there is no domain realm mapping return null realm *krb5_get_cred_via_tkt: New behavior as described below 1) the referrals case: - check for TGT for initial realm - if a remote realm was specified (which must have happened via a domain_realm mapping), obtain a TGT for it the standard way and start with that. - use client realm for server if not specified - iterate through this loop: - request ticket with referrals turned on - if that fails: - if this was the first request, punt to non-referrals case - otherwise, retry once without referrals turned on then terminate either way - if it works, either use the service ticket or follow the referral path - if loop count exceeded, hardfail 2) the nonreferrals case - this is mostly the old walk_realm_tree TGT-finding (which allows limited shortcut referrals per 4120) followed by a standard tgs-req. - originally requested principal is used for this, although if we were handed something without a realm, determine a fallback realm based on DNS TXT records or a truncation of the domain name. ticket: 2652 Owner: amb git-svn-id: svn://anonsvn.mit.edu/krb5/trunk@18598 dc483132-0cff-0310-8789-dd5450dbe970 --- src/appl/telnet/libtelnet/kerberos5.c | 2 + src/include/k5-int.h | 17 ++ src/include/krb5/krb5.hin | 22 +- src/lib/krb5/krb/copy_princ.c | 26 ++- src/lib/krb5/krb/gc_frm_kdc.c | 292 +++++++++++++++++++++++++- src/lib/krb5/krb/gc_via_tkt.c | 45 +++- src/lib/krb5/krb/parse.c | 10 +- src/lib/krb5/krb/princ_comp.c | 20 ++ src/lib/krb5/krb/walk_rtree.c | 32 ++- src/lib/krb5/libkrb5.exports | 1 + src/lib/krb5/os/hst_realm.c | 277 ++++++++++++++++-------- src/lib/krb5/os/sn2princ.c | 24 +++ src/lib/krb5_32.def | 1 + 13 files changed, 647 insertions(+), 122 deletions(-) diff --git a/src/appl/telnet/libtelnet/kerberos5.c b/src/appl/telnet/libtelnet/kerberos5.c index 958a4ce4a..9761f6dd9 100644 --- a/src/appl/telnet/libtelnet/kerberos5.c +++ b/src/appl/telnet/libtelnet/kerberos5.c @@ -249,6 +249,7 @@ kerberos5_send(ap) } memset((char *)&creds, 0, sizeof(creds)); + printf("calling krb5_sname_to_principal\n"); if ((r = krb5_sname_to_principal(telnet_context, RemoteHostName, "host", KRB5_NT_SRV_HST, &creds.server))) { @@ -256,6 +257,7 @@ kerberos5_send(ap) printf("telnet: Kerberos V5: error while constructing service name: %s\r\n", error_message(r)); return(0); } + printf("done calling krb5_sname_to_principal\n"); if (telnet_krb5_realm != NULL) { krb5_data rdata; diff --git a/src/include/k5-int.h b/src/include/k5-int.h index a55fea7a4..46f4e03e3 100644 --- a/src/include/k5-int.h +++ b/src/include/k5-int.h @@ -1893,4 +1893,21 @@ extern int krb5int_prng_init(void); #define krb5_copy_error_state(CTX, OCTX) \ krb5int_set_error(&(CTX)->errinfo, (OCTX)->errinfo.code, "%s", (OCTX)->errinfo.msg) +/* + * Referral definitions, debugging hooks, and subfunctions. + */ +#define KRB5_REFERRAL_MAXHOPS 5 +/* #define DEBUG_REFERRALS */ + +#ifdef DEBUG_REFERRALS +void krb5int_dbgref_dump_principal(char *, krb5_principal); +#endif + +/* Common hostname-parsing code. */ +krb5_error_code KRB5_CALLCONV krb5int_clean_hostname + (krb5_context, + const char *, + char *, + size_t); + #endif /* _KRB5_INT_H */ diff --git a/src/include/krb5/krb5.hin b/src/include/krb5/krb5.hin index 23f93c7a9..418a21c0a 100644 --- a/src/include/krb5/krb5.hin +++ b/src/include/krb5/krb5.hin @@ -259,6 +259,16 @@ typedef const krb5_principal_data *krb5_const_principal; ? (princ)->data + (i) \ : NULL) +/* + * Constants for realm referrals. + */ +#define KRB5_REFERRAL_REALM "" + +/* + * Referral-specific functions. + */ +krb5_boolean KRB5_CALLCONV krb5_is_referral_realm(krb5_data *); + /* * end "base-defs.h" */ @@ -554,6 +564,9 @@ krb5_error_code KRB5_CALLCONV #define KRB5_KEYUSAGE_PA_SAM_CHALLENGE_TRACKID 26 #define KRB5_KEYUSAGE_PA_SAM_RESPONSE 27 +/* Defined in KDC referrals draft */ +#define KRB5_KEYUSAGE_PA_REFERRAL 26 /* XXX note conflict with above */ + krb5_boolean KRB5_CALLCONV krb5_c_valid_enctype (krb5_enctype ktype); krb5_boolean KRB5_CALLCONV krb5_c_valid_cksumtype @@ -690,7 +703,7 @@ krb5_error_code krb5_decrypt_data /* #define KDC_OPT_RESERVED 0x00080000 */ /* #define KDC_OPT_RESERVED 0x00040000 */ #define KDC_OPT_REQUEST_ANONYMOUS 0x00020000 -/* #define KDC_OPT_RESERVED 0x00010000 */ +#define KDC_OPT_CANONICALIZE 0x00010000 /* #define KDC_OPT_RESERVED 0x00008000 */ /* #define KDC_OPT_RESERVED 0x00004000 */ /* #define KDC_OPT_RESERVED 0x00002000 */ @@ -873,7 +886,8 @@ krb5_error_code krb5_decrypt_data #define KRB5_PADATA_SAM_RESPONSE 13 /* draft challenge system response */ #define KRB5_PADATA_PK_AS_REQ 14 /* PKINIT */ #define KRB5_PADATA_PK_AS_REP 15 /* PKINIT */ -#define KRB5_PADATA_ETYPE_INFO2 19 +#define KRB5_PADATA_ETYPE_INFO2 19 +#define KRB5_PADATA_REFERRAL 25 /* draft referral system */ #define KRB5_PADATA_SAM_CHALLENGE_2 30 /* draft challenge system, updated */ #define KRB5_PADATA_SAM_RESPONSE_2 31 /* draft challenge system, updated */ @@ -2242,6 +2256,10 @@ krb5_error_code KRB5_CALLCONV krb5_get_host_realm (krb5_context, const char *, char *** ); +krb5_error_code KRB5_CALLCONV krb5_get_fallback_host_realm + (krb5_context, + krb5_data *, + char *** ); krb5_error_code KRB5_CALLCONV krb5_free_host_realm (krb5_context, char * const * ); diff --git a/src/lib/krb5/krb/copy_princ.c b/src/lib/krb5/krb/copy_princ.c index d16c17b5c..85dc816ad 100644 --- a/src/lib/krb5/krb/copy_princ.c +++ b/src/lib/krb5/krb/copy_princ.c @@ -71,20 +71,18 @@ krb5_copy_principal(krb5_context context, krb5_const_principal inprinc, krb5_pri krb5_princ_component(context, tempprinc, i)->data = 0; } - if (tempprinc->realm.length) { - tempprinc->realm.data = - malloc(tempprinc->realm.length = inprinc->realm.length); - if (!tempprinc->realm.data) { - for (i = 0; i < nelems; i++) - free(krb5_princ_component(context, tempprinc, i)->data); - free(tempprinc->data); - free(tempprinc); - return ENOMEM; - } - memcpy(tempprinc->realm.data, inprinc->realm.data, - inprinc->realm.length); - } else - tempprinc->realm.data = 0; + tempprinc->realm.data = + malloc((tempprinc->realm.length = inprinc->realm.length) + 1); + if (!tempprinc->realm.data) { + for (i = 0; i < nelems; i++) + free(krb5_princ_component(context, tempprinc, i)->data); + free(tempprinc->data); + free(tempprinc); + return ENOMEM; + } + memcpy(tempprinc->realm.data, inprinc->realm.data, + inprinc->realm.length); + tempprinc->realm.data[tempprinc->realm.length] = 0; *outprinc = tempprinc; return 0; diff --git a/src/lib/krb5/krb/gc_frm_kdc.c b/src/lib/krb5/krb/gc_frm_kdc.c index 807b81a86..44e1b6c39 100644 --- a/src/lib/krb5/krb/gc_frm_kdc.c +++ b/src/lib/krb5/krb/gc_frm_kdc.c @@ -753,6 +753,11 @@ cleanup: * On success, OUT_CRED contains the desired credentials; the caller * must free them. * + * Beware memory management issues if you have modifications in mind. + * With the addition of referral support, it is now the case that *tgts, + * referral_tgts, tgtptr, referral_tgts, and *out_creds all may point to + * the same credential at different times. + * * Returns errors, system errors. */ @@ -761,35 +766,241 @@ krb5_get_cred_from_kdc_opt(krb5_context context, krb5_ccache ccache, krb5_creds *in_cred, krb5_creds **out_cred, krb5_creds ***tgts, int kdcopt) { - krb5_error_code retval; - krb5_principal client, server; - krb5_creds tgtq, cc_tgt, *tgtptr; + krb5_error_code retval, subretval; + krb5_principal client, server, supplied_server, out_supplied_server; + krb5_creds tgtq, cc_tgt, *tgtptr, *referral_tgts[KRB5_REFERRAL_MAXHOPS]; krb5_boolean old_use_conf_ktypes; + char **hrealms; + int referral_count, i; + /* + * Set up client and server pointers. Make a fresh and modifyable + * copy of the in_cred server and save the supplied version. + */ client = in_cred->client; - server = in_cred->server; + if ((retval=krb5_copy_principal(context, in_cred->server, &server))) + return retval; + /* We need a second copy for the output creds. */ + if ((retval = krb5_copy_principal(context, server, &out_supplied_server)) != 0 ) { + krb5_free_principal(context, server); + return retval; + } + supplied_server = in_cred->server; + in_cred->server=server; + + +#ifdef DEBUG_REFERRALS + krb5int_dbgref_dump_principal("gc_from_kdc initial client", client); + krb5int_dbgref_dump_principal("gc_from_kdc initial server", server); +#endif memset(&cc_tgt, 0, sizeof(cc_tgt)); memset(&tgtq, 0, sizeof(tgtq)); + memset(&referral_tgts, 0, sizeof(referral_tgts)); + tgtptr = NULL; *tgts = NULL; + *out_cred=NULL; old_use_conf_ktypes = context->use_conf_ktypes; + /* Copy client realm to server if no hint. */ + if (krb5_is_referral_realm(&server->realm)) { + /* Use the client realm. */ +#ifdef DEBUG_REFERRALS + printf("gc_from_kdc: no server realm supplied, using client realm.\n"); +#endif + krb5_free_data_contents(context, &server->realm); + if (!( server->realm.data = (char *)malloc(client->realm.length+1))) + return ENOMEM; + memcpy(server->realm.data, client->realm.data, client->realm.length); + server->realm.length = client->realm.length; + server->realm.data[server->realm.length] = 0; + } /* - * Get a TGT for the target realm. + * Retreive initial TGT to match the specified server, either for the + * local realm in the default (referral) case or for the remote + * realm if we're starting someplace non-local. + */ + retval = tgt_mcred(context, client, server, client, &tgtq); + if (retval) + goto cleanup; + + /* Fast path: Is it in the ccache? */ + context->use_conf_ktypes = 1; + retval = krb5_cc_retrieve_cred(context, ccache, RETR_FLAGS, + &tgtq, &cc_tgt); + if (!retval) { + tgtptr = &cc_tgt; + } else if (!HARD_CC_ERR(retval)) { +#ifdef DEBUG_REFERRALS + printf("gc_from_kdc: starting do_traversal to find initial TGT for referral\n"); +#endif + retval = do_traversal(context, ccache, client, server, + &cc_tgt, &tgtptr, tgts); + } + if (retval) { +#ifdef DEBUG_REFERRALS + printf("gc_from_kdc: failed to find initial TGT for referral\n"); +#endif + goto cleanup; + } + +#ifdef DEBUG_REFERRALS + krb5int_dbgref_dump_principal("gc_from_kdc: server as requested", supplied_server); +#endif + + /* + * Try requesting a service ticket from our local KDC with referrals + * turned on. If the first referral succeeds, follow a referral-only + * path, otherwise fall back to old-style assumptions. */ + for (referral_count=0;referral_countserver); + krb5int_dbgref_dump_principal("gc_from_kdc: referral loop: request is for", server); +#endif +#endif + retval = krb5_get_cred_via_tkt(context, tgtptr, + KDC_OPT_CANONICALIZE | + FLAGS2OPTS(tgtptr->ticket_flags) | + kdcopt | + (in_cred->second_ticket.length ? + KDC_OPT_ENC_TKT_IN_SKEY : 0), + tgtptr->addresses, in_cred, out_cred); + if (retval) { +#ifdef DEBUG_REFERRALS + printf("gc_from_kdc: referral TGS-REQ request failed: <%s>\n",error_message(retval)); +#endif + /* If we haven't gone anywhere yet, fail through to the + non-referral case. */ + if (referral_count==0) { +#ifdef DEBUG_REFERRALS + printf("gc_from_kdc: initial referral failed; punting to fallback.\n"); +#endif + break; + } + /* Otherwise, try the same query without canonicalization + set, and fail hard if that doesn't work. */ +#ifdef DEBUG_REFERRALS + printf("gc_from_kdc: referral #%d failed; retrying without option.\n", + referral_count+1); +#endif + retval = krb5_get_cred_via_tkt(context, tgtptr, + FLAGS2OPTS(tgtptr->ticket_flags) | + kdcopt | + (in_cred->second_ticket.length ? + KDC_OPT_ENC_TKT_IN_SKEY : 0), + tgtptr->addresses, + in_cred, out_cred); + /* Whether or not that succeeded, we're done. */ + goto cleanup; + } + else { + /* Referral request succeeded; let's see what it is. */ + if (krb5_principal_compare(context, in_cred->server, (*out_cred)->server)) { +#ifdef DEBUG_REFERRALS + printf("gc_from_kdc: request generated ticket for requested server principal\n"); + krb5int_dbgref_dump_principal("gc_from_kdc final referred reply",in_cred->server); +#endif + goto cleanup; + } + else { +#ifdef DEBUG_REFERRALS + printf("gc_from_kdc: request generated referral tgt\n"); + krb5int_dbgref_dump_principal("gc_from_kdc credential received", (*out_cred)->server); +#endif + /* Check for referral routing loop. */ + for (i=0;iserver); + krb5int_dbgref_dump_principal("gc_from_kdc: loop compare #2", referral_tgts[i]->server); +#endif +#endif + if (krb5_principal_compare(context, (*out_cred)->server, referral_tgts[i]->server)) { + fprintf(stderr, "krb5_get_cred_from_kdc_opt: referral routing loop afer %d hops\n",i); + retval=KRB5_KDC_UNREACH; + goto cleanup; + } + } + /* Point current tgt pointer at newly-received TGT. */ + if (tgtptr == &cc_tgt) + krb5_free_cred_contents(context, tgtptr); + tgtptr=*out_cred; + /* Save pointer to tgt in referral_tgts. */ + referral_tgts[referral_count]=*out_cred; + /* Copy krbtgt realm to server principal. */ + krb5_free_data_contents(context, &server->realm); + if ((retval=krb5int_copy_data_contents(context, &tgtptr->server->data[1], &server->realm))) + return retval; + /* Future work: rewrite server principal per any supplied padata. */ + } + } + } + +#ifdef DEBUG_REFERRALS + krb5int_dbgref_dump_principal("gc_from_kdc client at fallback", client); + krb5int_dbgref_dump_principal("gc_from_kdc server at fallback", server); +#endif + + /* + * At this point referrals have been tried and have failed. Go back + * to the server principal as originally issued and try the conventional path. + */ + + /* Referrals have failed. Look up fallback realm if not originally provided. */ + if (krb5_is_referral_realm(&supplied_server->realm)) { + if (server->length >= 2) { + retval=krb5_get_fallback_host_realm(context, &server->data[1], + &hrealms); + if (retval) goto cleanup; +#ifdef DEBUG_REFERRALS +#if 0 + printf("gc_from_kdc: using fallback realm of %s\n",hrealms[0]); +#endif +#endif + krb5_free_data_contents(context,&in_cred->server->realm); + server->realm.data=hrealms[0]; + server->realm.length=strlen(hrealms[0]); + free(hrealms); + } + else { + /* + * Problem case: Realm tagged for referral but apparently not + * in a / format that + * krb5_get_fallback_host_realm can deal with. + */ +#ifdef DEBUG_REFERRALS + printf("gc_from_kdc: referral specified but no fallback realm avaiable!\n"); +#endif + return KRB5_ERR_HOST_REALM_UNKNOWN; + } + } + +#ifdef DEBUG_REFERRALS + krb5int_dbgref_dump_principal("gc_from_kdc server at fallback after fallback rewrite", server); +#endif + + /* + * Get a TGT for the target realm. + */ + + krb5_free_cred_contents(context, &tgtq); retval = tgt_mcred(context, client, server, client, &tgtq); if (retval) goto cleanup; /* Fast path: Is it in the ccache? */ + /* Free tgtptr data if reused from above. */ + if (tgtptr == &cc_tgt) + krb5_free_cred_contents(context, tgtptr); context->use_conf_ktypes = 1; retval = krb5_cc_retrieve_cred(context, ccache, RETR_FLAGS, &tgtq, &cc_tgt); if (!retval) { tgtptr = &cc_tgt; } else if (!HARD_CC_ERR(retval)) { - /* Not in ccache, so traverse the transit path. */ retval = do_traversal(context, ccache, client, server, &cc_tgt, &tgtptr, tgts); } @@ -818,7 +1029,76 @@ cleanup: if (tgtptr == &cc_tgt) krb5_free_cred_contents(context, tgtptr); context->use_conf_ktypes = old_use_conf_ktypes; + /* Drop the original principal back into in_cred so that it's cached + in the expected format. */ +#ifdef DEBUG_REFERRALS + krb5int_dbgref_dump_principal("gc_from_kdc: final hacked server principal at cleanup",server); +#endif + krb5_free_principal(context, server); + in_cred->server = supplied_server; + if (*out_cred && !retval) { + /* Success: free server, swap supplied server back in. */ + krb5_free_principal (context, (*out_cred)->server); + (*out_cred)->server= out_supplied_server; + } + else { + /* + * Failure: free out_supplied_server. Don't free out_cred here + * since it's either null or a referral TGT that we free below, + * and we may need it to return. + */ + krb5_free_principal (context, out_supplied_server); + } +#ifdef DEBUG_REFERRALS + krb5int_dbgref_dump_principal("gc_from_kdc: final server after reversion",in_cred->server); +#endif + /* + * Deal with ccache TGT management: If tgts has been set from + * initial non-referral TGT discovery, leave it alone. Otherwise, if + * referral_tgts[0] exists return it as the only entry in tgts. + * (Further referrals are never cached, only the referral from the + * local KDC.) This is part of cleanup because useful received TGTs + * should be cached even if the main request resulted in failure. + */ + if (*tgts == NULL) { + if (referral_tgts[0]) { +#if 0 + /* + * This should possibly be a check on the candidate return + * credential against the cache, in the circumstance where we + * don't want to clutter the cache with near-duplicate + * credentials on subsequent iterations. For now, it is + * disabled. + */ + subretval=...?; + if (subretval) { +#endif + /* Allocate returnable TGT list. */ + if (!(*tgts=calloc(sizeof (krb5_creds *), 2))) + return ENOMEM; + subretval=krb5_copy_creds(context, referral_tgts[0], &((*tgts)[0])); + if(subretval) + return subretval; + (*tgts)[1]=NULL; +#ifdef DEBUG_REFERRALS + krb5int_dbgref_dump_principal("gc_from_kdc: returning referral TGT for ccache",(*tgts)[0]->server); +#endif +#if 0 + } +#endif + } + } + + /* Free referral TGTs list. */ + for (i=0;ienc_part2->server, - &(*ppcreds)->server))) + &(*ppcreds)->server))) goto cleanup; if ((retval = krb5_copy_keyblock_contents(context, @@ -107,6 +107,12 @@ krb5_get_cred_via_tkt (krb5_context context, krb5_creds *tkt, krb5_response tgsrep; krb5_enctype *enctypes = 0; +#ifdef DEBUG_REFERRALS + printf("krb5_get_cred_via_tkt starting; referral flag is %s\n", kdcoptions&KDC_OPT_CANONICALIZE?"on":"off"); + krb5int_dbgref_dump_principal("krb5_get_cred_via_tkt requested ticket", in_cred->server); + krb5int_dbgref_dump_principal("krb5_get_cred_via_tkt TGT in use", tkt->server); +#endif + /* tkt->client must be equal to in_cred->client */ if (!krb5_principal_compare(context, tkt->client, in_cred->client)) return KRB5_PRINC_NOMATCH; @@ -154,8 +160,13 @@ krb5_get_cred_via_tkt (krb5_context context, krb5_creds *tkt, tkt, &tgsrep); if (enctypes) free(enctypes); - if (retval) + if (retval) { +#ifdef DEBUG_REFERRALS + printf("krb5_get_cred_via_tkt ending early after send_tgs with: %s\n", + error_message(retval)); +#endif return retval; + } switch (tgsrep.message_type) { case KRB5_TGS_REP: @@ -167,7 +178,7 @@ krb5_get_cred_via_tkt (krb5_context context, krb5_creds *tkt, else retval = KRB5KRB_AP_ERR_MSG_TYPE; - if (retval) /* neither proper reply nor error! */ + if (retval) /* neither proper reply nor error! */ goto error_4; retval = (krb5_error_code) err_reply->error + ERROR_TABLE_BASE_krb5; @@ -218,11 +229,26 @@ krb5_get_cred_via_tkt (krb5_context context, krb5_creds *tkt, if (!krb5_principal_compare(context, dec_rep->client, tkt->client)) retval = KRB5_KDCREP_MODIFIED; - if (!krb5_principal_compare(context, dec_rep->enc_part2->server, in_cred->server)) - retval = KRB5_KDCREP_MODIFIED; - - if (!krb5_principal_compare(context, dec_rep->ticket->server, in_cred->server)) - retval = KRB5_KDCREP_MODIFIED; + if ((!krb5_principal_compare(context, dec_rep->enc_part2->server, in_cred->server)) || + (!krb5_principal_compare(context, dec_rep->ticket->server, in_cred->server))) { + if (krb5_principal_compare(context, dec_rep->ticket->server, dec_rep->enc_part2->server) + && (kdcoptions&KDC_OPT_CANONICALIZE) ) { + /* in_cred server differs from ticket returned, but ticket + returned is consistent and we requested canonicalization. */ +#if 0 +#ifdef DEBUG_REFERRALS + printf("gc_via_tkt: in_cred and encoding don't match but referrals requested\n"); + krb5int_dbgref_dump_principal("gc_via_tkt: in_cred",in_cred->server); + krb5int_dbgref_dump_principal("gc_via_tkt: encoded server",dec_rep->enc_part2->server); +#endif +#endif + } + else { + /* in_cred server differs from ticket returned, and ticket + returned is *not* consistent. */ + retval = KRB5_KDCREP_MODIFIED; + } + } if (dec_rep->enc_part2->nonce != tgsrep.expected_nonce) retval = KRB5_KDCREP_MODIFIED; @@ -267,5 +293,8 @@ error_3:; error_4:; free(tgsrep.response.data); +#ifdef DEBUG_REFERRALS + printf("krb5_get_cred_via_tkt ending; %s\n", retval?error_message(retval):"no error"); +#endif return retval; } diff --git a/src/lib/krb5/krb/parse.c b/src/lib/krb5/krb/parse.c index cd23d14d1..fbcc49db0 100644 --- a/src/lib/krb5/krb/parse.c +++ b/src/lib/krb5/krb/parse.c @@ -30,6 +30,7 @@ * characters in the principal name. */ + #include "k5-int.h" /* @@ -108,10 +109,10 @@ krb5_parse_name(krb5_context context, const char *name, krb5_principal *nprincip size = 0; i++; } else if (c == REALM_SEP) { - if (parsed_realm || !*(cp+1)) + if (parsed_realm) /* - * Multiple realm separaters or null - * realm names are not allowed! + * Multiple realm separaters + * not allowed; zero-length realms are. */ return(KRB5_PARSE_MALFORMED); parsed_realm = cp+1; @@ -278,8 +279,7 @@ krb5_parse_name(krb5_context context, const char *name, krb5_principal *nprincip principal->magic = KV5M_PRINCIPAL; principal->realm.magic = KV5M_DATA; *nprincipal = principal; + krb5_xfree(default_realm); return(0); } - - diff --git a/src/lib/krb5/krb/princ_comp.c b/src/lib/krb5/krb/princ_comp.c index b61f525f4..8b52330b3 100644 --- a/src/lib/krb5/krb/princ_comp.c +++ b/src/lib/krb5/krb/princ_comp.c @@ -65,3 +65,23 @@ krb5_principal_compare(krb5_context context, krb5_const_principal princ1, krb5_c } return TRUE; } + +krb5_boolean KRB5_CALLCONV krb5_is_referral_realm(krb5_data *r) +{ + /* + * Check for a match with KRB5_REFERRAL_REALM. Currently this relies + * on that string constant being zero-length. (Unlike principal realm + * names, KRB5_REFERRAL_REALM is known to be a string.) + */ +#ifdef DEBUG_REFERRALS +#if 0 + printf("krb5_is_ref_realm: checking <%s> for referralness: %s\n", + r->data,(r->length==0)?"true":"false"); +#endif +#endif + assert(strlen(KRB5_REFERRAL_REALM)==0); + if (r->length==0) + return TRUE; + else + return FALSE; +} diff --git a/src/lib/krb5/krb/walk_rtree.c b/src/lib/krb5/krb/walk_rtree.c index 074c7e1d0..7210be7c7 100644 --- a/src/lib/krb5/krb/walk_rtree.c +++ b/src/lib/krb5/krb/walk_rtree.c @@ -102,7 +102,7 @@ * in order to get from the source realm to the destination realm. It * takes a realm separater character (normally ., but presumably there * for all those X.500 realms) . There are two modes it runs in: the - * ANL krb5.confmode and the hierarchy mode. The ANL mode is + * ANL krb5.conf mode and the hierarchy mode. The ANL mode is * fairly obvious. The hierarchy mode looks for common components in * both the client and server realms. In general, the pointer scp and * ccp are used to walk through the client and server realms. The @@ -133,6 +133,13 @@ krb5_walk_realm_tree(krb5_context context, const krb5_data *client, const krb5_d char **cap_nodes; krb5_error_code cap_code; #endif + +#ifdef DEBUG_REFERRALS + printf("krb5_walk_realm_tree starting\n"); + printf(" client is %s\n",client->data); + printf(" server is %s\n",server->data); +#endif + if (!(client->data &&server->data)) return KRB5_NO_TKT_IN_RLM; #ifdef CONFIGURABLE_AUTHENTICATION_PATH @@ -380,5 +387,28 @@ krb5_walk_realm_tree(krb5_context context, const krb5_data *client, const krb5_d } #endif *tree = rettree; + +#ifdef DEBUG_REFERRALS + printf("krb5_walk_realm_tree ending; tree (length %d) is:\n",links); + for(i=0;ilength;n++) + printf("%s<%.*s>",(n>0)?"/":"",p->data[n].length,p->data[n].data); + printf("@<%.*s> (length %d, type %d)\n",p->realm.length,p->realm.data, + p->length, p->type); +} +#endif diff --git a/src/lib/krb5/libkrb5.exports b/src/lib/krb5/libkrb5.exports index 94e94e45d..952ab2f75 100644 --- a/src/lib/krb5/libkrb5.exports +++ b/src/lib/krb5/libkrb5.exports @@ -458,6 +458,7 @@ krb5_init_keyblock krb5_init_secure_context krb5_internalize_opaque krb5_is_permitted_enctype +krb5_is_referral_realm krb5_is_thread_safe krb5_kdc_rep_decrypt_proc krb5_kt_add_entry diff --git a/src/lib/krb5/os/hst_realm.c b/src/lib/krb5/os/hst_realm.c index 17b1e42c4..6646d2da9 100644 --- a/src/lib/krb5/os/hst_realm.c +++ b/src/lib/krb5/os/hst_realm.c @@ -196,52 +196,15 @@ krb5_error_code KRB5_CALLCONV krb5_get_host_realm(krb5_context context, const char *host, char ***realmsp) { char **retrealms; - char *default_realm, *realm, *cp, *temp_realm; + char *realm, *cp, *temp_realm; krb5_error_code retval; - int l; char local_host[MAXDNAME+1]; - if (host) { - /* Filter out numeric addresses if the caller utterly failed to - convert them to names. */ - /* IPv4 - dotted quads only */ - if (strspn(host, "01234567890.") == strlen(host)) { - /* All numbers and dots... if it's three dots, it's an - IP address, and we reject it. But "12345" could be - a local hostname, couldn't it? We'll just assume - that a name with three dots is not meant to be an - all-numeric hostname three all-numeric domains down - from the current domain. */ - int ndots = 0; - const char *p; - for (p = host; *p; p++) - if (*p == '.') - ndots++; - if (ndots == 3) - return KRB5_ERR_NUMERIC_REALM; - } - if (strchr(host, ':')) - /* IPv6 numeric address form? Bye bye. */ - return KRB5_ERR_NUMERIC_REALM; - - /* Should probably error out if strlen(host) > MAXDNAME. */ - strncpy(local_host, host, sizeof(local_host)); - local_host[sizeof(local_host) - 1] = '\0'; - } else { - retval = krb5int_get_fq_local_hostname (local_host, - sizeof (local_host)); - if (retval) - return retval; - } +#ifdef DEBUG_REFERRALS + printf("get_host_realm(host:%s) called\n",host); +#endif - for (cp = local_host; *cp; cp++) { - if (isupper((unsigned char) (*cp))) - *cp = tolower((unsigned char) *cp); - } - l = strlen(local_host); - /* strip off trailing dot */ - if (l && local_host[l-1] == '.') - local_host[l-1] = 0; + krb5int_clean_hostname(context, host, local_host, sizeof local_host); /* Search for the best match for the host or domain. @@ -256,9 +219,15 @@ krb5_get_host_realm(krb5_context context, const char *host, char ***realmsp) */ cp = local_host; - realm = default_realm = (char *)NULL; +#ifdef DEBUG_REFERRALS + printf(" local_host: %s\n",local_host); +#endif + realm = (char *)NULL; temp_realm = 0; while (cp) { +#ifdef DEBUG_REFERRALS + printf(" trying to look up %s in the domain_realm map\n",cp); +#endif retval = profile_get_string(context->profile, "domain_realm", cp, 0, (char *)NULL, &temp_realm); if (retval) @@ -269,15 +238,17 @@ krb5_get_host_realm(krb5_context context, const char *host, char ***realmsp) /* Setup for another test */ if (*cp == '.') { cp++; - if (default_realm == (char *)NULL) { - /* If nothing else works, use the host's domain */ - default_realm = cp; - } } else { cp = strchr(cp, '.'); } } +#ifdef DEBUG_REFERRALS + printf(" done searching the domain_realm map\n"); +#endif if (temp_realm) { +#ifdef DEBUG_REFERRALS + printf(" temp_realm is %s\n",temp_realm); +#endif realm = malloc(strlen(temp_realm) + 1); if (!realm) { profile_release_string(temp_realm); @@ -287,47 +258,13 @@ krb5_get_host_realm(krb5_context context, const char *host, char ***realmsp) profile_release_string(temp_realm); } -#ifdef KRB5_DNS_LOOKUP - if (realm == (char *)NULL) { - int use_dns = _krb5_use_dns_realm(context); - if ( use_dns ) { - /* - * Since this didn't appear in our config file, try looking - * it up via DNS. Look for a TXT records of the form: - * - * _kerberos. - * - */ - cp = local_host; - do { - retval = krb5_try_realm_txt_rr("_kerberos", cp, &realm); - cp = strchr(cp,'.'); - if (cp) - cp++; - } while (retval && cp && cp[0]); - } - } -#endif /* KRB5_DNS_LOOKUP */ if (realm == (char *)NULL) { - if (default_realm != (char *)NULL) { - /* We are defaulting to the realm of the host */ - if (!(cp = (char *)malloc(strlen(default_realm)+1))) - return ENOMEM; - strcpy(cp, default_realm); - realm = cp; - - /* Assume the realm name is upper case */ - for (cp = realm; *cp; cp++) - if (islower((unsigned char) (*cp))) - *cp = toupper((unsigned char) *cp); - } else { - /* We are defaulting to the local realm */ - retval = krb5_get_default_realm(context, &realm); - if (retval) { - return retval; - } - } + if (!(cp = (char *)malloc(strlen(KRB5_REFERRAL_REALM)+1))) + return ENOMEM; + strcpy(cp, KRB5_REFERRAL_REALM); + realm = cp; } + if (!(retrealms = (char **)calloc(2, sizeof(*retrealms)))) { if (realm != (char *)NULL) free(realm); @@ -383,3 +320,171 @@ krb5int_translate_gai_error (int num) abort (); return -1; } + + +/* + * Ganked from krb5_get_host_realm; handles determining a fallback realm + * to try in the case where referrals have failed and it's time to go + * look at TXT records or make a DNS-based assumption. + */ + +krb5_error_code KRB5_CALLCONV +krb5_get_fallback_host_realm(krb5_context context, krb5_data *hdata, char ***realmsp) +{ + char **retrealms; + char *default_realm, *realm, *cp, *temp_realm; + krb5_error_code retval; + char local_host[MAXDNAME+1], host[MAXDNAME+1]; + + /* Convert what we hope is a hostname to a string. */ + memcpy(host, hdata->data, hdata->length); + host[hdata->length]=0; + +#ifdef DEBUG_REFERRALS + printf("get_fallback_host_realm(host >%s<) called\n",host); +#endif + + krb5int_clean_hostname(context, host, local_host, sizeof local_host); + + /* Scan hostname for DNS realm, and save as last-ditch realm + assumption. */ + cp = local_host; +#ifdef DEBUG_REFERRALS + printf(" local_host: %s\n",local_host); +#endif + realm = default_realm = (char *)NULL; + temp_realm = 0; + while (cp && !default_realm) { + if (*cp == '.') { + cp++; + if (default_realm == (char *)NULL) { + /* If nothing else works, use the host's domain */ + default_realm = cp; + } + } else { + cp = strchr(cp, '.'); + } + } +#ifdef DEBUG_REFERRALS + printf(" done finding DNS-based default realm: >%s<\n",default_realm); +#endif + +#ifdef KRB5_DNS_LOOKUP + if (realm == (char *)NULL) { + int use_dns = _krb5_use_dns_realm(context); + if ( use_dns ) { + /* + * Since this didn't appear in our config file, try looking + * it up via DNS. Look for a TXT records of the form: + * + * _kerberos. + * + */ + cp = local_host; + do { + retval = krb5_try_realm_txt_rr("_kerberos", cp, &realm); + cp = strchr(cp,'.'); + if (cp) + cp++; + } while (retval && cp && cp[0]); + } + } +#endif /* KRB5_DNS_LOOKUP */ + + + if (realm == (char *)NULL) { + if (default_realm != (char *)NULL) { + /* We are defaulting to the realm of the host */ + if (!(cp = (char *)malloc(strlen(default_realm)+1))) + return ENOMEM; + strcpy(cp, default_realm); + realm = cp; + + /* Assume the realm name is upper case */ + for (cp = realm; *cp; cp++) + if (islower((int) (*cp))) + *cp = toupper((int) *cp); + } else { + /* We are defaulting to the local realm */ + retval = krb5_get_default_realm(context, &realm); + if (retval) { + return retval; + } + } + } + if (!(retrealms = (char **)calloc(2, sizeof(*retrealms)))) { + if (realm != (char *)NULL) + free(realm); + return ENOMEM; + } + + retrealms[0] = realm; + retrealms[1] = 0; + + *realmsp = retrealms; + return 0; +} + +/* + * Common code for krb5_get_host_realm and krb5_get_fallback_host_realm + * to do basic sanity checks on supplied hostname. + */ +krb5_error_code KRB5_CALLCONV +krb5int_clean_hostname(krb5_context context, const char *host, char *local_host, size_t lhsize) +{ + char **retrealms; + char *realm, *cp, *temp_realm; + krb5_error_code retval; + int l; + + local_host[0]=0; +#ifdef DEBUG_REFERRALS + printf("krb5int_clean_hostname called: host<%s>, local_host<%s>, size %d\n",host,local_host,lhsize); +#endif + if (host) { + /* Filter out numeric addresses if the caller utterly failed to + convert them to names. */ + /* IPv4 - dotted quads only */ + if (strspn(host, "01234567890.") == strlen(host)) { + /* All numbers and dots... if it's three dots, it's an + IP address, and we reject it. But "12345" could be + a local hostname, couldn't it? We'll just assume + that a name with three dots is not meant to be an + all-numeric hostname three all-numeric domains down + from the current domain. */ + int ndots = 0; + const char *p; + for (p = host; *p; p++) + if (*p == '.') + ndots++; + if (ndots == 3) + return KRB5_ERR_NUMERIC_REALM; + } + if (strchr(host, ':')) + /* IPv6 numeric address form? Bye bye. */ + return KRB5_ERR_NUMERIC_REALM; + + /* Should probably error out if strlen(host) > MAXDNAME. */ + strncpy(local_host, host, lhsize); + local_host[lhsize - 1] = '\0'; + } else { + retval = krb5int_get_fq_local_hostname (local_host, lhsize); + if (retval) + return retval; + } + + /* fold to lowercase */ + for (cp = local_host; *cp; cp++) { + if (isupper((unsigned char) (*cp))) + *cp = tolower((unsigned char) *cp); + } + l = strlen(local_host); + /* strip off trailing dot */ + if (l && local_host[l-1] == '.') + local_host[l-1] = 0; + +#ifdef DEBUG_REFERRALS + printf("krb5int_clean_hostname ending: host<%s>, local_host<%s>, size %d\n",host,local_host,lhsize); +#endif + return 0; +} diff --git a/src/lib/krb5/os/sn2princ.c b/src/lib/krb5/os/sn2princ.c index 2ba7e8ae2..683cc10cb 100644 --- a/src/lib/krb5/os/sn2princ.c +++ b/src/lib/krb5/os/sn2princ.c @@ -68,6 +68,12 @@ krb5_sname_to_principal(krb5_context context, const char *hostname, const char * register char *cp; char localname[MAXHOSTNAMELEN]; + FILE *log; + +#ifdef DEBUG_REFERRALS + printf("krb5_sname_to_principal(host=%s, sname=%s, type=%d)\n",hostname,sname,type); + printf(" name types: 0=unknown, 3=srv_host\n"); +#endif if ((type == KRB5_NT_UNKNOWN) || (type == KRB5_NT_SRV_HST)) { @@ -105,6 +111,9 @@ krb5_sname_to_principal(krb5_context context, const char *hostname, const char * try_getaddrinfo_again: err = getaddrinfo(hostname, 0, &hints, &ai); if (err) { +#ifdef DEBUG_REFERRALS + printf("sname_to_princ: probably punting due to bad hostname of %s\n",hostname); +#endif if (hints.ai_family == AF_INET) { /* Just in case it's an IPv6-only name. */ hints.ai_family = 0; @@ -145,6 +154,9 @@ krb5_sname_to_principal(krb5_context context, const char *hostname, const char * } if (!remote_host) return ENOMEM; +#ifdef DEBUG_REFERRALS + printf("sname_to_princ: hostname <%s> after rdns processing\n",remote_host); +#endif if (type == KRB5_NT_SRV_HST) for (cp = remote_host; *cp; cp++) @@ -167,6 +179,11 @@ krb5_sname_to_principal(krb5_context context, const char *hostname, const char * free(remote_host); return retval; } + +#ifdef DEBUG_REFERRALS + printf("sname_to_princ: realm <%s> after krb5_get_host_realm\n",hrealms[0]); +#endif + if (!hrealms[0]) { free(remote_host); krb5_xfree(hrealms); @@ -180,6 +197,13 @@ krb5_sname_to_principal(krb5_context context, const char *hostname, const char * krb5_princ_type(context, *ret_princ) = type; +#ifdef DEBUG_REFERRALS + printf("krb5_sname_to_principal returning\n"); + printf("realm: <%s>, sname: <%s>, remote_host: <%s>\n", + realm,sname,remote_host); + krb5int_dbgref_dump_principal("krb5_sname_to_principal",*ret_princ); +#endif + free(remote_host); krb5_free_host_realm(context, hrealms); diff --git a/src/lib/krb5_32.def b/src/lib/krb5_32.def index 9785df563..b2a8361c1 100644 --- a/src/lib/krb5_32.def +++ b/src/lib/krb5_32.def @@ -171,6 +171,7 @@ krb5_c_string_to_key_with_params krb5_init_keyblock krb5_init_random_key krb5_init_secure_context + krb5_is_referral_realm krb5_is_thread_safe krb5_kt_add_entry krb5_kt_close -- 2.26.2