/*
- * Copyright (c) 1994,2003 by the Massachusetts Institute of Technology.
+ * 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.
* this software for any purpose. It is provided "as is" without express
* or implied warranty.
*
- * krb5_get_cred_from_kdc()
- * Get credentials from some KDC somewhere, possibly accumulating tgts
+ * krb5_get_cred_from_kdc() and related functions:
+ *
+ * Get credentials from some KDC somewhere, possibly accumulating TGTs
* along the way.
*/
#include <stdio.h>
#include "int-proto.h"
+struct tr_state;
+
/*
- * Retrieve credentials for principal in_cred->client,
- * server in_cred->server, ticket flags creds->ticket_flags, possibly
- * second_ticket if needed by ticket_flags.
- *
- * Credentials are requested from the KDC for the server's realm. Any
- * TGT credentials obtained in the process of contacting the KDC are
- * returned in an array of credentials; tgts is filled in to point to an
- * array of pointers to credential structures (if no TGT's were used, the
- * pointer is zeroed). TGT's may be returned even if no useful end ticket
- * was obtained.
- *
- * The returned credentials are NOT cached.
+ * Ring buffer abstraction for TGTs returned from a ccache; avoids
+ * lots of excess copying.
+ */
+
+#define NCC_TGTS 2
+struct cc_tgts {
+ krb5_creds cred[NCC_TGTS];
+ int dirty[NCC_TGTS];
+ unsigned int cur, nxt;
+};
+
+/* NOTE: This only checks if NXT_TGT is CUR_CC_TGT. */
+#define NXT_TGT_IS_CACHED(ts) \
+ ((ts)->nxt_tgt == (ts)->cur_cc_tgt)
+
+#define MARK_CUR_CC_TGT_CLEAN(ts) \
+do { \
+ (ts)->cc_tgts.dirty[(ts)->cc_tgts.cur] = 0; \
+} 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.
*
- * This routine should not be called if the credentials are already in
- * the cache.
- *
- * If credentials are obtained, creds is filled in with the results;
- * creds->ticket and creds->keyblock->key are set to allocated storage,
- * which should be freed by the caller when finished.
- *
- * returns errors, system errors.
+ * 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 {
+ krb5_context ctx;
+ krb5_ccache ccache;
+ krb5_principal *kdc_list;
+ unsigned int nkdcs;
+ krb5_principal *cur_kdc;
+ krb5_principal *nxt_kdc;
+ krb5_principal *lst_kdc;
+ krb5_creds *cur_tgt;
+ krb5_creds *nxt_tgt;
+ krb5_creds **kdc_tgts;
+ struct cc_tgts cc_tgts;
+ krb5_creds *cur_cc_tgt;
+ krb5_creds *nxt_cc_tgt;
+ unsigned int ntgts;
+};
-/* helper macro: convert flags to necessary KDC options */
+/*
+ * Debug support
+ */
+#ifdef DEBUG_GC_FRM_KDC
+
+#define TR_DBG(ts, prog) tr_dbg(ts, prog)
+#define TR_DBG_RET(ts, prog, ret) tr_dbg_ret(ts, prog, ret)
+#define TR_DBG_RTREE(ts, prog, princ) tr_dbg_rtree(ts, prog, princ)
+
+static void tr_dbg(struct tr_state *, const char *);
+static void tr_dbg_ret(struct tr_state *, const char *, krb5_error_code);
+static void tr_dbg_rtree(struct tr_state *, const char *, krb5_principal);
+
+#else
+#define TR_DBG(ts, prog)
+#define TR_DBG_RET(ts, prog, ret)
+#define TR_DBG_RTREE(ts, prog, princ)
+
+#endif /* !DEBUG_GC_FRM_KDC */
+
+/* Convert ticket flags to necessary KDC options */
#define FLAGS2OPTS(flags) (flags & KDC_TKT_COMMON_MASK)
-static krb5_error_code
-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)
+/*
+ * 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.
+ */
+#define RETR_FLAGS (KRB5_TC_MATCH_SRV_NAMEONLY | KRB5_TC_SUPPORTED_KTYPES)
+
+/*
+ * Prototypes of helper functions
+ */
+static krb5_error_code tgt_mcred(krb5_context, krb5_principal,
+ krb5_principal, krb5_principal, krb5_creds *);
+static krb5_error_code retr_local_tgt(struct tr_state *, krb5_principal);
+static krb5_error_code try_ccache(struct tr_state *, krb5_creds *);
+static krb5_error_code find_nxt_kdc(struct tr_state *);
+static krb5_error_code try_kdc(struct tr_state *, krb5_creds *);
+static krb5_error_code kdc_mcred(struct tr_state *, krb5_principal,
+ krb5_creds *mcreds);
+static krb5_error_code next_closest_tgt(struct tr_state *, krb5_principal);
+static krb5_error_code init_rtree(struct tr_state *,
+ krb5_principal, krb5_principal);
+static krb5_error_code do_traversal(krb5_context ctx, krb5_ccache,
+ krb5_principal client, krb5_principal server,
+ krb5_creds *out_cc_tgt, krb5_creds **out_tgt,
+ krb5_creds ***out_kdc_tgts);
+static krb5_error_code krb5_get_cred_from_kdc_opt(krb5_context, krb5_ccache,
+ krb5_creds *, krb5_creds **, krb5_creds ***, int);
+
+/*
+ * init_cc_tgts()
+ *
+ * Initialize indices for cached-TGT ring buffer. Caller must zero
+ * CC_TGTS, CC_TGT_DIRTY arrays prior to calling.
+ */
+static void
+init_cc_tgts(struct tr_state *ts)
{
- krb5_creds **ret_tgts = NULL;
- int ntgts = 0;
- krb5_creds tgt, tgtq, *tgtr = NULL, otgt;
+ ts->cc_tgts.cur = 0;
+ ts->cc_tgts.nxt = 1;
+ ts->cur_cc_tgt = &ts->cc_tgts.cred[0];
+ ts->nxt_cc_tgt = &ts->cc_tgts.cred[1];
+}
+
+/*
+ * 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
+shift_cc_tgts(struct tr_state *ts)
+{
+ unsigned int i;
+ struct cc_tgts *rb;
+
+ rb = &ts->cc_tgts;
+ i = rb->cur = rb->nxt;
+ rb->dirty[i] = 1;
+ ts->cur_cc_tgt = ts->nxt_cc_tgt;
+
+ i = (i + 1) % NCC_TGTS;
+
+ rb->nxt = i;
+ ts->nxt_cc_tgt = &rb->cred[i];
+ if (rb->dirty[i]) {
+ krb5_free_cred_contents(ts->ctx, &rb->cred[i]);
+ rb->dirty[i] = 0;
+ }
+}
+
+/*
+ * clean_cc_tgts()
+ *
+ * Free CC_TGTS which were dirty, then mark them clean.
+ */
+static void
+clean_cc_tgts(struct tr_state *ts)
+{
+ unsigned int i;
+ struct cc_tgts *rb;
+
+ rb = &ts->cc_tgts;
+ for (i = 0; i < NCC_TGTS; i++) {
+ if (rb->dirty[i]) {
+ krb5_free_cred_contents(ts->ctx, &rb->cred[i]);
+ rb->dirty[i] = 0;
+ }
+ }
+}
+
+/*
+ * Debug support
+ */
+#ifdef DEBUG_GC_FRM_KDC
+static void
+tr_dbg(struct tr_state *ts, const char *prog)
+{
krb5_error_code retval;
- krb5_principal int_server = NULL; /* Intermediate server for request */
+ char *cur_tgt_str, *cur_kdc_str, *nxt_kdc_str;
+
+ cur_tgt_str = cur_kdc_str = nxt_kdc_str = NULL;
+ retval = krb5_unparse_name(ts->ctx, ts->cur_tgt->server, &cur_tgt_str);
+ if (retval) goto cleanup;
+ retval = krb5_unparse_name(ts->ctx, *ts->cur_kdc, &cur_kdc_str);
+ if (retval) goto cleanup;
+ retval = krb5_unparse_name(ts->ctx, *ts->nxt_kdc, &nxt_kdc_str);
+ if (retval) goto cleanup;
+ fprintf(stderr, "%s: cur_tgt %s\n", prog, cur_tgt_str);
+ fprintf(stderr, "%s: cur_kdc %s\n", prog, cur_kdc_str);
+ fprintf(stderr, "%s: nxt_kdc %s\n", prog, nxt_kdc_str);
+cleanup:
+ if (cur_tgt_str)
+ krb5_free_unparsed_name(ts->ctx, cur_tgt_str);
+ if (cur_kdc_str)
+ krb5_free_unparsed_name(ts->ctx, cur_kdc_str);
+ if (nxt_kdc_str)
+ krb5_free_unparsed_name(ts->ctx, nxt_kdc_str);
+}
+
+static void
+tr_dbg_ret(struct tr_state *ts, const char *prog, krb5_error_code ret)
+{
+ fprintf(stderr, "%s: return %d (%s)\n", prog, (int)ret,
+ error_message(ret));
+}
- krb5_principal *tgs_list = NULL;
- krb5_principal *top_server = NULL;
- krb5_principal *next_server = NULL;
- unsigned int nservers = 0;
- krb5_boolean old_use_conf_ktypes = context->use_conf_ktypes;
- int retr_flags = KRB5_TC_MATCH_SRV_NAMEONLY | KRB5_TC_SUPPORTED_KTYPES;
- int free_tgt = 0, free_otgt = 0;
+static void
+tr_dbg_rtree(struct tr_state *ts, const char *prog, krb5_principal princ)
+{
+ char *str;
- /* in case we never get a TGT, zero the return */
+ if (krb5_unparse_name(ts->ctx, princ, &str))
+ return;
+ fprintf(stderr, "%s: %s\n", prog, str);
+ krb5_free_unparsed_name(ts->ctx, str);
+}
+#endif /* DEBUG_GC_FRM_KDC */
- *tgts = NULL;
-
- memset((char *)&tgtq, 0, sizeof(tgtq));
- memset((char *)&tgt, 0, sizeof(tgt));
- memset((char *)&otgt, 0, sizeof(otgt));
+/*
+ * tgt_mcred()
+ *
+ * Return MCREDS for use as a match criterion.
+ *
+ * Resulting credential has CLIENT as the client principal, and
+ * krbtgt/realm_of(DST)@realm_of(SRC) as the server principal. Zeroes
+ * 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
+tgt_mcred(krb5_context ctx, krb5_principal client,
+ krb5_principal dst, krb5_principal src,
+ krb5_creds *mcreds)
+{
+ krb5_error_code retval;
- /*
- * we know that the desired credentials aren't in the cache yet.
- *
- * To get them, we first need a tgt for the realm of the server.
- * first, we see if we have such a TGT in cache. if not, then
- * we ask the kdc to give us one. if that doesn't work, then
- * we try to get a tgt for a realm that is closest to the target.
- * once we have that, then we ask that realm if it can give us
- * tgt for the target. if not, we do the process over with this
- * new tgt.
- */
-
- /*
- * (the ticket may be issued by some other intermediate
- * realm's KDC; so we use KRB5_TC_MATCH_SRV_NAMEONLY)
- */
- if ((retval = krb5_copy_principal(context, in_cred->client, &tgtq.client)))
+ retval = 0;
+ memset(mcreds, 0, sizeof(*mcreds));
+
+ retval = krb5_copy_principal(ctx, client, &mcreds->client);
+ if (retval)
goto cleanup;
- /* get target tgt from cache */
- if ((retval = krb5_tgtname(context,
- krb5_princ_realm(context, in_cred->server),
- krb5_princ_realm(context, in_cred->client),
- &int_server))) {
+ retval = krb5_tgtname(ctx, krb5_princ_realm(ctx, dst),
+ krb5_princ_realm(ctx, src), &mcreds->server);
+ if (retval)
goto cleanup;
+
+cleanup:
+ if (retval)
+ krb5_free_cred_contents(ctx, mcreds);
+
+ return retval;
+}
+
+/*
+ * init_rtree()
+ *
+ * Populate KDC_LIST with the output of krb5_walk_realm_tree().
+ */
+static krb5_error_code
+init_rtree(struct tr_state *ts,
+ krb5_principal client, krb5_principal server)
+{
+ krb5_error_code retval;
+
+ ts->kdc_list = NULL;
+ retval = krb5_walk_realm_tree(ts->ctx, krb5_princ_realm(ts->ctx, client),
+ krb5_princ_realm(ts->ctx, server),
+ &ts->kdc_list, KRB5_REALM_BRANCH_CHAR);
+ if (retval)
+ return retval;
+
+ for (ts->nkdcs = 0; ts->kdc_list[ts->nkdcs]; ts->nkdcs++) {
+ assert(krb5_princ_size(ts->ctx, ts->kdc_list[ts->nkdcs]) == 2);
+ TR_DBG_RTREE(ts, "init_rtree", ts->kdc_list[ts->nkdcs]);
}
+ assert(ts->nkdcs > 1);
+ ts->lst_kdc = ts->kdc_list + ts->nkdcs - 1;
- if ((retval = krb5_copy_principal(context, int_server, &tgtq.server))) {
- goto cleanup;
+ ts->kdc_tgts = calloc(ts->nkdcs + 1, sizeof(krb5_creds));
+ if (ts->kdc_tgts == NULL)
+ return ENOMEM;
+
+ return 0;
+}
+
+/*
+ * retr_local_tgt()
+ *
+ * Prime CUR_TGT with the cached TGT of the client's local realm.
+ */
+static krb5_error_code
+retr_local_tgt(struct tr_state *ts, krb5_principal client)
+{
+ krb5_error_code retval;
+ krb5_creds tgtq;
+
+ memset(&tgtq, 0, sizeof(tgtq));
+ retval = tgt_mcred(ts->ctx, client, client, client, &tgtq);
+ if (retval)
+ return retval;
+
+ /* Match realm, unlike other ccache retrievals here. */
+ retval = krb5_cc_retrieve_cred(ts->ctx, ts->ccache,
+ KRB5_TC_SUPPORTED_KTYPES,
+ &tgtq, ts->nxt_cc_tgt);
+ krb5_free_cred_contents(ts->ctx, &tgtq);
+ if (!retval) {
+ shift_cc_tgts(ts);
+ ts->nxt_tgt = ts->cur_tgt = ts->cur_cc_tgt;
}
+ return retval;
+}
- context->use_conf_ktypes = 1;
- retval = krb5_cc_retrieve_cred(context, ccache, retr_flags, &tgtq, &tgt);
- if (retval == 0) {
- free_tgt = 1;
- /* Falls through to the got/finally have tgt */
- } else {
- if (retval != KRB5_CC_NOTFOUND && retval != KRB5_CC_NOT_KTYPE) {
- goto cleanup;
- }
+/*
+ * try_ccache()
+ *
+ * Attempt to retrieve desired NXT_TGT from ccache. Point NXT_TGT to
+ * it if successful.
+ */
+static krb5_error_code
+try_ccache(struct tr_state *ts, krb5_creds *tgtq)
+{
+ krb5_error_code retval;
+ TR_DBG(ts, "try_ccache");
+ retval = krb5_cc_retrieve_cred(ts->ctx, ts->ccache, RETR_FLAGS,
+ tgtq, ts->nxt_cc_tgt);
+ if (!retval) {
+ shift_cc_tgts(ts);
+ ts->nxt_tgt = ts->cur_cc_tgt;
+ }
+ TR_DBG_RET(ts, "try_ccache", retval);
+ 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
+ *
+ * KDC_LIST == { krbtgt/R1@R1, krbtgt/R2@R1, krbtgt/R3@R2,
+ * krbtgt/R4@R3, NULL }
+ * CUR_TGT->SERVER == krbtgt/R2@R1
+ * NXT_TGT->SERVER == krbtgt/R4@R2
+ *
+ * i.e., we got a ticket issued by R2 with remote realm R4, we want to
+ * find krbtgt/R4@R3, not krbtgt/R3@R2, even though we have no TGT
+ * 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
+find_nxt_kdc(struct tr_state *ts)
+{
+ krb5_data *r1, *r2;
+ krb5_principal *kdcptr;
+
+ TR_DBG(ts, "find_nxt_kdc");
+ assert(ts->nxt_tgt == ts->kdc_tgts[ts->ntgts-1]);
+ if (krb5_princ_size(ts->ctx, ts->nxt_tgt->server) != 2)
+ return KRB5_KDCREP_MODIFIED;
+
+ r1 = krb5_princ_component(ts->ctx, ts->nxt_tgt->server, 1);
+
+ for (kdcptr = ts->cur_kdc + 1; *kdcptr != NULL; kdcptr++) {
+
+ r2 = krb5_princ_component(ts->ctx, *kdcptr, 1);
+
+ if (r1 != NULL && r2 != NULL &&
+ r1->length == r2->length &&
+ !memcmp(r1->data, r2->data, r1->length)) {
+ break;
+ }
+ }
+ if (*kdcptr == NULL) {
/*
- * Note that we want to request a TGT from our local KDC, even
- * if we already have a TGT for some intermediate realm. The
- * reason is that our local KDC may have a shortcut to the
- * destination realm, and if it does we want to use the
- * shortcut because it will provide greater security. - bcn
- */
-
- /*
- * didn't find it in the cache so it is time to get a local
- * tgt and walk the realms tree.
+ * Not found; we probably got an unexpected realm referral.
+ * Don't touch NXT_KDC, thus allowing next_closest_tgt() to
+ * continue looping backwards.
*/
- krb5_free_principal(context, int_server);
- int_server = NULL;
- if ((retval = krb5_tgtname(context,
- krb5_princ_realm(context, in_cred->client),
- krb5_princ_realm(context, in_cred->client),
- &int_server))) {
- goto cleanup;
+ if (ts->ntgts > 0) {
+ /* Punt NXT_TGT from KDC_TGTS if bogus. */
+ krb5_free_creds(ts->ctx, ts->kdc_tgts[--ts->ntgts]);
}
-
- krb5_free_cred_contents(context, &tgtq);
- memset((char *)&tgtq, 0, sizeof(tgtq));
- if ((retval = krb5_copy_principal(context, in_cred->client,
- &tgtq.client)))
- goto cleanup;
- if ((retval = krb5_copy_principal(context, int_server, &tgtq.server)))
- goto cleanup;
+ TR_DBG_RET(ts, "find_nxt_kdc", KRB5_KDCREP_MODIFIED);
+ return KRB5_KDCREP_MODIFIED;
+ }
+ ts->nxt_kdc = kdcptr;
+ TR_DBG_RET(ts, "find_nxt_kdc", 0);
+ return 0;
+}
- if ((retval = krb5_cc_retrieve_cred(context, ccache,
- retr_flags,
- &tgtq, &tgt))) {
- goto cleanup;
- }
- free_tgt = 1;
-
- /* get a list of realms to consult */
-
- if ((retval = krb5_walk_realm_tree(context,
- krb5_princ_realm(context,
- in_cred->client),
- krb5_princ_realm(context,
- in_cred->server),
- &tgs_list,
- KRB5_REALM_BRANCH_CHAR))) {
+/*
+ * try_kdc()
+ *
+ * Using CUR_TGT, attempt to get desired NXT_TGT. Update NXT_KDC if
+ * successful.
+ */
+static krb5_error_code
+try_kdc(struct tr_state *ts, krb5_creds *tgtq)
+{
+ krb5_error_code retval;
+ krb5_creds ltgtq;
+
+ TR_DBG(ts, "try_kdc");
+ /* This check should probably be in gc_via_tkt. */
+ if (!krb5_c_valid_enctype(ts->cur_tgt->keyblock.enctype))
+ return KRB5_PROG_ETYPE_NOSUPP;
+
+ ltgtq = *tgtq;
+ ltgtq.is_skey = FALSE;
+ ltgtq.ticket_flags = ts->cur_tgt->ticket_flags;
+ retval = krb5_get_cred_via_tkt(ts->ctx, ts->cur_tgt,
+ FLAGS2OPTS(ltgtq.ticket_flags),
+ ts->cur_tgt->addresses,
+ <gtq, &ts->kdc_tgts[ts->ntgts++]);
+ if (retval) {
+ ts->ntgts--;
+ ts->nxt_tgt = ts->cur_tgt;
+ TR_DBG_RET(ts, "try_kdc", retval);
+ return retval;
+ }
+ ts->nxt_tgt = ts->kdc_tgts[ts->ntgts-1];
+ retval = find_nxt_kdc(ts);
+ TR_DBG_RET(ts, "try_kdc", 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
+kdc_mcred(struct tr_state *ts, krb5_principal client, krb5_creds *mcreds)
+{
+ krb5_error_code retval;
+ krb5_data *rdst, *rsrc;
+
+ retval = 0;
+ memset(mcreds, 0, sizeof(*mcreds));
+
+ rdst = krb5_princ_component(ts->ctx, *ts->nxt_kdc, 1);
+ rsrc = krb5_princ_component(ts->ctx, *ts->cur_kdc, 1);
+ retval = krb5_copy_principal(ts->ctx, client, &mcreds->client);
+ if (retval)
+ goto cleanup;
+
+ retval = krb5_tgtname(ts->ctx, rdst, rsrc, &mcreds->server);
+ if (retval)
+ goto cleanup;
+
+cleanup:
+ if (retval)
+ krb5_free_cred_contents(ts->ctx, mcreds);
+
+ 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
+next_closest_tgt(struct tr_state *ts, krb5_principal client)
+{
+ krb5_error_code retval;
+ krb5_creds tgtq;
+
+ retval = 0;
+ memset(&tgtq, 0, sizeof(tgtq));
+
+ for (ts->nxt_kdc = ts->lst_kdc;
+ ts->nxt_kdc > ts->cur_kdc;
+ ts->nxt_kdc--) {
+
+ krb5_free_cred_contents(ts->ctx, &tgtq);
+ retval = kdc_mcred(ts, client, &tgtq);
+ if (retval)
goto cleanup;
+ /* Don't waste time retrying ccache for direct path. */
+ if (ts->cur_kdc != ts->kdc_list || ts->nxt_kdc != ts->lst_kdc) {
+ retval = try_ccache(ts, &tgtq);
+ if (!retval)
+ break;
+ if (HARD_CC_ERR(retval))
+ goto cleanup;
}
-
- for (nservers = 0; tgs_list[nservers]; nservers++)
- ;
-
- /* allocate storage for TGT pointers. */
-
- if (!(ret_tgts = (krb5_creds **) calloc(nservers+1,
- sizeof(krb5_creds)))) {
- retval = ENOMEM;
- goto cleanup;
+ /* Not in the ccache, so talk to a KDC. */
+ retval = try_kdc(ts, &tgtq);
+ if (!retval) {
+ break;
}
- *tgts = ret_tgts;
-
/*
- * step one is to take the current tgt and see if there is a tgt for
- * krbtgt/realmof(target)@realmof(tgt). if not, try to get one with
- * the tgt.
- *
- * if we don't get a tgt for the target, then try to find a tgt as
- * close to the target realm as possible. at each step if there isn't
- * a tgt in the cache we have to try and get one with our latest tgt.
- * once we have a tgt for a closer realm, we go back to step one.
- *
- * once we have a tgt for the target, we go try and get credentials.
+ * 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.)
*/
-
- for (top_server = tgs_list;
- top_server < tgs_list + nservers;
- top_server = next_server) {
-
- /* look in cache for a tgt for the destination */
-
- krb5_free_cred_contents(context, &tgtq);
- memset(&tgtq, 0, sizeof(tgtq));
- if ((retval = krb5_copy_principal(context, tgt.client,
- &tgtq.client)))
- goto cleanup;
+ }
+ /*
+ * If we have a non-zero retval, we either have a hard error or we
+ * failed to find a closer TGT.
+ */
+cleanup:
+ krb5_free_cred_contents(ts->ctx, &tgtq);
+ return retval;
+}
- krb5_free_principal(context, int_server);
- int_server = NULL;
- if ((retval = krb5_tgtname(context,
- krb5_princ_realm(context,
- in_cred->server),
- krb5_princ_realm(context, *top_server),
- &int_server))) {
- goto cleanup;
- }
-
- if ((retval = krb5_copy_principal(context, int_server,
- &tgtq.server)))
- goto cleanup;
+/*
+ * 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:
+ *
+ * krbtgt/REMOTE@LOCAL
+ *
+ * krb5_walk_realm_tree() returns a list like:
+ *
+ * krbtgt/R1@R1, krbtgt/R2@R1, krbtgt/R3@R2, ...
+ *
+ * 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
+ *
+ * krbtgt/R1@R1, krbtgt/R2@R1, krbtgt/R3@R2, krbtgt/R4@R3,
+ * krbtgt/R5@R4
+ *
+ * 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 |
+ * +----+----+----+----+----+
+ *
+ * next_closest_tgt() returns after finding a ticket for krbtgt/R3@R1:
+ *
+ * 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
+do_traversal(krb5_context ctx,
+ krb5_ccache ccache,
+ krb5_principal client,
+ krb5_principal server,
+ krb5_creds *out_cc_tgt,
+ krb5_creds **out_tgt,
+ krb5_creds ***out_kdc_tgts)
+{
+ krb5_error_code retval;
+ struct tr_state state, *ts;
+
+ *out_tgt = NULL;
+ *out_kdc_tgts = NULL;
+ ts = &state;
+ memset(ts, 0, sizeof(*ts));
+ ts->ctx = ctx;
+ ts->ccache = ccache;
+ init_cc_tgts(ts);
+
+ retval = init_rtree(ts, client, server);
+ if (retval)
+ goto cleanup;
- if (free_otgt)
- krb5_free_cred_contents(context, &otgt);
- otgt = tgt;
- free_otgt = free_tgt;
- free_tgt = 0;
-
- retval = krb5_cc_retrieve_cred(context, ccache, retr_flags,
- &tgtq, &tgt);
- if (retval == 0) {
- free_tgt = 1;
- /* We are now done - proceed to got/finally have tgt */
- } else {
- if (retval != KRB5_CC_NOTFOUND && retval != KRB5_CC_NOT_KTYPE) {
- goto cleanup;
- }
-
- /* didn't find it in the cache so try and get one */
- /* with current tgt. */
- /* Copy back in case invalided */
- tgt = otgt;
- free_tgt = free_otgt;
- free_otgt = 0;
- if (!krb5_c_valid_enctype(tgt.keyblock.enctype)) {
- retval = KRB5_PROG_ETYPE_NOSUPP;
- goto cleanup;
- }
-
- krb5_free_cred_contents(context, &tgtq);
- memset(&tgtq, 0, sizeof(tgtq));
- tgtq.times = tgt.times;
- if ((retval = krb5_copy_principal(context, tgt.client,
- &tgtq.client)))
- goto cleanup;
- if ((retval = krb5_copy_principal(context, int_server,
- &tgtq.server)))
- goto cleanup;
- tgtq.is_skey = FALSE;
- tgtq.ticket_flags = tgt.ticket_flags;
- retval = krb5_get_cred_via_tkt(context, &tgt,
- FLAGS2OPTS(tgtq.ticket_flags),
- tgt.addresses, &tgtq, &tgtr);
- if (retval == 0) {
- /* Falls through to 'Got a tgt. If it is for the target' */
- ;
- } else {
- /*
- * Couldn't get one so now loop backwards through
- * the realms list and try and get a tgt for a
- * realm as close to the target as possible. The
- * kdc should give us a tgt for the closest one it
- * knows about, but not all kdc's do this yet.
- */
- for (next_server = tgs_list + nservers - 1;
- next_server > top_server;
- next_server--) {
- krb5_free_cred_contents(context, &tgtq);
- memset(&tgtq, 0, sizeof(tgtq));
- if ((retval = krb5_copy_principal(context, tgt.client,
- &tgtq.client)))
- goto cleanup;
-
- krb5_free_principal(context, int_server);
- int_server = NULL;
- if ((retval = krb5_tgtname(context,
- krb5_princ_realm(context, *next_server),
- krb5_princ_realm(context, *top_server),
- &int_server))) {
- goto cleanup;
- }
-
- if ((retval = krb5_copy_principal(context, int_server,
- &tgtq.server)))
- goto cleanup;
-
- if (free_otgt)
- krb5_free_cred_contents(context, &otgt);
- otgt = tgt;
- free_otgt = free_tgt;
- free_tgt = 0;
- retval = krb5_cc_retrieve_cred(context, ccache,
- retr_flags,
- &tgtq, &tgt);
- if (retval == 0) {
- free_tgt = 1;
- /* Continues with 'got one as close as possible' */
- } else {
- if (retval != KRB5_CC_NOTFOUND) {
- goto cleanup;
- }
-
- /* not in the cache so try and get one with our current tgt. */
-
- tgt = otgt;
- free_tgt = free_otgt;
- free_otgt = 0;
- if (!krb5_c_valid_enctype(tgt.keyblock.enctype)) {
- retval = KRB5_PROG_ETYPE_NOSUPP;
- goto cleanup;
- }
-
- krb5_free_cred_contents(context, &tgtq);
- memset(&tgtq, 0, sizeof(tgtq));
- tgtq.times = tgt.times;
- if ((retval = krb5_copy_principal(context,
- tgt.client,
- &tgtq.client)))
- goto cleanup;
- if ((retval = krb5_copy_principal(context,
- int_server,
- &tgtq.server)))
- goto cleanup;
- tgtq.is_skey = FALSE;
- tgtq.ticket_flags = tgt.ticket_flags;
- retval = krb5_get_cred_via_tkt(context, &tgt,
- FLAGS2OPTS(tgtq.ticket_flags),
- tgt.addresses,
- &tgtq, &tgtr);
- if (retval)
- continue;
-
- /* save tgt in return array */
- if ((retval = krb5_copy_creds(context, tgtr,
- &ret_tgts[ntgts]))) {
- goto cleanup;
- }
- krb5_free_creds(context, tgtr);
- tgtr = NULL;
-
- if (free_tgt) {
- krb5_free_cred_contents(context, &tgt);
- free_tgt = 0;
- }
-
- tgt = *ret_tgts[ntgts++];
- }
-
- /* got one as close as possible, now start all over */
-
- break;
- }
-
- if (next_server == top_server) {
- goto cleanup;
- }
- continue;
- }
-
- /*
- * Got a tgt. If it is for the target realm we can go
- * try for the credentials. If it is not for the
- * target realm, then make sure it is in the realms
- * hierarchy and if so, save it and start the loop
- * over from there. Note that we only need to compare
- * the instance names since that is the target realm
- * of the tgt.
- */
-
- for (next_server = top_server; *next_server; next_server++) {
- krb5_data *realm_1 = krb5_princ_component(context,
- next_server[0],
- 1);
- krb5_data *realm_2 = krb5_princ_component(context,
- tgtr->server, 1);
- if (realm_1 != NULL &&
- realm_2 != NULL &&
- realm_1->length == realm_2->length &&
- !memcmp(realm_1->data, realm_2->data, realm_1->length)) {
- break;
- }
- }
-
- if (!next_server) {
- retval = KRB5_KDCREP_MODIFIED;
- goto cleanup;
- }
-
- if ((retval = krb5_copy_creds(context, tgtr, &ret_tgts[ntgts]))) {
- goto cleanup;
- }
- krb5_free_creds(context, tgtr);
- tgtr = NULL;
-
- if (free_tgt) {
- krb5_free_cred_contents(context, &tgt);
- free_tgt = 0;
- }
-
- tgt = *ret_tgts[ntgts++];
-
- /* we're done if it is the target */
-
- if (!*next_server++) break;
- }
- }
+ retval = retr_local_tgt(ts, client);
+ if (retval)
+ goto cleanup;
+
+ for (ts->cur_kdc = ts->kdc_list, ts->nxt_kdc = NULL;
+ ts->cur_kdc != NULL && ts->cur_kdc < ts->lst_kdc;
+ ts->cur_kdc = ts->nxt_kdc, ts->cur_tgt = ts->nxt_tgt) {
+
+ retval = next_closest_tgt(ts, client);
+ if (retval)
+ goto cleanup;
+ assert(ts->cur_kdc != ts->nxt_kdc);
+ }
+
+ if (NXT_TGT_IS_CACHED(ts)) {
+ *out_cc_tgt = *ts->cur_cc_tgt;
+ *out_tgt = out_cc_tgt;
+ MARK_CUR_CC_TGT_CLEAN(ts);
+ } else {
+ /* CUR_TGT is somewhere in KDC_TGTS; no need to copy. */
+ *out_tgt = ts->nxt_tgt;
}
- /* got/finally have tgt! try for the creds */
+cleanup:
+ clean_cc_tgts(ts);
+ if (ts->kdc_list != NULL)
+ krb5_free_realm_tree(ctx, ts->kdc_list);
+ if (ts->ntgts == 0) {
+ *out_kdc_tgts = NULL;
+ if (ts->kdc_tgts != NULL)
+ free(ts->kdc_tgts);
+ } else
+ *out_kdc_tgts = ts->kdc_tgts;
+ return retval;
+}
- if (!krb5_c_valid_enctype(tgt.keyblock.enctype)) {
+/*
+ * 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.
+ *
+ * Returns errors, system errors.
+ */
+
+static krb5_error_code
+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_boolean old_use_conf_ktypes;
+
+ client = in_cred->client;
+ server = in_cred->server;
+ memset(&cc_tgt, 0, sizeof(cc_tgt));
+ memset(&tgtq, 0, sizeof(tgtq));
+ tgtptr = NULL;
+ *tgts = NULL;
+ old_use_conf_ktypes = context->use_conf_ktypes;
+
+ /*
+ * Get a TGT for the target realm.
+ */
+
+ 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)) {
+ /* Not in ccache, so traverse the transit path. */
+ retval = do_traversal(context, ccache, client, server,
+ &cc_tgt, &tgtptr, tgts);
+ }
+ if (retval)
+ goto cleanup;
+
+ /*
+ * Finally have TGT for target realm! Try using it to get creds.
+ */
+
+ if (!krb5_c_valid_enctype(tgtptr->keyblock.enctype)) {
retval = KRB5_PROG_ETYPE_NOSUPP;
goto cleanup;
}
context->use_conf_ktypes = old_use_conf_ktypes;
- retval = krb5_get_cred_via_tkt(context, &tgt,
- FLAGS2OPTS(tgt.ticket_flags) |
+ 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),
- tgt.addresses, in_cred, out_cred);
-
- /* cleanup and return */
+ tgtptr->addresses, in_cred, out_cred);
cleanup:
-
- if (tgtr) krb5_free_creds(context, tgtr);
- if(tgs_list) krb5_free_realm_tree(context, tgs_list);
- krb5_free_cred_contents(context, &tgtq);
- if (int_server) krb5_free_principal(context, int_server);
- if (ntgts == 0) {
- *tgts = NULL;
- if (ret_tgts) free(ret_tgts);
- }
- if (free_otgt)
- krb5_free_cred_contents(context, &otgt);
- if (free_tgt)
- krb5_free_cred_contents(context, &tgt);
+ krb5_free_cred_contents(context, &tgtq);
+ if (tgtptr == &cc_tgt)
+ krb5_free_cred_contents(context, tgtptr);
context->use_conf_ktypes = old_use_conf_ktypes;
- return(retval);
+
+ return retval;
}
krb5_error_code