Coverage Report

Created: 2025-11-03 06:53

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/krb5/src/lib/krb5/krb/get_creds.c
Line
Count
Source
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
52
construct_matching_creds(krb5_context context, krb5_flags options,
53
                         krb5_creds *in_creds, krb5_creds *mcreds,
54
                         krb5_flags *fields)
55
0
{
56
0
    krb5_error_code ret;
57
58
0
    if (!in_creds || !in_creds->server || !in_creds->client)
59
0
        return EINVAL;
60
61
0
    memset(mcreds, 0, sizeof(krb5_creds));
62
0
    mcreds->magic = KV5M_CREDS;
63
0
    ret = krb5_timeofday(context, &mcreds->times.endtime);
64
0
    if (ret)
65
0
        return ret;
66
0
    mcreds->keyblock = in_creds->keyblock;
67
0
    mcreds->authdata = in_creds->authdata;
68
0
    mcreds->server = in_creds->server;
69
0
    mcreds->client = in_creds->client;
70
71
0
    *fields = KRB5_TC_MATCH_TIMES /*XXX |KRB5_TC_MATCH_SKEY_TYPE */
72
0
        | KRB5_TC_MATCH_AUTHDATA
73
0
        | KRB5_TC_SUPPORTED_KTYPES;
74
0
    if (mcreds->keyblock.enctype) {
75
0
        krb5_enctype *ktypes;
76
0
        size_t i;
77
78
0
        *fields |= KRB5_TC_MATCH_KTYPE;
79
0
        ret = krb5_get_tgs_ktypes(context, mcreds->server, &ktypes);
80
0
        for (i = 0; ktypes[i]; i++)
81
0
            if (ktypes[i] == mcreds->keyblock.enctype)
82
0
                break;
83
0
        if (ktypes[i] == 0)
84
0
            ret = KRB5_CC_NOT_KTYPE;
85
0
        free (ktypes);
86
0
        if (ret)
87
0
            return ret;
88
0
    }
89
0
    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
0
        *fields |= KRB5_TC_MATCH_2ND_TKT;
93
0
        if (options & KRB5_GC_USER_USER) {
94
0
            *fields |= KRB5_TC_MATCH_IS_SKEY;
95
0
            mcreds->is_skey = TRUE;
96
0
        }
97
0
        mcreds->second_ticket = in_creds->second_ticket;
98
0
        if (!in_creds->second_ticket.length)
99
0
            return KRB5_NO_2ND_TKT;
100
0
    }
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
0
    if (options & KRB5_GC_CONSTRAINED_DELEGATION)
105
0
        mcreds->client = NULL;
106
107
0
    return 0;
108
0
}
109
110
/* Simple wrapper around krb5_cc_retrieve_cred which allocates the result
111
 * container. */
112
static krb5_error_code
113
cache_get(krb5_context context, krb5_ccache ccache, krb5_flags flags,
114
          krb5_creds *in_creds, krb5_creds **out_creds)
115
0
{
116
0
    krb5_error_code code;
117
0
    krb5_creds *creds;
118
119
0
    *out_creds = NULL;
120
121
0
    creds = malloc(sizeof(*creds));
122
0
    if (creds == NULL)
123
0
        return ENOMEM;
124
125
0
    code = krb5_cc_retrieve_cred(context, ccache, flags, in_creds, creds);
126
0
    if (code != 0) {
127
0
        free(creds);
128
0
        return code;
129
0
    }
130
131
0
    *out_creds = creds;
132
0
    return 0;
133
0
}
134
135
krb5_error_code
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
0
{
140
0
    krb5_error_code code;
141
0
    krb5_creds mcreds;
142
0
    krb5_flags fields;
143
144
0
    *creds_out = NULL;
145
146
0
    code = construct_matching_creds(context, options, in_creds,
147
0
                                    &mcreds, &fields);
148
0
    if (code)
149
0
        return code;
150
151
0
    return cache_get(context, ccache, fields, &mcreds, creds_out);
152
0
}
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
0
#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
255
set_caller_request(krb5_context context, krb5_tkt_creds_context ctx)
256
0
{
257
0
    krb5_error_code code;
258
0
    const krb5_data *req = &ctx->previous_request;
259
0
    const krb5_data *realm = &ctx->cur_tgt->server->data[1];
260
0
    krb5_data out_copy = empty_data(), realm_copy = empty_data();
261
262
0
    code = krb5int_copy_data_contents(context, req, &out_copy);
263
0
    if (code != 0)
264
0
        goto cleanup;
265
0
    code = krb5int_copy_data_contents(context, realm, &realm_copy);
266
0
    if (code != 0)
267
0
        goto cleanup;
268
269
0
    *ctx->caller_out = out_copy;
270
0
    *ctx->caller_realm = realm_copy;
271
0
    *ctx->caller_flags = KRB5_TKT_CREDS_STEP_FLAG_CONTINUE;
272
0
    return 0;
273
274
0
cleanup:
275
0
    krb5_free_data_contents(context, &out_copy);
276
0
    krb5_free_data_contents(context, &realm_copy);
277
0
    return code;
278
0
}
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
286
make_request(krb5_context context, krb5_tkt_creds_context ctx,
287
             int extra_options)
