/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
/*
 * Copyright 2000, 2004, 2007, 2008  by the Massachusetts Institute of Technology.
 * All Rights Reserved.
 *
 * Export of this software from the United States of America may
 *   require a specific license from the United States Government.
 *   It is the responsibility of any person or organization contemplating
 *   export to obtain such a license before exporting.
 *
 * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
 * distribute this software and its documentation for any purpose and
 * without fee is hereby granted, provided that the above copyright
 * notice appear in all copies and that both that copyright notice and
 * this permission notice appear in supporting documentation, and that
 * the name of M.I.T. not be used in advertising or publicity pertaining
 * to distribution of the software without specific, written prior
 * permission.  Furthermore if you modify this software you must label
 * your software as modified software and not distribute it in such a
 * fashion that it might be confused with the original M.I.T. software.
 * M.I.T. makes no representations about the suitability of
 * this software for any purpose.  It is provided "as is" without express
 * or implied warranty.
 */
/*
 * Copyright 1993 by OpenVision Technologies, Inc.
 *
 * Permission to use, copy, modify, distribute, and sell this software
 * and its documentation for any purpose is hereby granted without fee,
 * provided that the above copyright notice appears in all copies and
 * that both that copyright notice and this permission notice appear in
 * supporting documentation, and that the name of OpenVision not be used
 * in advertising or publicity pertaining to distribution of the software
 * without specific, written prior permission. OpenVision makes no
 * representations about the suitability of this software for any
 * purpose.  It is provided "as is" without express or implied warranty.
 *
 * OPENVISION DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
 * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
 * EVENT SHALL OPENVISION BE LIABLE FOR ANY SPECIAL, INDIRECT OR
 * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF
 * USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
 * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 * PERFORMANCE OF THIS SOFTWARE.
 */

/*
 * Copyright (C) 1998 by the FundsXpress, INC.
 *
 * All rights reserved.
 *
 * Export of this software from the United States of America may require
 * a specific license from the United States Government.  It is the
 * responsibility of any person or organization contemplating export to
 * obtain such a license before exporting.
 *
 * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
 * distribute this software and its documentation for any purpose and
 * without fee is hereby granted, provided that the above copyright
 * notice appear in all copies and that both that copyright notice and
 * this permission notice appear in supporting documentation, and that
 * the name of FundsXpress. not be used in advertising or publicity pertaining
 * to distribution of the software without specific, written prior
 * permission.  FundsXpress makes no representations about the suitability of
 * this software for any purpose.  It is provided "as is" without express
 * or implied warranty.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
 * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 */
