xref: /freebsd/crypto/krb5/src/lib/krb5/krb/get_creds.c (revision f1c4c3daccbaf3820f0e2224de53df12fc952fcc)
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