288
0
{
289
0
    krb5_error_code code;
290
0
    krb5_data request = empty_data();
291
292
0
    ctx->kdcopt = extra_options | FLAGS2OPTS(ctx->cur_tgt->ticket_flags);
293
294
    /* XXX This check belongs in gc_via_tgt.c or nowhere. */
295
0
    if (!krb5_c_valid_enctype(ctx->cur_tgt->keyblock.enctype))
296
0
        return KRB5_PROG_ETYPE_NOSUPP;
297
298
    /* Create a new FAST state structure to store this request's armor key. */
299
0
    krb5int_fast_free_state(context, ctx->fast_state);
300
0
    ctx->fast_state = NULL;
301
0
    code = krb5int_fast_make_state(context, &ctx->fast_state);
302
0
    if (code)
303
0
        return code;
304
305
0
    krb5_free_keyblock(context, ctx->subkey);
306
0
    ctx->subkey = NULL;
307
0
    code = k5_make_tgs_req(context, ctx->fast_state, ctx->cur_tgt, ctx->kdcopt,
308
0
                           ctx->cur_tgt->addresses, NULL, ctx->tgs_in_creds,
309
0
                           NULL, NULL, &request, &ctx->timestamp, &ctx->nonce,
310
0
                           &ctx->subkey);
311
0
    if (code != 0)
312
0
        return code;
313
314
0
    krb5_free_data_contents(context, &ctx->previous_request);
315
0
    ctx->previous_request = request;
316
0
    return set_caller_request(context, ctx);
317
0
}
318
319
/* Set up a request for a TGT for realm, using ctx->cur_tgt. */
320
static krb5_error_code
321
make_request_for_tgt(krb5_context context, krb5_tkt_creds_context ctx,
322
                     const krb5_data *realm)
323
0
{
324
0
    krb5_error_code code;
325
326
    /* Construct the principal krbtgt/<realm>@<cur-tgt-realm>. */
327
0
    krb5_free_principal(context, ctx->tgt_princ);
328
0
    ctx->tgt_princ = NULL;
329
0
    code = krb5int_tgtname(context, realm, &ctx->cur_tgt->server->data[1],
330
0
                           &ctx->tgt_princ);
331
0
    if (code != 0)
332
0
        return code;
333
334
0
    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
0
    memset(&ctx->tgt_in_creds, 0, sizeof(ctx->tgt_in_creds));
338
0
    ctx->tgt_in_creds.client = ctx->client;
339
0
    ctx->tgt_in_creds.server = ctx->tgt_princ;
340
341
    /* Make a request for the above creds with no extra options. */
342
0
    ctx->tgs_in_creds = &ctx->tgt_in_creds;
343
0
    code = make_request(context, ctx, 0);
344
0
    return code;
345
0
}
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
350
make_request_for_service(krb5_context context, krb5_tkt_creds_context ctx,
351
                         krb5_boolean referral)
352
0
{
353
0
    krb5_error_code code;
354
0
    int extra_options;
355
356
0
    TRACE_TKT_CREDS_SERVICE_REQ(context, ctx->server, referral);
357
358
    /* Include the caller-specified KDC options in service requests. */
359
0
    extra_options = ctx->req_kdcopt;
360
361
    /* Automatically set the enc-tkt-in-skey flag for user-to-user requests. */
362
0
    if (ctx->in_creds->second_ticket.length != 0)
363
0
        extra_options |= KDC_OPT_ENC_TKT_IN_SKEY;
364
365
    /* Set the canonicalize flag for referral requests. */
366
0
    if (referral)
367
0
        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
0
    if (referral)
375
0
        context->use_conf_ktypes = TRUE;
376
0
    ctx->tgs_in_creds = ctx->in_creds;
377
0
    code = make_request(context, ctx, extra_options);
378
0
    if (referral)
379
0
        context->use_conf_ktypes = FALSE;
380
0
    return code;
381
0
}
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
386
get_creds_from_tgs_reply(krb5_context context, krb5_tkt_creds_context ctx,
387
                         krb5_data *reply)
388
0
{
389
0
    krb5_error_code code;
390
391
0
    krb5_free_creds(context, ctx->reply_creds);
392
0
    ctx->reply_creds = NULL;
393
0
    code = krb5int_process_tgs_reply(context, ctx->fast_state,
394
0
                                     reply, ctx->cur_tgt, ctx->kdcopt,
395
0
                                     ctx->cur_tgt->addresses, NULL,
396
0
                                     ctx->tgs_in_creds, ctx->timestamp,
397
0
                                     ctx->nonce, ctx->subkey, NULL, NULL,
398
0
                                     &ctx->reply_creds);
399
0
    if (code == KRB5KRB_ERR_RESPONSE_TOO_BIG) {
400
        /* Instruct the caller to re-send the request with TCP. */
401
0
        code = set_caller_request(context, ctx);
402
0
        if (code != 0)
403
0
            return code;
404
0
        return KRB5KRB_ERR_RESPONSE_TOO_BIG;
405
0
    }
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
0
    TRACE_TKT_CREDS_RESPONSE_CODE(context, code);
410
0
    ctx->reply_code = code;
411
0
    return 0;
412
0
}
413
414
/* Add realm to ctx->realms_seen so that we can avoid revisiting it later. */
415
static krb5_error_code
416
remember_realm(krb5_context context, krb5_tkt_creds_context ctx,
417
               const krb5_data *realm)
418
0
{
419
0
    size_t len = 0;
420
0
    krb5_data *new_list;
421
422
0
    if (ctx->realms_seen != NULL) {
423
0
        for (len = 0; ctx->realms_seen[len].data != NULL; len++);
424
0
    }
425
0
    new_list = realloc(ctx->realms_seen, (len + 2) * sizeof(krb5_data));
426
0
    if (new_list == NULL)
427
0
        return ENOMEM;
428
0
    ctx->realms_seen = new_list;
429
0
    new_list[len] = empty_data();
430
0
    new_list[len + 1] = empty_data();
431
0
    return krb5int_copy_data_contents(context, realm, &new_list[len]);
432
0
}
433
434
/* Return TRUE if realm appears to ctx->realms_seen. */
435
static krb5_boolean
436
seen_realm_before(krb5_context context, krb5_tkt_creds_context ctx,
437
                  const krb5_data *realm)
