1 /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /* lib/krb5/krb/get_creds.c */
3 /*
4 * Copyright 1990, 2008, 2010 by the Massachusetts Institute of Technology.
5 * All Rights Reserved.
6 *
7 * Export of this software from the United States of America may
8 * require a specific license from the United States Government.
9 * It is the responsibility of any person or organization contemplating
10 * export to obtain such a license before exporting.
11 *
12 * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
13 * distribute this software and its documentation for any purpose and
14 * without fee is hereby granted, provided that the above copyright
15 * notice appear in all copies and that both that copyright notice and
16 * this permission notice appear in supporting documentation, and that
17 * the name of M.I.T. not be used in advertising or publicity pertaining
18 * to distribution of the software without specific, written prior
19 * permission. Furthermore if you modify this software you must label
20 * your software as modified software and not distribute it in such a
21 * fashion that it might be confused with the original M.I.T. software.
22 * M.I.T. makes no representations about the suitability of
23 * this software for any purpose. It is provided "as is" without express
24 * or implied warranty.
25 */
26
27 /*
28 * Attempts to use the credentials cache or TGS exchange to get an additional
29 * ticket for the client identified by in_creds->client, the server identified
30 * by in_creds->server, with options options, expiration date specified in
31 * in_creds->times.endtime (0 means as long as possible), session key type
32 * specified in in_creds->keyblock.enctype (if non-zero)
33 *
34 * Any returned ticket and intermediate ticket-granting tickets are stored in
35 * ccache.
36 *
37 * Returns errors from encryption routines, system errors.
38 */
39
40 #include "k5-int.h"
41 #include "int-proto.h"
42 #include "os-proto.h"
43 #include "fast.h"
44
45 /*
46 * Set *mcreds and *fields to a matching credential and field set for
47 * use with krb5_cc_retrieve_cred, based on a set of input credentials
48 * and options. The fields of *mcreds will be aliased to the fields
49 * of in_creds, so the contents of *mcreds should not be freed.
50 */
51 static krb5_error_code
construct_matching_creds(krb5_context context,krb5_flags options,krb5_creds * in_creds,krb5_creds * mcreds,krb5_flags * fields)52 construct_matching_creds(krb5_context context, krb5_flags options,
53 krb5_creds *in_creds, krb5_creds *mcreds,
54 krb5_flags *fields)
55 {
56 krb5_error_code ret;
57
58 if (!in_creds || !in_creds->server || !in_creds->client)
59 return EINVAL;
60
61 memset(mcreds, 0, sizeof(krb5_creds));
62 mcreds->magic = KV5M_CREDS;
63 ret = krb5_timeofday(context, &mcreds->times.endtime);
64 if (ret)
65 return ret;
66 mcreds->keyblock = in_creds->keyblock;
67 mcreds->authdata = in_creds->authdata;
68 mcreds->server = in_creds->server;
69 mcreds->client = in_creds->client;
70
71 *fields = KRB5_TC_MATCH_TIMES /*XXX |KRB5_TC_MATCH_SKEY_TYPE */
72 | KRB5_TC_MATCH_AUTHDATA
73 | KRB5_TC_SUPPORTED_KTYPES;
74 if (mcreds->keyblock.enctype) {
75 krb5_enctype *ktypes;
76 size_t i;
77
78 *fields |= KRB5_TC_MATCH_KTYPE;
79 ret = krb5_get_tgs_ktypes(context, mcreds->server, &ktypes);
80 for (i = 0; ktypes[i]; i++)
81 if (ktypes[i] == mcreds->keyblock.enctype)
82 break;
83 if (ktypes[i] == 0)
84 ret = KRB5_CC_NOT_KTYPE;
85 free (ktypes);
86 if (ret)
87 return ret;
88 }
89 if (options & (KRB5_GC_USER_USER | KRB5_GC_CONSTRAINED_DELEGATION)) {
90 /* also match on identical 2nd tkt and tkt encrypted in a
91 session key */
92 *fields |= KRB5_TC_MATCH_2ND_TKT;
93 if (options & KRB5_GC_USER_USER) {
94 *fields |= KRB5_TC_MATCH_IS_SKEY;
95 mcreds->is_skey = TRUE;
96 }
97 mcreds->second_ticket = in_creds->second_ticket;
98 if (!in_creds->second_ticket.length)
99 return KRB5_NO_2ND_TKT;
100 }
101
102 /* For S4U2Proxy requests we don't know the impersonated client in this
103 * API, but matching against the second ticket is good enough. */
104 if (options & KRB5_GC_CONSTRAINED_DELEGATION)
105 mcreds->client = NULL;
106
107 return 0;
108 }
109
110 /* Simple wrapper around krb5_cc_retrieve_cred which allocates the result
111 * container. */
112 static krb5_error_code
cache_get(krb5_context context,krb5_ccache ccache,krb5_flags flags,krb5_creds * in_creds,krb5_creds ** out_creds)113 cache_get(krb5_context context, krb5_ccache ccache, krb5_flags flags,
114 krb5_creds *in_creds, krb5_creds **out_creds)
115 {
116 krb5_error_code code;
117 krb5_creds *creds;
118
119 *out_creds = NULL;
120
121 creds = malloc(sizeof(*creds));
122 if (creds == NULL)
123 return ENOMEM;
124
125 code = krb5_cc_retrieve_cred(context, ccache, flags, in_creds, creds);
126 if (code != 0) {
127 free(creds);
128 return code;
129 }
130
131 *out_creds = creds;
132 return 0;
133 }
134
135 krb5_error_code
k5_get_cached_cred(krb5_context context,krb5_flags options,krb5_ccache ccache,krb5_creds * in_creds,krb5_creds ** creds_out)136 k5_get_cached_cred(krb5_context context, krb5_flags options,
137 krb5_ccache ccache, krb5_creds *in_creds,
138 krb5_creds **creds_out)
139 {
140 krb5_error_code code;
141 krb5_creds mcreds;
142 krb5_flags fields;
143
144 *creds_out = NULL;
145
146 code = construct_matching_creds(context, options, in_creds,
147 &mcreds, &fields);
148 if (code)
149 return code;
150
151 return cache_get(context, ccache, fields, &mcreds, creds_out);
152 }
153
154 /*
155 * krb5_tkt_creds_step() is implemented using a tail call style. Every
156 * begin_*, step_*, or *_request function is responsible for returning an
157 * error, generating the next request, or delegating to another function using
158 * a tail call.
159 *
160 * The process is divided up into states which govern how the next input token
161 * should be interpreted. Each state has a "begin_<state>" function to set up
162 * the context fields related to the state, a "step_<state>" function to
163 * process a reply and update the related context fields, and possibly a
164 * "<state>_request" function (invoked by the begin_ and step_ functions) to
165 * generate the next request. If it's time to advance to another state, any of
166 * the three functions can make a tail call to begin_<nextstate> to do so.
167 *
168 * The general process is as follows:
169 * 1. Get a TGT for the service principal's realm (STATE_GET_TGT).
170 * 2. Make one or more referrals queries (STATE_REFERRALS).
171 * 3. In some cases, get a TGT for the fallback realm (STATE_GET_TGT again).
172 * 4. In some cases, make a non-referral query (STATE_NON_REFERRAL).
173 *
174 * STATE_GET_TGT can precede either STATE_REFERRALS or STATE_NON_REFERRAL. The
175 * getting_tgt_for field in the context keeps track of what state we will go to
176 * after successfully obtaining the TGT, and the end_get_tgt() function
177 * advances to the proper next state.
178 *
179 * If fallback DNS canonicalization is in use, the process can be repeated a
180 * second time for the second server principal canonicalization candidate.
181 */
182
183 enum state {
184 STATE_BEGIN, /* Initial step (no input token) */
185 STATE_GET_TGT, /* Getting TGT for service realm */
186 STATE_GET_TGT_OFFPATH, /* Getting TGT via off-path referrals */
187 STATE_REFERRALS, /* Retrieving service ticket or referral */
188 STATE_NON_REFERRAL, /* Non-referral service ticket request */
189 STATE_COMPLETE /* Creds ready for retrieval */
190 };
191
192 struct _krb5_tkt_creds_context {
193 enum state state; /* What we should do with the next reply */
194 enum state getting_tgt_for; /* STATE_REFERRALS or STATE_NON_REFERRAL */
195
196 /* The following fields are set up at initialization time. */
197 krb5_creds *in_creds; /* Creds requested by caller */
198 krb5_principal client; /* Caller-requested client principal (alias) */
199 krb5_principal server; /* Server principal (alias) */
200 krb5_principal req_server; /* Caller-requested server principal */
201 krb5_ccache ccache; /* Caller-provided ccache */
202 krb5_data start_realm; /* Realm of starting TGT in ccache */
203 krb5_flags req_options; /* Caller-requested KRB5_GC_* options */
204 krb5_flags req_kdcopt; /* Caller-requested options as KDC options */
205 krb5_authdata **authdata; /* Caller-requested authdata */
206 struct canonprinc iter; /* Iterator over canonicalized server princs */
207 krb5_boolean referral_req; /* Server initially contained referral realm */
208
209 /* The following fields are used in multiple steps. */
210 krb5_creds *cur_tgt; /* TGT to be used for next query */
211 krb5_data *realms_seen; /* For loop detection */
212
213 /* The following fields track state between request and reply. */
214 krb5_principal tgt_princ; /* Storage for TGT principal */
215 krb5_creds tgt_in_creds; /* Container for TGT matching creds */
216 krb5_creds *tgs_in_creds; /* Input credentials of request (alias) */
217 krb5_timestamp timestamp; /* Timestamp of request */
218 krb5_int32 nonce; /* Nonce of request */
219 int kdcopt; /* KDC options of request */
220 krb5_keyblock *subkey; /* subkey of request */
221 krb5_data previous_request; /* Encoded request (for TCP retransmission) */
222 struct krb5int_fast_request_state *fast_state;
223
224 /* The following fields are used when acquiring foreign TGTs. */
225 krb5_data *realm_path; /* Path from client to server realm */
226 const krb5_data *last_realm;/* Last realm in realm_path */
227 const krb5_data *cur_realm; /* Position of cur_tgt in realm_path */
228 const krb5_data *next_realm;/* Current target realm in realm_path */
229 unsigned int offpath_count; /* Offpath requests made */
230
231 /* The following fields are used during the referrals loop. */
232 unsigned int referral_count;/* Referral requests made */
233
234 /* The following fields are used within a _step call to avoid
235 * passing them as parameters everywhere. */
236 krb5_creds *reply_creds; /* Creds from TGS reply */
237 krb5_error_code reply_code; /* Error status from TGS reply */
238 krb5_data *caller_out; /* Caller's out parameter */
239 krb5_data *caller_realm; /* Caller's realm parameter */
240 unsigned int *caller_flags; /* Caller's flags parameter */
241 };
242
243 /* Convert ticket flags to necessary KDC options */
244 #define FLAGS2OPTS(flags) (flags & KDC_TKT_COMMON_MASK)
245
246 static krb5_error_code
247 begin_get_tgt(krb5_context context, krb5_tkt_creds_context ctx);
248
249 /*
250 * Fill in the caller out, realm, and flags output variables. out is filled in
251 * with ctx->previous_request, which the caller should set, and realm is filled
252 * in with the realm of ctx->cur_tgt.
253 */
254 static krb5_error_code
set_caller_request(krb5_context context,krb5_tkt_creds_context ctx)255 set_caller_request(krb5_context context, krb5_tkt_creds_context ctx)
256 {
257 krb5_error_code code;
258 const krb5_data *req = &ctx->previous_request;
259 const krb5_data *realm = &ctx->cur_tgt->server->data[1];
260 krb5_data out_copy = empty_data(), realm_copy = empty_data();
261
262 code = krb5int_copy_data_contents(context, req, &out_copy);
263 if (code != 0)
264 goto cleanup;
265 code = krb5int_copy_data_contents(context, realm, &realm_copy);
266 if (code != 0)
267 goto cleanup;
268
269 *ctx->caller_out = out_copy;
270 *ctx->caller_realm = realm_copy;
271 *ctx->caller_flags = KRB5_TKT_CREDS_STEP_FLAG_CONTINUE;
272 return 0;
273
274 cleanup:
275 krb5_free_data_contents(context, &out_copy);
276 krb5_free_data_contents(context, &realm_copy);
277 return code;
278 }
279
280 /*
281 * Set up the request given by ctx->tgs_in_creds, using ctx->cur_tgt. KDC
282 * options for the requests are determined by ctx->cur_tgt->ticket_flags and
283 * extra_options.
284 */
285 static krb5_error_code
make_request(krb5_context context,krb5_tkt_creds_context ctx,int extra_options)286 make_request(krb5_context context, krb5_tkt_creds_context ctx,
287 int extra_options)
288 {
289 krb5_error_code code;
290 krb5_data request = empty_data();
291
292 ctx->kdcopt = extra_options | FLAGS2OPTS(ctx->cur_tgt->ticket_flags);
293
294 /* XXX This check belongs in gc_via_tgt.c or nowhere. */
295 if (!krb5_c_valid_enctype(ctx->cur_tgt->keyblock.enctype))
296 return KRB5_PROG_ETYPE_NOSUPP;
297
298 /* Create a new FAST state structure to store this request's armor key. */
299 krb5int_fast_free_state(context, ctx->fast_state);
300 ctx->fast_state = NULL;
301 code = krb5int_fast_make_state(context, &ctx->fast_state);
302 if (code)
303 return code;
304
305 krb5_free_keyblock(context, ctx->subkey);
306 ctx->subkey = NULL;
307 code = k5_make_tgs_req(context, ctx->fast_state, ctx->cur_tgt, ctx->kdcopt,
308 ctx->cur_tgt->addresses, NULL, ctx->tgs_in_creds,
309 NULL, NULL, &request, &ctx->timestamp, &ctx->nonce,
310 &ctx->subkey);
311 if (code != 0)
312 return code;
313
314 krb5_free_data_contents(context, &ctx->previous_request);
315 ctx->previous_request = request;
316 return set_caller_request(context, ctx);
317 }
318
319 /* Set up a request for a TGT for realm, using ctx->cur_tgt. */
320 static krb5_error_code
make_request_for_tgt(krb5_context context,krb5_tkt_creds_context ctx,const krb5_data * realm)321 make_request_for_tgt(krb5_context context, krb5_tkt_creds_context ctx,
322 const krb5_data *realm)
323 {
324 krb5_error_code code;
325
326 /* Construct the principal krbtgt/<realm>@<cur-tgt-realm>. */
327 krb5_free_principal(context, ctx->tgt_princ);
328 ctx->tgt_princ = NULL;
329 code = krb5int_tgtname(context, realm, &ctx->cur_tgt->server->data[1],
330 &ctx->tgt_princ);
331 if (code != 0)
332 return code;
333
334 TRACE_TKT_CREDS_TGT_REQ(context, ctx->tgt_princ, ctx->cur_tgt->server);
335
336 /* Construct input creds using ctx->tgt_in_creds as a container. */
337 memset(&ctx->tgt_in_creds, 0, sizeof(ctx->tgt_in_creds));
338 ctx->tgt_in_creds.client = ctx->client;
339 ctx->tgt_in_creds.server = ctx->tgt_princ;
340
341 /* Make a request for the above creds with no extra options. */
342 ctx->tgs_in_creds = &ctx->tgt_in_creds;
343 code = make_request(context, ctx, 0);
344 return code;
345 }
346
347 /* Set up a request for the desired service principal, using ctx->cur_tgt.
348 * Optionally allow the answer to be a referral. */
349 static krb5_error_code
make_request_for_service(krb5_context context,krb5_tkt_creds_context ctx,krb5_boolean referral)350 make_request_for_service(krb5_context context, krb5_tkt_creds_context ctx,
351 krb5_boolean referral)
352 {
353 krb5_error_code code;
354 int extra_options;
355
356 TRACE_TKT_CREDS_SERVICE_REQ(context, ctx->server, referral);
357
358 /* Include the caller-specified KDC options in service requests. */
359 extra_options = ctx->req_kdcopt;
360
361 /* Automatically set the enc-tkt-in-skey flag for user-to-user requests. */
362 if (ctx->in_creds->second_ticket.length != 0)
363 extra_options |= KDC_OPT_ENC_TKT_IN_SKEY;
364
365 /* Set the canonicalize flag for referral requests. */
366 if (referral)
367 extra_options |= KDC_OPT_CANONICALIZE;
368
369 /*
370 * Use the profile enctypes for referral requests, since we might get back
371 * a TGT. We'll ask again with context enctypes if we get the actual
372 * service ticket and it's not consistent with the context enctypes.
373 */
374 if (referral)
375 context->use_conf_ktypes = TRUE;
376 ctx->tgs_in_creds = ctx->in_creds;
377 code = make_request(context, ctx, extra_options);
378 if (referral)
379 context->use_conf_ktypes = FALSE;
380 return code;
381 }
382
383 /* Decode and decrypt a TGS reply, and set the reply_code or reply_creds field
384 * of ctx with the result. Also handle too-big errors. */
385 static krb5_error_code
get_creds_from_tgs_reply(krb5_context context,krb5_tkt_creds_context ctx,krb5_data * reply)386 get_creds_from_tgs_reply(krb5_context context, krb5_tkt_creds_context ctx,
387 krb5_data *reply)
388 {
389 krb5_error_code code;
390
391 krb5_free_creds(context, ctx->reply_creds);
392 ctx->reply_creds = NULL;
393 code = krb5int_process_tgs_reply(context, ctx->fast_state,
394 reply, ctx->cur_tgt, ctx->kdcopt,
395 ctx->cur_tgt->addresses, NULL,
396 ctx->tgs_in_creds, ctx->timestamp,
397 ctx->nonce, ctx->subkey, NULL, NULL,
398 &ctx->reply_creds);
399 if (code == KRB5KRB_ERR_RESPONSE_TOO_BIG) {
400 /* Instruct the caller to re-send the request with TCP. */
401 code = set_caller_request(context, ctx);
402 if (code != 0)
403 return code;
404 return KRB5KRB_ERR_RESPONSE_TOO_BIG;
405 }
406
407 /* Depending on our state, we may or may not be able to handle an error.
408 * For now, store it in the context and return success. */
409 TRACE_TKT_CREDS_RESPONSE_CODE(context, code);
410 ctx->reply_code = code;
411 return 0;
412 }
413
414 /* Add realm to ctx->realms_seen so that we can avoid revisiting it later. */
415 static krb5_error_code
remember_realm(krb5_context context,krb5_tkt_creds_context ctx,const krb5_data * realm)416 remember_realm(krb5_context context, krb5_tkt_creds_context ctx,
417 const krb5_data *realm)
418 {
419 size_t len = 0;
420 krb5_data *new_list;
421
422 if (ctx->realms_seen != NULL) {
423 for (len = 0; ctx->realms_seen[len].data != NULL; len++);
424 }
425 new_list = realloc(ctx->realms_seen, (len + 2) * sizeof(krb5_data));
426 if (new_list == NULL)
427 return ENOMEM;
428 ctx->realms_seen = new_list;
429 new_list[len] = empty_data();
430 new_list[len + 1] = empty_data();
431 return krb5int_copy_data_contents(context, realm, &new_list[len]);
432 }
433
434 /* Return TRUE if realm appears to ctx->realms_seen. */
435 static krb5_boolean
seen_realm_before(krb5_context context,krb5_tkt_creds_context ctx,const krb5_data * realm)436 seen_realm_before(krb5_context context, krb5_tkt_creds_context ctx,
437 const krb5_data *realm)
438 {
439 size_t i;
440
441 if (ctx->realms_seen != NULL) {
442 for (i = 0; ctx->realms_seen[i].data != NULL; i++) {
443 if (data_eq(ctx->realms_seen[i], *realm))
444 return TRUE;
445 }
446 }
447 return FALSE;
448 }
449
450 /***** STATE_COMPLETE *****/
451
452 /* Check and cache the desired credential when we receive it. Expects the
453 * received credential to be in ctx->reply_creds. */
454 static krb5_error_code
complete(krb5_context context,krb5_tkt_creds_context ctx)455 complete(krb5_context context, krb5_tkt_creds_context ctx)
456 {
457 TRACE_TKT_CREDS_COMPLETE(context, ctx->reply_creds->server);
458
459 /* Put the requested server principal in the output creds. */
460 krb5_free_principal(context, ctx->reply_creds->server);
461 ctx->reply_creds->server = ctx->req_server;
462 ctx->req_server = NULL;
463
464 /* Note the authdata we asked for in the output creds. */
465 ctx->reply_creds->authdata = ctx->authdata;
466 ctx->authdata = NULL;
467
468 if (!(ctx->req_options & KRB5_GC_NO_STORE)) {
469 /* Try to cache the credential. */
470 (void) krb5_cc_store_cred(context, ctx->ccache, ctx->reply_creds);
471 }
472
473 ctx->state = STATE_COMPLETE;
474 return 0;
475 }
476
477 /***** STATE_NON_REFERRAL *****/
478
479 /* Process the response to a non-referral request. */
480 static krb5_error_code
step_non_referral(krb5_context context,krb5_tkt_creds_context ctx)481 step_non_referral(krb5_context context, krb5_tkt_creds_context ctx)
482 {
483 /* No fallbacks if we didn't get a successful reply. */
484 if (ctx->reply_code)
485 return ctx->reply_code;
486
487 return complete(context, ctx);
488 }
489
490 /* Make a non-referrals request for the desired service ticket. */
491 static krb5_error_code
begin_non_referral(krb5_context context,krb5_tkt_creds_context ctx)492 begin_non_referral(krb5_context context, krb5_tkt_creds_context ctx)
493 {
494 ctx->state = STATE_NON_REFERRAL;
495 return make_request_for_service(context, ctx, FALSE);
496 }
497
498 /***** STATE_REFERRALS *****/
499
500 /* Possibly try a non-referral request after a referral request failure.
501 * Expects ctx->reply_code to be set to the error from a referral request. */
502 static krb5_error_code
try_fallback(krb5_context context,krb5_tkt_creds_context ctx)503 try_fallback(krb5_context context, krb5_tkt_creds_context ctx)
504 {
505 krb5_error_code code;
506 char **hrealms;
507
508 /* Only fall back if our error was from the first referral request. */
509 if (ctx->referral_count > 1)
510 return ctx->reply_code;
511
512 /* If the request used a specified realm, make a non-referral request to
513 * that realm (in case it's a KDC which rejects KDC_OPT_CANONICALIZE). */
514 if (!ctx->referral_req)
515 return begin_non_referral(context, ctx);
516
517 if (ctx->server->length < 2) {
518 /* We need a type/host format principal to find a fallback realm. */
519 return KRB5_ERR_HOST_REALM_UNKNOWN;
520 }
521
522 /* We expect this to give exactly one answer (XXX clean up interface). */
523 code = krb5_get_fallback_host_realm(context, &ctx->server->data[1],
524 &hrealms);
525 if (code != 0)
526 return code;
527
528 /* If the fallback realm isn't any different, use the existing TGT. */
529 if (data_eq_string(ctx->server->realm, hrealms[0])) {
530 krb5_free_host_realm(context, hrealms);
531 return begin_non_referral(context, ctx);
532 }
533
534 /* Rewrite server->realm to be the fallback realm. */
535 krb5_free_data_contents(context, &ctx->server->realm);
536 ctx->server->realm = string2data(hrealms[0]);
537 free(hrealms);
538 TRACE_TKT_CREDS_FALLBACK(context, &ctx->server->realm);
539
540 /* Obtain a TGT for the new service realm. */
541 ctx->getting_tgt_for = STATE_NON_REFERRAL;
542 return begin_get_tgt(context, ctx);
543 }
544
545 /* Return true if context contains app-provided TGS enctypes and enctype is not
546 * one of them. */
547 static krb5_boolean
wrong_enctype(krb5_context context,krb5_enctype enctype)548 wrong_enctype(krb5_context context, krb5_enctype enctype)
549 {
550 size_t i;
551
552 if (context->tgs_etypes == NULL)
553 return FALSE;
554 for (i = 0; context->tgs_etypes[i] != 0; i++) {
555 if (enctype == context->tgs_etypes[i])
556 return FALSE;
557 }
558 return TRUE;
559 }
560
561 /* Advance the referral request loop. */
562 static krb5_error_code
step_referrals(krb5_context context,krb5_tkt_creds_context ctx)563 step_referrals(krb5_context context, krb5_tkt_creds_context ctx)
564 {
565 krb5_error_code code;
566 const krb5_data *referral_realm;
567
568 /* Possibly try a non-referral fallback request on error. */
569 if (ctx->reply_code != 0)
570 return try_fallback(context, ctx);
571
572 /* Check if we got the ticket we asked for. Allow the KDC to canonicalize
573 * the realm. */
574 if (krb5_principal_compare_any_realm(context, ctx->reply_creds->server,
575 ctx->server)) {
576 /* We didn't necessarily ask for it with the right enctypes. Try a
577 * non-referral request if so. */
578 if (wrong_enctype(context, ctx->reply_creds->keyblock.enctype)) {
579 TRACE_TKT_CREDS_WRONG_ENCTYPE(context);
580 return begin_non_referral(context, ctx);
581 }
582
583 return complete(context, ctx);
584 }
585
586 /* Old versions of Active Directory can rewrite the server name instead of
587 * returning a referral. Try a non-referral query if we see this. */
588 if (!IS_TGS_PRINC(ctx->reply_creds->server)) {
589 TRACE_TKT_CREDS_NON_TGT(context, ctx->reply_creds->server);
590 return begin_non_referral(context, ctx);
591 }
592
593 /* Active Directory may return a TGT to the local realm. Try a
594 * non-referral query if we see this. */
595 referral_realm = &ctx->reply_creds->server->data[1];
596 if (data_eq(*referral_realm, ctx->cur_tgt->server->data[1])) {
597 TRACE_TKT_CREDS_SAME_REALM_TGT(context, referral_realm);
598 return begin_non_referral(context, ctx);
599 }
600
601 if (ctx->referral_count == 1) {
602 /* The authdata in this TGT will be copied into subsequent TGTs or the
603 * final credentials, so we don't need to request it again. */
604 krb5_free_authdata(context, ctx->in_creds->authdata);
605 ctx->in_creds->authdata = NULL;
606 }
607
608 /* Give up if we've gotten too many referral TGTs. */
609 if (ctx->referral_count++ >= KRB5_REFERRAL_MAXHOPS)
610 return KRB5_KDC_UNREACH;
611
612 /* Check for referral loops. */
613 if (seen_realm_before(context, ctx, referral_realm))
614 return KRB5_KDC_UNREACH;
615 code = remember_realm(context, ctx, referral_realm);
616 if (code != 0)
617 return code;
618
619 /* Use the referral TGT for the next request. */
620 krb5_free_creds(context, ctx->cur_tgt);
621 ctx->cur_tgt = ctx->reply_creds;
622 ctx->reply_creds = NULL;
623 TRACE_TKT_CREDS_REFERRAL(context, ctx->cur_tgt->server);
624
625 /* Rewrite the server realm to be the referral realm. */
626 krb5_free_data_contents(context, &ctx->server->realm);
627 code = krb5int_copy_data_contents(context, referral_realm,
628 &ctx->server->realm);
629 if (code != 0)
630 return code;
631
632 /* Generate the next referral request. */
633 return make_request_for_service(context, ctx, TRUE);
634 }
635
636 /*
637 * Begin the referrals request loop. Expects ctx->cur_tgt to be a TGT for
638 * ctx->realm->server.
639 */
640 static krb5_error_code
begin_referrals(krb5_context context,krb5_tkt_creds_context ctx)641 begin_referrals(krb5_context context, krb5_tkt_creds_context ctx)
642 {
643 ctx->state = STATE_REFERRALS;
644 ctx->referral_count = 1;
645
646 /* Empty out the realms-seen list for loop checking. */
647 krb5int_free_data_list(context, ctx->realms_seen);
648 ctx->realms_seen = NULL;
649
650 /* Generate the first referral request. */
651 return make_request_for_service(context, ctx, TRUE);
652 }
653
654 /***** STATE_GET_TGT_OFFPATH *****/
655
656 /*
657 * Foreign TGT acquisition can happen either before the referrals loop, if the
658 * service principal had an explicitly specified foreign realm, or after it
659 * fails, if we wind up using the fallback realm. end_get_tgt() advances to
660 * the appropriate state depending on which we were doing.
661 */
662 static krb5_error_code
end_get_tgt(krb5_context context,krb5_tkt_creds_context ctx)663 end_get_tgt(krb5_context context, krb5_tkt_creds_context ctx)
664 {
665 if (ctx->getting_tgt_for == STATE_REFERRALS)
666 return begin_referrals(context, ctx);
667 else
668 return begin_non_referral(context, ctx);
669 }
670
671 /*
672 * We enter STATE_GET_TGT_OFFPATH from STATE_GET_TGT if we receive, from one of
673 * the KDCs in the expected path, a TGT for a realm not in the path. This may
674 * happen if the KDC has a different idea of the expected path than we do. If
675 * it happens, we repeatedly ask the KDC of the TGT we have for a destination
676 * realm TGT, until we get it, fail, or give up.
677 */
678
679 /* Advance the process of chasing off-path TGTs. */
680 static krb5_error_code
step_get_tgt_offpath(krb5_context context,krb5_tkt_creds_context ctx)681 step_get_tgt_offpath(krb5_context context, krb5_tkt_creds_context ctx)
682 {
683 krb5_error_code code;
684 const krb5_data *tgt_realm;
685
686 /* We have no fallback if the last request failed, so just give up. */
687 if (ctx->reply_code != 0)
688 return ctx->reply_code;
689
690 /* Verify that we got a TGT. */
691 if (!IS_TGS_PRINC(ctx->reply_creds->server))
692 return KRB5_KDCREP_MODIFIED;
693
694 /* Use this tgt for the next request. */
695 krb5_free_creds(context, ctx->cur_tgt);
696 ctx->cur_tgt = ctx->reply_creds;
697 ctx->reply_creds = NULL;
698
699 /* Check if we've seen this realm before, and remember it. */
700 tgt_realm = &ctx->cur_tgt->server->data[1];
701 if (seen_realm_before(context, ctx, tgt_realm))
702 return KRB5_KDC_UNREACH;
703 code = remember_realm(context, ctx, tgt_realm);
704 if (code != 0)
705 return code;
706
707 if (data_eq(*tgt_realm, ctx->server->realm)) {
708 /* We received the server realm TGT we asked for. */
709 TRACE_TKT_CREDS_TARGET_TGT_OFFPATH(context, ctx->cur_tgt->server);
710 return end_get_tgt(context, ctx);
711 } else if (ctx->offpath_count++ >= KRB5_REFERRAL_MAXHOPS) {
712 /* Time to give up. */
713 return KRB5_KDCREP_MODIFIED;
714 }
715
716 return make_request_for_tgt(context, ctx, &ctx->server->realm);
717 }
718
719 /* Begin chasing off-path referrals, starting from ctx->cur_tgt. */
720 static krb5_error_code
begin_get_tgt_offpath(krb5_context context,krb5_tkt_creds_context ctx)721 begin_get_tgt_offpath(krb5_context context, krb5_tkt_creds_context ctx)
722 {
723 ctx->state = STATE_GET_TGT_OFFPATH;
724 ctx->offpath_count = 1;
725 return make_request_for_tgt(context, ctx, &ctx->server->realm);
726 }
727
728 /***** STATE_GET_TGT *****/
729
730 /*
731 * To obtain a foreign TGT, we first construct a path of realms R1..Rn between
732 * the local realm and the target realm, using k5_client_realm_path(). Usually
733 * this path is based on the domain hierarchy, but it may be altered by
734 * configuration.
735 *
736 * We begin with cur_realm set to the local realm (R1) and next_realm set to
737 * the target realm (Rn). At each step, we check to see if we have a cached
738 * TGT for next_realm; if not, we ask cur_realm to give us a TGT for
739 * next_realm. If that fails, we decrement next_realm until we get a
740 * successful answer or reach cur_realm--in which case we've gotten as far as
741 * we can, and have to give up. If we do get back a TGT, it may or may not be
742 * for the realm we asked for, so we search for it in the path. The realm of
743 * the TGT we get back becomes cur_realm, and next_realm is reset to the target
744 * realm. Overall, this is an O(n^2) process in the length of the path, but
745 * the path length will generally be short and the process will usually end
746 * much faster than the worst case.
747 *
748 * In some cases we may get back a TGT for a realm not in the path. In that
749 * case we enter STATE_GET_TGT_OFFPATH.
750 */
751
752 /*
753 * Point *tgt_out at an allocated credentials structure containing a
754 * cross-realm TGT for realm retrieved from ctx->ccache. Accept any issuing
755 * realm (i.e. match only the service principal name). If the TGT is not found
756 * in the cache, return successfully but set *tgt_out to NULL.
757 */
758 static krb5_error_code
get_cached_tgt(krb5_context context,krb5_tkt_creds_context ctx,const krb5_data * realm,krb5_creds ** tgt_out)759 get_cached_tgt(krb5_context context, krb5_tkt_creds_context ctx,
760 const krb5_data *realm, krb5_creds **tgt_out)
761 {
762 krb5_creds mcreds;
763 krb5_error_code code;
764 krb5_principal tgtname = NULL;
765 krb5_flags flags = KRB5_TC_SUPPORTED_KTYPES | KRB5_TC_MATCH_SRV_NAMEONLY |
766 KRB5_TC_MATCH_TIMES;
767 krb5_timestamp now;
768
769 *tgt_out = NULL;
770
771 code = krb5_timeofday(context, &now);
772 if (code != 0)
773 return code;
774
775 /* Construct the TGT principal name (the realm part doesn't matter). */
776 code = krb5int_tgtname(context, realm, realm, &tgtname);
777 if (code != 0)
778 return code;
779
780 /* Construct a matching cred for the ccache query. Look for unexpired
781 * entries since there could be more than one. */
782 memset(&mcreds, 0, sizeof(mcreds));
783 mcreds.client = ctx->client;
784 mcreds.server = tgtname;
785 mcreds.times.endtime = now;
786
787 /* Fetch the TGT credential. */
788 context->use_conf_ktypes = TRUE;
789 code = cache_get(context, ctx->ccache, flags, &mcreds, tgt_out);
790 context->use_conf_ktypes = FALSE;
791 krb5_free_principal(context, tgtname);
792 return (code == KRB5_CC_NOTFOUND || code != KRB5_CC_NOT_KTYPE) ? 0 : code;
793 }
794
795 /* Point *tgt_out at an allocated credentials structure containing the local
796 * TGT retrieved from ctx->ccache. */
797 static krb5_error_code
get_cached_local_tgt(krb5_context context,krb5_tkt_creds_context ctx,krb5_creds ** tgt_out)798 get_cached_local_tgt(krb5_context context, krb5_tkt_creds_context ctx,
799 krb5_creds **tgt_out)
800 {
801 krb5_creds mcreds;
802 krb5_error_code code;
803 krb5_principal tgtname = NULL;
804 krb5_flags flags = KRB5_TC_SUPPORTED_KTYPES;
805 krb5_timestamp now;
806 krb5_creds *tgt;
807
808 *tgt_out = NULL;
809
810 code = krb5_timeofday(context, &now);
811 if (code != 0)
812 return code;
813
814 /* Construct the principal name. */
815 code = krb5int_tgtname(context, &ctx->start_realm, &ctx->start_realm,
816 &tgtname);
817 if (code != 0)
818 return code;
819
820 /* Construct a matching cred for the ccache query. */
821 memset(&mcreds, 0, sizeof(mcreds));
822 mcreds.client = ctx->client;
823 mcreds.server = tgtname;
824
825 /* Fetch the TGT credential. */
826 context->use_conf_ktypes = TRUE;
827 code = cache_get(context, ctx->ccache, flags, &mcreds, &tgt);
828 context->use_conf_ktypes = FALSE;
829 krb5_free_principal(context, tgtname);
830 if (code)
831 return code;
832
833 /* Check if the TGT is expired before bothering the KDC with it. */
834 if (ts_after(now, tgt->times.endtime)) {
835 krb5_free_creds(context, tgt);
836 return KRB5KRB_AP_ERR_TKT_EXPIRED;
837 }
838
839 *tgt_out = tgt;
840 return 0;
841 }
842
843 /* Initialize the realm path fields for getting a TGT for
844 * ctx->server->realm. */
845 static krb5_error_code
init_realm_path(krb5_context context,krb5_tkt_creds_context ctx)846 init_realm_path(krb5_context context, krb5_tkt_creds_context ctx)
847 {
848 krb5_error_code code;
849 krb5_data *realm_path;
850 size_t nrealms;
851
852 /* Get the client realm path and count its length. */
853 code = k5_client_realm_path(context, &ctx->start_realm,
854 &ctx->server->realm, &realm_path);
855 if (code != 0)
856 return code;
857 for (nrealms = 0; realm_path[nrealms].data != NULL; nrealms++);
858 assert(nrealms > 1);
859
860 /* Initialize the realm path fields in ctx. */
861 krb5int_free_data_list(context, ctx->realm_path);
862 ctx->realm_path = realm_path;
863 ctx->last_realm = realm_path + nrealms - 1;
864 ctx->cur_realm = realm_path;
865 ctx->next_realm = ctx->last_realm;
866 return 0;
867 }
868
869 /* Find realm within the portion of ctx->realm_path following
870 * ctx->cur_realm. Return NULL if it is not found. */
871 static const krb5_data *
find_realm_in_path(krb5_context context,krb5_tkt_creds_context ctx,const krb5_data * realm)872 find_realm_in_path(krb5_context context, krb5_tkt_creds_context ctx,
873 const krb5_data *realm)
874 {
875 const krb5_data *r;
876
877 for (r = ctx->cur_realm + 1; r->data != NULL; r++) {
878 if (data_eq(*r, *realm))
879 return r;
880 }
881 return NULL;
882 }
883
884 /*
885 * Generate the next request in the path traversal. If a cached TGT for the
886 * target realm appeared in the ccache since we started the TGT acquisition
887 * process, this function may invoke end_get_tgt().
888 */
889 static krb5_error_code
get_tgt_request(krb5_context context,krb5_tkt_creds_context ctx)890 get_tgt_request(krb5_context context, krb5_tkt_creds_context ctx)
891 {
892 krb5_error_code code;
893 krb5_creds *cached_tgt;
894
895 while (1) {
896 /* Check if we have a cached TGT for the target realm. */
897 code = get_cached_tgt(context, ctx, ctx->next_realm, &cached_tgt);
898 if (code != 0)
899 return code;
900 if (cached_tgt != NULL) {
901 /* Advance the current realm and keep going. */
902 TRACE_TKT_CREDS_CACHED_INTERMEDIATE_TGT(context, cached_tgt);
903 krb5_free_creds(context, ctx->cur_tgt);
904 ctx->cur_tgt = cached_tgt;
905 if (ctx->next_realm == ctx->last_realm)
906 return end_get_tgt(context, ctx);
907 ctx->cur_realm = ctx->next_realm;
908 ctx->next_realm = ctx->last_realm;
909 continue;
910 }
911
912 return make_request_for_tgt(context, ctx, ctx->next_realm);
913 }
914 }
915
916 /* Process a TGS reply and advance the path traversal to get a foreign TGT. */
917 static krb5_error_code
step_get_tgt(krb5_context context,krb5_tkt_creds_context ctx)918 step_get_tgt(krb5_context context, krb5_tkt_creds_context ctx)
919 {
920 krb5_error_code code;
921 const krb5_data *tgt_realm, *path_realm;
922
923 if (ctx->reply_code != 0) {
924 /* The last request failed. Try the next-closest realm to
925 * ctx->cur_realm. */
926 ctx->next_realm--;
927 if (ctx->next_realm == ctx->cur_realm) {
928 /* We've tried all the realms we could and couldn't progress beyond
929 * ctx->cur_realm, so it's time to give up. */
930 return ctx->reply_code;
931 }
932 TRACE_TKT_CREDS_CLOSER_REALM(context, ctx->next_realm);
933 } else {
934 /* Verify that we got a TGT. */
935 if (!IS_TGS_PRINC(ctx->reply_creds->server))
936 return KRB5_KDCREP_MODIFIED;
937
938 /* Use this tgt for the next request regardless of what it is. */
939 krb5_free_creds(context, ctx->cur_tgt);
940 ctx->cur_tgt = ctx->reply_creds;
941 ctx->reply_creds = NULL;
942
943 /* Remember that we saw this realm. */
944 tgt_realm = &ctx->cur_tgt->server->data[1];
945 code = remember_realm(context, ctx, tgt_realm);
946 if (code != 0)
947 return code;
948
949 /* See where we wound up on the path (or off it). */
950 path_realm = find_realm_in_path(context, ctx, tgt_realm);
951 if (path_realm != NULL) {
952 /* Only cache the TGT if we asked for it, to avoid duplicates. */
953 if (path_realm == ctx->next_realm)
954 (void)krb5_cc_store_cred(context, ctx->ccache, ctx->cur_tgt);
955 if (path_realm == ctx->last_realm) {
956 /* We received a TGT for the target realm. */
957 TRACE_TKT_CREDS_TARGET_TGT(context, ctx->cur_tgt->server);
958 return end_get_tgt(context, ctx);
959 } else if (path_realm != NULL) {
960 /* We still have further to go; advance the traversal. */
961 TRACE_TKT_CREDS_ADVANCE(context, tgt_realm);
962 ctx->cur_realm = path_realm;
963 ctx->next_realm = ctx->last_realm;
964 }
965 } else if (data_eq(*tgt_realm, ctx->start_realm)) {
966 /* We were referred back to the local realm, which is bad. */
967 return KRB5_KDCREP_MODIFIED;
968 } else {
969 /* We went off the path; start the off-path chase. */
970 TRACE_TKT_CREDS_OFFPATH(context, tgt_realm);
971 return begin_get_tgt_offpath(context, ctx);
972 }
973 }
974
975 /* Generate the next request in the path traversal. */
976 return get_tgt_request(context, ctx);
977 }
978
979 /*
980 * Begin the process of getting a foreign TGT, either for the explicitly
981 * specified server realm or for the fallback realm. Expects that
982 * ctx->server->realm is the realm of the desired TGT, and that
983 * ctx->getting_tgt_for is the state we should advance to after we have the
984 * desired TGT.
985 */
986 static krb5_error_code
begin_get_tgt(krb5_context context,krb5_tkt_creds_context ctx)987 begin_get_tgt(krb5_context context, krb5_tkt_creds_context ctx)
988 {
989 krb5_error_code code;
990 krb5_creds *cached_tgt;
991 krb5_boolean is_local_service;
992
993 ctx->state = STATE_GET_TGT;
994
995 is_local_service = data_eq(ctx->start_realm, ctx->server->realm);
996 if (!is_local_service) {
997 /* See if we have a cached TGT for the server realm. */
998 code = get_cached_tgt(context, ctx, &ctx->server->realm, &cached_tgt);
999 if (code != 0)
1000 return code;
1001 if (cached_tgt != NULL) {
1002 TRACE_TKT_CREDS_CACHED_SERVICE_TGT(context, cached_tgt);
1003 krb5_free_creds(context, ctx->cur_tgt);
1004 ctx->cur_tgt = cached_tgt;
1005 return end_get_tgt(context, ctx);
1006 }
1007 }
1008
1009 /* Start with the local tgt. */
1010 krb5_free_creds(context, ctx->cur_tgt);
1011 ctx->cur_tgt = NULL;
1012 code = get_cached_local_tgt(context, ctx, &ctx->cur_tgt);
1013 if (code != 0)
1014 return code;
1015 TRACE_TKT_CREDS_LOCAL_TGT(context, ctx->cur_tgt);
1016
1017 if (is_local_service)
1018 return end_get_tgt(context, ctx);
1019
1020 /* Initialize the realm path. */
1021 code = init_realm_path(context, ctx);
1022 if (code != 0)
1023 return code;
1024
1025 /* Empty out the realms-seen list for loop checking. */
1026 krb5int_free_data_list(context, ctx->realms_seen);
1027 ctx->realms_seen = NULL;
1028
1029 /* Generate the first request. */
1030 return get_tgt_request(context, ctx);
1031 }
1032
1033 /***** STATE_BEGIN *****/
1034
1035 /*
1036 * Look for the desired credentials in the cache, if possible. If we find
1037 * them, put them in ctx->reply_creds and advance the state to STATE_COMPLETE.
1038 * Return successfully even if creds are not found, unless the caller only
1039 * wanted cached creds.
1040 */
1041 static krb5_error_code
check_cache(krb5_context context,krb5_tkt_creds_context ctx)1042 check_cache(krb5_context context, krb5_tkt_creds_context ctx)
1043 {
1044 krb5_error_code code;
1045 krb5_creds req_in_creds;
1046
1047 /* Check the cache for the originally requested server principal. */
1048 req_in_creds = *ctx->in_creds;
1049 req_in_creds.server = ctx->req_server;
1050 code = k5_get_cached_cred(context, ctx->req_options, ctx->ccache,
1051 &req_in_creds, &ctx->reply_creds);
1052 if (code == 0) {
1053 ctx->state = STATE_COMPLETE;
1054 return 0;
1055 }
1056
1057 /* Stop on unexpected cache errors. */
1058 if (code != KRB5_CC_NOTFOUND && code != KRB5_CC_NOT_KTYPE)
1059 return code;
1060
1061 /* Stop if the caller only wanted cached creds. */
1062 if (ctx->req_options & KRB5_GC_CACHED)
1063 return code;
1064
1065 return 0;
1066 }
1067
1068 /* Decide where to begin the acquisition process. */
1069 static krb5_error_code
begin(krb5_context context,krb5_tkt_creds_context ctx)1070 begin(krb5_context context, krb5_tkt_creds_context ctx)
1071 {
1072 krb5_error_code code;
1073
1074 /* If the server realm is unspecified, start with the TGT realm. */
1075 ctx->referral_req = krb5_is_referral_realm(&ctx->server->realm);
1076 if (ctx->referral_req) {
1077 krb5_free_data_contents(context, &ctx->server->realm);
1078 code = krb5int_copy_data_contents(context, &ctx->start_realm,
1079 &ctx->server->realm);
1080 TRACE_TKT_CREDS_REFERRAL_REALM(context, ctx->server);
1081 if (code != 0)
1082 return code;
1083 }
1084
1085 /* Obtain a TGT for the service realm. */
1086 ctx->getting_tgt_for = STATE_REFERRALS;
1087 return begin_get_tgt(context, ctx);
1088 }
1089
1090 /***** API functions *****/
1091
1092 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)1093 krb5_tkt_creds_init(krb5_context context, krb5_ccache ccache,
1094 krb5_creds *in_creds, krb5_flags options,
1095 krb5_tkt_creds_context *pctx)
1096 {
1097 krb5_error_code code;
1098 krb5_tkt_creds_context ctx = NULL;
1099 krb5_const_principal canonprinc;
1100
1101 TRACE_TKT_CREDS(context, in_creds, ccache);
1102 ctx = k5alloc(sizeof(*ctx), &code);
1103 if (ctx == NULL)
1104 goto cleanup;
1105
1106 ctx->req_options = options;
1107 ctx->req_kdcopt = 0;
1108 if (options & KRB5_GC_CANONICALIZE)
1109 ctx->req_kdcopt |= KDC_OPT_CANONICALIZE;
1110 if (options & KRB5_GC_FORWARDABLE)
1111 ctx->req_kdcopt |= KDC_OPT_FORWARDABLE;
1112 if (options & KRB5_GC_NO_TRANSIT_CHECK)
1113 ctx->req_kdcopt |= KDC_OPT_DISABLE_TRANSITED_CHECK;
1114
1115 ctx->state = STATE_BEGIN;
1116
1117 /* Copy the matching cred so we can modify it. Steal the copy of the
1118 * service principal name to remember the original request server. */
1119 code = krb5_copy_creds(context, in_creds, &ctx->in_creds);
1120 if (code != 0)
1121 goto cleanup;
1122 ctx->req_server = ctx->in_creds->server;
1123 ctx->in_creds->server = NULL;
1124
1125 /* Get the first canonicalization candidate for the requested server. */
1126 ctx->iter.princ = ctx->req_server;
1127
1128 code = k5_canonprinc(context, &ctx->iter, &canonprinc);
1129 if (code == 0 && canonprinc == NULL)
1130 code = KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN;
1131 if (code != 0)
1132 goto cleanup;
1133 code = krb5_copy_principal(context, canonprinc, &ctx->in_creds->server);
1134 if (code != 0)
1135 goto cleanup;
1136
1137 ctx->client = ctx->in_creds->client;
1138 ctx->server = ctx->in_creds->server;
1139 code = krb5_cc_dup(context, ccache, &ctx->ccache);
1140 if (code != 0)
1141 goto cleanup;
1142
1143 /* Get the start realm from the cache config, defaulting to the client
1144 * realm. */
1145 code = krb5_cc_get_config(context, ccache, NULL, "start_realm",
1146 &ctx->start_realm);
1147 if (code != 0) {
1148 code = krb5int_copy_data_contents(context, &ctx->client->realm,
1149 &ctx->start_realm);
1150 if (code != 0)
1151 goto cleanup;
1152 }
1153
1154 code = krb5_copy_authdata(context, in_creds->authdata, &ctx->authdata);
1155 if (code != 0)
1156 goto cleanup;
1157
1158 *pctx = ctx;
1159 ctx = NULL;
1160
1161 cleanup:
1162 krb5_tkt_creds_free(context, ctx);
1163 return code;
1164 }
1165
1166 krb5_error_code KRB5_CALLCONV
krb5_tkt_creds_get_creds(krb5_context context,krb5_tkt_creds_context ctx,krb5_creds * creds)1167 krb5_tkt_creds_get_creds(krb5_context context, krb5_tkt_creds_context ctx,
1168 krb5_creds *creds)
1169 {
1170 if (ctx->state != STATE_COMPLETE)
1171 return KRB5_NO_TKT_SUPPLIED;
1172 return k5_copy_creds_contents(context, ctx->reply_creds, creds);
1173 }
1174
1175 krb5_error_code KRB5_CALLCONV
krb5_tkt_creds_get_times(krb5_context context,krb5_tkt_creds_context ctx,krb5_ticket_times * times)1176 krb5_tkt_creds_get_times(krb5_context context, krb5_tkt_creds_context ctx,
1177 krb5_ticket_times *times)
1178 {
1179 if (ctx->state != STATE_COMPLETE)
1180 return KRB5_NO_TKT_SUPPLIED;
1181 *times = ctx->reply_creds->times;
1182 return 0;
1183 }
1184
1185 void KRB5_CALLCONV
krb5_tkt_creds_free(krb5_context context,krb5_tkt_creds_context ctx)1186 krb5_tkt_creds_free(krb5_context context, krb5_tkt_creds_context ctx)
1187 {
1188 if (ctx == NULL)
1189 return;
1190 krb5int_fast_free_state(context, ctx->fast_state);
1191 krb5_free_creds(context, ctx->in_creds);
1192 free_canonprinc(&ctx->iter);
1193 krb5_cc_close(context, ctx->ccache);
1194 krb5_free_data_contents(context, &ctx->start_realm);
1195 krb5_free_principal(context, ctx->req_server);
1196 krb5_free_authdata(context, ctx->authdata);
1197 krb5_free_creds(context, ctx->cur_tgt);
1198 krb5int_free_data_list(context, ctx->realms_seen);
1199 krb5_free_principal(context, ctx->tgt_princ);
1200 krb5_free_keyblock(context, ctx->subkey);
1201 krb5_free_data_contents(context, &ctx->previous_request);
1202 krb5int_free_data_list(context, ctx->realm_path);
1203 krb5_free_creds(context, ctx->reply_creds);
1204 free(ctx);
1205 }
1206
1207 krb5_error_code KRB5_CALLCONV
krb5_tkt_creds_get(krb5_context context,krb5_tkt_creds_context ctx)1208 krb5_tkt_creds_get(krb5_context context, krb5_tkt_creds_context ctx)
1209 {
1210 krb5_error_code code;
1211 krb5_data request = empty_data(), reply = empty_data();
1212 krb5_data realm = empty_data();
1213 unsigned int flags = 0;
1214 int no_udp = 0;
1215
1216 for (;;) {
1217 /* Get the next request and realm. Turn on TCP if necessary. */
1218 code = krb5_tkt_creds_step(context, ctx, &reply, &request, &realm,
1219 &flags);
1220 if (code == KRB5KRB_ERR_RESPONSE_TOO_BIG && !no_udp) {
1221 TRACE_TKT_CREDS_RETRY_TCP(context);
1222 no_udp = 1;
1223 } else if (code != 0 || !(flags & KRB5_TKT_CREDS_STEP_FLAG_CONTINUE))
1224 break;
1225 krb5_free_data_contents(context, &reply);
1226
1227 /* Send it to a KDC for the appropriate realm. */
1228 code = k5_sendto_kdc(context, &request, &realm, FALSE, no_udp,
1229 &reply, NULL);
1230 if (code != 0)
1231 break;
1232
1233 krb5_free_data_contents(context, &request);
1234 krb5_free_data_contents(context, &realm);
1235 }
1236
1237 krb5_free_data_contents(context, &request);
1238 krb5_free_data_contents(context, &reply);
1239 krb5_free_data_contents(context, &realm);
1240 return code;
1241 }
1242
1243 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)1244 krb5_tkt_creds_step(krb5_context context, krb5_tkt_creds_context ctx,
1245 krb5_data *in, krb5_data *out, krb5_data *realm,
1246 unsigned int *flags)
1247 {
1248 krb5_error_code code;
1249 krb5_boolean no_input = (in == NULL || in->length == 0);
1250 krb5_const_principal canonprinc;
1251
1252 *out = empty_data();
1253 *realm = empty_data();
1254 *flags = 0;
1255
1256 /* We should receive an empty input on the first step only, and should not
1257 * get called after completion. */
1258 if (no_input != (ctx->state == STATE_BEGIN) ||
1259 ctx->state == STATE_COMPLETE)
1260 return EINVAL;
1261
1262 if (ctx->state == STATE_BEGIN) {
1263 code = check_cache(context, ctx);
1264 if (code != 0 || ctx->state == STATE_COMPLETE)
1265 return code;
1266 }
1267
1268 ctx->caller_out = out;
1269 ctx->caller_realm = realm;
1270 ctx->caller_flags = flags;
1271
1272 if (!no_input) {
1273 /* Convert the input token into a credential and store it in ctx. */
1274 code = get_creds_from_tgs_reply(context, ctx, in);
1275 if (code != 0)
1276 return code;
1277 }
1278
1279 if (ctx->state == STATE_BEGIN)
1280 code = begin(context, ctx);
1281 else if (ctx->state == STATE_GET_TGT)
1282 code = step_get_tgt(context, ctx);
1283 else if (ctx->state == STATE_GET_TGT_OFFPATH)
1284 code = step_get_tgt_offpath(context, ctx);
1285 else if (ctx->state == STATE_REFERRALS)
1286 code = step_referrals(context, ctx);
1287 else if (ctx->state == STATE_NON_REFERRAL)
1288 code = step_non_referral(context, ctx);
1289 else
1290 code = EINVAL;
1291
1292 /* Terminate on success or most errors. */
1293 if (code != KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN)
1294 return code;
1295
1296 /* Restart with the next server principal canonicalization candidate. */
1297 code = k5_canonprinc(context, &ctx->iter, &canonprinc);
1298 if (code)
1299 return code;
1300 if (canonprinc == NULL)
1301 return KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN;
1302 krb5_free_principal(context, ctx->in_creds->server);
1303 code = krb5_copy_principal(context, canonprinc, &ctx->in_creds->server);
1304 ctx->server = ctx->in_creds->server;
1305 return begin(context, ctx);
1306 }
1307
1308 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)1309 krb5_get_credentials(krb5_context context, krb5_flags options,
1310 krb5_ccache ccache, krb5_creds *in_creds,
1311 krb5_creds **out_creds)
1312 {
1313 krb5_error_code code;
1314 krb5_creds *ncreds = NULL;
1315 krb5_tkt_creds_context ctx = NULL;
1316
1317 *out_creds = NULL;
1318
1319 /* If S4U2Proxy is requested, use the synchronous implementation in
1320 * s4u_creds.c. */
1321 if (options & KRB5_GC_CONSTRAINED_DELEGATION) {
1322 return k5_get_proxy_cred_from_kdc(context, options, ccache, in_creds,
1323 out_creds);
1324 }
1325
1326 /* Allocate a container. */
1327 ncreds = k5alloc(sizeof(*ncreds), &code);
1328 if (ncreds == NULL)
1329 goto cleanup;
1330
1331 /* Make and execute a krb5_tkt_creds context to get the credential. */
1332 code = krb5_tkt_creds_init(context, ccache, in_creds, options, &ctx);
1333 if (code != 0)
1334 goto cleanup;
1335 code = krb5_tkt_creds_get(context, ctx);
1336 if (code != 0)
1337 goto cleanup;
1338 code = krb5_tkt_creds_get_creds(context, ctx, ncreds);
1339 if (code != 0)
1340 goto cleanup;
1341
1342 *out_creds = ncreds;
1343 ncreds = NULL;
1344
1345 cleanup:
1346 krb5_free_creds(context, ncreds);
1347 krb5_tkt_creds_free(context, ctx);
1348 return code;
1349 }
1350