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