438
0
{
439
0
    size_t i;
440
441
0
    if (ctx->realms_seen != NULL) {
442
0
        for (i = 0; ctx->realms_seen[i].data != NULL; i++) {
443
0
            if (data_eq(ctx->realms_seen[i], *realm))
444
0
                return TRUE;
445
0
        }
446
0
    }
447
0
    return FALSE;
448
0
}
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
455
complete(krb5_context context, krb5_tkt_creds_context ctx)
456
0
{
457
0
    TRACE_TKT_CREDS_COMPLETE(context, ctx->reply_creds->server);
458
459
    /* Put the requested server principal in the output creds. */
460
0
    krb5_free_principal(context, ctx->reply_creds->server);
461
0
    ctx->reply_creds->server = ctx->req_server;
462
0
    ctx->req_server = NULL;
463
464
    /* Note the authdata we asked for in the output creds. */
465
0
    ctx->reply_creds->authdata = ctx->authdata;
466
0
    ctx->authdata = NULL;
467
468
0
    if (!(ctx->req_options & KRB5_GC_NO_STORE)) {
469
        /* Try to cache the credential. */
470
0
        (void) krb5_cc_store_cred(context, ctx->ccache, ctx->reply_creds);
471
0
    }
472
473
0
    ctx->state = STATE_COMPLETE;
474
0
    return 0;
475
0
}
476
477
/***** STATE_NON_REFERRAL *****/
478
479
/* Process the response to a non-referral request. */
480
static krb5_error_code
481
step_non_referral(krb5_context context, krb5_tkt_creds_context ctx)
482
0
{
483
    /* No fallbacks if we didn't get a successful reply. */
484
0
    if (ctx->reply_code)
485
0
        return ctx->reply_code;
486
487
0
    return complete(context, ctx);
488
0
}
489
490
/* Make a non-referrals request for the desired service ticket. */
491
static krb5_error_code
492
begin_non_referral(krb5_context context, krb5_tkt_creds_context ctx)
493
0
{
494
0
    ctx->state = STATE_NON_REFERRAL;
495
0
    return make_request_for_service(context, ctx, FALSE);
496
0
}
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
503
try_fallback(krb5_context context, krb5_tkt_creds_context ctx)
504
0
{
505
0
    krb5_error_code code;
506
0
    char **hrealms;
507
508
    /* Only fall back if our error was from the first referral request. */
509
0
    if (ctx->referral_count > 1)
510
0
        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
0
    if (!ctx->referral_req)
515
0
        return begin_non_referral(context, ctx);
516
517
0
    if (ctx->server->length < 2) {
518
        /* We need a type/host format principal to find a fallback realm. */
519
0
        return KRB5_ERR_HOST_REALM_UNKNOWN;
520
0
    }
521
522
    /* We expect this to give exactly one answer (XXX clean up interface). */
523
0
    code = krb5_get_fallback_host_realm(context, &ctx->server->data[1],
524
0
                                        &hrealms);
525
0
    if (code != 0)
526
0
        return code;
527
528
    /* If the fallback realm isn't any different, use the existing TGT. */
529
0
    if (data_eq_string(ctx->server->realm, hrealms[0])) {
530
0
        krb5_free_host_realm(context, hrealms);
531
0
        return begin_non_referral(context, ctx);
532
0
    }
533
534
    /* Rewrite server->realm to be the fallback realm. */
535
0
    krb5_free_data_contents(context, &ctx->server->realm);
536
0
    ctx->server->realm = string2data(hrealms[0]);
537
0
    free(hrealms);
538
0
    TRACE_TKT_CREDS_FALLBACK(context, &ctx->server->realm);
539
540
    /* Obtain a TGT for the new service realm. */
541
0
    ctx->getting_tgt_for = STATE_NON_REFERRAL;
542
0
    return begin_get_tgt(context, ctx);
543
0
}
544
545
/* Return true if context contains app-provided TGS enctypes and enctype is not
546
 * one of them. */
547
static krb5_boolean
548
wrong_enctype(krb5_context context, krb5_enctype enctype)
549
0
{
550
0
    size_t i;
551
552
0
    if (context->tgs_etypes == NULL)
553
0
        return FALSE;
554
0
    for (i = 0; context->tgs_etypes[i] != 0; i++) {
555
0
        if (enctype == context->tgs_etypes[i])
556
0
            return FALSE;
557
0
    }
558
0
    return TRUE;
559
0
}
560
561
/* Advance the referral request loop. */
562
static krb5_error_code
563
step_referrals(krb5_context context, krb5_tkt_creds_context ctx)
564
0
{
565
0
    krb5_error_code code;
566
0
    const krb5_data *referral_realm;
567
568
    /* Possibly try a non-referral fallback request on error. */
569
0
    if (ctx->reply_code != 0)
570
0
        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
0
    if (krb5_principal_compare_any_realm(context, ctx->reply_creds->server,
575
0
                                         ctx->server)) {
576
        /* We didn't necessarily ask for it with the right enctypes.  Try a
577
         * non-referral request if so. */
578
0
        if (wrong_enctype(context, ctx->reply_creds->keyblock.enctype)) {
579
0
            TRACE_TKT_CREDS_WRONG_ENCTYPE(context);
580
0
            return begin_non_referral(context, ctx);
581
0
        }
582
583
0
        return complete(context, ctx);
584
0
    }
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
0
    if (!IS_TGS_PRINC(ctx->reply_creds->server)) {
589
0
        TRACE_TKT_CREDS_NON_TGT(context, ctx->reply_creds->server);
590
0
        return begin_non_referral(context, ctx);
591
0
    }
592
593
    /* Active Directory may return a TGT to the local realm.  Try a
594
     * non-referral query if we see this. */
595
0
    referral_realm = &ctx->reply_creds->server->data[1];
596
0
    if (data_eq(*referral_realm, ctx->cur_tgt->server->data[1])) {
597
0
        TRACE_TKT_CREDS_SAME_REALM_TGT(context, referral_realm);
598
0
        return begin_non_referral(context, ctx);
599
0
    }
600
601
0
    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
0
        krb5_free_authdata(context, ctx->in_creds->authdata);
605
0
        ctx->in_creds->authdata = NULL;
606
0
    }
607
608
    /* Give up if we've gotten too many referral TGTs. */
