1/*
2 * Copyright (c) 1997 - 2008 Kungliga Tekniska Högskolan
3 * (Royal Institute of Technology, Stockholm, Sweden).
4 * All rights reserved.
5 *
6 * Portions Copyright (c) 2009 - 2010 Apple Inc. All rights reserved.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
11 *
12 * 1. Redistributions of source code must retain the above copyright
13 *    notice, this list of conditions and the following disclaimer.
14 *
15 * 2. Redistributions in binary form must reproduce the above copyright
16 *    notice, this list of conditions and the following disclaimer in the
17 *    documentation and/or other materials provided with the distribution.
18 *
19 * 3. Neither the name of the Institute nor the names of its contributors
20 *    may be used to endorse or promote products derived from this software
21 *    without specific prior written permission.
22 *
23 * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
24 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26 * ARE DISCLAIMED.  IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
27 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
29 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
32 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
33 * SUCH DAMAGE.
34 */
35
36#include "krb5_locl.h"
37#include <assert.h>
38
39/*
40 * Take the `body' and encode it into `padata' using the credentials
41 * in `creds'.
42 */
43
44static krb5_error_code
45make_pa_tgs_req(krb5_context context,
46		krb5_auth_context ac,
47		KDC_REQ_BODY *body,
48		krb5_ccache ccache,
49		krb5_creds *creds,
50		krb5_data *tgs_req)
51{
52    krb5_error_code ret;
53    krb5_data in_data;
54    size_t buf_size;
55    size_t len = 0;
56    uint8_t *buf;
57
58    ASN1_MALLOC_ENCODE(KDC_REQ_BODY, buf, buf_size, body, &len, ret);
59    if (ret)
60	return ret;
61
62    if(buf_size != len)
63	krb5_abortx(context, "internal error in ASN.1 encoder");
64
65    in_data.length = len;
66    in_data.data   = buf;
67    ret = _krb5_mk_req_internal(context, &ac, 0, &in_data,
68				ccache, creds,
69				tgs_req,
70				KRB5_KU_TGS_REQ_AUTH_CKSUM,
71				KRB5_KU_TGS_REQ_AUTH);
72    free (buf);
73    return ret;
74}
75
76/*
77 * Set the `enc-authorization-data' in `req_body' based on `authdata'
78 */
79
80static krb5_error_code
81set_auth_data (krb5_context context,
82	       KDC_REQ_BODY *req_body,
83	       krb5_authdata *authdata,
84	       krb5_keyblock *subkey)
85{
86    if(authdata->len) {
87	size_t len = 0, buf_size;
88	unsigned char *buf;
89	krb5_crypto crypto;
90	krb5_error_code ret;
91
92	ASN1_MALLOC_ENCODE(AuthorizationData, buf, buf_size, authdata,
93			   &len, ret);
94	if (ret)
95	    return ret;
96	if (buf_size != len)
97	    krb5_abortx(context, "internal error in ASN.1 encoder");
98
99	ALLOC(req_body->enc_authorization_data, 1);
100	if (req_body->enc_authorization_data == NULL) {
101	    free (buf);
102	    krb5_set_error_message(context, ENOMEM,
103				   N_("malloc: out of memory", ""));
104	    return ENOMEM;
105	}
106	ret = krb5_crypto_init(context, subkey, 0, &crypto);
107	if (ret) {
108	    free (buf);
109	    free (req_body->enc_authorization_data);
110	    req_body->enc_authorization_data = NULL;
111	    return ret;
112	}
113	krb5_encrypt_EncryptedData(context,
114				   crypto,
115				   KRB5_KU_TGS_REQ_AUTH_DAT_SUBKEY,
116				   buf,
117				   len,
118				   0,
119				   req_body->enc_authorization_data);
120	free (buf);
121	krb5_crypto_destroy(context, crypto);
122    } else {
123	req_body->enc_authorization_data = NULL;
124    }
125    return 0;
126}
127
128/*
129 * Create a tgs-req in `t' with `addresses', `flags', `second_ticket'
130 * (if not-NULL), `in_creds', `krbtgt', and returning the generated
131 * subkey in `subkey'.
132 */
133
134krb5_error_code
135_krb5_init_tgs_req(krb5_context context,
136		   krb5_ccache ccache,
137		   struct krb5_fast_state *state,
138		   krb5_addresses *addresses,
139		   krb5_kdc_flags flags,
140		   krb5_const_principal impersonate_principal,
141		   Ticket *second_ticket,
142		   krb5_creds *in_creds,
143		   krb5_creds *krbtgt,
144		   unsigned nonce,
145		   METHOD_DATA *padata,
146		   krb5_keyblock **subkey,
147		   TGS_REQ *t)
148{
149    krb5_auth_context ac = NULL;
150    krb5_error_code ret = 0;
151    krb5_data tgs_req;
152
153    memset(t, 0, sizeof(*t));
154    krb5_data_zero(&tgs_req);
155
156    /* inherit the forwardable/proxyable flags from the krbtgt */
157    flags.b.forwardable = krbtgt->flags.b.forwardable;
158    flags.b.proxiable = krbtgt->flags.b.proxiable;
159
160    if (ccache->ops->tgt_req) {
161	KERB_TGS_REQ_OUT out;
162	KERB_TGS_REQ_IN in;
163
164	memset(&in, 0, sizeof(in));
165	memset(&out, 0, sizeof(out));
166
167	ret = ccache->ops->tgt_req(context, ccache, &in, &out);
168	if (ret)
169	    return ret;
170
171	free_KERB_TGS_REQ_OUT(&out);
172	return 0;
173    }
174
175
176    if (impersonate_principal) {
177	krb5_crypto crypto;
178	PA_S4U2Self self;
179	krb5_data data;
180	void *buf;
181	size_t size = 0, len;
182
183	self.name = impersonate_principal->name;
184	self.realm = impersonate_principal->realm;
185	self.auth = rk_UNCONST("Kerberos");
186
187	ret = _krb5_s4u2self_to_checksumdata(context, &self, &data);
188	if (ret)
189	    goto fail;
190
191	ret = krb5_crypto_init(context, &krbtgt->session, 0, &crypto);
192	if (ret) {
193	    krb5_data_free(&data);
194	    goto fail;
195	}
196
197	ret = krb5_create_checksum(context,
198				   crypto,
199				   KRB5_KU_OTHER_CKSUM,
200				   0,
201				   data.data,
202				   data.length,
203				   &self.cksum);
204	krb5_crypto_destroy(context, crypto);
205	krb5_data_free(&data);
206	if (ret)
207	    goto fail;
208
209	ASN1_MALLOC_ENCODE(PA_S4U2Self, buf, len, &self, &size, ret);
210	free_Checksum(&self.cksum);
211	if (ret)
212	    goto fail;
213	if (len != size)
214	    krb5_abortx(context, "internal asn1 error");
215
216	ret = krb5_padata_add(context, padata, KRB5_PADATA_FOR_USER, buf, len);
217	if (ret)
218	    goto fail;
219    }
220
221    t->pvno = 5;
222    t->msg_type = krb_tgs_req;
223    if (in_creds->session.keytype) {
224	ALLOC_SEQ(&t->req_body.etype, 1);
225	if(t->req_body.etype.val == NULL) {
226	    ret = ENOMEM;
227	    krb5_set_error_message(context, ret,
228				   N_("malloc: out of memory", ""));
229	    goto fail;
230	}
231	t->req_body.etype.val[0] = in_creds->session.keytype;
232    } else {
233	ret = _krb5_init_etype(context,
234			       KRB5_PDU_TGS_REQUEST,
235			       &t->req_body.etype.len,
236			       &t->req_body.etype.val,
237			       NULL);
238    }
239    if (ret)
240	goto fail;
241    t->req_body.addresses = addresses;
242    t->req_body.kdc_options = flags.b;
243    ret = copy_Realm(&in_creds->server->realm, &t->req_body.realm);
244    if (ret)
245	goto fail;
246    ALLOC(t->req_body.sname, 1);
247    if (t->req_body.sname == NULL) {
248	ret = ENOMEM;
249	krb5_set_error_message(context, ret, N_("malloc: out of memory", ""));
250	goto fail;
251    }
252
253    /* some versions of some code might require that the client be
254       present in TGS-REQs, but this is clearly against the spec */
255
256    ret = copy_PrincipalName(&in_creds->server->name, t->req_body.sname);
257    if (ret)
258	goto fail;
259
260    /* req_body.till should be NULL if there is no endtime specified,
261       but old MIT code (like DCE secd) doesn't like that */
262    ALLOC(t->req_body.till, 1);
263    if(t->req_body.till == NULL){
264	ret = ENOMEM;
265	krb5_set_error_message(context, ret, N_("malloc: out of memory", ""));
266	goto fail;
267    }
268    *t->req_body.till = in_creds->times.endtime;
269
270    t->req_body.nonce = nonce;
271    if(second_ticket){
272	ALLOC(t->req_body.additional_tickets, 1);
273	if (t->req_body.additional_tickets == NULL) {
274	    ret = ENOMEM;
275	    krb5_set_error_message(context, ret,
276				   N_("malloc: out of memory", ""));
277	    goto fail;
278	}
279	ALLOC_SEQ(t->req_body.additional_tickets, 1);
280	if (t->req_body.additional_tickets->val == NULL) {
281	    ret = ENOMEM;
282	    krb5_set_error_message(context, ret,
283				   N_("malloc: out of memory", ""));
284	    goto fail;
285	}
286	ret = copy_Ticket(second_ticket, t->req_body.additional_tickets->val);
287	if (ret)
288	    goto fail;
289    }
290
291    if (padata) {
292	if (t->padata == NULL) {
293	    ALLOC(t->padata, 1);
294	    if (t->padata == NULL) {
295		ret = krb5_enomem(context);
296		goto fail;
297	    }
298	}
299
300	ret = copy_METHOD_DATA(padata, t->padata);
301	if (ret)
302	    goto fail;
303    }
304
305    ret = krb5_auth_con_init(context, &ac);
306    if(ret)
307	goto fail;
308
309    ret = krb5_auth_con_generatelocalsubkey(context, ac, &krbtgt->session);
310    if (ret)
311	goto fail;
312
313    ret = set_auth_data(context, &t->req_body,
314			&in_creds->authdata, ac->local_subkey);
315    if (ret)
316	goto fail;
317
318    if (t->padata == NULL) {
319	ALLOC(t->padata, 1);
320	if (t->padata == NULL) {
321	    ret = krb5_enomem(context);
322	    goto fail;
323	}
324    }
325
326    ret = make_pa_tgs_req(context,
327			  ac,
328			  &t->req_body,
329			  ccache,
330			  krbtgt,
331			  &tgs_req);
332    if(ret)
333	goto fail;
334
335    if (state) {
336
337	state->armor_ac = ac;
338	ret = _krb5_fast_create_armor(context, state, NULL);
339	state->armor_ac = NULL;
340	if (ret)
341	    goto fail;
342
343	ret = _krb5_fast_wrap_req(context, state, &tgs_req, t);
344	if (ret)
345	    goto fail;
346
347	/* Its ok if there is no fast in the TGS-REP, older heimdal only support it in the AS code path */
348	state->flags &= ~KRB5_FAST_EXPECTED;
349    }
350
351    ret = krb5_padata_add(context, t->padata, KRB5_PADATA_TGS_REQ,
352			  tgs_req.data, tgs_req.length);
353    if (ret)
354	goto fail;
355
356    krb5_data_zero(&tgs_req);
357
358    ret = krb5_auth_con_getlocalsubkey(context, ac, subkey);
359    if (ret)
360	goto fail;
361
362fail:
363    if (ac)
364	krb5_auth_con_free(context, ac);
365    if (ret) {
366	t->req_body.addresses = NULL;
367	free_TGS_REQ(t);
368    }
369    krb5_data_free(&tgs_req);
370
371    return ret;
372}
373
374/* DCE compatible decrypt proc */
375krb5_error_code KRB5_CALLCONV
376_krb5_decrypt_tkt_with_subkey (krb5_context context,
377			       krb5_keyblock *key,
378			       krb5_key_usage usage,
379			       krb5_const_pointer skey,
380			       krb5_kdc_rep *dec_rep)
381{
382    struct krb5_decrypt_tkt_with_subkey_state *state;
383    krb5_error_code ret = 0;
384    krb5_data data;
385    size_t size;
386    krb5_crypto crypto;
387    krb5_keyblock extract_key;
388
389    state = (struct krb5_decrypt_tkt_with_subkey_state *)skey;
390
391    assert(usage == 0);
392
393    krb5_data_zero(&data);
394    krb5_keyblock_zero(&extract_key);
395
396    /*
397     * start out with trying with subkey if we have one
398     */
399    if (state->subkey) {
400
401	ret = _krb5_fast_tgs_strengthen_key(context, state->fast_state, state->subkey, &extract_key);
402	if (ret)
403	    return ret;
404
405	ret = krb5_crypto_init(context, &extract_key, 0, &crypto);
406	krb5_free_keyblock_contents(context, &extract_key);
407	if (ret)
408	    return ret;
409
410	ret = krb5_decrypt_EncryptedData (context,
411					  crypto,
412					  KRB5_KU_TGS_REP_ENC_PART_SUB_KEY,
413					  &dec_rep->kdc_rep.enc_part,
414					  &data);
415	/*
416	 * If the is Windows 2000 DC, we need to retry with key usage
417	 * 8 when doing ARCFOUR.
418	 */
419	if (ret && state->subkey->keytype == ETYPE_ARCFOUR_HMAC_MD5) {
420	    ret = krb5_decrypt_EncryptedData(context,
421					     crypto,
422					     8,
423					     &dec_rep->kdc_rep.enc_part,
424					     &data);
425	}
426	krb5_crypto_destroy(context, crypto);
427    }
428    if (state->subkey == NULL || ret) {
429
430	ret = _krb5_fast_tgs_strengthen_key(context, state->fast_state, key, &extract_key);
431	if (ret)
432	    return ret;
433
434	ret = krb5_crypto_init(context, &extract_key, 0, &crypto);
435	krb5_free_keyblock_contents(context, &extract_key);
436	if (ret)
437	    return ret;
438	ret = krb5_decrypt_EncryptedData (context,
439					  crypto,
440					  KRB5_KU_TGS_REP_ENC_PART_SESSION,
441					  &dec_rep->kdc_rep.enc_part,
442					  &data);
443	krb5_crypto_destroy(context, crypto);
444    }
445    if (ret)
446	return ret;
447
448    ret = decode_EncASRepPart(data.data,
449			      data.length,
450			      &dec_rep->enc_part,
451			      &size);
452    if (ret)
453	ret = decode_EncTGSRepPart(data.data,
454				   data.length,
455				   &dec_rep->enc_part,
456				   &size);
457    if (ret)
458      krb5_set_error_message(context, ret,
459			     N_("Failed to decode encpart in ticket", ""));
460    krb5_data_free (&data);
461    return ret;
462}
463
464static int
465not_found(krb5_context context, krb5_const_principal p, krb5_error_code code)
466{
467    krb5_error_code ret;
468    char *str;
469
470    ret = krb5_unparse_name(context, p, &str);
471    if(ret) {
472	krb5_clear_error_message(context);
473	return code;
474    }
475    krb5_set_error_message(context, code,
476			   N_("Matching credential (%s) not found", ""), str);
477    free(str);
478    return code;
479}
480
481static krb5_error_code
482find_cred(krb5_context context,
483	  krb5_ccache id,
484	  krb5_principal server,
485	  krb5_creds **tgts,
486	  krb5_creds *out_creds)
487{
488    krb5_error_code ret;
489    krb5_creds mcreds;
490
491    krb5_cc_clear_mcred(&mcreds);
492    mcreds.server = server;
493    ret = krb5_cc_retrieve_cred(context, id, KRB5_TC_DONT_MATCH_REALM,
494				&mcreds, out_creds);
495    if(ret == 0)
496	return 0;
497    while(tgts && *tgts){
498	if(krb5_compare_creds(context, KRB5_TC_DONT_MATCH_REALM,
499			      &mcreds, *tgts)){
500	    ret = krb5_copy_creds_contents(context, *tgts, out_creds);
501	    return ret;
502	}
503	tgts++;
504    }
505    return not_found(context, server, KRB5_CC_NOTFOUND);
506}
507
508static krb5_error_code
509add_cred(krb5_context context, krb5_creds const *tkt, krb5_creds ***tgts)
510{
511    int i;
512    krb5_error_code ret;
513    krb5_creds **tmp = *tgts;
514
515    for(i = 0; tmp && tmp[i]; i++); /* XXX */
516    tmp = realloc(tmp, (i+2)*sizeof(*tmp));
517    if(tmp == NULL) {
518	krb5_set_error_message(context, ENOMEM,
519			       N_("malloc: out of memory", ""));
520	return ENOMEM;
521    }
522    *tgts = tmp;
523    ret = krb5_copy_creds(context, tkt, &tmp[i]);
524    tmp[i+1] = NULL;
525    return ret;
526}
527
528/*
529 * Store all credentials that seems not to be _the_ krbtgt
530 * credential.
531 */
532
533static void
534store_tgts(krb5_context context, krb5_ccache ccache, krb5_creds **tgts)
535{
536    size_t n;
537
538    for (n = 0; tgts && tgts[n]; n++) {
539	krb5_const_principal server = tgts[n]->server;
540
541	if (krb5_principal_is_krbtgt(context, server) && strcmp(server->name.name_string.val[1], server->realm) != 0)
542	    krb5_cc_store_cred(context, ccache, tgts[n]);
543    }
544    for (n = 0; tgts && tgts[n]; n++)
545	krb5_free_creds(context, tgts[n]);
546}
547
548/*
549 *
550 */
551
552typedef krb5_error_code
553(*tkt_step_state)(krb5_context, krb5_tkt_creds_context,
554		  krb5_data *, krb5_data *, krb5_realm *, unsigned int *);
555
556
557struct krb5_tkt_creds_context_data {
558    struct heim_base_uniq base;
559    krb5_context context;
560    tkt_step_state state;
561    unsigned int options;
562    char *server_name;
563    krb5_error_code error;
564
565    /* input data */
566    krb5_kdc_flags req_kdc_flags;
567    krb5_principal impersonate_principal; /* s4u2(self) */
568    krb5_addresses *addreseses;
569    krb5_ccache ccache;
570    krb5_creds *in_cred;
571
572    /* current tgs state, reset/free with tkt_reset() */
573    krb5_kdc_flags kdc_flags;
574    int32_t nonce;
575    krb5_keyblock *subkey;
576    krb5_creds tgt;
577    krb5_creds next; /* next name we are going to fetch */
578    krb5_creds **tickets;
579    int ok_as_delegate;
580
581    struct krb5_fast_state fast_state;
582
583    /* output */
584    krb5_creds *cred;
585};
586
587#define TKT_STEP(funcname)					\
588static krb5_error_code tkt_##funcname(krb5_context,		\
589		   krb5_tkt_creds_context,			\
590		   krb5_data *, krb5_data *, krb5_realm *,	\
591		   unsigned int *)
592
593TKT_STEP(init);
594TKT_STEP(referral_init);
595TKT_STEP(referral_recv);
596TKT_STEP(referral_send);
597TKT_STEP(direct_init);
598TKT_STEP(capath_init);
599TKT_STEP(store);
600#undef TKT_STEP
601
602
603/*
604 * Setup state to transmit the first request and send the request
605 */
606
607static krb5_error_code
608tkt_init(krb5_context context,
609	 krb5_tkt_creds_context ctx,
610	 krb5_data *in,
611	 krb5_data *out,
612	 krb5_realm *realm,
613	 unsigned int *flags)
614{
615    _krb5_debugx(context, 10, "tkt_init: %s", ctx->server_name);
616    ctx->state = tkt_referral_init;
617    return 0;
618}
619
620static void
621tkt_reset(krb5_context context, krb5_tkt_creds_context ctx)
622{
623    _krb5_fast_free(context, &ctx->fast_state);
624
625    krb5_free_cred_contents(context, &ctx->tgt);
626    krb5_free_cred_contents(context, &ctx->next);
627    krb5_free_keyblock(context, ctx->subkey);
628    ctx->subkey = NULL;
629}
630
631/*
632 * Get
633 */
634
635static krb5_error_code
636tkt_referral_init(krb5_context context,
637		  krb5_tkt_creds_context ctx,
638		  krb5_data *in,
639		  krb5_data *out,
640		  krb5_realm *realm,
641		  unsigned int *flags)
642{
643    krb5_error_code ret;
644    krb5_creds ticket;
645    krb5_const_realm client_realm;
646    krb5_principal tgtname;
647
648    _krb5_debugx(context, 10, "tkt_step_referrals: %s", ctx->server_name);
649
650    memset(&ticket, 0, sizeof(ticket));
651    memset(&ctx->kdc_flags, 0, sizeof(ctx->kdc_flags));
652    memset(&ctx->next, 0, sizeof(ctx->next));
653
654    ctx->kdc_flags.b.canonicalize = 1;
655
656    client_realm = krb5_principal_get_realm(context, ctx->in_cred->client);
657
658    /* find tgt for the clients base realm */
659    ret = krb5_make_principal(context, &tgtname,
660			      client_realm,
661			      KRB5_TGS_NAME,
662			      client_realm,
663			      NULL);
664    if(ret)
665	goto out;
666
667    ret = find_cred(context, ctx->ccache, tgtname, NULL, &ctx->tgt);
668    krb5_free_principal(context, tgtname);
669    if (ret)
670	goto out;
671
672
673    ret = krb5_copy_principal(context, ctx->in_cred->client, &ctx->next.client);
674    if (ret)
675	goto out;
676
677    ret = krb5_copy_principal(context, ctx->in_cred->server, &ctx->next.server);
678    if (ret)
679	goto out;
680
681    ret = krb5_principal_set_realm(context, ctx->next.server, ctx->tgt.server->realm);
682    if (ret)
683	goto out;
684
685
686 out:
687    if (ret) {
688	ctx->error = ret;
689	ctx->state = tkt_direct_init;
690    } else {
691	ctx->error = 0;
692	ctx->state = tkt_referral_send;
693    }
694
695    return 0;
696}
697
698static krb5_error_code
699tkt_referral_send(krb5_context context,
700		  krb5_tkt_creds_context ctx,
701		  krb5_data *in,
702		  krb5_data *out,
703		  krb5_realm *realm,
704		  unsigned int *flags)
705{
706    krb5_error_code ret;
707    TGS_REQ req;
708    size_t len = 0;
709    METHOD_DATA padata;
710
711    memset(&req, 0, sizeof(req));
712
713    padata.val = NULL;
714    padata.len = 0;
715
716    krb5_generate_random_block(&ctx->nonce, sizeof(ctx->nonce));
717    ctx->nonce &= 0xffffffff;
718
719    if (_krb5_have_debug(context, 10)) {
720	char *sname, *tgtname;
721	krb5_unparse_name(context, ctx->tgt.server, &tgtname);
722	krb5_unparse_name(context, ctx->next.server, &sname);
723	_krb5_debugx(context, 10, "sending TGS-REQ for %s using %s", sname, tgtname);
724    }
725
726    ret = _krb5_init_tgs_req(context,
727			     ctx->ccache,
728			     &ctx->fast_state,
729			     ctx->addreseses,
730			     ctx->kdc_flags,
731			     ctx->impersonate_principal,
732			     NULL,
733			     &ctx->next,
734			     &ctx->tgt,
735			     ctx->nonce,
736			     &padata,
737			     &ctx->subkey,
738			     &req);
739    if (ret)
740	goto out;
741
742    ASN1_MALLOC_ENCODE(TGS_REQ, out->data, out->length, &req, &len, ret);
743    if (ret)
744	goto out;
745    if(out->length != len)
746	krb5_abortx(context, "internal error in ASN.1 encoder");
747
748    /* don't free addresses */
749    req.req_body.addresses = NULL;
750    free_TGS_REQ(&req);
751
752    *realm = ctx->tgt.server->name.name_string.val[1];
753
754    *flags |= KRB5_TKT_STATE_CONTINUE;
755
756    ctx->error = 0;
757    ctx->state = tkt_referral_recv;
758
759    return 0;
760
761out:
762    ctx->error = ret;
763    ctx->state = NULL;
764    return ret;
765}
766
767krb5_error_code
768_krb5_fast_tgs_strengthen_key(krb5_context context,
769			      struct krb5_fast_state *state,
770			      krb5_keyblock *reply_key,
771			      krb5_keyblock *extract_key)
772{
773    krb5_error_code ret;
774
775    if (state && state->strengthen_key) {
776
777	_krb5_debugx(context, 5, "_krb5_fast_tgs_strengthen_key");
778
779	if (state->strengthen_key->keytype != reply_key->keytype) {
780	    krb5_set_error_message(context, KRB5KRB_AP_ERR_MODIFIED,
781				   N_("strengthen_key %d not same enctype as reply key %d", ""),
782				   state->strengthen_key->keytype, reply_key->keytype);
783	    return KRB5KRB_AP_ERR_MODIFIED;
784	}
785
786	_krb5_debug_keyblock(context, 10, "tkt: strengthen_key", state->strengthen_key);
787	_krb5_debug_keyblock(context, 10, "tkt: old reply_key", reply_key);
788
789	ret = _krb5_fast_cf2(context,
790			     state->strengthen_key,
791			     "strengthenkey",
792			     reply_key,
793			     "replykey",
794			     extract_key,
795			     NULL);
796	if (ret)
797	    return ret;
798    } else {
799	ret = krb5_copy_keyblock_contents(context, reply_key, extract_key);
800	if (ret)
801	    return ret;
802    }
803
804    _krb5_debug_keyblock(context, 10, "tkt: extract key", extract_key);
805
806    return 0;
807}
808
809static krb5_error_code
810parse_tgs_rep(krb5_context context,
811	      krb5_tkt_creds_context ctx,
812	      krb5_data *in,
813	      krb5_creds *outcred)
814{
815    krb5_error_code ret;
816    krb5_kdc_rep rep;
817    size_t len;
818
819    memset(&rep, 0, sizeof(rep));
820    memset(outcred, 0, sizeof(*outcred));
821
822    if (ctx->ccache->ops->tgt_rep) {
823	return EINVAL;
824    }
825
826    if(decode_TGS_REP(in->data, in->length, &rep.kdc_rep, &len) == 0) {
827	struct krb5_decrypt_tkt_with_subkey_state state;
828	unsigned eflags = 0;
829
830	ret = _krb5_fast_unwrap_kdc_rep(context, ctx->nonce, NULL,
831					&ctx->fast_state, &rep.kdc_rep);
832	if (ret)
833	    return ret;
834
835	ret = krb5_copy_principal(context,
836				  ctx->next.client,
837				  &outcred->client);
838	if(ret)
839	    return ret;
840	ret = krb5_copy_principal(context,
841				  ctx->next.server,
842				  &outcred->server);
843	if(ret)
844	    return ret;
845	/* this should go someplace else */
846	outcred->times.endtime = ctx->in_cred->times.endtime;
847
848#define constrained_delegation request_anonymous
849	if (ctx->kdc_flags.b.constrained_delegation || ctx->impersonate_principal)
850	    eflags |= EXTRACT_TICKET_ALLOW_CNAME_MISMATCH;
851#undef constrained_delegation
852
853	state.subkey = ctx->subkey;
854	state.fast_state = &ctx->fast_state;
855
856	ret = _krb5_extract_ticket(context,
857				   &rep,
858				   outcred,
859				   &ctx->tgt.session,
860				   0,
861				   &ctx->tgt.addresses,
862				   ctx->nonce,
863				   eflags,
864				   NULL,
865				   _krb5_decrypt_tkt_with_subkey,
866				   &state);
867
868    } else if(krb5_rd_error(context, in, &rep.error) == 0) {
869	METHOD_DATA md;
870
871	memset(&md, 0, sizeof(md));
872
873	if (rep.error.e_data) {
874	    ret = decode_METHOD_DATA(rep.error.e_data->data,
875				     rep.error.e_data->length,
876				     &md, NULL);
877	    if (ret) {
878		krb5_set_error_message(context, ret,
879				       N_("Failed to decode METHOD-DATA", ""));
880		goto out;
881	    }
882	}
883
884	ret = _krb5_fast_unwrap_error(context, &ctx->fast_state,
885				      &md, &rep.error);
886	free_METHOD_DATA(&md);
887	if (ret)
888	    goto out;
889
890	ret = krb5_error_from_rd_error(context, &rep.error, ctx->in_cred);
891
892	/* log the failure */
893	if (_krb5_have_debug(context, 5)) {
894	    const char *str = krb5_get_error_message(context, ret);
895	    _krb5_debugx(context, 5, "parse_tgs_rep: KRB-ERROR %d/%s", ret, str);
896	    krb5_free_error_message(context, str);
897	}
898
899    } else {
900	ret = KRB5KRB_AP_ERR_MSG_TYPE;
901	krb5_clear_error_message(context);
902    }
903 out:
904    krb5_free_kdc_rep(context, &rep);
905    return ret;
906}
907
908
909static krb5_error_code
910tkt_referral_recv(krb5_context context,
911		  krb5_tkt_creds_context ctx,
912		  krb5_data *in,
913		  krb5_data *out,
914		  krb5_realm *realm,
915		  unsigned int *flags)
916{
917    krb5_error_code ret;
918    krb5_creds outcred, mcred;
919    unsigned long n;
920
921    _krb5_debugx(context, 10, "tkt_referral_recv: %s", ctx->server_name);
922
923    memset(&outcred, 0, sizeof(outcred));
924
925    ret = parse_tgs_rep(context, ctx, in, &outcred);
926    if (ret) {
927	_krb5_debugx(context, 10, "tkt_referral_recv: parse_tgs_rep %d", ret);
928	tkt_reset(context, ctx);
929	ctx->state = tkt_capath_init;
930	return 0;
931    }
932
933    /*
934     * Check if we found the right ticket
935     */
936
937    if (krb5_principal_compare_any_realm(context, ctx->next.server, outcred.server)) {
938	ret = krb5_copy_creds(context, &outcred, &ctx->cred);
939	if (ret)
940	    return (ctx->error = ret);
941	krb5_free_cred_contents(context, &outcred);
942	ctx->state = tkt_store;
943	return 0;
944    }
945
946    if (!krb5_principal_is_krbtgt(context, outcred.server)) {
947	krb5_set_error_message(context, KRB5KRB_AP_ERR_NOT_US,
948			       N_("Got back an non krbtgt "
949				      "ticket referrals", ""));
950	krb5_free_cred_contents(context, &outcred);
951	ctx->state = tkt_capath_init;
952	return 0;
953    }
954
955    _krb5_debugx(context, 10, "KDC for realm %s sends a referrals to %s",
956		ctx->tgt.server->realm, outcred.server->name.name_string.val[1]);
957
958    /*
959     * check if there is a loop
960     */
961    krb5_cc_clear_mcred(&mcred);
962    mcred.server = outcred.server;
963
964    for (n = 0; ctx->tickets && ctx->tickets[n]; n++) {
965	if(krb5_compare_creds(context,
966			      KRB5_TC_DONT_MATCH_REALM,
967			      &mcred,
968			      ctx->tickets[n]))
969	{
970	    _krb5_debugx(context, 5, "Referral from %s loops back to realm %s",
971				    ctx->tgt.server->realm,
972				    outcred.server->realm);
973	    ctx->state = tkt_capath_init;
974	    return 0;
975	}
976    }
977#define MAX_KDC_REFERRALS_LOOPS 15
978    if (n > MAX_KDC_REFERRALS_LOOPS) {
979	ctx->state = tkt_capath_init;
980	return 0;
981    }
982
983    /*
984     * filter out ok-as-delegate if needed
985     */
986
987    if (ctx->ok_as_delegate == 0 || outcred.flags.b.ok_as_delegate == 0) {
988	ctx->ok_as_delegate = 0;
989	outcred.flags.b.ok_as_delegate = 0;
990    }
991
992    /* add to iteration cache */
993    ret = add_cred(context, &outcred, &ctx->tickets);
994    if (ret) {
995	ctx->state = tkt_capath_init;
996	return 0;
997    }
998
999    /* set up next server to talk to */
1000    krb5_free_cred_contents(context, &ctx->tgt);
1001    ctx->tgt = outcred;
1002
1003    /*
1004     * Setup next target principal to target
1005     */
1006
1007    ret = krb5_principal_set_realm(context, ctx->next.server,
1008				   ctx->tgt.server->realm);
1009    if (ret) {
1010	ctx->state = tkt_capath_init;
1011	return 0;
1012    }
1013
1014    ctx->state = tkt_referral_send;
1015
1016    return 0;
1017}
1018
1019static krb5_error_code
1020tkt_direct_init(krb5_context context,
1021		krb5_tkt_creds_context ctx,
1022		krb5_data *in,
1023		krb5_data *out,
1024		krb5_realm *realm,
1025		unsigned int *flags)
1026{
1027    _krb5_debugx(context, 10, "tkt_direct_init: %s", ctx->server_name);
1028
1029    tkt_reset(context, ctx);
1030
1031    ctx->error = EINVAL;
1032    ctx->state = tkt_capath_init;
1033
1034    return 0;
1035}
1036
1037static krb5_error_code
1038tkt_capath_init(krb5_context context,
1039		krb5_tkt_creds_context ctx,
1040		krb5_data *in,
1041		krb5_data *out,
1042		krb5_realm *realm,
1043		unsigned int *flags)
1044{
1045    _krb5_debugx(context, 10, "tkt_step_capath: %s", ctx->server_name);
1046
1047    tkt_reset(context, ctx);
1048
1049    ctx->error = EINVAL;
1050    ctx->state = NULL;
1051
1052    return 0;
1053}
1054
1055
1056static krb5_error_code
1057tkt_store(krb5_context context,
1058	  krb5_tkt_creds_context ctx,
1059	  krb5_data *in,
1060	  krb5_data *out,
1061	  krb5_realm *realm,
1062	  unsigned int *flags)
1063{
1064    krb5_boolean bret;
1065
1066    _krb5_debugx(context, 10, "tkt_step_store: %s", ctx->server_name);
1067
1068    ctx->error = 0;
1069    ctx->state = NULL;
1070
1071    if (ctx->options & KRB5_GC_NO_STORE)
1072	return 0;
1073
1074    if (ctx->tickets) {
1075	store_tgts(context, ctx->ccache, ctx->tickets);
1076	free(ctx->tickets);
1077	ctx->tickets = NULL;
1078    }
1079
1080    heim_assert(ctx->cred != NULL, "store but no credential");
1081
1082    krb5_cc_store_cred(context, ctx->ccache, ctx->cred);
1083    /*
1084     * Store an referrals entry since the server changed from that
1085     * expected and if we want to find it again next time, it
1086     * better have the right name.
1087     *
1088     * We only need to compare any realm since the referrals
1089     * matching code will do the same for us.
1090     */
1091    bret = krb5_principal_compare_any_realm(context,
1092					    ctx->cred->server,
1093					    ctx->in_cred->server);
1094    if (!bret) {
1095	krb5_creds ref = *ctx->cred;
1096	krb5_principal_data refp = *ctx->in_cred->server;
1097	refp.realm = "";
1098	ref.server = &refp;
1099	krb5_cc_store_cred(context, ctx->ccache, &ref);
1100    }
1101
1102    return 0;
1103}
1104
1105
1106static void
1107tkt_release(void *ptr)
1108{
1109    krb5_tkt_creds_context ctx = ptr;
1110    krb5_free_creds(ctx->context, ctx->cred);
1111    tkt_reset(ctx->context, ctx);
1112    if (ctx->tickets) {
1113	size_t n;
1114	for (n = 0; ctx->tickets[n]; n++)
1115	    krb5_free_creds(ctx->context, ctx->tickets[n]);
1116	free(ctx->tickets);
1117    }
1118    free(ctx->server_name);
1119}
1120
1121/**
1122 * Create a context for a credential fetching process
1123 */
1124
1125KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
1126krb5_tkt_creds_init(krb5_context context,
1127		    krb5_ccache ccache,
1128                    krb5_creds *in_cred,
1129		    krb5_flags options,
1130                    krb5_tkt_creds_context *pctx)
1131{
1132    krb5_tkt_creds_context ctx;
1133    krb5_error_code ret;
1134
1135    *pctx = NULL;
1136
1137    ctx = heim_uniq_alloc(sizeof(*ctx), "tkt-ctx", tkt_release);
1138    if (ctx == NULL)
1139	return ENOMEM;
1140
1141    ctx->context = context;
1142    ctx->state = tkt_init;
1143    ctx->options = options;
1144    ctx->ccache = ccache;
1145
1146    if (ctx->options & KRB5_GC_FORWARDABLE)
1147	ctx->req_kdc_flags.b.forwardable = 1;
1148    if (ctx->options & KRB5_GC_USER_USER) {
1149	ctx->req_kdc_flags.b.enc_tkt_in_skey = 1;
1150	ctx->options |= KRB5_GC_NO_STORE;
1151    }
1152    if (options & KRB5_GC_CANONICALIZE)
1153	ctx->req_kdc_flags.b.canonicalize = 1;
1154
1155    ret = krb5_copy_creds(context, in_cred, &ctx->in_cred);
1156    if (ret) {
1157	heim_release(ctx);
1158	return ret;
1159    }
1160
1161    ret = krb5_unparse_name(context, ctx->in_cred->server, &ctx->server_name);
1162    if (ret) {
1163	heim_release(ctx);
1164	return ret;
1165    }
1166
1167    *pctx = ctx;
1168
1169    return 0;
1170}
1171
1172/**
1173 * Step though next step in the TGS-REP process
1174 *
1175 * Pointer returned in realm is valid to next call to
1176 * krb5_tkt_creds_step() or krb5_tkt_creds_free().
1177 */
1178
1179KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
1180krb5_tkt_creds_step(krb5_context context,
1181		    krb5_tkt_creds_context ctx,
1182                    krb5_data *in,
1183		    krb5_data *out,
1184		    krb5_realm *realm,
1185                    unsigned int *flags)
1186{
1187    krb5_error_code ret;
1188
1189    krb5_data_zero(out);
1190    *flags = 0;
1191    *realm = NULL;
1192
1193    ret = ctx->error = 0;
1194
1195    while (out->length == 0 && ctx->state != NULL) {
1196	ret = ctx->state(context, ctx, in, out, realm, flags);
1197	if (ret) {
1198	    heim_assert(ctx->error == ret, "error not same as saved");
1199	    break;
1200	}
1201
1202	if ((*flags) & KRB5_TKT_STATE_CONTINUE) {
1203	    heim_assert(out->length != 0, "no data to send to KDC");
1204	    heim_assert(*realm != NULL, "no realm to send data too");
1205	    break;
1206	} else {
1207	    heim_assert(out->length == 0, "out state but not state continue");
1208	}
1209    }
1210
1211    return ret;
1212}
1213
1214/**
1215 *
1216 */
1217
1218KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
1219krb5_tkt_creds_get_creds(krb5_context context,
1220			 krb5_tkt_creds_context ctx,
1221                         krb5_creds **cred)
1222{
1223    if (ctx->state != NULL)
1224	return EINVAL;
1225
1226    if (ctx->cred)
1227	return krb5_copy_creds(context, ctx->cred, cred);
1228
1229    return ctx->error;
1230}
1231
1232/**
1233 *
1234 */
1235
1236KRB5_LIB_FUNCTION void KRB5_LIB_CALL
1237krb5_tkt_creds_free(krb5_context context,
1238		    krb5_tkt_creds_context ctx)
1239{
1240    heim_release(ctx);
1241}
1242