/*
 * Copyright (c) 2006-2008, Novell, Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *   * Redistributions of source code must retain the above copyright notice,
 *       this list of conditions and the following disclaimer.
 *   * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *   * The copyright holder's name is not used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include "k5-int.h"
#include "k5-input.h"
#include "gssapiP_krb5.h"
#ifdef HAVE_MEMORY_H
#include <memory.h>
#endif
#include <assert.h>

#ifdef CFX_EXERCISE
#define CFX_ACCEPTOR_SUBKEY (time(0) & 1)
#else
#define CFX_ACCEPTOR_SUBKEY 1
#endif

#ifndef LEAN_CLIENT

static OM_uint32
create_constrained_deleg_creds(OM_uint32 *minor_status,
                               krb5_gss_cred_id_t verifier_cred_handle,
                               krb5_ticket *ticket,
                               krb5_gss_cred_id_t *out_cred,
                               krb5_context context)
{
    OM_uint32 major_status;
    krb5_creds krb_creds;
    krb5_data *data;
    krb5_error_code code;

    assert(out_cred != NULL);
    assert(verifier_cred_handle->usage == GSS_C_BOTH);

    memset(&krb_creds, 0, sizeof(krb_creds));
    krb_creds.client = ticket->enc_part2->client;
    krb_creds.server = ticket->server;
    krb_creds.keyblock = *(ticket->enc_part2->session);
    krb_creds.ticket_flags = ticket->enc_part2->flags;
    krb_creds.times = ticket->enc_part2->times;
    krb_creds.magic = KV5M_CREDS;
    krb_creds.authdata = NULL;

    code = encode_krb5_ticket(ticket, &data);
    if (code) {
        *minor_status = code;
        return GSS_S_FAILURE;
    }

    krb_creds.ticket = *data;

    major_status = kg_compose_deleg_cred(minor_status,
                                         verifier_cred_handle,
                                         &krb_creds,
                                         GSS_C_INDEFINITE,
                                         out_cred,
                                         NULL,
                                         context);

    krb5_free_data(context, data);

    return major_status;
}

/* Decode, decrypt and store the forwarded creds in the local ccache. */
static krb5_error_code
rd_and_store_for_creds(context, auth_context, inbuf, out_cred)
    krb5_context context;
    krb5_auth_context auth_context;
    krb5_data *inbuf;
    krb5_gss_cred_id_t *out_cred;
{
    krb5_creds ** creds = NULL;
    krb5_error_code retval;
    krb5_ccache ccache = NULL;
    krb5_gss_cred_id_t cred = NULL;
    krb5_auth_context new_auth_ctx = NULL;
    krb5_int32 flags_org;

    if ((retval = krb5_auth_con_getflags(context, auth_context, &flags_org)))
        return retval;
    krb5_auth_con_setflags(context, auth_context,
                           0);

    /*
     * By the time krb5_rd_cred is called here (after krb5_rd_req has been
     * called in krb5_gss_accept_sec_context), the "keyblock" field of
     * auth_context contains a pointer to the session key, and the
     * "recv_subkey" field might contain a session subkey.  Either of
     * these (the "recv_subkey" if it isn't NULL, otherwise the
     * "keyblock") might have been used to encrypt the encrypted part of
     * the KRB_CRED message that contains the forwarded credentials.  (The
     * Java Crypto and Security Implementation from the DSTC in Australia
     * always uses the session key.  But apparently it never negotiates a
     * subkey, so this code works fine against a JCSI client.)  Up to the
     * present, though, GSSAPI clients linked against the MIT code (which
     * is almost all GSSAPI clients) don't encrypt the KRB_CRED message at
     * all -- at this level.  So if the first call to krb5_rd_cred fails,
     * we should call it a second time with another auth context freshly
     * created by krb5_auth_con_init.  All of its keyblock fields will be
     * NULL, so krb5_rd_cred will assume that the KRB_CRED message is
     * unencrypted.  (The MIT code doesn't actually send the KRB_CRED
     * message in the clear -- the "authenticator" whose "checksum" ends up
     * containing the KRB_CRED message does get encrypted.)
     */
    if (krb5_rd_cred(context, auth_context, inbuf, &creds, NULL)) {
        if ((retval = krb5_auth_con_init(context, &new_auth_ctx)))
            goto cleanup;
        krb5_auth_con_setflags(context, new_auth_ctx, 0);
        if ((retval = krb5_rd_cred(context, new_auth_ctx, inbuf,
                                   &creds, NULL)))
            goto cleanup;
    }

    if ((retval = krb5_cc_new_unique(context, "MEMORY", NULL, &ccache))) {
        ccache = NULL;
        goto cleanup;
    }

    if ((retval = krb5_cc_initialize(context, ccache, creds[0]->client)))
        goto cleanup;

    if ((retval = k5_cc_store_primary_cred(context, ccache, creds[0])))
        goto cleanup;

    /* generate a delegated credential handle */
    if (out_cred) {
        /* allocate memory for a cred_t... */
        if (!(cred =
              (krb5_gss_cred_id_t) xmalloc(sizeof(krb5_gss_cred_id_rec)))) {
            retval = ENOMEM; /* out of memory? */
            goto cleanup;
        }

        /* zero it out... */
        memset(cred, 0, sizeof(krb5_gss_cred_id_rec));

        retval = k5_mutex_init(&cred->lock);
        if (retval) {
            xfree(cred);
            cred = NULL;
            goto cleanup;
        }

        /* copy the client principle into it... */
        if ((retval =
             kg_init_name(context, creds[0]->client, NULL, NULL, NULL, 0,
                          &cred->name))) {
            k5_mutex_destroy(&cred->lock);
            retval = ENOMEM; /* out of memory? */
            xfree(cred); /* clean up memory on failure */
            cred = NULL;
            goto cleanup;
        }

        cred->usage = GSS_C_INITIATE; /* we can't accept with this */
        /* cred->name already set */
        cred->keytab = NULL; /* no keytab associated with this... */
        cred->expire = creds[0]->times.endtime; /* store the end time */
        cred->ccache = ccache; /* the ccache containing the credential */
        cred->destroy_ccache = 1;
        ccache = NULL; /* cred takes ownership so don't destroy */
    }

    /* If there were errors, there might have been a memory leak
       if (!cred)
       if ((retval = krb5_cc_close(context, ccache)))
       goto cleanup;
    */
cleanup:
    if (creds)
        krb5_free_tgt_creds(context, creds);

    if (ccache)
        (void)krb5_cc_destroy(context, ccache);

    if (out_cred)
        *out_cred = cred; /* return credential */

    if (new_auth_ctx)
        krb5_auth_con_free(context, new_auth_ctx);

    krb5_auth_con_setflags(context, auth_context, flags_org);

    return retval;
}


/*
 * Performs third leg of DCE authentication
 */
static OM_uint32
kg_accept_dce(minor_status, context_handle, verifier_cred_handle,
              input_token, input_chan_bindings, src_name, mech_type,
              output_token, ret_flags, time_rec, delegated_cred_handle)
    OM_uint32 *minor_status;
    gss_ctx_id_t *context_handle;
    gss_cred_id_t verifier_cred_handle;
    gss_buffer_t input_token;
    gss_channel_bindings_t input_chan_bindings;
    gss_name_t *src_name;
    gss_OID *mech_type;
    gss_buffer_t output_token;
    OM_uint32 *ret_flags;
    OM_uint32 *time_rec;
    gss_cred_id_t *delegated_cred_handle;
{
    krb5_error_code code;
    krb5_gss_ctx_id_rec *ctx = 0;
    krb5_timestamp now;
    krb5_gss_name_t name = NULL;
    krb5_ui_4 nonce = 0;
    krb5_data ap_rep;
    OM_uint32 major_status = GSS_S_FAILURE;

    output_token->length = 0;
    output_token->value = NULL;

    if (mech_type)
        *mech_type = GSS_C_NULL_OID;
    /* return a bogus cred handle */
    if (delegated_cred_handle)
        *delegated_cred_handle = GSS_C_NO_CREDENTIAL;

    ctx = (krb5_gss_ctx_id_rec *)*context_handle;

    code = krb5_timeofday(ctx->k5_context, &now);
    if (code != 0) {
        major_status = GSS_S_FAILURE;
        goto fail;
    }

    ap_rep.data = input_token->value;
    ap_rep.length = input_token->length;

    code = krb5_rd_rep_dce(ctx->k5_context,
                           ctx->auth_context,
                           &ap_rep,
                           &nonce);
    if (code != 0) {
        major_status = GSS_S_FAILURE;
        goto fail;
    }

    ctx->established = 1;

    if (src_name) {
        code = kg_duplicate_name(ctx->k5_context, ctx->there, &name);
        if (code) {
            major_status = GSS_S_FAILURE;
            goto fail;
        }
        *src_name = (gss_name_t) name;
    }

    if (mech_type)
        *mech_type = ctx->mech_used;

    if (time_rec) {
        *time_rec = ts_delta(ctx->krb_times.endtime, now) +
            ctx->k5_context->clockskew;
    }

    /* Never return GSS_C_DELEG_FLAG since we don't support DCE credential
     * delegation yet. */
    if (ret_flags)
        *ret_flags = (ctx->gss_flags & ~GSS_C_DELEG_FLAG);

    *minor_status = 0;

    return GSS_S_COMPLETE;

fail:
    /* real failure code follows */

    (void) krb5_gss_delete_sec_context(minor_status, (gss_ctx_id_t *) &ctx,
                                       NULL);
    *context_handle = GSS_C_NO_CONTEXT;
    *minor_status = code;

    return major_status;
}