609
0
    if (ctx->referral_count++ >= KRB5_REFERRAL_MAXHOPS)
610
0
        return KRB5_KDC_UNREACH;
611
612
    /* Check for referral loops. */
613
0
    if (seen_realm_before(context, ctx, referral_realm))
614
0
        return KRB5_KDC_UNREACH;
615
0
    code = remember_realm(context, ctx, referral_realm);
616
0
    if (code != 0)
617
0
        return code;
618
619
    /* Use the referral TGT for the next request. */
620
0
    krb5_free_creds(context, ctx->cur_tgt);
621
0
    ctx->cur_tgt = ctx->reply_creds;
622
0
    ctx->reply_creds = NULL;
623
0
    TRACE_TKT_CREDS_REFERRAL(context, ctx->cur_tgt->server);
624
625
    /* Rewrite the server realm to be the referral realm. */
626
0
    krb5_free_data_contents(context, &ctx->server->realm);
627
0
    code = krb5int_copy_data_contents(context, referral_realm,
628
0
                                      &ctx->server->realm);
629
0
    if (code != 0)
630
0
        return code;
631
632
    /* Generate the next referral request. */
633
0
    return make_request_for_service(context, ctx, TRUE);
634
0
}
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
641
begin_referrals(krb5_context context, krb5_tkt_creds_context ctx)
642
0
{
643
0
    ctx->state = STATE_REFERRALS;
644
0
    ctx->referral_count = 1;
645
646
    /* Empty out the realms-seen list for loop checking. */
647
0
    krb5int_free_data_list(context, ctx->realms_seen);
648
0
    ctx->realms_seen = NULL;
649
650
    /* Generate the first referral request. */
651
0
    return make_request_for_service(context, ctx, TRUE);
652
0
}
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
663
end_get_tgt(krb5_context context, krb5_tkt_creds_context ctx)
664
0
{
665
0
    if (ctx->getting_tgt_for == STATE_REFERRALS)
666
0
        return begin_referrals(context, ctx);
667
0
    else
668
0
        return begin_non_referral(context, ctx);
669
0
}
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
681
step_get_tgt_offpath(krb5_context context, krb5_tkt_creds_context ctx)
682
0
{
683
0
    krb5_error_code code;
684
0
    const krb5_data *tgt_realm;
685
686
    /* We have no fallback if the last request failed, so just give up. */
687
0
    if (ctx->reply_code != 0)
688
0
        return ctx->reply_code;
689
690
    /* Verify that we got a TGT. */
691
0
    if (!IS_TGS_PRINC(ctx->reply_creds->server))
692
0
        return KRB5_KDCREP_MODIFIED;
693
694
    /* Use this tgt for the next request. */
695
0
    krb5_free_creds(context, ctx->cur_tgt);
696
0
    ctx->cur_tgt = ctx->reply_creds;
697
0
    ctx->reply_creds = NULL;
698
699
    /* Check if we've seen this realm before, and remember it. */
700
0
    tgt_realm = &ctx->cur_tgt->server->data[1];
701
0
    if (seen_realm_before(context, ctx, tgt_realm))
702
0
        return KRB5_KDC_UNREACH;
703
0
    code = remember_realm(context, ctx, tgt_realm);
704
0
    if (code != 0)
705
0
        return code;
706
707
0
    if (data_eq(*tgt_realm, ctx->server->realm)) {
708
        /* We received the server realm TGT we asked for. */
709
0
        TRACE_TKT_CREDS_TARGET_TGT_OFFPATH(context, ctx->cur_tgt->server);
710
0
        return end_get_tgt(context, ctx);
711
0
    } else if (ctx->offpath_count++ >= KRB5_REFERRAL_MAXHOPS) {
712
        /* Time to give up. */
713
0
        return KRB5_KDCREP_MODIFIED;
714
0
    }
715
716
0
    return make_request_for_tgt(context, ctx, &ctx->server->realm);
717
0
}
718
719
/* Begin chasing off-path referrals, starting from ctx->cur_tgt. */
720
static krb5_error_code
721
begin_get_tgt_offpath(krb5_context context, krb5_tkt_creds_context ctx)
722
0
{
723
0
    ctx->state = STATE_GET_TGT_OFFPATH;
724
0
    ctx->offpath_count = 1;
725
0
    return make_request_for_tgt(context, ctx, &ctx->server->realm);
726
0
}
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
759
get_cached_tgt(krb5_context context, krb5_tkt_creds_context ctx,
760
               const krb5_data *realm, krb5_creds **tgt_out)
761
0
{
762
0
    krb5_creds mcreds;
763
0
    krb5_error_code code;
764
0
    krb5_principal tgtname = NULL;
765
0
    krb5_flags flags = KRB5_TC_SUPPORTED_KTYPES | KRB5_TC_MATCH_SRV_NAMEONLY |
766
0
        KRB5_TC_MATCH_TIMES;
767
0
    krb5_timestamp now;
768
769
0
    *tgt_out = NULL;
770
771
0
    code = krb5_timeofday(context, &now);
772
0
    if (code != 0)
773
0
        return code;
774
775
    /* Construct the TGT principal name (the realm part doesn't matter). */
776
0
    code = krb5int_tgtname(context, realm, realm, &tgtname);
777
0
    if (code != 0)
778
0
        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
0
    memset(&mcreds, 0, sizeof(mcreds));
783
0
    mcreds.client = ctx->client;
784
0
    mcreds.server = tgtname;
785
0
    mcreds.times.endtime = now;
786
787
    /* Fetch the TGT credential. */
788
0
    context->use_conf_ktypes = TRUE;
789
0
    code = cache_get(context, ctx->ccache, flags, &mcreds, tgt_out);
790
0
    context->use_conf_ktypes = FALSE;
791
0
    krb5_free_principal(context, tgtname);
792
0
    return (code == KRB5_CC_NOTFOUND || code != KRB5_CC_NOT_KTYPE) ? 0 : code;
793
0
}
794
795
/* Point *tgt_out at an allocated credentials structure containing the local
796
 * TGT retrieved from ctx->ccache. */
