svn rev #23854: branches/iakerb/src/lib/krb5/krb/
ghudson@MIT.EDU
ghudson at MIT.EDU
Sun Apr 4 13:17:17 EDT 2010
http://src.mit.edu/fisheye/changelog/krb5/?cs=23854
Commit By: ghudson
Log Message:
Rewrite gc_frm_kdc_step.c to handle the full functionality of
gc_frm_krb.c. Not tested yet.
Changed Files:
U branches/iakerb/src/lib/krb5/krb/gc_frm_kdc_step.c
Modified: branches/iakerb/src/lib/krb5/krb/gc_frm_kdc_step.c
===================================================================
--- branches/iakerb/src/lib/krb5/krb/gc_frm_kdc_step.c 2010-04-02 18:03:11 UTC (rev 23853)
+++ branches/iakerb/src/lib/krb5/krb/gc_frm_kdc_step.c 2010-04-04 17:17:17 UTC (rev 23854)
@@ -36,415 +36,963 @@
#include <stdio.h>
#include "int-proto.h"
-#define KRB5_TKT_CREDS_STEP_FLAG_COMPLETE 0x1
-#define KRB5_TKT_CREDS_STEP_FLAG_CTX_KTYPES 0x2
+/*
+ * krb5_tkt_creds_step() is implemented using a tail call style. Every
+ * begin_*, step_*, or *_request function is responsible for returning an
+ * error, generating the next request, or delegating to another function using
+ * a tail call.
+ *
+ * The process is divided up into states which govern how the next input token
+ * should be interpreted. Each state has a "begin_<state>" function to set up
+ * the context fields related to the state, a "step_<state>" function to
+ * process a reply and update the related context fields, and possibly a
+ * "<state>_request" function (invoked by the begin_ and step_ functions) to
+ * generate the next request. If it's time to advance to another state, any of
+ * the three functions can make a tail call to begin_<nextstate> to do so.
+ *
+ * For the most part the state is always increasing, but we may go from
+ * REFERRALS to GET_TGT in order to get a TGT for the fallback realm. The
+ * getting_tgt_for field in the context keeps track of what state we will go to
+ * after successfully obtaining a foreign TGT, and the end_get_tgt() function
+ * advances to the proper next state.
+ */
+enum state {
+ STATE_BEGIN, /* Initial step (no input token) */
+ STATE_GET_TGT, /* Getting TGT for service realm */
+ STATE_GET_TGT_OFFPATH, /* Getting TGT via off-path referrals */
+ STATE_REFERRALS, /* Retrieving service ticket or referral */
+ STATE_NON_REFERRAL, /* Non-referral service ticket request */
+ STATE_COMPLETE /* Creds ready for retrieval */
+};
+
struct _krb5_tkt_creds_context {
- krb5_ccache ccache;
- krb5_creds in_cred;
- krb5_principal client;
- krb5_principal server;
- krb5_principal req_server;
- int req_kdcopt;
+ enum state state; /* What we should do with the next reply */
+ enum state getting_tgt_for; /* STATE_REFERRALS or STATE_NON_REFERRAL */
- unsigned int flags;
- krb5_creds cc_tgt;
- krb5_creds *tgtptr;
- unsigned int referral_count;
- krb5_creds *referral_tgts[KRB5_REFERRAL_MAXHOPS];
- krb5_boolean default_use_conf_ktypes;
- krb5_timestamp timestamp;
- krb5_int32 nonce;
- int kdcopt;
- krb5_keyblock *subkey;
- krb5_data encoded_previous_request;
+ /* The following fields are set up at initialization time. */
+ krb5_creds *in_creds; /* Creds requested by caller */
+ krb5_principal client; /* Caller-requested client principal (alias) */
+ krb5_principal server; /* Server principal (alias) */
+ krb5_principal req_server; /* Caller-requested server principal */
+ krb5_ccache ccache; /* Caller-provided ccache (alias) */
+ int req_kdcopt; /* Caller-requested KDC options */
+ krb5_authdata **authdata; /* Caller-requested authdata */
- krb5_creds *out_cred;
+ /* The following fields are used in multiple steps. */
+ krb5_creds *cur_tgt; /* TGT to be used for next query */
+ krb5_data *realms_seen; /* For loop detection */
+
+ /* The following fields track state between request and reply. */
+ krb5_timestamp timestamp; /* Timestamp of request */
+ krb5_int32 nonce; /* Nonce of request */
+ int kdcopt; /* KDC options of request */
+ krb5_keyblock *subkey; /* subkey of request */
+ krb5_data previous_request; /* Encoded request (for TCP retransmission) */
+
+ /* The following fields are used when acquiring foreign TGTs. */
+ krb5_data *realm_path; /* Path from client to server realm */
+ const krb5_data *last_realm;/* Last realm in realm_path */
+ const krb5_data *cur_realm; /* Position of cur_tgt in realm_path */
+ const krb5_data *next_realm;/* Current target realm in realm_path */
+ unsigned int offpath_count; /* Offpath requests made */
+
+ /* The following fields are used during the referrals loop. */
+ unsigned int referral_count;/* Referral requests made */
+
+ /* The following fields are used within a _step call to avoid
+ * passing them as parameters everywhere. */
+ krb5_creds *reply_creds; /* Creds from TGS reply */
+ krb5_error_code reply_code; /* Error status from TGS reply */
+ krb5_data *caller_out; /* Caller's out parameter */
+ krb5_data *caller_realm; /* Caller's realm parameter */
+ unsigned int *caller_flags; /* Caller's flags parameter */
};
/* Convert ticket flags to necessary KDC options */
#define FLAGS2OPTS(flags) (flags & KDC_TKT_COMMON_MASK)
static krb5_error_code
-tkt_make_tgs_request(krb5_context context,
- krb5_tkt_creds_context ctx,
- krb5_creds *tgt,
- krb5_creds *in_cred,
- krb5_data *req)
-{
- krb5_error_code code;
+begin_get_tgt(krb5_context context, krb5_tkt_creds_context ctx);
- /* These flags are always included */
- ctx->kdcopt |= FLAGS2OPTS(tgt->ticket_flags);
-
- if ((ctx->kdcopt & KDC_OPT_ENC_TKT_IN_SKEY) == 0)
- in_cred->is_skey = FALSE;
-
- if (!krb5_c_valid_enctype(tgt->keyblock.enctype))
- return KRB5_PROG_ETYPE_NOSUPP;
-
- code = krb5int_make_tgs_request(context, tgt, ctx->kdcopt,
- tgt->addresses, NULL,
- in_cred, NULL, NULL, req,
- &ctx->timestamp, &ctx->nonce, &ctx->subkey);
- return code;
-}
-
+/*
+ * Fill in the caller out, realm, and flags output variables. out is filled in
+ * with ctx->previous_request, which the caller should set, and realm is filled
+ * in with the realm of ctx->cur_tgt.
+ */
static krb5_error_code
-tkt_process_tgs_reply(krb5_context context,
- krb5_tkt_creds_context ctx,
- krb5_data *rep,
- krb5_creds *tgt,
- krb5_creds *in_cred,
- krb5_creds **out_cred)
+set_caller_request(krb5_context context, krb5_tkt_creds_context ctx)
{
krb5_error_code code;
+ const krb5_data *req = &ctx->previous_request;
+ const krb5_data *realm = &ctx->cur_tgt->server->data[1];
+ krb5_data out_copy = empty_data(), realm_copy = empty_data();
- code = krb5int_process_tgs_reply(context,
- rep,
- tgt,
- ctx->kdcopt,
- tgt->addresses,
- NULL,
- in_cred,
- ctx->timestamp,
- ctx->nonce,
- ctx->subkey,
- NULL,
- NULL,
- out_cred);
+ code = krb5int_copy_data_contents(context, req, &out_copy);
+ if (code != 0)
+ goto cleanup;
+ code = krb5int_copy_data_contents(context, realm, &realm_copy);
+ if (code != 0)
+ goto cleanup;
+ *ctx->caller_out = out_copy;
+ *ctx->caller_realm = realm_copy;
+ *ctx->caller_flags = 1;
+ return 0;
+
+cleanup:
+ krb5_free_data_contents(context, &out_copy);
+ krb5_free_data_contents(context, &realm_copy);
return code;
}
/*
- * Asynchronous API
+ * Point *TGT at an allocated credentials structure containing a TGT for realm
+ * retrieved from ctx->ccache. If we are retrieving a foreign TGT, accept any
+ * issuing realm (i.e. match only the service principal name). If the TGT is
+ * not found in the cache, return successfully but set *tgt to NULL.
*/
-krb5_error_code KRB5_CALLCONV
-krb5_tkt_creds_init(krb5_context context,
- krb5_ccache ccache,
- krb5_creds *creds,
- int kdcopt,
- krb5_tkt_creds_context *pctx)
+static krb5_error_code
+get_cached_tgt(krb5_context context, krb5_tkt_creds_context ctx,
+ const krb5_data *realm, krb5_creds **tgt)
{
+ krb5_creds mcreds, *creds = NULL;
krb5_error_code code;
- krb5_tkt_creds_context ctx = NULL;
- krb5_creds tgtq;
- krb5_flags flags = KRB5_TC_MATCH_SRV_NAMEONLY | KRB5_TC_SUPPORTED_KTYPES;
+ krb5_principal tgtname = NULL;
+ krb5_flags flags;
- memset(&tgtq, 0, sizeof(tgtq));
+ *tgt = NULL;
- ctx = k5alloc(sizeof(*ctx), &code);
+ /* Construct the principal krbtgt/<realm>@<client realm>. The realm
+ * won't matter unless we're getting the local TGT. */
+ code = krb5int_tgtname(context, realm, &ctx->client->realm, &tgtname);
if (code != 0)
goto cleanup;
- code = krb5int_copy_creds_contents(context, creds, &ctx->in_cred);
- if (code != 0)
+ /* Match the TGT realm only if we're getting the local TGT. */
+ flags = KRB5_TC_SUPPORTED_KTYPES;
+ if (!data_eq(*realm, ctx->client->realm))
+ flags |= KRB5_TC_MATCH_SRV_NAMEONLY;
+
+ /* Allocate a structure for the resulting creds. */
+ creds = k5alloc(sizeof(*creds), &code);
+ if (creds == NULL)
goto cleanup;
- ctx->ccache = ccache; /* XXX */
+ /* Construct a matching cred for the ccache query. */
+ memset(&mcreds, 0, sizeof(mcreds));
+ mcreds.client = ctx->client;
+ mcreds.server = tgtname;
- ctx->req_kdcopt = kdcopt;
- ctx->default_use_conf_ktypes = context->use_conf_ktypes;
- ctx->client = ctx->in_cred.client;
- ctx->server = ctx->in_cred.server;
-
- code = krb5_copy_principal(context, ctx->server, &ctx->req_server);
- if (code != 0)
+ /* Fetch the TGT credential, handling not-found errors. */
+ context->use_conf_ktypes = TRUE;
+ code = krb5_cc_retrieve_cred(context, ctx->ccache, flags, &mcreds,
+ creds);
+ context->use_conf_ktypes = FALSE;
+ if (code != 0 && code != KRB5_CC_NOTFOUND && code != KRB5_CC_NOT_KTYPE)
goto cleanup;
+ if (code == 0) {
+ *tgt = creds;
+ creds = NULL;
+ }
+ code = 0;
- code = krb5int_tgt_mcred(context, ctx->client, ctx->client,
- ctx->client, &tgtq);
- if (code != 0)
- goto cleanup;
+cleanup:
+ krb5_free_principal(context, tgtname);
+ free(creds);
+ return code;
+}
- code = krb5_cc_retrieve_cred(context, ctx->ccache, flags,
- &tgtq, &ctx->cc_tgt);
- if (code != 0)
- goto cleanup;
+/*
+ * Set up the request given by in_cred, using ctx->cur_tgt. KDC options for
+ * the requests are determined by ctx->cur_tgt->ticket_flags and
+ * extra_options.
+ */
+static krb5_error_code
+make_request(krb5_context context, krb5_tkt_creds_context ctx,
+ krb5_creds *in_cred, int extra_options)
+{
+ krb5_error_code code;
+ krb5_data request = empty_data();
- ctx->tgtptr = &ctx->cc_tgt;
+ ctx->kdcopt = extra_options | FLAGS2OPTS(ctx->cur_tgt->ticket_flags);
- *pctx = ctx;
+ /* XXX This check belongs in gc_via_tgt.c or nowhere. */
+ if (!krb5_c_valid_enctype(ctx->cur_tgt->keyblock.enctype))
+ return KRB5_PROG_ETYPE_NOSUPP;
-cleanup:
+ code = krb5int_make_tgs_request(context, ctx->cur_tgt, ctx->kdcopt,
+ ctx->cur_tgt->addresses,
+ NULL, in_cred, NULL, NULL, &request,
+ &ctx->timestamp, &ctx->nonce,
+ &ctx->subkey);
if (code != 0)
- krb5_tkt_creds_free(context, ctx);
- krb5_free_cred_contents(context, &tgtq);
+ return code;
- return code;
+ krb5_free_data_contents(context, &ctx->previous_request);
+ ctx->previous_request = request;
+ return set_caller_request(context, ctx);
}
-krb5_error_code KRB5_CALLCONV
-krb5_tkt_creds_get_creds(krb5_context context,
- krb5_tkt_creds_context ctx,
- krb5_creds *creds)
+/* Set up a request for a TGT for realm, using ctx->cur_tgt. */
+static krb5_error_code
+make_request_for_tgt(krb5_context context, krb5_tkt_creds_context ctx,
+ const krb5_data *realm)
{
krb5_error_code code;
+ krb5_creds mcreds;
+ krb5_principal tgtname = NULL;
- if (ctx->flags & KRB5_TKT_CREDS_STEP_FLAG_COMPLETE)
- code = krb5int_copy_creds_contents(context, ctx->out_cred, creds);
- else
- code = KRB5_NO_TKT_SUPPLIED;
+ /* Construct the principal krbtgt/<realm>@<cur-tgt-realm>. */
+ code = krb5int_tgtname(context, realm, &ctx->cur_tgt->server->realm,
+ &tgtname);
+ if (code != 0)
+ return code;
+ /* Make a request for the specified TGT with no extra flags. */
+ memset(&mcreds, 0, sizeof(mcreds));
+ mcreds.client = ctx->client;
+ mcreds.server = tgtname;
+ code = make_request(context, ctx, &mcreds, 0);
+ krb5_free_principal(context, tgtname);
return code;
}
-/*
- * Store credentials in credentials cache. If ccache is NULL, the
- * credentials cache associated with the context is used. This can
- * be called on an incomplete context, in which case the referral
- * TGT only will be stored.
- */
-krb5_error_code KRB5_CALLCONV
-krb5_tkt_creds_store_creds(krb5_context context,
- krb5_tkt_creds_context ctx,
- krb5_ccache ccache)
+/* Set up a request for the desired service principal, using ctx->cur_tgt.
+ * Optionally allow the answer to be a referral. */
+static krb5_error_code
+make_request_for_service(krb5_context context, krb5_tkt_creds_context ctx,
+ krb5_boolean referral)
{
krb5_error_code code;
+ int extra_options;
- if (ccache == NULL)
- ccache = ctx->ccache;
+ /* Include the caller-specified KDC options in service requests. */
+ extra_options = ctx->kdcopt;
- /* Only store the referral from our local KDC */
- if (ctx->referral_tgts[0] != NULL)
- krb5_cc_store_cred(context, ccache, ctx->referral_tgts[0]);
+ /* Automatically set the enc-tkt-in-skey flag for user-to-user requests. */
+ if (ctx->in_creds->second_ticket.length != 0 &&
+ (extra_options & KDC_OPT_CNAME_IN_ADDL_TKT) == 0)
+ extra_options |= KDC_OPT_ENC_TKT_IN_SKEY;
- if (ctx->flags & KRB5_TKT_CREDS_STEP_FLAG_COMPLETE)
- code = krb5_cc_store_cred(context, ccache, ctx->out_cred);
- else
- code = KRB5_NO_TKT_SUPPLIED;
+ /* Set the canonicalize flag for referral requests. */
+ if (referral)
+ extra_options |= KDC_OPT_CANONICALIZE;
+ /*
+ * Use the profile enctypes for referral requests, since we might get back
+ * a TGT. We'll ask again with context enctypes if we get the actual
+ * service ticket and it's not consistent with the context enctypes.
+ */
+ if (referral)
+ context->use_conf_ktypes = TRUE;
+ code = make_request(context, ctx, ctx->in_creds, extra_options);
+ if (referral)
+ context->use_conf_ktypes = FALSE;
return code;
}
-krb5_error_code KRB5_CALLCONV
-krb5_tkt_creds_get_times(krb5_context context,
- krb5_tkt_creds_context ctx,
- krb5_ticket_times *times)
+/* Decode and decrypt a TGS reply, and set the reply_code or reply_creds field
+ * of ctx with the result. Also handle too-big errors. */
+static krb5_error_code
+get_creds_from_tgs_reply(krb5_context context, krb5_tkt_creds_context ctx,
+ krb5_data *reply)
{
- if ((ctx->flags & KRB5_TKT_CREDS_STEP_FLAG_COMPLETE) == 0)
- return KRB5_NO_TKT_SUPPLIED;
+ krb5_error_code code;
- *times = ctx->out_cred->times;
+ krb5_free_creds(context, ctx->reply_creds);
+ ctx->reply_creds = NULL;
+ code = krb5int_process_tgs_reply(context, reply, ctx->cur_tgt, ctx->kdcopt,
+ ctx->cur_tgt->addresses, NULL,
+ ctx->in_creds, ctx->timestamp,
+ ctx->nonce, ctx->subkey, NULL, NULL,
+ &ctx->reply_creds);
+ if (code == KRB5KRB_ERR_RESPONSE_TOO_BIG) {
+ /* Instruct the caller to re-send the request with TCP. */
+ code = set_caller_request(context, ctx);
+ if (code != 0)
+ return code;
+ return KRB5KRB_ERR_RESPONSE_TOO_BIG;
+ }
+ /* Depending on our state, we may or may not be able to handle an error.
+ * For now, store it in the context and return success. */
+ ctx->reply_code = code;
return 0;
}
-void KRB5_CALLCONV
-krb5_tkt_creds_free(krb5_context context,
- krb5_tkt_creds_context ctx)
+/* Add realm to ctx->realms_seen so that we can avoid revisiting it later. */
+static krb5_error_code
+remember_realm(krb5_context context, krb5_tkt_creds_context ctx,
+ const krb5_data *realm)
{
- int i;
+ size_t len = 0;
+ krb5_data *new_list;
- if (ctx == NULL)
- return;
+ if (ctx->realms_seen != NULL) {
+ for (len = 0; ctx->realms_seen[len].data != NULL; len++);
+ }
+ new_list = realloc(ctx->realms_seen, (len + 2) * sizeof(krb5_data));
+ if (new_list == NULL)
+ return ENOMEM;
+ ctx->realms_seen = new_list;
+ new_list[len] = empty_data();
+ new_list[len + 1] = empty_data();
+ return krb5int_copy_data_contents(context, realm, &new_list[len]);
+}
- krb5_free_principal(context, ctx->req_server);
- krb5_free_cred_contents(context, &ctx->in_cred);
- krb5_free_creds(context, ctx->out_cred);
- krb5_free_data_contents(context, &ctx->encoded_previous_request);
- krb5_free_keyblock(context, ctx->subkey);
+/* Return TRUE if realm appears to ctx->realms_seen. */
+static krb5_boolean
+seen_realm_before(krb5_context context, krb5_tkt_creds_context ctx,
+ const krb5_data *realm)
+{
+ size_t i;
- /* Free referral TGTs list. */
- for (i = 0; i < KRB5_REFERRAL_MAXHOPS; i++) {
- if (ctx->referral_tgts[i] != NULL) {
- krb5_free_creds(context, ctx->referral_tgts[i]);
- ctx->referral_tgts[i] = NULL;
+ if (ctx->realms_seen != NULL) {
+ for (i = 0; ctx->realms_seen[i].data != NULL; i++) {
+ if (data_eq(ctx->realms_seen[i], *realm))
+ return TRUE;
}
}
+ return FALSE;
+}
- free(ctx);
+/***** STATE_NON_REFERRAL *****/
+
+/* Process the response to a non-referral request. */
+static krb5_error_code
+step_non_referral(krb5_context context, krb5_tkt_creds_context ctx)
+{
+ /* No fallbacks if we didn't get a successful reply. */
+ if (ctx->reply_code)
+ return ctx->reply_code;
+
+ /* Note the authdata we asked for in the output creds. */
+ ctx->reply_creds->authdata = ctx->authdata;
+ ctx->authdata = NULL;
+ ctx->state = STATE_COMPLETE;
+ return 0;
}
+/* Make a non-referrals request for the desired service ticket. */
static krb5_error_code
-tkt_creds_step_request(krb5_context context,
- krb5_tkt_creds_context ctx,
- krb5_data *req)
+begin_non_referral(krb5_context context, krb5_tkt_creds_context ctx)
{
+ ctx->state = STATE_NON_REFERRAL;
+ return make_request_for_service(context, ctx, FALSE);
+}
+
+/***** STATE_REFERRALS *****/
+
+/*
+ * Retry a request in the fallback realm after a referral request failure in
+ * the local realm. We only do this if the originally requested service
+ * principal was in the referral realm.
+ */
+static krb5_error_code
+try_fallback_realm(krb5_context context, krb5_tkt_creds_context ctx)
+{
krb5_error_code code;
+ char **hrealms;
+ krb5_creds *server_tgt;
- if (ctx->referral_count >= KRB5_REFERRAL_MAXHOPS)
+ if (ctx->server->length < 2) {
+ /* We need a type/host format principal to find a fallback realm. */
+ return KRB5_ERR_HOST_REALM_UNKNOWN;
+ }
+
+ /* We expect this to give exactly one answer (XXX clean up interface). */
+ code = krb5_get_fallback_host_realm(context, &ctx->server->data[1],
+ &hrealms);
+ if (code != 0)
+ return code;
+
+ if (data_eq_string(ctx->server->realm, hrealms[0])) {
+ /* Fallback realm isn't any different, so just give up. */
+ return KRB5_ERR_HOST_REALM_UNKNOWN;
+ }
+
+ /* Rewrite server->realm to be the fallback realm. */
+ krb5_free_data_contents(context, &ctx->server->realm);
+ ctx->server->realm = string2data(hrealms[0]);
+ free(hrealms);
+
+ /* Obtain a TGT for the new service realm. */
+ ctx->getting_tgt_for = STATE_NON_REFERRAL;
+ return begin_get_tgt(context, ctx);
+}
+
+/* Return true if context contains app-provided TGS enctypes and enctype is not
+ * one of them. */
+static krb5_boolean
+wrong_enctype(krb5_context context, krb5_enctype enctype)
+{
+ size_t i;
+
+ if (context->tgs_etypes == NULL)
+ return FALSE;
+ for (i = 0; context->tgs_etypes[i] != 0; i++) {
+ if (enctype == context->tgs_etypes[i])
+ return FALSE;
+ }
+ return TRUE;
+}
+
+/* Advance the referral request loop. */
+static krb5_error_code
+step_referrals(krb5_context context, krb5_tkt_creds_context ctx)
+{
+ krb5_error_code code;
+ const krb5_data *referral_realm;
+
+ if (ctx->reply_code != 0) {
+ /* If we had an unknown realm, and we tried the local realm and failed,
+ * try the fallback realm before giving up. */
+ if (ctx->referral_count == 1 &&
+ krb5_is_referral_realm(&ctx->req_server->realm))
+ return try_fallback_realm(context, ctx);
+ else
+ return ctx->reply_code;
+ }
+
+ if (krb5_principal_compare(context, ctx->reply_creds->server,
+ ctx->server)) {
+ /* We got the ticket we asked for... but we didn't necessarily ask for
+ * it with the right enctypes. */
+ if (wrong_enctype(context, ctx->reply_creds->keyblock.enctype)) {
+ /* Try again with the app-provided enctypes. */
+ krb5_free_creds(context, ctx->reply_creds);
+ ctx->reply_creds = NULL;
+ return begin_non_referral(context, ctx);
+ }
+
+ /* Note the authdata we asked for in the output creds. */
+ ctx->reply_creds->authdata = ctx->authdata;
+ ctx->authdata = NULL;
+ ctx->state = STATE_COMPLETE;
+ return 0;
+ }
+
+ if (!IS_TGS_PRINC(context, ctx->reply_creds->server)) {
+ /* We didn't get what we asked or a TGT. Old versions of Active
+ * Directory can do this. Try again with canonicalize off. */
+ krb5_free_creds(context, ctx->reply_creds);
+ ctx->reply_creds = NULL;
+ return begin_non_referral(context, ctx);
+ }
+
+ if (ctx->referral_count == 1) {
+ /* Cache the referral TGT only if it's from the local realm.
+ * Make sure to note the associated authdata, if any. */
+ code = krb5_copy_authdata(context, ctx->authdata,
+ &ctx->reply_creds->authdata);
+ if (code != 0)
+ return code;
+ code = krb5_cc_store_cred(context, ctx->ccache, ctx->reply_creds);
+ if (code != 0)
+ return code;
+
+ /* The authdata is in this TGT and will be copied into subsequent TGTs
+ * or the final credentials, so we don't need to ask for it again. */
+ krb5_free_authdata(context, ctx->in_creds->authdata);
+ ctx->in_creds->authdata = NULL;
+ }
+
+ if (ctx->referral_count++ >= KRB5_REFERRAL_MAXHOPS) {
+ /* We've gotten too many referral TGTs; it's time to give up. */
return KRB5_KDC_UNREACH;
+ }
- assert(ctx->tgtptr != NULL);
+ /* Check for referral loops. */
+ referral_realm = &ctx->reply_creds->server->data[1];
+ if (seen_realm_before(context, ctx, referral_realm))
+ return KRB5_KDC_UNREACH;
+ code = remember_realm(context, ctx, referral_realm);
+ if (code != 0)
+ return code;
- /* Copy krbtgt realm to server principal */
+ /* Use the referral TGT for the next request. */
+ krb5_free_creds(context, ctx->cur_tgt);
+ ctx->cur_tgt = ctx->reply_creds;
+ ctx->reply_creds = NULL;
+
+ /* Rewrite the server realm to be the referral realm. */
krb5_free_data_contents(context, &ctx->server->realm);
- code = krb5int_copy_data_contents(context,
- &ctx->tgtptr->server->data[1],
+ code = krb5int_copy_data_contents(context, referral_realm,
&ctx->server->realm);
if (code != 0)
return code;
- ctx->kdcopt = ctx->req_kdcopt | KDC_OPT_CANONICALIZE;
+ /* Generate the next referral request. */
+ return make_request_for_service(context, ctx, TRUE);
+}
- if (ctx->in_cred.second_ticket.length != 0 &&
- (ctx->kdcopt & KDC_OPT_CNAME_IN_ADDL_TKT) == 0) {
- ctx->kdcopt |= KDC_OPT_ENC_TKT_IN_SKEY;
- }
+/*
+ * Begin the referrals request loop. Expects ctx->cur_tgt to be a TGT for
+ * ctx->realm->server.
+ */
+static krb5_error_code
+begin_referrals(krb5_context context, krb5_tkt_creds_context ctx)
+{
+ ctx->state = STATE_REFERRALS;
+ ctx->referral_count = 1;
- if ((ctx->flags & KRB5_TKT_CREDS_STEP_FLAG_CTX_KTYPES) == 0)
- context->use_conf_ktypes = 1;
+ /* Empty out the realms-seen list for loop checking. */
+ krb5int_free_data_list(context, ctx->realms_seen);
+ ctx->realms_seen = NULL;
- code = tkt_make_tgs_request(context, ctx, ctx->tgtptr,
- &ctx->in_cred, req);
+ /* Generate the first referral request. */
+ return make_request_for_service(context, ctx, TRUE);
+}
- context->use_conf_ktypes = ctx->default_use_conf_ktypes;
+/***** STATE_GET_TGT_OFFPATH *****/
- return code;
+/*
+ * Foreign TGT acquisition can happen either before the referrals loop, if the
+ * service principal had an explicitly specified foreign realm, or after it
+ * fails, if we wind up using the fallback realm. end_get_tgt() advances to
+ * the appropriate state depending on which we were doing.
+ */
+static krb5_error_code
+end_get_tgt(krb5_context context, krb5_tkt_creds_context ctx)
+{
+ if (ctx->getting_tgt_for == STATE_REFERRALS)
+ return begin_referrals(context, ctx);
+ else
+ return begin_non_referral(context, ctx);
}
+/*
+ * We enter STATE_GET_TGT_OFFPATH from STATE_GET_TGT if we receive, from one of
+ * the KDCs in the expected path, a TGT for a realm not in the path. This may
+ * happen if the KDC has a different idea of the expected path than we do. If
+ * it happens, we repeatedly ask the KDC of the TGT we have for a destination
+ * realm TGT, until we get it, fail, or give up.
+ */
+
+/* Advance the process of chasing off-path TGTs. */
static krb5_error_code
-tkt_creds_step_reply(krb5_context context,
- krb5_tkt_creds_context ctx,
- krb5_data *rep)
+step_get_tgt_offpath(krb5_context context, krb5_tkt_creds_context ctx)
{
krb5_error_code code;
- unsigned int i;
- krb5_boolean got_tkt = FALSE;
+ const krb5_data *tgt_realm;
- krb5_free_creds(context, ctx->out_cred);
- ctx->out_cred = NULL;
+ /* We have no fallback if the last request failed, so just give up. */
+ if (ctx->reply_code != 0)
+ return ctx->reply_code;
- code = tkt_process_tgs_reply(context, ctx, rep, ctx->tgtptr,
- &ctx->in_cred, &ctx->out_cred);
+ /* Verify that we got a TGT. */
+ if (!IS_TGS_PRINC(context, ctx->reply_creds->server))
+ return KRB5_KDCREP_MODIFIED;
+
+ /* Use this tgt for the next request. */
+ krb5_free_creds(context, ctx->cur_tgt);
+ ctx->cur_tgt = ctx->reply_creds;
+ ctx->reply_creds = NULL;
+
+ /* Check if we've seen this realm before, and remember it. */
+ tgt_realm = &ctx->cur_tgt->server->data[1];
+ if (seen_realm_before(context, ctx, tgt_realm))
+ return KRB5_KDC_UNREACH;
+ code = remember_realm(context, ctx, tgt_realm);
if (code != 0)
+ return code;
+
+ if (data_eq(*tgt_realm, ctx->server->realm)) {
+ /* We received the server realm TGT we asked for. */
+ return end_get_tgt(context, ctx);
+ } else if (ctx->offpath_count++ >= KRB5_REFERRAL_MAXHOPS) {
+ /* Time to give up. */
+ return KRB5_KDCREP_MODIFIED;
+ }
+
+ return make_request_for_tgt(context, ctx, &ctx->server->realm);
+}
+
+/* Begin chasing off-path referrals, starting from ctx->cur_tgt. */
+static krb5_error_code
+begin_get_tgt_offpath(krb5_context context, krb5_tkt_creds_context ctx)
+{
+ ctx->state = STATE_GET_TGT_OFFPATH;
+ ctx->offpath_count = 1;
+ return make_request_for_tgt(context, ctx, &ctx->server->realm);
+}
+
+/***** STATE_GET_TGT *****/
+
+/*
+ * To obtain a foreign TGT, we first construct a path of realms R1..Rn between
+ * the local realm and the target realm, using krb5_walk_realm_tree(). Usually
+ * this path is based on the domain hierarchy, but it may be altered by
+ * configuration.
+ *
+ * We begin with cur_realm set to the local realm (R1) and next_realm set to
+ * the target realm (Rn). At each step, we check to see if we have a cached
+ * TGT for next_realm; if not, we ask cur_realm to give us a TGT for
+ * next_realm. If that fails, we decrement next_realm until we get a
+ * successful answer or reach cur_realm--in which case we've gotten as far as
+ * we can, and have to give up. If we do get back a TGT, it may or may not be
+ * for the realm we asked for, so we search for it in the path. The realm of
+ * the TGT we get back becomes cur_realm, and next_realm is reset to the target
+ * realm. Overall, this is an O(n^2) process in the length of the path, but
+ * the path length will generally be short and the process will usually end
+ * much faster than the worst case.
+ *
+ * In some cases we may get back a realm for a TGT not in the path. In that
+ * case we enter STATE_GET_TGT_OFFPATH.
+ */
+
+/* Initialize the realm path fields for getting a TGT for
+ * ctx->server->realm. */
+static krb5_error_code
+init_realm_path(krb5_context context, krb5_tkt_creds_context ctx)
+{
+ krb5_error_code code;
+ krb5_principal *tgt_princ_list = NULL;
+ krb5_data *realm_path;
+ size_t nrealms, i;
+
+ /* Make sure we're actually trying to acquire a foreign TGT. */
+ if (data_eq(ctx->client->realm, ctx->server->realm))
+ return KRB5_CC_NOTFOUND;
+
+ /* Construct a list of TGT principals from client to server. We will throw
+ * this away after grabbing the remote realms from each principal. */
+ code = krb5_walk_realm_tree(context, &ctx->client->realm,
+ &ctx->server->realm,
+ &tgt_princ_list, KRB5_REALM_BRANCH_CHAR);
+ if (code != 0)
+ return code;
+
+ /* Count the number of principals and allocate the realm path. */
+ for (nrealms = 0; tgt_princ_list[nrealms]; nrealms++);
+ assert(nrealms > 1);
+ realm_path = k5alloc((nrealms + 1) * sizeof(*realm_path), &code);
+ if (realm_path == NULL)
goto cleanup;
- /*
- * Referral request succeeded; let's see what it is
- */
- if (krb5_principal_compare(context, ctx->server, ctx->out_cred->server)) {
- /*
- * Check if the return enctype is one that we requested if
- * needed.
- */
- if (ctx->default_use_conf_ktypes || context->tgs_etypes == NULL)
- got_tkt = TRUE;
- else
- for (i = 0; context->tgs_etypes[i] != ENCTYPE_NULL; i++) {
- if (ctx->out_cred->keyblock.enctype == context->tgs_etypes[i]) {
- /* Found an allowable etype, so we're done */
- got_tkt = TRUE;
- break;
- }
- }
+ /* Steal the remote realm field from each TGT principal. */
+ for (i = 0; i < nrealms; i++) {
+ assert(tgt_princ_list[i]->length == 2);
+ realm_path[i] = tgt_princ_list[i]->data[1];
+ tgt_princ_list[i]->data[1].data = NULL;
+ }
+ realm_path[nrealms] = empty_data();
- if (got_tkt == FALSE)
- ctx->flags |= KRB5_TKT_CREDS_STEP_FLAG_CTX_KTYPES; /* try again */
- } else if (IS_TGS_PRINC(context, ctx->out_cred->server)) {
- krb5_data *r1, *r2;
+ /* Initialize the realm path fields in ctx. */
+ ctx->realm_path = realm_path;
+ ctx->last_realm = realm_path + nrealms - 1;
+ ctx->cur_realm = realm_path;
+ ctx->next_realm = ctx->last_realm;
+ realm_path = NULL;
- if (ctx->referral_count == 0)
- r1 = &ctx->tgtptr->server->data[1];
- else
- r1 = &ctx->referral_tgts[ctx->referral_count - 1]->server->data[1];
+cleanup:
+ krb5_free_realm_tree(context, tgt_princ_list);
+ return 0;
+}
- r2 = &ctx->out_cred->server->data[1];
- if (data_eq(*r1, *r2)) {
- code = KRB5_KDC_UNREACH;
- goto cleanup;
- }
+/* Find realm within the portion of ctx->realm_path following
+ * ctx->cur_realm. Return NULL if it is not found. */
+static const krb5_data *
+find_realm_in_path(krb5_context context, krb5_tkt_creds_context ctx,
+ const krb5_data *realm)
+{
+ const krb5_data *r;
- /* Check for referral routing loop. */
- for (i = 0; i < ctx->referral_count; i++) {
- if (krb5_principal_compare(context,
- ctx->out_cred->server,
- ctx->referral_tgts[i]->server)) {
- code = KRB5_KDC_UNREACH;
- goto cleanup;
- }
+ for (r = ctx->cur_realm + 1; r->data != NULL; r++) {
+ if (data_eq(*r, *realm))
+ return r;
+ }
+ return NULL;
+}
+
+/*
+ * Generate the next request in the path traversal. If a cached TGT for the
+ * target realm appeared in the ccache since we started the TGT acquisition
+ * process, tihs function may invoke end_get_tgt().
+ */
+static krb5_error_code
+get_tgt_request(krb5_context context, krb5_tkt_creds_context ctx)
+{
+ krb5_error_code code;
+ krb5_creds *cached_tgt;
+
+ while (1) {
+ /* Check if we have a cached TGT for the target realm. */
+ code = get_cached_tgt(context, ctx, ctx->next_realm, &cached_tgt);
+ if (code != 0)
+ return code;
+ if (cached_tgt != NULL) {
+ /* Advance the current realm and keep going. */
+ krb5_free_creds(context, ctx->cur_tgt);
+ ctx->cur_tgt = cached_tgt;
+ if (ctx->next_realm == ctx->last_realm)
+ return end_get_tgt(context, ctx);
+ ctx->cur_realm = ctx->next_realm;
+ ctx->next_realm = ctx->last_realm;
+ continue;
}
- /* Point current tgt pointer at newly-received TGT. */
- ctx->tgtptr = ctx->out_cred;
- /* avoid multiple copies of authdata */
- ctx->out_cred->authdata = ctx->in_cred.authdata;
- ctx->in_cred.authdata = NULL;
+ return make_request_for_tgt(context, ctx, ctx->next_realm);
+ }
+}
- ctx->referral_tgts[ctx->referral_count++] = ctx->out_cred;
- ctx->out_cred = NULL;
+/* Process a TGS reply and advance the path traversal to get a foreign TGT. */
+static krb5_error_code
+step_get_tgt(krb5_context context, krb5_tkt_creds_context ctx)
+{
+ krb5_error_code code;
+ const krb5_data *tgt_realm, *path_realm;
+
+ if (ctx->reply_code != 0) {
+ /* The last request failed. Try the next-closest realm to
+ * ctx->cur_realm. */
+ ctx->next_realm--;
+ if (ctx->next_realm == ctx->cur_realm) {
+ /* We've tried all the realms we could and couldn't progress beyond
+ * ctx->cur_realm, so it's time to give up. */
+ return ctx->reply_code;
+ }
} else {
- code = KRB5KRB_AP_ERR_NO_TGT;
- }
+ /* Verify that we got a TGT. */
+ if (!IS_TGS_PRINC(context, ctx->reply_creds->server))
+ return KRB5_KDCREP_MODIFIED;
- assert(ctx->tgtptr == NULL || code == 0);
+ /* Use this tgt for the next request regardless of what it is. */
+ krb5_free_creds(context, ctx->cur_tgt);
+ ctx->cur_tgt = ctx->reply_creds;
+ ctx->reply_creds = NULL;
- if (code == 0 && got_tkt == TRUE) {
- krb5_free_principal(context, ctx->out_cred->server);
- ctx->out_cred->server = ctx->req_server;
- ctx->req_server = NULL;
+ /* Remember that we saw this realm. */
+ tgt_realm = &ctx->cur_tgt->server->data[1];
+ code = remember_realm(context, ctx, tgt_realm);
+ if (code != 0)
+ return code;
- if (ctx->in_cred.authdata != NULL) {
- code = krb5_copy_authdata(context, ctx->in_cred.authdata,
- &ctx->out_cred->authdata);
+ /* See where we wound up on the path (or off it). */
+ path_realm = find_realm_in_path(context, ctx, tgt_realm);
+ if (path_realm != NULL) {
+ /* We got a realm on the expected path, so we can cache it. */
+ code = krb5_cc_store_cred(context, ctx->ccache, ctx->cur_tgt);
+ if (code != 0)
+ return code;
+ if (path_realm == ctx->last_realm) {
+ /* We received a TGT for the target realm. */
+ return end_get_tgt(context, ctx);
+ } else if (path_realm != NULL) {
+ /* We still have further to go; advance the traversal. */
+ ctx->cur_realm = path_realm;
+ ctx->next_realm = ctx->last_realm;
+ }
+ } else if (data_eq(*tgt_realm, ctx->client->realm)) {
+ /* We were referred back to the local realm, which is bad. */
+ return KRB5_KDCREP_MODIFIED;
+ } else {
+ /* We went off the path; start the off-path chase. */
+ return begin_get_tgt_offpath(context, ctx);
}
-
- ctx->flags |= KRB5_TKT_CREDS_STEP_FLAG_COMPLETE;
}
-cleanup:
- return code;
+ /* Generate the next request in the path traversal. */
+ return get_tgt_request(context, ctx);
}
-krb5_error_code KRB5_CALLCONV
-krb5_tkt_creds_step(krb5_context context,
- krb5_tkt_creds_context ctx,
- krb5_data *in,
- krb5_data *out,
- krb5_data *realm,
- unsigned int *flags)
+/*
+ * Begin the process of getting a foreign TGT, either for the explicitly
+ * specified server realm or for the fallback realm. Expects that
+ * ctx->server->realm is the realm of the desired TGT, and that
+ * ctx->getting_tgt_for is the state we should advance to after we have the
+ * desired TGT.
+ */
+static krb5_error_code
+begin_get_tgt(krb5_context context, krb5_tkt_creds_context ctx)
{
- krb5_error_code code, code2;
+ krb5_error_code code;
+ krb5_creds *cached_tgt;
- *flags = 0;
+ ctx->state = STATE_GET_TGT;
- out->data = NULL;
- out->length = 0;
+ /* See if we have a cached TGT for the server realm. */
+ code = get_cached_tgt(context, ctx, &ctx->server->realm, &cached_tgt);
+ if (code != 0)
+ return code;
+ if (cached_tgt != NULL) {
+ krb5_free_creds(context, ctx->cur_tgt);
+ ctx->cur_tgt = cached_tgt;
+ return end_get_tgt(context, ctx);
+ }
- realm->data = NULL;
- realm->length = 0;
+ /* Initialize the realm path. */
+ code = init_realm_path(context, ctx);
+ if (code != 0)
+ return code;
- if (ctx->flags & KRB5_TKT_CREDS_STEP_FLAG_COMPLETE)
- goto cleanup;
+ /* Start with the local tgt. */
+ krb5_free_creds(context, ctx->cur_tgt);
+ ctx->cur_tgt = NULL;
+ code = get_cached_tgt(context, ctx, &ctx->client->realm, &ctx->cur_tgt);
+ if (code != 0)
+ return code;
+ if (ctx->cur_tgt == NULL)
+ return KRB5_CC_NOTFOUND;
- if (in != NULL && in->length != 0) {
- code = tkt_creds_step_reply(context, ctx, in);
- if (code == KRB5KRB_ERR_RESPONSE_TOO_BIG) {
- code2 = krb5int_copy_data_contents(context,
- &ctx->encoded_previous_request,
- out);
- if (code2 != 0)
- code = code2;
- goto copy_realm;
- }
- if (code != 0 || (ctx->flags & KRB5_TKT_CREDS_STEP_FLAG_COMPLETE))
- goto cleanup;
+ /* Empty out the realms-seen list for loop checking. */
+ krb5int_free_data_list(context, ctx->realms_seen);
+ ctx->realms_seen = NULL;
+
+ /* Generate the first request. */
+ return get_tgt_request(context, ctx);
+}
+
+/***** STATE_BEGIN *****/
+
+static krb5_error_code
+begin(krb5_context context, krb5_tkt_creds_context ctx)
+{
+ krb5_creds *server_tgt;
+ krb5_error_code code;
+
+ /* If the server realm is unspecified, start with the client realm. */
+ if (krb5_is_referral_realm(&ctx->server->realm)) {
+ krb5_free_data_contents(context, &ctx->server->realm);
+ code = krb5int_copy_data_contents(context, &ctx->client->realm,
+ &ctx->server->realm);
+ if (code != 0)
+ return code;
}
- code = tkt_creds_step_request(context, ctx, out);
- if (code != 0)
+ /* Obtain a TGT for the service realm. */
+ ctx->getting_tgt_for = STATE_REFERRALS;
+ return begin_get_tgt(context, ctx);
+}
+
+/***** API functions *****/
+
+krb5_error_code KRB5_CALLCONV
+krb5_tkt_creds_init(krb5_context context, krb5_ccache ccache,
+ krb5_creds *in_creds, int kdcopt,
+ krb5_tkt_creds_context *pctx)
+{
+ krb5_error_code code;
+ krb5_tkt_creds_context ctx = NULL;
+
+ ctx = k5alloc(sizeof(*ctx), &code);
+ if (ctx == NULL)
goto cleanup;
- assert(out->length != 0);
+ ctx->state = STATE_BEGIN;
- code = krb5int_copy_data_contents(context,
- out,
- &ctx->encoded_previous_request);
+ code = krb5_copy_creds(context, in_creds, &ctx->in_creds);
if (code != 0)
goto cleanup;
-
-copy_realm:
- code2 = krb5int_copy_data_contents(context, &ctx->server->realm, realm);
- if (code2 != 0) {
- code = code2;
+ ctx->client = ctx->in_creds->client;
+ ctx->server = ctx->in_creds->server;
+ code = krb5_copy_principal(context, ctx->server, &ctx->req_server);
+ if (code != 0)
goto cleanup;
- }
+ /* XXX Make an alias for now; use krb5_cc_dup later. */
+ ctx->ccache = ccache;
+ ctx->req_kdcopt = kdcopt;
+ code = krb5_copy_authdata(context, in_creds->authdata, &ctx->authdata);
+ if (code != 0)
+ goto cleanup;
+ *pctx = ctx;
+ ctx = NULL;
+
cleanup:
- *flags = (ctx->flags & KRB5_TKT_CREDS_STEP_FLAG_COMPLETE);
-
+ krb5_tkt_creds_free(context, ctx);
return code;
}
+krb5_error_code KRB5_CALLCONV
+krb5_tkt_creds_get_creds(krb5_context context, krb5_tkt_creds_context ctx,
+ krb5_creds *creds)
+{
+ if (ctx->state != STATE_COMPLETE)
+ return KRB5_NO_TKT_SUPPLIED;
+ return krb5int_copy_creds_contents(context, ctx->reply_creds, creds);
+}
+
+/* Store credentials in credentials cache. If ccache is NULL, the
+ * credentials cache associated with the context is used. */
+krb5_error_code KRB5_CALLCONV
+krb5_tkt_creds_store_creds(krb5_context context, krb5_tkt_creds_context ctx,
+ krb5_ccache ccache)
+{
+ if (ctx->state != STATE_COMPLETE)
+ return KRB5_NO_TKT_SUPPLIED;
+ if (ccache == NULL)
+ ccache = ctx->ccache;
+ return krb5_cc_store_cred(context, ccache, ctx->reply_creds);
+}
+
+krb5_error_code KRB5_CALLCONV
+krb5_tkt_creds_get_times(krb5_context context, krb5_tkt_creds_context ctx,
+ krb5_ticket_times *times)
+{
+ if (ctx->state != STATE_COMPLETE)
+ return KRB5_NO_TKT_SUPPLIED;
+ *times = ctx->reply_creds->times;
+ return 0;
+}
+
+void KRB5_CALLCONV
+krb5_tkt_creds_free(krb5_context context, krb5_tkt_creds_context ctx)
+{
+ if (ctx == NULL)
+ return;
+ krb5_free_creds(context, ctx->in_creds);
+ krb5_free_principal(context, ctx->req_server);
+ krb5_free_authdata(context, ctx->authdata);
+ krb5_free_creds(context, ctx->cur_tgt);
+ krb5int_free_data_list(context, ctx->realms_seen);
+ krb5_free_keyblock(context, ctx->subkey);
+ krb5_free_data_contents(context, &ctx->previous_request);
+ krb5int_free_data_list(context, ctx->realm_path);
+ krb5_free_creds(context, ctx->reply_creds);
+ free(ctx);
+}
+
+krb5_error_code KRB5_CALLCONV
+krb5_tkt_creds_step(krb5_context context, krb5_tkt_creds_context ctx,
+ krb5_data *in, krb5_data *out, krb5_data *realm,
+ unsigned int *flags)
+{
+ krb5_error_code code;
+ krb5_boolean no_input = (in == NULL || in->length == 0);
+
+ *out = empty_data();
+ *realm = empty_data();
+ *flags = 0;
+
+ /* We should receive an empty input on the first step only, and should not
+ * get called after completion. */
+ if (no_input != (ctx->state == STATE_BEGIN) ||
+ ctx->state == STATE_COMPLETE)
+ return EINVAL;
+
+ ctx->caller_out = out;
+ ctx->caller_realm = realm;
+ ctx->caller_flags = flags;
+
+ if (!no_input) {
+ /* Convert the input token into a credential and store it in ctx. */
+ code = get_creds_from_tgs_reply(context, ctx, in);
+ if (code != 0)
+ return code;
+ }
+
+ if (ctx->state == STATE_BEGIN)
+ return begin(context, ctx);
+ else if (ctx->state == STATE_GET_TGT)
+ return step_get_tgt(context, ctx);
+ else if (ctx->state == STATE_GET_TGT_OFFPATH)
+ return step_get_tgt_offpath(context, ctx);
+ else if (ctx->state == STATE_REFERRALS)
+ return step_referrals(context, ctx);
+ else if (ctx->state == STATE_NON_REFERRAL)
+ return step_non_referral(context, ctx);
+ else
+ return EINVAL;
+}
More information about the cvs-krb5
mailing list