static krb5_error_code
kg_process_extension(krb5_context context,
                     krb5_auth_context auth_context,
                     int ext_type,
                     krb5_data *ext_data,
                     krb5_gss_ctx_ext_t exts)
{
    krb5_error_code code = 0;

    assert(exts != NULL);

    switch (ext_type) {
    case KRB5_GSS_EXTS_IAKERB_FINISHED:
        if (exts->iakerb.conv == NULL) {
            code = KRB5KRB_AP_ERR_MSG_TYPE; /* XXX */
        } else {
            krb5_key key;

            code = krb5_auth_con_getrecvsubkey_k(context, auth_context, &key);
            if (code != 0)
                break;

            code = iakerb_verify_finished(context, key, exts->iakerb.conv,
                                          ext_data);
            if (code == 0)
                exts->iakerb.verified = 1;

            krb5_k_free_key(context, key);
        }
        break;
    default:
        break;
    }

    return code;
}

/* The length of the MD5 channel bindings in an 0x8003 checksum */
#define CB_MD5_LEN 16

/* The minimum length of an 0x8003 checksum value (4-byte channel bindings
 * length, 16-byte channel bindings, 4-byte flags) */
#define MIN_8003_LEN (4 + CB_MD5_LEN + 4)

/* The flags we accept from the initiator's authenticator checksum. */
#define INITIATOR_FLAGS (GSS_C_INTEG_FLAG | GSS_C_CONF_FLAG |           \
                         GSS_C_MUTUAL_FLAG | GSS_C_REPLAY_FLAG |        \
                         GSS_C_SEQUENCE_FLAG | GSS_C_DCE_STYLE |        \
                         GSS_C_IDENTIFY_FLAG | GSS_C_EXTENDED_ERROR_FLAG)

/* A zero-value channel binding, for comparison */
static const uint8_t null_cb[CB_MD5_LEN];

/* Look for AP_OPTIONS in authdata.  If present and the options include
 * KERB_AP_OPTIONS_CBT, set *cbt_out to true. */
static krb5_error_code
check_cbt(krb5_context context, krb5_authdata *const *authdata,
          krb5_boolean *cbt_out)
{
    krb5_error_code code;
    krb5_authdata **ad;
    uint32_t ad_ap_options;
    const uint32_t KERB_AP_OPTIONS_CBT = 0x4000;

    *cbt_out = FALSE;

    code = krb5_find_authdata(context, NULL, authdata,
                              KRB5_AUTHDATA_AP_OPTIONS, &ad);
    if (code || ad == NULL)
        return code;
    if (ad[1] != NULL || ad[0]->length != 4) {
        code = KRB5KRB_AP_ERR_MSG_TYPE;
    } else {
        ad_ap_options = load_32_le(ad[0]->contents);
        if (ad_ap_options & KERB_AP_OPTIONS_CBT)
            *cbt_out = TRUE;
    }

    krb5_free_authdata(context, ad);
    return code;
}

/*
 * The krb5 GSS mech appropriates the authenticator checksum field from RFC
 * 4120 to store structured data instead of a checksum, indicated with checksum
 * type 0x8003 (see RFC 4121 section 4.1.1).  Some implementations instead send
 * no checksum, or a regular checksum over empty data.
 *
 * Interpret the checksum.  Read delegated creds into *deleg_out if it is not
 * NULL.  Set *flags_out to the allowed subset of token flags, plus
 * GSS_C_DELEG_FLAG if a delegated credential was present and
 * GSS_C_CHANNEL_BOUND_FLAG if matching channel bindings are present.  Process
 * any extensions found using exts.  On error, set *code_out to a krb5_error
 * code for use as a minor status value.
 */
