gc_frm_kdc.c revision 5e01956f3000408c2a2c5a08c8d0acf2c2a9d8ee
/*
*/
/*
* Copyright (c) 1994,2003,2005 by the Massachusetts Institute of Technology.
* Copyright (c) 1994 CyberSAFE Corporation
* Copyright (c) 1993 Open Computing Security Group
* Copyright (c) 1990,1991 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.
* Neither M.I.T., the Open Computing Security Group, nor
* CyberSAFE Corporation make any representations about the suitability of
* this software for any purpose. It is provided "as is" without express
* or implied warranty.
*
* krb5_get_cred_from_kdc() and related functions:
*
* Get credentials from some KDC somewhere, possibly accumulating TGTs
* along the way.
*/
#include "k5-int.h"
#include <stdio.h>
#include "int-proto.h"
#include <locale.h>
struct tr_state;
/*
* Ring buffer abstraction for TGTs returned from a ccache; avoids
* lots of excess copying.
*/
#define NCC_TGTS 2
struct cc_tgts {
};
/* NOTE: This only checks if NXT_TGT is CUR_CC_TGT. */
#define NXT_TGT_IS_CACHED(ts) \
#define MARK_CUR_CC_TGT_CLEAN(ts) \
do { \
} while (0)
static void init_cc_tgts(struct tr_state *);
static void shift_cc_tgts(struct tr_state *);
static void clean_cc_tgts(struct tr_state *);
/*
* State struct for do_traversal() and helpers.
*
* CUR_TGT and NXT_TGT can each point either into CC_TGTS or into
* KDC_TGTS.
*
* CUR_TGT is the "working" TGT, which will be used to obtain new
* TGTs. NXT_TGT will be CUR_TGT for the next iteration of the loop.
*
* Part of the baroqueness of this setup is to deal with annoying
* differences between krb5_cc_retrieve_cred() and
* krb5_get_cred_via_tkt(); krb5_cc_retrieve_cred() fills in a
* caller-allocated krb5_creds, while krb5_get_cred_via_tkt()
* allocates a krb5_creds for return.
*/
struct tr_state {
unsigned int nkdcs;
unsigned int ntgts;
};
/*
* Debug support
*/
#ifdef DEBUG_GC_FRM_KDC
#else
#endif /* !DEBUG_GC_FRM_KDC */
#ifdef DEBUG_REFERRALS
#define DUMP_PRINC(x, y) krb5int_dbgref_dump_principal((x), (y))
#else
#define DPRINTF(x)
#define DFPRINTF(x)
#define DUMP_PRINC(x, y)
#endif
/* Convert ticket flags to necessary KDC options */
/*
* Certain krb5_cc_retrieve_cred() errors are soft errors when looking
* for a cross-realm TGT.
*/
#define HARD_CC_ERR(r) ((r) && (r) != KRB5_CC_NOTFOUND && \
(r) != KRB5_CC_NOT_KTYPE)
/*
* Flags for ccache lookups of cross-realm TGTs.
*
* A cross-realm TGT may be issued by some other intermediate realm's
* KDC, so we use KRB5_TC_MATCH_SRV_NAMEONLY.
*/
/*
* Solaris Kerberos:
* Include KRB5_TC_MATCH_TIMES so as to ensure that the retrieved ticket
* isn't stale.
*/
#define RETR_FLAGS (KRB5_TC_MATCH_SRV_NAMEONLY | \
/*
* Prototypes of helper functions
*/
krb5_creds *mcreds);
krb5_creds ***out_kdc_tgts);
/*
* init_cc_tgts()
*
* Initialize indices for cached-TGT ring buffer. Caller must zero
* CC_TGTS, CC_TGT_DIRTY arrays prior to calling.
*/
static void
{
}
/*
* shift_cc_tgts()
*
* Given a fresh assignment to NXT_CC_TGT, mark NXT_CC_TGT as dirty,
* and shift indices so old NXT_CC_TGT becomes new CUR_CC_TGT. Clean
* the new NXT_CC_TGT.
*/
static void
{
unsigned int i;
i = (i + 1) % NCC_TGTS;
}
}
/*
* clean_cc_tgts()
*
* Free CC_TGTS which were dirty, then mark them clean.
*/
static void
{
unsigned int i;
for (i = 0; i < NCC_TGTS; i++) {
}
}
}
/*
* Debug support
*/
#ifdef DEBUG_GC_FRM_KDC
static void
{
if (cur_tgt_str)
if (cur_kdc_str)
if (nxt_kdc_str)
}
static void
{
error_message(ret));
}
static void
{
char *str;
return;
}
#endif /* DEBUG_GC_FRM_KDC */
/*
* tgt_mcred()
*
* Return MCREDS for use as a match criterion.
*
* Resulting credential has CLIENT as the client principal, and
* MCREDS first, does not allocate MCREDS, and cleans MCREDS on
* failure. The peculiar ordering of DST and SRC args is for
* consistency with krb5_tgtname().
*/
static krb5_error_code
{
retval = 0;
if (retval)
goto cleanup;
if (retval)
goto cleanup;
if (retval)
return retval;
}
/*
* init_rtree()
*
* Populate KDC_LIST with the output of krb5_walk_realm_tree().
*/
static krb5_error_code
{
if (retval)
return retval;
}
return ENOMEM;
return 0;
}
/*
* retr_local_tgt()
*
* Prime CUR_TGT with the cached TGT of the client's local realm.
*/
static krb5_error_code
{
if (retval)
return retval;
/* Match realm, unlike other ccache retrievals here. */
if (!retval) {
}
return retval;
}
/*
* try_ccache()
*
* Attempt to retrieve desired NXT_TGT from ccache. Point NXT_TGT to
* it if successful.
*/
static krb5_error_code
{
/*
* Solaris Kerberos:
* Ensure the retrieved cred isn't stale.
* Set endtime to now so krb5_cc_retrieve_cred won't return an expired ticket.
*/
return retval;
}
if (!retval) {
}
/*
* Solaris Kerberos:
* Ensure that tgtq->times.endtime is reset back to its original value so
* that if tgtq is used to request a ticket from the KDC it doesn't request
* a ticket with an endtime set to "now".
*/
return retval;
}
/*
* find_nxt_kdc()
*
* A NXT_TGT gotten from an intermediate KDC might actually be a
* referral. Search KDC_LIST forward starting from CUR_KDC, looking
* for the KDC with the same remote realm as NXT_TGT. If we don't
* find it, the intermediate KDC is leading us off the transit path.
*
* Match on CUR_KDC's remote realm, not local realm, because, among
* other reasons, we can get a referral to the final realm; e.g.,
* given
*
*
* i.e., we got a ticket issued by R2 with remote realm R4, we want to
* with R3 as its local realm.
*
* Set up for next iteration of do_traversal() loop by pointing
* NXT_KDC to one entry forward of the match.
*/
static krb5_error_code
{
/*
* Solaris Kerberos:
* The following assertion is not be true for the case when
* ts->nxt points to a cached ticket and not to a freshly
* fetched TGT in ts->kdc_tgts. See changes in try_kdc()
*/
/* assert(ts->nxt_tgt == ts->kdc_tgts[ts->ntgts-1]); */
/* Solaris Kerberos */
if (!err) {
"KDC reply did not match expectations: server '%s' principal size should be 2"),
s_name);
} else
"KDC reply did not match expectations: server principal size should be 2"));
return KRB5_KDCREP_MODIFIED;
}
break;
}
}
/*
* Not found; we probably got an unexpected realm referral.
* Don't touch NXT_KDC, thus allowing next_closest_tgt() to
* continue looping backwards.
*/
/*
* Solaris Kerberos:
* Only free the allocated creds if they are in kdc_tgts. If they
* are in cc_tgts no freeing is necessary.
*/
/* Punt NXT_TGT from KDC_TGTS if bogus. */
}
"KDC reply did not match expectation: KDC not found. Probably got an unexpected realm referral"));
return KRB5_KDCREP_MODIFIED;
}
return 0;
}
/*
* try_kdc()
*
* Using CUR_TGT, attempt to get desired NXT_TGT. Update NXT_KDC if
* successful.
*/
static krb5_error_code
{
/* This check should probably be in gc_via_tkt. */
return KRB5_PROG_ETYPE_NOSUPP;
/*
* Solaris Kerberos:
* Store credential in a temporary ticket as we may not
* want to add it to ts->kdc_tgts if it is already in
* the cache.
*/
<gtq, &tmp_out_cred);
if (retval) {
return retval;
}
/*
* Solaris Kerberos:
* See if the returned creds are different to the requested creds.
* This can happen when the server returns a TGT "closer" to the
* desired realm.
*/
/* Not equal, ticket may already be in the cache */
if (!retval) {
return retval;
}
}
return retval;
}
/*
* kdc_mcred()
*
* Return MCREDS for use as a match criterion.
*
* Resulting credential has CLIENT as the client principal, and
* krbtgt/remote_realm(NXT_KDC)@local_realm(CUR_KDC) as the server
* principal. Zeroes MCREDS first, does not allocate MCREDS, and
* cleans MCREDS on failure.
*/
static krb5_error_code
{
retval = 0;
if (retval)
goto cleanup;
if (retval)
goto cleanup;
if (retval)
return retval;
}
/*
* next_closest_tgt()
*
* Using CUR_TGT, attempt to get the cross-realm TGT having its remote
* realm closest to the target principal's. Update NXT_TGT, NXT_KDC
* accordingly.
*/
static krb5_error_code
{
retval = 0;
if (retval)
goto cleanup;
/* Don't waste time retrying ccache for direct path. */
if (!retval)
break;
if (HARD_CC_ERR(retval))
goto cleanup;
}
/* Not in the ccache, so talk to a KDC. */
if (!retval) {
break;
}
/*
* Because try_kdc() validates referral TGTs, it can return an
* error indicating a bogus referral. The loop continues when
* it gets a bogus referral, which is arguably the right
* thing. (Previous implementation unconditionally failed.)
*/
}
/*
* If we have a non-zero retval, we either have a hard error or we
* failed to find a closer TGT.
*/
return retval;
}
/*
* do_traversal()
*
* Find final TGT needed to get CLIENT a ticket for SERVER. Point
* OUT_TGT at the desired TGT, which may be an existing cached TGT
* (copied into OUT_CC_TGT) or one of the newly obtained TGTs
* (collected in OUT_KDC_TGTS).
*
* Get comfortable; this is somewhat complicated.
*
* Nomenclature: Cross-realm TGS principal names have the form:
*
*
* krb5_walk_realm_tree() returns a list like:
*
*
* These are prinicpal names, not realm names. We only really use the
* remote parts of the TGT principal names.
*
* The do_traversal loop calls next_closest_tgt() to find the next
* closest TGT to the destination realm. next_closest_tgt() updates
* NXT_KDC for the following iteration of the do_traversal() loop.
*
* At the beginning of any given iteration of the do_traversal() loop,
* CUR_KDC's remote realm is the remote realm of CUR_TGT->SERVER. The
* local realms of CUR_KDC and CUR_TGT->SERVER may not match due to
* short-circuit paths provided by intermediate KDCs, e.g., CUR_KDC
* might be krbtgt/D@C, while CUR_TGT->SERVER is krbtgt/D@B.
*
* For example, given KDC_LIST of
*
*
* The next_closest_tgt() loop moves NXT_KDC to the left starting from
* R5, stopping before it reaches CUR_KDC. When next_closest_tgt()
* returns, the do_traversal() loop updates CUR_KDC to be NXT_KDC, and
* calls next_closest_tgt() again.
*
* next_closest_tgt() at start of its loop:
*
* CUR NXT
* | |
* V V
* +----+----+----+----+----+
* | R1 | R2 | R3 | R4 | R5 |
* +----+----+----+----+----+
*
*
* CUR NXT
* | |
* V V
* +----+----+----+----+----+
* | R1 | R2 | R3 | R4 | R5 |
* +----+----+----+----+----+
*
* do_traversal() updates CUR_KDC:
*
* NXT
* CUR
* |
* V
* +----+----+----+----+----+
* | R1 | R2 | R3 | R4 | R5 |
* +----+----+----+----+----+
*
* next_closest_tgt() at start of its loop:
*
* CUR NXT
* | |
* V V
* +----+----+----+----+----+
* | R1 | R2 | R3 | R4 | R5 |
* +----+----+----+----+----+
*
* etc.
*
* The algorithm executes in n*(n-1)/2 (the sum of integers from 1 to
* n-1) attempts in the worst case, i.e., each KDC only has a
* cross-realm ticket for the immediately following KDC in the transit
* path. Typically, short-circuit paths will cause execution occur
* faster than this worst-case scenario.
*
* When next_closest_tgt() updates NXT_KDC, it may not perform a
* simple increment from CUR_KDC, in part because some KDC may
* short-circuit pieces of the transit path.
*/
static krb5_error_code
{
*out_kdc_tgts = NULL;
if (retval)
goto cleanup;
if (retval)
goto cleanup;
if (retval)
goto cleanup;
}
if (NXT_TGT_IS_CACHED(ts)) {
*out_tgt = out_cc_tgt;
} else {
/* CUR_TGT is somewhere in KDC_TGTS; no need to copy. */
}
*out_kdc_tgts = NULL;
} else
return retval;
}
/*
* krb5_get_cred_from_kdc_opt()
* krb5_get_cred_from_kdc()
* krb5_get_cred_from_kdc_validate()
* krb5_get_cred_from_kdc_renew()
*
* Retrieve credentials for client IN_CRED->CLIENT, server
* IN_CRED->SERVER, ticket flags IN_CRED->TICKET_FLAGS, possibly
* second_ticket if needed.
*
* Request credentials from the KDC for the server's realm. Point
* TGTS to an allocated array of pointers to krb5_creds, containing
* any intermediate credentials obtained in the process of contacting
* the server's KDC; if no intermediate credentials were obtained,
* TGTS is a null pointer. Return intermediate credentials if
* intermediate KDCs provided credentials, even if no useful end
* ticket results.
*
* Caller must free TGTS, regardless of whether this function returns
* success.
*
* This function does NOT cache the intermediate TGTs.
*
* Do not call this routine if desired credentials are already cached.
*
* 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.
*/
static krb5_error_code
{
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.
*/
return retval;
/* We need a second copy for the output creds. */
&out_supplied_server)) != 0 ) {
return retval;
}
/* Copy client realm to server if no hint. */
/* Use the client realm. */
DPRINTF(("gc_from_kdc: no server realm supplied, "
"using client realm.\n"));
return ENOMEM;
}
/*
* 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.
*/
if (retval)
goto cleanup;
/* Fast path: Is it in the ccache? */
/*
* Solaris Kerberos:
* Ensure the retrieved cred isn't stale.
* Set endtime to now so krb5_cc_retrieve_cred won't return an expired ticket.
*/
goto cleanup;
}
if (!retval) {
} else if (!HARD_CC_ERR(retval)) {
DPRINTF(("gc_from_kdc: starting do_traversal to find "
"initial TGT for referral\n"));
}
if (retval) {
DPRINTF(("gc_from_kdc: failed to find initial TGT for referral\n"));
goto cleanup;
}
/*
* 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_count++) {
#if 0
#endif
kdcopt |
KDC_OPT_ENC_TKT_IN_SKEY : 0),
if (retval) {
DPRINTF(("gc_from_kdc: referral TGS-REQ request failed: <%s>\n",
error_message(retval)));
/* If we haven't gone anywhere yet, fail through to the
non-referral case. */
if (referral_count==0) {
DPRINTF(("gc_from_kdc: initial referral failed; "
"punting to fallback.\n"));
break;
}
/* Otherwise, try the same query without canonicalization
set, and fail hard if that doesn't work. */
DPRINTF(("gc_from_kdc: referral #%d failed; "
kdcopt |
KDC_OPT_ENC_TKT_IN_SKEY : 0),
/* Whether or not that succeeded, we're done. */
goto cleanup;
}
/* Referral request succeeded; let's see what it is. */
DPRINTF(("gc_from_kdc: request generated ticket "
"for requested server principal\n"));
DUMP_PRINC("gc_from_kdc final referred reply",
/*
* Check if the return enctype is one that we requested if
* needed.
*/
goto cleanup;
for (i = 0; i < context->tgs_ktype_count; i++) {
/* Found an allowable etype, so we're done */
goto cleanup;
}
}
/*
* We need to try again, but this time use the
* tgs_ktypes in the context. At this point we should
* have all the tgts to succeed.
*/
/* Free "wrong" credential */
/* Re-establish tgs etypes */
kdcopt |
KDC_OPT_ENC_TKT_IN_SKEY : 0),
goto cleanup;
}
DPRINTF(("gc_from_kdc: request generated referral tgt\n"));
DUMP_PRINC("gc_from_kdc credential received",
if (referral_count == 0)
else
DPRINTF(("gc_from_kdc: referred back to "
"previous realm; fall back\n"));
break;
}
/* Check for referral routing loop. */
for (i=0;i<referral_count;i++) {
#if 0
DUMP_PRINC("gc_from_kdc: loop compare #1",
DUMP_PRINC("gc_from_kdc: loop compare #2",
referral_tgts[i]->server);
#endif
referral_tgts[i]->server)) {
"krb5_get_cred_from_kdc_opt: "
"referral routing loop - "
"got referral back to hop #%d\n", i));
goto cleanup;
}
}
/* Point current tgt pointer at newly-received TGT. */
/* Save pointer to tgt in referral_tgts. */
/* Copy krbtgt realm to server principal. */
if (retval)
return retval;
/*
* Future work: rewrite server principal per any
* supplied padata.
*/
} else {
/* Not a TGT; punt to fallback. */
break;
}
}
/*
* 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.
*/
&hrealms);
#if 0
DPRINTF(("gc_from_kdc: using fallback realm of %s\n",
hrealms[0]));
#endif
}
else {
/*
* Problem case: Realm tagged for referral but apparently not
* in a <type>/<host> format that
* krb5_get_fallback_host_realm can deal with.
*/
/* Solaris Kerberos */
"Cannot determine realm for host: Referral specified but no fallback realm available. Client is '%s' and Server is '%s'"),
if (s_name)
if (c_name)
DPRINTF(("gc_from_kdc: referral specified "
"but no fallback realm avaiable!\n"));
return KRB5_ERR_HOST_REALM_UNKNOWN;
}
}
DUMP_PRINC("gc_from_kdc server at fallback after fallback rewrite",
server);
/*
* Get a TGT for the target realm.
*/
if (retval)
goto cleanup;
/* Fast path: Is it in the ccache? */
/* Free tgtptr data if reused from above. */
/* Free TGTS if previously filled by do_traversal() */
}
}
/*
* Solaris Kerberos:
* Ensure the retrieved cred isn't stale.
* Set endtime to now so krb5_cc_retrieve_cred won't return an expired ticket.
*/
goto cleanup;
}
if (!retval) {
} else if (!HARD_CC_ERR(retval)) {
}
if (retval)
goto cleanup;
/*
* Finally have TGT for target realm! Try using it to get creds.
*/
goto cleanup;
}
kdcopt |
KDC_OPT_ENC_TKT_IN_SKEY : 0),
/* Drop the original principal back into in_cred so that it's cached
in the expected format. */
DUMP_PRINC("gc_from_kdc: final hacked server principal at cleanup",
server);
/* Success: free server, swap supplied server back in. */
}
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.
*/
}
/*
* 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 (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. */
return ENOMEM;
if(subretval)
return subretval;
DUMP_PRINC("gc_from_kdc: returning referral TGT for ccache",
#if 0
}
#endif
}
}
/* Free referral TGTs list. */
for (i=0;i<KRB5_REFERRAL_MAXHOPS;i++) {
if(referral_tgts[i]) {
}
}
DPRINTF(("gc_from_kdc finishing with %s\n",
return retval;
}
krb5_creds ***tgts)
{
0);
}
krb5_creds ***tgts)
{
}
krb5_creds ***tgts)
{
}