xref: /netbsd-src/crypto/external/bsd/heimdal/dist/lib/krb5/get_cred.c (revision bdc22b2e01993381dcefeff2bc9b56ca75a4235c)
1 /*	$NetBSD: get_cred.c,v 1.2 2017/01/28 21:31:49 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 
41 static krb5_error_code
42 get_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 
51 static krb5_error_code
52 make_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 
88 static krb5_error_code
89 set_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 
140 static krb5_error_code
141 init_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 
286 fail:
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 
296 KRB5_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 */
339 static krb5_error_code KRB5_CALLCONV
340 decrypt_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 
411 static krb5_error_code
412 get_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;
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     memset(&rep, 0, sizeof(rep));
546     if(decode_TGS_REP(resp.data, resp.length, &rep.kdc_rep, &len) == 0) {
547 	unsigned eflags = 0;
548 
549 	ret = krb5_copy_principal(context,
550 				  in_creds->client,
551 				  &out_creds->client);
552 	if(ret)
553 	    goto out2;
554 	ret = krb5_copy_principal(context,
555 				  in_creds->server,
556 				  &out_creds->server);
557 	if(ret)
558 	    goto out2;
559 	/* this should go someplace else */
560 	out_creds->times.endtime = in_creds->times.endtime;
561 
562 	/* XXX should do better testing */
563 	if (flags.b.constrained_delegation || impersonate_principal)
564 	    eflags |= EXTRACT_TICKET_ALLOW_CNAME_MISMATCH;
565 
566 	ret = _krb5_extract_ticket(context,
567 				   &rep,
568 				   out_creds,
569 				   &krbtgt->session,
570 				   NULL,
571 				   0,
572 				   &krbtgt->addresses,
573 				   nonce,
574 				   eflags,
575 				   NULL,
576 				   decrypt_tkt_with_subkey,
577 				   subkey);
578     out2:
579 	krb5_free_kdc_rep(context, &rep);
580     } else if(krb5_rd_error(context, &resp, &error) == 0) {
581 	ret = krb5_error_from_rd_error(context, &error, in_creds);
582 	krb5_free_error_contents(context, &error);
583     } else if(resp.length > 0 && ((char*)resp.data)[0] == 4) {
584 	ret = KRB5KRB_AP_ERR_V4_REPLY;
585 	krb5_clear_error_message(context);
586     } else {
587 	ret = KRB5KRB_AP_ERR_MSG_TYPE;
588 	krb5_clear_error_message(context);
589     }
590 
591 out:
592     if (second_ticket == &second_ticket_data)
593 	free_Ticket(&second_ticket_data);
594     free_METHOD_DATA(&padata);
595     krb5_data_free(&resp);
596     krb5_data_free(&enc);
597     if(subkey)
598 	krb5_free_keyblock(context, subkey);
599     return ret;
600 
601 }
602 
603 /*
604  * same as above, just get local addresses first if the krbtgt have
605  * them and the realm is not addressless
606  */
607 
608 static krb5_error_code
609 get_cred_kdc_address(krb5_context context,
610 		     krb5_ccache id,
611 		     krb5_kdc_flags flags,
612 		     krb5_addresses *addrs,
613 		     krb5_creds *in_creds,
614 		     krb5_creds *krbtgt,
615 		     krb5_principal impersonate_principal,
616 		     Ticket *second_ticket,
617 		     krb5_creds *out_creds)
618 {
619     krb5_error_code ret;
620     krb5_addresses addresses = { 0, NULL };
621 
622     /*
623      * Inherit the address-ness of the krbtgt if the address is not
624      * specified.
625      */
626 
627     if (addrs == NULL && krbtgt->addresses.len != 0) {
628 	krb5_boolean noaddr;
629 
630 	krb5_appdefault_boolean(context, NULL, krbtgt->server->realm,
631 				"no-addresses", FALSE, &noaddr);
632 
633 	if (!noaddr) {
634 	    krb5_get_all_client_addrs(context, &addresses);
635 	    /* XXX this sucks. */
636 	    addrs = &addresses;
637 	    if(addresses.len == 0)
638 		addrs = NULL;
639 	}
640     }
641     ret = get_cred_kdc(context, id, flags, addrs, in_creds,
642 		       krbtgt, impersonate_principal,
643 		       second_ticket, out_creds);
644     krb5_free_addresses(context, &addresses);
645     return ret;
646 }
647 
648 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
649 krb5_get_kdc_cred(krb5_context context,
650 		  krb5_ccache id,
651 		  krb5_kdc_flags flags,
652 		  krb5_addresses *addresses,
653 		  Ticket  *second_ticket,
654 		  krb5_creds *in_creds,
655 		  krb5_creds **out_creds
656 		  )
657 {
658     krb5_error_code ret;
659     krb5_creds *krbtgt;
660 
661     *out_creds = calloc(1, sizeof(**out_creds));
662     if(*out_creds == NULL)
663 	return krb5_enomem(context);
664     ret = _krb5_get_krbtgt (context,
665 			    id,
666 			    in_creds->server->realm,
667 			    &krbtgt);
668     if(ret) {
669 	free(*out_creds);
670 	*out_creds = NULL;
671 	return ret;
672     }
673     ret = get_cred_kdc(context, id, flags, addresses,
674 		       in_creds, krbtgt, NULL, NULL, *out_creds);
675     krb5_free_creds (context, krbtgt);
676     if(ret) {
677 	free(*out_creds);
678 	*out_creds = NULL;
679     }
680     return ret;
681 }
682 
683 static int
684 not_found(krb5_context context, krb5_const_principal p, krb5_error_code code)
685 {
686     krb5_error_code ret;
687     char *str;
688 
689     ret = krb5_unparse_name(context, p, &str);
690     if(ret) {
691 	krb5_clear_error_message(context);
692 	return code;
693     }
694     krb5_set_error_message(context, code,
695 			   N_("Matching credential (%s) not found", ""), str);
696     free(str);
697     return code;
698 }
699 
700 static krb5_error_code
701 find_cred(krb5_context context,
702 	  krb5_ccache id,
703 	  krb5_principal server,
704 	  krb5_creds **tgts,
705 	  krb5_creds *out_creds)
706 {
707     krb5_error_code ret;
708     krb5_creds mcreds;
709 
710     krb5_cc_clear_mcred(&mcreds);
711     mcreds.server = server;
712     krb5_timeofday(context, &mcreds.times.endtime);
713     ret = krb5_cc_retrieve_cred(context, id,
714 				KRB5_TC_DONT_MATCH_REALM |
715 				KRB5_TC_MATCH_TIMES,
716 				&mcreds, out_creds);
717     if(ret == 0)
718 	return 0;
719     while(tgts && *tgts){
720 	if(krb5_compare_creds(context, KRB5_TC_DONT_MATCH_REALM,
721 			      &mcreds, *tgts)){
722 	    ret = krb5_copy_creds_contents(context, *tgts, out_creds);
723 	    return ret;
724 	}
725 	tgts++;
726     }
727     return not_found(context, server, KRB5_CC_NOTFOUND);
728 }
729 
730 static krb5_error_code
731 add_cred(krb5_context context, krb5_creds const *tkt, krb5_creds ***tgts)
732 {
733     int i;
734     krb5_error_code ret;
735     krb5_creds **tmp = *tgts;
736 
737     for(i = 0; tmp && tmp[i]; i++); /* XXX */
738     tmp = realloc(tmp, (i+2)*sizeof(*tmp));
739     if(tmp == NULL)
740 	return krb5_enomem(context);
741     *tgts = tmp;
742     ret = krb5_copy_creds(context, tkt, &tmp[i]);
743     tmp[i+1] = NULL;
744     return ret;
745 }
746 
747 static krb5_error_code
748 get_cred_kdc_capath_worker(krb5_context context,
749                            krb5_kdc_flags flags,
750                            krb5_ccache ccache,
751                            krb5_creds *in_creds,
752                            krb5_const_realm try_realm,
753                            krb5_principal impersonate_principal,
754                            Ticket *second_ticket,
755                            krb5_creds **out_creds,
756                            krb5_creds ***ret_tgts)
757 {
758     krb5_error_code ret;
759     krb5_creds *tgt = NULL;
760     krb5_creds tmp_creds;
761     krb5_const_realm client_realm, server_realm;
762     int ok_as_delegate = 1;
763 
764     *out_creds = calloc(1, sizeof(**out_creds));
765     if (*out_creds == NULL)
766 	return krb5_enomem(context);
767 
768     memset(&tmp_creds, 0, sizeof(tmp_creds));
769 
770     client_realm = krb5_principal_get_realm(context, in_creds->client);
771     server_realm = krb5_principal_get_realm(context, in_creds->server);
772     ret = krb5_copy_principal(context, in_creds->client, &tmp_creds.client);
773     if (ret)
774 	goto out;
775 
776     ret = krb5_make_principal(context,
777 			      &tmp_creds.server,
778 			      try_realm,
779 			      KRB5_TGS_NAME,
780 			      server_realm,
781 			      NULL);
782     if (ret)
783 	goto out;
784 
785     {
786 	krb5_creds tgts;
787 
788 	/*
789 	 * If we have krbtgt/server_realm@try_realm cached, use it and we're
790 	 * done.
791 	 */
792 	ret = find_cred(context, ccache, tmp_creds.server,
793 			*ret_tgts, &tgts);
794 	if (ret == 0) {
795 	    /* only allow implicit ok_as_delegate if the realm is the clients realm */
796 	    if (strcmp(try_realm, client_realm) != 0
797 		 || strcmp(try_realm, server_realm) != 0) {
798 		ok_as_delegate = tgts.flags.b.ok_as_delegate;
799 	    }
800 
801 	    ret = get_cred_kdc_address(context, ccache, flags, NULL,
802 				   in_creds, &tgts,
803 				   impersonate_principal,
804 				   second_ticket,
805 				   *out_creds);
806             krb5_free_cred_contents(context, &tgts);
807 	    if (ret == 0 &&
808                 !krb5_principal_compare(context, in_creds->server,
809                                         (*out_creds)->server)) {
810 		ret = KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN;
811 	    }
812 	    if (ret == 0 && ok_as_delegate == 0)
813 		(*out_creds)->flags.b.ok_as_delegate = 0;
814 
815 	    goto out;
816 	}
817     }
818 
819     if (krb5_realm_compare(context, in_creds->client, in_creds->server)) {
820 	ret = not_found(context, in_creds->server, KRB5_CC_NOTFOUND);
821 	goto out;
822     }
823 
824     /*
825      * XXX This can loop forever, plus we recurse, so we can't just keep a
826      * count here.  The count would have to get passed around by reference.
827      *
828      * The KDCs check for transit loops for us, and capath data is finite, so
829      * in fact we'll fall out of this loop at some point.  We should do our own
830      * transit loop checking (like get_cred_kdc_referral()), and we should
831      * impose a max number of iterations altogether.  But barring malicious or
832      * broken KDCs, this is good enough.
833      */
834     while (1) {
835 	heim_general_string tgt_inst;
836 
837 	ret = get_cred_kdc_capath(context, flags, ccache, &tmp_creds,
838 				  NULL, NULL, &tgt, ret_tgts);
839 	if (ret)
840 	    goto out;
841 
842 	/*
843 	 * if either of the chain or the ok_as_delegate was stripped
844 	 * by the kdc, make sure we strip it too.
845 	 */
846 	if (ok_as_delegate == 0 || tgt->flags.b.ok_as_delegate == 0) {
847 	    ok_as_delegate = 0;
848 	    tgt->flags.b.ok_as_delegate = 0;
849 	}
850 
851 	ret = add_cred(context, tgt, ret_tgts);
852 	if (ret)
853 	    goto out;
854 	tgt_inst = tgt->server->name.name_string.val[1];
855 	if (strcmp(tgt_inst, server_realm) == 0)
856 	    break;
857 	krb5_free_principal(context, tmp_creds.server);
858 	tmp_creds.server = NULL;
859 	ret = krb5_make_principal(context, &tmp_creds.server,
860 				  tgt_inst, KRB5_TGS_NAME, server_realm, NULL);
861 	if (ret)
862 	    goto out;
863 	ret = krb5_free_creds(context, tgt);
864 	tgt = NULL;
865 	if (ret)
866 	    goto out;
867     }
868 
869     ret = get_cred_kdc_address(context, ccache, flags, NULL,
870 			       in_creds, tgt, impersonate_principal,
871 			       second_ticket, *out_creds);
872     if (ret == 0 &&
873         !krb5_principal_compare(context, in_creds->server,
874                                     (*out_creds)->server)) {
875         krb5_free_cred_contents(context, *out_creds);
876         ret = KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN;
877     }
878     if (ret == 0 && ok_as_delegate == 0)
879         (*out_creds)->flags.b.ok_as_delegate = 0;
880 
881 out:
882     if (ret) {
883 	krb5_free_creds(context, *out_creds);
884         *out_creds = NULL;
885     }
886     if (tmp_creds.server)
887 	krb5_free_principal(context, tmp_creds.server);
888     if (tmp_creds.client)
889 	krb5_free_principal(context, tmp_creds.client);
890     if (tgt)
891 	krb5_free_creds(context, tgt);
892     return ret;
893 }
894 
895 /*
896 get_cred(server)
897 	creds = cc_get_cred(server)
898 	if(creds) return creds
899 	tgt = cc_get_cred(krbtgt/server_realm@any_realm)
900 	if(tgt)
901 		return get_cred_tgt(server, tgt)
902 	if(client_realm == server_realm)
903 		return NULL
904 	tgt = get_cred(krbtgt/server_realm@client_realm)
905 	while(tgt_inst != server_realm)
906 		tgt = get_cred(krbtgt/server_realm@tgt_inst)
907 	return get_cred_tgt(server, tgt)
908 	*/
909 
910 static krb5_error_code
911 get_cred_kdc_capath(krb5_context context,
912 		    krb5_kdc_flags flags,
913 		    krb5_ccache ccache,
914 		    krb5_creds *in_creds,
915 		    krb5_principal impersonate_principal,
916 		    Ticket *second_ticket,
917 		    krb5_creds **out_creds,
918 		    krb5_creds ***ret_tgts)
919 {
920     krb5_error_code ret;
921     krb5_const_realm client_realm, server_realm, try_realm;
922 
923     client_realm = krb5_principal_get_realm(context, in_creds->client);
924     server_realm = krb5_principal_get_realm(context, in_creds->server);
925 
926     try_realm = client_realm;
927     ret = get_cred_kdc_capath_worker(context, flags, ccache, in_creds, try_realm,
928                                      impersonate_principal, second_ticket, out_creds,
929                                      ret_tgts);
930 
931     if (ret == KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN) {
932         try_realm = krb5_config_get_string(context, NULL, "capaths",
933                                            client_realm, server_realm, NULL);
934 
935         if (try_realm != NULL && strcmp(try_realm, client_realm)) {
936             ret = get_cred_kdc_capath_worker(context, flags, ccache, in_creds,
937                                              try_realm, impersonate_principal,
938                                              second_ticket, out_creds, ret_tgts);
939         }
940     }
941 
942     return ret;
943 }
944 
945 /*
946  * Get a service ticket from a KDC by chasing referrals from a start realm.
947  *
948  * All referral TGTs produced in the process are thrown away when we're done.
949  * We don't store them, and we don't allow other search mechanisms (capaths) to
950  * use referral TGTs produced here.
951  */
952 static krb5_error_code
953 get_cred_kdc_referral(krb5_context context,
954 		      krb5_kdc_flags flags,
955 		      krb5_ccache ccache,
956 		      krb5_creds *in_creds,
957 		      krb5_principal impersonate_principal,
958 		      Ticket *second_ticket,
959 		      krb5_creds **out_creds)
960 {
961     krb5_realm start_realm = NULL;
962     krb5_data config_start_realm;
963     krb5_error_code ret;
964     krb5_creds tgt, referral, ticket;
965     krb5_creds **referral_tgts = NULL;  /* used for loop detection */
966     int loop = 0;
967     int ok_as_delegate = 1;
968     size_t i;
969 
970     if (in_creds->server->name.name_string.len < 2 && !flags.b.canonicalize) {
971 	krb5_set_error_message(context, KRB5KDC_ERR_PATH_NOT_ACCEPTED,
972 			       N_("Name too short to do referals, skipping", ""));
973 	return KRB5KDC_ERR_PATH_NOT_ACCEPTED;
974     }
975 
976     memset(&tgt, 0, sizeof(tgt));
977     memset(&ticket, 0, sizeof(ticket));
978 
979     flags.b.canonicalize = 1;
980 
981     *out_creds = NULL;
982 
983 
984     ret = krb5_cc_get_config(context, ccache, NULL, "start_realm", &config_start_realm);
985     if (ret == 0) {
986         start_realm = strndup(config_start_realm.data, config_start_realm.length);
987 	krb5_data_free(&config_start_realm);
988     } else {
989         start_realm = strdup(krb5_principal_get_realm(context, in_creds->client));
990     }
991     if (start_realm == NULL)
992         return krb5_enomem(context);
993 
994     /* find tgt for the clients base realm */
995     {
996 	krb5_principal tgtname;
997 
998 	ret = krb5_make_principal(context, &tgtname,
999 				  start_realm,
1000 				  KRB5_TGS_NAME,
1001 				  start_realm,
1002 				  NULL);
1003 	if (ret) {
1004             free(start_realm);
1005 	    return ret;
1006         }
1007 
1008 	ret = find_cred(context, ccache, tgtname, NULL, &tgt);
1009 	krb5_free_principal(context, tgtname);
1010 	if (ret) {
1011             free(start_realm);
1012 	    return ret;
1013         }
1014     }
1015 
1016     referral = *in_creds;
1017     ret = krb5_copy_principal(context, in_creds->server, &referral.server);
1018     if (ret) {
1019 	krb5_free_cred_contents(context, &tgt);
1020         free(start_realm);
1021 	return ret;
1022     }
1023     ret = krb5_principal_set_realm(context, referral.server, start_realm);
1024     free(start_realm);
1025     start_realm = NULL;
1026     if (ret) {
1027 	krb5_free_cred_contents(context, &tgt);
1028 	krb5_free_principal(context, referral.server);
1029 	return ret;
1030     }
1031 
1032     while (loop++ < 17) {
1033 	krb5_creds **tickets;
1034 	krb5_creds mcreds;
1035 	char *referral_realm;
1036 
1037 	/* Use cache if we are not doing impersonation or contrained deleg */
1038 	if (impersonate_principal == NULL || flags.b.constrained_delegation) {
1039 	    krb5_cc_clear_mcred(&mcreds);
1040 	    mcreds.server = referral.server;
1041 	    krb5_timeofday(context, &mcreds.times.endtime);
1042 	    ret = krb5_cc_retrieve_cred(context, ccache, KRB5_TC_MATCH_TIMES,
1043 					&mcreds, &ticket);
1044 	} else
1045 	    ret = EINVAL;
1046 
1047 	if (ret) {
1048 	    ret = get_cred_kdc_address(context, ccache, flags, NULL,
1049 				       &referral, &tgt, impersonate_principal,
1050 				       second_ticket, &ticket);
1051 	    if (ret)
1052 		goto out;
1053 	}
1054 
1055 	/* Did we get the right ticket ? */
1056 	if (krb5_principal_compare_any_realm(context,
1057 					     referral.server,
1058 					     ticket.server))
1059 	    break;
1060 
1061 	if (!krb5_principal_is_krbtgt(context, ticket.server)) {
1062 	    krb5_set_error_message(context, KRB5KRB_AP_ERR_NOT_US,
1063 				   N_("Got back an non krbtgt "
1064 				      "ticket referrals", ""));
1065 	    ret = KRB5KRB_AP_ERR_NOT_US;
1066 	    goto out;
1067 	}
1068 
1069 	referral_realm = ticket.server->name.name_string.val[1];
1070 
1071 	/* check that there are no referrals loops */
1072 	tickets = referral_tgts;
1073 
1074 	krb5_cc_clear_mcred(&mcreds);
1075 	mcreds.server = ticket.server;
1076 
1077 	while (tickets && *tickets){
1078 	    if (krb5_compare_creds(context,
1079 				  KRB5_TC_DONT_MATCH_REALM,
1080 				  &mcreds,
1081 				  *tickets)) {
1082 		krb5_set_error_message(context, KRB5_GET_IN_TKT_LOOP,
1083 				       N_("Referral from %s "
1084 					  "loops back to realm %s", ""),
1085 				       tgt.server->realm,
1086 				       referral_realm);
1087 		ret = KRB5_GET_IN_TKT_LOOP;
1088                 goto out;
1089 	    }
1090 	    tickets++;
1091 	}
1092 
1093 	/*
1094 	 * if either of the chain or the ok_as_delegate was stripped
1095 	 * by the kdc, make sure we strip it too.
1096 	 */
1097 
1098 	if (ok_as_delegate == 0 || ticket.flags.b.ok_as_delegate == 0) {
1099 	    ok_as_delegate = 0;
1100 	    ticket.flags.b.ok_as_delegate = 0;
1101 	}
1102 
1103         _krb5_debug(context, 6, "get_cred_kdc_referral: got referral "
1104                     "to %s from %s", referral_realm, referral.server->realm);
1105 	ret = add_cred(context, &ticket, &referral_tgts);
1106 	if (ret)
1107 	    goto out;
1108 
1109 	/* try realm in the referral */
1110 	ret = krb5_principal_set_realm(context,
1111 				       referral.server,
1112 				       referral_realm);
1113 	krb5_free_cred_contents(context, &tgt);
1114 	tgt = ticket;
1115 	memset(&ticket, 0, sizeof(ticket));
1116 	if (ret)
1117 	    goto out;
1118     }
1119 
1120     ret = krb5_copy_creds(context, &ticket, out_creds);
1121 
1122 out:
1123     for (i = 0; referral_tgts && referral_tgts[i]; i++)
1124 	krb5_free_creds(context, referral_tgts[i]);
1125     free(referral_tgts);
1126     krb5_free_principal(context, referral.server);
1127     krb5_free_cred_contents(context, &tgt);
1128     krb5_free_cred_contents(context, &ticket);
1129     return ret;
1130 }
1131 
1132 
1133 /*
1134  * Glue function between referrals version and old client chasing
1135  * codebase.
1136  */
1137 
1138 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
1139 _krb5_get_cred_kdc_any(krb5_context context,
1140 		       krb5_kdc_flags flags,
1141 		       krb5_ccache ccache,
1142 		       krb5_creds *in_creds,
1143 		       krb5_principal impersonate_principal,
1144 		       Ticket *second_ticket,
1145 		       krb5_creds **out_creds,
1146 		       krb5_creds ***ret_tgts)
1147 {
1148     krb5_error_code ret;
1149     krb5_deltat offset;
1150 
1151     ret = krb5_cc_get_kdc_offset(context, ccache, &offset);
1152     if (ret == 0) {
1153 	context->kdc_sec_offset = offset;
1154 	context->kdc_usec_offset = 0;
1155     }
1156 
1157     if (strcmp(in_creds->server->realm, "") != 0) {
1158         /*
1159          * Non-empty realm?  Try capaths first.  We might have local
1160          * policy (capaths) to honor.
1161          */
1162         ret = get_cred_kdc_capath(context,
1163                                   flags,
1164                                   ccache,
1165                                   in_creds,
1166                                   impersonate_principal,
1167                                   second_ticket,
1168                                   out_creds,
1169                                   ret_tgts);
1170         if (ret == 0)
1171             return ret;
1172     }
1173 
1174     /* Otherwise try referrals */
1175     return get_cred_kdc_referral(context,
1176                                  flags,
1177                                  ccache,
1178                                  in_creds,
1179                                  impersonate_principal,
1180                                  second_ticket,
1181                                  out_creds);
1182 }
1183 
1184 static krb5_error_code
1185 check_cc(krb5_context context, krb5_flags options, krb5_ccache ccache,
1186 	 krb5_creds *in_creds, krb5_creds *out_creds)
1187 {
1188     krb5_error_code ret;
1189     krb5_timestamp now;
1190     krb5_times save_times = in_creds->times;
1191     NAME_TYPE save_type = in_creds->server->name.name_type;
1192 
1193     krb5_timeofday(context, &now);
1194 
1195     if (!(options & KRB5_GC_EXPIRED_OK) &&
1196 	in_creds->times.endtime < now) {
1197 	in_creds->times.renew_till = 0;
1198 	krb5_timeofday(context, &in_creds->times.endtime);
1199 	options |= KRB5_TC_MATCH_TIMES;
1200     }
1201 
1202     if (save_type == KRB5_NT_SRV_HST_NEEDS_CANON) {
1203         /* Avoid name canonicalization in krb5_cc_retrieve_cred() */
1204         krb5_principal_set_type(context, in_creds->server, KRB5_NT_SRV_HST);
1205     }
1206 
1207     ret = krb5_cc_retrieve_cred(context, ccache,
1208 				(options &
1209 				 (KRB5_TC_DONT_MATCH_REALM |
1210                                   KRB5_TC_MATCH_KEYTYPE |
1211 				  KRB5_TC_MATCH_TIMES)),
1212 				in_creds, out_creds);
1213 
1214     in_creds->server->name.name_type = save_type;
1215     in_creds->times = save_times;
1216     return ret;
1217 }
1218 
1219 static void
1220 store_cred(krb5_context context, krb5_ccache ccache,
1221 	   krb5_const_principal server_princ, krb5_creds *creds)
1222 {
1223     if (!krb5_principal_compare(context, creds->server, server_princ)) {
1224         krb5_principal tmp_princ = creds->server;
1225         /*
1226          * Store the cred with the pre-canon server princ first so it
1227          * can be found quickly in the future.
1228          */
1229         creds->server = (krb5_principal)server_princ;
1230         krb5_cc_store_cred(context, ccache, creds);
1231         creds->server = tmp_princ;
1232         /* Then store again with the canonicalized server princ */
1233     }
1234     krb5_cc_store_cred(context, ccache, creds);
1235 }
1236 
1237 
1238 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
1239 krb5_get_credentials_with_flags(krb5_context context,
1240 				krb5_flags options,
1241 				krb5_kdc_flags flags,
1242 				krb5_ccache ccache,
1243 				krb5_creds *in_creds,
1244 				krb5_creds **out_creds)
1245 {
1246     krb5_error_code ret;
1247     krb5_name_canon_iterator name_canon_iter = NULL;
1248     krb5_name_canon_rule_options rule_opts;
1249     krb5_const_principal try_princ = NULL;
1250     krb5_principal save_princ = in_creds->server;
1251     krb5_creds **tgts;
1252     krb5_creds *res_creds;
1253     int i;
1254 
1255     if (_krb5_have_debug(context, 5)) {
1256         char *unparsed;
1257 
1258         ret = krb5_unparse_name(context, in_creds->server, &unparsed);
1259         if (ret) {
1260             _krb5_debug(context, 5, "krb5_get_creds: unable to display "
1261                         "requested service principal");
1262         } else {
1263             _krb5_debug(context, 5, "krb5_get_creds: requesting a ticket "
1264                         "for %s", unparsed);
1265             free(unparsed);
1266         }
1267     }
1268 
1269     if (in_creds->session.keytype) {
1270 	ret = krb5_enctype_valid(context, in_creds->session.keytype);
1271 	if (ret)
1272 	    return ret;
1273 	options |= KRB5_TC_MATCH_KEYTYPE;
1274     }
1275 
1276     *out_creds = NULL;
1277     res_creds = calloc(1, sizeof(*res_creds));
1278     if (res_creds == NULL)
1279 	return krb5_enomem(context);
1280 
1281     ret = krb5_name_canon_iterator_start(context, in_creds->server,
1282 					 &name_canon_iter);
1283     if (ret)
1284 	return ret;
1285 
1286 next_rule:
1287     krb5_free_cred_contents(context, res_creds);
1288     memset(res_creds, 0, sizeof (*res_creds));
1289     ret = krb5_name_canon_iterate(context, &name_canon_iter, &try_princ,
1290                                   &rule_opts);
1291     in_creds->server = rk_UNCONST(try_princ);
1292     if (ret)
1293 	goto out;
1294 
1295     if (name_canon_iter == NULL) {
1296 	if (options & KRB5_GC_CACHED)
1297 	    ret = KRB5_CC_NOTFOUND;
1298 	else
1299 	    ret = KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN;
1300 	goto out;
1301     }
1302 
1303     ret = check_cc(context, options, ccache, in_creds, res_creds);
1304     if (ret == 0) {
1305 	*out_creds = res_creds;
1306         res_creds = NULL;
1307 	goto out;
1308     } else if(ret != KRB5_CC_END) {
1309         goto out;
1310     }
1311     if (options & KRB5_GC_CACHED)
1312 	goto next_rule;
1313 
1314     if(options & KRB5_GC_USER_USER)
1315 	flags.b.enc_tkt_in_skey = 1;
1316     if (flags.b.enc_tkt_in_skey)
1317 	options |= KRB5_GC_NO_STORE;
1318 
1319     tgts = NULL;
1320     ret = _krb5_get_cred_kdc_any(context, flags, ccache,
1321 				 in_creds, NULL, NULL, out_creds, &tgts);
1322     for (i = 0; tgts && tgts[i]; i++) {
1323 	if ((options & KRB5_GC_NO_STORE) == 0)
1324 	    krb5_cc_store_cred(context, ccache, tgts[i]);
1325 	krb5_free_creds(context, tgts[i]);
1326     }
1327     free(tgts);
1328 
1329     /* We don't yet have TGS w/ FAST, so we can't protect KBR-ERRORs */
1330     if (ret == KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN &&
1331 	!(rule_opts & KRB5_NCRO_USE_FAST))
1332 	goto next_rule;
1333 
1334     if(ret == 0 && (options & KRB5_GC_NO_STORE) == 0)
1335 	store_cred(context, ccache, in_creds->server, *out_creds);
1336 
1337     if (ret == 0 && _krb5_have_debug(context, 5)) {
1338         char *unparsed;
1339 
1340         ret = krb5_unparse_name(context, (*out_creds)->server, &unparsed);
1341         if (ret) {
1342             _krb5_debug(context, 5, "krb5_get_creds: unable to display "
1343                         "service principal");
1344         } else {
1345             _krb5_debug(context, 5, "krb5_get_creds: got a ticket for %s",
1346                         unparsed);
1347             free(unparsed);
1348         }
1349     }
1350 
1351 out:
1352     in_creds->server = save_princ;
1353     krb5_free_creds(context, res_creds);
1354     krb5_free_name_canon_iterator(context, name_canon_iter);
1355     if (ret)
1356 	return not_found(context, in_creds->server, ret);
1357     return 0;
1358 }
1359 
1360 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
1361 krb5_get_credentials(krb5_context context,
1362 		     krb5_flags options,
1363 		     krb5_ccache ccache,
1364 		     krb5_creds *in_creds,
1365 		     krb5_creds **out_creds)
1366 {
1367     krb5_kdc_flags flags;
1368     flags.i = 0;
1369     return krb5_get_credentials_with_flags(context, options, flags,
1370 					   ccache, in_creds, out_creds);
1371 }
1372 
1373 struct krb5_get_creds_opt_data {
1374     krb5_principal self;
1375     krb5_flags options;
1376     krb5_enctype enctype;
1377     Ticket *ticket;
1378 };
1379 
1380 
1381 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
1382 krb5_get_creds_opt_alloc(krb5_context context, krb5_get_creds_opt *opt)
1383 {
1384     *opt = calloc(1, sizeof(**opt));
1385     if (*opt == NULL)
1386 	return krb5_enomem(context);
1387     return 0;
1388 }
1389 
1390 KRB5_LIB_FUNCTION void KRB5_LIB_CALL
1391 krb5_get_creds_opt_free(krb5_context context, krb5_get_creds_opt opt)
1392 {
1393     if (opt->self)
1394 	krb5_free_principal(context, opt->self);
1395     if (opt->ticket) {
1396 	free_Ticket(opt->ticket);
1397 	free(opt->ticket);
1398     }
1399     memset(opt, 0, sizeof(*opt));
1400     free(opt);
1401 }
1402 
1403 KRB5_LIB_FUNCTION void KRB5_LIB_CALL
1404 krb5_get_creds_opt_set_options(krb5_context context,
1405 			       krb5_get_creds_opt opt,
1406 			       krb5_flags options)
1407 {
1408     opt->options = options;
1409 }
1410 
1411 KRB5_LIB_FUNCTION void KRB5_LIB_CALL
1412 krb5_get_creds_opt_add_options(krb5_context context,
1413 			       krb5_get_creds_opt opt,
1414 			       krb5_flags options)
1415 {
1416     opt->options |= options;
1417 }
1418 
1419 KRB5_LIB_FUNCTION void KRB5_LIB_CALL
1420 krb5_get_creds_opt_set_enctype(krb5_context context,
1421 			       krb5_get_creds_opt opt,
1422 			       krb5_enctype enctype)
1423 {
1424     opt->enctype = enctype;
1425 }
1426 
1427 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
1428 krb5_get_creds_opt_set_impersonate(krb5_context context,
1429 				   krb5_get_creds_opt opt,
1430 				   krb5_const_principal self)
1431 {
1432     if (opt->self)
1433 	krb5_free_principal(context, opt->self);
1434     return krb5_copy_principal(context, self, &opt->self);
1435 }
1436 
1437 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
1438 krb5_get_creds_opt_set_ticket(krb5_context context,
1439 			      krb5_get_creds_opt opt,
1440 			      const Ticket *ticket)
1441 {
1442     if (opt->ticket) {
1443 	free_Ticket(opt->ticket);
1444 	free(opt->ticket);
1445 	opt->ticket = NULL;
1446     }
1447     if (ticket) {
1448 	krb5_error_code ret;
1449 
1450 	opt->ticket = malloc(sizeof(*ticket));
1451 	if (opt->ticket == NULL)
1452 	    return krb5_enomem(context);
1453 	ret = copy_Ticket(ticket, opt->ticket);
1454 	if (ret) {
1455 	    free(opt->ticket);
1456 	    opt->ticket = NULL;
1457 	    krb5_set_error_message(context, ret,
1458 				   N_("malloc: out of memory", ""));
1459 	    return ret;
1460 	}
1461     }
1462     return 0;
1463 }
1464 
1465 
1466 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
1467 krb5_get_creds(krb5_context context,
1468 	       krb5_get_creds_opt opt,
1469 	       krb5_ccache ccache,
1470 	       krb5_const_principal inprinc,
1471 	       krb5_creds **out_creds)
1472 {
1473     krb5_kdc_flags flags;
1474     krb5_flags options;
1475     krb5_creds in_creds;
1476     krb5_error_code ret;
1477     krb5_creds **tgts;
1478     krb5_creds *res_creds;
1479     krb5_const_principal try_princ = NULL;
1480     krb5_name_canon_iterator name_canon_iter = NULL;
1481     krb5_name_canon_rule_options rule_opts;
1482     int i;
1483     int type;
1484     const char *comp;
1485 
1486     memset(&in_creds, 0, sizeof(in_creds));
1487     in_creds.server = rk_UNCONST(inprinc);
1488 
1489     if (_krb5_have_debug(context, 5)) {
1490         char *unparsed;
1491 
1492         ret = krb5_unparse_name(context, in_creds.server, &unparsed);
1493         if (ret) {
1494             _krb5_debug(context, 5, "krb5_get_creds: unable to display "
1495                         "requested service principal");
1496         } else {
1497             _krb5_debug(context, 5, "krb5_get_creds: requesting a ticket "
1498                         "for %s", unparsed);
1499             free(unparsed);
1500         }
1501     }
1502 
1503     if (opt && opt->enctype) {
1504 	ret = krb5_enctype_valid(context, opt->enctype);
1505 	if (ret)
1506 	    return ret;
1507     }
1508 
1509     ret = krb5_cc_get_principal(context, ccache, &in_creds.client);
1510     if (ret)
1511 	return ret;
1512 
1513     if (opt)
1514 	options = opt->options;
1515     else
1516 	options = 0;
1517     flags.i = 0;
1518 
1519     *out_creds = NULL;
1520     res_creds = calloc(1, sizeof(*res_creds));
1521     if (res_creds == NULL) {
1522 	krb5_free_principal(context, in_creds.client);
1523 	return krb5_enomem(context);
1524     }
1525 
1526     if (opt && opt->enctype) {
1527 	in_creds.session.keytype = opt->enctype;
1528 	options |= KRB5_TC_MATCH_KEYTYPE;
1529     }
1530 
1531     ret = krb5_name_canon_iterator_start(context, in_creds.server,
1532 					 &name_canon_iter);
1533     if (ret)
1534 	goto out;
1535 
1536 next_rule:
1537     ret = krb5_name_canon_iterate(context, &name_canon_iter, &try_princ,
1538                                   &rule_opts);
1539     in_creds.server = rk_UNCONST(try_princ);
1540     if (ret)
1541 	goto out;
1542 
1543     if (name_canon_iter == NULL) {
1544 	if (options & KRB5_GC_CACHED)
1545 	    ret = KRB5_CC_NOTFOUND;
1546 	else
1547 	    ret = KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN;
1548 	goto out;
1549     }
1550 
1551     ret = check_cc(context, options, ccache, &in_creds, res_creds);
1552     if (ret == 0) {
1553 	*out_creds = res_creds;
1554         res_creds = NULL;
1555 	goto out;
1556     } else if (ret != KRB5_CC_END) {
1557 	goto out;
1558     }
1559     if (options & KRB5_GC_CACHED)
1560 	goto next_rule;
1561 
1562     type = krb5_principal_get_type(context, try_princ);
1563     comp = krb5_principal_get_comp_string(context, try_princ, 0);
1564     if ((type == KRB5_NT_SRV_HST || type == KRB5_NT_UNKNOWN) &&
1565         comp != NULL && strcmp(comp, "host") == 0)
1566 	flags.b.canonicalize = 1;
1567     if (rule_opts & KRB5_NCRO_NO_REFERRALS)
1568 	flags.b.canonicalize = 0;
1569     else
1570 	flags.b.canonicalize = (options & KRB5_GC_CANONICALIZE) ? 1 : 0;
1571     if (options & KRB5_GC_USER_USER) {
1572 	flags.b.enc_tkt_in_skey = 1;
1573 	options |= KRB5_GC_NO_STORE;
1574     }
1575     if (options & KRB5_GC_FORWARDABLE)
1576 	flags.b.forwardable = 1;
1577     if (options & KRB5_GC_NO_TRANSIT_CHECK)
1578 	flags.b.disable_transited_check = 1;
1579     if (options & KRB5_GC_CONSTRAINED_DELEGATION) {
1580 	flags.b.request_anonymous = 1; /* XXX ARGH confusion */
1581 	flags.b.constrained_delegation = 1;
1582     }
1583 
1584     tgts = NULL;
1585     ret = _krb5_get_cred_kdc_any(context, flags, ccache,
1586 				 &in_creds, opt ? opt->self : 0,
1587 				 opt ? opt->ticket : 0, out_creds,
1588 				 &tgts);
1589     for (i = 0; tgts && tgts[i]; i++) {
1590 	if ((options & KRB5_GC_NO_STORE) == 0)
1591 	    krb5_cc_store_cred(context, ccache, tgts[i]);
1592 	krb5_free_creds(context, tgts[i]);
1593     }
1594     free(tgts);
1595 
1596     /* We don't yet have TGS w/ FAST, so we can't protect KBR-ERRORs */
1597     if (ret == KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN &&
1598 	!(rule_opts & KRB5_NCRO_USE_FAST))
1599 	goto next_rule;
1600 
1601     if (ret == 0 && (options & KRB5_GC_NO_STORE) == 0)
1602 	store_cred(context, ccache, inprinc, *out_creds);
1603 
1604     if (ret == 0 && _krb5_have_debug(context, 5)) {
1605         char *unparsed;
1606 
1607         ret = krb5_unparse_name(context, (*out_creds)->server, &unparsed);
1608         if (ret) {
1609             _krb5_debug(context, 5, "krb5_get_creds: unable to display "
1610                         "service principal");
1611         } else {
1612             _krb5_debug(context, 5, "krb5_get_creds: got a ticket for %s",
1613                         unparsed);
1614             free(unparsed);
1615         }
1616     }
1617 
1618 out:
1619     krb5_free_creds(context, res_creds);
1620     krb5_free_principal(context, in_creds.client);
1621     krb5_free_name_canon_iterator(context, name_canon_iter);
1622     if (ret)
1623 	return not_found(context, inprinc, ret);
1624     return ret;
1625 }
1626 
1627 /*
1628  *
1629  */
1630 
1631 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
1632 krb5_get_renewed_creds(krb5_context context,
1633 		       krb5_creds *creds,
1634 		       krb5_const_principal client,
1635 		       krb5_ccache ccache,
1636 		       const char *in_tkt_service)
1637 {
1638     krb5_error_code ret;
1639     krb5_kdc_flags flags;
1640     krb5_creds in, *template, *out = NULL;
1641 
1642     memset(&in, 0, sizeof(in));
1643     memset(creds, 0, sizeof(*creds));
1644 
1645     ret = krb5_copy_principal(context, client, &in.client);
1646     if (ret)
1647 	return ret;
1648 
1649     if (in_tkt_service) {
1650 	ret = krb5_parse_name(context, in_tkt_service, &in.server);
1651 	if (ret) {
1652 	    krb5_free_principal(context, in.client);
1653 	    return ret;
1654 	}
1655     } else {
1656 	const char *realm = krb5_principal_get_realm(context, client);
1657 
1658 	ret = krb5_make_principal(context, &in.server, realm, KRB5_TGS_NAME,
1659 				  realm, NULL);
1660 	if (ret) {
1661 	    krb5_free_principal(context, in.client);
1662 	    return ret;
1663 	}
1664     }
1665 
1666     flags.i = 0;
1667     flags.b.renewable = flags.b.renew = 1;
1668 
1669     /*
1670      * Get template from old credential cache for the same entry, if
1671      * this failes, no worries.
1672      */
1673     ret = krb5_get_credentials(context, KRB5_GC_CACHED, ccache, &in, &template);
1674     if (ret == 0) {
1675 	flags.b.forwardable = template->flags.b.forwardable;
1676 	flags.b.proxiable = template->flags.b.proxiable;
1677 	krb5_free_creds (context, template);
1678     }
1679 
1680     ret = krb5_get_kdc_cred(context, ccache, flags, NULL, NULL, &in, &out);
1681     krb5_free_principal(context, in.client);
1682     krb5_free_principal(context, in.server);
1683     if (ret)
1684 	return ret;
1685 
1686     ret = krb5_copy_creds_contents(context, out, creds);
1687     krb5_free_creds(context, out);
1688 
1689     return ret;
1690 }
1691