static OM_uint32
process_checksum(OM_uint32 *minor_status, krb5_context context,
                 gss_channel_bindings_t acceptor_cb,
                 krb5_auth_context auth_context, krb5_flags ap_req_options,
                 krb5_authenticator *authenticator, krb5_gss_ctx_ext_t exts,
                 krb5_gss_cred_id_t *deleg_out, krb5_ui_4 *flags_out,
                 krb5_error_code *code_out)
{
    krb5_error_code code = 0;
    OM_uint32 status, option_id, token_flags;
    size_t cb_len, option_len;
    krb5_boolean valid, client_cbt, token_cb_present = FALSE, cb_match = FALSE;
    krb5_key subkey;
    krb5_data option, empty = empty_data();
    krb5_checksum cb_cksum;
    const uint8_t *token_cb, *option_bytes;
    struct k5input in;
    const krb5_checksum *cksum = authenticator->checksum;

    cb_cksum.contents = NULL;

    if (cksum == NULL) {
        /*
         * Some SMB client implementations use handcrafted GSSAPI code that
         * does not provide a checksum.  MS-KILE documents that the Microsoft
         * implementation considers a missing checksum acceptable; the server
         * assumes all flags are unset in this case, and does not check channel
         * bindings.
         */
        *flags_out = 0;
    } else if (cksum->checksum_type != CKSUMTYPE_KG_CB) {
        /* Samba sends a regular checksum. */
        code = krb5_auth_con_getkey_k(context, auth_context, &subkey);
        if (code) {
            status = GSS_S_FAILURE;
            goto fail;
        }

        /* Verifying the checksum ensures that this authenticator wasn't
         * replayed from one with a checksum over actual data. */
        code = krb5_k_verify_checksum(context, subkey,
                                      KRB5_KEYUSAGE_AP_REQ_AUTH_CKSUM, &empty,
                                      cksum, &valid);
        krb5_k_free_key(context, subkey);
        if (code || !valid) {
            status = GSS_S_BAD_SIG;
            goto fail;
        }

        /* Use ap_options from the request to guess the mutual flag. */
        *flags_out = GSS_C_REPLAY_FLAG | GSS_C_SEQUENCE_FLAG;
        if (ap_req_options & AP_OPTS_MUTUAL_REQUIRED)
            *flags_out |= GSS_C_MUTUAL_FLAG;
    } else {
        /* The checksum must contain at least a fixed 24-byte part. */
        if (cksum->length < MIN_8003_LEN) {
            status = GSS_S_BAD_BINDINGS;
            goto fail;
        }

        k5_input_init(&in, cksum->contents, cksum->length);
        cb_len = k5_input_get_uint32_le(&in);
        if (cb_len != CB_MD5_LEN) {
            code = KG_BAD_LENGTH;
            status = GSS_S_FAILURE;
            goto fail;
        }

        token_cb = k5_input_get_bytes(&in, cb_len);
        if (acceptor_cb != GSS_C_NO_CHANNEL_BINDINGS) {
            code = kg_checksum_channel_bindings(context, acceptor_cb,
                                                &cb_cksum);
            if (code) {
                status = GSS_S_BAD_BINDINGS;
                goto fail;
            }
            assert(cb_cksum.length == cb_len);
            token_cb_present = (k5_bcmp(token_cb, null_cb, cb_len) != 0);
            cb_match = (k5_bcmp(token_cb, cb_cksum.contents, cb_len) == 0);
            if (token_cb_present && !cb_match) {
                status = GSS_S_BAD_BINDINGS;
                goto fail;
            }
        }

        /* Read the token flags and accept some of them as context flags. */
        token_flags = k5_input_get_uint32_le(&in);
        *flags_out = token_flags & INITIATOR_FLAGS;
        if (cb_match)
            *flags_out |= GSS_C_CHANNEL_BOUND_FLAG;

        /* Read the delegated credential if present. */
        if (in.len >= 4 && (token_flags & GSS_C_DELEG_FLAG)) {
            option_id = k5_input_get_uint16_le(&in);
            option_len = k5_input_get_uint16_le(&in);
            option_bytes = k5_input_get_bytes(&in, option_len);
            option = make_data((uint8_t *)option_bytes, option_len);
            if (in.status) {
                code = KG_BAD_LENGTH;
                status = GSS_S_FAILURE;
                goto fail;
            }
            if (option_id != KRB5_GSS_FOR_CREDS_OPTION) {
                status = GSS_S_FAILURE;
                goto fail;
            }

            /* Store the delegated credential. */
            code = rd_and_store_for_creds(context, auth_context, &option,
                                          deleg_out);
            if (code) {
                status = GSS_S_FAILURE;
                goto fail;
            }
            *flags_out |= GSS_C_DELEG_FLAG;
        }

        /* Process any extensions at the end of the checksum.  Extensions use
         * 4-byte big-endian tag and length instead of 2-byte little-endian. */
        while (in.len > 0) {
            option_id = k5_input_get_uint32_be(&in);
            option_len = k5_input_get_uint32_be(&in);
            option_bytes = k5_input_get_bytes(&in, option_len);
            option = make_data((uint8_t *)option_bytes, option_len);
            if (in.status) {
                code = KG_BAD_LENGTH;
                status = GSS_S_FAILURE;
                goto fail;
            }

            code = kg_process_extension(context, auth_context, option_id,
                                        &option, exts);
            if (code) {
                status = GSS_S_FAILURE;
                goto fail;
            }
        }
    }

    /*
     * If the client asserts the KERB_AP_OPTIONS_CBT flag (from MS-KILE) in the
     * authenticator authdata, and the acceptor passed channel bindings,
     * require matching channel bindings from the client.  The intent is to
     * prevent an authenticator generated for use outside of a TLS channel from
     * being used inside of one.
     */
    code = check_cbt(context, authenticator->authorization_data, &client_cbt);
    if (code) {
        status = GSS_S_FAILURE;
        goto fail;
    }
    if (client_cbt && acceptor_cb != GSS_C_NO_CHANNEL_BINDINGS && !cb_match) {
        status = GSS_S_BAD_BINDINGS;
        goto fail;
    }

    status = GSS_S_COMPLETE;

fail:
    free(cb_cksum.contents);
    *code_out = code;
    return status;
}

