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