#include "k5-int.h"
#include "int-proto.h"
#include "os-proto.h"
#include "fast.h"
static krb5_error_code
construct_matching_creds(krb5_context context, krb5_flags options,
krb5_creds *in_creds, krb5_creds *mcreds,
krb5_flags *fields)
{
krb5_error_code ret;
if (!in_creds || !in_creds->server || !in_creds->client)
return EINVAL;
memset(mcreds, 0, sizeof(krb5_creds));
mcreds->magic = KV5M_CREDS;
ret = krb5_timeofday(context, &mcreds->times.endtime);
if (ret)
return ret;
mcreds->keyblock = in_creds->keyblock;
mcreds->authdata = in_creds->authdata;
mcreds->server = in_creds->server;
mcreds->client = in_creds->client;
*fields = KRB5_TC_MATCH_TIMES
| KRB5_TC_MATCH_AUTHDATA
| KRB5_TC_SUPPORTED_KTYPES;
if (mcreds->keyblock.enctype) {
krb5_enctype *ktypes;
size_t i;
*fields |= KRB5_TC_MATCH_KTYPE;
ret = krb5_get_tgs_ktypes(context, mcreds->server, &ktypes);
for (i = 0; ktypes[i]; i++)
if (ktypes[i] == mcreds->keyblock.enctype)
break;
if (ktypes[i] == 0)
ret = KRB5_CC_NOT_KTYPE;
free (ktypes);
if (ret)
return ret;
}
if (options & (KRB5_GC_USER_USER | KRB5_GC_CONSTRAINED_DELEGATION)) {
*fields |= KRB5_TC_MATCH_2ND_TKT;
if (options & KRB5_GC_USER_USER) {
*fields |= KRB5_TC_MATCH_IS_SKEY;
mcreds->is_skey = TRUE;
}
mcreds->second_ticket = in_creds->second_ticket;
if (!in_creds->second_ticket.length)
return KRB5_NO_2ND_TKT;
}
if (options & KRB5_GC_CONSTRAINED_DELEGATION)
mcreds->client = NULL;
return 0;
}
static krb5_error_code
cache_get(krb5_context context, krb5_ccache ccache, krb5_flags flags,
krb5_creds *in_creds, krb5_creds **out_creds)
{
krb5_error_code code;
krb5_creds *creds;
*out_creds = NULL;
creds = malloc(sizeof(*creds));
if (creds == NULL)
return ENOMEM;
code = krb5_cc_retrieve_cred(context, ccache, flags, in_creds, creds);
if (code != 0) {
free(creds);
return code;
}
*out_creds = creds;
return 0;
}
krb5_error_code
k5_get_cached_cred(krb5_context context, krb5_flags options,
krb5_ccache ccache, krb5_creds *in_creds,
krb5_creds **creds_out)
{
krb5_error_code code;
krb5_creds mcreds;
krb5_flags fields;
*creds_out = NULL;
code = construct_matching_creds(context, options, in_creds,
&mcreds, &fields);
if (code)
return code;
return cache_get(context, ccache, fields, &mcreds, creds_out);
}
enum state {
STATE_BEGIN,
STATE_GET_TGT,
STATE_GET_TGT_OFFPATH,
STATE_REFERRALS,
STATE_NON_REFERRAL,
STATE_COMPLETE
};
struct _krb5_tkt_creds_context {
enum state state;
enum state getting_tgt_for;
krb5_creds *in_creds;
krb5_principal client;
krb5_principal server;
krb5_principal req_server;
krb5_ccache ccache;
krb5_data start_realm;
krb5_flags req_options;
krb5_flags req_kdcopt;
krb5_authdata **authdata;
struct canonprinc iter;
krb5_boolean referral_req;
krb5_creds *cur_tgt;
krb5_data *realms_seen;
krb5_principal tgt_princ;
krb5_creds tgt_in_creds;
krb5_creds *tgs_in_creds;
krb5_timestamp timestamp;
krb5_int32 nonce;
int kdcopt;
krb5_keyblock *subkey;
krb5_data previous_request;
struct krb5int_fast_request_state *fast_state;
krb5_data *realm_path;
const krb5_data *last_realm;
const krb5_data *cur_realm;
const krb5_data *next_realm;
unsigned int offpath_count;
unsigned int referral_count;
krb5_creds *reply_creds;
krb5_error_code reply_code;
krb5_data *caller_out;
krb5_data *caller_realm;
unsigned int *caller_flags;
};
#define FLAGS2OPTS(flags) (flags & KDC_TKT_COMMON_MASK)
static krb5_error_code
begin_get_tgt(krb5_context context, krb5_tkt_creds_context ctx);
static krb5_error_code
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_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 = KRB5_TKT_CREDS_STEP_FLAG_CONTINUE;
return 0;
cleanup:
krb5_free_data_contents(context, &out_copy);
krb5_free_data_contents(context, &realm_copy);
return code;
}
static krb5_error_code
make_request(krb5_context context, krb5_tkt_creds_context ctx,
int extra_options)
{
krb5_error_code code;
krb5_data request = empty_data();
ctx->kdcopt = extra_options | FLAGS2OPTS(ctx->cur_tgt->ticket_flags);
if (!krb5_c_valid_enctype(ctx->cur_tgt->keyblock.enctype))
return KRB5_PROG_ETYPE_NOSUPP;
krb5int_fast_free_state(context, ctx->fast_state);
ctx->fast_state = NULL;
code = krb5int_fast_make_state(context, &ctx->fast_state);
if (code)
return code;
krb5_free_keyblock(context, ctx->subkey);
ctx->subkey = NULL;
code = k5_make_tgs_req(context, ctx->fast_state, ctx->cur_tgt, ctx->kdcopt,
ctx->cur_tgt->addresses, NULL, ctx->tgs_in_creds,
NULL, NULL, &request, &ctx->timestamp, &ctx->nonce,
&ctx->subkey);
if (code != 0)
return code;
krb5_free_data_contents(context, &ctx->previous_request);
ctx->previous_request = request;
return set_caller_request(context, ctx);
}
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_free_principal(context, ctx->tgt_princ);
ctx->tgt_princ = NULL;
code = krb5int_tgtname(context, realm, &ctx->cur_tgt->server->data[1],
&ctx->tgt_princ);
if (code != 0)
return code;
TRACE_TKT_CREDS_TGT_REQ(context, ctx->tgt_princ, ctx->cur_tgt->server);
memset(&ctx->tgt_in_creds, 0, sizeof(ctx->tgt_in_creds));
ctx->tgt_in_creds.client = ctx->client;
ctx->tgt_in_creds.server = ctx->tgt_princ;
ctx->tgs_in_creds = &ctx->tgt_in_creds;
code = make_request(context, ctx, 0);
return code;
}
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;
TRACE_TKT_CREDS_SERVICE_REQ(context, ctx->server, referral);
extra_options = ctx->req_kdcopt;
if (ctx->in_creds->second_ticket.length != 0)
extra_options |= KDC_OPT_ENC_TKT_IN_SKEY;
if (referral)
extra_options |= KDC_OPT_CANONICALIZE;
if (referral)
context->use_conf_ktypes = TRUE;
ctx->tgs_in_creds = ctx->in_creds;
code = make_request(context, ctx, extra_options);
if (referral)
context->use_conf_ktypes = FALSE;
return code;
}
static krb5_error_code
get_creds_from_tgs_reply(krb5_context context, krb5_tkt_creds_context ctx,
krb5_data *reply)
{
krb5_error_code code;
krb5_free_creds(context, ctx->reply_creds);
ctx->reply_creds = NULL;
code = krb5int_process_tgs_reply(context, ctx->fast_state,
reply, ctx->cur_tgt, ctx->kdcopt,
ctx->cur_tgt->addresses, NULL,
ctx->tgs_in_creds, ctx->timestamp,
ctx->nonce, ctx->subkey, NULL, NULL,
&ctx->reply_creds);
if (code == KRB5KRB_ERR_RESPONSE_TOO_BIG) {
code = set_caller_request(context, ctx);
if (code != 0)
return code;
return KRB5KRB_ERR_RESPONSE_TOO_BIG;
}
TRACE_TKT_CREDS_RESPONSE_CODE(context, code);
ctx->reply_code = code;
return 0;
}
static krb5_error_code
remember_realm(krb5_context context, krb5_tkt_creds_context ctx,
const krb5_data *realm)
{
size_t len = 0;
krb5_data *new_list;
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]);
}
static krb5_boolean
seen_realm_before(krb5_context context, krb5_tkt_creds_context ctx,
const krb5_data *realm)
{
size_t i;
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;
}
static krb5_error_code
complete(krb5_context context, krb5_tkt_creds_context ctx)
{
TRACE_TKT_CREDS_COMPLETE(context, ctx->reply_creds->server);
krb5_free_principal(context, ctx->reply_creds->server);
ctx->reply_creds->server = ctx->req_server;
ctx->req_server = NULL;
ctx->reply_creds->authdata = ctx->authdata;
ctx->authdata = NULL;
if (!(ctx->req_options & KRB5_GC_NO_STORE)) {
(void) krb5_cc_store_cred(context, ctx->ccache, ctx->reply_creds);
}
ctx->state = STATE_COMPLETE;
return 0;
}
static krb5_error_code
step_non_referral(krb5_context context, krb5_tkt_creds_context ctx)
{
if (ctx->reply_code)
return ctx->reply_code;
return complete(context, ctx);
}
static krb5_error_code
begin_non_referral(krb5_context context, krb5_tkt_creds_context ctx)
{
ctx->state = STATE_NON_REFERRAL;
return make_request_for_service(context, ctx, FALSE);
}
static krb5_error_code
try_fallback(krb5_context context, krb5_tkt_creds_context ctx)
{
krb5_error_code code;
char **hrealms;
if (ctx->referral_count > 1)
return ctx->reply_code;
if (!ctx->referral_req)
return begin_non_referral(context, ctx);
if (ctx->server->length < 2) {
return KRB5_ERR_HOST_REALM_UNKNOWN;
}
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])) {
krb5_free_host_realm(context, hrealms);
return begin_non_referral(context, ctx);
}
krb5_free_data_contents(context, &ctx->server->realm);
ctx->server->realm = string2data(hrealms[0]);
free(hrealms);
TRACE_TKT_CREDS_FALLBACK(context, &ctx->server->realm);
ctx->getting_tgt_for = STATE_NON_REFERRAL;
return begin_get_tgt(context, ctx);
}
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;
}
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)
return try_fallback(context, ctx);
if (krb5_principal_compare_any_realm(context, ctx->reply_creds->server,
ctx->server)) {
if (wrong_enctype(context, ctx->reply_creds->keyblock.enctype)) {
TRACE_TKT_CREDS_WRONG_ENCTYPE(context);
return begin_non_referral(context, ctx);
}
return complete(context, ctx);
}
if (!IS_TGS_PRINC(ctx->reply_creds->server)) {
TRACE_TKT_CREDS_NON_TGT(context, ctx->reply_creds->server);
return begin_non_referral(context, ctx);
}
referral_realm = &ctx->reply_creds->server->data[1];
if (data_eq(*referral_realm, ctx->cur_tgt->server->data[1])) {
TRACE_TKT_CREDS_SAME_REALM_TGT(context, referral_realm);
return begin_non_referral(context, ctx);
}
if (ctx->referral_count == 1) {
krb5_free_authdata(context, ctx->in_creds->authdata);
ctx->in_creds->authdata = NULL;
}
if (ctx->referral_count++ >= KRB5_REFERRAL_MAXHOPS)
return KRB5_KDC_UNREACH;
if (seen_realm_before(context, ctx, referral_realm))
return KRB5_KDC_UNREACH;
code = remember_realm(context, ctx, referral_realm);
if (code != 0)
return code;
krb5_free_creds(context, ctx->cur_tgt);
ctx->cur_tgt = ctx->reply_creds;
ctx->reply_creds = NULL;
TRACE_TKT_CREDS_REFERRAL(context, ctx->cur_tgt->server);
krb5_free_data_contents(context, &ctx->server->realm);
code = krb5int_copy_data_contents(context, referral_realm,
&ctx->server->realm);
if (code != 0)
return code;
return make_request_for_service(context, ctx, TRUE);
}
static krb5_error_code
begin_referrals(krb5_context context, krb5_tkt_creds_context ctx)
{
ctx->state = STATE_REFERRALS;
ctx->referral_count = 1;
krb5int_free_data_list(context, ctx->realms_seen);
ctx->realms_seen = NULL;
return make_request_for_service(context, ctx, TRUE);
}
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);
}
static krb5_error_code
step_get_tgt_offpath(krb5_context context, krb5_tkt_creds_context ctx)
{
krb5_error_code code;
const krb5_data *tgt_realm;
if (ctx->reply_code != 0)
return ctx->reply_code;
if (!IS_TGS_PRINC(ctx->reply_creds->server))
return KRB5_KDCREP_MODIFIED;
krb5_free_creds(context, ctx->cur_tgt);
ctx->cur_tgt = ctx->reply_creds;
ctx->reply_creds = NULL;
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)) {
TRACE_TKT_CREDS_TARGET_TGT_OFFPATH(context, ctx->cur_tgt->server);
return end_get_tgt(context, ctx);
} else if (ctx->offpath_count++ >= KRB5_REFERRAL_MAXHOPS) {
return KRB5_KDCREP_MODIFIED;
}
return make_request_for_tgt(context, ctx, &ctx->server->realm);
}
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);
}
static krb5_error_code
get_cached_tgt(krb5_context context, krb5_tkt_creds_context ctx,
const krb5_data *realm, krb5_creds **tgt_out)
{
krb5_creds mcreds;
krb5_error_code code;
krb5_principal tgtname = NULL;
krb5_flags flags = KRB5_TC_SUPPORTED_KTYPES | KRB5_TC_MATCH_SRV_NAMEONLY |
KRB5_TC_MATCH_TIMES;
krb5_timestamp now;
*tgt_out = NULL;
code = krb5_timeofday(context, &now);
if (code != 0)
return code;
code = krb5int_tgtname(context, realm, realm, &tgtname);
if (code != 0)
return code;
memset(&mcreds, 0, sizeof(mcreds));
mcreds.client = ctx->client;
mcreds.server = tgtname;
mcreds.times.endtime = now;
context->use_conf_ktypes = TRUE;
code = cache_get(context, ctx->ccache, flags, &mcreds, tgt_out);
context->use_conf_ktypes = FALSE;
krb5_free_principal(context, tgtname);
return (code == KRB5_CC_NOTFOUND || code != KRB5_CC_NOT_KTYPE) ? 0 : code;
}
static krb5_error_code
get_cached_local_tgt(krb5_context context, krb5_tkt_creds_context ctx,
krb5_creds **tgt_out)
{
krb5_creds mcreds;
krb5_error_code code;
krb5_principal tgtname = NULL;
krb5_flags flags = KRB5_TC_SUPPORTED_KTYPES;
krb5_timestamp now;
krb5_creds *tgt;
*tgt_out = NULL;
code = krb5_timeofday(context, &now);
if (code != 0)
return code;
code = krb5int_tgtname(context, &ctx->start_realm, &ctx->start_realm,
&tgtname);
if (code != 0)
return code;
memset(&mcreds, 0, sizeof(mcreds));
mcreds.client = ctx->client;
mcreds.server = tgtname;
context->use_conf_ktypes = TRUE;
code = cache_get(context, ctx->ccache, flags, &mcreds, &tgt);
context->use_conf_ktypes = FALSE;
krb5_free_principal(context, tgtname);
if (code)
return code;
if (ts_after(now, tgt->times.endtime)) {
krb5_free_creds(context, tgt);
return KRB5KRB_AP_ERR_TKT_EXPIRED;
}
*tgt_out = tgt;
return 0;
}
static krb5_error_code
init_realm_path(krb5_context context, krb5_tkt_creds_context ctx)
{
krb5_error_code code;
krb5_data *realm_path;
size_t nrealms;
code = k5_client_realm_path(context, &ctx->start_realm,
&ctx->server->realm, &realm_path);
if (code != 0)
return code;
for (nrealms = 0; realm_path[nrealms].data != NULL; nrealms++);
assert(nrealms > 1);
krb5int_free_data_list(context, ctx->realm_path);
ctx->realm_path = realm_path;
ctx->last_realm = realm_path + nrealms - 1;
ctx->cur_realm = realm_path;
ctx->next_realm = ctx->last_realm;
return 0;
}
static const krb5_data *
find_realm_in_path(krb5_context context, krb5_tkt_creds_context ctx,
const krb5_data *realm)
{
const krb5_data *r;
for (r = ctx->cur_realm + 1; r->data != NULL; r++) {
if (data_eq(*r, *realm))
return r;
}
return NULL;
}
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) {
code = get_cached_tgt(context, ctx, ctx->next_realm, &cached_tgt);
if (code != 0)
return code;
if (cached_tgt != NULL) {
TRACE_TKT_CREDS_CACHED_INTERMEDIATE_TGT(context, cached_tgt);
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;
}
return make_request_for_tgt(context, ctx, ctx->next_realm);
}
}
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) {
ctx->next_realm--;
if (ctx->next_realm == ctx->cur_realm) {
return ctx->reply_code;
}
TRACE_TKT_CREDS_CLOSER_REALM(context, ctx->next_realm);
} else {
if (!IS_TGS_PRINC(ctx->reply_creds->server))
return KRB5_KDCREP_MODIFIED;
krb5_free_creds(context, ctx->cur_tgt);
ctx->cur_tgt = ctx->reply_creds;
ctx->reply_creds = NULL;
tgt_realm = &ctx->cur_tgt->server->data[1];
code = remember_realm(context, ctx, tgt_realm);
if (code != 0)
return code;
path_realm = find_realm_in_path(context, ctx, tgt_realm);
if (path_realm != NULL) {
if (path_realm == ctx->next_realm)
(void)krb5_cc_store_cred(context, ctx->ccache, ctx->cur_tgt);
if (path_realm == ctx->last_realm) {
TRACE_TKT_CREDS_TARGET_TGT(context, ctx->cur_tgt->server);
return end_get_tgt(context, ctx);
} else if (path_realm != NULL) {
TRACE_TKT_CREDS_ADVANCE(context, tgt_realm);
ctx->cur_realm = path_realm;
ctx->next_realm = ctx->last_realm;
}
} else if (data_eq(*tgt_realm, ctx->start_realm)) {
return KRB5_KDCREP_MODIFIED;
} else {
TRACE_TKT_CREDS_OFFPATH(context, tgt_realm);
return begin_get_tgt_offpath(context, ctx);
}
}
return get_tgt_request(context, ctx);
}
static krb5_error_code
begin_get_tgt(krb5_context context, krb5_tkt_creds_context ctx)
{
krb5_error_code code;
krb5_creds *cached_tgt;
krb5_boolean is_local_service;
ctx->state = STATE_GET_TGT;
is_local_service = data_eq(ctx->start_realm, ctx->server->realm);
if (!is_local_service) {
code = get_cached_tgt(context, ctx, &ctx->server->realm, &cached_tgt);
if (code != 0)
return code;
if (cached_tgt != NULL) {
TRACE_TKT_CREDS_CACHED_SERVICE_TGT(context, cached_tgt);
krb5_free_creds(context, ctx->cur_tgt);
ctx->cur_tgt = cached_tgt;
return end_get_tgt(context, ctx);
}
}
krb5_free_creds(context, ctx->cur_tgt);
ctx->cur_tgt = NULL;
code = get_cached_local_tgt(context, ctx, &ctx->cur_tgt);
if (code != 0)
return code;
TRACE_TKT_CREDS_LOCAL_TGT(context, ctx->cur_tgt);
if (is_local_service)
return end_get_tgt(context, ctx);
code = init_realm_path(context, ctx);
if (code != 0)
return code;
krb5int_free_data_list(context, ctx->realms_seen);
ctx->realms_seen = NULL;
return get_tgt_request(context, ctx);
}
static krb5_error_code
check_cache(krb5_context context, krb5_tkt_creds_context ctx)
{
krb5_error_code code;
krb5_creds req_in_creds;
req_in_creds = *ctx->in_creds;
req_in_creds.server = ctx->req_server;
code = k5_get_cached_cred(context, ctx->req_options, ctx->ccache,
&req_in_creds, &ctx->reply_creds);
if (code == 0) {
ctx->state = STATE_COMPLETE;
return 0;
}
if (code != KRB5_CC_NOTFOUND && code != KRB5_CC_NOT_KTYPE)
return code;
if (ctx->req_options & KRB5_GC_CACHED)
return code;
return 0;
}
static krb5_error_code
begin(krb5_context context, krb5_tkt_creds_context ctx)
{
krb5_error_code code;
ctx->referral_req = krb5_is_referral_realm(&ctx->server->realm);
if (ctx->referral_req) {
krb5_free_data_contents(context, &ctx->server->realm);
code = krb5int_copy_data_contents(context, &ctx->start_realm,
&ctx->server->realm);
TRACE_TKT_CREDS_REFERRAL_REALM(context, ctx->server);
if (code != 0)
return code;
}
ctx->getting_tgt_for = STATE_REFERRALS;
return begin_get_tgt(context, ctx);
}
krb5_error_code KRB5_CALLCONV
krb5_tkt_creds_init(krb5_context context, krb5_ccache ccache,
krb5_creds *in_creds, krb5_flags options,
krb5_tkt_creds_context *pctx)
{
krb5_error_code code;
krb5_tkt_creds_context ctx = NULL;
krb5_const_principal canonprinc;
TRACE_TKT_CREDS(context, in_creds, ccache);
ctx = k5alloc(sizeof(*ctx), &code);
if (ctx == NULL)
goto cleanup;
ctx->req_options = options;
ctx->req_kdcopt = 0;
if (options & KRB5_GC_CANONICALIZE)
ctx->req_kdcopt |= KDC_OPT_CANONICALIZE;
if (options & KRB5_GC_FORWARDABLE)
ctx->req_kdcopt |= KDC_OPT_FORWARDABLE;
if (options & KRB5_GC_NO_TRANSIT_CHECK)
ctx->req_kdcopt |= KDC_OPT_DISABLE_TRANSITED_CHECK;
ctx->state = STATE_BEGIN;
code = krb5_copy_creds(context, in_creds, &ctx->in_creds);
if (code != 0)
goto cleanup;
ctx->req_server = ctx->in_creds->server;
ctx->in_creds->server = NULL;
ctx->iter.princ = ctx->req_server;
code = k5_canonprinc(context, &ctx->iter, &canonprinc);
if (code == 0 && canonprinc == NULL)
code = KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN;
if (code != 0)
goto cleanup;
code = krb5_copy_principal(context, canonprinc, &ctx->in_creds->server);
if (code != 0)
goto cleanup;
ctx->client = ctx->in_creds->client;
ctx->server = ctx->in_creds->server;
code = krb5_cc_dup(context, ccache, &ctx->ccache);
if (code != 0)
goto cleanup;
code = krb5_cc_get_config(context, ccache, NULL, "start_realm",
&ctx->start_realm);
if (code != 0) {
code = krb5int_copy_data_contents(context, &ctx->client->realm,
&ctx->start_realm);
if (code != 0)
goto cleanup;
}
code = krb5_copy_authdata(context, in_creds->authdata, &ctx->authdata);
if (code != 0)
goto cleanup;
*pctx = ctx;
ctx = NULL;
cleanup:
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 k5_copy_creds_contents(context, ctx->reply_creds, 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;
krb5int_fast_free_state(context, ctx->fast_state);
krb5_free_creds(context, ctx->in_creds);
free_canonprinc(&ctx->iter);
krb5_cc_close(context, ctx->ccache);
krb5_free_data_contents(context, &ctx->start_realm);
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_principal(context, ctx->tgt_princ);
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_get(krb5_context context, krb5_tkt_creds_context ctx)
{
krb5_error_code code;
krb5_data request = empty_data(), reply = empty_data();
krb5_data realm = empty_data();
unsigned int flags = 0;
int no_udp = 0;
for (;;) {
code = krb5_tkt_creds_step(context, ctx, &reply, &request, &realm,
&flags);
if (code == KRB5KRB_ERR_RESPONSE_TOO_BIG && !no_udp) {
TRACE_TKT_CREDS_RETRY_TCP(context);
no_udp = 1;
} else if (code != 0 || !(flags & KRB5_TKT_CREDS_STEP_FLAG_CONTINUE))
break;
krb5_free_data_contents(context, &reply);
code = k5_sendto_kdc(context, &request, &realm, FALSE, no_udp,
&reply, NULL);
if (code != 0)
break;
krb5_free_data_contents(context, &request);
krb5_free_data_contents(context, &realm);
}
krb5_free_data_contents(context, &request);
krb5_free_data_contents(context, &reply);
krb5_free_data_contents(context, &realm);
return code;
}
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);
krb5_const_principal canonprinc;
*out = empty_data();
*realm = empty_data();
*flags = 0;
if (no_input != (ctx->state == STATE_BEGIN) ||
ctx->state == STATE_COMPLETE)
return EINVAL;
if (ctx->state == STATE_BEGIN) {
code = check_cache(context, ctx);
if (code != 0 || ctx->state == STATE_COMPLETE)
return code;
}
ctx->caller_out = out;
ctx->caller_realm = realm;
ctx->caller_flags = flags;
if (!no_input) {
code = get_creds_from_tgs_reply(context, ctx, in);
if (code != 0)
return code;
}
if (ctx->state == STATE_BEGIN)
code = begin(context, ctx);
else if (ctx->state == STATE_GET_TGT)
code = step_get_tgt(context, ctx);
else if (ctx->state == STATE_GET_TGT_OFFPATH)
code = step_get_tgt_offpath(context, ctx);
else if (ctx->state == STATE_REFERRALS)
code = step_referrals(context, ctx);
else if (ctx->state == STATE_NON_REFERRAL)
code = step_non_referral(context, ctx);
else
code = EINVAL;
if (code != KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN)
return code;
code = k5_canonprinc(context, &ctx->iter, &canonprinc);
if (code)
return code;
if (canonprinc == NULL)
return KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN;
krb5_free_principal(context, ctx->in_creds->server);
code = krb5_copy_principal(context, canonprinc, &ctx->in_creds->server);
ctx->server = ctx->in_creds->server;
return begin(context, ctx);
}
krb5_error_code KRB5_CALLCONV
krb5_get_credentials(krb5_context context, krb5_flags options,
krb5_ccache ccache, krb5_creds *in_creds,
krb5_creds **out_creds)
{
krb5_error_code code;
krb5_creds *ncreds = NULL;
krb5_tkt_creds_context ctx = NULL;
*out_creds = NULL;
if (options & KRB5_GC_CONSTRAINED_DELEGATION) {
return k5_get_proxy_cred_from_kdc(context, options, ccache, in_creds,
out_creds);
}
ncreds = k5alloc(sizeof(*ncreds), &code);
if (ncreds == NULL)
goto cleanup;
code = krb5_tkt_creds_init(context, ccache, in_creds, options, &ctx);
if (code != 0)
goto cleanup;
code = krb5_tkt_creds_get(context, ctx);
if (code != 0)
goto cleanup;
code = krb5_tkt_creds_get_creds(context, ctx, ncreds);
if (code != 0)
goto cleanup;
*out_creds = ncreds;
ncreds = NULL;
cleanup:
krb5_free_creds(context, ncreds);
krb5_tkt_creds_free(context, ctx);
return code;
}