static OM_uint32
kg_accept_krb5(minor_status, context_handle,
               verifier_cred_handle, input_token,
               input_chan_bindings, src_name, mech_type,
               output_token, ret_flags, time_rec,
               delegated_cred_handle, exts)
    OM_uint32 *minor_status;
    gss_ctx_id_t *context_handle;
    gss_cred_id_t verifier_cred_handle;
    gss_buffer_t input_token;
    gss_channel_bindings_t input_chan_bindings;
    gss_name_t *src_name;
    gss_OID *mech_type;
    gss_buffer_t output_token;
    OM_uint32 *ret_flags;
    OM_uint32 *time_rec;
    gss_cred_id_t *delegated_cred_handle;
    krb5_gss_ctx_ext_t exts;
{
    krb5_context context;
    unsigned char *ptr;
    char *sptr;
    krb5_gss_cred_id_t cred = 0;
    krb5_data ap_rep, ap_req;
    krb5_error_code code;
    krb5_address addr, *paddr;
    krb5_authenticator *authdat = 0;
    krb5_gss_name_t name = NULL;
    krb5_ui_4 gss_flags = 0;
    krb5_gss_ctx_id_rec *ctx = NULL;
    krb5_timestamp now;
    gss_buffer_desc token;
    krb5_auth_context auth_context = NULL;
    krb5_ticket * ticket = NULL;
    const gss_OID_desc *mech_used = NULL;
    OM_uint32 major_status;
    OM_uint32 tmp_minor_status;
    krb5_error krb_error_data;
    krb5_data scratch;
    gss_cred_id_t defcred = GSS_C_NO_CREDENTIAL;
    krb5_gss_cred_id_t deleg_cred = NULL;
    krb5int_access kaccess;
    int cred_rcache = 0;
    int no_encap = 0;
    krb5_flags ap_req_options = 0;
    krb5_enctype negotiated_etype;
    krb5_authdata_context ad_context = NULL;
    krb5_ap_req *request = NULL;

    code = krb5int_accessor (&kaccess, KRB5INT_ACCESS_VERSION);
    if (code) {
        *minor_status = code;
        return(GSS_S_FAILURE);
    }

    code = krb5_gss_init_context(&context);
    if (code) {
        *minor_status = code;
        return GSS_S_FAILURE;
    }

    /* set up returns to be freeable */

    if (src_name)
        *src_name = (gss_name_t) NULL;
    output_token->length = 0;
    output_token->value = NULL;
    token.value = 0;
    ap_req.data = 0;
    ap_rep.data = 0;

    if (mech_type)
        *mech_type = GSS_C_NULL_OID;
    /* return a bogus cred handle */
    if (delegated_cred_handle)
        *delegated_cred_handle = GSS_C_NO_CREDENTIAL;

    /* handle default cred handle */
    if (verifier_cred_handle == GSS_C_NO_CREDENTIAL) {
        major_status = krb5_gss_acquire_cred(minor_status, GSS_C_NO_NAME,
                                             GSS_C_INDEFINITE, GSS_C_NO_OID_SET,
                                             GSS_C_ACCEPT, &defcred,
                                             NULL, NULL);
        if (major_status != GSS_S_COMPLETE) {
            code = *minor_status;
            goto fail;
        }
        verifier_cred_handle = defcred;
    }

    /* Resolve any initiator state in the verifier cred and lock it. */
    major_status = kg_cred_resolve(minor_status, context, verifier_cred_handle,
                                   GSS_C_NO_NAME);
    if (GSS_ERROR(major_status)) {
        code = *minor_status;
        goto fail;
    }
    cred = (krb5_gss_cred_id_t)verifier_cred_handle;

    /* make sure the supplied credentials are valid for accept */

    if ((cred->usage != GSS_C_ACCEPT) &&
        (cred->usage != GSS_C_BOTH)) {
        code = 0;
        major_status = GSS_S_NO_CRED;
        goto fail;
    }

    /* verify the token's integrity, and leave the token in ap_req.
       figure out which mech oid was used, and save it */

    ptr = (unsigned char *) input_token->value;

    if (!(code = g_verify_token_header(gss_mech_krb5,
                                       &(ap_req.length),
                                       &ptr, KG_TOK_CTX_AP_REQ,
                                       input_token->length, 1))) {
        mech_used = gss_mech_krb5;
    } else if ((code == G_WRONG_MECH)
               &&!(code = g_verify_token_header((gss_OID) gss_mech_iakerb,
                                                &(ap_req.length),
                                                &ptr, KG_TOK_CTX_AP_REQ,
                                                input_token->length, 1))) {
        mech_used = gss_mech_iakerb;
    } else if ((code == G_WRONG_MECH)
               &&!(code = g_verify_token_header((gss_OID) gss_mech_krb5_wrong,
                                                &(ap_req.length),
                                                &ptr, KG_TOK_CTX_AP_REQ,
                                                input_token->length, 1))) {
        mech_used = gss_mech_krb5_wrong;
    } else if ((code == G_WRONG_MECH) &&
               !(code = g_verify_token_header(gss_mech_krb5_old,
                                              &(ap_req.length),
                                              &ptr, KG_TOK_CTX_AP_REQ,
                                              input_token->length, 1))) {
        /*
         * Previous versions of this library used the old mech_id
         * and some broken behavior (wrong IV on checksum
         * encryption).  We support the old mech_id for
         * compatibility, and use it to decide when to use the
         * old behavior.
         */
        mech_used = gss_mech_krb5_old;
    } else if (code == G_WRONG_TOKID) {
        major_status = GSS_S_CONTINUE_NEEDED;
        code = KRB5KRB_AP_ERR_MSG_TYPE;
        mech_used = gss_mech_krb5;
        goto fail;
    } else if (code == G_BAD_TOK_HEADER) {
        /* DCE style not encapsulated */
        ap_req.length = input_token->length;
        ap_req.data = input_token->value;
        mech_used = gss_mech_krb5;
        no_encap = 1;
    } else {
        major_status = GSS_S_DEFECTIVE_TOKEN;
        goto fail;
    }

    sptr = (char *) ptr;
    TREAD_STR(sptr, ap_req.data, ap_req.length);

    /* construct the sender_addr */

    if ((input_chan_bindings != GSS_C_NO_CHANNEL_BINDINGS) &&
        (input_chan_bindings->initiator_addrtype == GSS_C_AF_INET)) {
        /* XXX is this right? */
        addr.addrtype = ADDRTYPE_INET;
        addr.length = input_chan_bindings->initiator_address.length;
        addr.contents = input_chan_bindings->initiator_address.value;

        paddr = &addr;
    } else {
        paddr = NULL;
    }

    /* decode the AP_REQ message */
    code = decode_krb5_ap_req(&ap_req, &request);
    if (code) {
        major_status = GSS_S_FAILURE;
        goto done;
    }
    ticket = request->ticket;

    /* decode the message */

    if ((code = krb5_auth_con_init(context, &auth_context))) {
        major_status = GSS_S_FAILURE;
        save_error_info((OM_uint32)code, context);
        goto fail;
    }
    if (cred->rcache) {
        cred_rcache = 1;
        if ((code = krb5_auth_con_setrcache(context, auth_context, cred->rcache))) {
            major_status = GSS_S_FAILURE;
            goto fail;
        }
    }
    if ((code = krb5_auth_con_setaddrs(context, auth_context, NULL, paddr))) {
        major_status = GSS_S_FAILURE;
        goto fail;
    }

    /* Limit the encryption types negotiated (if requested). */
    if (cred->req_enctypes) {
        if ((code = krb5_auth_con_setpermetypes(context, auth_context,
                                                cred->req_enctypes))) {
            major_status = GSS_S_FAILURE;
            goto fail;
        }
    }

    code = krb5_rd_req_decoded(context, &auth_context, request,
                               cred->acceptor_mprinc, cred->keytab,
                               &ap_req_options, NULL);
    if (code) {
        major_status = GSS_S_FAILURE;
        goto fail;
    }
    krb5_auth_con_setflags(context, auth_context,
                           KRB5_AUTH_CONTEXT_DO_SEQUENCE);

    krb5_auth_con_getauthenticator(context, auth_context, &authdat);

    major_status = process_checksum(minor_status, context, input_chan_bindings,
                                    auth_context, ap_req_options,
                                    authdat, exts,
                                    delegated_cred_handle ? &deleg_cred : NULL,
                                    &gss_flags, &code);

    if (major_status != GSS_S_COMPLETE)
        goto fail;

    if (exts->iakerb.conv && !exts->iakerb.verified) {
        major_status = GSS_S_BAD_SIG;
        goto fail;
    }

    /* only DCE_STYLE clients are allowed to send raw AP-REQs */
    if (no_encap != ((gss_flags & GSS_C_DCE_STYLE) != 0)) {
        major_status = GSS_S_DEFECTIVE_TOKEN;
        goto fail;
    }

    /* create the ctx struct and start filling it in */

    if ((ctx = (krb5_gss_ctx_id_rec *) xmalloc(sizeof(krb5_gss_ctx_id_rec)))
        == NULL) {
        code = ENOMEM;
        major_status = GSS_S_FAILURE;
        goto fail;
    }

    memset(ctx, 0, sizeof(krb5_gss_ctx_id_rec));
    ctx->magic = KG_CONTEXT;
    ctx->mech_used = (gss_OID) mech_used;
    ctx->auth_context = auth_context;
    ctx->initiate = 0;
    ctx->gss_flags = gss_flags | GSS_C_TRANS_FLAG;
    ctx->seed_init = 0;
    ctx->cred_rcache = cred_rcache;

    /* XXX move this into gss_name_t */
    if (        (code = krb5_merge_authdata(context,
                                            ticket->enc_part2->authorization_data,
                                            authdat->authorization_data,
                                            &ctx->authdata))) {
        major_status = GSS_S_FAILURE;
        goto fail;
    }
    if ((code = kg_init_name(context, ticket->server, NULL, NULL, NULL, 0,
                             &ctx->here))) {
        major_status = GSS_S_FAILURE;
        goto fail;
    }
    if ((code = krb5_auth_con_get_authdata_context(context, auth_context,
                                                   &ad_context))) {
        major_status = GSS_S_FAILURE;
        goto fail;
    }
    if ((code = kg_init_name(context, authdat->client, NULL, NULL,
                             ad_context, KG_INIT_NAME_NO_COPY, &ctx->there))) {
        major_status = GSS_S_FAILURE;
        goto fail;
    }
    /* Now owned by ctx->there */
    authdat->client = NULL;
    krb5_auth_con_set_authdata_context(context, auth_context, NULL);

    if ((code = krb5_auth_con_getrecvsubkey_k(context, auth_context,
                                              &ctx->subkey))) {
        major_status = GSS_S_FAILURE;
        goto fail;
    }

    /* use the session key if the subkey isn't present */

    if (ctx->subkey == NULL) {
        if ((code = krb5_auth_con_getkey_k(context, auth_context,
                                           &ctx->subkey))) {
            major_status = GSS_S_FAILURE;
            goto fail;
        }
    }

    if (ctx->subkey == NULL) {
        /* this isn't a very good error, but it's not clear to me this
           can actually happen */
        major_status = GSS_S_FAILURE;
        code = KRB5KDC_ERR_NULL_KEY;
        goto fail;
    }

    ctx->enc = NULL;
    ctx->seq = NULL;
    ctx->have_acceptor_subkey = 0;
    /* DCE_STYLE implies acceptor_subkey */
    if ((ctx->gss_flags & GSS_C_DCE_STYLE) == 0) {
        code = kg_setup_keys(context, ctx, ctx->subkey, &ctx->cksumtype);
        if (code) {
            major_status = GSS_S_FAILURE;
            goto fail;
        }
    }
    ctx->krb_times = ticket->enc_part2->times; /* struct copy */
    ctx->krb_flags = ticket->enc_part2->flags;

    if (delegated_cred_handle != NULL &&
        deleg_cred == NULL && /* no unconstrained delegation */
        cred->usage == GSS_C_BOTH) {
        /*
         * Now, we always fabricate a delegated credentials handle
         * containing the service ticket to ourselves, which can be
         * used for S4U2Proxy.
         */
        major_status = create_constrained_deleg_creds(minor_status, cred,
                                                      ticket, &deleg_cred,
                                                      context);
        if (GSS_ERROR(major_status))
            goto fail;
        ctx->gss_flags |= GSS_C_DELEG_FLAG;
    }

    {
        krb5_int32 seq_temp;
        krb5_auth_con_getremoteseqnumber(context, auth_context, &seq_temp);
        ctx->seq_recv = seq_temp;
    }

    if ((code = krb5_timeofday(context, &now))) {
        major_status = GSS_S_FAILURE;
        goto fail;
    }

    code = g_seqstate_init(&ctx->seqstate, ctx->seq_recv,
                           (ctx->gss_flags & GSS_C_REPLAY_FLAG) != 0,
                           (ctx->gss_flags & GSS_C_SEQUENCE_FLAG) != 0,
                           ctx->proto);
    if (code) {
        major_status = GSS_S_FAILURE;
        goto fail;
    }

    /* DCE_STYLE implies mutual authentication */
    if (ctx->gss_flags & GSS_C_DCE_STYLE)
        ctx->gss_flags |= GSS_C_MUTUAL_FLAG;

    /* at this point, the entire context structure is filled in,
       so it can be released.  */

    /* generate an AP_REP if necessary */

    if (ctx->gss_flags & GSS_C_MUTUAL_FLAG) {
        unsigned char * ptr3;
        krb5_int32 seq_temp;
        int cfx_generate_subkey;

        /*
         * Do not generate a subkey per RFC 4537 unless we are upgrading to CFX,
         * because pre-CFX tokens do not indicate which key to use. (Note that
         * DCE_STYLE implies that we will use a subkey.)
         */
        if (ctx->proto == 0 &&
            (ctx->gss_flags & GSS_C_DCE_STYLE) == 0 &&
            (ap_req_options & AP_OPTS_USE_SUBKEY)) {
            code = (*kaccess.auth_con_get_subkey_enctype)(context,
                                                          auth_context,
                                                          &negotiated_etype);
            if (code != 0) {
                major_status = GSS_S_FAILURE;
                goto fail;
            }

            switch (negotiated_etype) {
            case ENCTYPE_ARCFOUR_HMAC:
            case ENCTYPE_ARCFOUR_HMAC_EXP:
                /* RFC 4121 accidentally omits RC4-HMAC-EXP as a "not-newer"
                 * enctype, even though RFC 4757 treats it as one. */
                ap_req_options &= ~(AP_OPTS_USE_SUBKEY);
                break;
            }
        }

        if (ctx->proto == 1 || (ctx->gss_flags & GSS_C_DCE_STYLE) ||
            (ap_req_options & AP_OPTS_USE_SUBKEY))
            cfx_generate_subkey = CFX_ACCEPTOR_SUBKEY;
        else
            cfx_generate_subkey = 0;

        if (cfx_generate_subkey) {
            krb5_int32 acflags;
            code = krb5_auth_con_getflags(context, auth_context, &acflags);
            if (code == 0) {
                acflags |= KRB5_AUTH_CONTEXT_USE_SUBKEY;
                code = krb5_auth_con_setflags(context, auth_context, acflags);
            }
            if (code) {
                major_status = GSS_S_FAILURE;
                goto fail;
            }
        }

        if ((code = krb5_mk_rep(context, auth_context, &ap_rep))) {
            major_status = GSS_S_FAILURE;
            goto fail;
        }

        krb5_auth_con_getlocalseqnumber(context, auth_context, &seq_temp);
        ctx->seq_send = seq_temp & 0xffffffffL;

        if (cfx_generate_subkey) {
            /* Get the new acceptor subkey.  With the code above, there
               should always be one if we make it to this point.  */
            code = krb5_auth_con_getsendsubkey_k(context, auth_context,
                                                 &ctx->acceptor_subkey);
            if (code != 0) {
                major_status = GSS_S_FAILURE;
                goto fail;
            }
            ctx->have_acceptor_subkey = 1;

            code = kg_setup_keys(context, ctx, ctx->acceptor_subkey,
                                 &ctx->acceptor_subkey_cksumtype);
            if (code) {
                major_status = GSS_S_FAILURE;
                goto fail;
            }
        }

        /* the reply token hasn't been sent yet, but that's ok. */
        if (ctx->gss_flags & GSS_C_DCE_STYLE) {
            assert(ctx->have_acceptor_subkey);

            /* in order to force acceptor subkey to be used, don't set PROT_READY */

            /* Raw AP-REP is returned */
            code = data_to_gss(&ap_rep, output_token);
            if (code)
            {
                major_status = GSS_S_FAILURE;
                goto fail;
            }

            ctx->established = 0;

            *context_handle = (gss_ctx_id_t)ctx;
            *minor_status = 0;
            major_status = GSS_S_CONTINUE_NEEDED;

            /* Only last leg should set return arguments */
            goto fail;
        } else
            ctx->gss_flags |= GSS_C_PROT_READY_FLAG;

        ctx->established = 1;

        token.length = g_token_size(mech_used, ap_rep.length);

        if ((token.value = (unsigned char *) gssalloc_malloc(token.length))
            == NULL) {
            major_status = GSS_S_FAILURE;
            code = ENOMEM;
            goto fail;
        }
        ptr3 = token.value;
        g_make_token_header(mech_used, ap_rep.length,
                            &ptr3, KG_TOK_CTX_AP_REP);

        TWRITE_STR(ptr3, ap_rep.data, ap_rep.length);

        ctx->established = 1;

    } else {
        token.length = 0;
        token.value = NULL;
        ctx->seq_send = ctx->seq_recv;

        ctx->established = 1;
    }

    /* set the return arguments */

    if (src_name) {
        code = kg_duplicate_name(context, ctx->there, &name);
        if (code) {
            major_status = GSS_S_FAILURE;
            goto fail;
        }
    }

    if (mech_type)
        *mech_type = (gss_OID) mech_used;

    /* Add the maximum allowable clock skew as a grace period for context
     * expiration, just as we do for the ticket. */
    if (time_rec)
        *time_rec = ts_delta(ctx->krb_times.endtime, now) + context->clockskew;

    if (ret_flags)
        *ret_flags = ctx->gss_flags;

    *context_handle = (gss_ctx_id_t)ctx;
    *output_token = token;

    if (src_name)
        *src_name = (gss_name_t) name;

    if (delegated_cred_handle)
        *delegated_cred_handle = (gss_cred_id_t) deleg_cred;

    /* finally! */

    *minor_status = 0;
    major_status = GSS_S_COMPLETE;

fail:
    if (authdat)
        krb5_free_authenticator(context, authdat);
    /* The ctx structure has the handle of the auth_context */
    if (auth_context && !ctx) {
        if (cred_rcache)
            (void)krb5_auth_con_setrcache(context, auth_context, NULL);

        krb5_auth_con_free(context, auth_context);
    }
    if (ap_rep.data)
        krb5_free_data_contents(context, &ap_rep);
    if (major_status == GSS_S_COMPLETE ||
        (major_status == GSS_S_CONTINUE_NEEDED && code != KRB5KRB_AP_ERR_MSG_TYPE)) {
        ctx->k5_context = context;
        context = NULL;
        goto done;
    }

    /* from here on is the real "fail" code */

    if (ctx)
        (void) krb5_gss_delete_sec_context(&tmp_minor_status,
                                           (gss_ctx_id_t *) &ctx, NULL);
    if (deleg_cred) { /* free memory associated with the deleg credential */
        if (deleg_cred->ccache)
            (void)krb5_cc_close(context, deleg_cred->ccache);
        if (deleg_cred->name)
            kg_release_name(context, &deleg_cred->name);
        xfree(deleg_cred);
    }
    if (token.value)
        xfree(token.value);
    if (name) {
        (void) kg_release_name(context, &name);
    }

    *minor_status = code;

    /* We may have failed before being able to read the GSS flags from the
     * authenticator, so also check the request AP options. */
    if (cred != NULL && request != NULL &&
        ((gss_flags & GSS_C_MUTUAL_FLAG) ||
         (request->ap_options & AP_OPTS_MUTUAL_REQUIRED) ||
         major_status == GSS_S_CONTINUE_NEEDED)) {
        unsigned int tmsglen;
        int toktype;

        /*
         * The client is expecting a response, so we can send an
         * error token back
         */
        memset(&krb_error_data, 0, sizeof(krb_error_data));

        code -= ERROR_TABLE_BASE_krb5;
        if (code < 0 || code > KRB_ERR_MAX)
            code = 60 /* KRB_ERR_GENERIC */;

        krb_error_data.error = code;
        (void) krb5_us_timeofday(context, &krb_error_data.stime,
                                 &krb_error_data.susec);

        krb_error_data.server = ticket->server;
        code = krb5_mk_error(context, &krb_error_data, &scratch);
        if (code)
            goto done;

        tmsglen = scratch.length;
        toktype = KG_TOK_CTX_ERROR;

        token.length = g_token_size(mech_used, tmsglen);
        token.value = gssalloc_malloc(token.length);
        if (!token.value)
            goto done;

        ptr = token.value;
        g_make_token_header(mech_used, tmsglen, &ptr, toktype);

        TWRITE_STR(ptr, scratch.data, scratch.length);
        krb5_free_data_contents(context, &scratch);

        *output_token = token;
    }

done:
    krb5_free_ap_req(context, request);
    if (cred)
        k5_mutex_unlock(&cred->lock);
    if (defcred)
        krb5_gss_release_cred(&tmp_minor_status, &defcred);
    if (context) {
        if (major_status && *minor_status)
            save_error_info(*minor_status, context);
        krb5_free_context(context);
    }
    return (major_status);
}
#endif /* LEAN_CLIENT */