797
static krb5_error_code
798
get_cached_local_tgt(krb5_context context, krb5_tkt_creds_context ctx,
799
                     krb5_creds **tgt_out)
800
0
{
801
0
    krb5_creds mcreds;
802
0
    krb5_error_code code;
803
0
    krb5_principal tgtname = NULL;
804
0
    krb5_flags flags = KRB5_TC_SUPPORTED_KTYPES;
805
0
    krb5_timestamp now;
806
0
    krb5_creds *tgt;
807
808
0
    *tgt_out = NULL;
809
810
0
    code = krb5_timeofday(context, &now);
811
0
    if (code != 0)
812
0
        return code;
813
814
    /* Construct the principal name. */
815
0
    code = krb5int_tgtname(context, &ctx->start_realm, &ctx->start_realm,
816
0
                           &tgtname);
817
0
    if (code != 0)
818
0
        return code;
819
820
    /* Construct a matching cred for the ccache query. */
821
0
    memset(&mcreds, 0, sizeof(mcreds));
822
0
    mcreds.client = ctx->client;
823
0
    mcreds.server = tgtname;
824
825
    /* Fetch the TGT credential. */
826
0
    context->use_conf_ktypes = TRUE;
827
0
    code = cache_get(context, ctx->ccache, flags, &mcreds, &tgt);
828
0
    context->use_conf_ktypes = FALSE;
829
0
    krb5_free_principal(context, tgtname);
830
0
    if (code)
831
0
        return code;
832
833
    /* Check if the TGT is expired before bothering the KDC with it. */
834
0
    if (ts_after(now, tgt->times.endtime)) {
835
0
        krb5_free_creds(context, tgt);
836
0
        return KRB5KRB_AP_ERR_TKT_EXPIRED;
837
0
    }
838
839
0
    *tgt_out = tgt;
840
0
    return 0;
841
0
}
842
843
/* Initialize the realm path fields for getting a TGT for
844
 * ctx->server->realm. */
845
static krb5_error_code
846
init_realm_path(krb5_context context, krb5_tkt_creds_context ctx)
847
0
{
848
0
    krb5_error_code code;
849
0
    krb5_data *realm_path;
850
0
    size_t nrealms;
851
852
    /* Get the client realm path and count its length. */
853
0
    code = k5_client_realm_path(context, &ctx->start_realm,
854
0
                                &ctx->server->realm, &realm_path);
855
0
    if (code != 0)
856
0
        return code;
857
0
    for (nrealms = 0; realm_path[nrealms].data != NULL; nrealms++);
858
0
    assert(nrealms > 1);
859
860
    /* Initialize the realm path fields in ctx. */
861
0
    krb5int_free_data_list(context, ctx->realm_path);
862
0
    ctx->realm_path = realm_path;
863
0
    ctx->last_realm = realm_path + nrealms - 1;
864
0
    ctx->cur_realm = realm_path;
865
0
    ctx->next_realm = ctx->last_realm;
866
0
    return 0;
867
0
}
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 *
872
find_realm_in_path(krb5_context context, krb5_tkt_creds_context ctx,
873
                   const krb5_data *realm)
874
0
{
875
0
    const krb5_data *r;
876
877
0
    for (r = ctx->cur_realm + 1; r->data != NULL; r++) {
878
0
        if (data_eq(*r, *realm))
879
0
            return r;
880
0
    }
881
0
    return NULL;
882
0
}
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
890
get_tgt_request(krb5_context context, krb5_tkt_creds_context ctx)
891
0
{
892
0
    krb5_error_code code;
893
0
    krb5_creds *cached_tgt;
894
895
0
    while (1) {
896
        /* Check if we have a cached TGT for the target realm. */
897
0
        code = get_cached_tgt(context, ctx, ctx->next_realm, &cached_tgt);
898
0
        if (code != 0)
899
0
            return code;
900
0
        if (cached_tgt != NULL) {
901
            /* Advance the current realm and keep going. */
902
0
            TRACE_TKT_CREDS_CACHED_INTERMEDIATE_TGT(context, cached_tgt);
903
0
            krb5_free_creds(context, ctx->cur_tgt);
904
0
            ctx->cur_tgt = cached_tgt;
905
0
            if (ctx->next_realm == ctx->last_realm)
906
0
                return end_get_tgt(context, ctx);
907
0
            ctx->cur_realm = ctx->next_realm;
908
0
            ctx->next_realm = ctx->last_realm;
909
0
            continue;
910
0
        }
911
912
0
        return make_request_for_tgt(context, ctx, ctx->next_realm);
913
0
    }
914
0
}
915
916
/* Process a TGS reply and advance the path traversal to get a foreign TGT. */
917
static krb5_error_code
918
step_get_tgt(krb5_context context, krb5_tkt_creds_context ctx)
919
0
{
920
0
    krb5_error_code code;
921
0
    const krb5_data *tgt_realm, *path_realm;
922
923
0
    if (ctx->reply_code != 0) {
924
        /* The last request failed.  Try the next-closest realm to
925
         * ctx->cur_realm. */
926
0
        ctx->next_realm--;
927
0
        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
0
            return ctx->reply_code;
931
0
        }
932
0
        TRACE_TKT_CREDS_CLOSER_REALM(context, ctx->next_realm);
933
0
    } else {
934
        /* Verify that we got a TGT. */
935
0
        if (!IS_TGS_PRINC(ctx->reply_creds->server))
936
0
            return KRB5_KDCREP_MODIFIED;
937
938
        /* Use this tgt for the next request regardless of what it is. */
939
0
        krb5_free_creds(context, ctx->cur_tgt);
940
0
        ctx->cur_tgt = ctx->reply_creds;
941
0
        ctx->reply_creds = NULL;
942
943
        /* Remember that we saw this realm. */
944
0
        tgt_realm = &ctx->cur_tgt->server->data[1];
945
0
        code = remember_realm(context, ctx, tgt_realm);
946
0
        if (code != 0)
947
0
            return code;
948
949
        /* See where we wound up on the path (or off it). */
950
0
        path_realm = find_realm_in_path(context, ctx, tgt_realm);
951
0
        if (path_realm != NULL) {
952
            /* Only cache the TGT if we asked for it, to avoid duplicates. */
953
0
            if (path_realm == ctx->next_realm)
954
0
                (void)krb5_cc_store_cred(context, ctx->ccache, ctx->cur_tgt);
955
0
            if (path_realm == ctx->last_realm) {
956
                /* We received a TGT for the target realm. */
957
0
                TRACE_TKT_CREDS_TARGET_TGT(context, ctx->cur_tgt->server);
958
0
                return end_get_tgt(context, ctx);
959
0
            } else if (path_realm != NULL) {
960
                /* We still have further to go; advance the traversal. */
961
0
                TRACE_TKT_CREDS_ADVANCE(context, tgt_realm);
962
0
                ctx->cur_realm = path_realm;
963
0
                ctx->next_realm = ctx->last_realm;
964
0
            }
965
0
        } else if (data_eq(*tgt_realm, ctx->start_realm)) {
966
            /* We were referred back to the local realm, which is bad. */
967
0
            return KRB5_KDCREP_MODIFIED;
968
0
        } else {
969
            /* We went off the path; start the off-path chase. */
970
0
            TRACE_TKT_CREDS_OFFPATH(context, tgt_realm);
971
0
            return begin_get_tgt_offpath(context, ctx);
972
0
        }
973
0
    }
