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