OM_uint32 KRB5_CALLCONV
krb5_gss_accept_sec_context_ext(
    OM_uint32 *minor_status,
    gss_ctx_id_t *context_handle,
    gss_cred_id_t verifier_cred_handle,
    gss_buffer_t input_token,
    gss_channel_bindings_t input_chan_bindings,
    gss_name_t *src_name,
    gss_OID *mech_type,
    gss_buffer_t output_token,
    OM_uint32 *ret_flags,
    OM_uint32 *time_rec,
    gss_cred_id_t *delegated_cred_handle,
    krb5_gss_ctx_ext_t exts)
{
    krb5_gss_ctx_id_rec *ctx = (krb5_gss_ctx_id_rec *)*context_handle;

    /*
     * Context handle must be unspecified.  Actually, it must be
     * non-established, but currently, accept_sec_context never returns
     * a non-established context handle.
     */
    /*SUPPRESS 29*/
    if (ctx != NULL) {
        if (ctx->established == 0 && (ctx->gss_flags & GSS_C_DCE_STYLE)) {
            return kg_accept_dce(minor_status, context_handle,
                                 verifier_cred_handle, input_token,
                                 input_chan_bindings, src_name, mech_type,
                                 output_token, ret_flags, time_rec,
                                 delegated_cred_handle);
        } else {
            *minor_status = EINVAL;
            save_error_string(EINVAL, "accept_sec_context called with existing context handle");
            return GSS_S_FAILURE;
        }
    }

    return kg_accept_krb5(minor_status, context_handle,
                          verifier_cred_handle, input_token,
                          input_chan_bindings, src_name, mech_type,
                          output_token, ret_flags, time_rec,
                          delegated_cred_handle, exts);
}

OM_uint32 KRB5_CALLCONV
krb5_gss_accept_sec_context(minor_status, context_handle,
                            verifier_cred_handle, input_token,
                            input_chan_bindings, src_name, mech_type,
                            output_token, ret_flags, time_rec,
                            delegated_cred_handle)
    OM_uint32 *minor_status;
    gss_ctx_id_t *context_handle;
    gss_cred_id_t verifier_cred_handle;
    gss_buffer_t input_token;
    gss_channel_bindings_t input_chan_bindings;
    gss_name_t *src_name;
    gss_OID *mech_type;
    gss_buffer_t output_token;
    OM_uint32 *ret_flags;
    OM_uint32 *time_rec;
    gss_cred_id_t *delegated_cred_handle;
{
    krb5_gss_ctx_ext_rec exts;

    memset(&exts, 0, sizeof(exts));

    return krb5_gss_accept_sec_context_ext(minor_status,
                                           context_handle,
                                           verifier_cred_handle,
                                           input_token,
                                           input_chan_bindings,
                                           src_name,
                                           mech_type,
                                           output_token,
                                           ret_flags,
                                           time_rec,
                                           delegated_cred_handle,
                                           &exts);
}