974
975
    /* Generate the next request in the path traversal. */
976
0
    return get_tgt_request(context, ctx);
977
0
}
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
987
begin_get_tgt(krb5_context context, krb5_tkt_creds_context ctx)
988
0
{
989
0
    krb5_error_code code;
990
0
    krb5_creds *cached_tgt;
991
0
    krb5_boolean is_local_service;
992
993
0
    ctx->state = STATE_GET_TGT;
994
995
0
    is_local_service = data_eq(ctx->start_realm, ctx->server->realm);
996
0
    if (!is_local_service) {
997
        /* See if we have a cached TGT for the server realm. */
998
0
        code = get_cached_tgt(context, ctx, &ctx->server->realm, &cached_tgt);
999
0
        if (code != 0)
1000
0
            return code;
1001
0
        if (cached_tgt != NULL) {
1002
0
            TRACE_TKT_CREDS_CACHED_SERVICE_TGT(context, cached_tgt);
1003
0
            krb5_free_creds(context, ctx->cur_tgt);
1004
0
            ctx->cur_tgt = cached_tgt;
1005
0
            return end_get_tgt(context, ctx);
1006
0
        }
1007
0
    }
1008
1009
    /* Start with the local tgt. */
1010
0
    krb5_free_creds(context, ctx->cur_tgt);
1011
0
    ctx->cur_tgt = NULL;
1012
0
    code = get_cached_local_tgt(context, ctx, &ctx->cur_tgt);
1013
0
    if (code != 0)
1014
0
        return code;
1015
0
    TRACE_TKT_CREDS_LOCAL_TGT(context, ctx->cur_tgt);
1016
1017
0
    if (is_local_service)
1018
0
        return end_get_tgt(context, ctx);
1019
1020
    /* Initialize the realm path. */
1021
0
    code = init_realm_path(context, ctx);
1022
0
    if (code != 0)
1023
0
        return code;
1024
1025
    /* Empty out the realms-seen list for loop checking. */
1026
0
    krb5int_free_data_list(context, ctx->realms_seen);
1027
0
    ctx->realms_seen = NULL;
1028
1029
    /* Generate the first request. */
1030
0
    return get_tgt_request(context, ctx);
1031
0
}
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
1042
check_cache(krb5_context context, krb5_tkt_creds_context ctx)
1043
0
{
1044
0
    krb5_error_code code;
1045
0
    krb5_creds req_in_creds;
1046
1047
    /* Check the cache for the originally requested server principal. */
1048
0
    req_in_creds = *ctx->in_creds;
1049
0
    req_in_creds.server = ctx->req_server;
1050
0
    code = k5_get_cached_cred(context, ctx->req_options, ctx->ccache,
1051
0
                              &req_in_creds, &ctx->reply_creds);
1052
0
    if (code == 0) {
1053
0
        ctx->state = STATE_COMPLETE;
1054
0
        return 0;
1055
0
    }
1056
1057
    /* Stop on unexpected cache errors. */
1058
0
    if (code != KRB5_CC_NOTFOUND && code != KRB5_CC_NOT_KTYPE)
1059
0
        return code;
1060
1061
    /* Stop if the caller only wanted cached creds. */
1062
0
    if (ctx->req_options & KRB5_GC_CACHED)
1063
0
        return code;
1064
1065
0
    return 0;
1066
0
}
1067
1068
/* Decide where to begin the acquisition process. */
1069
static krb5_error_code
1070
begin(krb5_context context, krb5_tkt_creds_context ctx)
1071
0
{
1072
0
    krb5_error_code code;
1073
1074
    /* If the server realm is unspecified, start with the TGT realm. */
1075
0
    ctx->referral_req = krb5_is_referral_realm(&ctx->server->realm);
1076
0
    if (ctx->referral_req) {
1077
0
        krb5_free_data_contents(context, &ctx->server->realm);
1078
0
        code = krb5int_copy_data_contents(context, &ctx->start_realm,
1079
0
                                          &ctx->server->realm);
1080
0
        TRACE_TKT_CREDS_REFERRAL_REALM(context, ctx->server);
1081
0
        if (code != 0)
1082
0
            return code;
1083
0
    }
1084
1085
    /* Obtain a TGT for the service realm. */
1086
0
    ctx->getting_tgt_for = STATE_REFERRALS;
1087
0
    return begin_get_tgt(context, ctx);
1088
0
}
1089
1090
/***** API functions *****/
1091
1092
krb5_error_code KRB5_CALLCONV
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
0
{
1097
0
    krb5_error_code code;
1098
0
    krb5_tkt_creds_context ctx = NULL;
1099
0
    krb5_const_principal canonprinc;
1100
1101
0
    TRACE_TKT_CREDS(context, in_creds, ccache);
1102
0
    ctx = k5alloc(sizeof(*ctx), &code);
1103
0
    if (ctx == NULL)
1104
0
        goto cleanup;
1105
1106
0
    ctx->req_options = options;
1107
0
    ctx->req_kdcopt = 0;
1108
0
    if (options & KRB5_GC_CANONICALIZE)
1109
0
        ctx->req_kdcopt |= KDC_OPT_CANONICALIZE;
1110
0
    if (options & KRB5_GC_FORWARDABLE)
1111
0
        ctx->req_kdcopt |= KDC_OPT_FORWARDABLE;
1112
0
    if (options & KRB5_GC_NO_TRANSIT_CHECK)
1113
0
        ctx->req_kdcopt |= KDC_OPT_DISABLE_TRANSITED_CHECK;
1114
1115
0
    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
0
    code = krb5_copy_creds(context, in_creds, &ctx->in_creds);
1120
0
    if (code != 0)
1121
0
        goto cleanup;
1122
0
    ctx->req_server = ctx->in_creds->server;
1123
0
    ctx->in_creds->server = NULL;
1124
1125
    /* Get the first canonicalization candidate for the requested server. */
1126
0
    ctx->iter.princ = ctx->req_server;
1127
1128
0
    code = k5_canonprinc(context, &ctx->iter, &canonprinc);
1129
0
    if (code == 0 && canonprinc == NULL)
1130
0
        code = KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN;
1131
0
    if (code != 0)
1132
0
        goto cleanup;
1133
0
    code = krb5_copy_principal(context, canonprinc, &ctx->in_creds->server);
1134
0
    if (code != 0)
1135
0
        goto cleanup;
1136
1137
0
    ctx->client = ctx->in_creds->client;
1138
0
    ctx->server = ctx->in_creds->server;
1139
0
    code = krb5_cc_dup(context, ccache, &ctx->ccache);
1140
0
    if (code != 0)
1141
0
        goto cleanup;
1142
1143
    /* Get the start realm from the cache config, defaulting to the client
1144
     * realm. */
1145
0
    code = krb5_cc_get_config(context, ccache, NULL, "start_realm",
1146
0
                              &ctx->start_realm);
1147
0
    if (code != 0) {
1148
0
        code = krb5int_copy_data_contents(context, &ctx->client->realm,
1149
0
                                          &ctx->start_realm);
1150
0
        if (code != 0)
1151
0
            goto cleanup;
1152
0
    }
1153
1154
0
    code = krb5_copy_authdata(context, in_creds->authdata, &ctx->authdata);
1155
0
    if (code != 0)
1156
0
        goto cleanup;
1157
1158
0
    *pctx = ctx;
1159
0
    ctx = NULL;
1160
1161
0
cleanup:
1162
0
    krb5_tkt_creds_free(context, ctx);
1163
0
    return code;
1164
0
}
1165
1166
krb5_error_code KRB5_CALLCONV
1167
krb5_tkt_creds_get_creds(krb5_context context, krb5_tkt_creds_context ctx,
1168
                         krb5_creds *creds)
