1/*	$NetBSD: get_cred.c,v 1.4 2023/06/19 21:41:44 christos Exp $	*/
2
3/*
4 * Copyright (c) 1997 - 2008 Kungliga Tekniska H��gskolan
5 * (Royal Institute of Technology, Stockholm, Sweden).
6 * All rights reserved.
7 *
8 * Portions Copyright (c) 2009 Apple Inc. All rights reserved.
9 *
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
12 * are met:
13 *
14 * 1. Redistributions of source code must retain the above copyright
15 *    notice, this list of conditions and the following disclaimer.
16 *
17 * 2. Redistributions in binary form must reproduce the above copyright
18 *    notice, this list of conditions and the following disclaimer in the
19 *    documentation and/or other materials provided with the distribution.
20 *
21 * 3. Neither the name of the Institute nor the names of its contributors
22 *    may be used to endorse or promote products derived from this software
23 *    without specific prior written permission.
24 *
25 * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
26 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
27 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
28 * ARE DISCLAIMED.  IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
29 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
30 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
31 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
32 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
33 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
34 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
35 * SUCH DAMAGE.
36 */
37
38#include "krb5_locl.h"
39#include <assert.h>
40
41static krb5_error_code
42get_cred_kdc_capath(krb5_context, krb5_kdc_flags,
43		    krb5_ccache, krb5_creds *, krb5_principal,
44		    Ticket *, krb5_creds **, krb5_creds ***);
45
46/*
47 * Take the `body' and encode it into `padata' using the credentials
48 * in `creds'.
49 */
50
51static krb5_error_code
52make_pa_tgs_req(krb5_context context,
53		krb5_auth_context ac,
54		KDC_REQ_BODY *body,
55		PA_DATA *padata,
56		krb5_creds *creds)
57{
58    u_char *buf;
59    size_t buf_size;
60    size_t len = 0;
61    krb5_data in_data;
62    krb5_error_code ret;
63
64    ASN1_MALLOC_ENCODE(KDC_REQ_BODY, buf, buf_size, body, &len, ret);
65    if (ret)
66	goto out;
67    if(buf_size != len)
68	krb5_abortx(context, "internal error in ASN.1 encoder");
69
70    in_data.length = len;
71    in_data.data   = buf;
72    ret = _krb5_mk_req_internal(context, &ac, 0, &in_data, creds,
73				&padata->padata_value,
74				KRB5_KU_TGS_REQ_AUTH_CKSUM,
75				KRB5_KU_TGS_REQ_AUTH);
76 out:
77    free (buf);
78    if(ret)
79	return ret;
80    padata->padata_type = KRB5_PADATA_TGS_REQ;
81    return 0;
82}
83
84/*
85 * Set the `enc-authorization-data' in `req_body' based on `authdata'
86 */
87
88static krb5_error_code
89set_auth_data (krb5_context context,
90	       KDC_REQ_BODY *req_body,
91	       krb5_authdata *authdata,
92	       krb5_keyblock *subkey)
93{
94    if(authdata->len) {
95	size_t len = 0, buf_size;
96	unsigned char *buf;
97	krb5_crypto crypto;
98	krb5_error_code ret;
99
100	ASN1_MALLOC_ENCODE(AuthorizationData, buf, buf_size, authdata,
101			   &len, ret);
102	if (ret)
103	    return ret;
104	if (buf_size != len)
105	    krb5_abortx(context, "internal error in ASN.1 encoder");
106
107	ALLOC(req_body->enc_authorization_data, 1);
108	if (req_body->enc_authorization_data == NULL) {
109	    free (buf);
110	    return krb5_enomem(context);
111	}
112	ret = krb5_crypto_init(context, subkey, 0, &crypto);
113	if (ret) {
114	    free (buf);
115	    free (req_body->enc_authorization_data);
116	    req_body->enc_authorization_data = NULL;
117	    return ret;
118	}
119	krb5_encrypt_EncryptedData(context,
120				   crypto,
121				   KRB5_KU_TGS_REQ_AUTH_DAT_SUBKEY,
122				   buf,
123				   len,
124				   0,
125				   req_body->enc_authorization_data);
126	free (buf);
127	krb5_crypto_destroy(context, crypto);
128    } else {
129	req_body->enc_authorization_data = NULL;
130    }
131    return 0;
132}
133
134/*
135 * Create a tgs-req in `t' with `addresses', `flags', `second_ticket'
136 * (if not-NULL), `in_creds', `krbtgt', and returning the generated
137 * subkey in `subkey'.
138 */
139
140static krb5_error_code
141init_tgs_req (krb5_context context,
142	      krb5_ccache ccache,
143	      krb5_addresses *addresses,
144	      krb5_kdc_flags flags,
145	      Ticket *second_ticket,
146	      krb5_creds *in_creds,
147	      krb5_creds *krbtgt,
148	      unsigned nonce,
149	      const METHOD_DATA *padata,
150	      krb5_keyblock **subkey,
151	      TGS_REQ *t)
152{
153    krb5_auth_context ac = NULL;
154    krb5_error_code ret = 0;
155
156    memset(t, 0, sizeof(*t));
157    t->pvno = 5;
158    t->msg_type = krb_tgs_req;
159    if (in_creds->session.keytype) {
160	ALLOC_SEQ(&t->req_body.etype, 1);
161	if(t->req_body.etype.val == NULL) {
162	    ret = krb5_enomem(context);
163	    goto fail;
164	}
165	t->req_body.etype.val[0] = in_creds->session.keytype;
166    } else {
167	ret = _krb5_init_etype(context,
168			       KRB5_PDU_TGS_REQUEST,
169			       &t->req_body.etype.len,
170			       &t->req_body.etype.val,
171			       NULL);
172    }
173    if (ret)
174	goto fail;
175    t->req_body.addresses = addresses;
176    t->req_body.kdc_options = flags.b;
177    t->req_body.kdc_options.forwardable = krbtgt->flags.b.forwardable;
178    t->req_body.kdc_options.renewable = krbtgt->flags.b.renewable;
179    t->req_body.kdc_options.proxiable = krbtgt->flags.b.proxiable;
180    ret = copy_Realm(&in_creds->server->realm, &t->req_body.realm);
181    if (ret)
182	goto fail;
183    ALLOC(t->req_body.sname, 1);
184    if (t->req_body.sname == NULL) {
185	ret = krb5_enomem(context);
186	goto fail;
187    }
188
189    /* some versions of some code might require that the client be
190       present in TGS-REQs, but this is clearly against the spec */
191
192    ret = copy_PrincipalName(&in_creds->server->name, t->req_body.sname);
193    if (ret)
194	goto fail;
195
196    if (krbtgt->times.starttime) {
197        ALLOC(t->req_body.from, 1);
198        if(t->req_body.from == NULL){
199            ret = krb5_enomem(context);
200            goto fail;
201        }
202        *t->req_body.from = in_creds->times.starttime;
203    }
204
205    /* req_body.till should be NULL if there is no endtime specified,
206       but old MIT code (like DCE secd) doesn't like that */
207    ALLOC(t->req_body.till, 1);
208    if(t->req_body.till == NULL){
209	ret = krb5_enomem(context);
210	goto fail;
211    }
212    *t->req_body.till = in_creds->times.endtime;
213
214    if (t->req_body.kdc_options.renewable && krbtgt->times.renew_till) {
215        ALLOC(t->req_body.rtime, 1);
216        if(t->req_body.rtime == NULL){
217            ret = krb5_enomem(context);
218            goto fail;
219        }
220        *t->req_body.rtime = in_creds->times.renew_till;
221    }
222
223    t->req_body.nonce = nonce;
224    if(second_ticket){
225	ALLOC(t->req_body.additional_tickets, 1);
226	if (t->req_body.additional_tickets == NULL) {
227	    ret = krb5_enomem(context);
228	    goto fail;
229	}
230	ALLOC_SEQ(t->req_body.additional_tickets, 1);
231	if (t->req_body.additional_tickets->val == NULL) {
232	    ret = krb5_enomem(context);
233	    goto fail;
234	}
235	ret = copy_Ticket(second_ticket, t->req_body.additional_tickets->val);
236	if (ret)
237	    goto fail;
238    }
239    ALLOC(t->padata, 1);
240    if (t->padata == NULL) {
241	ret = krb5_enomem(context);
242	goto fail;
243    }
244    ALLOC_SEQ(t->padata, 1 + padata->len);
245    if (t->padata->val == NULL) {
246	ret = krb5_enomem(context);
247	goto fail;
248    }
249    {
250	size_t i;
251	for (i = 0; i < padata->len; i++) {
252	    ret = copy_PA_DATA(&padata->val[i], &t->padata->val[i + 1]);
253	    if (ret) {
254		krb5_set_error_message(context, ret,
255				       N_("malloc: out of memory", ""));
256		goto fail;
257	    }
258	}
259    }
260
261    ret = krb5_auth_con_init(context, &ac);
262    if(ret)
263	goto fail;
264
265    ret = krb5_auth_con_generatelocalsubkey(context, ac, &krbtgt->session);
266    if (ret)
267	goto fail;
268
269    ret = set_auth_data (context, &t->req_body, &in_creds->authdata,
270			 ac->local_subkey);
271    if (ret)
272	goto fail;
273
274    ret = make_pa_tgs_req(context,
275			  ac,
276			  &t->req_body,
277			  &t->padata->val[0],
278			  krbtgt);
279    if(ret)
280	goto fail;
281
282    ret = krb5_auth_con_getlocalsubkey(context, ac, subkey);
283    if (ret)
284	goto fail;
285
286fail:
287    if (ac)
288	krb5_auth_con_free(context, ac);
289    if (ret) {
290	t->req_body.addresses = NULL;
291	free_TGS_REQ (t);
292    }
293    return ret;
294}
295
296KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
297_krb5_get_krbtgt(krb5_context context,
298		 krb5_ccache  id,
299		 krb5_realm realm,
300		 krb5_creds **cred)
301{
302    krb5_error_code ret;
303    krb5_creds tmp_cred;
304
305    memset(&tmp_cred, 0, sizeof(tmp_cred));
306
307    ret = krb5_cc_get_principal(context, id, &tmp_cred.client);
308    if (ret)
309	return ret;
310
311    ret = krb5_make_principal(context,
312			      &tmp_cred.server,
313			      realm,
314			      KRB5_TGS_NAME,
315			      realm,
316			      NULL);
317    if(ret) {
318	krb5_free_principal(context, tmp_cred.client);
319	return ret;
320    }
321    /*
322     * The forwardable TGT might not be the start TGT, in which case, it is
323     * generally, but not always already cached.  Just in case, get it again if
324     * lost.
325     */
326    ret = krb5_get_credentials(context,
327			       0,
328			       id,
329			       &tmp_cred,
330			       cred);
331    krb5_free_principal(context, tmp_cred.client);
332    krb5_free_principal(context, tmp_cred.server);
333    if(ret)
334	return ret;
335    return 0;
336}
337
338/* DCE compatible decrypt proc */
339static krb5_error_code KRB5_CALLCONV
340decrypt_tkt_with_subkey (krb5_context context,
341			 krb5_keyblock *key,
342			 krb5_key_usage usage,
343			 krb5_const_pointer skey,
344			 krb5_kdc_rep *dec_rep)
345{
346    const krb5_keyblock *subkey = skey;
347    krb5_error_code ret = 0;
348    krb5_data data;
349    size_t size;
350    krb5_crypto crypto;
351
352    assert(usage == 0);
353
354    krb5_data_zero(&data);
355
356    /*
357     * start out with trying with subkey if we have one
358     */
359    if (subkey) {
360	ret = krb5_crypto_init(context, subkey, 0, &crypto);
361	if (ret)
362	    return ret;
363	ret = krb5_decrypt_EncryptedData (context,
364					  crypto,
365					  KRB5_KU_TGS_REP_ENC_PART_SUB_KEY,
366					  &dec_rep->kdc_rep.enc_part,
367					  &data);
368	/*
369	 * If the is Windows 2000 DC, we need to retry with key usage
370	 * 8 when doing ARCFOUR.
371	 */
372	if (ret && subkey->keytype == ETYPE_ARCFOUR_HMAC_MD5) {
373	    ret = krb5_decrypt_EncryptedData(context,
374					     crypto,
375					     8,
376					     &dec_rep->kdc_rep.enc_part,
377					     &data);
378	}
379	krb5_crypto_destroy(context, crypto);
380    }
381    if (subkey == NULL || ret) {
382	ret = krb5_crypto_init(context, key, 0, &crypto);
383	if (ret)
384	    return ret;
385	ret = krb5_decrypt_EncryptedData (context,
386					  crypto,
387					  KRB5_KU_TGS_REP_ENC_PART_SESSION,
388					  &dec_rep->kdc_rep.enc_part,
389					  &data);
390	krb5_crypto_destroy(context, crypto);
391    }
392    if (ret)
393	return ret;
394
395    ret = decode_EncASRepPart(data.data,
396			      data.length,
397			      &dec_rep->enc_part,
398			      &size);
399    if (ret)
400	ret = decode_EncTGSRepPart(data.data,
401				   data.length,
402				   &dec_rep->enc_part,
403				   &size);
404    if (ret)
405      krb5_set_error_message(context, ret,
406			     N_("Failed to decode encpart in ticket", ""));
407    krb5_data_free (&data);
408    return ret;
409}
410
411static krb5_error_code
412get_cred_kdc(krb5_context context,
413	     krb5_ccache id,
414	     krb5_kdc_flags flags,
415	     krb5_addresses *addresses,
416	     krb5_creds *in_creds,
417	     krb5_creds *krbtgt,
418	     krb5_principal impersonate_principal,
419	     Ticket *second_ticket,
420	     krb5_creds *out_creds)
421{
422    TGS_REQ req;
423    krb5_data enc;
424    krb5_data resp;
425    krb5_kdc_rep rep = {0};
426    KRB_ERROR error;
427    krb5_error_code ret;
428    unsigned nonce;
429    krb5_keyblock *subkey = NULL;
430    size_t len = 0;
431    Ticket second_ticket_data;
432    METHOD_DATA padata;
433
434    krb5_data_zero(&resp);
435    krb5_data_zero(&enc);
436    padata.val = NULL;
437    padata.len = 0;
438
439    krb5_generate_random_block(&nonce, sizeof(nonce));
440    nonce &= 0xffffffff;
441
442    if(flags.b.enc_tkt_in_skey && second_ticket == NULL){
443	ret = decode_Ticket(in_creds->second_ticket.data,
444			    in_creds->second_ticket.length,
445			    &second_ticket_data, &len);
446	if(ret)
447	    return ret;
448	second_ticket = &second_ticket_data;
449    }
450
451
452    if (impersonate_principal) {
453	krb5_crypto crypto;
454	PA_S4U2Self self;
455	krb5_data data;
456	void *buf;
457	size_t size = 0;
458
459	self.name = impersonate_principal->name;
460	self.realm = impersonate_principal->realm;
461	self.auth = estrdup("Kerberos");
462
463	ret = _krb5_s4u2self_to_checksumdata(context, &self, &data);
464	if (ret) {
465	    free(self.auth);
466	    goto out;
467	}
468
469	ret = krb5_crypto_init(context, &krbtgt->session, 0, &crypto);
470	if (ret) {
471	    free(self.auth);
472	    krb5_data_free(&data);
473	    goto out;
474	}
475
476	ret = krb5_create_checksum(context,
477				   crypto,
478				   KRB5_KU_OTHER_CKSUM,
479				   0,
480				   data.data,
481				   data.length,
482				   &self.cksum);
483	krb5_crypto_destroy(context, crypto);
484	krb5_data_free(&data);
485	if (ret) {
486	    free(self.auth);
487	    goto out;
488	}
489
490	ASN1_MALLOC_ENCODE(PA_S4U2Self, buf, len, &self, &size, ret);
491	free(self.auth);
492	free_Checksum(&self.cksum);
493	if (ret)
494	    goto out;
495	if (len != size)
496	    krb5_abortx(context, "internal asn1 error");
497
498	ret = krb5_padata_add(context, &padata, KRB5_PADATA_FOR_USER, buf, len);
499	if (ret)
500	    goto out;
501    }
502
503    ret = init_tgs_req (context,
504			id,
505			addresses,
506			flags,
507			second_ticket,
508			in_creds,
509			krbtgt,
510			nonce,
511			&padata,
512			&subkey,
513			&req);
514    if (ret)
515	goto out;
516
517    ASN1_MALLOC_ENCODE(TGS_REQ, enc.data, enc.length, &req, &len, ret);
518    if (ret)
519	goto out;
520    if(enc.length != len)
521	krb5_abortx(context, "internal error in ASN.1 encoder");
522
523    /* don't free addresses */
524    req.req_body.addresses = NULL;
525    free_TGS_REQ(&req);
526
527    /*
528     * Send and receive
529     */
530    {
531	krb5_sendto_ctx stctx;
532	ret = krb5_sendto_ctx_alloc(context, &stctx);
533	if (ret)
534	    return ret;
535	krb5_sendto_ctx_set_func(stctx, _krb5_kdc_retry, NULL);
536
537	ret = krb5_sendto_context (context, stctx, &enc,
538				   krbtgt->server->name.name_string.val[1],
539				   &resp);
540	krb5_sendto_ctx_free(context, stctx);
541    }
542    if(ret)
543	goto out;
544
545    if(decode_TGS_REP(resp.data, resp.length, &rep.kdc_rep, &len) == 0) {
546	unsigned eflags = 0;
547
548	ret = krb5_copy_principal(context,
549				  in_creds->client,
550				  &out_creds->client);
551	if(ret)
552	    goto out2;
553	ret = krb5_copy_principal(context,
554				  in_creds->server,
555				  &out_creds->server);
556	if(ret)
557	    goto out2;
558	/* this should go someplace else */
559	out_creds->times.endtime = in_creds->times.endtime;
560
561	/* XXX should do better testing */
562	if (flags.b.cname_in_addl_tkt || impersonate_principal)
563	    eflags |= EXTRACT_TICKET_ALLOW_CNAME_MISMATCH;
564	if (flags.b.request_anonymous)
565	    eflags |= EXTRACT_TICKET_MATCH_ANON;
566
567	ret = _krb5_extract_ticket(context,
568				   &rep,
569				   out_creds,
570				   &krbtgt->session,
571				   NULL,
572				   0,
573				   &krbtgt->addresses,
574				   nonce,
575				   eflags,
576				   NULL,
577				   decrypt_tkt_with_subkey,
578				   subkey);
579    out2:
580	krb5_free_kdc_rep(context, &rep);
581    } else if(krb5_rd_error(context, &resp, &error) == 0) {
582	ret = krb5_error_from_rd_error(context, &error, in_creds);
583	krb5_free_error_contents(context, &error);
584    } else if(resp.length > 0 && ((char*)resp.data)[0] == 4) {
585	ret = KRB5KRB_AP_ERR_V4_REPLY;
586	krb5_clear_error_message(context);
587    } else {
588	ret = KRB5KRB_AP_ERR_MSG_TYPE;
589	krb5_clear_error_message(context);
590    }
591
592out:
593    if (second_ticket == &second_ticket_data)
594	free_Ticket(&second_ticket_data);
595    free_METHOD_DATA(&padata);
596    krb5_data_free(&resp);
597    krb5_data_free(&enc);
598    if(subkey)
599	krb5_free_keyblock(context, subkey);
600    return ret;
601
602}
603
604/*
605 * same as above, just get local addresses first if the krbtgt have
606 * them and the realm is not addressless
607 */
608
609static krb5_error_code
610get_cred_kdc_address(krb5_context context,
611		     krb5_ccache id,
612		     krb5_kdc_flags flags,
613		     krb5_addresses *addrs,
614		     krb5_creds *in_creds,
615		     krb5_creds *krbtgt,
616		     krb5_principal impersonate_principal,
617		     Ticket *second_ticket,
618		     krb5_creds *out_creds)
619{
620    krb5_error_code ret;
621    krb5_addresses addresses = { 0, NULL };
622
623    /*
624     * Inherit the address-ness of the krbtgt if the address is not
625     * specified.
626     */
627
628    if (addrs == NULL && krbtgt->addresses.len != 0) {
629	krb5_boolean noaddr;
630
631	krb5_appdefault_boolean(context, NULL, krbtgt->server->realm,
632				"no-addresses", FALSE, &noaddr);
633
634	if (!noaddr) {
635	    krb5_get_all_client_addrs(context, &addresses);
636	    /* XXX this sucks. */
637	    addrs = &addresses;
638	    if(addresses.len == 0)
639		addrs = NULL;
640	}
641    }
642    ret = get_cred_kdc(context, id, flags, addrs, in_creds,
643		       krbtgt, impersonate_principal,
644		       second_ticket, out_creds);
645    krb5_free_addresses(context, &addresses);
646    return ret;
647}
648
649KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
650krb5_get_kdc_cred(krb5_context context,
651		  krb5_ccache id,
652		  krb5_kdc_flags flags,
653		  krb5_addresses *addresses,
654		  Ticket  *second_ticket,
655		  krb5_creds *in_creds,
656		  krb5_creds **out_creds
657		  )
658{
659    krb5_error_code ret;
660    krb5_creds *krbtgt;
661
662    *out_creds = calloc(1, sizeof(**out_creds));
663    if(*out_creds == NULL)
664	return krb5_enomem(context);
665    ret = _krb5_get_krbtgt (context,
666			    id,
667			    in_creds->server->realm,
668			    &krbtgt);
669    if(ret) {
670	free(*out_creds);
671	*out_creds = NULL;
672	return ret;
673    }
674    ret = get_cred_kdc(context, id, flags, addresses,
675		       in_creds, krbtgt, NULL, NULL, *out_creds);
676    krb5_free_creds (context, krbtgt);
677    if(ret) {
678	free(*out_creds);
679	*out_creds = NULL;
680    }
681    return ret;
682}
683
684static int
685not_found(krb5_context context, krb5_const_principal p, krb5_error_code code)
686{
687    krb5_error_code ret;
688    const char *err;
689    char *str;
690
691    err = krb5_get_error_message(context, code);
692    ret = krb5_unparse_name(context, p, &str);
693    if(ret) {
694	krb5_clear_error_message(context);
695	return code;
696    }
697    krb5_set_error_message(context, code, N_("%s (%s)", ""), err, str);
698    free(str);
699    return code;
700}
701
702static krb5_error_code
703find_cred(krb5_context context,
704	  krb5_ccache id,
705	  krb5_principal server,
706	  krb5_creds **tgts,
707	  krb5_creds *out_creds)
708{
709    krb5_error_code ret;
710    krb5_creds mcreds;
711
712    krb5_cc_clear_mcred(&mcreds);
713    mcreds.server = server;
714    krb5_timeofday(context, &mcreds.times.endtime);
715    ret = krb5_cc_retrieve_cred(context, id,
716				KRB5_TC_DONT_MATCH_REALM |
717				KRB5_TC_MATCH_TIMES,
718				&mcreds, out_creds);
719    if(ret == 0)
720	return 0;
721    while(tgts && *tgts){
722	if(krb5_compare_creds(context, KRB5_TC_DONT_MATCH_REALM,
723			      &mcreds, *tgts)){
724	    ret = krb5_copy_creds_contents(context, *tgts, out_creds);
725	    return ret;
726	}
727	tgts++;
728    }
729    return not_found(context, server, KRB5_CC_NOTFOUND);
730}
731
732static krb5_error_code
733add_cred(krb5_context context, krb5_creds const *tkt, krb5_creds ***tgts)
734{
735    int i;
736    krb5_error_code ret;
737    krb5_creds **tmp = *tgts;
738
739    for(i = 0; tmp && tmp[i]; i++); /* XXX */
740    tmp = realloc(tmp, (i+2)*sizeof(*tmp));
741    if(tmp == NULL)
742	return krb5_enomem(context);
743    *tgts = tmp;
744    ret = krb5_copy_creds(context, tkt, &tmp[i]);
745    tmp[i+1] = NULL;
746    return ret;
747}
748
749static krb5_error_code
750get_cred_kdc_capath_worker(krb5_context context,
751                           krb5_kdc_flags flags,
752                           krb5_ccache ccache,
753                           krb5_creds *in_creds,
754                           krb5_const_realm try_realm,
755                           krb5_principal impersonate_principal,
756                           Ticket *second_ticket,
757                           krb5_creds **out_creds,
758                           krb5_creds ***ret_tgts)
759{
760    krb5_error_code ret;
761    krb5_creds *tgt = NULL;
762    krb5_creds tmp_creds;
763    krb5_const_realm client_realm, server_realm;
764    int ok_as_delegate = 1;
765
766    *out_creds = calloc(1, sizeof(**out_creds));
767    if (*out_creds == NULL)
768	return krb5_enomem(context);
769
770    memset(&tmp_creds, 0, sizeof(tmp_creds));
771
772    client_realm = krb5_principal_get_realm(context, in_creds->client);
773    server_realm = krb5_principal_get_realm(context, in_creds->server);
774    ret = krb5_copy_principal(context, in_creds->client, &tmp_creds.client);
775    if (ret)
776	goto out;
777
778    ret = krb5_make_principal(context,
779			      &tmp_creds.server,
780			      try_realm,
781			      KRB5_TGS_NAME,
782			      server_realm,
783			      NULL);
784    if (ret)
785	goto out;
786
787    {
788	krb5_creds tgts;
789
790	/*
791	 * If we have krbtgt/server_realm@try_realm cached, use it and we're
792	 * done.
793	 */
794	ret = find_cred(context, ccache, tmp_creds.server,
795			*ret_tgts, &tgts);
796	if (ret == 0) {
797	    /* only allow implicit ok_as_delegate if the realm is the clients realm */
798	    if (strcmp(try_realm, client_realm) != 0
799		 || strcmp(try_realm, server_realm) != 0) {
800		ok_as_delegate = tgts.flags.b.ok_as_delegate;
801	    }
802
803	    ret = get_cred_kdc_address(context, ccache, flags, NULL,
804				   in_creds, &tgts,
805				   impersonate_principal,
806				   second_ticket,
807				   *out_creds);
808            krb5_free_cred_contents(context, &tgts);
809	    if (ret == 0 &&
810                !krb5_principal_compare(context, in_creds->server,
811                                        (*out_creds)->server)) {
812		ret = KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN;
813	    }
814	    if (ret == 0 && ok_as_delegate == 0)
815		(*out_creds)->flags.b.ok_as_delegate = 0;
816
817	    goto out;
818	}
819    }
820
821    if (krb5_realm_compare(context, in_creds->client, in_creds->server)) {
822	ret = not_found(context, in_creds->server, KRB5_CC_NOTFOUND);
823	goto out;
824    }
825
826    /*
827     * XXX This can loop forever, plus we recurse, so we can't just keep a
828     * count here.  The count would have to get passed around by reference.
829     *
830     * The KDCs check for transit loops for us, and capath data is finite, so
831     * in fact we'll fall out of this loop at some point.  We should do our own
832     * transit loop checking (like get_cred_kdc_referral()), and we should
833     * impose a max number of iterations altogether.  But barring malicious or
834     * broken KDCs, this is good enough.
835     */
836    while (1) {
837	heim_general_string tgt_inst;
838
839	ret = get_cred_kdc_capath(context, flags, ccache, &tmp_creds,
840				  NULL, NULL, &tgt, ret_tgts);
841	if (ret)
842	    goto out;
843
844	/*
845	 * if either of the chain or the ok_as_delegate was stripped
846	 * by the kdc, make sure we strip it too.
847	 */
848	if (ok_as_delegate == 0 || tgt->flags.b.ok_as_delegate == 0) {
849	    ok_as_delegate = 0;
850	    tgt->flags.b.ok_as_delegate = 0;
851	}
852
853	ret = add_cred(context, tgt, ret_tgts);
854	if (ret)
855	    goto out;
856	tgt_inst = tgt->server->name.name_string.val[1];
857	if (strcmp(tgt_inst, server_realm) == 0)
858	    break;
859	krb5_free_principal(context, tmp_creds.server);
860	tmp_creds.server = NULL;
861	ret = krb5_make_principal(context, &tmp_creds.server,
862				  tgt_inst, KRB5_TGS_NAME, server_realm, NULL);
863	if (ret)
864	    goto out;
865	ret = krb5_free_creds(context, tgt);
866	tgt = NULL;
867	if (ret)
868	    goto out;
869    }
870
871    ret = get_cred_kdc_address(context, ccache, flags, NULL,
872			       in_creds, tgt, impersonate_principal,
873			       second_ticket, *out_creds);
874    if (ret == 0 &&
875        !krb5_principal_compare(context, in_creds->server,
876                                    (*out_creds)->server)) {
877        krb5_free_cred_contents(context, *out_creds);
878        ret = KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN;
879    }
880    if (ret == 0 && ok_as_delegate == 0)
881        (*out_creds)->flags.b.ok_as_delegate = 0;
882
883out:
884    if (ret) {
885	krb5_free_creds(context, *out_creds);
886        *out_creds = NULL;
887    }
888    if (tmp_creds.server)
889	krb5_free_principal(context, tmp_creds.server);
890    if (tmp_creds.client)
891	krb5_free_principal(context, tmp_creds.client);
892    if (tgt)
893	krb5_free_creds(context, tgt);
894    return ret;
895}
896
897/*
898get_cred(server)
899	creds = cc_get_cred(server)
900	if(creds) return creds
901	tgt = cc_get_cred(krbtgt/server_realm@any_realm)
902	if(tgt)
903		return get_cred_tgt(server, tgt)
904	if(client_realm == server_realm)
905		return NULL
906	tgt = get_cred(krbtgt/server_realm@client_realm)
907	while(tgt_inst != server_realm)
908		tgt = get_cred(krbtgt/server_realm@tgt_inst)
909	return get_cred_tgt(server, tgt)
910	*/
911
912static krb5_error_code
913get_cred_kdc_capath(krb5_context context,
914		    krb5_kdc_flags flags,
915		    krb5_ccache ccache,
916		    krb5_creds *in_creds,
917		    krb5_principal impersonate_principal,
918		    Ticket *second_ticket,
919		    krb5_creds **out_creds,
920		    krb5_creds ***ret_tgts)
921{
922    krb5_error_code ret;
923    krb5_const_realm client_realm, server_realm, try_realm;
924
925    client_realm = krb5_principal_get_realm(context, in_creds->client);
926    server_realm = krb5_principal_get_realm(context, in_creds->server);
927
928    try_realm = client_realm;
929    ret = get_cred_kdc_capath_worker(context, flags, ccache, in_creds, try_realm,
930                                     impersonate_principal, second_ticket, out_creds,
931                                     ret_tgts);
932
933    if (ret == KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN) {
934        try_realm = krb5_config_get_string(context, NULL, "capaths",
935                                           client_realm, server_realm, NULL);
936
937        if (try_realm != NULL && strcmp(try_realm, client_realm)) {
938            ret = get_cred_kdc_capath_worker(context, flags, ccache, in_creds,
939                                             try_realm, impersonate_principal,
940                                             second_ticket, out_creds, ret_tgts);
941        }
942    }
943
944    return ret;
945}
946
947/*
948 * Get a service ticket from a KDC by chasing referrals from a start realm.
949 *
950 * All referral TGTs produced in the process are thrown away when we're done.
951 * We don't store them, and we don't allow other search mechanisms (capaths) to
952 * use referral TGTs produced here.
953 */
954static krb5_error_code
955get_cred_kdc_referral(krb5_context context,
956		      krb5_kdc_flags flags,
957		      krb5_ccache ccache,
958		      krb5_creds *in_creds,
959		      krb5_principal impersonate_principal,
960		      Ticket *second_ticket,
961		      krb5_creds **out_creds)
962{
963    krb5_realm start_realm = NULL;
964    krb5_data config_start_realm;
965    krb5_error_code ret;
966    krb5_creds tgt, referral, ticket;
967    krb5_creds **referral_tgts = NULL;  /* used for loop detection */
968    int loop = 0;
969    int ok_as_delegate = 1;
970    size_t i;
971
972    if (in_creds->server->name.name_string.len < 2 && !flags.b.canonicalize) {
973	krb5_set_error_message(context, KRB5KDC_ERR_PATH_NOT_ACCEPTED,
974			       N_("Name too short to do referals, skipping", ""));
975	return KRB5KDC_ERR_PATH_NOT_ACCEPTED;
976    }
977
978    memset(&tgt, 0, sizeof(tgt));
979    memset(&ticket, 0, sizeof(ticket));
980
981    flags.b.canonicalize = 1;
982
983    *out_creds = NULL;
984
985
986    ret = krb5_cc_get_config(context, ccache, NULL, "start_realm", &config_start_realm);
987    if (ret == 0) {
988        start_realm = strndup(config_start_realm.data, config_start_realm.length);
989	krb5_data_free(&config_start_realm);
990    } else {
991        start_realm = strdup(krb5_principal_get_realm(context, in_creds->client));
992    }
993    if (start_realm == NULL)
994        return krb5_enomem(context);
995
996    /* find tgt for the clients base realm */
997    {
998	krb5_principal tgtname;
999
1000	ret = krb5_make_principal(context, &tgtname,
1001				  start_realm,
1002				  KRB5_TGS_NAME,
1003				  start_realm,
1004				  NULL);
1005	if (ret) {
1006            free(start_realm);
1007	    return ret;
1008        }
1009
1010	ret = find_cred(context, ccache, tgtname, NULL, &tgt);
1011	krb5_free_principal(context, tgtname);
1012	if (ret) {
1013            free(start_realm);
1014	    return ret;
1015        }
1016    }
1017
1018    referral = *in_creds;
1019    ret = krb5_copy_principal(context, in_creds->server, &referral.server);
1020    if (ret) {
1021	krb5_free_cred_contents(context, &tgt);
1022        free(start_realm);
1023	return ret;
1024    }
1025    ret = krb5_principal_set_realm(context, referral.server, start_realm);
1026    free(start_realm);
1027    start_realm = NULL;
1028    if (ret) {
1029	krb5_free_cred_contents(context, &tgt);
1030	krb5_free_principal(context, referral.server);
1031	return ret;
1032    }
1033
1034    while (loop++ < 17) {
1035	krb5_creds **tickets;
1036	krb5_creds mcreds;
1037	char *referral_realm;
1038
1039	/* Use cache if we are not doing impersonation or contrained deleg */
1040	if (impersonate_principal == NULL || flags.b.cname_in_addl_tkt) {
1041	    krb5_cc_clear_mcred(&mcreds);
1042	    mcreds.server = referral.server;
1043	    krb5_timeofday(context, &mcreds.times.endtime);
1044	    ret = krb5_cc_retrieve_cred(context, ccache, KRB5_TC_MATCH_TIMES,
1045					&mcreds, &ticket);
1046	} else
1047	    ret = EINVAL;
1048
1049	if (ret) {
1050	    ret = get_cred_kdc_address(context, ccache, flags, NULL,
1051				       &referral, &tgt, impersonate_principal,
1052				       second_ticket, &ticket);
1053	    if (ret)
1054		goto out;
1055	}
1056
1057	/* Did we get the right ticket ? */
1058	if (krb5_principal_compare_any_realm(context,
1059					     referral.server,
1060					     ticket.server))
1061	    break;
1062
1063	if (!krb5_principal_is_krbtgt(context, ticket.server)) {
1064	    krb5_set_error_message(context, KRB5KRB_AP_ERR_NOT_US,
1065				   N_("Got back an non krbtgt "
1066				      "ticket referrals", ""));
1067	    ret = KRB5KRB_AP_ERR_NOT_US;
1068	    goto out;
1069	}
1070
1071	referral_realm = ticket.server->name.name_string.val[1];
1072
1073	/* check that there are no referrals loops */
1074	tickets = referral_tgts;
1075
1076	krb5_cc_clear_mcred(&mcreds);
1077	mcreds.server = ticket.server;
1078
1079	while (tickets && *tickets){
1080	    if (krb5_compare_creds(context,
1081				  KRB5_TC_DONT_MATCH_REALM,
1082				  &mcreds,
1083				  *tickets)) {
1084		krb5_set_error_message(context, KRB5_GET_IN_TKT_LOOP,
1085				       N_("Referral from %s "
1086					  "loops back to realm %s", ""),
1087				       tgt.server->realm,
1088				       referral_realm);
1089		ret = KRB5_GET_IN_TKT_LOOP;
1090                goto out;
1091	    }
1092	    tickets++;
1093	}
1094
1095	/*
1096	 * if either of the chain or the ok_as_delegate was stripped
1097	 * by the kdc, make sure we strip it too.
1098	 */
1099
1100	if (ok_as_delegate == 0 || ticket.flags.b.ok_as_delegate == 0) {
1101	    ok_as_delegate = 0;
1102	    ticket.flags.b.ok_as_delegate = 0;
1103	}
1104
1105        _krb5_debug(context, 6, "get_cred_kdc_referral: got referral "
1106                    "to %s from %s", referral_realm, referral.server->realm);
1107	ret = add_cred(context, &ticket, &referral_tgts);
1108	if (ret)
1109	    goto out;
1110
1111	/* try realm in the referral */
1112	ret = krb5_principal_set_realm(context,
1113				       referral.server,
1114				       referral_realm);
1115	krb5_free_cred_contents(context, &tgt);
1116	tgt = ticket;
1117	memset(&ticket, 0, sizeof(ticket));
1118	if (ret)
1119	    goto out;
1120    }
1121
1122    ret = krb5_copy_creds(context, &ticket, out_creds);
1123
1124out:
1125    for (i = 0; referral_tgts && referral_tgts[i]; i++)
1126	krb5_free_creds(context, referral_tgts[i]);
1127    free(referral_tgts);
1128    krb5_free_principal(context, referral.server);
1129    krb5_free_cred_contents(context, &tgt);
1130    krb5_free_cred_contents(context, &ticket);
1131    return ret;
1132}
1133
1134
1135/*
1136 * Glue function between referrals version and old client chasing
1137 * codebase.
1138 */
1139
1140KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
1141_krb5_get_cred_kdc_any(krb5_context context,
1142		       krb5_kdc_flags flags,
1143		       krb5_ccache ccache,
1144		       krb5_creds *in_creds,
1145		       krb5_principal impersonate_principal,
1146		       Ticket *second_ticket,
1147		       krb5_creds **out_creds,
1148		       krb5_creds ***ret_tgts)
1149{
1150    krb5_error_code ret;
1151    krb5_deltat offset;
1152
1153    ret = krb5_cc_get_kdc_offset(context, ccache, &offset);
1154    if (ret == 0) {
1155	context->kdc_sec_offset = offset;
1156	context->kdc_usec_offset = 0;
1157    }
1158
1159    if (strcmp(in_creds->server->realm, "") != 0) {
1160        /*
1161         * Non-empty realm?  Try capaths first.  We might have local
1162         * policy (capaths) to honor.
1163         */
1164        ret = get_cred_kdc_capath(context,
1165                                  flags,
1166                                  ccache,
1167                                  in_creds,
1168                                  impersonate_principal,
1169                                  second_ticket,
1170                                  out_creds,
1171                                  ret_tgts);
1172        if (ret == 0)
1173            return ret;
1174    }
1175
1176    /* Otherwise try referrals */
1177    return get_cred_kdc_referral(context,
1178                                 flags,
1179                                 ccache,
1180                                 in_creds,
1181                                 impersonate_principal,
1182                                 second_ticket,
1183                                 out_creds);
1184}
1185
1186static krb5_error_code
1187check_cc(krb5_context context, krb5_flags options, krb5_ccache ccache,
1188	 krb5_creds *in_creds, krb5_creds *out_creds)
1189{
1190    krb5_error_code ret;
1191    krb5_timestamp now;
1192    krb5_creds mcreds = *in_creds;
1193
1194    krb5_timeofday(context, &now);
1195
1196    if (!(options & KRB5_GC_EXPIRED_OK) &&
1197	mcreds.times.endtime < now) {
1198	mcreds.times.renew_till = 0;
1199	krb5_timeofday(context, &mcreds.times.endtime);
1200	options |= KRB5_TC_MATCH_TIMES;
1201    }
1202
1203    if (mcreds.server->name.name_type == KRB5_NT_SRV_HST_NEEDS_CANON) {
1204        /* Avoid name canonicalization in krb5_cc_retrieve_cred() */
1205        krb5_principal_set_type(context, mcreds.server, KRB5_NT_SRV_HST);
1206    }
1207
1208    if (options & KRB5_GC_ANONYMOUS) {
1209	ret = krb5_make_principal(context,
1210				  &mcreds.client,
1211				  krb5_principal_get_realm(context, mcreds.client),
1212				  KRB5_WELLKNOWN_NAME,
1213				  KRB5_ANON_NAME,
1214				  NULL);
1215	if (ret)
1216	    return ret;
1217    }
1218
1219    ret = krb5_cc_retrieve_cred(context, ccache,
1220				(options &
1221				 (KRB5_TC_DONT_MATCH_REALM |
1222                                  KRB5_TC_MATCH_KEYTYPE |
1223				  KRB5_TC_MATCH_TIMES)),
1224				&mcreds, out_creds);
1225
1226    if (options & KRB5_GC_ANONYMOUS)
1227	krb5_free_principal(context, mcreds.client);
1228
1229    return ret;
1230}
1231
1232static void
1233store_cred(krb5_context context, krb5_ccache ccache,
1234	   krb5_const_principal server_princ, krb5_creds *creds)
1235{
1236    if (!krb5_principal_compare(context, creds->server, server_princ)) {
1237        krb5_principal tmp_princ = creds->server;
1238        /*
1239         * Store the cred with the pre-canon server princ first so it
1240         * can be found quickly in the future.
1241         */
1242        creds->server = (krb5_principal)server_princ;
1243        krb5_cc_store_cred(context, ccache, creds);
1244        creds->server = tmp_princ;
1245        /* Then store again with the canonicalized server princ */
1246    }
1247    krb5_cc_store_cred(context, ccache, creds);
1248}
1249
1250
1251KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
1252krb5_get_credentials_with_flags(krb5_context context,
1253				krb5_flags options,
1254				krb5_kdc_flags flags,
1255				krb5_ccache ccache,
1256				krb5_creds *in_creds,
1257				krb5_creds **out_creds)
1258{
1259    krb5_error_code ret;
1260    krb5_name_canon_iterator name_canon_iter = NULL;
1261    krb5_name_canon_rule_options rule_opts;
1262    krb5_const_principal try_princ = NULL;
1263    krb5_principal save_princ = in_creds->server;
1264    krb5_creds **tgts;
1265    krb5_creds *res_creds;
1266    int i;
1267
1268    if (_krb5_have_debug(context, 5)) {
1269        char *unparsed;
1270
1271        ret = krb5_unparse_name(context, in_creds->server, &unparsed);
1272        if (ret) {
1273            _krb5_debug(context, 5, "krb5_get_creds: unable to display "
1274                        "requested service principal");
1275        } else {
1276            _krb5_debug(context, 5, "krb5_get_creds: requesting a ticket "
1277                        "for %s", unparsed);
1278            free(unparsed);
1279        }
1280    }
1281
1282    if (in_creds->session.keytype) {
1283	ret = krb5_enctype_valid(context, in_creds->session.keytype);
1284	if (ret)
1285	    return ret;
1286	options |= KRB5_TC_MATCH_KEYTYPE;
1287    }
1288
1289    *out_creds = NULL;
1290    res_creds = calloc(1, sizeof(*res_creds));
1291    if (res_creds == NULL)
1292	return krb5_enomem(context);
1293
1294    ret = krb5_name_canon_iterator_start(context, in_creds->server,
1295					 &name_canon_iter);
1296    if (ret)
1297	return ret;
1298
1299next_rule:
1300    krb5_free_cred_contents(context, res_creds);
1301    memset(res_creds, 0, sizeof (*res_creds));
1302    ret = krb5_name_canon_iterate(context, &name_canon_iter, &try_princ,
1303                                  &rule_opts);
1304    in_creds->server = rk_UNCONST(try_princ);
1305    if (ret)
1306	goto out;
1307
1308    if (name_canon_iter == NULL) {
1309	if (options & KRB5_GC_CACHED)
1310	    ret = KRB5_CC_NOTFOUND;
1311	else
1312	    ret = KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN;
1313	goto out;
1314    }
1315
1316    ret = check_cc(context, options, ccache, in_creds, res_creds);
1317    if (ret == 0) {
1318	*out_creds = res_creds;
1319        res_creds = NULL;
1320	goto out;
1321    } else if(ret != KRB5_CC_END) {
1322        goto out;
1323    }
1324    if (options & KRB5_GC_CACHED)
1325	goto next_rule;
1326
1327    if(options & KRB5_GC_USER_USER)
1328	flags.b.enc_tkt_in_skey = 1;
1329    if (flags.b.enc_tkt_in_skey)
1330	options |= KRB5_GC_NO_STORE;
1331
1332    tgts = NULL;
1333    ret = _krb5_get_cred_kdc_any(context, flags, ccache,
1334				 in_creds, NULL, NULL, out_creds, &tgts);
1335    for (i = 0; tgts && tgts[i]; i++) {
1336	if ((options & KRB5_GC_NO_STORE) == 0)
1337	    krb5_cc_store_cred(context, ccache, tgts[i]);
1338	krb5_free_creds(context, tgts[i]);
1339    }
1340    free(tgts);
1341
1342    /* We don't yet have TGS w/ FAST, so we can't protect KBR-ERRORs */
1343    if (ret == KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN &&
1344	!(rule_opts & KRB5_NCRO_USE_FAST))
1345	goto next_rule;
1346
1347    if(ret == 0 && (options & KRB5_GC_NO_STORE) == 0)
1348	store_cred(context, ccache, in_creds->server, *out_creds);
1349
1350    if (ret == 0 && _krb5_have_debug(context, 5)) {
1351        char *unparsed;
1352
1353        ret = krb5_unparse_name(context, (*out_creds)->server, &unparsed);
1354        if (ret) {
1355            _krb5_debug(context, 5, "krb5_get_creds: unable to display "
1356                        "service principal");
1357        } else {
1358            _krb5_debug(context, 5, "krb5_get_creds: got a ticket for %s",
1359                        unparsed);
1360            free(unparsed);
1361        }
1362    }
1363
1364out:
1365    in_creds->server = save_princ;
1366    krb5_free_creds(context, res_creds);
1367    krb5_free_name_canon_iterator(context, name_canon_iter);
1368    if (ret)
1369	return not_found(context, in_creds->server, ret);
1370    return 0;
1371}
1372
1373KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
1374krb5_get_credentials(krb5_context context,
1375		     krb5_flags options,
1376		     krb5_ccache ccache,
1377		     krb5_creds *in_creds,
1378		     krb5_creds **out_creds)
1379{
1380    krb5_kdc_flags flags;
1381    flags.i = 0;
1382    return krb5_get_credentials_with_flags(context, options, flags,
1383					   ccache, in_creds, out_creds);
1384}
1385
1386struct krb5_get_creds_opt_data {
1387    krb5_principal self;
1388    krb5_flags options;
1389    krb5_enctype enctype;
1390    Ticket *ticket;
1391};
1392
1393
1394KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
1395krb5_get_creds_opt_alloc(krb5_context context, krb5_get_creds_opt *opt)
1396{
1397    *opt = calloc(1, sizeof(**opt));
1398    if (*opt == NULL)
1399	return krb5_enomem(context);
1400    return 0;
1401}
1402
1403KRB5_LIB_FUNCTION void KRB5_LIB_CALL
1404krb5_get_creds_opt_free(krb5_context context, krb5_get_creds_opt opt)
1405{
1406    if (opt->self)
1407	krb5_free_principal(context, opt->self);
1408    if (opt->ticket) {
1409	free_Ticket(opt->ticket);
1410	free(opt->ticket);
1411    }
1412    memset(opt, 0, sizeof(*opt));
1413    free(opt);
1414}
1415
1416KRB5_LIB_FUNCTION void KRB5_LIB_CALL
1417krb5_get_creds_opt_set_options(krb5_context context,
1418			       krb5_get_creds_opt opt,
1419			       krb5_flags options)
1420{
1421    opt->options = options;
1422}
1423
1424KRB5_LIB_FUNCTION void KRB5_LIB_CALL
1425krb5_get_creds_opt_add_options(krb5_context context,
1426			       krb5_get_creds_opt opt,
1427			       krb5_flags options)
1428{
1429    opt->options |= options;
1430}
1431
1432KRB5_LIB_FUNCTION void KRB5_LIB_CALL
1433krb5_get_creds_opt_set_enctype(krb5_context context,
1434			       krb5_get_creds_opt opt,
1435			       krb5_enctype enctype)
1436{
1437    opt->enctype = enctype;
1438}
1439
1440KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
1441krb5_get_creds_opt_set_impersonate(krb5_context context,
1442				   krb5_get_creds_opt opt,
1443				   krb5_const_principal self)
1444{
1445    if (opt->self)
1446	krb5_free_principal(context, opt->self);
1447    return krb5_copy_principal(context, self, &opt->self);
1448}
1449
1450KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
1451krb5_get_creds_opt_set_ticket(krb5_context context,
1452			      krb5_get_creds_opt opt,
1453			      const Ticket *ticket)
1454{
1455    if (opt->ticket) {
1456	free_Ticket(opt->ticket);
1457	free(opt->ticket);
1458	opt->ticket = NULL;
1459    }
1460    if (ticket) {
1461	krb5_error_code ret;
1462
1463	opt->ticket = malloc(sizeof(*ticket));
1464	if (opt->ticket == NULL)
1465	    return krb5_enomem(context);
1466	ret = copy_Ticket(ticket, opt->ticket);
1467	if (ret) {
1468	    free(opt->ticket);
1469	    opt->ticket = NULL;
1470	    krb5_set_error_message(context, ret,
1471				   N_("malloc: out of memory", ""));
1472	    return ret;
1473	}
1474    }
1475    return 0;
1476}
1477
1478
1479KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
1480krb5_get_creds(krb5_context context,
1481	       krb5_get_creds_opt opt,
1482	       krb5_ccache ccache,
1483	       krb5_const_principal inprinc,
1484	       krb5_creds **out_creds)
1485{
1486    krb5_kdc_flags flags;
1487    krb5_flags options;
1488    krb5_creds in_creds;
1489    krb5_error_code ret;
1490    krb5_creds **tgts;
1491    krb5_creds *res_creds;
1492    krb5_const_principal try_princ = NULL;
1493    krb5_name_canon_iterator name_canon_iter = NULL;
1494    krb5_name_canon_rule_options rule_opts;
1495    int i;
1496    int type;
1497    const char *comp;
1498
1499    memset(&in_creds, 0, sizeof(in_creds));
1500    in_creds.server = rk_UNCONST(inprinc);
1501
1502    if (_krb5_have_debug(context, 5)) {
1503        char *unparsed;
1504
1505        ret = krb5_unparse_name(context, in_creds.server, &unparsed);
1506        if (ret) {
1507            _krb5_debug(context, 5, "krb5_get_creds: unable to display "
1508                        "requested service principal");
1509        } else {
1510            _krb5_debug(context, 5, "krb5_get_creds: requesting a ticket "
1511                        "for %s", unparsed);
1512            free(unparsed);
1513        }
1514    }
1515
1516    if (opt && opt->enctype) {
1517	ret = krb5_enctype_valid(context, opt->enctype);
1518	if (ret)
1519	    return ret;
1520    }
1521
1522    ret = krb5_cc_get_principal(context, ccache, &in_creds.client);
1523    if (ret)
1524	return ret;
1525
1526    if (opt)
1527	options = opt->options;
1528    else
1529	options = 0;
1530    flags.i = 0;
1531
1532    *out_creds = NULL;
1533    res_creds = calloc(1, sizeof(*res_creds));
1534    if (res_creds == NULL) {
1535	krb5_free_principal(context, in_creds.client);
1536	return krb5_enomem(context);
1537    }
1538
1539    if (opt && opt->enctype) {
1540	in_creds.session.keytype = opt->enctype;
1541	options |= KRB5_TC_MATCH_KEYTYPE;
1542    }
1543
1544    ret = krb5_name_canon_iterator_start(context, in_creds.server,
1545					 &name_canon_iter);
1546    if (ret)
1547	goto out;
1548
1549next_rule:
1550    ret = krb5_name_canon_iterate(context, &name_canon_iter, &try_princ,
1551                                  &rule_opts);
1552    in_creds.server = rk_UNCONST(try_princ);
1553    if (ret)
1554	goto out;
1555
1556    if (name_canon_iter == NULL) {
1557	if (options & KRB5_GC_CACHED)
1558	    ret = KRB5_CC_NOTFOUND;
1559	else
1560	    ret = KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN;
1561	goto out;
1562    }
1563
1564    ret = check_cc(context, options, ccache, &in_creds, res_creds);
1565    if (ret == 0) {
1566	*out_creds = res_creds;
1567        res_creds = NULL;
1568	goto out;
1569    } else if (ret != KRB5_CC_END) {
1570	goto out;
1571    }
1572    if (options & KRB5_GC_CACHED)
1573	goto next_rule;
1574
1575    type = krb5_principal_get_type(context, try_princ);
1576    comp = krb5_principal_get_comp_string(context, try_princ, 0);
1577    if ((type == KRB5_NT_SRV_HST || type == KRB5_NT_UNKNOWN) &&
1578        comp != NULL && strcmp(comp, "host") == 0)
1579	flags.b.canonicalize = 1;
1580    if (rule_opts & KRB5_NCRO_NO_REFERRALS)
1581	flags.b.canonicalize = 0;
1582    else
1583	flags.b.canonicalize = (options & KRB5_GC_CANONICALIZE) ? 1 : 0;
1584    if (options & KRB5_GC_USER_USER) {
1585	flags.b.enc_tkt_in_skey = 1;
1586	options |= KRB5_GC_NO_STORE;
1587    }
1588    if (options & KRB5_GC_FORWARDABLE)
1589	flags.b.forwardable = 1;
1590    if (options & KRB5_GC_NO_TRANSIT_CHECK)
1591	flags.b.disable_transited_check = 1;
1592    if (options & KRB5_GC_CONSTRAINED_DELEGATION)
1593	flags.b.cname_in_addl_tkt = 1;
1594    if (options & KRB5_GC_ANONYMOUS)
1595	flags.b.request_anonymous = 1;
1596
1597    tgts = NULL;
1598    ret = _krb5_get_cred_kdc_any(context, flags, ccache,
1599				 &in_creds, opt ? opt->self : 0,
1600				 opt ? opt->ticket : 0, out_creds,
1601				 &tgts);
1602    for (i = 0; tgts && tgts[i]; i++) {
1603	if ((options & KRB5_GC_NO_STORE) == 0)
1604	    krb5_cc_store_cred(context, ccache, tgts[i]);
1605	krb5_free_creds(context, tgts[i]);
1606    }
1607    free(tgts);
1608
1609    /* We don't yet have TGS w/ FAST, so we can't protect KBR-ERRORs */
1610    if (ret == KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN &&
1611	!(rule_opts & KRB5_NCRO_USE_FAST))
1612	goto next_rule;
1613
1614    if (ret == 0 && (options & KRB5_GC_NO_STORE) == 0)
1615	store_cred(context, ccache, inprinc, *out_creds);
1616
1617    if (ret == 0 && _krb5_have_debug(context, 5)) {
1618        char *unparsed;
1619
1620        ret = krb5_unparse_name(context, (*out_creds)->server, &unparsed);
1621        if (ret) {
1622            _krb5_debug(context, 5, "krb5_get_creds: unable to display "
1623                        "service principal");
1624        } else {
1625            _krb5_debug(context, 5, "krb5_get_creds: got a ticket for %s",
1626                        unparsed);
1627            free(unparsed);
1628        }
1629    }
1630
1631out:
1632    krb5_free_creds(context, res_creds);
1633    krb5_free_principal(context, in_creds.client);
1634    krb5_free_name_canon_iterator(context, name_canon_iter);
1635    if (ret)
1636	return not_found(context, inprinc, ret);
1637    return ret;
1638}
1639
1640/*
1641 *
1642 */
1643
1644KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
1645krb5_get_renewed_creds(krb5_context context,
1646		       krb5_creds *creds,
1647		       krb5_const_principal client,
1648		       krb5_ccache ccache,
1649		       const char *in_tkt_service)
1650{
1651    krb5_error_code ret;
1652    krb5_kdc_flags flags;
1653    krb5_creds in, *template, *out = NULL;
1654
1655    memset(&in, 0, sizeof(in));
1656    memset(creds, 0, sizeof(*creds));
1657
1658    ret = krb5_copy_principal(context, client, &in.client);
1659    if (ret)
1660	return ret;
1661
1662    if (in_tkt_service) {
1663	ret = krb5_parse_name(context, in_tkt_service, &in.server);
1664	if (ret) {
1665	    krb5_free_principal(context, in.client);
1666	    return ret;
1667	}
1668    } else {
1669	const char *realm = krb5_principal_get_realm(context, client);
1670
1671	ret = krb5_make_principal(context, &in.server, realm, KRB5_TGS_NAME,
1672				  realm, NULL);
1673	if (ret) {
1674	    krb5_free_principal(context, in.client);
1675	    return ret;
1676	}
1677    }
1678
1679    flags.i = 0;
1680    flags.b.renewable = flags.b.renew = 1;
1681
1682    /*
1683     * Get template from old credential cache for the same entry, if
1684     * this failes, no worries.
1685     */
1686    ret = krb5_get_credentials(context, KRB5_GC_CACHED, ccache, &in, &template);
1687    if (ret == 0) {
1688	flags.b.forwardable = template->flags.b.forwardable;
1689	flags.b.proxiable = template->flags.b.proxiable;
1690	krb5_free_creds (context, template);
1691    }
1692
1693    ret = krb5_get_kdc_cred(context, ccache, flags, NULL, NULL, &in, &out);
1694    krb5_free_principal(context, in.client);
1695    krb5_free_principal(context, in.server);
1696    if (ret)
1697	return ret;
1698
1699    ret = krb5_copy_creds_contents(context, out, creds);
1700    krb5_free_creds(context, out);
1701
1702    return ret;
1703}
1704