2 * Copyright (c) 1997 - 2006 Kungliga Tekniska Högskolan
3 * (Royal Institute of Technology, Stockholm, Sweden).
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 * notice, this list of conditions and the following disclaimer in the
15 * documentation and/or other materials provided with the distribution.
17 * 3. Neither the name of the Institute nor the names of its contributors
18 * may be used to endorse or promote products derived from this software
19 * without specific prior written permission.
21 * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
22 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24 * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
25 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
34 #include "gsskrb5_locl.h"
36 HEIMDAL_MUTEX gssapi_keytab_mutex
= HEIMDAL_MUTEX_INITIALIZER
;
37 krb5_keytab _gsskrb5_keytab
;
39 static krb5_error_code
40 validate_keytab(krb5_context context
, const char *name
, krb5_keytab
*id
)
44 ret
= krb5_kt_resolve(context
, name
, id
);
48 ret
= krb5_kt_have_content(context
, *id
);
50 krb5_kt_close(context
, *id
);
58 _gsskrb5_register_acceptor_identity(OM_uint32
*min_stat
, const char *identity
)
65 ret
= _gsskrb5_init(&context
);
69 HEIMDAL_MUTEX_lock(&gssapi_keytab_mutex
);
71 if(_gsskrb5_keytab
!= NULL
) {
72 krb5_kt_close(context
, _gsskrb5_keytab
);
73 _gsskrb5_keytab
= NULL
;
75 if (identity
== NULL
) {
76 ret
= krb5_kt_default(context
, &_gsskrb5_keytab
);
79 * First check if we can the keytab as is and if it has content...
81 ret
= validate_keytab(context
, identity
, &_gsskrb5_keytab
);
83 * if it doesn't, lets prepend FILE: and try again
87 ret
= asprintf(&p
, "FILE:%s", identity
);
88 if(ret
< 0 || p
== NULL
) {
89 HEIMDAL_MUTEX_unlock(&gssapi_keytab_mutex
);
92 ret
= validate_keytab(context
, p
, &_gsskrb5_keytab
);
96 HEIMDAL_MUTEX_unlock(&gssapi_keytab_mutex
);
101 return GSS_S_COMPLETE
;
105 _gsskrb5i_is_cfx(krb5_context context
, gsskrb5_ctx ctx
, int acceptor
)
110 if (ctx
->auth_context
->local_subkey
)
111 key
= ctx
->auth_context
->local_subkey
;
113 key
= ctx
->auth_context
->remote_subkey
;
115 if (ctx
->auth_context
->remote_subkey
)
116 key
= ctx
->auth_context
->remote_subkey
;
118 key
= ctx
->auth_context
->local_subkey
;
121 key
= ctx
->auth_context
->keyblock
;
126 switch (key
->keytype
) {
127 case ETYPE_DES_CBC_CRC
:
128 case ETYPE_DES_CBC_MD4
:
129 case ETYPE_DES_CBC_MD5
:
130 case ETYPE_DES3_CBC_MD5
:
131 case ETYPE_OLD_DES3_CBC_SHA1
:
132 case ETYPE_DES3_CBC_SHA1
:
133 case ETYPE_ARCFOUR_HMAC_MD5
:
134 case ETYPE_ARCFOUR_HMAC_MD5_56
:
137 ctx
->more_flags
|= IS_CFX
;
139 if ((acceptor
&& ctx
->auth_context
->local_subkey
) ||
140 (!acceptor
&& ctx
->auth_context
->remote_subkey
))
141 ctx
->more_flags
|= ACCEPTOR_SUBKEY
;
145 krb5_crypto_destroy(context
, ctx
->crypto
);
146 /* XXX We really shouldn't ignore this; will come back to this */
147 (void) krb5_crypto_init(context
, key
, 0, &ctx
->crypto
);
152 gsskrb5_accept_delegated_token(OM_uint32
*minor_status
,
154 krb5_context context
,
155 gss_cred_id_t
*delegated_cred_handle
)
157 krb5_ccache ccache
= NULL
;
158 krb5_error_code kret
;
159 int32_t ac_flags
, ret
= GSS_S_COMPLETE
;
164 /* XXX Create a new delegated_cred_handle? */
165 if (delegated_cred_handle
== NULL
)
166 return GSS_S_COMPLETE
;
168 *delegated_cred_handle
= NULL
;
169 kret
= krb5_cc_resolve(context
, "MEMORY:anonymous", &ccache
);
171 kret
= krb5_cc_initialize(context
, ccache
, ctx
->source
);
173 (void) krb5_auth_con_removeflags(context
,
175 KRB5_AUTH_CONTEXT_DO_TIME
,
177 kret
= krb5_rd_cred2(context
,
181 (void) krb5_auth_con_setflags(context
,
186 ctx
->flags
&= ~GSS_C_DELEG_FLAG
;
188 *minor_status
= kret
;
192 ret
= _gsskrb5_krb5_import_cred(minor_status
,
196 delegated_cred_handle
);
197 if (ret
!= GSS_S_COMPLETE
)
200 handle
= (gsskrb5_cred
) *delegated_cred_handle
;
201 handle
->cred_flags
|= GSS_CF_DESTROY_CRED_ON_RELEASE
;
204 * A root TGT is one of the form krbtgt/REALM@SAME-REALM.
206 * A destination TGT is a root TGT for the same realm as the acceptor
209 * Normally clients delegate a root TGT for the client's realm.
211 * In some deployments clients may want to delegate destination TGTs as
212 * a form of constrained delegation: so that the destination service
213 * cannot use the delegated credential to impersonate the client
214 * principal to services in its home realm (due to KDC lineage/transit
215 * checks). In those deployments there may not even be a route back to
216 * the KDCs of the client's realm, and attempting to use a
217 * non-destination TGT might even lead to timeouts.
219 * We could simply pretend not to have obtained a credential, except
220 * that a) we don't (yet) have an app name here for the appdefault we
221 * need to check, b) the application really wants to be able to log a
222 * message about the delegated credential being no good.
224 * Thus we leave it to _gsskrb5_store_cred_into2() to decide what to do
225 * with non-destination TGTs. To do that, it needs the realm of the
226 * acceptor service, which we record here.
228 handle
->destination_realm
=
229 strdup(krb5_principal_get_realm(context
, ctx
->target
));
230 if (handle
->destination_realm
== NULL
) {
231 _gsskrb5_release_cred(minor_status
, delegated_cred_handle
);
232 *minor_status
= krb5_enomem(context
);
239 krb5_cc_close(context
, ccache
);
245 gsskrb5_acceptor_ready(OM_uint32
* minor_status
,
247 krb5_context context
,
248 gss_cred_id_t
*delegated_cred_handle
)
254 krb5_auth_con_getremoteseqnumber (context
,
258 _gsskrb5i_is_cfx(context
, ctx
, 1);
259 is_cfx
= (ctx
->more_flags
& IS_CFX
);
261 ret
= _gssapi_msg_order_create(minor_status
,
263 _gssapi_msg_order_f(ctx
->flags
),
264 seq_number
, 0, is_cfx
);
269 * If requested, set local sequence num to remote sequence if this
270 * isn't a mutual authentication context
272 if (!(ctx
->flags
& GSS_C_MUTUAL_FLAG
) && _gssapi_msg_order_f(ctx
->flags
)) {
273 krb5_auth_con_setlocalseqnumber(context
,
279 * We should handle the delegation ticket, in case it's there
281 if (ctx
->fwd_data
.length
> 0 && (ctx
->flags
& GSS_C_DELEG_FLAG
)) {
282 ret
= gsskrb5_accept_delegated_token(minor_status
,
285 delegated_cred_handle
);
286 if (ret
!= GSS_S_COMPLETE
)
289 /* Well, looks like it wasn't there after all */
290 ctx
->flags
&= ~GSS_C_DELEG_FLAG
;
293 ctx
->state
= ACCEPTOR_READY
;
294 ctx
->more_flags
|= OPEN
;
296 return GSS_S_COMPLETE
;
300 send_error_token(OM_uint32
*minor_status
,
301 krb5_context context
,
302 krb5_error_code kret
,
303 krb5_principal server
,
305 gss_buffer_t output_token
)
307 krb5_principal ap_req_server
= NULL
;
310 /* this e_data value encodes KERB_AP_ERR_TYPE_SKEW_RECOVERY which
311 tells windows to try again with the corrected timestamp. See
312 [MS-KILE] 2.2.1 KERB-ERROR-DATA */
313 krb5_data e_data
= { 7, rk_UNCONST("\x30\x05\xa1\x03\x02\x01\x02") };
315 /* build server from request if the acceptor had not selected one */
316 if (server
== NULL
) {
319 ret
= krb5_decode_ap_req(context
, indata
, &ap_req
);
322 return GSS_S_FAILURE
;
324 ret
= _krb5_principalname2krb5_principal(context
,
327 ap_req
.ticket
.realm
);
328 free_AP_REQ(&ap_req
);
331 return GSS_S_FAILURE
;
333 server
= ap_req_server
;
336 ret
= krb5_mk_error(context
, kret
, NULL
, &e_data
, NULL
,
337 server
, NULL
, NULL
, &outbuf
);
339 krb5_free_principal(context
, ap_req_server
);
342 return GSS_S_FAILURE
;
345 ret
= _gsskrb5_encapsulate(minor_status
,
350 krb5_data_free (&outbuf
);
355 return GSS_S_CONTINUE_NEEDED
;
360 gsskrb5_acceptor_start(OM_uint32
* minor_status
,
362 krb5_context context
,
363 gss_const_cred_id_t acceptor_cred_handle
,
364 const gss_buffer_t input_token_buffer
,
365 const gss_channel_bindings_t input_chan_bindings
,
366 gss_name_t
* src_name
,
368 gss_buffer_t output_token
,
369 OM_uint32
* ret_flags
,
370 OM_uint32
* time_rec
,
371 gss_cred_id_t
* delegated_cred_handle
)
373 krb5_error_code kret
;
374 OM_uint32 ret
= GSS_S_COMPLETE
;
376 krb5_flags ap_options
;
377 krb5_keytab keytab
= NULL
;
380 const gsskrb5_cred acceptor_cred
= (gsskrb5_cred
)acceptor_cred_handle
;
383 * We may, or may not, have an escapsulation.
385 ret
= _gsskrb5_decapsulate (minor_status
,
392 /* Could be a raw AP-REQ (check for APPLICATION tag) */
393 if (input_token_buffer
->length
== 0 ||
394 ((const uint8_t *)input_token_buffer
->value
)[0] != 0x6E) {
395 *minor_status
= ASN1_MISPLACED_FIELD
;
396 return GSS_S_DEFECTIVE_TOKEN
;
399 /* Assume that there is no OID wrapping. */
400 indata
.length
= input_token_buffer
->length
;
401 indata
.data
= input_token_buffer
->value
;
405 * We need to get our keytab
407 if (acceptor_cred
== NULL
) {
408 HEIMDAL_MUTEX_lock(&gssapi_keytab_mutex
);
409 if (_gsskrb5_keytab
!= NULL
) {
411 kret
= krb5_kt_get_full_name(context
, _gsskrb5_keytab
, &name
);
413 kret
= krb5_kt_resolve(context
, name
, &keytab
);
421 HEIMDAL_MUTEX_unlock(&gssapi_keytab_mutex
);
422 } else if (acceptor_cred
->keytab
!= NULL
) {
423 keytab
= acceptor_cred
->keytab
;
427 * We need to check the ticket and create the AP-REP packet
431 krb5_rd_req_in_ctx in
= NULL
;
432 krb5_rd_req_out_ctx out
= NULL
;
433 krb5_principal server
= NULL
;
436 server
= acceptor_cred
->principal
;
438 kret
= krb5_rd_req_in_ctx_alloc(context
, &in
);
440 kret
= krb5_rd_req_in_set_keytab(context
, in
, keytab
);
443 krb5_rd_req_in_ctx_free(context
, in
);
445 krb5_kt_close(context
, keytab
);
446 *minor_status
= kret
;
447 return GSS_S_FAILURE
;
450 kret
= krb5_rd_req_ctx(context
,
455 krb5_rd_req_in_ctx_free(context
, in
);
457 krb5_kt_close(context
, keytab
);
458 if (kret
== KRB5KRB_AP_ERR_SKEW
|| kret
== KRB5KRB_AP_ERR_TKT_NYV
) {
460 * No reply in non-MUTUAL mode, but we don't know that its
461 * non-MUTUAL mode yet, thats inside the 8003 checksum, so
462 * lets only send the error token on clock skew, that
463 * limit when send error token for non-MUTUAL.
465 krb5_auth_con_free(context
, ctx
->auth_context
);
466 krb5_auth_con_free(context
, ctx
->deleg_auth_context
);
467 ctx
->deleg_auth_context
= NULL
;
468 ctx
->auth_context
= NULL
;
469 return send_error_token(minor_status
, context
, kret
,
470 server
, &indata
, output_token
);
472 *minor_status
= kret
;
473 return GSS_S_FAILURE
;
477 * we need to remember some data on the context_handle.
479 kret
= krb5_rd_req_out_get_ap_req_options(context
, out
,
482 kret
= krb5_rd_req_out_get_ticket(context
, out
,
485 kret
= krb5_rd_req_out_get_keyblock(context
, out
,
486 &ctx
->service_keyblock
);
487 ctx
->endtime
= ctx
->ticket
->ticket
.endtime
;
489 krb5_rd_req_out_ctx_free(context
, out
);
492 *minor_status
= kret
;
499 * We need to copy the principal names to the context and the
502 kret
= krb5_copy_principal(context
,
507 *minor_status
= kret
;
511 kret
= krb5_copy_principal(context
,
516 *minor_status
= kret
;
521 * We need to setup some compat stuff, this assumes that
522 * context_handle->target is already set.
524 ret
= _gss_DES3_get_mic_compat(minor_status
, ctx
, context
);
528 if (src_name
!= NULL
) {
529 kret
= krb5_copy_principal (context
,
531 (gsskrb5_name
*)src_name
);
534 *minor_status
= kret
;
540 * We need to get the flags out of the 8003 checksum.
544 krb5_authenticator authenticator
;
546 kret
= krb5_auth_con_getauthenticator(context
,
551 *minor_status
= kret
;
555 if (authenticator
->cksum
!= NULL
556 && authenticator
->cksum
->cksumtype
== CKSUMTYPE_GSSAPI
) {
557 ret
= _gsskrb5_verify_8003_checksum(context
,
565 krb5_free_authenticator(context
, &authenticator
);
569 if (authenticator
->cksum
!= NULL
) {
572 kret
= krb5_crypto_init(context
,
573 ctx
->auth_context
->keyblock
,
576 krb5_free_authenticator(context
, &authenticator
);
578 *minor_status
= kret
;
583 * Windows accepts Samba3's use of a kerberos, rather than
584 * GSSAPI checksum here
587 _krb5_crypto_set_flags(context
, crypto
, KRB5_CRYPTO_FLAG_ALLOW_UNKEYED_CHECKSUM
);
588 kret
= krb5_verify_checksum(context
,
589 crypto
, KRB5_KU_AP_REQ_AUTH_CKSUM
, NULL
, 0,
590 authenticator
->cksum
);
591 krb5_crypto_destroy(context
, crypto
);
594 krb5_free_authenticator(context
, &authenticator
);
596 *minor_status
= kret
;
602 * If there is no checksum or a kerberos checksum (which Windows
603 * and Samba accept), we use the ap_options to guess the mutual
607 ctx
->flags
= GSS_C_REPLAY_FLAG
| GSS_C_SEQUENCE_FLAG
;
608 if (ap_options
& AP_OPTS_MUTUAL_REQUIRED
)
609 ctx
->flags
|= GSS_C_MUTUAL_FLAG
;
611 krb5_free_authenticator(context
, &authenticator
);
614 if(ctx
->flags
& GSS_C_MUTUAL_FLAG
) {
618 _gsskrb5i_is_cfx(context
, ctx
, 1);
619 is_cfx
= (ctx
->more_flags
& IS_CFX
);
621 if (is_cfx
|| (ap_options
& AP_OPTS_USE_SUBKEY
)) {
627 * If there is a initiator subkey, copy that to acceptor
628 * subkey to match Windows behavior
630 kret
= krb5_auth_con_getremotesubkey(context
,
634 kret
= krb5_auth_con_setlocalsubkey(context
,
640 krb5_free_keyblock(context
, rkey
);
643 ctx
->more_flags
|= ACCEPTOR_SUBKEY
;
644 krb5_auth_con_addflags(context
, ctx
->auth_context
,
645 KRB5_AUTH_CONTEXT_USE_SUBKEY
,
649 kret
= krb5_mk_rep(context
,
653 *minor_status
= kret
;
654 return GSS_S_FAILURE
;
657 if (IS_DCE_STYLE(ctx
)) {
658 output_token
->length
= outbuf
.length
;
659 output_token
->value
= outbuf
.data
;
661 ret
= _gsskrb5_encapsulate(minor_status
,
666 krb5_data_free (&outbuf
);
672 ctx
->flags
|= GSS_C_TRANS_FLAG
;
674 /* Remember the flags */
676 ctx
->endtime
= ctx
->ticket
->ticket
.endtime
;
677 ctx
->more_flags
|= OPEN
;
680 *mech_type
= GSS_KRB5_MECHANISM
;
683 ret
= _gsskrb5_lifetime_left(minor_status
,
693 * When GSS_C_DCE_STYLE is in use, we need ask for a AP-REP from
696 if (IS_DCE_STYLE(ctx
)) {
698 * Return flags to caller, but we haven't processed
702 *ret_flags
= (ctx
->flags
& ~GSS_C_DELEG_FLAG
);
704 ctx
->state
= ACCEPTOR_WAIT_FOR_DCESTYLE
;
705 return GSS_S_CONTINUE_NEEDED
;
708 ret
= gsskrb5_acceptor_ready(minor_status
, ctx
, context
,
709 delegated_cred_handle
);
712 *ret_flags
= ctx
->flags
;
718 acceptor_wait_for_dcestyle(OM_uint32
* minor_status
,
720 krb5_context context
,
721 gss_const_cred_id_t acceptor_cred_handle
,
722 const gss_buffer_t input_token_buffer
,
723 const gss_channel_bindings_t input_chan_bindings
,
724 gss_name_t
* src_name
,
726 gss_buffer_t output_token
,
727 OM_uint32
* ret_flags
,
728 OM_uint32
* time_rec
,
729 gss_cred_id_t
* delegated_cred_handle
)
732 krb5_error_code kret
;
734 int32_t r_seq_number
, l_seq_number
;
737 * We know it's GSS_C_DCE_STYLE so we don't need to decapsulate the AP_REP
740 inbuf
.length
= input_token_buffer
->length
;
741 inbuf
.data
= input_token_buffer
->value
;
744 * We need to remeber the old remote seq_number, then check if the
745 * client has replied with our local seq_number, and then reset
746 * the remote seq_number to the old value
749 kret
= krb5_auth_con_getlocalseqnumber(context
,
753 *minor_status
= kret
;
754 return GSS_S_FAILURE
;
757 kret
= krb5_auth_con_getremoteseqnumber(context
,
761 *minor_status
= kret
;
762 return GSS_S_FAILURE
;
765 kret
= krb5_auth_con_setremoteseqnumber(context
,
769 *minor_status
= kret
;
770 return GSS_S_FAILURE
;
775 * We need to verify the AP_REP, but we need to flag that this is
776 * DCE_STYLE, so don't check the timestamps this time, but put the
777 * flag DO_TIME back afterward.
780 krb5_ap_rep_enc_part
*repl
;
783 krb5_auth_con_removeflags(context
,
785 KRB5_AUTH_CONTEXT_DO_TIME
,
788 kret
= krb5_rd_rep(context
, ctx
->auth_context
, &inbuf
, &repl
);
790 *minor_status
= kret
;
791 return GSS_S_FAILURE
;
793 krb5_free_ap_rep_enc_part(context
, repl
);
794 krb5_auth_con_setflags(context
, ctx
->auth_context
, auth_flags
);
797 /* We need to check the liftime */
799 OM_uint32 lifetime_rec
;
801 ret
= _gsskrb5_lifetime_left(minor_status
,
808 if (lifetime_rec
== 0) {
809 return GSS_S_CONTEXT_EXPIRED
;
812 if (time_rec
) *time_rec
= lifetime_rec
;
815 /* We need to give the caller the flags which are in use */
816 if (ret_flags
) *ret_flags
= ctx
->flags
;
819 kret
= krb5_copy_principal(context
,
821 (gsskrb5_name
*)src_name
);
823 *minor_status
= kret
;
824 return GSS_S_FAILURE
;
829 * After the krb5_rd_rep() the remote and local seq_number should
830 * be the same, because the client just replies the seq_number
831 * from our AP-REP in its AP-REP, but then the client uses the
832 * seq_number from its AP-REQ for GSS_wrap()
835 int32_t tmp_r_seq_number
, tmp_l_seq_number
;
837 kret
= krb5_auth_con_getremoteseqnumber(context
,
841 *minor_status
= kret
;
842 return GSS_S_FAILURE
;
845 kret
= krb5_auth_con_getlocalseqnumber(context
,
850 *minor_status
= kret
;
851 return GSS_S_FAILURE
;
855 * Here we check if the client has responsed with our local seq_number,
857 if (tmp_r_seq_number
!= tmp_l_seq_number
) {
858 return GSS_S_UNSEQ_TOKEN
;
863 * We need to reset the remote seq_number, because the client will use,
864 * the old one for the GSS_wrap() calls
867 kret
= krb5_auth_con_setremoteseqnumber(context
,
871 *minor_status
= kret
;
872 return GSS_S_FAILURE
;
876 return gsskrb5_acceptor_ready(minor_status
, ctx
, context
,
877 delegated_cred_handle
);
881 OM_uint32 GSSAPI_CALLCONV
882 _gsskrb5_accept_sec_context(OM_uint32
* minor_status
,
883 gss_ctx_id_t
* context_handle
,
884 gss_const_cred_id_t acceptor_cred_handle
,
885 const gss_buffer_t input_token_buffer
,
886 const gss_channel_bindings_t input_chan_bindings
,
887 gss_name_t
* src_name
,
889 gss_buffer_t output_token
,
890 OM_uint32
* ret_flags
,
891 OM_uint32
* time_rec
,
892 gss_cred_id_t
* delegated_cred_handle
)
894 krb5_context context
;
898 GSSAPI_KRB5_INIT(&context
);
900 output_token
->length
= 0;
901 output_token
->value
= NULL
;
903 if (src_name
!= NULL
)
906 *mech_type
= GSS_KRB5_MECHANISM
;
908 if (*context_handle
== GSS_C_NO_CONTEXT
) {
909 ret
= _gsskrb5_create_ctx(minor_status
,
918 ctx
= (gsskrb5_ctx
)*context_handle
;
922 * TODO: check the channel_bindings
923 * (above just sets them to krb5 layer)
926 HEIMDAL_MUTEX_lock(&ctx
->ctx_id_mutex
);
928 switch (ctx
->state
) {
930 ret
= gsskrb5_acceptor_start(minor_status
,
933 acceptor_cred_handle
,
941 delegated_cred_handle
);
943 case ACCEPTOR_WAIT_FOR_DCESTYLE
:
944 ret
= acceptor_wait_for_dcestyle(minor_status
,
947 acceptor_cred_handle
,
955 delegated_cred_handle
);
959 * If we get there, the caller have called
960 * gss_accept_sec_context() one time too many.
962 ret
= GSS_S_BAD_STATUS
;
965 /* TODO: is this correct here? --metze */
966 ret
= GSS_S_BAD_STATUS
;
970 HEIMDAL_MUTEX_unlock(&ctx
->ctx_id_mutex
);
972 if (GSS_ERROR(ret
)) {
974 _gsskrb5_delete_sec_context(&min2
, context_handle
, GSS_C_NO_BUFFER
);