1169
0
{
1170
0
    if (ctx->state != STATE_COMPLETE)
1171
0
        return KRB5_NO_TKT_SUPPLIED;
1172
0
    return k5_copy_creds_contents(context, ctx->reply_creds, creds);
1173
0
}
1174
1175
krb5_error_code KRB5_CALLCONV
1176
krb5_tkt_creds_get_times(krb5_context context, krb5_tkt_creds_context ctx,
1177
                         krb5_ticket_times *times)
1178
0
{
1179
0
    if (ctx->state != STATE_COMPLETE)
1180
0
        return KRB5_NO_TKT_SUPPLIED;
1181
0
    *times = ctx->reply_creds->times;
1182
0
    return 0;
1183
0
}
1184
1185
void KRB5_CALLCONV
1186
krb5_tkt_creds_free(krb5_context context, krb5_tkt_creds_context ctx)
1187
0
{
1188
0
    if (ctx == NULL)
1189
0
        return;
1190
0
    krb5int_fast_free_state(context, ctx->fast_state);
1191
0
    krb5_free_creds(context, ctx->in_creds);
1192
0
    free_canonprinc(&ctx->iter);
1193
0
    krb5_cc_close(context, ctx->ccache);
1194
0
    krb5_free_data_contents(context, &ctx->start_realm);
1195
0
    krb5_free_principal(context, ctx->req_server);
1196
0
    krb5_free_authdata(context, ctx->authdata);
1197
0
    krb5_free_creds(context, ctx->cur_tgt);
1198
0
    krb5int_free_data_list(context, ctx->realms_seen);
1199
0
    krb5_free_principal(context, ctx->tgt_princ);
1200
0
    krb5_free_keyblock(context, ctx->subkey);
1201
0
    krb5_free_data_contents(context, &ctx->previous_request);
1202
0
    krb5int_free_data_list(context, ctx->realm_path);
1203
0
    krb5_free_creds(context, ctx->reply_creds);
1204
0
    free(ctx);
1205
0
}
1206
1207
krb5_error_code KRB5_CALLCONV
1208
krb5_tkt_creds_get(krb5_context context, krb5_tkt_creds_context ctx)
1209
0
{
1210
0
    krb5_error_code code;
1211
0
    krb5_data request = empty_data(), reply = empty_data();
1212
0
    krb5_data realm = empty_data();
1213
0
    unsigned int flags = 0;
1214
0
    int no_udp = 0;
1215
1216
0
    for (;;) {
1217
        /* Get the next request and realm.  Turn on TCP if necessary. */
1218
0
        code = krb5_tkt_creds_step(context, ctx, &reply, &request, &realm,
1219
0
                                   &flags);
1220
0
        if (code == KRB5KRB_ERR_RESPONSE_TOO_BIG && !no_udp) {
1221
0
            TRACE_TKT_CREDS_RETRY_TCP(context);
1222
0
            no_udp = 1;
1223
0
        } else if (code != 0 || !(flags & KRB5_TKT_CREDS_STEP_FLAG_CONTINUE))
1224
0
            break;
1225
0
        krb5_free_data_contents(context, &reply);
1226
1227
        /* Send it to a KDC for the appropriate realm. */
1228
0
        code = k5_sendto_kdc(context, &request, &realm, FALSE, no_udp,
1229
0
                             &reply, NULL);
1230
0
        if (code != 0)
1231
0
            break;
1232
1233
0
        krb5_free_data_contents(context, &request);
1234
0
        krb5_free_data_contents(context, &realm);
1235
0
    }
1236
1237
0
    krb5_free_data_contents(context, &request);
1238
0
    krb5_free_data_contents(context, &reply);
1239
0
    krb5_free_data_contents(context, &realm);
1240
0
    return code;
1241
0
}
1242
1243
krb5_error_code KRB5_CALLCONV
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
0
{
1248
0
    krb5_error_code code;
1249
0
    krb5_boolean no_input = (in == NULL || in->length == 0);
1250
0
    krb5_const_principal canonprinc;
1251
1252
0
    *out = empty_data();
1253
0
    *realm = empty_data();
1254
0
    *flags = 0;
1255
1256
    /* We should receive an empty input on the first step only, and should not
1257
     * get called after completion. */
1258
0
    if (no_input != (ctx->state == STATE_BEGIN) ||
1259
0
        ctx->state == STATE_COMPLETE)
1260
0
        return EINVAL;
1261
1262
0
    if (ctx->state == STATE_BEGIN) {
1263
0
        code = check_cache(context, ctx);
1264
0
        if (code != 0 || ctx->state == STATE_COMPLETE)
1265
0
            return code;
1266
0
    }
1267
1268
0
    ctx->caller_out = out;
1269
0
    ctx->caller_realm = realm;
1270
0
    ctx->caller_flags = flags;
1271
1272
0
    if (!no_input) {
1273
        /* Convert the input token into a credential and store it in ctx. */
1274
0
        code = get_creds_from_tgs_reply(context, ctx, in);
1275
0
        if (code != 0)
1276
0
            return code;
1277
0
    }
1278
1279
0
    if (ctx->state == STATE_BEGIN)
1280
0
        code = begin(context, ctx);
1281
0
    else if (ctx->state == STATE_GET_TGT)
1282
0
        code = step_get_tgt(context, ctx);
1283
0
    else if (ctx->state == STATE_GET_TGT_OFFPATH)
1284
0
        code = step_get_tgt_offpath(context, ctx);
1285
0
    else if (ctx->state == STATE_REFERRALS)
1286
0
        code = step_referrals(context, ctx);
1287
0
    else if (ctx->state == STATE_NON_REFERRAL)
1288
0
        code = step_non_referral(context, ctx);
1289
0
    else
1290
0
        code = EINVAL;
1291
1292
    /* Terminate on success or most errors. */
1293
0
    if (code != KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN)
1294
0
        return code;
1295
1296
    /* Restart with the next server principal canonicalization candidate. */
1297
0
    code = k5_canonprinc(context, &ctx->iter, &canonprinc);
1298
0
    if (code)
1299
0
        return code;
1300
0
    if (canonprinc == NULL)
1301
0
        return KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN;
1302
0
    krb5_free_principal(context, ctx->in_creds->server);
1303
0
    code = krb5_copy_principal(context, canonprinc, &ctx->in_creds->server);
1304
0
    ctx->server = ctx->in_creds->server;
1305
0
    return begin(context, ctx);
1306
0
}
1307
1308
krb5_error_code KRB5_CALLCONV
1309
krb5_get_credentials(krb5_context context, krb5_flags options,
1310
                     krb5_ccache ccache, krb5_creds *in_creds,
1311
                     krb5_creds **out_creds)
