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 "gsskrb5_locl.h"
37
38#ifdef __APPLE__
39
40#include <notify.h>
41#include <notify_keys.h>
42#include "kcm.h"
43
44/*
45 * Provide a negative cache that store a couple of entries in the
46 * process, this will take the edge of application that are very
47 * insistant on calling gss_init_sec_context().
48 *
49 * The cache users noticiation from KCM to know when to clear the
50 * cache.
51 */
52
53struct negative_cache {
54    gss_OID mech;
55    krb5_principal client;
56    krb5_principal server;
57    OM_uint32 major;
58    OM_uint32 minor;
59    const char *message;
60};
61
62static HEIMDAL_MUTEX nc_mutex = HEIMDAL_MUTEX_INITIALIZER;
63
64static struct gnegative_cache {
65    int inited;
66    int token_cache;
67    int token_time;
68    size_t next_entry;
69    struct negative_cache cache[7];
70} nc;
71
72static void
73free_entry(krb5_context context, struct negative_cache *e)
74{
75    if (e->server)
76        krb5_free_principal(context, e->server);
77    if (e->client)
78        krb5_free_principal(context, e->client);
79    if (e->message)
80	krb5_free_error_message(context, e->message);
81    e->client = NULL;
82    e->server = NULL;
83    e->message = NULL;
84}
85
86static OM_uint32
87check_neg_cache(OM_uint32 *minor_status,
88		krb5_context context,
89		const gss_OID mech,
90                gss_cred_id_t gss_cred,
91		gss_name_t target_name)
92{
93    krb5_principal server = (krb5_principal)target_name;
94    gsskrb5_cred cred = (gsskrb5_cred)gss_cred;
95    OM_uint32 major;
96    int check = 0;
97    size_t i;
98
99    HEIMDAL_MUTEX_lock(&nc_mutex);
100
101    if (!nc.inited) {
102
103        (void)notify_register_check(KRB5_KCM_NOTIFY_CACHE_CHANGED, &nc.token_cache);
104	(void)notify_register_check(kNotifyClockSet, &nc.token_time);
105
106        nc.inited = 1;
107    }
108
109    notify_check(nc.token_cache, &check);
110    if (!check)
111	notify_check(nc.token_time, &check);
112
113    /* if something changed, remove cache and return success */
114    if (check) {
115        for (i = 0; i < sizeof(nc.cache)/sizeof(nc.cache[0]); i++)
116            free_entry(context, &nc.cache[i]);
117	_gss_mg_log(1, "krb5-isc: got a notification, drop negative cache");
118	HEIMDAL_MUTEX_unlock(&nc_mutex);
119        return GSS_S_COMPLETE;
120    }
121
122    /* check if match */
123    for (i = 0; i < sizeof(nc.cache)/sizeof(nc.cache[0]); i++) {
124	if (gss_oid_equal(nc.cache[i].mech, mech) == 0)
125	    continue;
126        if (nc.cache[i].server == NULL)
127            continue;
128        if (!krb5_principal_compare(context, server, nc.cache[i].server))
129            continue;
130        if (cred && cred->principal) {
131            if (nc.cache[i].client == NULL)
132                continue;
133            if (!krb5_principal_compare(context, cred->principal,
134					nc.cache[i].client))
135                continue;
136        } else if (nc.cache[i].client)
137            continue;
138
139        *minor_status = nc.cache[i].minor;
140        major = nc.cache[i].major;
141
142	_gss_mg_log(1, "gss-isc: negative cache %d/%d - %s",
143		    (int)nc.cache[i].major,
144		    (int)nc.cache[i].minor,
145		    nc.cache[i].message);
146
147	krb5_set_error_message(context, *minor_status, "%s (negative cache)",
148			       nc.cache[i].message);
149
150        HEIMDAL_MUTEX_unlock(&nc_mutex);
151        return major;
152    }
153
154    HEIMDAL_MUTEX_unlock(&nc_mutex);
155
156    _gss_mg_log(1, "gss-isc: not negative cache");
157
158    return GSS_S_COMPLETE;
159}
160
161static void
162update_neg_cache(OM_uint32 major_status, OM_uint32 minor_status,
163                 krb5_context context,
164		 gss_OID mech,
165                 gsskrb5_cred cred,
166                 krb5_principal server)
167{
168    HEIMDAL_MUTEX_lock(&nc_mutex);
169
170    free_entry(context, &nc.cache[nc.next_entry]);
171
172    nc.cache[nc.next_entry].mech = mech;
173    krb5_copy_principal(context, server, &nc.cache[nc.next_entry].server);
174    if (cred && cred->principal) {
175        krb5_copy_principal(context, cred->principal,
176                            &nc.cache[nc.next_entry].client);
177    }
178    nc.cache[nc.next_entry].major = major_status;
179    nc.cache[nc.next_entry].minor = minor_status;
180    nc.cache[nc.next_entry].message =
181	krb5_get_error_message(context, minor_status);
182
183    nc.next_entry = (nc.next_entry + 1) %
184        (sizeof(nc.cache)/sizeof(nc.cache[0]));
185
186    HEIMDAL_MUTEX_unlock(&nc_mutex);
187}
188
189#endif /* __APPLE__ */
190
191#define GKIS(name) \
192static								   \
193OM_uint32 name(OM_uint32 *, gsskrb5_cred, gsskrb5_ctx,		   \
194	       krb5_context, gss_name_t, const gss_OID,		   \
195	       OM_uint32, OM_uint32, const gss_channel_bindings_t, \
196	       const gss_buffer_t, gss_buffer_t,                   \
197	       OM_uint32 *, OM_uint32 *)
198
199#ifdef PKINIT
200GKIS(init_pku2u_auth);
201GKIS(step_pku2u_auth);
202#endif
203GKIS(init_iakerb_auth);
204GKIS(step_iakerb_auth_as);
205GKIS(step_iakerb_auth_tgs);
206GKIS(init_krb5_auth);
207GKIS(step_setup_keys);
208GKIS(init_auth_step);
209GKIS(wait_repl_mutual);
210GKIS(step_completed);
211
212/*
213 * copy the addresses from `input_chan_bindings' (if any) to
214 * the auth context `ac'
215 */
216
217static OM_uint32
218set_addresses (krb5_context context,
219	       krb5_auth_context ac,
220	       const gss_channel_bindings_t input_chan_bindings)
221{
222    /* Port numbers are expected to be in application_data.value,
223     * initator's port first */
224
225    krb5_address initiator_addr, acceptor_addr;
226    krb5_error_code kret;
227
228    if (input_chan_bindings == GSS_C_NO_CHANNEL_BINDINGS
229	|| input_chan_bindings->application_data.length !=
230	2 * sizeof(ac->local_port))
231	return 0;
232
233    memset(&initiator_addr, 0, sizeof(initiator_addr));
234    memset(&acceptor_addr, 0, sizeof(acceptor_addr));
235
236    ac->local_port =
237	*(int16_t *) input_chan_bindings->application_data.value;
238
239    ac->remote_port =
240	*((int16_t *) input_chan_bindings->application_data.value + 1);
241
242    kret = _gsskrb5i_address_to_krb5addr(context,
243					 input_chan_bindings->acceptor_addrtype,
244					 &input_chan_bindings->acceptor_address,
245					 ac->remote_port,
246					 &acceptor_addr);
247    if (kret)
248	return kret;
249
250    kret = _gsskrb5i_address_to_krb5addr(context,
251					 input_chan_bindings->initiator_addrtype,
252					 &input_chan_bindings->initiator_address,
253					 ac->local_port,
254					 &initiator_addr);
255    if (kret) {
256	krb5_free_address (context, &acceptor_addr);
257	return kret;
258    }
259
260    kret = krb5_auth_con_setaddrs(context,
261				  ac,
262				  &initiator_addr,  /* local address */
263				  &acceptor_addr);  /* remote address */
264
265    krb5_free_address (context, &initiator_addr);
266    krb5_free_address (context, &acceptor_addr);
267
268#if 0
269    free(input_chan_bindings->application_data.value);
270    input_chan_bindings->application_data.value = NULL;
271    input_chan_bindings->application_data.length = 0;
272#endif
273
274    return kret;
275}
276
277OM_uint32
278_gsskrb5_create_ctx(OM_uint32 * minor_status,
279		    gss_ctx_id_t * context_handle,
280		    krb5_context context,
281		    const gss_channel_bindings_t input_chan_bindings,
282		    gss_OID mech)
283{
284    krb5_error_code kret;
285    gsskrb5_ctx ctx;
286
287    *context_handle = NULL;
288
289    ctx = calloc(1, sizeof(*ctx));
290    if (ctx == NULL) {
291	*minor_status = ENOMEM;
292	return GSS_S_FAILURE;
293    }
294    ctx->mech			= mech;
295    ctx->auth_context		= NULL;
296    ctx->deleg_auth_context	= NULL;
297    ctx->source			= NULL;
298    ctx->target			= NULL;
299    ctx->kcred			= NULL;
300    ctx->ccache			= NULL;
301    ctx->flags			= 0;
302    ctx->more_flags		= 0;
303    ctx->service_keyblock	= NULL;
304    ctx->ticket			= NULL;
305    krb5_data_zero(&ctx->fwd_data);
306
307    HEIMDAL_MUTEX_init(&ctx->ctx_id_mutex);
308
309    kret = krb5_auth_con_init (context, &ctx->auth_context);
310    if (kret) {
311	*minor_status = kret;
312	HEIMDAL_MUTEX_destroy(&ctx->ctx_id_mutex);
313	return GSS_S_FAILURE;
314    }
315
316    kret = krb5_auth_con_init (context, &ctx->deleg_auth_context);
317    if (kret) {
318	*minor_status = kret;
319	krb5_auth_con_free(context, ctx->auth_context);
320	HEIMDAL_MUTEX_destroy(&ctx->ctx_id_mutex);
321	return GSS_S_FAILURE;
322    }
323
324    kret = set_addresses(context, ctx->auth_context, input_chan_bindings);
325    if (kret) {
326	*minor_status = kret;
327
328	krb5_auth_con_free(context, ctx->auth_context);
329	krb5_auth_con_free(context, ctx->deleg_auth_context);
330
331	HEIMDAL_MUTEX_destroy(&ctx->ctx_id_mutex);
332
333	return GSS_S_BAD_BINDINGS;
334    }
335
336    kret = set_addresses(context, ctx->deleg_auth_context, input_chan_bindings);
337    if (kret) {
338	*minor_status = kret;
339
340	krb5_auth_con_free(context, ctx->auth_context);
341	krb5_auth_con_free(context, ctx->deleg_auth_context);
342
343	HEIMDAL_MUTEX_destroy(&ctx->ctx_id_mutex);
344
345	return GSS_S_BAD_BINDINGS;
346    }
347
348    /*
349     * We need a sequence number
350     */
351
352    krb5_auth_con_addflags(context, ctx->auth_context,
353			   KRB5_AUTH_CONTEXT_DO_SEQUENCE, NULL);
354
355    /*
356     * We need a sequence number
357     */
358
359    krb5_auth_con_addflags(context,
360			   ctx->deleg_auth_context,
361			   KRB5_AUTH_CONTEXT_DO_SEQUENCE,
362			   NULL);
363
364    *context_handle = (gss_ctx_id_t)ctx;
365
366    return GSS_S_COMPLETE;
367}
368
369/*
370 * On success, set ctx->kcred to a valid ticket
371 */
372
373static OM_uint32
374gsskrb5_get_creds(
375        OM_uint32 * minor_status,
376	krb5_context context,
377	krb5_ccache ccache,
378	gsskrb5_ctx ctx,
379	const gss_name_t target_name,
380	int use_dns,
381	OM_uint32 time_req,
382	OM_uint32 * time_rec)
383{
384    OM_uint32 ret;
385    krb5_error_code kret;
386    krb5_creds this_cred;
387    OM_uint32 lifetime_rec;
388
389    if (ctx->target) {
390	krb5_free_principal(context, ctx->target);
391	ctx->target = NULL;
392    }
393    if (ctx->kcred) {
394	krb5_free_creds(context, ctx->kcred);
395	ctx->kcred = NULL;
396    }
397
398    ret = _gsskrb5_canon_name(minor_status, context, use_dns,
399			      ctx->source, target_name, &ctx->target);
400    if (ret)
401	return ret;
402
403    if (_krb5_have_debug(context, 1)) {
404	char *str;
405	ret = krb5_unparse_name(context, ctx->target, &str);
406	if (ret == 0) {
407	    _gss_mg_log(1, "gss-krb5: ISC server %s %s", str,
408			use_dns ? "dns" : "referals");
409	    krb5_xfree(str);
410	}
411    }
412
413    memset(&this_cred, 0, sizeof(this_cred));
414    this_cred.client = ctx->source;
415    this_cred.server = ctx->target;
416
417    if (time_req && time_req != GSS_C_INDEFINITE) {
418	krb5_timestamp ts;
419
420	krb5_timeofday (context, &ts);
421	this_cred.times.endtime = ts + time_req;
422    }
423
424    kret = krb5_get_credentials(context,
425				KRB5_TC_MATCH_REFERRAL,
426				ccache,
427				&this_cred,
428				&ctx->kcred);
429    if (kret) {
430	_gss_mg_log(1, "gss-krb5: ISC get cred failed with %d %s", kret,
431		    use_dns ? "dns" : "referals");
432	*minor_status = kret;
433	return GSS_S_FAILURE;
434    }
435
436    if (_krb5_have_debug(context, 1)) {
437	char *str;
438	ret = krb5_unparse_name(context, ctx->kcred->server, &str);
439	if (ret == 0) {
440	    _gss_mg_log(1, "gss-krb5: ISC will use %s", str);
441	    krb5_xfree(str);
442	}
443    }
444
445    ctx->endtime = ctx->kcred->times.endtime;
446
447    ret = _gsskrb5_lifetime_left(minor_status, context,
448				 ctx->endtime, &lifetime_rec);
449    if (ret) return ret;
450
451    if (lifetime_rec == 0) {
452	_gss_mg_log(1, "gss-krb5: credentials expired");
453	*minor_status = 0;
454	return GSS_S_CONTEXT_EXPIRED;
455    }
456
457    if (time_rec) *time_rec = lifetime_rec;
458
459    return GSS_S_COMPLETE;
460}
461
462static OM_uint32
463initiator_ready(OM_uint32 * minor_status,
464		gsskrb5_ctx ctx,
465		krb5_context context,
466		OM_uint32 *ret_flags)
467{
468    OM_uint32 ret;
469    int32_t seq_number;
470    int is_cfx = 0;
471
472    krb5_free_creds(context, ctx->kcred);
473    ctx->kcred = NULL;
474
475    if (ctx->more_flags & CLOSE_CCACHE)
476	krb5_cc_close(context, ctx->ccache);
477    ctx->ccache = NULL;
478
479    krb5_auth_con_getremoteseqnumber (context, ctx->auth_context, &seq_number);
480
481    _gsskrb5i_is_cfx(context, ctx, 0);
482    is_cfx = (ctx->more_flags & IS_CFX);
483
484    ret = _gssapi_msg_order_create(minor_status,
485				   &ctx->gk5c.order,
486				   _gssapi_msg_order_f(ctx->flags),
487				   seq_number, 0, is_cfx);
488    if (ret) return ret;
489
490    ctx->initiator_state = step_completed;
491    ctx->more_flags	|= OPEN;
492
493    if (ret_flags)
494	*ret_flags = ctx->flags;
495
496    return GSS_S_COMPLETE;
497}
498
499/*
500 * handle delegated creds in init-sec-context
501 */
502
503static void
504do_delegation (krb5_context context,
505	       krb5_auth_context ac,
506	       krb5_ccache ccache,
507	       krb5_creds *cred,
508	       krb5_const_principal name,
509	       krb5_data *fwd_data,
510	       uint32_t flagmask,
511	       uint32_t *flags)
512{
513    krb5_creds creds;
514    KDCOptions fwd_flags;
515    krb5_error_code kret;
516
517    memset (&creds, 0, sizeof(creds));
518    krb5_data_zero (fwd_data);
519
520    kret = krb5_cc_get_principal(context, ccache, &creds.client);
521    if (kret)
522	goto out;
523
524    kret = krb5_make_principal(context,
525			       &creds.server,
526			       creds.client->realm,
527			       KRB5_TGS_NAME,
528			       creds.client->realm,
529			       NULL);
530    if (kret)
531	goto out;
532
533    creds.times.endtime = 0;
534
535    memset(&fwd_flags, 0, sizeof(fwd_flags));
536    fwd_flags.forwarded = 1;
537    fwd_flags.forwardable = 1;
538
539    if (name->name.name_string.len < 2) {
540	krb5_set_error_message(context, GSS_KRB5_S_G_BAD_USAGE,
541			       "ISC: only support forwarding to services");
542	kret = GSS_KRB5_S_G_BAD_USAGE;
543	goto out;
544    }
545
546    kret = krb5_get_forwarded_creds(context,
547				    ac,
548				    ccache,
549				    KDCOptions2int(fwd_flags),
550				    name->name.name_string.val[1],
551				    &creds,
552				    fwd_data);
553    if (kret)
554	goto out;
555
556 out:
557    _gss_mg_log(1, "gss-krb5: delegation %s -> %s",
558		(flagmask & GSS_C_DELEG_POLICY_FLAG) ?
559		"ok-as-delegate" : "delegate",
560		fwd_data->length ? "yes" : "no");
561
562    if (kret)
563	*flags &= ~flagmask;
564    else
565	*flags |= flagmask;
566
567    if (creds.client)
568	krb5_free_principal(context, creds.client);
569    if (creds.server)
570	krb5_free_principal(context, creds.server);
571}
572
573
574static OM_uint32
575setup_icc(krb5_context context,
576	  gsskrb5_ctx ctx,
577	  krb5_principal client)
578{
579    krb5_error_code ret;
580
581    heim_assert(ctx->gic_opt == NULL, "icc already setup");
582
583    _gss_mg_log(1, "gss-iakerb: setup_icc: cert: %s passwd: %s",
584		ctx->cert ? "yes" : "no",
585		ctx->password ? "yes" : "no");
586
587
588    ret = krb5_get_init_creds_opt_alloc(context, &ctx->gic_opt);
589    if (ret)
590	return ret;
591
592    krb5_get_init_creds_opt_set_canonicalize(context, ctx->gic_opt, TRUE);
593
594#ifdef PKINIT
595    if (ctx->cert) {
596	char *cert_pool[2] = { "KEYCHAIN:", NULL };
597
598	ret = krb5_get_init_creds_opt_set_pkinit(context, ctx->gic_opt, client,
599						 NULL, "KEYCHAIN:",
600						 cert_pool, NULL, 8,
601						 NULL, NULL, NULL);
602	if (ret)
603	    return ret;
604    }
605#endif
606
607    ret = krb5_init_creds_init(context, client, NULL, NULL, 0,
608			       ctx->gic_opt, &ctx->asctx);
609    if (ret)
610	return ret;
611
612
613#ifndef PKINIT
614    heim_assert(ctx->password, "no password");
615#else
616    heim_assert(ctx->password || ctx->cert, "no password nor cert ?");
617
618    if (ctx->cert) {
619	ret = krb5_init_creds_set_pkinit_client_cert(context, ctx->asctx, ctx->cert);
620	if (ret)
621	    return ret;
622    }
623#endif
624    if (ctx->password) {
625	ret = krb5_init_creds_set_password(context, ctx->asctx, ctx->password);
626	if (ret)
627	    return ret;
628    }
629
630    return 0;
631}
632
633#ifdef PKINIT
634
635static OM_uint32
636init_pku2u_auth(OM_uint32 * minor_status,
637		gsskrb5_cred cred,
638		gsskrb5_ctx ctx,
639		krb5_context context,
640		gss_name_t name,
641		const gss_OID mech_type,
642		OM_uint32 req_flags,
643		OM_uint32 time_req,
644		gss_channel_bindings_t channel_bindings,
645		const gss_buffer_t input_token,
646		gss_buffer_t output_token,
647		OM_uint32 * ret_flags,
648		OM_uint32 * time_rec)
649{
650    OM_uint32 maj_stat = GSS_S_FAILURE;
651    krb5_error_code ret;
652    krb5_principal client = NULL;
653
654    *minor_status = 0;
655
656    ctx->messages = krb5_storage_emem();
657    if (ctx->messages == NULL) {
658	*minor_status = ENOMEM;
659	return GSS_S_FAILURE;
660    }
661
662    /*
663     * XXX Search for existing credentials here before going of and
664     * doing PK-U2U
665     */
666
667
668    /*
669     * Pick upp the certificate from gsskrb5_cred.
670     * XXX fix better mapping for client principal.
671     */
672
673    if (cred == NULL) {
674	gss_cred_id_t temp;
675	gsskrb5_cred cred2;
676
677	maj_stat = _gsspku2u_acquire_cred(minor_status, GSS_C_NO_NAME,
678					  GSS_C_INDEFINITE, GSS_C_NO_OID_SET,
679					  GSS_C_INITIATE, &temp, NULL, NULL);
680	if (maj_stat)
681	    return maj_stat;
682
683	cred2 = (gsskrb5_cred)temp;
684
685	ret = krb5_copy_principal(context, cred2->principal, &client);
686	if (ret) {
687	    _gsskrb5_release_cred(minor_status, &temp);
688	    *minor_status = ret;
689	    return GSS_S_FAILURE;
690	}
691
692	ctx->cert = hx509_cert_ref(cred2->cert);
693	_gsskrb5_release_cred(minor_status, &temp);
694
695	maj_stat = GSS_S_FAILURE;
696    } else if (cred->cert) {
697	ret = krb5_copy_principal(context, cred->principal, &client);
698	if (ret) {
699	    *minor_status = ret;
700	    return GSS_S_FAILURE;
701	}
702	ctx->cert = hx509_cert_ref(cred->cert);
703
704    } else {
705	*minor_status = EINVAL;
706	return GSS_S_FAILURE;
707    }
708
709    ret = setup_icc(context, ctx, client);
710    if (ret) {
711	*minor_status = ret;
712	goto out;
713    }
714
715    /* XXX should be based on target_name */
716    ret = krb5_init_creds_set_service(context, ctx->asctx, "WELLKNOWN/NULL");
717    if (ret) {
718	*minor_status = ret;
719	goto out;
720    }
721
722    if (krb5_principal_is_null(context, client)) {
723	InitiatorNameAssertion na;
724	hx509_name subject;
725	krb5_data data;
726	size_t size = 0;
727	Name n;
728
729	memset(&na, 0, sizeof(na));
730	memset(&n, 0, sizeof(n));
731
732	na.initiatorName = calloc(1, sizeof(*na.initiatorName));
733	if (na.initiatorName == NULL) {
734	    *minor_status = ENOMEM;
735	    goto out;
736	}
737
738	ret = hx509_cert_get_subject(ctx->cert, &subject);
739	if (ret) {
740	    free_InitiatorNameAssertion(&na);
741	    *minor_status = ret;
742	    goto out;
743	}
744
745	ret = hx509_name_to_Name(subject, &n);
746	hx509_name_free(&subject);
747	if (ret) {
748	    free_InitiatorNameAssertion(&na);
749	    *minor_status = ret;
750	    goto out;
751	}
752
753	na.initiatorName->element =
754	    choice_InitiatorName_nameNotInCert;
755	na.initiatorName->u.nameNotInCert.element =
756	    choice_GeneralName_directoryName;
757	na.initiatorName->u.nameNotInCert.u.directoryName.element =
758	    choice_GeneralName_directoryName_rdnSequence;
759	na.initiatorName->u.nameNotInCert.u.directoryName.u.rdnSequence.len =
760	    n.u.rdnSequence.len;
761	na.initiatorName->u.nameNotInCert.u.directoryName.u.rdnSequence.val =
762	    n.u.rdnSequence.val;
763
764	ASN1_MALLOC_ENCODE(InitiatorNameAssertion, data.data, data.length,
765			   &na, &size, ret);
766	free_InitiatorNameAssertion(&na);
767	if (ret)
768	    goto out;
769	if (size != data.length)
770	    krb5_abortx(context, "internal error in ASN.1 encoder");
771
772	ret = _krb5_init_creds_set_pku2u(context, ctx->asctx, &data);
773	krb5_data_free(&data);
774    } else {
775	ret = _krb5_init_creds_set_pku2u(context, ctx->asctx, NULL);
776    }
777
778    if (ret) {
779	*minor_status = ret;
780	goto out;
781    }
782
783    maj_stat = GSS_S_COMPLETE;
784    ctx->initiator_state = step_pku2u_auth;
785 out:
786    if (client)
787	krb5_free_principal(context, client);
788
789    return maj_stat;
790}
791
792static OM_uint32
793step_pku2u_auth(OM_uint32 * minor_status,
794		gsskrb5_cred cred,
795		gsskrb5_ctx ctx,
796		krb5_context context,
797		gss_name_t name,
798		const gss_OID mech_type,
799		OM_uint32 req_flags,
800		OM_uint32 time_req,
801		gss_channel_bindings_t channel_bindings,
802		const gss_buffer_t input_token,
803		gss_buffer_t output_token,
804		OM_uint32 * ret_flags,
805		OM_uint32 * time_rec)
806{
807    OM_uint32 maj_stat;
808    unsigned int flags = 0;
809    krb5_error_code ret;
810    krb5_data in, out;
811
812    krb5_data_zero(&out);
813
814    if (input_token && input_token->length) {
815
816	krb5_storage_write(ctx->messages,
817			   input_token->value,
818			   input_token->length);
819
820	maj_stat = _gsskrb5_decapsulate (minor_status,
821					 input_token,
822					 &in,
823					 "\x06\x00",
824					 ctx->mech);
825	if (maj_stat)
826	    return maj_stat;
827    } else
828	krb5_data_zero(&in);
829
830    maj_stat = GSS_S_FAILURE;
831
832    ret = krb5_init_creds_step(context, ctx->asctx,
833			       &in, &out, NULL, NULL, &flags);
834    if (ret)
835	goto out;
836
837    if ((flags & 1) == 0) {
838
839	ctx->kcred = calloc(1, sizeof(*ctx->kcred));
840	if (ctx->kcred == NULL) {
841	    ret = ENOMEM;
842	    goto out;
843	}
844
845	/*
846	 * Pull out credential and store in ctx->kcred and just use
847	 * that as the credential in AP-REQ.
848	 */
849
850	ret = krb5_init_creds_get_creds(context, ctx->asctx, ctx->kcred);
851	krb5_init_creds_free(context, ctx->asctx);
852	ctx->asctx = NULL;
853	if (ret)
854	    goto out;
855
856	ret = krb5_copy_principal(context, ctx->kcred->client, &ctx->source);
857	if (ret)
858	    goto out;
859	ret = krb5_copy_principal(context, ctx->kcred->server, &ctx->target);
860	if (ret)
861	    goto out;
862
863	/* XXX store credential in credential cache */
864#if 0
865	{
866	    krb5_ccache id = NULL;
867
868	    ret = krb5_cc_new_unique(context, "API", NULL, &id);
869	    if (ret == 0)
870		ret = krb5_cc_initialize(context, id, ctx->kcred->client);
871	    if (ret == 0)
872		krb5_cc_store_cred(context, id, ctx->kcred);
873	    if (id)
874		krb5_cc_close(id);
875	}
876#endif
877
878	maj_stat = GSS_S_COMPLETE;
879	ctx->initiator_state = step_setup_keys;
880
881    } else {
882        ret = _gsskrb5_encapsulate (minor_status, &out, output_token,
883				    (u_char *)"\x05\x00", ctx->mech);
884	if (ret)
885	    goto out;
886
887	krb5_storage_write(ctx->messages,
888			   output_token->value,
889			   output_token->length);
890
891	maj_stat = GSS_S_CONTINUE_NEEDED;
892    }
893
894 out:
895    *minor_status = ret;
896
897    return maj_stat;
898}
899
900#endif /* PKINIT */
901
902
903/*
904 * IAKERB
905 */
906
907OM_uint32
908_gsskrb5_iakerb_parse_header(OM_uint32 *minor_status,
909			     krb5_context context,
910			     gsskrb5_ctx ctx,
911			     const gss_buffer_t input_token,
912			     krb5_data *data)
913{
914    krb5_error_code ret;
915    OM_uint32 maj_stat;
916    uint8_t type[2];
917    size_t size;
918
919    maj_stat = _gssapi_decapsulate(minor_status, input_token, type, data, GSS_IAKERB_MECHANISM);
920    if (maj_stat)
921	return maj_stat;
922
923    if (memcmp(type, "\x05\x01", 2) == 0) {
924	IAKERB_HEADER header;
925
926	ret = decode_IAKERB_HEADER(data->data, data->length, &header, &size);
927	if (ret) {
928	    *minor_status = ret;
929	    return GSS_S_FAILURE;
930	}
931
932	heim_assert(data->length >= size, "internal asn1 decoder failure");
933
934	data->data = ((uint8_t *)data->data) + size;
935	data->length -= size;
936
937	if (header.cookie) {
938	    if (ctx->cookie)
939		krb5_free_data(context, ctx->cookie);
940	    (void)krb5_copy_data(context, header.cookie, &ctx->cookie);
941	}
942
943	if (ctx->iakerbrealm)
944	    free(ctx->iakerbrealm);
945	ctx->iakerbrealm = strdup(header.target_realm);
946
947	free_IAKERB_HEADER(&header);
948
949	return GSS_S_COMPLETE;
950
951    } else if (memcmp(type, "\x03\x00", 2) == 0) {
952	/* XXX parse KRB-ERROR and set appropriate minor code */
953	*minor_status = 0;
954	return GSS_S_FAILURE;
955    } else {
956	*minor_status = 0;
957	return GSS_S_DEFECTIVE_TOKEN;
958    }
959}
960
961OM_uint32
962_gsskrb5_iakerb_make_header(OM_uint32 *minor_status,
963			    krb5_context context,
964			    gsskrb5_ctx ctx,
965			    krb5_realm realm,
966			    krb5_data *kdata,
967			    gss_buffer_t output_token)
968{
969    IAKERB_HEADER header;
970    unsigned char *data;
971    size_t length, size = 0;
972    OM_uint32 maj_stat;
973    krb5_data iadata;
974    int ret;
975
976    header.target_realm = realm;
977    header.cookie = ctx->cookie;
978
979    ASN1_MALLOC_ENCODE(IAKERB_HEADER, data, length, &header, &size, ret);
980    if (ret) {
981	*minor_status = ret;
982	return GSS_S_FAILURE;
983    }
984    heim_assert(length == size, "internal asn1 encoder error");
985
986    if (ctx->cookie) {
987	krb5_free_data(context, ctx->cookie);
988	ctx->cookie = NULL;
989    }
990
991    iadata.length = length + kdata->length;
992    iadata.data = malloc(iadata.length);
993    if (iadata.data == NULL) {
994	free(data);
995	*minor_status = ENOMEM;
996	return GSS_S_FAILURE;
997    }
998    memcpy(iadata.data, data, length);
999    memcpy(((uint8_t *)iadata.data) + length, kdata->data, kdata->length);
1000    free(data);
1001
1002    maj_stat = _gsskrb5_encapsulate(minor_status, &iadata, output_token,
1003				    (u_char *)"\x05\x01", ctx->mech);
1004    free(iadata.data);
1005
1006    return maj_stat;
1007}
1008
1009static OM_uint32
1010init_iakerb_auth(OM_uint32 * minor_status,
1011		 gsskrb5_cred cred,
1012		 gsskrb5_ctx ctx,
1013		 krb5_context context,
1014		 gss_name_t target_name,
1015		 const gss_OID mech_type,
1016		 OM_uint32 req_flags,
1017		 OM_uint32 time_req,
1018		 gss_channel_bindings_t channel_bindings,
1019		 const gss_buffer_t input_token,
1020		 gss_buffer_t output_token,
1021		 OM_uint32 * ret_flags,
1022		 OM_uint32 * time_rec)
1023{
1024    krb5_error_code ret;
1025
1026    ctx->messages = krb5_storage_emem();
1027    if (ctx->messages == NULL) {
1028	*minor_status = ENOMEM;
1029	return GSS_S_FAILURE;
1030    }
1031
1032    /*
1033     * XXX
1034     */
1035
1036    if (cred == NULL)
1037	return GSS_S_FAILURE;
1038
1039    ret = krb5_copy_principal(context, cred->principal, &ctx->source);
1040    if (ret) {
1041	*minor_status = ret;
1042	return GSS_S_FAILURE;
1043    }
1044
1045    /* XXX this kind of sucks, forcing referrals and then fixing up LKDC realm later */
1046    ret = krb5_copy_principal(context, (krb5_const_principal)target_name, &ctx->target);
1047    if (ret) {
1048	*minor_status = ret;
1049	return GSS_S_FAILURE;
1050    }
1051    krb5_principal_set_realm(context, ctx->target, ctx->source->realm);
1052
1053    if (cred->password) {
1054
1055	ctx->password = strdup(cred->password);
1056	if (ctx->password == NULL) {
1057	    *minor_status = ENOMEM;
1058	    return GSS_S_FAILURE;
1059	}
1060
1061#ifdef PKINIT
1062    } else if (cred->cert) {
1063
1064	ctx->cert = heim_retain(cred->cert);
1065#endif
1066    } else if (cred->cred_flags & GSS_CF_IAKERB_RESOLVED) {
1067	/* all work done in auth_tgs */
1068    } else {
1069
1070	*minor_status = EINVAL;
1071	return GSS_S_FAILURE;
1072    }
1073
1074    ctx->ccache = cred->ccache;
1075
1076    /* capture ctx->ccache */
1077    krb5_cc_get_config(context, ctx->ccache, NULL, "FriendlyName", &ctx->friendlyname);
1078    krb5_cc_get_config(context, ctx->ccache, NULL, "lkdc-hostname", &ctx->lkdchostname);
1079
1080    if (cred->cred_flags & GSS_CF_IAKERB_RESOLVED) {
1081	ctx->initiator_state = step_iakerb_auth_tgs;
1082    } else {
1083	ctx->initiator_state = step_iakerb_auth_as;
1084    }
1085
1086    *minor_status = 0;
1087
1088
1089    return GSS_S_COMPLETE;
1090}
1091
1092static OM_uint32
1093step_iakerb_auth_as(OM_uint32 * minor_status,
1094		    gsskrb5_cred cred,
1095		    gsskrb5_ctx ctx,
1096		    krb5_context context,
1097		    gss_name_t name,
1098		    const gss_OID mech_type,
1099		    OM_uint32 req_flags,
1100		    OM_uint32 time_req,
1101		    gss_channel_bindings_t channel_bindings,
1102		    const gss_buffer_t input_token,
1103		    gss_buffer_t output_token,
1104		    OM_uint32 * ret_flags,
1105		    OM_uint32 * time_rec)
1106{
1107    OM_uint32 maj_stat;
1108    krb5_data in, out;
1109    krb5_realm realm = NULL;
1110    krb5_error_code ret;
1111    unsigned int flags = 0;
1112
1113    if (ctx->asctx == NULL) {
1114	/* first packet */
1115
1116	ret = setup_icc(context, ctx, ctx->source);
1117	if (ret) {
1118	    *minor_status = ret;
1119	    return GSS_S_FAILURE;
1120	}
1121
1122	krb5_data_zero(&in);
1123
1124    } else {
1125
1126	krb5_storage_write(ctx->messages,
1127			   input_token->value,
1128			   input_token->length);
1129
1130	maj_stat = _gsskrb5_iakerb_parse_header(minor_status, context, ctx, input_token, &in);
1131	if (maj_stat)
1132	    return maj_stat;
1133    }
1134
1135    ret = krb5_init_creds_step(context, ctx->asctx, &in, &out, NULL, &realm, &flags);
1136    if (ret) {
1137	_gss_mg_log(1, "gss-iakerb: init_creds_step: %d", ret);
1138	_gsskrb5_error_token(minor_status, ctx->mech, context, ret,
1139			     NULL, NULL, output_token);
1140	*minor_status = ret;
1141	return GSS_S_FAILURE;
1142    }
1143
1144    if (flags & KRB5_INIT_CREDS_STEP_FLAG_CONTINUE) {
1145
1146	heim_assert(realm != NULL, "krb5_init_creds_step return data w/o a realm");
1147
1148	maj_stat = _gsskrb5_iakerb_make_header(minor_status, context, ctx, realm, &out, output_token);
1149	if (maj_stat)
1150	    return maj_stat;
1151
1152	krb5_storage_write(ctx->messages,
1153			   output_token->value,
1154			   output_token->length);
1155
1156
1157	return GSS_S_CONTINUE_NEEDED;
1158
1159    } else {
1160	krb5_creds kcred;
1161
1162	memset(&kcred, 0, sizeof(kcred));
1163
1164	_gss_mg_log(1, "gss-iakerb: going to state auth-tgs");
1165
1166	heim_assert(out.length == 0, "output of AS-REQ not 0 when done");
1167
1168	/* store credential in credential cache and lets continue the TGS REQ */
1169	ret = krb5_init_creds_get_creds(context, ctx->asctx, &kcred);
1170	if (ret) {
1171	    *minor_status = ret;
1172	    _gsskrb5_error_token(minor_status, ctx->mech, context, ret,
1173				 NULL, NULL, output_token);
1174	    return GSS_S_FAILURE;
1175	}
1176
1177	//(void)krb5_cc_new_unique(context, "MEMORY", NULL, &ctx->ccache);
1178	(void)krb5_cc_initialize(context, ctx->ccache, kcred.client);
1179	(void)krb5_cc_store_cred(context, ctx->ccache, &kcred);
1180	if (ctx->password) {
1181	    krb5_data pw;
1182	    pw.data = ctx->password;
1183	    pw.length = strlen(ctx->password);
1184	    (void)krb5_cc_set_config(context, ctx->ccache, NULL, "password", &pw);
1185	}
1186#ifdef PKINIT
1187	if (ctx->cert) {
1188	    krb5_data pd;
1189	    ret = hx509_cert_get_persistent(ctx->cert, &pd);
1190	    if (ret) {
1191		*minor_status = ret;
1192		return GSS_S_FAILURE;
1193	    }
1194	    krb5_cc_set_config(context, ctx->ccache, NULL, "certificate-ref", &pd);
1195	    der_free_octet_string(&pd);
1196	}
1197#endif
1198	if (ctx->friendlyname.length)
1199	    krb5_cc_set_config(context, ctx->ccache, NULL, "FriendlyName", &ctx->friendlyname);
1200	if (ctx->lkdchostname.length) {
1201	    krb5_data data = { 1, "1" } ;
1202	    krb5_cc_set_config(context, ctx->ccache, NULL, "lkdc-hostname", &ctx->lkdchostname);
1203	    krb5_cc_set_config(context, ctx->ccache, NULL, "nah-created", &data);
1204	    krb5_cc_set_config(context, ctx->ccache, NULL, "iakerb", &data);
1205	}
1206
1207	/* update source if we got a referrals */
1208	krb5_free_principal(context, ctx->source);
1209	(void)krb5_copy_principal(context, kcred.client, &ctx->source);
1210
1211	krb5_free_cred_contents(context, &kcred);
1212
1213	ctx->initiator_state = step_iakerb_auth_tgs;
1214
1215	return GSS_S_COMPLETE;
1216    }
1217
1218}
1219
1220static OM_uint32
1221step_iakerb_auth_tgs(OM_uint32 * minor_status,
1222		     gsskrb5_cred cred,
1223		     gsskrb5_ctx ctx,
1224		     krb5_context context,
1225		     gss_name_t name,
1226		     const gss_OID mech_type,
1227		     OM_uint32 req_flags,
1228		     OM_uint32 time_req,
1229		     gss_channel_bindings_t channel_bindings,
1230		     const gss_buffer_t input_token,
1231		     gss_buffer_t output_token,
1232		     OM_uint32 * ret_flags,
1233		     OM_uint32 * time_rec)
1234{
1235    krb5_realm realm = NULL;
1236    OM_uint32 maj_stat;
1237    krb5_data in, out;
1238    krb5_error_code ret;
1239    unsigned int flags = 0;
1240
1241    *minor_status = 0;
1242    krb5_data_zero(&out);
1243
1244    if (ctx->tgsctx == NULL) {
1245	krb5_creds incred;
1246
1247	memset(&incred, 0, sizeof(incred));
1248
1249	incred.client = ctx->source;
1250	incred.server = ctx->target;
1251
1252	ret = krb5_tkt_creds_init(context, ctx->ccache, &incred, 0, &ctx->tgsctx);
1253	if (ret) {
1254	    *minor_status = ret;
1255	    _gsskrb5_error_token(minor_status, ctx->mech, context, ret,
1256				 NULL, NULL, output_token);
1257	    return GSS_S_FAILURE;
1258	}
1259
1260    } else {
1261
1262	krb5_storage_write(ctx->messages,
1263			   input_token->value,
1264			   input_token->length);
1265
1266	maj_stat = _gsskrb5_iakerb_parse_header(minor_status, context, ctx, input_token, &in);
1267	if (maj_stat)
1268	    return maj_stat;
1269    }
1270
1271    ret = krb5_tkt_creds_step(context, ctx->tgsctx, &in, &out, &realm, &flags);
1272    if (ret) {
1273	_gss_mg_log(1, "gss-iakerb: tkt_creds_step: %d", ret);
1274	_gsskrb5_error_token(minor_status, ctx->mech, context, ret,
1275			     NULL, NULL, output_token);
1276	*minor_status = ret;
1277	return GSS_S_FAILURE;
1278    }
1279
1280    if (flags & KRB5_TKT_STATE_CONTINUE) {
1281
1282	maj_stat = _gsskrb5_iakerb_make_header(minor_status, context, ctx, realm, &out, output_token);
1283	krb5_data_free(&out);
1284	if (maj_stat != GSS_S_COMPLETE)
1285	    return maj_stat;
1286
1287	krb5_storage_write(ctx->messages,
1288			   output_token->value,
1289			   output_token->length);
1290
1291	return GSS_S_CONTINUE_NEEDED;
1292
1293    } else {
1294	heim_assert(out.length == 0, "output data is not zero");
1295
1296	_gss_mg_log(1, "gss-iakerb: going to state setup-keys");
1297
1298	ret = krb5_tkt_creds_get_creds(context, ctx->tgsctx, &ctx->kcred);
1299	if (ret) {
1300	    _gss_mg_log(1, "gss-iakerb: tkt_get_creds: %d", ret);
1301	    _gsskrb5_error_token(minor_status, ctx->mech, context, ret,
1302				 NULL, NULL, output_token);
1303	    *minor_status = ret;
1304	    return GSS_S_FAILURE;
1305	}
1306
1307	ctx->endtime = ctx->kcred->times.endtime;
1308
1309	ctx->initiator_state = step_setup_keys;
1310
1311	return GSS_S_COMPLETE;
1312    }
1313}
1314
1315/*
1316 * KRB5
1317 */
1318
1319static OM_uint32
1320init_krb5_auth(OM_uint32 * minor_status,
1321	       gsskrb5_cred cred,
1322	       gsskrb5_ctx ctx,
1323	       krb5_context context,
1324	       gss_name_t name,
1325	       const gss_OID mech_type,
1326	       OM_uint32 req_flags,
1327	       OM_uint32 time_req,
1328	       const gss_channel_bindings_t input_chan_bindings,
1329	       const gss_buffer_t input_token,
1330	       gss_buffer_t output_token,
1331	       OM_uint32 * ret_flags,
1332	       OM_uint32 * time_rec)
1333{
1334    OM_uint32 ret = GSS_S_FAILURE;
1335    krb5_error_code kret;
1336    krb5_data outbuf;
1337    krb5_data fwd_data;
1338    OM_uint32 lifetime_rec;
1339    int allow_dns = 1;
1340
1341    krb5_data_zero(&outbuf);
1342    krb5_data_zero(&fwd_data);
1343
1344    *minor_status = 0;
1345
1346    /*
1347     * If ctx->ccache is set, we already have a cache and source
1348     */
1349
1350    if (ctx->ccache == NULL) {
1351
1352	if (cred == NULL) {
1353	    kret = krb5_cc_default (context, &ctx->ccache);
1354	    if (kret) {
1355		*minor_status = kret;
1356		ret = GSS_S_FAILURE;
1357		goto failure;
1358	    }
1359	    ctx->more_flags |= CLOSE_CCACHE;
1360	} else
1361	    ctx->ccache = cred->ccache;
1362
1363	kret = krb5_cc_get_principal (context, ctx->ccache, &ctx->source);
1364	if (kret) {
1365	    *minor_status = kret;
1366	    ret = GSS_S_FAILURE;
1367	    goto failure;
1368	}
1369    }
1370
1371    /*
1372     * This is hideous glue for (NFS) clients that wants to limit the
1373     * available enctypes to what it can support (encryption in
1374     * kernel). If there is no enctypes selected for this credential,
1375     * reset it to the default set of enctypes.
1376     */
1377    {
1378	krb5_enctype *enctypes = NULL;
1379
1380	if (cred && cred->enctypes)
1381	    enctypes = cred->enctypes;
1382	krb5_set_default_in_tkt_etypes(context, enctypes);
1383    }
1384
1385    /* canon name if needed for client + target realm */
1386    kret = krb5_cc_get_config(context, ctx->ccache, NULL,
1387			      "realm-config", &outbuf);
1388    if (kret == 0) {
1389	/* XXX 2 is no server canon */
1390	if (outbuf.length < 1 || ((((unsigned char *)outbuf.data)[0]) & 2))
1391	    allow_dns = 0;
1392	krb5_data_free(&outbuf);
1393    }
1394
1395    if (_gss_mg_log_level(1)) {
1396	char *str, *fullname;
1397	ret = krb5_unparse_name(context, ctx->source, &str);
1398	if (ret)
1399	    goto failure;
1400
1401	ret = krb5_cc_get_full_name(context, ctx->ccache, &fullname);
1402	if (ret) {
1403	    krb5_xfree(str);
1404	    goto failure;
1405	}
1406	_gss_mg_log(1, "gss-krb5: ISC client: %s cache: %s", str, fullname);
1407
1408	krb5_xfree(str);
1409	krb5_xfree(fullname);
1410    }
1411
1412    /*
1413     * First we try w/o dns, hope that the KDC have register alias
1414     * (and referrals if cross realm) for this principal. If that
1415     * fails and if we are allowed to using this realm try again with
1416     * DNS canonicalizion.
1417     */
1418    ret = gsskrb5_get_creds(minor_status, context, ctx->ccache,
1419			    ctx, name, 0, time_req,
1420			    time_rec);
1421    if (ret && allow_dns)
1422	ret = gsskrb5_get_creds(minor_status, context, ctx->ccache,
1423				ctx, name, 1, time_req,
1424				time_rec);
1425    if (ret)
1426	goto failure;
1427
1428    ctx->endtime = ctx->kcred->times.endtime;
1429
1430    ret = _gss_DES3_get_mic_compat(minor_status, ctx, context);
1431    if (ret)
1432	goto failure;
1433
1434    ret = _gsskrb5_lifetime_left(minor_status,
1435				 context,
1436				 ctx->endtime,
1437				 &lifetime_rec);
1438    if (ret)
1439	goto failure;
1440
1441    if (lifetime_rec == 0) {
1442	_gss_mg_log(1, "gss-krb5: credentials expired");
1443	*minor_status = 0;
1444	ret = GSS_S_CONTEXT_EXPIRED;
1445	goto failure;
1446    }
1447
1448    ctx->initiator_state = step_setup_keys;
1449
1450    return GSS_S_COMPLETE;
1451
1452failure:
1453
1454#ifdef __APPLE__
1455    if (GSS_ERROR(ret))
1456	update_neg_cache(ret, *minor_status, context,
1457			 ctx->mech, cred, (krb5_principal)name);
1458#endif
1459
1460    return ret;
1461}
1462
1463/*
1464 * Common part of iakerb, pku2u and krb5
1465 */
1466
1467static OM_uint32
1468step_setup_keys(OM_uint32 * minor_status,
1469		gsskrb5_cred cred,
1470		gsskrb5_ctx ctx,
1471		krb5_context context,
1472		gss_name_t name,
1473		const gss_OID mech_type,
1474		OM_uint32 req_flags,
1475		OM_uint32 time_req,
1476		const gss_channel_bindings_t input_chan_bindings,
1477		const gss_buffer_t input_token,
1478		gss_buffer_t output_token,
1479		OM_uint32 * ret_flags,
1480		OM_uint32 * time_rec)
1481{
1482    krb5_error_code kret;
1483
1484    heim_assert(ctx->kcred != NULL, "gsskrb5 context is missing kerberos credential");
1485
1486    krb5_auth_con_setkey(context, ctx->auth_context, &ctx->kcred->session);
1487    krb5_auth_con_setkey(context, ctx->deleg_auth_context, &ctx->kcred->session);
1488
1489    kret = krb5_auth_con_generatelocalsubkey(context,
1490					     ctx->auth_context,
1491					     &ctx->kcred->session);
1492    if (kret) {
1493	*minor_status = kret;
1494	return GSS_S_FAILURE;
1495    }
1496
1497    ctx->initiator_state = init_auth_step;
1498
1499    return GSS_S_COMPLETE;
1500}
1501
1502
1503static OM_uint32
1504init_auth_step(OM_uint32 * minor_status,
1505	       gsskrb5_cred cred,
1506	       gsskrb5_ctx ctx,
1507	       krb5_context context,
1508	       gss_name_t name,
1509	       const gss_OID mech_type,
1510	       OM_uint32 req_flags,
1511	       OM_uint32 time_req,
1512	       const gss_channel_bindings_t input_chan_bindings,
1513	       const gss_buffer_t input_token,
1514	       gss_buffer_t output_token,
1515	       OM_uint32 * ret_flags,
1516	       OM_uint32 * time_rec)
1517{
1518    krb5_crypto crypto = NULL;
1519    OM_uint32 ret = GSS_S_FAILURE;
1520    krb5_error_code kret;
1521    krb5_flags ap_options;
1522    krb5_data outbuf;
1523    uint32_t flags;
1524    krb5_data authenticator;
1525    Checksum cksum;
1526    krb5_enctype enctype;
1527
1528    krb5_data fwd_data, timedata, pkt_cksum;
1529    int32_t offset = 0, oldoffset = 0;
1530    uint32_t flagmask;
1531
1532    krb5_data_zero(&outbuf);
1533    krb5_data_zero(&fwd_data);
1534    krb5_data_zero(&pkt_cksum);
1535    memset(&cksum, 0, sizeof(cksum));
1536
1537    *minor_status = 0;
1538
1539    /*
1540     * If the credential doesn't have ok-as-delegate, check if there
1541     * is a realm setting and use that.
1542     */
1543    if (!ctx->kcred->flags.b.ok_as_delegate && ctx->ccache) {
1544	krb5_data data;
1545
1546	ret = krb5_cc_get_config(context, ctx->ccache, NULL,
1547				 "realm-config", &data);
1548	if (ret == 0) {
1549	    /* XXX 1 is use ok-as-delegate */
1550	    if (data.length < 1 || ((((unsigned char *)data.data)[0]) & 1) == 0)
1551		req_flags &= ~(GSS_C_DELEG_FLAG|GSS_C_DELEG_POLICY_FLAG);
1552	    krb5_data_free(&data);
1553	}
1554    }
1555
1556    flagmask = 0;
1557
1558    /* if we used GSS_C_DELEG_POLICY_FLAG, trust KDC */
1559    if ((req_flags & GSS_C_DELEG_POLICY_FLAG)
1560	&& ctx->kcred->flags.b.ok_as_delegate)
1561	flagmask |= GSS_C_DELEG_FLAG | GSS_C_DELEG_POLICY_FLAG;
1562    /* if there still is a GSS_C_DELEG_FLAG, use that */
1563    if (req_flags & GSS_C_DELEG_FLAG)
1564	flagmask |= GSS_C_DELEG_FLAG;
1565
1566
1567    flags = 0;
1568    ap_options = 0;
1569    if ((flagmask & GSS_C_DELEG_FLAG) && ctx->ccache) {
1570	do_delegation (context,
1571		       ctx->deleg_auth_context,
1572		       ctx->ccache, ctx->kcred, ctx->target,
1573		       &fwd_data, flagmask, &flags);
1574    }
1575
1576    if (req_flags & GSS_C_MUTUAL_FLAG) {
1577	flags |= GSS_C_MUTUAL_FLAG;
1578	ap_options |= AP_OPTS_MUTUAL_REQUIRED;
1579    }
1580
1581    if (req_flags & GSS_C_REPLAY_FLAG)
1582	flags |= GSS_C_REPLAY_FLAG;
1583    if (req_flags & GSS_C_SEQUENCE_FLAG)
1584	flags |= GSS_C_SEQUENCE_FLAG;
1585#if 0
1586    if (req_flags & GSS_C_ANON_FLAG)
1587	;                               /* XXX */
1588#endif
1589    if (req_flags & GSS_C_DCE_STYLE) {
1590	/* GSS_C_DCE_STYLE implies GSS_C_MUTUAL_FLAG */
1591	flags |= GSS_C_DCE_STYLE | GSS_C_MUTUAL_FLAG;
1592	ap_options |= AP_OPTS_MUTUAL_REQUIRED;
1593    }
1594    if (req_flags & GSS_C_IDENTIFY_FLAG)
1595	flags |= GSS_C_IDENTIFY_FLAG;
1596    if (req_flags & GSS_C_EXTENDED_ERROR_FLAG)
1597	flags |= GSS_C_EXTENDED_ERROR_FLAG;
1598
1599    if (req_flags & GSS_C_CONF_FLAG) {
1600	flags |= GSS_C_CONF_FLAG;
1601    }
1602    if (req_flags & GSS_C_INTEG_FLAG) {
1603	flags |= GSS_C_INTEG_FLAG;
1604    }
1605    if (cred == NULL || !(cred->cred_flags & GSS_CF_NO_CI_FLAGS)) {
1606	flags |= GSS_C_CONF_FLAG;
1607	flags |= GSS_C_INTEG_FLAG;
1608    }
1609    flags |= GSS_C_TRANS_FLAG;
1610
1611    ctx->flags = flags;
1612    ctx->more_flags |= LOCAL;
1613
1614    ret = krb5_crypto_init(context, ctx->auth_context->local_subkey,
1615			   0, &crypto);
1616    if (ret) {
1617	goto failure;
1618    }
1619
1620    if (ctx->messages) {
1621	GSS_KRB5_FINISHED pkt;
1622	krb5_data pkts;
1623	size_t size = 0;
1624
1625	memset(&pkt, 0, sizeof(pkt));
1626
1627	ret = krb5_storage_to_data(ctx->messages, &pkts);
1628	krb5_storage_free(ctx->messages);
1629	ctx->messages = NULL;
1630	if (ret)
1631	    goto failure;
1632
1633	ret = krb5_create_checksum(context,
1634				   crypto,
1635				   KRB5_KU_FINISHED,
1636				   0,
1637				   pkts.data,
1638				   pkts.length,
1639				   &pkt.gss_mic);
1640	krb5_data_free(&pkts);
1641	if (ret)
1642	    goto failure;
1643
1644	ASN1_MALLOC_ENCODE(GSS_KRB5_FINISHED,
1645			   pkt_cksum.data, pkt_cksum.length,
1646			   &pkt, &size, ret);
1647	free_GSS_KRB5_FINISHED(&pkt);
1648	if (ret)
1649	    goto failure;
1650	if (pkt_cksum.length != size)
1651	    krb5_abortx(context, "internal error in ASN.1 encoder");
1652    }
1653
1654    ret = _gsskrb5_create_8003_checksum (minor_status,
1655					 context,
1656					 crypto,
1657					 input_chan_bindings,
1658					 flags,
1659					 &fwd_data,
1660					 &pkt_cksum,
1661					 &cksum);
1662    if (ret)
1663	goto failure;
1664
1665    enctype = ctx->auth_context->keyblock->keytype;
1666
1667    if (ctx->ccache) {
1668
1669	ret = krb5_cc_get_config(context, ctx->ccache, ctx->target,
1670				 "time-offset", &timedata);
1671	if (ret == 0) {
1672	    if (timedata.length == 4) {
1673		const u_char *p = timedata.data;
1674		offset = (p[0] <<24) | (p[1] << 16) | (p[2] << 8) | (p[3] << 0);
1675	    }
1676	    krb5_data_free(&timedata);
1677	}
1678
1679	if (offset) {
1680	    krb5_get_kdc_sec_offset(context, &oldoffset, NULL);
1681	    krb5_set_kdc_sec_offset(context, offset, -1);
1682	}
1683    }
1684
1685    kret = _krb5_build_authenticator(context,
1686				     ctx->auth_context,
1687				     enctype,
1688				     ctx->kcred,
1689				     &cksum,
1690				     &authenticator,
1691				     KRB5_KU_AP_REQ_AUTH);
1692
1693    if (kret) {
1694	if (offset)
1695	    krb5_set_kdc_sec_offset (context, oldoffset, -1);
1696	*minor_status = kret;
1697	ret = GSS_S_FAILURE;
1698	goto failure;
1699    }
1700
1701    kret = krb5_build_ap_req (context,
1702			      enctype,
1703			      ctx->kcred,
1704			      ap_options,
1705			      authenticator,
1706			      &outbuf);
1707    if (offset)
1708	krb5_set_kdc_sec_offset (context, oldoffset, -1);
1709    if (kret) {
1710	*minor_status = kret;
1711	ret = GSS_S_FAILURE;
1712	goto failure;
1713    }
1714
1715    if (flags & GSS_C_DCE_STYLE) {
1716	output_token->value = outbuf.data;
1717	output_token->length = outbuf.length;
1718    } else {
1719        ret = _gsskrb5_encapsulate (minor_status, &outbuf, output_token,
1720				    (u_char *)(intptr_t)"\x01\x00", ctx->mech);
1721	krb5_data_free (&outbuf);
1722	if (ret)
1723	    goto failure;
1724    }
1725
1726    if (crypto)
1727	krb5_crypto_destroy(context, crypto);
1728    free_Checksum(&cksum);
1729    krb5_data_free (&fwd_data);
1730    krb5_data_free (&pkt_cksum);
1731
1732    if (flags & GSS_C_MUTUAL_FLAG) {
1733	ctx->initiator_state = wait_repl_mutual;
1734	return GSS_S_CONTINUE_NEEDED;
1735    }
1736
1737    return initiator_ready(minor_status, ctx, context, ret_flags);
1738
1739failure:
1740    if (crypto)
1741	krb5_crypto_destroy(context, crypto);
1742    free_Checksum(&cksum);
1743    krb5_data_free (&fwd_data);
1744    krb5_data_free (&pkt_cksum);
1745    return ret;
1746}
1747
1748static OM_uint32
1749handle_error_packet(OM_uint32 *minor_status,
1750		    krb5_context context,
1751		    gsskrb5_ctx ctx,
1752		    krb5_data indata)
1753{
1754    krb5_error_code kret;
1755    KRB_ERROR error;
1756
1757    heim_assert(ctx->initiator_state == wait_repl_mutual,
1758		"handle_error in wrong state");
1759
1760    kret = krb5_rd_error(context, &indata, &error);
1761    if (kret == 0) {
1762	kret = krb5_error_from_rd_error(context, &error, NULL);
1763
1764	_gss_mg_log(1, "gss-krb5: server return KRB-ERROR with error code %d", kret);
1765
1766	/*
1767	 * If we get back an error code that we know about, then lets retry again:
1768	 * - KRB5KRB_AP_ERR_MODIFIED: delete entry and retry
1769	 * - KRB5KRB_AP_ERR_SKEW: time skew sent, retry again with right time skew
1770	 * - KRB5KRB_ERR_GENERIC: with edata, maybe get windows error code or time skew (windows style)
1771	 */
1772
1773	if (kret == KRB5KRB_AP_ERR_MODIFIED && ctx->ccache) {
1774
1775	    if ((ctx->more_flags & RETRIED_NEWTICKET) == 0) {
1776		krb5_creds mcreds;
1777
1778		_gss_mg_log(1, "gss-krb5: trying to renew ticket");
1779
1780		krb5_cc_clear_mcred(&mcreds);
1781		mcreds.client = ctx->source;
1782		mcreds.server = ctx->target;
1783
1784		krb5_cc_remove_cred(context, ctx->ccache, 0, &mcreds);
1785
1786		ctx->initiator_state = init_krb5_auth;
1787	    }
1788	    ctx->more_flags |= RETRIED_NEWTICKET;
1789
1790	} else if (kret == KRB5KRB_AP_ERR_SKEW && ctx->ccache) {
1791
1792	recover_skew:
1793	    if ((ctx->more_flags & RETRIED_SKEW) == 0) {
1794		krb5_data timedata;
1795		uint8_t p[4];
1796		int32_t t = (int32_t)(error.stime - time(NULL));
1797
1798		_gss_mg_encode_be_uint32(t, p);
1799
1800		timedata.data = p;
1801		timedata.length = sizeof(p);
1802
1803		krb5_cc_set_config(context, ctx->ccache, ctx->target,
1804				   "time-offset", &timedata);
1805
1806		_gss_mg_log(1, "gss-krb5: time skew of %d", (int)t);
1807
1808		ctx->initiator_state = init_auth_step;
1809	    }
1810	    ctx->more_flags |= RETRIED_SKEW;
1811	} else if (kret == KRB5KRB_ERR_GENERIC && error.e_data) {
1812	    KERB_ERROR_DATA data;
1813	    krb5_error_code ret;
1814
1815	    _gss_mg_log(1, "gss-krb5: trying to decode a KRB5KRB_ERR_GENERIC");
1816
1817	    ret = decode_KERB_ERROR_DATA(error.e_data->data, error.e_data->length, &data, NULL);
1818	    if (ret)
1819		goto out;
1820
1821	    if (data.data_type == KRB5_AP_ERR_WINDOWS_ERROR_CODE) {
1822		if (data.data_value && data.data_value->length >= 4) {
1823		    uint32_t num;
1824		    _gss_mg_decode_le_uint32(data.data_value->data, &num);
1825		    _gss_mg_log(1, "gss-krb5: got an windows error code: %08x", (unsigned int)num);
1826		}
1827	    } else if (data.data_type == KRB5_AP_ERR_TYPE_SKEW_RECOVERY) {
1828		free_KERB_ERROR_DATA(&data);
1829		goto recover_skew;
1830	    } else {
1831		_gss_mg_log(1, "gss-krb5: got an KERB_ERROR_DATA of type %d", data.data_type);
1832	    }
1833	    free_KERB_ERROR_DATA(&data);
1834	}
1835	free_KRB_ERROR (&error);
1836    }
1837
1838    if (ctx->initiator_state != wait_repl_mutual)
1839	return GSS_S_COMPLETE;
1840
1841 out:
1842    *minor_status = kret;
1843    return GSS_S_FAILURE;
1844
1845}
1846
1847
1848static OM_uint32
1849wait_repl_mutual(OM_uint32 * minor_status,
1850		 gsskrb5_cred cred,
1851		 gsskrb5_ctx ctx,
1852		 krb5_context context,
1853		 gss_name_t name,
1854		 const gss_OID mech_type,
1855		 OM_uint32 req_flags,
1856		 OM_uint32 time_req,
1857		 const gss_channel_bindings_t input_chan_bindings,
1858		 const gss_buffer_t input_token,
1859		 gss_buffer_t output_token,
1860		 OM_uint32 * ret_flags,
1861		 OM_uint32 * time_rec)
1862{
1863    OM_uint32 ret;
1864    krb5_error_code kret;
1865    krb5_data indata;
1866    krb5_ap_rep_enc_part *repl;
1867
1868    output_token->length = 0;
1869    output_token->value = NULL;
1870
1871    if (IS_DCE_STYLE(ctx)) {
1872	/* There is no OID wrapping. */
1873	indata.length	= input_token->length;
1874	indata.data	= input_token->value;
1875	kret = krb5_rd_rep(context,
1876			   ctx->auth_context,
1877			   &indata,
1878			   &repl);
1879	if (kret) {
1880	    ret = _gsskrb5_decapsulate(minor_status,
1881				       input_token,
1882				       &indata,
1883				       "\x03\x00",
1884				       GSS_KRB5_MECHANISM);
1885	    if (ret == GSS_S_COMPLETE) {
1886		return handle_error_packet(minor_status, context, ctx, indata);
1887	    } else {
1888		*minor_status = kret;
1889		return GSS_S_FAILURE;
1890	    }
1891	}
1892    } else {
1893	ret = _gsskrb5_decapsulate (minor_status,
1894				    input_token,
1895				    &indata,
1896				    "\x02\x00",
1897				    ctx->mech);
1898	if (ret == GSS_S_DEFECTIVE_TOKEN) {
1899	    /* check if there is an error token sent instead */
1900	    ret = _gsskrb5_decapsulate (minor_status,
1901					input_token,
1902					&indata,
1903					"\x03\x00",
1904					ctx->mech);
1905	    if (ret != GSS_S_COMPLETE)
1906		return ret;
1907
1908	    return handle_error_packet(minor_status, context, ctx, indata);
1909	} else if (ret != GSS_S_COMPLETE) {
1910	    return ret;
1911	}
1912
1913	kret = krb5_rd_rep (context,
1914			    ctx->auth_context,
1915			    &indata,
1916			    &repl);
1917	if (kret) {
1918	    *minor_status = kret;
1919	    return GSS_S_FAILURE;
1920	}
1921    }
1922
1923    krb5_free_ap_rep_enc_part (context,
1924			       repl);
1925
1926    *minor_status = 0;
1927    if (time_rec) {
1928	ret = _gsskrb5_lifetime_left(minor_status,
1929				     context,
1930				     ctx->endtime,
1931				     time_rec);
1932	if (ret)
1933	    return ret;
1934    }
1935
1936    if (req_flags & GSS_C_DCE_STYLE) {
1937	int32_t local_seq, remote_seq;
1938	krb5_data outbuf;
1939
1940	/*
1941	 * So DCE_STYLE is strange. The client echos the seq number
1942	 * that the server used in the server's mk_rep in its own
1943	 * mk_rep(). After when done, it resets to it's own seq number
1944	 * for the gss_wrap calls.
1945	 */
1946
1947	krb5_auth_con_getremoteseqnumber(context, ctx->auth_context, &remote_seq);
1948	krb5_auth_con_getlocalseqnumber(context, ctx->auth_context, &local_seq);
1949	krb5_auth_con_setlocalseqnumber(context, ctx->auth_context, remote_seq);
1950
1951	kret = krb5_mk_rep(context, ctx->auth_context, &outbuf);
1952	if (kret) {
1953	    *minor_status = kret;
1954	    return GSS_S_FAILURE;
1955	}
1956
1957	/* reset local seq number */
1958	krb5_auth_con_setlocalseqnumber(context, ctx->auth_context, local_seq);
1959
1960	output_token->length = outbuf.length;
1961	output_token->value  = outbuf.data;
1962    }
1963
1964    return initiator_ready(minor_status, ctx, context, ret_flags);
1965}
1966
1967static OM_uint32
1968step_completed(OM_uint32 * minor_status,
1969	       gsskrb5_cred cred,
1970	       gsskrb5_ctx ctx,
1971	       krb5_context context,
1972	       gss_name_t name,
1973	       const gss_OID mech_type,
1974	       OM_uint32 req_flags,
1975	       OM_uint32 time_req,
1976	       const gss_channel_bindings_t input_chan_bindings,
1977	       const gss_buffer_t input_token,
1978	       gss_buffer_t output_token,
1979	       OM_uint32 * ret_flags,
1980	       OM_uint32 * time_rec)
1981{
1982    /*
1983     * If we get there, the caller have called
1984     * gss_init_sec_context() one time too many.
1985     */
1986    _gsskrb5_set_status(EINVAL, "init_sec_context "
1987			"called one time too many");
1988    *minor_status = EINVAL;
1989    return GSS_S_BAD_STATUS;
1990}
1991
1992/*
1993 * gss_init_sec_context
1994 */
1995
1996OM_uint32 GSSAPI_CALLCONV _gsskrb5_init_sec_context
1997(OM_uint32 * minor_status,
1998 const gss_cred_id_t cred_handle,
1999 gss_ctx_id_t * context_handle,
2000 const gss_name_t target_name,
2001 const gss_OID mech_type,
2002 OM_uint32 req_flags,
2003 OM_uint32 time_req,
2004 const gss_channel_bindings_t input_chan_bindings,
2005 const gss_buffer_t input_token,
2006 gss_OID * actual_mech_type,
2007 gss_buffer_t output_token,
2008 OM_uint32 * ret_flags,
2009 OM_uint32 * time_rec
2010    )
2011{
2012    krb5_context context;
2013    gsskrb5_cred cred = (gsskrb5_cred)cred_handle;
2014    gsskrb5_ctx ctx;
2015    OM_uint32 ret;
2016    gss_OID mech = GSS_C_NO_OID;
2017    gsskrb5_initator_state start_state;
2018
2019    GSSAPI_KRB5_INIT (&context);
2020
2021    output_token->length = 0;
2022    output_token->value  = NULL;
2023
2024    if (context_handle == NULL) {
2025	*minor_status = 0;
2026	return GSS_S_FAILURE | GSS_S_CALL_BAD_STRUCTURE;
2027    }
2028
2029    if (ret_flags)
2030	*ret_flags = 0;
2031    if (time_rec)
2032	*time_rec = 0;
2033
2034    if (target_name == GSS_C_NO_NAME) {
2035	if (actual_mech_type)
2036	    *actual_mech_type = GSS_C_NO_OID;
2037	*minor_status = 0;
2038	return GSS_S_BAD_NAME;
2039    }
2040
2041    if (cred && (cred->usage != GSS_C_INITIATE && cred->usage != GSS_C_BOTH)) {
2042	krb5_set_error_message(context, GSS_KRB5_S_G_BAD_USAGE,
2043			       "ISC: Credentials not of "
2044			       "usage type initiator or both");
2045	*minor_status = GSS_KRB5_S_G_BAD_USAGE;
2046	return GSS_S_DEFECTIVE_CREDENTIAL;
2047    }
2048
2049    if (gss_oid_equal(mech_type, GSS_KRB5_MECHANISM)) {
2050	mech = GSS_KRB5_MECHANISM;
2051	start_state = init_krb5_auth;
2052    } else if (gss_oid_equal(mech_type, GSS_IAKERB_MECHANISM)) {
2053	mech = GSS_IAKERB_MECHANISM;
2054	start_state = init_iakerb_auth;
2055#ifdef PKINIT
2056    } else if (gss_oid_equal(mech_type, GSS_PKU2U_MECHANISM)) {
2057	mech = GSS_PKU2U_MECHANISM;
2058	start_state = init_pku2u_auth;
2059#endif
2060    } else
2061	return GSS_S_BAD_MECH;
2062
2063    if (input_token == GSS_C_NO_BUFFER || input_token->length == 0) {
2064#ifdef __APPLE__
2065        ret = check_neg_cache(minor_status, context, mech,
2066			      cred_handle, target_name);
2067        if (ret != GSS_S_COMPLETE)
2068            return ret;
2069#endif /* __APPLE__ */
2070
2071	if (*context_handle != GSS_C_NO_CONTEXT) {
2072	    *minor_status = 0;
2073	    return GSS_S_FAILURE | GSS_S_CALL_BAD_STRUCTURE;
2074	}
2075
2076	ret = _gsskrb5_create_ctx(minor_status,
2077				  context_handle,
2078				  context,
2079				  input_chan_bindings,
2080				  mech);
2081	if (ret)
2082	    return ret;
2083
2084	ctx = (gsskrb5_ctx) *context_handle;
2085	ctx->initiator_state = start_state;
2086    } else {
2087	ctx = (gsskrb5_ctx) *context_handle;
2088    }
2089
2090    if (ctx == NULL) {
2091	*minor_status = 0;
2092	return GSS_S_FAILURE | GSS_S_CALL_BAD_STRUCTURE;
2093    }
2094
2095    HEIMDAL_MUTEX_lock(&ctx->ctx_id_mutex);
2096
2097    if (actual_mech_type)
2098	*actual_mech_type = ctx->mech;
2099
2100    do {
2101	ret = ctx->initiator_state(minor_status, cred, ctx, context, target_name,
2102				   mech, req_flags, time_req, input_chan_bindings, input_token,
2103				   output_token, ret_flags, time_rec);
2104
2105    } while (ret == GSS_S_COMPLETE &&
2106	     ctx->initiator_state != step_completed &&
2107	     output_token->length == 0);
2108
2109    if (GSS_ERROR(ret)) {
2110	if (ctx->ccache && (ctx->more_flags & CLOSE_CCACHE))
2111	    krb5_cc_close(context, ctx->ccache);
2112	ctx->ccache = NULL;
2113    }
2114
2115    HEIMDAL_MUTEX_unlock(&ctx->ctx_id_mutex);
2116
2117    /* destroy context in case of error */
2118    if (GSS_ERROR(ret)) {
2119	OM_uint32 junk;
2120	_gsskrb5_delete_sec_context(&junk, context_handle, GSS_C_NO_BUFFER);
2121    }
2122
2123    return ret;
2124
2125}
2126