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