1312
0
{
1313
0
    krb5_error_code code;
1314
0
    krb5_creds *ncreds = NULL;
1315
0
    krb5_tkt_creds_context ctx = NULL;
1316
1317
0
    *out_creds = NULL;
1318
1319
    /* If S4U2Proxy is requested, use the synchronous implementation in
1320
     * s4u_creds.c. */
1321
0
    if (options & KRB5_GC_CONSTRAINED_DELEGATION) {
1322
0
        return k5_get_proxy_cred_from_kdc(context, options, ccache, in_creds,
1323
0
                                          out_creds);
1324
0
    }
1325
1326
    /* Allocate a container. */
1327
0
    ncreds = k5alloc(sizeof(*ncreds), &code);
1328
0
    if (ncreds == NULL)
1329
0
        goto cleanup;
1330
1331
    /* Make and execute a krb5_tkt_creds context to get the credential. */
1332
0
    code = krb5_tkt_creds_init(context, ccache, in_creds, options, &ctx);
1333
0
    if (code != 0)
1334
0
        goto cleanup;
1335
0
    code = krb5_tkt_creds_get(context, ctx);
1336
0
    if (code != 0)
1337
0
        goto cleanup;
1338
0
    code = krb5_tkt_creds_get_creds(context, ctx, ncreds);
1339
0
    if (code != 0)
1340
0
        goto cleanup;
1341
1342
0
    *out_creds = ncreds;
1343
0
    ncreds = NULL;
1344
1345
0
cleanup:
1346
0
    krb5_free_creds(context, ncreds);
1347
0
    krb5_tkt_creds_free(context, ctx);
1348
0
    return code;
1349
0
}