=== added directory '.pc/CVE-2014-4341-4342.patch' === added directory '.pc/CVE-2014-4341-4342.patch/src' === added directory '.pc/CVE-2014-4341-4342.patch/src/lib' === added directory '.pc/CVE-2014-4341-4342.patch/src/lib/gssapi' === added directory '.pc/CVE-2014-4341-4342.patch/src/lib/gssapi/krb5' === added file '.pc/CVE-2014-4341-4342.patch/src/lib/gssapi/krb5/k5unseal.c' --- .pc/CVE-2014-4341-4342.patch/src/lib/gssapi/krb5/k5unseal.c 1970-01-01 00:00:00 +0000 +++ .pc/CVE-2014-4341-4342.patch/src/lib/gssapi/krb5/k5unseal.c 2014-08-12 11:29:31 +0000 @@ -0,0 +1,563 @@ +/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */ +/* + * Copyright 2001, 2007 by the Massachusetts Institute of Technology. + * 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. + */ + +#include "gssapiP_krb5.h" +#ifdef HAVE_MEMORY_H +#include +#endif +#include + +/* message_buffer is an input if SIGN, output if SEAL, and ignored if DEL_CTX + conf_state is only valid if SEAL. */ + +static OM_uint32 +kg_unseal_v1(context, minor_status, ctx, ptr, bodysize, message_buffer, + conf_state, qop_state, toktype) + krb5_context context; + OM_uint32 *minor_status; + krb5_gss_ctx_id_rec *ctx; + unsigned char *ptr; + int bodysize; + gss_buffer_t message_buffer; + int *conf_state; + gss_qop_t *qop_state; + int toktype; +{ + krb5_error_code code; + int conflen = 0; + int signalg; + int sealalg; + gss_buffer_desc token; + krb5_checksum cksum; + krb5_checksum md5cksum; + krb5_data plaind; + char *data_ptr; + unsigned char *plain; + unsigned int cksum_len = 0; + size_t plainlen; + int direction; + krb5_ui_4 seqnum; + OM_uint32 retval; + size_t sumlen; + krb5_keyusage sign_usage = KG_USAGE_SIGN; + + if (toktype == KG_TOK_SEAL_MSG) { + message_buffer->length = 0; + message_buffer->value = NULL; + } + + /* get the sign and seal algorithms */ + + signalg = ptr[0] + (ptr[1]<<8); + sealalg = ptr[2] + (ptr[3]<<8); + + /* Sanity checks */ + + if ((ptr[4] != 0xff) || (ptr[5] != 0xff)) { + *minor_status = 0; + return GSS_S_DEFECTIVE_TOKEN; + } + + if ((toktype != KG_TOK_SEAL_MSG) && + (sealalg != 0xffff)) { + *minor_status = 0; + return GSS_S_DEFECTIVE_TOKEN; + } + + /* in the current spec, there is only one valid seal algorithm per + key type, so a simple comparison is ok */ + + if ((toktype == KG_TOK_SEAL_MSG) && + !((sealalg == 0xffff) || + (sealalg == ctx->sealalg))) { + *minor_status = 0; + return GSS_S_DEFECTIVE_TOKEN; + } + + /* there are several mappings of seal algorithms to sign algorithms, + but few enough that we can try them all. */ + + if ((ctx->sealalg == SEAL_ALG_NONE && signalg > 1) || + (ctx->sealalg == SEAL_ALG_1 && signalg != SGN_ALG_3) || + (ctx->sealalg == SEAL_ALG_DES3KD && + signalg != SGN_ALG_HMAC_SHA1_DES3_KD)|| + (ctx->sealalg == SEAL_ALG_MICROSOFT_RC4 && + signalg != SGN_ALG_HMAC_MD5)) { + *minor_status = 0; + return GSS_S_DEFECTIVE_TOKEN; + } + + switch (signalg) { + case SGN_ALG_DES_MAC_MD5: + case SGN_ALG_MD2_5: + case SGN_ALG_HMAC_MD5: + cksum_len = 8; + if (toktype != KG_TOK_SEAL_MSG) + sign_usage = 15; + break; + case SGN_ALG_3: + cksum_len = 16; + break; + case SGN_ALG_HMAC_SHA1_DES3_KD: + cksum_len = 20; + break; + default: + *minor_status = 0; + return GSS_S_DEFECTIVE_TOKEN; + } + + /* get the token parameters */ + + if ((code = kg_get_seq_num(context, ctx->seq, ptr+14, ptr+6, &direction, + &seqnum))) { + *minor_status = code; + return(GSS_S_BAD_SIG); + } + + /* decode the message, if SEAL */ + + if (toktype == KG_TOK_SEAL_MSG) { + size_t tmsglen = bodysize-(14+cksum_len); + if (sealalg != 0xffff) { + if ((plain = (unsigned char *) xmalloc(tmsglen)) == NULL) { + *minor_status = ENOMEM; + return(GSS_S_FAILURE); + } + if (ctx->sealalg == SEAL_ALG_MICROSOFT_RC4) { + unsigned char bigend_seqnum[4]; + krb5_keyblock *enc_key; + int i; + store_32_be(seqnum, bigend_seqnum); + code = krb5_k_key_keyblock(context, ctx->enc, &enc_key); + if (code) + { + xfree(plain); + *minor_status = code; + return(GSS_S_FAILURE); + } + + assert (enc_key->length == 16); + for (i = 0; i <= 15; i++) + ((char *) enc_key->contents)[i] ^=0xf0; + code = kg_arcfour_docrypt (enc_key, 0, + &bigend_seqnum[0], 4, + ptr+14+cksum_len, tmsglen, + plain); + krb5_free_keyblock (context, enc_key); + } else { + code = kg_decrypt(context, ctx->enc, KG_USAGE_SEAL, NULL, + ptr+14+cksum_len, plain, tmsglen); + } + if (code) { + xfree(plain); + *minor_status = code; + return(GSS_S_FAILURE); + } + } else { + plain = ptr+14+cksum_len; + } + + plainlen = tmsglen; + + conflen = kg_confounder_size(context, ctx->enc->keyblock.enctype); + token.length = tmsglen - conflen - plain[tmsglen-1]; + + if (token.length) { + if ((token.value = (void *) gssalloc_malloc(token.length)) == NULL) { + if (sealalg != 0xffff) + xfree(plain); + *minor_status = ENOMEM; + return(GSS_S_FAILURE); + } + memcpy(token.value, plain+conflen, token.length); + } else { + token.value = NULL; + } + } else if (toktype == KG_TOK_SIGN_MSG) { + token = *message_buffer; + plain = token.value; + plainlen = token.length; + } else { + token.length = 0; + token.value = NULL; + plain = token.value; + plainlen = token.length; + } + + /* compute the checksum of the message */ + + /* initialize the the cksum */ + switch (signalg) { + case SGN_ALG_DES_MAC_MD5: + case SGN_ALG_MD2_5: + case SGN_ALG_DES_MAC: + case SGN_ALG_3: + md5cksum.checksum_type = CKSUMTYPE_RSA_MD5; + break; + case SGN_ALG_HMAC_MD5: + md5cksum.checksum_type = CKSUMTYPE_HMAC_MD5_ARCFOUR; + break; + case SGN_ALG_HMAC_SHA1_DES3_KD: + md5cksum.checksum_type = CKSUMTYPE_HMAC_SHA1_DES3; + break; + default: + abort (); + } + + code = krb5_c_checksum_length(context, md5cksum.checksum_type, &sumlen); + if (code) + return(code); + md5cksum.length = sumlen; + + switch (signalg) { + case SGN_ALG_DES_MAC_MD5: + case SGN_ALG_3: + /* compute the checksum of the message */ + + /* 8 = bytes of token body to be checksummed according to spec */ + + if (! (data_ptr = xmalloc(8 + plainlen))) { + if (sealalg != 0xffff) + xfree(plain); + if (toktype == KG_TOK_SEAL_MSG) + gssalloc_free(token.value); + *minor_status = ENOMEM; + return(GSS_S_FAILURE); + } + + (void) memcpy(data_ptr, ptr-2, 8); + + (void) memcpy(data_ptr+8, plain, plainlen); + + plaind.length = 8 + plainlen; + plaind.data = data_ptr; + code = krb5_k_make_checksum(context, md5cksum.checksum_type, + ctx->seq, sign_usage, + &plaind, &md5cksum); + xfree(data_ptr); + + if (code) { + if (toktype == KG_TOK_SEAL_MSG) + gssalloc_free(token.value); + *minor_status = code; + return(GSS_S_FAILURE); + } + + code = kg_encrypt_inplace(context, ctx->seq, KG_USAGE_SEAL, + (g_OID_equal(ctx->mech_used, + gss_mech_krb5_old) ? + ctx->seq->keyblock.contents : NULL), + md5cksum.contents, 16); + if (code) { + krb5_free_checksum_contents(context, &md5cksum); + if (toktype == KG_TOK_SEAL_MSG) + gssalloc_free(token.value); + *minor_status = code; + return GSS_S_FAILURE; + } + + if (signalg == 0) + cksum.length = 8; + else + cksum.length = 16; + cksum.contents = md5cksum.contents + 16 - cksum.length; + + code = k5_bcmp(cksum.contents, ptr + 14, cksum.length); + break; + + case SGN_ALG_MD2_5: + if (!ctx->seed_init && + (code = kg_make_seed(context, ctx->subkey, ctx->seed))) { + krb5_free_checksum_contents(context, &md5cksum); + if (sealalg != 0xffff) + xfree(plain); + if (toktype == KG_TOK_SEAL_MSG) + gssalloc_free(token.value); + *minor_status = code; + return GSS_S_FAILURE; + } + + if (! (data_ptr = xmalloc(sizeof(ctx->seed) + 8 + plainlen))) { + krb5_free_checksum_contents(context, &md5cksum); + if (sealalg == 0) + xfree(plain); + if (toktype == KG_TOK_SEAL_MSG) + gssalloc_free(token.value); + *minor_status = ENOMEM; + return(GSS_S_FAILURE); + } + (void) memcpy(data_ptr, ptr-2, 8); + (void) memcpy(data_ptr+8, ctx->seed, sizeof(ctx->seed)); + (void) memcpy(data_ptr+8+sizeof(ctx->seed), plain, plainlen); + plaind.length = 8 + sizeof(ctx->seed) + plainlen; + plaind.data = data_ptr; + krb5_free_checksum_contents(context, &md5cksum); + code = krb5_k_make_checksum(context, md5cksum.checksum_type, + ctx->seq, sign_usage, + &plaind, &md5cksum); + xfree(data_ptr); + + if (code) { + if (sealalg == 0) + xfree(plain); + if (toktype == KG_TOK_SEAL_MSG) + gssalloc_free(token.value); + *minor_status = code; + return(GSS_S_FAILURE); + } + + code = k5_bcmp(md5cksum.contents, ptr + 14, 8); + /* Falls through to defective-token?? */ + + default: + *minor_status = 0; + return(GSS_S_DEFECTIVE_TOKEN); + + case SGN_ALG_HMAC_SHA1_DES3_KD: + case SGN_ALG_HMAC_MD5: + /* compute the checksum of the message */ + + /* 8 = bytes of token body to be checksummed according to spec */ + + if (! (data_ptr = xmalloc(8 + plainlen))) { + if (sealalg != 0xffff) + xfree(plain); + if (toktype == KG_TOK_SEAL_MSG) + gssalloc_free(token.value); + *minor_status = ENOMEM; + return(GSS_S_FAILURE); + } + + (void) memcpy(data_ptr, ptr-2, 8); + + (void) memcpy(data_ptr+8, plain, plainlen); + + plaind.length = 8 + plainlen; + plaind.data = data_ptr; + code = krb5_k_make_checksum(context, md5cksum.checksum_type, + ctx->seq, sign_usage, + &plaind, &md5cksum); + xfree(data_ptr); + + if (code) { + if (toktype == KG_TOK_SEAL_MSG) + gssalloc_free(token.value); + *minor_status = code; + return(GSS_S_FAILURE); + } + + code = k5_bcmp(md5cksum.contents, ptr + 14, cksum_len); + break; + } + + krb5_free_checksum_contents(context, &md5cksum); + if (sealalg != 0xffff) + xfree(plain); + + /* compare the computed checksum against the transmitted checksum */ + + if (code) { + if (toktype == KG_TOK_SEAL_MSG) + gssalloc_free(token.value); + *minor_status = 0; + return(GSS_S_BAD_SIG); + } + + + /* it got through unscathed. Make sure the context is unexpired */ + + if (toktype == KG_TOK_SEAL_MSG) + *message_buffer = token; + + if (conf_state) + *conf_state = (sealalg != 0xffff); + + if (qop_state) + *qop_state = GSS_C_QOP_DEFAULT; + + /* do sequencing checks */ + + if ((ctx->initiate && direction != 0xff) || + (!ctx->initiate && direction != 0)) { + if (toktype == KG_TOK_SEAL_MSG) { + gssalloc_free(token.value); + message_buffer->value = NULL; + message_buffer->length = 0; + } + *minor_status = (OM_uint32)G_BAD_DIRECTION; + return(GSS_S_BAD_SIG); + } + + retval = g_order_check(&(ctx->seqstate), (gssint_uint64)seqnum); + + /* success or ordering violation */ + + *minor_status = 0; + return(retval); +} + +/* message_buffer is an input if SIGN, output if SEAL, and ignored if DEL_CTX + conf_state is only valid if SEAL. */ + +OM_uint32 +kg_unseal(minor_status, context_handle, input_token_buffer, + message_buffer, conf_state, qop_state, toktype) + OM_uint32 *minor_status; + gss_ctx_id_t context_handle; + gss_buffer_t input_token_buffer; + gss_buffer_t message_buffer; + int *conf_state; + gss_qop_t *qop_state; + int toktype; +{ + krb5_gss_ctx_id_rec *ctx; + unsigned char *ptr; + unsigned int bodysize; + int err; + int toktype2; + int vfyflags = 0; + OM_uint32 ret; + + ctx = (krb5_gss_ctx_id_rec *) context_handle; + + if (! ctx->established) { + *minor_status = KG_CTX_INCOMPLETE; + return(GSS_S_NO_CONTEXT); + } + + /* parse the token, leave the data in message_buffer, setting conf_state */ + + /* verify the header */ + + ptr = (unsigned char *) input_token_buffer->value; + + + err = g_verify_token_header(ctx->mech_used, + &bodysize, &ptr, -1, + input_token_buffer->length, + vfyflags); + if (err) { + *minor_status = err; + return GSS_S_DEFECTIVE_TOKEN; + } + + if (bodysize < 2) { + *minor_status = (OM_uint32)G_BAD_TOK_HEADER; + return GSS_S_DEFECTIVE_TOKEN; + } + + toktype2 = load_16_be(ptr); + + ptr += 2; + bodysize -= 2; + + switch (toktype2) { + case KG2_TOK_MIC_MSG: + case KG2_TOK_WRAP_MSG: + case KG2_TOK_DEL_CTX: + ret = gss_krb5int_unseal_token_v3(&ctx->k5_context, minor_status, ctx, + ptr, bodysize, message_buffer, + conf_state, qop_state, toktype); + break; + case KG_TOK_MIC_MSG: + case KG_TOK_WRAP_MSG: + case KG_TOK_DEL_CTX: + ret = kg_unseal_v1(ctx->k5_context, minor_status, ctx, ptr, bodysize, + message_buffer, conf_state, qop_state, + toktype); + break; + default: + *minor_status = (OM_uint32)G_BAD_TOK_HEADER; + ret = GSS_S_DEFECTIVE_TOKEN; + break; + } + + if (ret != 0) + save_error_info (*minor_status, ctx->k5_context); + + return ret; +} + +OM_uint32 KRB5_CALLCONV +krb5_gss_unwrap(minor_status, context_handle, + input_message_buffer, output_message_buffer, + conf_state, qop_state) + OM_uint32 *minor_status; + gss_ctx_id_t context_handle; + gss_buffer_t input_message_buffer; + gss_buffer_t output_message_buffer; + int *conf_state; + gss_qop_t *qop_state; +{ + OM_uint32 rstat; + + rstat = kg_unseal(minor_status, context_handle, + input_message_buffer, output_message_buffer, + conf_state, qop_state, KG_TOK_WRAP_MSG); + return(rstat); +} + +OM_uint32 KRB5_CALLCONV +krb5_gss_verify_mic(minor_status, context_handle, + message_buffer, token_buffer, + qop_state) + OM_uint32 *minor_status; + gss_ctx_id_t context_handle; + gss_buffer_t message_buffer; + gss_buffer_t token_buffer; + gss_qop_t *qop_state; +{ + OM_uint32 rstat; + + rstat = kg_unseal(minor_status, context_handle, + token_buffer, message_buffer, + NULL, qop_state, KG_TOK_MIC_MSG); + return(rstat); +} === added file '.pc/CVE-2014-4341-4342.patch/src/lib/gssapi/krb5/k5unsealiov.c' --- .pc/CVE-2014-4341-4342.patch/src/lib/gssapi/krb5/k5unsealiov.c 1970-01-01 00:00:00 +0000 +++ .pc/CVE-2014-4341-4342.patch/src/lib/gssapi/krb5/k5unsealiov.c 2014-08-12 11:29:31 +0000 @@ -0,0 +1,671 @@ +/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */ +/* lib/gssapi/krb5/k5unsealiov.c */ +/* + * Copyright 2008, 2009 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. + */ + +#include +#include "k5-platform.h" /* for 64-bit support */ +#include "k5-int.h" /* for zap() */ +#include "gssapiP_krb5.h" +#include + +static OM_uint32 +kg_unseal_v1_iov(krb5_context context, + OM_uint32 *minor_status, + krb5_gss_ctx_id_rec *ctx, + gss_iov_buffer_desc *iov, + int iov_count, + size_t token_wrapper_len, + int *conf_state, + gss_qop_t *qop_state, + int toktype) +{ + OM_uint32 code; + gss_iov_buffer_t header; + gss_iov_buffer_t trailer; + unsigned char *ptr; + int sealalg; + int signalg; + krb5_checksum cksum; + krb5_checksum md5cksum; + size_t cksum_len = 0; + size_t conflen = 0; + int direction; + krb5_ui_4 seqnum; + OM_uint32 retval; + size_t sumlen; + krb5_keyusage sign_usage = KG_USAGE_SIGN; + + md5cksum.length = cksum.length = 0; + md5cksum.contents = cksum.contents = NULL; + + header = kg_locate_header_iov(iov, iov_count, toktype); + assert(header != NULL); + + trailer = kg_locate_iov(iov, iov_count, GSS_IOV_BUFFER_TYPE_TRAILER); + if (trailer != NULL && trailer->buffer.length != 0) { + *minor_status = (OM_uint32)KRB5_BAD_MSIZE; + return GSS_S_DEFECTIVE_TOKEN; + } + + if (header->buffer.length < token_wrapper_len + 14) { + *minor_status = 0; + return GSS_S_DEFECTIVE_TOKEN; + } + + ptr = (unsigned char *)header->buffer.value + token_wrapper_len; + + signalg = ptr[0]; + signalg |= ptr[1] << 8; + + sealalg = ptr[2]; + sealalg |= ptr[3] << 8; + + if (ptr[4] != 0xFF || ptr[5] != 0xFF) { + *minor_status = 0; + return GSS_S_DEFECTIVE_TOKEN; + } + + if (toktype != KG_TOK_WRAP_MSG && sealalg != 0xFFFF) { + *minor_status = 0; + return GSS_S_DEFECTIVE_TOKEN; + } + + if (toktype == KG_TOK_WRAP_MSG && + !(sealalg == 0xFFFF || sealalg == ctx->sealalg)) { + *minor_status = 0; + return GSS_S_DEFECTIVE_TOKEN; + } + + if ((ctx->sealalg == SEAL_ALG_NONE && signalg > 1) || + (ctx->sealalg == SEAL_ALG_1 && signalg != SGN_ALG_3) || + (ctx->sealalg == SEAL_ALG_DES3KD && + signalg != SGN_ALG_HMAC_SHA1_DES3_KD)|| + (ctx->sealalg == SEAL_ALG_MICROSOFT_RC4 && + signalg != SGN_ALG_HMAC_MD5)) { + *minor_status = 0; + return GSS_S_DEFECTIVE_TOKEN; + } + + switch (signalg) { + case SGN_ALG_DES_MAC_MD5: + case SGN_ALG_MD2_5: + case SGN_ALG_HMAC_MD5: + cksum_len = 8; + if (toktype != KG_TOK_WRAP_MSG) + sign_usage = 15; + break; + case SGN_ALG_3: + cksum_len = 16; + break; + case SGN_ALG_HMAC_SHA1_DES3_KD: + cksum_len = 20; + break; + default: + *minor_status = 0; + return GSS_S_DEFECTIVE_TOKEN; + } + + /* get the token parameters */ + code = kg_get_seq_num(context, ctx->seq, ptr + 14, ptr + 6, &direction, + &seqnum); + if (code != 0) { + *minor_status = code; + return GSS_S_BAD_SIG; + } + + /* decode the message, if SEAL */ + if (toktype == KG_TOK_WRAP_MSG) { + if (sealalg != 0xFFFF) { + if (ctx->sealalg == SEAL_ALG_MICROSOFT_RC4) { + unsigned char bigend_seqnum[4]; + krb5_keyblock *enc_key; + size_t i; + + store_32_be(seqnum, bigend_seqnum); + + code = krb5_k_key_keyblock(context, ctx->enc, &enc_key); + if (code != 0) { + retval = GSS_S_FAILURE; + goto cleanup; + } + + assert(enc_key->length == 16); + + for (i = 0; i < enc_key->length; i++) + ((char *)enc_key->contents)[i] ^= 0xF0; + + code = kg_arcfour_docrypt_iov(context, enc_key, 0, + &bigend_seqnum[0], 4, + iov, iov_count); + krb5_free_keyblock(context, enc_key); + } else { + code = kg_decrypt_iov(context, 0, + ((ctx->gss_flags & GSS_C_DCE_STYLE) != 0), + 0 /*EC*/, 0 /*RRC*/, + ctx->enc, KG_USAGE_SEAL, NULL, + iov, iov_count); + } + if (code != 0) { + retval = GSS_S_FAILURE; + goto cleanup; + } + } + conflen = kg_confounder_size(context, ctx->enc->keyblock.enctype); + } + + if (header->buffer.length != token_wrapper_len + 14 + cksum_len + conflen) { + retval = GSS_S_DEFECTIVE_TOKEN; + goto cleanup; + } + + /* compute the checksum of the message */ + + /* initialize the checksum */ + + switch (signalg) { + case SGN_ALG_DES_MAC_MD5: + case SGN_ALG_MD2_5: + case SGN_ALG_DES_MAC: + case SGN_ALG_3: + md5cksum.checksum_type = CKSUMTYPE_RSA_MD5; + break; + case SGN_ALG_HMAC_MD5: + md5cksum.checksum_type = CKSUMTYPE_HMAC_MD5_ARCFOUR; + break; + case SGN_ALG_HMAC_SHA1_DES3_KD: + md5cksum.checksum_type = CKSUMTYPE_HMAC_SHA1_DES3; + break; + default: + abort(); + } + + code = krb5_c_checksum_length(context, md5cksum.checksum_type, &sumlen); + if (code != 0) { + retval = GSS_S_FAILURE; + goto cleanup; + } + md5cksum.length = sumlen; + + /* compute the checksum of the message */ + code = kg_make_checksum_iov_v1(context, md5cksum.checksum_type, + cksum_len, ctx->seq, ctx->enc, + sign_usage, iov, iov_count, toktype, + &md5cksum); + if (code != 0) { + retval = GSS_S_FAILURE; + goto cleanup; + } + + switch (signalg) { + case SGN_ALG_DES_MAC_MD5: + case SGN_ALG_3: + code = kg_encrypt_inplace(context, ctx->seq, KG_USAGE_SEAL, + (g_OID_equal(ctx->mech_used, + gss_mech_krb5_old) ? + ctx->seq->keyblock.contents : NULL), + md5cksum.contents, 16); + if (code != 0) { + retval = GSS_S_FAILURE; + goto cleanup; + } + + cksum.length = cksum_len; + cksum.contents = md5cksum.contents + 16 - cksum.length; + + code = k5_bcmp(cksum.contents, ptr + 14, cksum.length); + break; + case SGN_ALG_HMAC_SHA1_DES3_KD: + case SGN_ALG_HMAC_MD5: + code = k5_bcmp(md5cksum.contents, ptr + 14, cksum_len); + break; + default: + code = 0; + retval = GSS_S_DEFECTIVE_TOKEN; + goto cleanup; + break; + } + + if (code != 0) { + code = 0; + retval = GSS_S_BAD_SIG; + goto cleanup; + } + + /* + * For GSS_C_DCE_STYLE, the caller manages the padding, because the + * pad length is in the RPC PDU. The value of the padding may be + * uninitialized. For normal GSS, the last bytes of the decrypted + * data contain the pad length. kg_fixup_padding_iov() will find + * this and fixup the last data IOV appropriately. + */ + if (toktype == KG_TOK_WRAP_MSG && + (ctx->gss_flags & GSS_C_DCE_STYLE) == 0) { + retval = kg_fixup_padding_iov(&code, iov, iov_count); + if (retval != GSS_S_COMPLETE) + goto cleanup; + } + + if (conf_state != NULL) + *conf_state = (sealalg != 0xFFFF); + + if (qop_state != NULL) + *qop_state = GSS_C_QOP_DEFAULT; + + if ((ctx->initiate && direction != 0xff) || + (!ctx->initiate && direction != 0)) { + *minor_status = (OM_uint32)G_BAD_DIRECTION; + retval = GSS_S_BAD_SIG; + } + + code = 0; + retval = g_order_check(&ctx->seqstate, (gssint_uint64)seqnum); + +cleanup: + krb5_free_checksum_contents(context, &md5cksum); + + *minor_status = code; + + return retval; +} + +/* + * Caller must provide TOKEN | DATA | PADDING | TRAILER, except + * for DCE in which case it can just provide TOKEN | DATA (must + * guarantee that DATA is padded) + */ +static OM_uint32 +kg_unseal_iov_token(OM_uint32 *minor_status, + krb5_gss_ctx_id_rec *ctx, + int *conf_state, + gss_qop_t *qop_state, + gss_iov_buffer_desc *iov, + int iov_count, + int toktype) +{ + krb5_error_code code; + krb5_context context = ctx->k5_context; + unsigned char *ptr; + gss_iov_buffer_t header; + gss_iov_buffer_t padding; + gss_iov_buffer_t trailer; + size_t input_length; + unsigned int bodysize; + int toktype2; + + header = kg_locate_header_iov(iov, iov_count, toktype); + if (header == NULL) { + *minor_status = EINVAL; + return GSS_S_FAILURE; + } + + padding = kg_locate_iov(iov, iov_count, GSS_IOV_BUFFER_TYPE_PADDING); + trailer = kg_locate_iov(iov, iov_count, GSS_IOV_BUFFER_TYPE_TRAILER); + + ptr = (unsigned char *)header->buffer.value; + input_length = header->buffer.length; + + if ((ctx->gss_flags & GSS_C_DCE_STYLE) == 0 && + toktype == KG_TOK_WRAP_MSG) { + size_t data_length, assoc_data_length; + + kg_iov_msglen(iov, iov_count, &data_length, &assoc_data_length); + + input_length += data_length - assoc_data_length; + + if (padding != NULL) + input_length += padding->buffer.length; + + if (trailer != NULL) + input_length += trailer->buffer.length; + } + + code = g_verify_token_header(ctx->mech_used, + &bodysize, &ptr, -1, + input_length, 0); + if (code != 0) { + *minor_status = code; + return GSS_S_DEFECTIVE_TOKEN; + } + + if (bodysize < 2) { + *minor_status = (OM_uint32)G_BAD_TOK_HEADER; + return GSS_S_DEFECTIVE_TOKEN; + } + + toktype2 = load_16_be(ptr); + + ptr += 2; + bodysize -= 2; + + switch (toktype2) { + case KG2_TOK_MIC_MSG: + case KG2_TOK_WRAP_MSG: + case KG2_TOK_DEL_CTX: + code = gss_krb5int_unseal_v3_iov(context, minor_status, ctx, iov, iov_count, + conf_state, qop_state, toktype); + break; + case KG_TOK_MIC_MSG: + case KG_TOK_WRAP_MSG: + case KG_TOK_DEL_CTX: + code = kg_unseal_v1_iov(context, minor_status, ctx, iov, iov_count, + (size_t)(ptr - (unsigned char *)header->buffer.value), + conf_state, qop_state, toktype); + break; + default: + *minor_status = (OM_uint32)G_BAD_TOK_HEADER; + code = GSS_S_DEFECTIVE_TOKEN; + break; + } + + if (code != 0) + save_error_info(*minor_status, context); + + return code; +} + +/* + * Split a STREAM | SIGN_DATA | DATA into + * HEADER | SIGN_DATA | DATA | PADDING | TRAILER + */ +static OM_uint32 +kg_unseal_stream_iov(OM_uint32 *minor_status, + krb5_gss_ctx_id_rec *ctx, + int *conf_state, + gss_qop_t *qop_state, + gss_iov_buffer_desc *iov, + int iov_count, + int toktype) +{ + unsigned char *ptr; + unsigned int bodysize; + OM_uint32 code = 0, major_status = GSS_S_FAILURE; + krb5_context context = ctx->k5_context; + int conf_req_flag, toktype2; + int i = 0, j; + gss_iov_buffer_desc *tiov = NULL; + gss_iov_buffer_t stream, data = NULL; + gss_iov_buffer_t theader, tdata = NULL, tpadding, ttrailer; + + assert(toktype == KG_TOK_WRAP_MSG); + + if (toktype != KG_TOK_WRAP_MSG || (ctx->gss_flags & GSS_C_DCE_STYLE)) { + code = EINVAL; + goto cleanup; + } + + stream = kg_locate_iov(iov, iov_count, GSS_IOV_BUFFER_TYPE_STREAM); + assert(stream != NULL); + + ptr = (unsigned char *)stream->buffer.value; + + code = g_verify_token_header(ctx->mech_used, + &bodysize, &ptr, -1, + stream->buffer.length, 0); + if (code != 0) { + major_status = GSS_S_DEFECTIVE_TOKEN; + goto cleanup; + } + + if (bodysize < 2) { + *minor_status = (OM_uint32)G_BAD_TOK_HEADER; + return GSS_S_DEFECTIVE_TOKEN; + } + + toktype2 = load_16_be(ptr); + + ptr += 2; + bodysize -= 2; + + tiov = (gss_iov_buffer_desc *)calloc((size_t)iov_count + 2, sizeof(gss_iov_buffer_desc)); + if (tiov == NULL) { + code = ENOMEM; + goto cleanup; + } + + /* HEADER */ + theader = &tiov[i++]; + theader->type = GSS_IOV_BUFFER_TYPE_HEADER; + theader->buffer.value = stream->buffer.value; + theader->buffer.length = ptr - (unsigned char *)stream->buffer.value; + if (bodysize < 14 || + stream->buffer.length != theader->buffer.length + bodysize) { + major_status = GSS_S_DEFECTIVE_TOKEN; + goto cleanup; + } + theader->buffer.length += 14; + + /* n[SIGN_DATA] | DATA | m[SIGN_DATA] */ + for (j = 0; j < iov_count; j++) { + OM_uint32 type = GSS_IOV_BUFFER_TYPE(iov[j].type); + + if (type == GSS_IOV_BUFFER_TYPE_DATA) { + if (data != NULL) { + /* only a single DATA buffer can appear */ + code = EINVAL; + goto cleanup; + } + + data = &iov[j]; + tdata = &tiov[i]; + } + if (type == GSS_IOV_BUFFER_TYPE_DATA || + type == GSS_IOV_BUFFER_TYPE_SIGN_ONLY) + tiov[i++] = iov[j]; + } + + if (data == NULL) { + /* a single DATA buffer must be present */ + code = EINVAL; + goto cleanup; + } + + /* PADDING | TRAILER */ + tpadding = &tiov[i++]; + tpadding->type = GSS_IOV_BUFFER_TYPE_PADDING; + tpadding->buffer.length = 0; + tpadding->buffer.value = NULL; + + ttrailer = &tiov[i++]; + ttrailer->type = GSS_IOV_BUFFER_TYPE_TRAILER; + + switch (toktype2) { + case KG2_TOK_MIC_MSG: + case KG2_TOK_WRAP_MSG: + case KG2_TOK_DEL_CTX: { + size_t ec, rrc; + krb5_enctype enctype; + unsigned int k5_headerlen = 0; + unsigned int k5_trailerlen = 0; + + if (ctx->have_acceptor_subkey) + enctype = ctx->acceptor_subkey->keyblock.enctype; + else + enctype = ctx->subkey->keyblock.enctype; + conf_req_flag = ((ptr[0] & FLAG_WRAP_CONFIDENTIAL) != 0); + ec = conf_req_flag ? load_16_be(ptr + 2) : 0; + rrc = load_16_be(ptr + 4); + + if (rrc != 0) { + if (!gss_krb5int_rotate_left((unsigned char *)stream->buffer.value + 16, + stream->buffer.length - 16, rrc)) { + code = ENOMEM; + goto cleanup; + } + store_16_be(0, ptr + 4); /* set RRC to zero */ + } + + if (conf_req_flag) { + code = krb5_c_crypto_length(context, enctype, KRB5_CRYPTO_TYPE_HEADER, &k5_headerlen); + if (code != 0) + goto cleanup; + theader->buffer.length += k5_headerlen; /* length validated later */ + } + + /* no PADDING for CFX, EC is used instead */ + code = krb5_c_crypto_length(context, enctype, + conf_req_flag ? KRB5_CRYPTO_TYPE_TRAILER : KRB5_CRYPTO_TYPE_CHECKSUM, + &k5_trailerlen); + if (code != 0) + goto cleanup; + + ttrailer->buffer.length = ec + (conf_req_flag ? 16 : 0 /* E(Header) */) + k5_trailerlen; + ttrailer->buffer.value = (unsigned char *)stream->buffer.value + + stream->buffer.length - ttrailer->buffer.length; + break; + } + case KG_TOK_MIC_MSG: + case KG_TOK_WRAP_MSG: + case KG_TOK_DEL_CTX: + theader->buffer.length += ctx->cksum_size + + kg_confounder_size(context, ctx->enc->keyblock.enctype); + + /* + * we can't set the padding accurately until decryption; + * kg_fixup_padding_iov() will take care of this + */ + tpadding->buffer.length = 1; + tpadding->buffer.value = (unsigned char *)stream->buffer.value + stream->buffer.length - 1; + + /* no TRAILER for pre-CFX */ + ttrailer->buffer.length = 0; + ttrailer->buffer.value = NULL; + + break; + default: + code = (OM_uint32)G_BAD_TOK_HEADER; + major_status = GSS_S_DEFECTIVE_TOKEN; + goto cleanup; + break; + } + + /* IOV: -----------0-------------+---1---+--2--+----------------3--------------*/ + /* Old: GSS-Header | Conf | Data | Pad | */ + /* CFX: GSS-Header | Kerb-Header | Data | | EC | E(Header) | Kerb-Trailer */ + /* GSS: -------GSS-HEADER--------+-DATA--+-PAD-+----------GSS-TRAILER----------*/ + + /* validate lengths */ + if (stream->buffer.length < theader->buffer.length + + tpadding->buffer.length + + ttrailer->buffer.length) + { + code = (OM_uint32)KRB5_BAD_MSIZE; + major_status = GSS_S_DEFECTIVE_TOKEN; + goto cleanup; + } + + /* setup data */ + tdata->buffer.length = stream->buffer.length - ttrailer->buffer.length - + tpadding->buffer.length - theader->buffer.length; + + assert(data != NULL); + + if (data->type & GSS_IOV_BUFFER_FLAG_ALLOCATE) { + code = kg_allocate_iov(tdata, tdata->buffer.length); + if (code != 0) + goto cleanup; + memcpy(tdata->buffer.value, + (unsigned char *)stream->buffer.value + theader->buffer.length, tdata->buffer.length); + } else + tdata->buffer.value = (unsigned char *)stream->buffer.value + theader->buffer.length; + + assert(i <= iov_count + 2); + + major_status = kg_unseal_iov_token(&code, ctx, conf_state, qop_state, + tiov, i, toktype); + if (major_status == GSS_S_COMPLETE) + *data = *tdata; + else + kg_release_iov(tdata, 1); + +cleanup: + if (tiov != NULL) + free(tiov); + + *minor_status = code; + + return major_status; +} + +OM_uint32 +kg_unseal_iov(OM_uint32 *minor_status, + gss_ctx_id_t context_handle, + int *conf_state, + gss_qop_t *qop_state, + gss_iov_buffer_desc *iov, + int iov_count, + int toktype) +{ + krb5_gss_ctx_id_rec *ctx; + OM_uint32 code; + + ctx = (krb5_gss_ctx_id_rec *)context_handle; + if (!ctx->established) { + *minor_status = KG_CTX_INCOMPLETE; + return GSS_S_NO_CONTEXT; + } + + if (kg_locate_iov(iov, iov_count, GSS_IOV_BUFFER_TYPE_STREAM) != NULL) { + code = kg_unseal_stream_iov(minor_status, ctx, conf_state, qop_state, + iov, iov_count, toktype); + } else { + code = kg_unseal_iov_token(minor_status, ctx, conf_state, qop_state, + iov, iov_count, toktype); + } + + return code; +} + +OM_uint32 KRB5_CALLCONV +krb5_gss_unwrap_iov(OM_uint32 *minor_status, + gss_ctx_id_t context_handle, + int *conf_state, + gss_qop_t *qop_state, + gss_iov_buffer_desc *iov, + int iov_count) +{ + OM_uint32 major_status; + + major_status = kg_unseal_iov(minor_status, context_handle, + conf_state, qop_state, + iov, iov_count, KG_TOK_WRAP_MSG); + + return major_status; +} + +OM_uint32 KRB5_CALLCONV +krb5_gss_verify_mic_iov(OM_uint32 *minor_status, + gss_ctx_id_t context_handle, + gss_qop_t *qop_state, + gss_iov_buffer_desc *iov, + int iov_count) +{ + OM_uint32 major_status; + + major_status = kg_unseal_iov(minor_status, context_handle, + NULL, qop_state, + iov, iov_count, KG_TOK_MIC_MSG); + + return major_status; +} === added directory '.pc/CVE-2014-4343.patch' === added directory '.pc/CVE-2014-4343.patch/src' === added directory '.pc/CVE-2014-4343.patch/src/lib' === added directory '.pc/CVE-2014-4343.patch/src/lib/gssapi' === added directory '.pc/CVE-2014-4343.patch/src/lib/gssapi/spnego' === added file '.pc/CVE-2014-4343.patch/src/lib/gssapi/spnego/spnego_mech.c' --- .pc/CVE-2014-4343.patch/src/lib/gssapi/spnego/spnego_mech.c 1970-01-01 00:00:00 +0000 +++ .pc/CVE-2014-4343.patch/src/lib/gssapi/spnego/spnego_mech.c 2014-08-12 11:29:31 +0000 @@ -0,0 +1,4176 @@ +/* + * Copyright (C) 2006,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 2004 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + * + * A module that implements the spnego security mechanism. + * It is used to negotiate the security mechanism between + * peers using the GSS-API. SPNEGO is specified in RFC 4178. + * + */ +/* + * 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. + */ +/* #pragma ident "@(#)spnego_mech.c 1.7 04/09/28 SMI" */ + +#include +#include +#include +#include +#include +#include +#include +#include "gssapiP_spnego.h" +#include + +#ifndef MAXHOSTNAMELEN +#define MAXHOSTNAMELEN 64 +#endif + +#undef g_token_size +#undef g_verify_token_header +#undef g_make_token_header + +#define HARD_ERROR(v) ((v) != GSS_S_COMPLETE && (v) != GSS_S_CONTINUE_NEEDED) +typedef const gss_OID_desc *gss_OID_const; + +/* der routines defined in libgss */ +extern unsigned int gssint_der_length_size(unsigned int); +extern int gssint_get_der_length(unsigned char **, unsigned int, + unsigned int*); +extern int gssint_put_der_length(unsigned int, unsigned char **, unsigned int); + + +/* private routines for spnego_mechanism */ +static spnego_token_t make_spnego_token(char *); +static gss_buffer_desc make_err_msg(char *); +static int g_token_size(gss_OID_const, unsigned int); +static int g_make_token_header(gss_OID_const, unsigned int, + unsigned char **, unsigned int); +static int g_verify_token_header(gss_OID_const, unsigned int *, + unsigned char **, + int, unsigned int); +static int g_verify_neg_token_init(unsigned char **, unsigned int); +static gss_OID get_mech_oid(OM_uint32 *, unsigned char **, size_t); +static gss_buffer_t get_input_token(unsigned char **, unsigned int); +static gss_OID_set get_mech_set(OM_uint32 *, unsigned char **, unsigned int); +static OM_uint32 get_req_flags(unsigned char **, OM_uint32, OM_uint32 *); +static OM_uint32 get_available_mechs(OM_uint32 *, gss_name_t, gss_cred_usage_t, + gss_const_key_value_set_t, + gss_cred_id_t *, gss_OID_set *); +static OM_uint32 get_negotiable_mechs(OM_uint32 *, spnego_gss_cred_id_t, + gss_cred_usage_t, gss_OID_set *); +static void release_spnego_ctx(spnego_gss_ctx_id_t *); +static void check_spnego_options(spnego_gss_ctx_id_t); +static spnego_gss_ctx_id_t create_spnego_ctx(void); +static int put_mech_set(gss_OID_set mechSet, gss_buffer_t buf); +static int put_input_token(unsigned char **, gss_buffer_t, unsigned int); +static int put_mech_oid(unsigned char **, gss_OID_const, unsigned int); +static int put_negResult(unsigned char **, OM_uint32, unsigned int); + +static OM_uint32 +process_mic(OM_uint32 *, gss_buffer_t, spnego_gss_ctx_id_t, + gss_buffer_t *, OM_uint32 *, send_token_flag *); +static OM_uint32 +handle_mic(OM_uint32 *, gss_buffer_t, int, spnego_gss_ctx_id_t, + gss_buffer_t *, OM_uint32 *, send_token_flag *); + +static OM_uint32 +init_ctx_new(OM_uint32 *, spnego_gss_cred_id_t, gss_ctx_id_t *, + send_token_flag *); +static OM_uint32 +init_ctx_nego(OM_uint32 *, spnego_gss_ctx_id_t, OM_uint32, gss_OID, + gss_buffer_t *, gss_buffer_t *, + OM_uint32 *, send_token_flag *); +static OM_uint32 +init_ctx_cont(OM_uint32 *, gss_ctx_id_t *, gss_buffer_t, + gss_buffer_t *, gss_buffer_t *, + OM_uint32 *, send_token_flag *); +static OM_uint32 +init_ctx_reselect(OM_uint32 *, spnego_gss_ctx_id_t, OM_uint32, + gss_OID, gss_buffer_t *, gss_buffer_t *, + OM_uint32 *, send_token_flag *); +static OM_uint32 +init_ctx_call_init(OM_uint32 *, spnego_gss_ctx_id_t, spnego_gss_cred_id_t, + gss_name_t, OM_uint32, OM_uint32, gss_buffer_t, + gss_OID *, gss_buffer_t, OM_uint32 *, OM_uint32 *, + OM_uint32 *, send_token_flag *); + +static OM_uint32 +acc_ctx_new(OM_uint32 *, gss_buffer_t, gss_ctx_id_t *, + spnego_gss_cred_id_t, gss_buffer_t *, + gss_buffer_t *, OM_uint32 *, send_token_flag *); +static OM_uint32 +acc_ctx_cont(OM_uint32 *, gss_buffer_t, gss_ctx_id_t *, + gss_buffer_t *, gss_buffer_t *, + OM_uint32 *, send_token_flag *); +static OM_uint32 +acc_ctx_vfy_oid(OM_uint32 *, spnego_gss_ctx_id_t, gss_OID, + OM_uint32 *, send_token_flag *); +static OM_uint32 +acc_ctx_call_acc(OM_uint32 *, spnego_gss_ctx_id_t, spnego_gss_cred_id_t, + gss_buffer_t, gss_OID *, gss_buffer_t, + OM_uint32 *, OM_uint32 *, gss_cred_id_t *, + OM_uint32 *, send_token_flag *); + +static gss_OID +negotiate_mech(gss_OID_set, gss_OID_set, OM_uint32 *); +static int +g_get_tag_and_length(unsigned char **, int, unsigned int, unsigned int *); + +static int +make_spnego_tokenInit_msg(spnego_gss_ctx_id_t, + int, + gss_buffer_t, + OM_uint32, gss_buffer_t, send_token_flag, + gss_buffer_t); +static int +make_spnego_tokenTarg_msg(OM_uint32, gss_OID, gss_buffer_t, + gss_buffer_t, send_token_flag, + gss_buffer_t); + +static OM_uint32 +get_negTokenInit(OM_uint32 *, gss_buffer_t, gss_buffer_t, + gss_OID_set *, OM_uint32 *, gss_buffer_t *, + gss_buffer_t *); +static OM_uint32 +get_negTokenResp(OM_uint32 *, unsigned char *, unsigned int, + OM_uint32 *, gss_OID *, gss_buffer_t *, gss_buffer_t *); + +static int +is_kerb_mech(gss_OID oid); + +/* SPNEGO oid structure */ +static const gss_OID_desc spnego_oids[] = { + {SPNEGO_OID_LENGTH, SPNEGO_OID}, +}; + +const gss_OID_desc * const gss_mech_spnego = spnego_oids+0; +static const gss_OID_set_desc spnego_oidsets[] = { + {1, (gss_OID) spnego_oids+0}, +}; +const gss_OID_set_desc * const gss_mech_set_spnego = spnego_oidsets+0; + +static int make_NegHints(OM_uint32 *, spnego_gss_cred_id_t, gss_buffer_t *); +static int put_neg_hints(unsigned char **, gss_buffer_t, unsigned int); +static OM_uint32 +acc_ctx_hints(OM_uint32 *, gss_ctx_id_t *, spnego_gss_cred_id_t, + gss_buffer_t *, OM_uint32 *, send_token_flag *); + +/* + * The Mech OID for SPNEGO: + * { iso(1) org(3) dod(6) internet(1) security(5) + * mechanism(5) spnego(2) } + */ +static struct gss_config spnego_mechanism = +{ + {SPNEGO_OID_LENGTH, SPNEGO_OID}, + NULL, + spnego_gss_acquire_cred, + spnego_gss_release_cred, + spnego_gss_init_sec_context, +#ifndef LEAN_CLIENT + spnego_gss_accept_sec_context, +#else + NULL, +#endif /* LEAN_CLIENT */ + NULL, /* gss_process_context_token */ + spnego_gss_delete_sec_context, /* gss_delete_sec_context */ + spnego_gss_context_time, /* gss_context_time */ + spnego_gss_get_mic, /* gss_get_mic */ + spnego_gss_verify_mic, /* gss_verify_mic */ + spnego_gss_wrap, /* gss_wrap */ + spnego_gss_unwrap, /* gss_unwrap */ + spnego_gss_display_status, + NULL, /* gss_indicate_mechs */ + spnego_gss_compare_name, + spnego_gss_display_name, + spnego_gss_import_name, + spnego_gss_release_name, + spnego_gss_inquire_cred, /* gss_inquire_cred */ + NULL, /* gss_add_cred */ +#ifndef LEAN_CLIENT + spnego_gss_export_sec_context, /* gss_export_sec_context */ + spnego_gss_import_sec_context, /* gss_import_sec_context */ +#else + NULL, /* gss_export_sec_context */ + NULL, /* gss_import_sec_context */ +#endif /* LEAN_CLIENT */ + NULL, /* gss_inquire_cred_by_mech */ + spnego_gss_inquire_names_for_mech, + spnego_gss_inquire_context, /* gss_inquire_context */ + NULL, /* gss_internal_release_oid */ + spnego_gss_wrap_size_limit, /* gss_wrap_size_limit */ + NULL, /* gssd_pname_to_uid */ + NULL, /* gss_userok */ + NULL, /* gss_export_name */ + spnego_gss_duplicate_name, /* gss_duplicate_name */ + NULL, /* gss_store_cred */ + spnego_gss_inquire_sec_context_by_oid, /* gss_inquire_sec_context_by_oid */ + spnego_gss_inquire_cred_by_oid, /* gss_inquire_cred_by_oid */ + spnego_gss_set_sec_context_option, /* gss_set_sec_context_option */ + spnego_gss_set_cred_option, /* gssspi_set_cred_option */ + NULL, /* gssspi_mech_invoke */ + spnego_gss_wrap_aead, + spnego_gss_unwrap_aead, + spnego_gss_wrap_iov, + spnego_gss_unwrap_iov, + spnego_gss_wrap_iov_length, + spnego_gss_complete_auth_token, + spnego_gss_acquire_cred_impersonate_name, + NULL, /* gss_add_cred_impersonate_name */ + spnego_gss_display_name_ext, + spnego_gss_inquire_name, + spnego_gss_get_name_attribute, + spnego_gss_set_name_attribute, + spnego_gss_delete_name_attribute, + spnego_gss_export_name_composite, + spnego_gss_map_name_to_any, + spnego_gss_release_any_name_mapping, + spnego_gss_pseudo_random, + spnego_gss_set_neg_mechs, + spnego_gss_inquire_saslname_for_mech, + spnego_gss_inquire_mech_for_saslname, + spnego_gss_inquire_attrs_for_mech, + spnego_gss_acquire_cred_from, + NULL, /* gss_store_cred_into */ + spnego_gss_acquire_cred_with_password, + spnego_gss_export_cred, + spnego_gss_import_cred, + NULL, /* gssspi_import_sec_context_by_mech */ + NULL, /* gssspi_import_name_by_mech */ + NULL, /* gssspi_import_cred_by_mech */ + spnego_gss_get_mic_iov, + spnego_gss_verify_mic_iov, + spnego_gss_get_mic_iov_length +}; + +#ifdef _GSS_STATIC_LINK +#include "mglueP.h" + +static int gss_spnegomechglue_init(void) +{ + struct gss_mech_config mech_spnego; + + memset(&mech_spnego, 0, sizeof(mech_spnego)); + mech_spnego.mech = &spnego_mechanism; + mech_spnego.mechNameStr = "spnego"; + mech_spnego.mech_type = GSS_C_NO_OID; + + return gssint_register_mechinfo(&mech_spnego); +} +#else +gss_mechanism KRB5_CALLCONV +gss_mech_initialize(void) +{ + return (&spnego_mechanism); +} + +MAKE_INIT_FUNCTION(gss_krb5int_lib_init); +MAKE_FINI_FUNCTION(gss_krb5int_lib_fini); +int gss_krb5int_lib_init(void); +#endif /* _GSS_STATIC_LINK */ + +int gss_spnegoint_lib_init(void) +{ +#ifdef _GSS_STATIC_LINK + return gss_spnegomechglue_init(); +#else + return 0; +#endif +} + +void gss_spnegoint_lib_fini(void) +{ +} + +/*ARGSUSED*/ +OM_uint32 KRB5_CALLCONV +spnego_gss_acquire_cred(OM_uint32 *minor_status, + gss_name_t desired_name, + OM_uint32 time_req, + gss_OID_set desired_mechs, + gss_cred_usage_t cred_usage, + gss_cred_id_t *output_cred_handle, + gss_OID_set *actual_mechs, + OM_uint32 *time_rec) +{ + return spnego_gss_acquire_cred_from(minor_status, desired_name, time_req, + desired_mechs, cred_usage, NULL, + output_cred_handle, actual_mechs, + time_rec); +} + +/*ARGSUSED*/ +OM_uint32 KRB5_CALLCONV +spnego_gss_acquire_cred_from(OM_uint32 *minor_status, + const gss_name_t desired_name, + OM_uint32 time_req, + const gss_OID_set desired_mechs, + gss_cred_usage_t cred_usage, + gss_const_key_value_set_t cred_store, + gss_cred_id_t *output_cred_handle, + gss_OID_set *actual_mechs, + OM_uint32 *time_rec) +{ + OM_uint32 status, tmpmin; + gss_OID_set amechs; + gss_cred_id_t mcred = NULL; + spnego_gss_cred_id_t spcred = NULL; + dsyslog("Entering spnego_gss_acquire_cred\n"); + + if (actual_mechs) + *actual_mechs = NULL; + + if (time_rec) + *time_rec = 0; + + /* We will obtain a mechglue credential and wrap it in a + * spnego_gss_cred_id_rec structure. Allocate the wrapper. */ + spcred = malloc(sizeof(spnego_gss_cred_id_rec)); + if (spcred == NULL) { + *minor_status = ENOMEM; + return (GSS_S_FAILURE); + } + spcred->neg_mechs = GSS_C_NULL_OID_SET; + + /* + * Always use get_available_mechs to collect a list of + * mechs for which creds are available. + */ + status = get_available_mechs(minor_status, desired_name, + cred_usage, cred_store, &mcred, + &amechs); + + if (actual_mechs && amechs != GSS_C_NULL_OID_SET) { + (void) gssint_copy_oid_set(&tmpmin, amechs, actual_mechs); + } + (void) gss_release_oid_set(&tmpmin, &amechs); + + if (status == GSS_S_COMPLETE) { + spcred->mcred = mcred; + *output_cred_handle = (gss_cred_id_t)spcred; + } else { + free(spcred); + *output_cred_handle = GSS_C_NO_CREDENTIAL; + } + + dsyslog("Leaving spnego_gss_acquire_cred\n"); + return (status); +} + +/*ARGSUSED*/ +OM_uint32 KRB5_CALLCONV +spnego_gss_release_cred(OM_uint32 *minor_status, + gss_cred_id_t *cred_handle) +{ + spnego_gss_cred_id_t spcred = NULL; + + dsyslog("Entering spnego_gss_release_cred\n"); + + if (minor_status == NULL || cred_handle == NULL) + return (GSS_S_CALL_INACCESSIBLE_WRITE); + + *minor_status = 0; + + if (*cred_handle == GSS_C_NO_CREDENTIAL) + return (GSS_S_COMPLETE); + + spcred = (spnego_gss_cred_id_t)*cred_handle; + *cred_handle = GSS_C_NO_CREDENTIAL; + gss_release_oid_set(minor_status, &spcred->neg_mechs); + gss_release_cred(minor_status, &spcred->mcred); + free(spcred); + + dsyslog("Leaving spnego_gss_release_cred\n"); + return (GSS_S_COMPLETE); +} + +static void +check_spnego_options(spnego_gss_ctx_id_t spnego_ctx) +{ + spnego_ctx->optionStr = gssint_get_modOptions( + (const gss_OID)&spnego_oids[0]); +} + +static spnego_gss_ctx_id_t +create_spnego_ctx(void) +{ + spnego_gss_ctx_id_t spnego_ctx = NULL; + spnego_ctx = (spnego_gss_ctx_id_t) + malloc(sizeof (spnego_gss_ctx_id_rec)); + + if (spnego_ctx == NULL) { + return (NULL); + } + + spnego_ctx->magic_num = SPNEGO_MAGIC_ID; + spnego_ctx->ctx_handle = GSS_C_NO_CONTEXT; + spnego_ctx->mech_set = NULL; + spnego_ctx->internal_mech = NULL; + spnego_ctx->optionStr = NULL; + spnego_ctx->DER_mechTypes.length = 0; + spnego_ctx->DER_mechTypes.value = NULL; + spnego_ctx->default_cred = GSS_C_NO_CREDENTIAL; + spnego_ctx->mic_reqd = 0; + spnego_ctx->mic_sent = 0; + spnego_ctx->mic_rcvd = 0; + spnego_ctx->mech_complete = 0; + spnego_ctx->nego_done = 0; + spnego_ctx->internal_name = GSS_C_NO_NAME; + spnego_ctx->actual_mech = GSS_C_NO_OID; + + check_spnego_options(spnego_ctx); + + return (spnego_ctx); +} + +/* + * Both initiator and acceptor call here to verify and/or create mechListMIC, + * and to consistency-check the MIC state. handle_mic is invoked only if the + * negotiated mech has completed and supports MICs. + */ +static OM_uint32 +handle_mic(OM_uint32 *minor_status, gss_buffer_t mic_in, + int send_mechtok, spnego_gss_ctx_id_t sc, + gss_buffer_t *mic_out, + OM_uint32 *negState, send_token_flag *tokflag) +{ + OM_uint32 ret; + + ret = GSS_S_FAILURE; + *mic_out = GSS_C_NO_BUFFER; + if (mic_in != GSS_C_NO_BUFFER) { + if (sc->mic_rcvd) { + /* Reject MIC if we've already received a MIC. */ + *negState = REJECT; + *tokflag = ERROR_TOKEN_SEND; + return GSS_S_DEFECTIVE_TOKEN; + } + } else if (sc->mic_reqd && !send_mechtok) { + /* + * If the peer sends the final mechanism token, it + * must send the MIC with that token if the + * negotiation requires MICs. + */ + *negState = REJECT; + *tokflag = ERROR_TOKEN_SEND; + return GSS_S_DEFECTIVE_TOKEN; + } + ret = process_mic(minor_status, mic_in, sc, mic_out, + negState, tokflag); + if (ret != GSS_S_COMPLETE) { + return ret; + } + if (sc->mic_reqd) { + assert(sc->mic_sent || sc->mic_rcvd); + } + if (sc->mic_sent && sc->mic_rcvd) { + ret = GSS_S_COMPLETE; + *negState = ACCEPT_COMPLETE; + if (*mic_out == GSS_C_NO_BUFFER) { + /* + * We sent a MIC on the previous pass; we + * shouldn't be sending a mechanism token. + */ + assert(!send_mechtok); + *tokflag = NO_TOKEN_SEND; + } else { + *tokflag = CONT_TOKEN_SEND; + } + } else if (sc->mic_reqd) { + *negState = ACCEPT_INCOMPLETE; + ret = GSS_S_CONTINUE_NEEDED; + } else if (*negState == ACCEPT_COMPLETE) { + ret = GSS_S_COMPLETE; + } else { + ret = GSS_S_CONTINUE_NEEDED; + } + return ret; +} + +/* + * Perform the actual verification and/or generation of mechListMIC. + */ +static OM_uint32 +process_mic(OM_uint32 *minor_status, gss_buffer_t mic_in, + spnego_gss_ctx_id_t sc, gss_buffer_t *mic_out, + OM_uint32 *negState, send_token_flag *tokflag) +{ + OM_uint32 ret, tmpmin; + gss_qop_t qop_state; + gss_buffer_desc tmpmic = GSS_C_EMPTY_BUFFER; + + ret = GSS_S_FAILURE; + if (mic_in != GSS_C_NO_BUFFER) { + ret = gss_verify_mic(minor_status, sc->ctx_handle, + &sc->DER_mechTypes, + mic_in, &qop_state); + if (ret != GSS_S_COMPLETE) { + *negState = REJECT; + *tokflag = ERROR_TOKEN_SEND; + return ret; + } + /* If we got a MIC, we must send a MIC. */ + sc->mic_reqd = 1; + sc->mic_rcvd = 1; + } + if (sc->mic_reqd && !sc->mic_sent) { + ret = gss_get_mic(minor_status, sc->ctx_handle, + GSS_C_QOP_DEFAULT, + &sc->DER_mechTypes, + &tmpmic); + if (ret != GSS_S_COMPLETE) { + gss_release_buffer(&tmpmin, &tmpmic); + *tokflag = NO_TOKEN_SEND; + return ret; + } + *mic_out = malloc(sizeof(gss_buffer_desc)); + if (*mic_out == GSS_C_NO_BUFFER) { + gss_release_buffer(&tmpmin, &tmpmic); + *tokflag = NO_TOKEN_SEND; + return GSS_S_FAILURE; + } + **mic_out = tmpmic; + sc->mic_sent = 1; + } + return GSS_S_COMPLETE; +} + +/* + * Initial call to spnego_gss_init_sec_context(). + */ +static OM_uint32 +init_ctx_new(OM_uint32 *minor_status, + spnego_gss_cred_id_t spcred, + gss_ctx_id_t *ctx, + send_token_flag *tokflag) +{ + OM_uint32 ret; + spnego_gss_ctx_id_t sc = NULL; + + sc = create_spnego_ctx(); + if (sc == NULL) + return GSS_S_FAILURE; + + /* determine negotiation mech set */ + ret = get_negotiable_mechs(minor_status, spcred, GSS_C_INITIATE, + &sc->mech_set); + if (ret != GSS_S_COMPLETE) + goto cleanup; + + /* Set an initial internal mech to make the first context token. */ + sc->internal_mech = &sc->mech_set->elements[0]; + + if (put_mech_set(sc->mech_set, &sc->DER_mechTypes) < 0) { + ret = GSS_S_FAILURE; + goto cleanup; + } + /* + * The actual context is not yet determined, set the output + * context handle to refer to the spnego context itself. + */ + sc->ctx_handle = GSS_C_NO_CONTEXT; + *ctx = (gss_ctx_id_t)sc; + sc = NULL; + *tokflag = INIT_TOKEN_SEND; + ret = GSS_S_CONTINUE_NEEDED; + +cleanup: + release_spnego_ctx(&sc); + return ret; +} + +/* + * Called by second and later calls to spnego_gss_init_sec_context() + * to decode reply and update state. + */ +static OM_uint32 +init_ctx_cont(OM_uint32 *minor_status, gss_ctx_id_t *ctx, gss_buffer_t buf, + gss_buffer_t *responseToken, gss_buffer_t *mechListMIC, + OM_uint32 *negState, send_token_flag *tokflag) +{ + OM_uint32 ret, tmpmin, acc_negState; + unsigned char *ptr; + spnego_gss_ctx_id_t sc; + gss_OID supportedMech = GSS_C_NO_OID; + + sc = (spnego_gss_ctx_id_t)*ctx; + *negState = REJECT; + *tokflag = ERROR_TOKEN_SEND; + + ptr = buf->value; + ret = get_negTokenResp(minor_status, ptr, buf->length, + &acc_negState, &supportedMech, + responseToken, mechListMIC); + if (ret != GSS_S_COMPLETE) + goto cleanup; + if (acc_negState == ACCEPT_DEFECTIVE_TOKEN && + supportedMech == GSS_C_NO_OID && + *responseToken == GSS_C_NO_BUFFER && + *mechListMIC == GSS_C_NO_BUFFER) { + /* Reject "empty" token. */ + ret = GSS_S_DEFECTIVE_TOKEN; + } + if (acc_negState == REJECT) { + *minor_status = ERR_SPNEGO_NEGOTIATION_FAILED; + map_errcode(minor_status); + *tokflag = NO_TOKEN_SEND; + ret = GSS_S_FAILURE; + goto cleanup; + } + /* + * nego_done is false for the first call to init_ctx_cont() + */ + if (!sc->nego_done) { + ret = init_ctx_nego(minor_status, sc, + acc_negState, + supportedMech, responseToken, + mechListMIC, + negState, tokflag); + } else if ((!sc->mech_complete && *responseToken == GSS_C_NO_BUFFER) || + (sc->mech_complete && *responseToken != GSS_C_NO_BUFFER)) { + /* Missing or spurious token from acceptor. */ + ret = GSS_S_DEFECTIVE_TOKEN; + } else if (!sc->mech_complete || + (sc->mic_reqd && + (sc->ctx_flags & GSS_C_INTEG_FLAG))) { + /* Not obviously done; we may decide we're done later in + * init_ctx_call_init or handle_mic. */ + *negState = ACCEPT_INCOMPLETE; + *tokflag = CONT_TOKEN_SEND; + ret = GSS_S_CONTINUE_NEEDED; + } else { + /* mech finished on last pass and no MIC required, so done. */ + *negState = ACCEPT_COMPLETE; + *tokflag = NO_TOKEN_SEND; + ret = GSS_S_COMPLETE; + } +cleanup: + if (supportedMech != GSS_C_NO_OID) + generic_gss_release_oid(&tmpmin, &supportedMech); + return ret; +} + +/* + * Consistency checking and mechanism negotiation handling for second + * call of spnego_gss_init_sec_context(). Call init_ctx_reselect() to + * update internal state if acceptor has counter-proposed. + */ +static OM_uint32 +init_ctx_nego(OM_uint32 *minor_status, spnego_gss_ctx_id_t sc, + OM_uint32 acc_negState, gss_OID supportedMech, + gss_buffer_t *responseToken, gss_buffer_t *mechListMIC, + OM_uint32 *negState, send_token_flag *tokflag) +{ + OM_uint32 ret; + + *negState = REJECT; + *tokflag = ERROR_TOKEN_SEND; + ret = GSS_S_DEFECTIVE_TOKEN; + /* + * Both supportedMech and negState must be present in first + * acceptor token. + */ + if (supportedMech == GSS_C_NO_OID) { + *minor_status = ERR_SPNEGO_NO_MECH_FROM_ACCEPTOR; + map_errcode(minor_status); + return GSS_S_DEFECTIVE_TOKEN; + } + if (acc_negState == ACCEPT_DEFECTIVE_TOKEN) { + *minor_status = ERR_SPNEGO_NEGOTIATION_FAILED; + map_errcode(minor_status); + return GSS_S_DEFECTIVE_TOKEN; + } + + /* + * If the mechanism we sent is not the mechanism returned from + * the server, we need to handle the server's counter + * proposal. There is a bug in SAMBA servers that always send + * the old Kerberos mech OID, even though we sent the new one. + * So we will treat all the Kerberos mech OIDS as the same. + */ + if (!(is_kerb_mech(supportedMech) && + is_kerb_mech(sc->internal_mech)) && + !g_OID_equal(supportedMech, sc->internal_mech)) { + ret = init_ctx_reselect(minor_status, sc, + acc_negState, supportedMech, + responseToken, mechListMIC, + negState, tokflag); + + } else if (*responseToken == GSS_C_NO_BUFFER) { + if (sc->mech_complete) { + /* + * Mech completed on first call to its + * init_sec_context(). Acceptor sends no mech + * token. + */ + *negState = ACCEPT_COMPLETE; + *tokflag = NO_TOKEN_SEND; + ret = GSS_S_COMPLETE; + } else { + /* + * Reject missing mech token when optimistic + * mech selected. + */ + *minor_status = ERR_SPNEGO_NO_TOKEN_FROM_ACCEPTOR; + map_errcode(minor_status); + ret = GSS_S_DEFECTIVE_TOKEN; + } + } else if (sc->mech_complete) { + /* Reject spurious mech token. */ + ret = GSS_S_DEFECTIVE_TOKEN; + } else { + *negState = ACCEPT_INCOMPLETE; + *tokflag = CONT_TOKEN_SEND; + ret = GSS_S_CONTINUE_NEEDED; + } + sc->nego_done = 1; + return ret; +} + +/* + * Handle acceptor's counter-proposal of an alternative mechanism. + */ +static OM_uint32 +init_ctx_reselect(OM_uint32 *minor_status, spnego_gss_ctx_id_t sc, + OM_uint32 acc_negState, gss_OID supportedMech, + gss_buffer_t *responseToken, gss_buffer_t *mechListMIC, + OM_uint32 *negState, send_token_flag *tokflag) +{ + OM_uint32 tmpmin; + size_t i; + + generic_gss_release_oid(&tmpmin, &sc->internal_mech); + gss_delete_sec_context(&tmpmin, &sc->ctx_handle, + GSS_C_NO_BUFFER); + + /* Find supportedMech in sc->mech_set. */ + for (i = 0; i < sc->mech_set->count; i++) { + if (g_OID_equal(supportedMech, &sc->mech_set->elements[i])) + break; + } + if (i == sc->mech_set->count) + return GSS_S_DEFECTIVE_TOKEN; + sc->internal_mech = &sc->mech_set->elements[i]; + + /* + * Windows 2003 and earlier don't correctly send a + * negState of request-mic when counter-proposing a + * mechanism. They probably don't handle mechListMICs + * properly either. + */ + if (acc_negState != REQUEST_MIC) + return GSS_S_DEFECTIVE_TOKEN; + + sc->mech_complete = 0; + sc->mic_reqd = 1; + *negState = REQUEST_MIC; + *tokflag = CONT_TOKEN_SEND; + return GSS_S_CONTINUE_NEEDED; +} + +/* + * Wrap call to mechanism gss_init_sec_context() and update state + * accordingly. + */ +static OM_uint32 +init_ctx_call_init(OM_uint32 *minor_status, + spnego_gss_ctx_id_t sc, + spnego_gss_cred_id_t spcred, + gss_name_t target_name, + OM_uint32 req_flags, + OM_uint32 time_req, + gss_buffer_t mechtok_in, + gss_OID *actual_mech, + gss_buffer_t mechtok_out, + OM_uint32 *ret_flags, + OM_uint32 *time_rec, + OM_uint32 *negState, + send_token_flag *send_token) +{ + OM_uint32 ret, tmpret, tmpmin; + gss_cred_id_t mcred; + + mcred = (spcred == NULL) ? GSS_C_NO_CREDENTIAL : spcred->mcred; + ret = gss_init_sec_context(minor_status, + mcred, + &sc->ctx_handle, + target_name, + sc->internal_mech, + (req_flags | GSS_C_INTEG_FLAG), + time_req, + GSS_C_NO_CHANNEL_BINDINGS, + mechtok_in, + &sc->actual_mech, + mechtok_out, + &sc->ctx_flags, + time_rec); + if (ret == GSS_S_COMPLETE) { + sc->mech_complete = 1; + if (ret_flags != NULL) + *ret_flags = sc->ctx_flags; + /* + * Microsoft SPNEGO implementations expect an even number of + * token exchanges. So if we're sending a final token, ask for + * a zero-length token back from the server. Also ask for a + * token back if this is the first token or if a MIC exchange + * is required. + */ + if (*send_token == CONT_TOKEN_SEND && + mechtok_out->length == 0 && + (!sc->mic_reqd || + !(sc->ctx_flags & GSS_C_INTEG_FLAG))) { + /* The exchange is complete. */ + *negState = ACCEPT_COMPLETE; + ret = GSS_S_COMPLETE; + *send_token = NO_TOKEN_SEND; + } else { + /* Ask for one more hop. */ + *negState = ACCEPT_INCOMPLETE; + ret = GSS_S_CONTINUE_NEEDED; + } + return ret; + } + + if (ret == GSS_S_CONTINUE_NEEDED) + return ret; + + if (*send_token != INIT_TOKEN_SEND) { + *send_token = ERROR_TOKEN_SEND; + *negState = REJECT; + return ret; + } + + /* + * Since this is the first token, we can fall back to later mechanisms + * in the list. Since the mechanism list is expected to be short, we + * can do this with recursion. If all mechanisms produce errors, the + * caller should get the error from the first mech in the list. + */ + memmove(sc->mech_set->elements, sc->mech_set->elements + 1, + --sc->mech_set->count * sizeof(*sc->mech_set->elements)); + if (sc->mech_set->count == 0) + goto fail; + gss_release_buffer(&tmpmin, &sc->DER_mechTypes); + if (put_mech_set(sc->mech_set, &sc->DER_mechTypes) < 0) + goto fail; + tmpret = init_ctx_call_init(&tmpmin, sc, spcred, target_name, + req_flags, time_req, mechtok_in, + actual_mech, mechtok_out, ret_flags, + time_rec, negState, send_token); + if (HARD_ERROR(tmpret)) + goto fail; + *minor_status = tmpmin; + return tmpret; + +fail: + /* Don't output token on error from first call. */ + *send_token = NO_TOKEN_SEND; + *negState = REJECT; + return ret; +} + +/*ARGSUSED*/ +OM_uint32 KRB5_CALLCONV +spnego_gss_init_sec_context( + OM_uint32 *minor_status, + gss_cred_id_t claimant_cred_handle, + gss_ctx_id_t *context_handle, + gss_name_t target_name, + gss_OID mech_type, + OM_uint32 req_flags, + OM_uint32 time_req, + gss_channel_bindings_t input_chan_bindings, + gss_buffer_t input_token, + gss_OID *actual_mech, + gss_buffer_t output_token, + OM_uint32 *ret_flags, + OM_uint32 *time_rec) +{ + send_token_flag send_token = NO_TOKEN_SEND; + OM_uint32 tmpmin, ret, negState; + gss_buffer_t mechtok_in, mechListMIC_in, mechListMIC_out; + gss_buffer_desc mechtok_out = GSS_C_EMPTY_BUFFER; + spnego_gss_cred_id_t spcred = NULL; + spnego_gss_ctx_id_t spnego_ctx = NULL; + + dsyslog("Entering init_sec_context\n"); + + mechtok_in = mechListMIC_out = mechListMIC_in = GSS_C_NO_BUFFER; + negState = REJECT; + + /* + * This function works in three steps: + * + * 1. Perform mechanism negotiation. + * 2. Invoke the negotiated or optimistic mech's gss_init_sec_context + * function and examine the results. + * 3. Process or generate MICs if necessary. + * + * The three steps share responsibility for determining when the + * exchange is complete. If the selected mech completed in a previous + * call and no MIC exchange is expected, then step 1 will decide. If + * the selected mech completes in this call and no MIC exchange is + * expected, then step 2 will decide. If a MIC exchange is expected, + * then step 3 will decide. If an error occurs in any step, the + * exchange will be aborted, possibly with an error token. + * + * negState determines the state of the negotiation, and is + * communicated to the acceptor if a continuing token is sent. + * send_token is used to indicate what type of token, if any, should be + * generated. + */ + + /* Validate arguments. */ + if (minor_status != NULL) + *minor_status = 0; + if (output_token != GSS_C_NO_BUFFER) { + output_token->length = 0; + output_token->value = NULL; + } + if (minor_status == NULL || + output_token == GSS_C_NO_BUFFER || + context_handle == NULL) + return GSS_S_CALL_INACCESSIBLE_WRITE; + + if (actual_mech != NULL) + *actual_mech = GSS_C_NO_OID; + + /* Step 1: perform mechanism negotiation. */ + spcred = (spnego_gss_cred_id_t)claimant_cred_handle; + if (*context_handle == GSS_C_NO_CONTEXT) { + ret = init_ctx_new(minor_status, spcred, + context_handle, &send_token); + if (ret != GSS_S_CONTINUE_NEEDED) { + goto cleanup; + } + } else { + ret = init_ctx_cont(minor_status, context_handle, + input_token, &mechtok_in, + &mechListMIC_in, &negState, &send_token); + if (HARD_ERROR(ret)) { + goto cleanup; + } + } + + /* Step 2: invoke the selected or optimistic mechanism's + * gss_init_sec_context function, if it didn't complete previously. */ + spnego_ctx = (spnego_gss_ctx_id_t)*context_handle; + if (!spnego_ctx->mech_complete) { + ret = init_ctx_call_init( + minor_status, spnego_ctx, spcred, + target_name, req_flags, + time_req, mechtok_in, + actual_mech, &mechtok_out, + ret_flags, time_rec, + &negState, &send_token); + } + + /* Step 3: process or generate the MIC, if the negotiated mech is + * complete and supports MICs. */ + if (!HARD_ERROR(ret) && spnego_ctx->mech_complete && + (spnego_ctx->ctx_flags & GSS_C_INTEG_FLAG)) { + + ret = handle_mic(minor_status, + mechListMIC_in, + (mechtok_out.length != 0), + spnego_ctx, &mechListMIC_out, + &negState, &send_token); + } +cleanup: + if (send_token == INIT_TOKEN_SEND) { + if (make_spnego_tokenInit_msg(spnego_ctx, + 0, + mechListMIC_out, + req_flags, + &mechtok_out, send_token, + output_token) < 0) { + ret = GSS_S_FAILURE; + } + } else if (send_token != NO_TOKEN_SEND) { + if (make_spnego_tokenTarg_msg(negState, GSS_C_NO_OID, + &mechtok_out, mechListMIC_out, + send_token, + output_token) < 0) { + ret = GSS_S_FAILURE; + } + } + gss_release_buffer(&tmpmin, &mechtok_out); + if (ret == GSS_S_COMPLETE) { + /* + * Now, switch the output context to refer to the + * negotiated mechanism's context. + */ + *context_handle = (gss_ctx_id_t)spnego_ctx->ctx_handle; + if (actual_mech != NULL) + *actual_mech = spnego_ctx->actual_mech; + if (ret_flags != NULL) + *ret_flags = spnego_ctx->ctx_flags; + release_spnego_ctx(&spnego_ctx); + } else if (ret != GSS_S_CONTINUE_NEEDED) { + if (spnego_ctx != NULL) { + gss_delete_sec_context(&tmpmin, + &spnego_ctx->ctx_handle, + GSS_C_NO_BUFFER); + release_spnego_ctx(&spnego_ctx); + } + *context_handle = GSS_C_NO_CONTEXT; + } + if (mechtok_in != GSS_C_NO_BUFFER) { + gss_release_buffer(&tmpmin, mechtok_in); + free(mechtok_in); + } + if (mechListMIC_in != GSS_C_NO_BUFFER) { + gss_release_buffer(&tmpmin, mechListMIC_in); + free(mechListMIC_in); + } + if (mechListMIC_out != GSS_C_NO_BUFFER) { + gss_release_buffer(&tmpmin, mechListMIC_out); + free(mechListMIC_out); + } + return ret; +} /* init_sec_context */ + +/* We don't want to import KRB5 headers here */ +static const gss_OID_desc gss_mech_krb5_oid = + { 9, "\052\206\110\206\367\022\001\002\002" }; +static const gss_OID_desc gss_mech_krb5_wrong_oid = + { 9, "\052\206\110\202\367\022\001\002\002" }; + +/* + * verify that the input token length is not 0. If it is, just return. + * If the token length is greater than 0, der encode as a sequence + * and place in buf_out, advancing buf_out. + */ + +static int +put_neg_hints(unsigned char **buf_out, gss_buffer_t input_token, + unsigned int buflen) +{ + int ret; + + /* if token length is 0, we do not want to send */ + if (input_token->length == 0) + return (0); + + if (input_token->length > buflen) + return (-1); + + *(*buf_out)++ = SEQUENCE; + if ((ret = gssint_put_der_length(input_token->length, buf_out, + input_token->length))) + return (ret); + TWRITE_STR(*buf_out, input_token->value, input_token->length); + return (0); +} + +/* + * NegHints ::= SEQUENCE { + * hintName [0] GeneralString OPTIONAL, + * hintAddress [1] OCTET STRING OPTIONAL + * } + */ + +#define HOST_PREFIX "host@" +#define HOST_PREFIX_LEN (sizeof(HOST_PREFIX) - 1) + +static int +make_NegHints(OM_uint32 *minor_status, + spnego_gss_cred_id_t spcred, gss_buffer_t *outbuf) +{ + gss_buffer_desc hintNameBuf; + gss_name_t hintName = GSS_C_NO_NAME; + gss_name_t hintKerberosName; + gss_OID hintNameType; + OM_uint32 major_status; + OM_uint32 minor; + unsigned int tlen = 0; + unsigned int hintNameSize = 0; + unsigned char *ptr; + unsigned char *t; + + *outbuf = GSS_C_NO_BUFFER; + + if (spcred != NULL) { + major_status = gss_inquire_cred(minor_status, + spcred->mcred, + &hintName, + NULL, + NULL, + NULL); + if (major_status != GSS_S_COMPLETE) + return (major_status); + } + + if (hintName == GSS_C_NO_NAME) { + krb5_error_code code; + krb5int_access kaccess; + char hostname[HOST_PREFIX_LEN + MAXHOSTNAMELEN + 1] = HOST_PREFIX; + + code = krb5int_accessor(&kaccess, KRB5INT_ACCESS_VERSION); + if (code != 0) { + *minor_status = code; + return (GSS_S_FAILURE); + } + + /* this breaks mutual authentication but Samba relies on it */ + code = (*kaccess.clean_hostname)(NULL, NULL, + &hostname[HOST_PREFIX_LEN], + MAXHOSTNAMELEN); + if (code != 0) { + *minor_status = code; + return (GSS_S_FAILURE); + } + + hintNameBuf.value = hostname; + hintNameBuf.length = strlen(hostname); + + major_status = gss_import_name(minor_status, + &hintNameBuf, + GSS_C_NT_HOSTBASED_SERVICE, + &hintName); + if (major_status != GSS_S_COMPLETE) { + return (major_status); + } + } + + hintNameBuf.value = NULL; + hintNameBuf.length = 0; + + major_status = gss_canonicalize_name(minor_status, + hintName, + (gss_OID)&gss_mech_krb5_oid, + &hintKerberosName); + if (major_status != GSS_S_COMPLETE) { + gss_release_name(&minor, &hintName); + return (major_status); + } + gss_release_name(&minor, &hintName); + + major_status = gss_display_name(minor_status, + hintKerberosName, + &hintNameBuf, + &hintNameType); + if (major_status != GSS_S_COMPLETE) { + gss_release_name(&minor, &hintName); + return (major_status); + } + gss_release_name(&minor, &hintKerberosName); + + /* + * Now encode the name hint into a NegHints ASN.1 type + */ + major_status = GSS_S_FAILURE; + + /* Length of DER encoded GeneralString */ + tlen = 1 + gssint_der_length_size(hintNameBuf.length) + + hintNameBuf.length; + hintNameSize = tlen; + + /* Length of DER encoded hintName */ + tlen += 1 + gssint_der_length_size(hintNameSize); + + t = gssalloc_malloc(tlen); + if (t == NULL) { + *minor_status = ENOMEM; + goto errout; + } + + ptr = t; + + *ptr++ = CONTEXT | 0x00; /* hintName identifier */ + if (gssint_put_der_length(hintNameSize, + &ptr, tlen - (int)(ptr-t))) + goto errout; + + *ptr++ = GENERAL_STRING; + if (gssint_put_der_length(hintNameBuf.length, + &ptr, tlen - (int)(ptr-t))) + goto errout; + + memcpy(ptr, hintNameBuf.value, hintNameBuf.length); + ptr += hintNameBuf.length; + + *outbuf = (gss_buffer_t)malloc(sizeof(gss_buffer_desc)); + if (*outbuf == NULL) { + *minor_status = ENOMEM; + goto errout; + } + (*outbuf)->value = (void *)t; + (*outbuf)->length = ptr - t; + + t = NULL; /* don't free */ + + *minor_status = 0; + major_status = GSS_S_COMPLETE; + +errout: + if (t != NULL) { + free(t); + } + + gss_release_buffer(&minor, &hintNameBuf); + + return (major_status); +} + +/* + * Support the Microsoft NegHints extension to SPNEGO for compatibility with + * some versions of Samba. See: + * http://msdn.microsoft.com/en-us/library/cc247039(PROT.10).aspx + */ +static OM_uint32 +acc_ctx_hints(OM_uint32 *minor_status, + gss_ctx_id_t *ctx, + spnego_gss_cred_id_t spcred, + gss_buffer_t *mechListMIC, + OM_uint32 *negState, + send_token_flag *return_token) +{ + OM_uint32 tmpmin, ret; + gss_OID_set supported_mechSet; + spnego_gss_ctx_id_t sc = NULL; + + *mechListMIC = GSS_C_NO_BUFFER; + supported_mechSet = GSS_C_NO_OID_SET; + *return_token = NO_TOKEN_SEND; + *negState = REJECT; + *minor_status = 0; + + /* A hint request must be the first token received. */ + if (*ctx != GSS_C_NO_CONTEXT) + return GSS_S_DEFECTIVE_TOKEN; + + ret = get_negotiable_mechs(minor_status, spcred, GSS_C_ACCEPT, + &supported_mechSet); + if (ret != GSS_S_COMPLETE) + goto cleanup; + + ret = make_NegHints(minor_status, spcred, mechListMIC); + if (ret != GSS_S_COMPLETE) + goto cleanup; + + sc = create_spnego_ctx(); + if (sc == NULL) { + ret = GSS_S_FAILURE; + goto cleanup; + } + if (put_mech_set(supported_mechSet, &sc->DER_mechTypes) < 0) { + ret = GSS_S_FAILURE; + goto cleanup; + } + sc->internal_mech = GSS_C_NO_OID; + + *negState = ACCEPT_INCOMPLETE; + *return_token = INIT_TOKEN_SEND; + sc->firstpass = 1; + *ctx = (gss_ctx_id_t)sc; + sc = NULL; + ret = GSS_S_COMPLETE; + +cleanup: + release_spnego_ctx(&sc); + gss_release_oid_set(&tmpmin, &supported_mechSet); + + return ret; +} + +/* + * Set negState to REJECT if the token is defective, else + * ACCEPT_INCOMPLETE or REQUEST_MIC, depending on whether initiator's + * preferred mechanism is supported. + */ +static OM_uint32 +acc_ctx_new(OM_uint32 *minor_status, + gss_buffer_t buf, + gss_ctx_id_t *ctx, + spnego_gss_cred_id_t spcred, + gss_buffer_t *mechToken, + gss_buffer_t *mechListMIC, + OM_uint32 *negState, + send_token_flag *return_token) +{ + OM_uint32 tmpmin, ret, req_flags; + gss_OID_set supported_mechSet, mechTypes; + gss_buffer_desc der_mechTypes; + gss_OID mech_wanted; + spnego_gss_ctx_id_t sc = NULL; + + ret = GSS_S_DEFECTIVE_TOKEN; + der_mechTypes.length = 0; + der_mechTypes.value = NULL; + *mechToken = *mechListMIC = GSS_C_NO_BUFFER; + supported_mechSet = mechTypes = GSS_C_NO_OID_SET; + *return_token = ERROR_TOKEN_SEND; + *negState = REJECT; + *minor_status = 0; + + ret = get_negTokenInit(minor_status, buf, &der_mechTypes, + &mechTypes, &req_flags, + mechToken, mechListMIC); + if (ret != GSS_S_COMPLETE) { + goto cleanup; + } + ret = get_negotiable_mechs(minor_status, spcred, GSS_C_ACCEPT, + &supported_mechSet); + if (ret != GSS_S_COMPLETE) { + *return_token = NO_TOKEN_SEND; + goto cleanup; + } + /* + * Select the best match between the list of mechs + * that the initiator requested and the list that + * the acceptor will support. + */ + mech_wanted = negotiate_mech(supported_mechSet, mechTypes, negState); + if (*negState == REJECT) { + ret = GSS_S_BAD_MECH; + goto cleanup; + } + sc = (spnego_gss_ctx_id_t)*ctx; + if (sc != NULL) { + gss_release_buffer(&tmpmin, &sc->DER_mechTypes); + assert(mech_wanted != GSS_C_NO_OID); + } else + sc = create_spnego_ctx(); + if (sc == NULL) { + ret = GSS_S_FAILURE; + *return_token = NO_TOKEN_SEND; + goto cleanup; + } + sc->mech_set = supported_mechSet; + supported_mechSet = GSS_C_NO_OID_SET; + sc->internal_mech = mech_wanted; + sc->DER_mechTypes = der_mechTypes; + der_mechTypes.length = 0; + der_mechTypes.value = NULL; + + if (*negState == REQUEST_MIC) + sc->mic_reqd = 1; + + *return_token = INIT_TOKEN_SEND; + sc->firstpass = 1; + *ctx = (gss_ctx_id_t)sc; + ret = GSS_S_COMPLETE; +cleanup: + gss_release_oid_set(&tmpmin, &mechTypes); + gss_release_oid_set(&tmpmin, &supported_mechSet); + if (der_mechTypes.length != 0) + gss_release_buffer(&tmpmin, &der_mechTypes); + + return ret; +} + +static OM_uint32 +acc_ctx_cont(OM_uint32 *minstat, + gss_buffer_t buf, + gss_ctx_id_t *ctx, + gss_buffer_t *responseToken, + gss_buffer_t *mechListMIC, + OM_uint32 *negState, + send_token_flag *return_token) +{ + OM_uint32 ret, tmpmin; + gss_OID supportedMech; + spnego_gss_ctx_id_t sc; + unsigned int len; + unsigned char *ptr, *bufstart; + + sc = (spnego_gss_ctx_id_t)*ctx; + ret = GSS_S_DEFECTIVE_TOKEN; + *negState = REJECT; + *minstat = 0; + supportedMech = GSS_C_NO_OID; + *return_token = ERROR_TOKEN_SEND; + *responseToken = *mechListMIC = GSS_C_NO_BUFFER; + + ptr = bufstart = buf->value; +#define REMAIN (buf->length - (ptr - bufstart)) + if (REMAIN > INT_MAX) + return GSS_S_DEFECTIVE_TOKEN; + + /* + * Attempt to work with old Sun SPNEGO. + */ + if (*ptr == HEADER_ID) { + ret = g_verify_token_header(gss_mech_spnego, + &len, &ptr, 0, REMAIN); + if (ret) { + *minstat = ret; + return GSS_S_DEFECTIVE_TOKEN; + } + } + if (*ptr != (CONTEXT | 0x01)) { + return GSS_S_DEFECTIVE_TOKEN; + } + ret = get_negTokenResp(minstat, ptr, REMAIN, + negState, &supportedMech, + responseToken, mechListMIC); + if (ret != GSS_S_COMPLETE) + goto cleanup; + + if (*responseToken == GSS_C_NO_BUFFER && + *mechListMIC == GSS_C_NO_BUFFER) { + + ret = GSS_S_DEFECTIVE_TOKEN; + goto cleanup; + } + if (supportedMech != GSS_C_NO_OID) { + ret = GSS_S_DEFECTIVE_TOKEN; + goto cleanup; + } + sc->firstpass = 0; + *negState = ACCEPT_INCOMPLETE; + *return_token = CONT_TOKEN_SEND; +cleanup: + if (supportedMech != GSS_C_NO_OID) { + generic_gss_release_oid(&tmpmin, &supportedMech); + } + return ret; +#undef REMAIN +} + +/* + * Verify that mech OID is either exactly the same as the negotiated + * mech OID, or is a mech OID supported by the negotiated mech. MS + * implementations can list a most preferred mech using an incorrect + * krb5 OID while emitting a krb5 initiator mech token having the + * correct krb5 mech OID. + */ +static OM_uint32 +acc_ctx_vfy_oid(OM_uint32 *minor_status, + spnego_gss_ctx_id_t sc, gss_OID mechoid, + OM_uint32 *negState, send_token_flag *tokflag) +{ + OM_uint32 ret, tmpmin; + gss_mechanism mech = NULL; + gss_OID_set mech_set = GSS_C_NO_OID_SET; + int present = 0; + + if (g_OID_equal(sc->internal_mech, mechoid)) + return GSS_S_COMPLETE; + + mech = gssint_get_mechanism(sc->internal_mech); + if (mech == NULL || mech->gss_indicate_mechs == NULL) { + *minor_status = ERR_SPNEGO_NEGOTIATION_FAILED; + map_errcode(minor_status); + *negState = REJECT; + *tokflag = ERROR_TOKEN_SEND; + return GSS_S_BAD_MECH; + } + ret = mech->gss_indicate_mechs(minor_status, &mech_set); + if (ret != GSS_S_COMPLETE) { + *tokflag = NO_TOKEN_SEND; + map_error(minor_status, mech); + goto cleanup; + } + ret = gss_test_oid_set_member(minor_status, mechoid, + mech_set, &present); + if (ret != GSS_S_COMPLETE) + goto cleanup; + if (!present) { + *minor_status = ERR_SPNEGO_NEGOTIATION_FAILED; + map_errcode(minor_status); + *negState = REJECT; + *tokflag = ERROR_TOKEN_SEND; + ret = GSS_S_BAD_MECH; + } +cleanup: + gss_release_oid_set(&tmpmin, &mech_set); + return ret; +} +#ifndef LEAN_CLIENT +/* + * Wrap call to gss_accept_sec_context() and update state + * accordingly. + */ +static OM_uint32 +acc_ctx_call_acc(OM_uint32 *minor_status, spnego_gss_ctx_id_t sc, + spnego_gss_cred_id_t spcred, gss_buffer_t mechtok_in, + gss_OID *mech_type, gss_buffer_t mechtok_out, + OM_uint32 *ret_flags, OM_uint32 *time_rec, + gss_cred_id_t *delegated_cred_handle, + OM_uint32 *negState, send_token_flag *tokflag) +{ + OM_uint32 ret; + gss_OID_desc mechoid; + gss_cred_id_t mcred; + + if (sc->ctx_handle == GSS_C_NO_CONTEXT) { + /* + * mechoid is an alias; don't free it. + */ + ret = gssint_get_mech_type(&mechoid, mechtok_in); + if (ret != GSS_S_COMPLETE) { + *tokflag = NO_TOKEN_SEND; + return ret; + } + ret = acc_ctx_vfy_oid(minor_status, sc, &mechoid, + negState, tokflag); + if (ret != GSS_S_COMPLETE) + return ret; + } + + mcred = (spcred == NULL) ? GSS_C_NO_CREDENTIAL : spcred->mcred; + ret = gss_accept_sec_context(minor_status, + &sc->ctx_handle, + mcred, + mechtok_in, + GSS_C_NO_CHANNEL_BINDINGS, + &sc->internal_name, + mech_type, + mechtok_out, + &sc->ctx_flags, + time_rec, + delegated_cred_handle); + if (ret == GSS_S_COMPLETE) { +#ifdef MS_BUG_TEST + /* + * Force MIC to be not required even if we previously + * requested a MIC. + */ + char *envstr = getenv("MS_FORCE_NO_MIC"); + + if (envstr != NULL && strcmp(envstr, "1") == 0 && + !(sc->ctx_flags & GSS_C_MUTUAL_FLAG) && + sc->mic_reqd) { + + sc->mic_reqd = 0; + } +#endif + sc->mech_complete = 1; + if (ret_flags != NULL) + *ret_flags = sc->ctx_flags; + + if (!sc->mic_reqd || + !(sc->ctx_flags & GSS_C_INTEG_FLAG)) { + /* No MIC exchange required, so we're done. */ + *negState = ACCEPT_COMPLETE; + ret = GSS_S_COMPLETE; + } else { + /* handle_mic will decide if we're done. */ + ret = GSS_S_CONTINUE_NEEDED; + } + } else if (ret != GSS_S_CONTINUE_NEEDED) { + *negState = REJECT; + *tokflag = ERROR_TOKEN_SEND; + } + return ret; +} + +/*ARGSUSED*/ +OM_uint32 KRB5_CALLCONV +spnego_gss_accept_sec_context( + 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) +{ + OM_uint32 ret, tmpmin, negState; + send_token_flag return_token; + gss_buffer_t mechtok_in, mic_in, mic_out; + gss_buffer_desc mechtok_out = GSS_C_EMPTY_BUFFER; + spnego_gss_ctx_id_t sc = NULL; + spnego_gss_cred_id_t spcred = NULL; + int sendTokenInit = 0, tmpret; + + mechtok_in = mic_in = mic_out = GSS_C_NO_BUFFER; + + /* + * This function works in three steps: + * + * 1. Perform mechanism negotiation. + * 2. Invoke the negotiated mech's gss_accept_sec_context function + * and examine the results. + * 3. Process or generate MICs if necessary. + * + * Step one determines whether the negotiation requires a MIC exchange, + * while steps two and three share responsibility for determining when + * the exchange is complete. If the selected mech completes in this + * call and no MIC exchange is expected, then step 2 will decide. If a + * MIC exchange is expected, then step 3 will decide. If an error + * occurs in any step, the exchange will be aborted, possibly with an + * error token. + * + * negState determines the state of the negotiation, and is + * communicated to the acceptor if a continuing token is sent. + * return_token is used to indicate what type of token, if any, should + * be generated. + */ + + /* Validate arguments. */ + if (minor_status != NULL) + *minor_status = 0; + if (output_token != GSS_C_NO_BUFFER) { + output_token->length = 0; + output_token->value = NULL; + } + + if (minor_status == NULL || + output_token == GSS_C_NO_BUFFER || + context_handle == NULL) + return GSS_S_CALL_INACCESSIBLE_WRITE; + + if (input_token == GSS_C_NO_BUFFER) + return GSS_S_CALL_INACCESSIBLE_READ; + + /* Step 1: Perform mechanism negotiation. */ + sc = (spnego_gss_ctx_id_t)*context_handle; + spcred = (spnego_gss_cred_id_t)verifier_cred_handle; + if (sc == NULL || sc->internal_mech == GSS_C_NO_OID) { + /* Process an initial token or request for NegHints. */ + if (src_name != NULL) + *src_name = GSS_C_NO_NAME; + if (mech_type != NULL) + *mech_type = GSS_C_NO_OID; + if (time_rec != NULL) + *time_rec = 0; + if (ret_flags != NULL) + *ret_flags = 0; + if (delegated_cred_handle != NULL) + *delegated_cred_handle = GSS_C_NO_CREDENTIAL; + if (input_token->length == 0) { + ret = acc_ctx_hints(minor_status, + context_handle, spcred, + &mic_out, + &negState, + &return_token); + if (ret != GSS_S_COMPLETE) + goto cleanup; + sendTokenInit = 1; + ret = GSS_S_CONTINUE_NEEDED; + } else { + /* Can set negState to REQUEST_MIC */ + ret = acc_ctx_new(minor_status, input_token, + context_handle, spcred, + &mechtok_in, &mic_in, + &negState, &return_token); + if (ret != GSS_S_COMPLETE) + goto cleanup; + ret = GSS_S_CONTINUE_NEEDED; + } + } else { + /* Process a response token. Can set negState to + * ACCEPT_INCOMPLETE. */ + ret = acc_ctx_cont(minor_status, input_token, + context_handle, &mechtok_in, + &mic_in, &negState, &return_token); + if (ret != GSS_S_COMPLETE) + goto cleanup; + ret = GSS_S_CONTINUE_NEEDED; + } + + /* Step 2: invoke the negotiated mechanism's gss_accept_sec_context + * function. */ + sc = (spnego_gss_ctx_id_t)*context_handle; + /* + * Handle mechtok_in and mic_in only if they are + * present in input_token. If neither is present, whether + * this is an error depends on whether this is the first + * round-trip. RET is set to a default value according to + * whether it is the first round-trip. + */ + if (negState != REQUEST_MIC && mechtok_in != GSS_C_NO_BUFFER) { + ret = acc_ctx_call_acc(minor_status, sc, spcred, + mechtok_in, mech_type, &mechtok_out, + ret_flags, time_rec, + delegated_cred_handle, + &negState, &return_token); + } + + /* Step 3: process or generate the MIC, if the negotiated mech is + * complete and supports MICs. */ + if (!HARD_ERROR(ret) && sc->mech_complete && + (sc->ctx_flags & GSS_C_INTEG_FLAG)) { + + ret = handle_mic(minor_status, mic_in, + (mechtok_out.length != 0), + sc, &mic_out, + &negState, &return_token); + } +cleanup: + if (return_token == INIT_TOKEN_SEND && sendTokenInit) { + assert(sc != NULL); + tmpret = make_spnego_tokenInit_msg(sc, 1, mic_out, 0, + GSS_C_NO_BUFFER, + return_token, output_token); + if (tmpret < 0) + ret = GSS_S_FAILURE; + } else if (return_token != NO_TOKEN_SEND && + return_token != CHECK_MIC) { + tmpret = make_spnego_tokenTarg_msg(negState, + sc ? sc->internal_mech : + GSS_C_NO_OID, + &mechtok_out, mic_out, + return_token, + output_token); + if (tmpret < 0) + ret = GSS_S_FAILURE; + } + if (ret == GSS_S_COMPLETE) { + *context_handle = (gss_ctx_id_t)sc->ctx_handle; + if (sc->internal_name != GSS_C_NO_NAME && + src_name != NULL) { + *src_name = sc->internal_name; + sc->internal_name = GSS_C_NO_NAME; + } + release_spnego_ctx(&sc); + } else if (ret != GSS_S_CONTINUE_NEEDED) { + if (sc != NULL) { + gss_delete_sec_context(&tmpmin, &sc->ctx_handle, + GSS_C_NO_BUFFER); + release_spnego_ctx(&sc); + } + *context_handle = GSS_C_NO_CONTEXT; + } + gss_release_buffer(&tmpmin, &mechtok_out); + if (mechtok_in != GSS_C_NO_BUFFER) { + gss_release_buffer(&tmpmin, mechtok_in); + free(mechtok_in); + } + if (mic_in != GSS_C_NO_BUFFER) { + gss_release_buffer(&tmpmin, mic_in); + free(mic_in); + } + if (mic_out != GSS_C_NO_BUFFER) { + gss_release_buffer(&tmpmin, mic_out); + free(mic_out); + } + return ret; +} +#endif /* LEAN_CLIENT */ + + +/*ARGSUSED*/ +OM_uint32 KRB5_CALLCONV +spnego_gss_display_status( + OM_uint32 *minor_status, + OM_uint32 status_value, + int status_type, + gss_OID mech_type, + OM_uint32 *message_context, + gss_buffer_t status_string) +{ + dsyslog("Entering display_status\n"); + + *message_context = 0; + switch (status_value) { + case ERR_SPNEGO_NO_MECHS_AVAILABLE: + /* CSTYLED */ + *status_string = make_err_msg(_("SPNEGO cannot find " + "mechanisms to negotiate")); + break; + case ERR_SPNEGO_NO_CREDS_ACQUIRED: + /* CSTYLED */ + *status_string = make_err_msg(_("SPNEGO failed to acquire " + "creds")); + break; + case ERR_SPNEGO_NO_MECH_FROM_ACCEPTOR: + /* CSTYLED */ + *status_string = make_err_msg(_("SPNEGO acceptor did not " + "select a mechanism")); + break; + case ERR_SPNEGO_NEGOTIATION_FAILED: + /* CSTYLED */ + *status_string = make_err_msg(_("SPNEGO failed to negotiate a " + "mechanism")); + break; + case ERR_SPNEGO_NO_TOKEN_FROM_ACCEPTOR: + /* CSTYLED */ + *status_string = make_err_msg(_("SPNEGO acceptor did not " + "return a valid token")); + break; + default: + status_string->length = 0; + status_string->value = ""; + break; + } + + dsyslog("Leaving display_status\n"); + return (GSS_S_COMPLETE); +} + + +/*ARGSUSED*/ +OM_uint32 KRB5_CALLCONV +spnego_gss_import_name( + OM_uint32 *minor_status, + gss_buffer_t input_name_buffer, + gss_OID input_name_type, + gss_name_t *output_name) +{ + OM_uint32 status; + + dsyslog("Entering import_name\n"); + + status = gss_import_name(minor_status, input_name_buffer, + input_name_type, output_name); + + dsyslog("Leaving import_name\n"); + return (status); +} + +/*ARGSUSED*/ +OM_uint32 KRB5_CALLCONV +spnego_gss_release_name( + OM_uint32 *minor_status, + gss_name_t *input_name) +{ + OM_uint32 status; + + dsyslog("Entering release_name\n"); + + status = gss_release_name(minor_status, input_name); + + dsyslog("Leaving release_name\n"); + return (status); +} + +/*ARGSUSED*/ +OM_uint32 KRB5_CALLCONV +spnego_gss_duplicate_name( + OM_uint32 *minor_status, + const gss_name_t input_name, + gss_name_t *output_name) +{ + OM_uint32 status; + + dsyslog("Entering duplicate_name\n"); + + status = gss_duplicate_name(minor_status, input_name, output_name); + + dsyslog("Leaving duplicate_name\n"); + return (status); +} + +OM_uint32 KRB5_CALLCONV +spnego_gss_inquire_cred( + OM_uint32 *minor_status, + gss_cred_id_t cred_handle, + gss_name_t *name, + OM_uint32 *lifetime, + int *cred_usage, + gss_OID_set *mechanisms) +{ + OM_uint32 status; + spnego_gss_cred_id_t spcred = NULL; + gss_cred_id_t creds = GSS_C_NO_CREDENTIAL; + OM_uint32 tmp_minor_status; + OM_uint32 initiator_lifetime, acceptor_lifetime; + + dsyslog("Entering inquire_cred\n"); + + /* + * To avoid infinite recursion, if GSS_C_NO_CREDENTIAL is + * supplied we call gss_inquire_cred_by_mech() on the + * first non-SPNEGO mechanism. + */ + spcred = (spnego_gss_cred_id_t)cred_handle; + if (spcred == NULL) { + status = get_available_mechs(minor_status, + GSS_C_NO_NAME, + GSS_C_BOTH, + GSS_C_NO_CRED_STORE, + &creds, + mechanisms); + if (status != GSS_S_COMPLETE) { + dsyslog("Leaving inquire_cred\n"); + return (status); + } + + if ((*mechanisms)->count == 0) { + gss_release_cred(&tmp_minor_status, &creds); + gss_release_oid_set(&tmp_minor_status, mechanisms); + dsyslog("Leaving inquire_cred\n"); + return (GSS_S_DEFECTIVE_CREDENTIAL); + } + + assert((*mechanisms)->elements != NULL); + + status = gss_inquire_cred_by_mech(minor_status, + creds, + &(*mechanisms)->elements[0], + name, + &initiator_lifetime, + &acceptor_lifetime, + cred_usage); + if (status != GSS_S_COMPLETE) { + gss_release_cred(&tmp_minor_status, &creds); + dsyslog("Leaving inquire_cred\n"); + return (status); + } + + if (lifetime != NULL) + *lifetime = (*cred_usage == GSS_C_ACCEPT) ? + acceptor_lifetime : initiator_lifetime; + + gss_release_cred(&tmp_minor_status, &creds); + } else { + status = gss_inquire_cred(minor_status, spcred->mcred, + name, lifetime, + cred_usage, mechanisms); + } + + dsyslog("Leaving inquire_cred\n"); + + return (status); +} + +/*ARGSUSED*/ +OM_uint32 KRB5_CALLCONV +spnego_gss_compare_name( + OM_uint32 *minor_status, + const gss_name_t name1, + const gss_name_t name2, + int *name_equal) +{ + OM_uint32 status = GSS_S_COMPLETE; + dsyslog("Entering compare_name\n"); + + status = gss_compare_name(minor_status, name1, name2, name_equal); + + dsyslog("Leaving compare_name\n"); + return (status); +} + +/*ARGSUSED*/ +/*ARGSUSED*/ +OM_uint32 KRB5_CALLCONV +spnego_gss_display_name( + OM_uint32 *minor_status, + gss_name_t input_name, + gss_buffer_t output_name_buffer, + gss_OID *output_name_type) +{ + OM_uint32 status = GSS_S_COMPLETE; + dsyslog("Entering display_name\n"); + + status = gss_display_name(minor_status, input_name, + output_name_buffer, output_name_type); + + dsyslog("Leaving display_name\n"); + return (status); +} + + +/*ARGSUSED*/ +OM_uint32 KRB5_CALLCONV +spnego_gss_inquire_names_for_mech( + OM_uint32 *minor_status, + gss_OID mechanism, + gss_OID_set *name_types) +{ + OM_uint32 major, minor; + + dsyslog("Entering inquire_names_for_mech\n"); + /* + * We only know how to handle our own mechanism. + */ + if ((mechanism != GSS_C_NULL_OID) && + !g_OID_equal(gss_mech_spnego, mechanism)) { + *minor_status = 0; + return (GSS_S_FAILURE); + } + + major = gss_create_empty_oid_set(minor_status, name_types); + if (major == GSS_S_COMPLETE) { + /* Now add our members. */ + if (((major = gss_add_oid_set_member(minor_status, + (gss_OID) GSS_C_NT_USER_NAME, + name_types)) == GSS_S_COMPLETE) && + ((major = gss_add_oid_set_member(minor_status, + (gss_OID) GSS_C_NT_MACHINE_UID_NAME, + name_types)) == GSS_S_COMPLETE) && + ((major = gss_add_oid_set_member(minor_status, + (gss_OID) GSS_C_NT_STRING_UID_NAME, + name_types)) == GSS_S_COMPLETE)) { + major = gss_add_oid_set_member(minor_status, + (gss_OID) GSS_C_NT_HOSTBASED_SERVICE, + name_types); + } + + if (major != GSS_S_COMPLETE) + (void) gss_release_oid_set(&minor, name_types); + } + + dsyslog("Leaving inquire_names_for_mech\n"); + return (major); +} + +OM_uint32 KRB5_CALLCONV +spnego_gss_unwrap( + OM_uint32 *minor_status, + gss_ctx_id_t context_handle, + gss_buffer_t input_message_buffer, + gss_buffer_t output_message_buffer, + int *conf_state, + gss_qop_t *qop_state) +{ + OM_uint32 ret; + ret = gss_unwrap(minor_status, + context_handle, + input_message_buffer, + output_message_buffer, + conf_state, + qop_state); + + return (ret); +} + +OM_uint32 KRB5_CALLCONV +spnego_gss_wrap( + OM_uint32 *minor_status, + gss_ctx_id_t context_handle, + int conf_req_flag, + gss_qop_t qop_req, + gss_buffer_t input_message_buffer, + int *conf_state, + gss_buffer_t output_message_buffer) +{ + OM_uint32 ret; + ret = gss_wrap(minor_status, + context_handle, + conf_req_flag, + qop_req, + input_message_buffer, + conf_state, + output_message_buffer); + + return (ret); +} + +OM_uint32 KRB5_CALLCONV +spnego_gss_process_context_token( + OM_uint32 *minor_status, + const gss_ctx_id_t context_handle, + const gss_buffer_t token_buffer) +{ + OM_uint32 ret; + ret = gss_process_context_token(minor_status, + context_handle, + token_buffer); + + return (ret); +} + +OM_uint32 KRB5_CALLCONV +spnego_gss_delete_sec_context( + OM_uint32 *minor_status, + gss_ctx_id_t *context_handle, + gss_buffer_t output_token) +{ + OM_uint32 ret = GSS_S_COMPLETE; + spnego_gss_ctx_id_t *ctx = + (spnego_gss_ctx_id_t *)context_handle; + + *minor_status = 0; + + if (context_handle == NULL) + return (GSS_S_FAILURE); + + if (*ctx == NULL) + return (GSS_S_COMPLETE); + + /* + * If this is still an SPNEGO mech, release it locally. + */ + if ((*ctx)->magic_num == SPNEGO_MAGIC_ID) { + (void) gss_delete_sec_context(minor_status, + &(*ctx)->ctx_handle, + output_token); + (void) release_spnego_ctx(ctx); + } else { + ret = gss_delete_sec_context(minor_status, + context_handle, + output_token); + } + + return (ret); +} + +OM_uint32 KRB5_CALLCONV +spnego_gss_context_time( + OM_uint32 *minor_status, + const gss_ctx_id_t context_handle, + OM_uint32 *time_rec) +{ + OM_uint32 ret; + ret = gss_context_time(minor_status, + context_handle, + time_rec); + return (ret); +} +#ifndef LEAN_CLIENT +OM_uint32 KRB5_CALLCONV +spnego_gss_export_sec_context( + OM_uint32 *minor_status, + gss_ctx_id_t *context_handle, + gss_buffer_t interprocess_token) +{ + OM_uint32 ret; + ret = gss_export_sec_context(minor_status, + context_handle, + interprocess_token); + return (ret); +} + +OM_uint32 KRB5_CALLCONV +spnego_gss_import_sec_context( + OM_uint32 *minor_status, + const gss_buffer_t interprocess_token, + gss_ctx_id_t *context_handle) +{ + OM_uint32 ret; + ret = gss_import_sec_context(minor_status, + interprocess_token, + context_handle); + return (ret); +} +#endif /* LEAN_CLIENT */ + +OM_uint32 KRB5_CALLCONV +spnego_gss_inquire_context( + OM_uint32 *minor_status, + const gss_ctx_id_t context_handle, + gss_name_t *src_name, + gss_name_t *targ_name, + OM_uint32 *lifetime_rec, + gss_OID *mech_type, + OM_uint32 *ctx_flags, + int *locally_initiated, + int *opened) +{ + OM_uint32 ret = GSS_S_COMPLETE; + + ret = gss_inquire_context(minor_status, + context_handle, + src_name, + targ_name, + lifetime_rec, + mech_type, + ctx_flags, + locally_initiated, + opened); + + return (ret); +} + +OM_uint32 KRB5_CALLCONV +spnego_gss_wrap_size_limit( + OM_uint32 *minor_status, + const gss_ctx_id_t context_handle, + int conf_req_flag, + gss_qop_t qop_req, + OM_uint32 req_output_size, + OM_uint32 *max_input_size) +{ + OM_uint32 ret; + ret = gss_wrap_size_limit(minor_status, + context_handle, + conf_req_flag, + qop_req, + req_output_size, + max_input_size); + return (ret); +} + +OM_uint32 KRB5_CALLCONV +spnego_gss_get_mic( + OM_uint32 *minor_status, + const gss_ctx_id_t context_handle, + gss_qop_t qop_req, + const gss_buffer_t message_buffer, + gss_buffer_t message_token) +{ + OM_uint32 ret; + ret = gss_get_mic(minor_status, + context_handle, + qop_req, + message_buffer, + message_token); + return (ret); +} + +OM_uint32 KRB5_CALLCONV +spnego_gss_verify_mic( + OM_uint32 *minor_status, + const gss_ctx_id_t context_handle, + const gss_buffer_t msg_buffer, + const gss_buffer_t token_buffer, + gss_qop_t *qop_state) +{ + OM_uint32 ret; + ret = gss_verify_mic(minor_status, + context_handle, + msg_buffer, + token_buffer, + qop_state); + return (ret); +} + +OM_uint32 KRB5_CALLCONV +spnego_gss_inquire_sec_context_by_oid( + OM_uint32 *minor_status, + const gss_ctx_id_t context_handle, + const gss_OID desired_object, + gss_buffer_set_t *data_set) +{ + OM_uint32 ret; + ret = gss_inquire_sec_context_by_oid(minor_status, + context_handle, + desired_object, + data_set); + return (ret); +} + +OM_uint32 KRB5_CALLCONV +spnego_gss_inquire_cred_by_oid( + OM_uint32 *minor_status, + const gss_cred_id_t cred_handle, + const gss_OID desired_object, + gss_buffer_set_t *data_set) +{ + OM_uint32 ret; + spnego_gss_cred_id_t spcred = (spnego_gss_cred_id_t)cred_handle; + gss_cred_id_t mcred; + mcred = (spcred == NULL) ? GSS_C_NO_CREDENTIAL : spcred->mcred; + ret = gss_inquire_cred_by_oid(minor_status, + mcred, + desired_object, + data_set); + return (ret); +} + +OM_uint32 KRB5_CALLCONV +spnego_gss_set_cred_option( + OM_uint32 *minor_status, + gss_cred_id_t *cred_handle, + const gss_OID desired_object, + const gss_buffer_t value) +{ + OM_uint32 ret; + OM_uint32 tmp_minor_status; + spnego_gss_cred_id_t spcred = (spnego_gss_cred_id_t)*cred_handle; + gss_cred_id_t mcred; + + mcred = (spcred == NULL) ? GSS_C_NO_CREDENTIAL : spcred->mcred; + ret = gss_set_cred_option(minor_status, + &mcred, + desired_object, + value); + if (ret == GSS_S_COMPLETE && spcred == NULL) { + /* + * If the mechanism allocated a new credential handle, then + * we need to wrap it up in an SPNEGO credential handle. + */ + + spcred = malloc(sizeof(spnego_gss_cred_id_rec)); + if (spcred == NULL) { + gss_release_cred(&tmp_minor_status, &mcred); + *minor_status = ENOMEM; + return (GSS_S_FAILURE); + } + spcred->mcred = mcred; + spcred->neg_mechs = GSS_C_NULL_OID_SET; + *cred_handle = (gss_cred_id_t)spcred; + } + + return (ret); +} + +OM_uint32 KRB5_CALLCONV +spnego_gss_set_sec_context_option( + OM_uint32 *minor_status, + gss_ctx_id_t *context_handle, + const gss_OID desired_object, + const gss_buffer_t value) +{ + OM_uint32 ret; + ret = gss_set_sec_context_option(minor_status, + context_handle, + desired_object, + value); + return (ret); +} + +OM_uint32 KRB5_CALLCONV +spnego_gss_wrap_aead(OM_uint32 *minor_status, + gss_ctx_id_t context_handle, + int conf_req_flag, + gss_qop_t qop_req, + gss_buffer_t input_assoc_buffer, + gss_buffer_t input_payload_buffer, + int *conf_state, + gss_buffer_t output_message_buffer) +{ + OM_uint32 ret; + ret = gss_wrap_aead(minor_status, + context_handle, + conf_req_flag, + qop_req, + input_assoc_buffer, + input_payload_buffer, + conf_state, + output_message_buffer); + + return (ret); +} + +OM_uint32 KRB5_CALLCONV +spnego_gss_unwrap_aead(OM_uint32 *minor_status, + gss_ctx_id_t context_handle, + gss_buffer_t input_message_buffer, + gss_buffer_t input_assoc_buffer, + gss_buffer_t output_payload_buffer, + int *conf_state, + gss_qop_t *qop_state) +{ + OM_uint32 ret; + ret = gss_unwrap_aead(minor_status, + context_handle, + input_message_buffer, + input_assoc_buffer, + output_payload_buffer, + conf_state, + qop_state); + return (ret); +} + +OM_uint32 KRB5_CALLCONV +spnego_gss_wrap_iov(OM_uint32 *minor_status, + gss_ctx_id_t context_handle, + int conf_req_flag, + gss_qop_t qop_req, + int *conf_state, + gss_iov_buffer_desc *iov, + int iov_count) +{ + OM_uint32 ret; + ret = gss_wrap_iov(minor_status, + context_handle, + conf_req_flag, + qop_req, + conf_state, + iov, + iov_count); + return (ret); +} + +OM_uint32 KRB5_CALLCONV +spnego_gss_unwrap_iov(OM_uint32 *minor_status, + gss_ctx_id_t context_handle, + int *conf_state, + gss_qop_t *qop_state, + gss_iov_buffer_desc *iov, + int iov_count) +{ + OM_uint32 ret; + ret = gss_unwrap_iov(minor_status, + context_handle, + conf_state, + qop_state, + iov, + iov_count); + return (ret); +} + +OM_uint32 KRB5_CALLCONV +spnego_gss_wrap_iov_length(OM_uint32 *minor_status, + gss_ctx_id_t context_handle, + int conf_req_flag, + gss_qop_t qop_req, + int *conf_state, + gss_iov_buffer_desc *iov, + int iov_count) +{ + OM_uint32 ret; + ret = gss_wrap_iov_length(minor_status, + context_handle, + conf_req_flag, + qop_req, + conf_state, + iov, + iov_count); + return (ret); +} + + +OM_uint32 KRB5_CALLCONV +spnego_gss_complete_auth_token( + OM_uint32 *minor_status, + const gss_ctx_id_t context_handle, + gss_buffer_t input_message_buffer) +{ + OM_uint32 ret; + ret = gss_complete_auth_token(minor_status, + context_handle, + input_message_buffer); + return (ret); +} + +OM_uint32 KRB5_CALLCONV +spnego_gss_acquire_cred_impersonate_name(OM_uint32 *minor_status, + const gss_cred_id_t impersonator_cred_handle, + const gss_name_t desired_name, + OM_uint32 time_req, + gss_OID_set desired_mechs, + gss_cred_usage_t cred_usage, + gss_cred_id_t *output_cred_handle, + gss_OID_set *actual_mechs, + OM_uint32 *time_rec) +{ + OM_uint32 status; + gss_OID_set amechs = GSS_C_NULL_OID_SET; + spnego_gss_cred_id_t imp_spcred = NULL, out_spcred = NULL; + gss_cred_id_t imp_mcred, out_mcred; + + dsyslog("Entering spnego_gss_acquire_cred_impersonate_name\n"); + + if (actual_mechs) + *actual_mechs = NULL; + + if (time_rec) + *time_rec = 0; + + imp_spcred = (spnego_gss_cred_id_t)impersonator_cred_handle; + imp_mcred = imp_spcred ? imp_spcred->mcred : GSS_C_NO_CREDENTIAL; + if (desired_mechs == GSS_C_NO_OID_SET) { + status = gss_inquire_cred(minor_status, imp_mcred, NULL, NULL, + NULL, &amechs); + if (status != GSS_S_COMPLETE) + return status; + + desired_mechs = amechs; + } + + status = gss_acquire_cred_impersonate_name(minor_status, imp_mcred, + desired_name, time_req, + desired_mechs, cred_usage, + &out_mcred, actual_mechs, + time_rec); + + if (amechs != GSS_C_NULL_OID_SET) + (void) gss_release_oid_set(minor_status, &amechs); + + out_spcred = malloc(sizeof(spnego_gss_cred_id_rec)); + if (out_spcred == NULL) { + gss_release_cred(minor_status, &out_mcred); + *minor_status = ENOMEM; + return (GSS_S_FAILURE); + } + out_spcred->mcred = out_mcred; + out_spcred->neg_mechs = GSS_C_NULL_OID_SET; + *output_cred_handle = (gss_cred_id_t)out_spcred; + + dsyslog("Leaving spnego_gss_acquire_cred_impersonate_name\n"); + return (status); +} + +OM_uint32 KRB5_CALLCONV +spnego_gss_acquire_cred_with_password(OM_uint32 *minor_status, + const gss_name_t desired_name, + const gss_buffer_t password, + OM_uint32 time_req, + const gss_OID_set desired_mechs, + gss_cred_usage_t cred_usage, + gss_cred_id_t *output_cred_handle, + gss_OID_set *actual_mechs, + OM_uint32 *time_rec) +{ + OM_uint32 status, tmpmin; + gss_OID_set amechs = GSS_C_NULL_OID_SET; + gss_cred_id_t mcred = NULL; + spnego_gss_cred_id_t spcred = NULL; + + dsyslog("Entering spnego_gss_acquire_cred_with_password\n"); + + if (actual_mechs) + *actual_mechs = NULL; + + if (time_rec) + *time_rec = 0; + + status = get_available_mechs(minor_status, desired_name, + cred_usage, GSS_C_NO_CRED_STORE, + NULL, &amechs); + if (status != GSS_S_COMPLETE) + goto cleanup; + + status = gss_acquire_cred_with_password(minor_status, desired_name, + password, time_req, amechs, + cred_usage, &mcred, + actual_mechs, time_rec); + if (status != GSS_S_COMPLETE) + goto cleanup; + + spcred = malloc(sizeof(spnego_gss_cred_id_rec)); + if (spcred == NULL) { + *minor_status = ENOMEM; + status = GSS_S_FAILURE; + goto cleanup; + } + spcred->neg_mechs = GSS_C_NULL_OID_SET; + spcred->mcred = mcred; + mcred = GSS_C_NO_CREDENTIAL; + *output_cred_handle = (gss_cred_id_t)spcred; + +cleanup: + + (void) gss_release_oid_set(&tmpmin, &amechs); + (void) gss_release_cred(&tmpmin, &mcred); + + dsyslog("Leaving spnego_gss_acquire_cred_with_password\n"); + return (status); +} + +OM_uint32 KRB5_CALLCONV +spnego_gss_display_name_ext(OM_uint32 *minor_status, + gss_name_t name, + gss_OID display_as_name_type, + gss_buffer_t display_name) +{ + OM_uint32 ret; + ret = gss_display_name_ext(minor_status, + name, + display_as_name_type, + display_name); + return (ret); +} + + +OM_uint32 KRB5_CALLCONV +spnego_gss_inquire_name(OM_uint32 *minor_status, + gss_name_t name, + int *name_is_MN, + gss_OID *MN_mech, + gss_buffer_set_t *attrs) +{ + OM_uint32 ret; + ret = gss_inquire_name(minor_status, + name, + name_is_MN, + MN_mech, + attrs); + return (ret); +} + +OM_uint32 KRB5_CALLCONV +spnego_gss_get_name_attribute(OM_uint32 *minor_status, + gss_name_t name, + gss_buffer_t attr, + int *authenticated, + int *complete, + gss_buffer_t value, + gss_buffer_t display_value, + int *more) +{ + OM_uint32 ret; + ret = gss_get_name_attribute(minor_status, + name, + attr, + authenticated, + complete, + value, + display_value, + more); + return (ret); +} + +OM_uint32 KRB5_CALLCONV +spnego_gss_set_name_attribute(OM_uint32 *minor_status, + gss_name_t name, + int complete, + gss_buffer_t attr, + gss_buffer_t value) +{ + OM_uint32 ret; + ret = gss_set_name_attribute(minor_status, + name, + complete, + attr, + value); + return (ret); +} + +OM_uint32 KRB5_CALLCONV +spnego_gss_delete_name_attribute(OM_uint32 *minor_status, + gss_name_t name, + gss_buffer_t attr) +{ + OM_uint32 ret; + ret = gss_delete_name_attribute(minor_status, + name, + attr); + return (ret); +} + +OM_uint32 KRB5_CALLCONV +spnego_gss_export_name_composite(OM_uint32 *minor_status, + gss_name_t name, + gss_buffer_t exp_composite_name) +{ + OM_uint32 ret; + ret = gss_export_name_composite(minor_status, + name, + exp_composite_name); + return (ret); +} + +OM_uint32 KRB5_CALLCONV +spnego_gss_map_name_to_any(OM_uint32 *minor_status, + gss_name_t name, + int authenticated, + gss_buffer_t type_id, + gss_any_t *output) +{ + OM_uint32 ret; + ret = gss_map_name_to_any(minor_status, + name, + authenticated, + type_id, + output); + return (ret); +} + +OM_uint32 KRB5_CALLCONV +spnego_gss_release_any_name_mapping(OM_uint32 *minor_status, + gss_name_t name, + gss_buffer_t type_id, + gss_any_t *input) +{ + OM_uint32 ret; + ret = gss_release_any_name_mapping(minor_status, + name, + type_id, + input); + return (ret); +} + +OM_uint32 KRB5_CALLCONV +spnego_gss_pseudo_random(OM_uint32 *minor_status, + gss_ctx_id_t context, + int prf_key, + const gss_buffer_t prf_in, + ssize_t desired_output_len, + gss_buffer_t prf_out) +{ + OM_uint32 ret; + ret = gss_pseudo_random(minor_status, + context, + prf_key, + prf_in, + desired_output_len, + prf_out); + return (ret); +} + +OM_uint32 KRB5_CALLCONV +spnego_gss_set_neg_mechs(OM_uint32 *minor_status, + gss_cred_id_t cred_handle, + const gss_OID_set mech_list) +{ + OM_uint32 ret; + spnego_gss_cred_id_t spcred = (spnego_gss_cred_id_t)cred_handle; + + /* Store mech_list in spcred for use in negotiation logic. */ + gss_release_oid_set(minor_status, &spcred->neg_mechs); + ret = generic_gss_copy_oid_set(minor_status, mech_list, + &spcred->neg_mechs); + return (ret); +} + +#define SPNEGO_SASL_NAME "SPNEGO" +#define SPNEGO_SASL_NAME_LEN (sizeof(SPNEGO_SASL_NAME) - 1) + +OM_uint32 KRB5_CALLCONV +spnego_gss_inquire_mech_for_saslname(OM_uint32 *minor_status, + const gss_buffer_t sasl_mech_name, + gss_OID *mech_type) +{ + if (sasl_mech_name->length == SPNEGO_SASL_NAME_LEN && + memcmp(sasl_mech_name->value, SPNEGO_SASL_NAME, + SPNEGO_SASL_NAME_LEN) == 0) { + if (mech_type != NULL) + *mech_type = (gss_OID)gss_mech_spnego; + return (GSS_S_COMPLETE); + } + + return (GSS_S_BAD_MECH); +} + +OM_uint32 KRB5_CALLCONV +spnego_gss_inquire_saslname_for_mech(OM_uint32 *minor_status, + const gss_OID desired_mech, + gss_buffer_t sasl_mech_name, + gss_buffer_t mech_name, + gss_buffer_t mech_description) +{ + *minor_status = 0; + + if (!g_OID_equal(desired_mech, gss_mech_spnego)) + return (GSS_S_BAD_MECH); + + if (!g_make_string_buffer(SPNEGO_SASL_NAME, sasl_mech_name) || + !g_make_string_buffer("spnego", mech_name) || + !g_make_string_buffer("Simple and Protected GSS-API " + "Negotiation Mechanism", mech_description)) + goto fail; + + return (GSS_S_COMPLETE); + +fail: + *minor_status = ENOMEM; + return (GSS_S_FAILURE); +} + +OM_uint32 KRB5_CALLCONV +spnego_gss_inquire_attrs_for_mech(OM_uint32 *minor_status, + gss_const_OID mech, + gss_OID_set *mech_attrs, + gss_OID_set *known_mech_attrs) +{ + OM_uint32 major, tmpMinor; + + /* known_mech_attrs is handled by mechglue */ + *minor_status = 0; + + if (mech_attrs == NULL) + return (GSS_S_COMPLETE); + + major = gss_create_empty_oid_set(minor_status, mech_attrs); + if (GSS_ERROR(major)) + goto cleanup; + +#define MA_SUPPORTED(ma) do { \ + major = gss_add_oid_set_member(minor_status, \ + (gss_OID)ma, mech_attrs); \ + if (GSS_ERROR(major)) \ + goto cleanup; \ + } while (0) + + MA_SUPPORTED(GSS_C_MA_MECH_NEGO); + MA_SUPPORTED(GSS_C_MA_ITOK_FRAMED); + +cleanup: + if (GSS_ERROR(major)) + gss_release_oid_set(&tmpMinor, mech_attrs); + + return (major); +} + +OM_uint32 KRB5_CALLCONV +spnego_gss_export_cred(OM_uint32 *minor_status, + gss_cred_id_t cred_handle, + gss_buffer_t token) +{ + spnego_gss_cred_id_t spcred = (spnego_gss_cred_id_t)cred_handle; + + return (gss_export_cred(minor_status, spcred->mcred, token)); +} + +OM_uint32 KRB5_CALLCONV +spnego_gss_import_cred(OM_uint32 *minor_status, + gss_buffer_t token, + gss_cred_id_t *cred_handle) +{ + OM_uint32 ret; + spnego_gss_cred_id_t spcred; + gss_cred_id_t mcred; + + ret = gss_import_cred(minor_status, token, &mcred); + if (GSS_ERROR(ret)) + return (ret); + spcred = malloc(sizeof(*spcred)); + if (spcred == NULL) { + gss_release_cred(minor_status, &mcred); + *minor_status = ENOMEM; + return (GSS_S_FAILURE); + } + spcred->mcred = mcred; + spcred->neg_mechs = GSS_C_NULL_OID_SET; + *cred_handle = (gss_cred_id_t)spcred; + return (ret); +} + +OM_uint32 KRB5_CALLCONV +spnego_gss_get_mic_iov(OM_uint32 *minor_status, gss_ctx_id_t context_handle, + gss_qop_t qop_req, gss_iov_buffer_desc *iov, + int iov_count) +{ + return gss_get_mic_iov(minor_status, context_handle, qop_req, iov, + iov_count); +} + +OM_uint32 KRB5_CALLCONV +spnego_gss_verify_mic_iov(OM_uint32 *minor_status, gss_ctx_id_t context_handle, + gss_qop_t *qop_state, gss_iov_buffer_desc *iov, + int iov_count) +{ + return gss_verify_mic_iov(minor_status, context_handle, qop_state, iov, + iov_count); +} + +OM_uint32 KRB5_CALLCONV +spnego_gss_get_mic_iov_length(OM_uint32 *minor_status, + gss_ctx_id_t context_handle, gss_qop_t qop_req, + gss_iov_buffer_desc *iov, int iov_count) +{ + return gss_get_mic_iov_length(minor_status, context_handle, qop_req, iov, + iov_count); +} + +/* + * We will release everything but the ctx_handle so that it + * can be passed back to init/accept context. This routine should + * not be called until after the ctx_handle memory is assigned to + * the supplied context handle from init/accept context. + */ +static void +release_spnego_ctx(spnego_gss_ctx_id_t *ctx) +{ + spnego_gss_ctx_id_t context; + OM_uint32 minor_stat; + context = *ctx; + + if (context != NULL) { + (void) gss_release_buffer(&minor_stat, + &context->DER_mechTypes); + + (void) gss_release_oid_set(&minor_stat, &context->mech_set); + + (void) gss_release_name(&minor_stat, &context->internal_name); + + if (context->optionStr != NULL) { + free(context->optionStr); + context->optionStr = NULL; + } + free(context); + *ctx = NULL; + } +} + +/* + * Can't use gss_indicate_mechs by itself to get available mechs for + * SPNEGO because it will also return the SPNEGO mech and we do not + * want to consider SPNEGO as an available security mech for + * negotiation. For this reason, get_available_mechs will return + * all available mechs except SPNEGO. + * + * If a ptr to a creds list is given, this function will attempt + * to acquire creds for the creds given and trim the list of + * returned mechanisms to only those for which creds are valid. + * + */ +static OM_uint32 +get_available_mechs(OM_uint32 *minor_status, + gss_name_t name, gss_cred_usage_t usage, + gss_const_key_value_set_t cred_store, + gss_cred_id_t *creds, gss_OID_set *rmechs) +{ + unsigned int i; + int found = 0; + OM_uint32 major_status = GSS_S_COMPLETE, tmpmin; + gss_OID_set mechs, goodmechs; + + major_status = gss_indicate_mechs(minor_status, &mechs); + + if (major_status != GSS_S_COMPLETE) { + return (major_status); + } + + major_status = gss_create_empty_oid_set(minor_status, rmechs); + + if (major_status != GSS_S_COMPLETE) { + (void) gss_release_oid_set(minor_status, &mechs); + return (major_status); + } + + for (i = 0; i < mechs->count && major_status == GSS_S_COMPLETE; i++) { + if ((mechs->elements[i].length + != spnego_mechanism.mech_type.length) || + memcmp(mechs->elements[i].elements, + spnego_mechanism.mech_type.elements, + spnego_mechanism.mech_type.length)) { + + major_status = gss_add_oid_set_member(minor_status, + &mechs->elements[i], + rmechs); + if (major_status == GSS_S_COMPLETE) + found++; + } + } + + /* + * If the caller wanted a list of creds returned, + * trim the list of mechanisms down to only those + * for which the creds are valid. + */ + if (found > 0 && major_status == GSS_S_COMPLETE && creds != NULL) { + major_status = gss_acquire_cred_from(minor_status, name, + GSS_C_INDEFINITE, + *rmechs, usage, + cred_store, creds, + &goodmechs, NULL); + + /* + * Drop the old list in favor of the new + * "trimmed" list. + */ + (void) gss_release_oid_set(&tmpmin, rmechs); + if (major_status == GSS_S_COMPLETE) { + (void) gssint_copy_oid_set(&tmpmin, + goodmechs, rmechs); + (void) gss_release_oid_set(&tmpmin, &goodmechs); + } + } + + (void) gss_release_oid_set(&tmpmin, &mechs); + if (found == 0 || major_status != GSS_S_COMPLETE) { + *minor_status = ERR_SPNEGO_NO_MECHS_AVAILABLE; + map_errcode(minor_status); + if (major_status == GSS_S_COMPLETE) + major_status = GSS_S_FAILURE; + } + + return (major_status); +} + +/* + * Return a list of mechanisms we are willing to negotiate for a credential, + * taking into account the mech set provided with gss_set_neg_mechs if it + * exists. + */ +static OM_uint32 +get_negotiable_mechs(OM_uint32 *minor_status, spnego_gss_cred_id_t spcred, + gss_cred_usage_t usage, gss_OID_set *rmechs) +{ + OM_uint32 ret, tmpmin; + gss_cred_id_t creds = GSS_C_NO_CREDENTIAL, *credptr; + gss_OID_set cred_mechs = GSS_C_NULL_OID_SET; + gss_OID_set intersect_mechs = GSS_C_NULL_OID_SET; + unsigned int i; + int present; + + if (spcred == NULL) { + /* + * The default credentials were supplied. Return a list of all + * available mechs except SPNEGO. When initiating, trim this + * list to mechs we can acquire credentials for. + */ + credptr = (usage == GSS_C_INITIATE) ? &creds : NULL; + ret = get_available_mechs(minor_status, GSS_C_NO_NAME, usage, + GSS_C_NO_CRED_STORE, credptr, + rmechs); + gss_release_cred(&tmpmin, &creds); + return (ret); + } + + /* Get the list of mechs in the mechglue cred. */ + ret = gss_inquire_cred(minor_status, spcred->mcred, NULL, NULL, NULL, + &cred_mechs); + if (ret != GSS_S_COMPLETE) + return (ret); + + if (spcred->neg_mechs == GSS_C_NULL_OID_SET) { + /* gss_set_neg_mechs was never called; return cred_mechs. */ + *rmechs = cred_mechs; + *minor_status = 0; + return (GSS_S_COMPLETE); + } + + /* Compute the intersection of cred_mechs and spcred->neg_mechs, + * preserving the order in spcred->neg_mechs. */ + ret = gss_create_empty_oid_set(minor_status, &intersect_mechs); + if (ret != GSS_S_COMPLETE) { + gss_release_oid_set(&tmpmin, &cred_mechs); + return (ret); + } + + for (i = 0; i < spcred->neg_mechs->count; i++) { + gss_test_oid_set_member(&tmpmin, + &spcred->neg_mechs->elements[i], + cred_mechs, &present); + if (!present) + continue; + ret = gss_add_oid_set_member(minor_status, + &spcred->neg_mechs->elements[i], + &intersect_mechs); + if (ret != GSS_S_COMPLETE) + break; + } + + gss_release_oid_set(&tmpmin, &cred_mechs); + if (intersect_mechs->count == 0 || ret != GSS_S_COMPLETE) { + gss_release_oid_set(&tmpmin, &intersect_mechs); + *minor_status = ERR_SPNEGO_NO_MECHS_AVAILABLE; + map_errcode(minor_status); + return (GSS_S_FAILURE); + } + + *rmechs = intersect_mechs; + *minor_status = 0; + return (GSS_S_COMPLETE); +} + +/* following are token creation and reading routines */ + +/* + * If buff_in is not pointing to a MECH_OID, then return NULL and do not + * advance the buffer, otherwise, decode the mech_oid from the buffer and + * place in gss_OID. + */ +static gss_OID +get_mech_oid(OM_uint32 *minor_status, unsigned char **buff_in, size_t length) +{ + OM_uint32 status; + gss_OID_desc toid; + gss_OID mech_out = NULL; + unsigned char *start, *end; + + if (length < 1 || **buff_in != MECH_OID) + return (NULL); + + start = *buff_in; + end = start + length; + + (*buff_in)++; + toid.length = *(*buff_in)++; + + if ((*buff_in + toid.length) > end) + return (NULL); + + toid.elements = *buff_in; + *buff_in += toid.length; + + status = generic_gss_copy_oid(minor_status, &toid, &mech_out); + + if (status != GSS_S_COMPLETE) { + map_errcode(minor_status); + mech_out = NULL; + } + + return (mech_out); +} + +/* + * der encode the given mechanism oid into buf_out, advancing the + * buffer pointer. + */ + +static int +put_mech_oid(unsigned char **buf_out, gss_OID_const mech, unsigned int buflen) +{ + if (buflen < mech->length + 2) + return (-1); + *(*buf_out)++ = MECH_OID; + *(*buf_out)++ = (unsigned char) mech->length; + memcpy(*buf_out, mech->elements, mech->length); + *buf_out += mech->length; + return (0); +} + +/* + * verify that buff_in points to an octet string, if it does not, + * return NULL and don't advance the pointer. If it is an octet string + * decode buff_in into a gss_buffer_t and return it, advancing the + * buffer pointer. + */ +static gss_buffer_t +get_input_token(unsigned char **buff_in, unsigned int buff_length) +{ + gss_buffer_t input_token; + unsigned int len; + + if (g_get_tag_and_length(buff_in, OCTET_STRING, buff_length, &len) < 0) + return (NULL); + + input_token = (gss_buffer_t)malloc(sizeof (gss_buffer_desc)); + if (input_token == NULL) + return (NULL); + + input_token->length = len; + input_token->value = gssalloc_malloc(input_token->length); + + if (input_token->value == NULL) { + free(input_token); + return (NULL); + } + + (void) memcpy(input_token->value, *buff_in, input_token->length); + *buff_in += input_token->length; + return (input_token); +} + +/* + * verify that the input token length is not 0. If it is, just return. + * If the token length is greater than 0, der encode as an octet string + * and place in buf_out, advancing buf_out. + */ + +static int +put_input_token(unsigned char **buf_out, gss_buffer_t input_token, + unsigned int buflen) +{ + int ret; + + /* if token length is 0, we do not want to send */ + if (input_token->length == 0) + return (0); + + if (input_token->length > buflen) + return (-1); + + *(*buf_out)++ = OCTET_STRING; + if ((ret = gssint_put_der_length(input_token->length, buf_out, + input_token->length))) + return (ret); + TWRITE_STR(*buf_out, input_token->value, input_token->length); + return (0); +} + +/* + * verify that buff_in points to a sequence of der encoding. The mech + * set is the only sequence of encoded object in the token, so if it is + * a sequence of encoding, decode the mechset into a gss_OID_set and + * return it, advancing the buffer pointer. + */ +static gss_OID_set +get_mech_set(OM_uint32 *minor_status, unsigned char **buff_in, + unsigned int buff_length) +{ + gss_OID_set returned_mechSet; + OM_uint32 major_status; + int length; + unsigned int bytes; + OM_uint32 set_length; + unsigned char *start; + int i; + + if (**buff_in != SEQUENCE_OF) + return (NULL); + + start = *buff_in; + (*buff_in)++; + + length = gssint_get_der_length(buff_in, buff_length, &bytes); + if (length < 0 || buff_length - bytes < (unsigned int)length) + return NULL; + + major_status = gss_create_empty_oid_set(minor_status, + &returned_mechSet); + if (major_status != GSS_S_COMPLETE) + return (NULL); + + for (set_length = 0, i = 0; set_length < (unsigned int)length; i++) { + gss_OID_desc *temp = get_mech_oid(minor_status, buff_in, + buff_length - (*buff_in - start)); + if (temp == NULL) + break; + + major_status = gss_add_oid_set_member(minor_status, + temp, &returned_mechSet); + if (major_status == GSS_S_COMPLETE) { + set_length += returned_mechSet->elements[i].length +2; + if (generic_gss_release_oid(minor_status, &temp)) + map_errcode(minor_status); + } + } + + return (returned_mechSet); +} + +/* + * Encode mechSet into buf. + */ +static int +put_mech_set(gss_OID_set mechSet, gss_buffer_t buf) +{ + unsigned char *ptr; + unsigned int i; + unsigned int tlen, ilen; + + tlen = ilen = 0; + for (i = 0; i < mechSet->count; i++) { + /* + * 0x06 [DER LEN] [OID] + */ + ilen += 1 + + gssint_der_length_size(mechSet->elements[i].length) + + mechSet->elements[i].length; + } + /* + * 0x30 [DER LEN] + */ + tlen = 1 + gssint_der_length_size(ilen) + ilen; + ptr = gssalloc_malloc(tlen); + if (ptr == NULL) + return -1; + + buf->value = ptr; + buf->length = tlen; +#define REMAIN (buf->length - ((unsigned char *)buf->value - ptr)) + + *ptr++ = SEQUENCE_OF; + if (gssint_put_der_length(ilen, &ptr, REMAIN) < 0) + return -1; + for (i = 0; i < mechSet->count; i++) { + if (put_mech_oid(&ptr, &mechSet->elements[i], REMAIN) < 0) { + return -1; + } + } + return 0; +#undef REMAIN +} + +/* + * Verify that buff_in is pointing to a BIT_STRING with the correct + * length and padding for the req_flags. If it is, decode req_flags + * and return them, otherwise, return NULL. + */ +static OM_uint32 +get_req_flags(unsigned char **buff_in, OM_uint32 bodysize, + OM_uint32 *req_flags) +{ + unsigned int len; + + if (**buff_in != (CONTEXT | 0x01)) + return (0); + + if (g_get_tag_and_length(buff_in, (CONTEXT | 0x01), + bodysize, &len) < 0) + return GSS_S_DEFECTIVE_TOKEN; + + if (*(*buff_in)++ != BIT_STRING) + return GSS_S_DEFECTIVE_TOKEN; + + if (*(*buff_in)++ != BIT_STRING_LENGTH) + return GSS_S_DEFECTIVE_TOKEN; + + if (*(*buff_in)++ != BIT_STRING_PADDING) + return GSS_S_DEFECTIVE_TOKEN; + + *req_flags = (OM_uint32) (*(*buff_in)++ >> 1); + return (0); +} + +static OM_uint32 +get_negTokenInit(OM_uint32 *minor_status, + gss_buffer_t buf, + gss_buffer_t der_mechSet, + gss_OID_set *mechSet, + OM_uint32 *req_flags, + gss_buffer_t *mechtok, + gss_buffer_t *mechListMIC) +{ + OM_uint32 err; + unsigned char *ptr, *bufstart; + unsigned int len; + gss_buffer_desc tmpbuf; + + *minor_status = 0; + der_mechSet->length = 0; + der_mechSet->value = NULL; + *mechSet = GSS_C_NO_OID_SET; + *req_flags = 0; + *mechtok = *mechListMIC = GSS_C_NO_BUFFER; + + ptr = bufstart = buf->value; + if ((buf->length - (ptr - bufstart)) > INT_MAX) + return GSS_S_FAILURE; +#define REMAIN (buf->length - (ptr - bufstart)) + + err = g_verify_token_header(gss_mech_spnego, + &len, &ptr, 0, REMAIN); + if (err) { + *minor_status = err; + map_errcode(minor_status); + return GSS_S_FAILURE; + } + *minor_status = g_verify_neg_token_init(&ptr, REMAIN); + if (*minor_status) { + map_errcode(minor_status); + return GSS_S_FAILURE; + } + + /* alias into input_token */ + tmpbuf.value = ptr; + tmpbuf.length = REMAIN; + *mechSet = get_mech_set(minor_status, &ptr, REMAIN); + if (*mechSet == NULL) + return GSS_S_FAILURE; + + tmpbuf.length = ptr - (unsigned char *)tmpbuf.value; + der_mechSet->value = gssalloc_malloc(tmpbuf.length); + if (der_mechSet->value == NULL) + return GSS_S_FAILURE; + memcpy(der_mechSet->value, tmpbuf.value, tmpbuf.length); + der_mechSet->length = tmpbuf.length; + + err = get_req_flags(&ptr, REMAIN, req_flags); + if (err != GSS_S_COMPLETE) { + return err; + } + if (g_get_tag_and_length(&ptr, (CONTEXT | 0x02), + REMAIN, &len) >= 0) { + *mechtok = get_input_token(&ptr, len); + if (*mechtok == GSS_C_NO_BUFFER) { + return GSS_S_FAILURE; + } + } + if (g_get_tag_and_length(&ptr, (CONTEXT | 0x03), + REMAIN, &len) >= 0) { + *mechListMIC = get_input_token(&ptr, len); + if (*mechListMIC == GSS_C_NO_BUFFER) { + return GSS_S_FAILURE; + } + } + return GSS_S_COMPLETE; +#undef REMAIN +} + +static OM_uint32 +get_negTokenResp(OM_uint32 *minor_status, + unsigned char *buf, unsigned int buflen, + OM_uint32 *negState, + gss_OID *supportedMech, + gss_buffer_t *responseToken, + gss_buffer_t *mechListMIC) +{ + unsigned char *ptr, *bufstart; + unsigned int len; + int tmplen; + unsigned int tag, bytes; + + *negState = ACCEPT_DEFECTIVE_TOKEN; + *supportedMech = GSS_C_NO_OID; + *responseToken = *mechListMIC = GSS_C_NO_BUFFER; + ptr = bufstart = buf; +#define REMAIN (buflen - (ptr - bufstart)) + + if (g_get_tag_and_length(&ptr, (CONTEXT | 0x01), REMAIN, &len) < 0) + return GSS_S_DEFECTIVE_TOKEN; + if (*ptr++ == SEQUENCE) { + tmplen = gssint_get_der_length(&ptr, REMAIN, &bytes); + if (tmplen < 0 || REMAIN < (unsigned int)tmplen) + return GSS_S_DEFECTIVE_TOKEN; + } + if (REMAIN < 1) + tag = 0; + else + tag = *ptr++; + + if (tag == CONTEXT) { + tmplen = gssint_get_der_length(&ptr, REMAIN, &bytes); + if (tmplen < 0 || REMAIN < (unsigned int)tmplen) + return GSS_S_DEFECTIVE_TOKEN; + + if (g_get_tag_and_length(&ptr, ENUMERATED, + REMAIN, &len) < 0) + return GSS_S_DEFECTIVE_TOKEN; + + if (len != ENUMERATION_LENGTH) + return GSS_S_DEFECTIVE_TOKEN; + + if (REMAIN < 1) + return GSS_S_DEFECTIVE_TOKEN; + *negState = *ptr++; + + if (REMAIN < 1) + tag = 0; + else + tag = *ptr++; + } + if (tag == (CONTEXT | 0x01)) { + tmplen = gssint_get_der_length(&ptr, REMAIN, &bytes); + if (tmplen < 0 || REMAIN < (unsigned int)tmplen) + return GSS_S_DEFECTIVE_TOKEN; + + *supportedMech = get_mech_oid(minor_status, &ptr, REMAIN); + if (*supportedMech == GSS_C_NO_OID) + return GSS_S_DEFECTIVE_TOKEN; + + if (REMAIN < 1) + tag = 0; + else + tag = *ptr++; + } + if (tag == (CONTEXT | 0x02)) { + tmplen = gssint_get_der_length(&ptr, REMAIN, &bytes); + if (tmplen < 0 || REMAIN < (unsigned int)tmplen) + return GSS_S_DEFECTIVE_TOKEN; + + *responseToken = get_input_token(&ptr, REMAIN); + if (*responseToken == GSS_C_NO_BUFFER) + return GSS_S_DEFECTIVE_TOKEN; + + if (REMAIN < 1) + tag = 0; + else + tag = *ptr++; + } + if (tag == (CONTEXT | 0x03)) { + tmplen = gssint_get_der_length(&ptr, REMAIN, &bytes); + if (tmplen < 0 || REMAIN < (unsigned int)tmplen) + return GSS_S_DEFECTIVE_TOKEN; + + *mechListMIC = get_input_token(&ptr, REMAIN); + if (*mechListMIC == GSS_C_NO_BUFFER) + return GSS_S_DEFECTIVE_TOKEN; + + /* Handle Windows 2000 duplicate response token */ + if (*responseToken && + ((*responseToken)->length == (*mechListMIC)->length) && + !memcmp((*responseToken)->value, (*mechListMIC)->value, + (*responseToken)->length)) { + OM_uint32 tmpmin; + + gss_release_buffer(&tmpmin, *mechListMIC); + free(*mechListMIC); + *mechListMIC = NULL; + } + } + return GSS_S_COMPLETE; +#undef REMAIN +} + +/* + * der encode the passed negResults as an ENUMERATED type and + * place it in buf_out, advancing the buffer. + */ + +static int +put_negResult(unsigned char **buf_out, OM_uint32 negResult, + unsigned int buflen) +{ + if (buflen < 3) + return (-1); + *(*buf_out)++ = ENUMERATED; + *(*buf_out)++ = ENUMERATION_LENGTH; + *(*buf_out)++ = (unsigned char) negResult; + return (0); +} + +/* + * This routine compares the recieved mechset to the mechset that + * this server can support. It looks sequentially through the mechset + * and the first one that matches what the server can support is + * chosen as the negotiated mechanism. If one is found, negResult + * is set to ACCEPT_INCOMPLETE if it's the first mech, REQUEST_MIC if + * it's not the first mech, otherwise we return NULL and negResult + * is set to REJECT. The returned pointer is an alias into + * supported->elements and should not be freed. + * + * NOTE: There is currently no way to specify a preference order of + * mechanisms supported by the acceptor. + */ +static gss_OID +negotiate_mech(gss_OID_set supported, gss_OID_set received, + OM_uint32 *negResult) +{ + size_t i, j; + + for (i = 0; i < received->count; i++) { + gss_OID mech_oid = &received->elements[i]; + + /* Accept wrong mechanism OID from MS clients */ + if (g_OID_equal(mech_oid, &gss_mech_krb5_wrong_oid)) + mech_oid = (gss_OID)&gss_mech_krb5_oid; + + for (j = 0; j < supported->count; j++) { + if (g_OID_equal(mech_oid, &supported->elements[j])) { + *negResult = (i == 0) ? ACCEPT_INCOMPLETE : + REQUEST_MIC; + return &supported->elements[j]; + } + } + } + *negResult = REJECT; + return (NULL); +} + +/* + * the next two routines make a token buffer suitable for + * spnego_gss_display_status. These currently take the string + * in name and place it in the token. Eventually, if + * spnego_gss_display_status returns valid error messages, + * these routines will be changes to return the error string. + */ +static spnego_token_t +make_spnego_token(char *name) +{ + return (spnego_token_t)strdup(name); +} + +static gss_buffer_desc +make_err_msg(char *name) +{ + gss_buffer_desc buffer; + + if (name == NULL) { + buffer.length = 0; + buffer.value = NULL; + } else { + buffer.length = strlen(name)+1; + buffer.value = make_spnego_token(name); + } + + return (buffer); +} + +/* + * Create the client side spnego token passed back to gss_init_sec_context + * and eventually up to the application program and over to the server. + * + * Use DER rules, definite length method per RFC 2478 + */ +static int +make_spnego_tokenInit_msg(spnego_gss_ctx_id_t spnego_ctx, + int negHintsCompat, + gss_buffer_t mechListMIC, OM_uint32 req_flags, + gss_buffer_t data, send_token_flag sendtoken, + gss_buffer_t outbuf) +{ + int ret = 0; + unsigned int tlen, dataLen = 0; + unsigned int negTokenInitSize = 0; + unsigned int negTokenInitSeqSize = 0; + unsigned int negTokenInitContSize = 0; + unsigned int rspTokenSize = 0; + unsigned int mechListTokenSize = 0; + unsigned int micTokenSize = 0; + unsigned char *t; + unsigned char *ptr; + + if (outbuf == GSS_C_NO_BUFFER) + return (-1); + + outbuf->length = 0; + outbuf->value = NULL; + + /* calculate the data length */ + + /* + * 0xa0 [DER LEN] [mechTypes] + */ + mechListTokenSize = 1 + + gssint_der_length_size(spnego_ctx->DER_mechTypes.length) + + spnego_ctx->DER_mechTypes.length; + dataLen += mechListTokenSize; + + /* + * If a token from gss_init_sec_context exists, + * add the length of the token + the ASN.1 overhead + */ + if (data != NULL) { + /* + * Encoded in final output as: + * 0xa2 [DER LEN] 0x04 [DER LEN] [DATA] + * -----s--------|--------s2---------- + */ + rspTokenSize = 1 + + gssint_der_length_size(data->length) + + data->length; + dataLen += 1 + gssint_der_length_size(rspTokenSize) + + rspTokenSize; + } + + if (mechListMIC) { + /* + * Encoded in final output as: + * 0xa3 [DER LEN] 0x04 [DER LEN] [DATA] + * --s-- -----tlen------------ + */ + micTokenSize = 1 + + gssint_der_length_size(mechListMIC->length) + + mechListMIC->length; + dataLen += 1 + + gssint_der_length_size(micTokenSize) + + micTokenSize; + } + + /* + * Add size of DER encoding + * [ SEQUENCE { MechTypeList | ReqFLags | Token | mechListMIC } ] + * 0x30 [DER_LEN] [data] + * + */ + negTokenInitContSize = dataLen; + negTokenInitSeqSize = 1 + gssint_der_length_size(dataLen) + dataLen; + dataLen = negTokenInitSeqSize; + + /* + * negTokenInitSize indicates the bytes needed to + * hold the ASN.1 encoding of the entire NegTokenInit + * SEQUENCE. + * 0xa0 [DER_LEN] + data + * + */ + negTokenInitSize = 1 + + gssint_der_length_size(negTokenInitSeqSize) + + negTokenInitSeqSize; + + tlen = g_token_size(gss_mech_spnego, negTokenInitSize); + + t = (unsigned char *) gssalloc_malloc(tlen); + + if (t == NULL) { + return (-1); + } + + ptr = t; + + /* create the message */ + if ((ret = g_make_token_header(gss_mech_spnego, negTokenInitSize, + &ptr, tlen))) + goto errout; + + *ptr++ = CONTEXT; /* NegotiationToken identifier */ + if ((ret = gssint_put_der_length(negTokenInitSeqSize, &ptr, tlen))) + goto errout; + + *ptr++ = SEQUENCE; + if ((ret = gssint_put_der_length(negTokenInitContSize, &ptr, + tlen - (int)(ptr-t)))) + goto errout; + + *ptr++ = CONTEXT | 0x00; /* MechTypeList identifier */ + if ((ret = gssint_put_der_length(spnego_ctx->DER_mechTypes.length, + &ptr, tlen - (int)(ptr-t)))) + goto errout; + + /* We already encoded the MechSetList */ + (void) memcpy(ptr, spnego_ctx->DER_mechTypes.value, + spnego_ctx->DER_mechTypes.length); + + ptr += spnego_ctx->DER_mechTypes.length; + + if (data != NULL) { + *ptr++ = CONTEXT | 0x02; + if ((ret = gssint_put_der_length(rspTokenSize, + &ptr, tlen - (int)(ptr - t)))) + goto errout; + + if ((ret = put_input_token(&ptr, data, + tlen - (int)(ptr - t)))) + goto errout; + } + + if (mechListMIC != GSS_C_NO_BUFFER) { + *ptr++ = CONTEXT | 0x03; + if ((ret = gssint_put_der_length(micTokenSize, + &ptr, tlen - (int)(ptr - t)))) + goto errout; + + if (negHintsCompat) { + ret = put_neg_hints(&ptr, mechListMIC, + tlen - (int)(ptr - t)); + if (ret) + goto errout; + } else if ((ret = put_input_token(&ptr, mechListMIC, + tlen - (int)(ptr - t)))) + goto errout; + } + +errout: + if (ret != 0) { + if (t) + free(t); + t = NULL; + tlen = 0; + } + outbuf->length = tlen; + outbuf->value = (void *) t; + + return (ret); +} + +/* + * create the server side spnego token passed back to + * gss_accept_sec_context and eventually up to the application program + * and over to the client. + */ +static int +make_spnego_tokenTarg_msg(OM_uint32 status, gss_OID mech_wanted, + gss_buffer_t data, gss_buffer_t mechListMIC, + send_token_flag sendtoken, + gss_buffer_t outbuf) +{ + unsigned int tlen = 0; + unsigned int ret = 0; + unsigned int NegTokenTargSize = 0; + unsigned int NegTokenSize = 0; + unsigned int rspTokenSize = 0; + unsigned int micTokenSize = 0; + unsigned int dataLen = 0; + unsigned char *t; + unsigned char *ptr; + + if (outbuf == GSS_C_NO_BUFFER) + return (GSS_S_DEFECTIVE_TOKEN); + if (sendtoken == INIT_TOKEN_SEND && mech_wanted == GSS_C_NO_OID) + return (GSS_S_DEFECTIVE_TOKEN); + + outbuf->length = 0; + outbuf->value = NULL; + + /* + * ASN.1 encoding of the negResult + * ENUMERATED type is 3 bytes + * ENUMERATED TAG, Length, Value, + * Plus 2 bytes for the CONTEXT id and length. + */ + dataLen = 5; + + /* + * calculate data length + * + * If this is the initial token, include length of + * mech_type and the negotiation result fields. + */ + if (sendtoken == INIT_TOKEN_SEND) { + int mechlistTokenSize; + /* + * 1 byte for the CONTEXT ID(0xa0), + * 1 byte for the OID ID(0x06) + * 1 byte for OID Length field + * Plus the rest... (OID Length, OID value) + */ + mechlistTokenSize = 3 + mech_wanted->length + + gssint_der_length_size(mech_wanted->length); + + dataLen += mechlistTokenSize; + } + if (data != NULL && data->length > 0) { + /* Length of the inner token */ + rspTokenSize = 1 + gssint_der_length_size(data->length) + + data->length; + + dataLen += rspTokenSize; + + /* Length of the outer token */ + dataLen += 1 + gssint_der_length_size(rspTokenSize); + } + if (mechListMIC != NULL) { + + /* Length of the inner token */ + micTokenSize = 1 + gssint_der_length_size(mechListMIC->length) + + mechListMIC->length; + + dataLen += micTokenSize; + + /* Length of the outer token */ + dataLen += 1 + gssint_der_length_size(micTokenSize); + } + /* + * Add size of DER encoded: + * NegTokenTarg [ SEQUENCE ] of + * NegResult[0] ENUMERATED { + * accept_completed(0), + * accept_incomplete(1), + * reject(2) } + * supportedMech [1] MechType OPTIONAL, + * responseToken [2] OCTET STRING OPTIONAL, + * mechListMIC [3] OCTET STRING OPTIONAL + * + * size = data->length + MechListMic + SupportedMech len + + * Result Length + ASN.1 overhead + */ + NegTokenTargSize = dataLen; + dataLen += 1 + gssint_der_length_size(NegTokenTargSize); + + /* + * NegotiationToken [ CHOICE ]{ + * negTokenInit [0] NegTokenInit, + * negTokenTarg [1] NegTokenTarg } + */ + NegTokenSize = dataLen; + dataLen += 1 + gssint_der_length_size(NegTokenSize); + + tlen = dataLen; + t = (unsigned char *) gssalloc_malloc(tlen); + + if (t == NULL) { + ret = GSS_S_DEFECTIVE_TOKEN; + goto errout; + } + + ptr = t; + + /* + * Indicate that we are sending CHOICE 1 + * (NegTokenTarg) + */ + *ptr++ = CONTEXT | 0x01; + if (gssint_put_der_length(NegTokenSize, &ptr, dataLen) < 0) { + ret = GSS_S_DEFECTIVE_TOKEN; + goto errout; + } + *ptr++ = SEQUENCE; + if (gssint_put_der_length(NegTokenTargSize, &ptr, + tlen - (int)(ptr-t)) < 0) { + ret = GSS_S_DEFECTIVE_TOKEN; + goto errout; + } + + /* + * First field of the NegTokenTarg SEQUENCE + * is the ENUMERATED NegResult. + */ + *ptr++ = CONTEXT; + if (gssint_put_der_length(3, &ptr, + tlen - (int)(ptr-t)) < 0) { + ret = GSS_S_DEFECTIVE_TOKEN; + goto errout; + } + if (put_negResult(&ptr, status, tlen - (int)(ptr - t)) < 0) { + ret = GSS_S_DEFECTIVE_TOKEN; + goto errout; + } + if (sendtoken == INIT_TOKEN_SEND) { + /* + * Next, is the Supported MechType + */ + *ptr++ = CONTEXT | 0x01; + if (gssint_put_der_length(mech_wanted->length + 2, + &ptr, + tlen - (int)(ptr - t)) < 0) { + ret = GSS_S_DEFECTIVE_TOKEN; + goto errout; + } + if (put_mech_oid(&ptr, mech_wanted, + tlen - (int)(ptr - t)) < 0) { + ret = GSS_S_DEFECTIVE_TOKEN; + goto errout; + } + } + if (data != NULL && data->length > 0) { + *ptr++ = CONTEXT | 0x02; + if (gssint_put_der_length(rspTokenSize, &ptr, + tlen - (int)(ptr - t)) < 0) { + ret = GSS_S_DEFECTIVE_TOKEN; + goto errout; + } + if (put_input_token(&ptr, data, + tlen - (int)(ptr - t)) < 0) { + ret = GSS_S_DEFECTIVE_TOKEN; + goto errout; + } + } + if (mechListMIC != NULL) { + *ptr++ = CONTEXT | 0x03; + if (gssint_put_der_length(micTokenSize, &ptr, + tlen - (int)(ptr - t)) < 0) { + ret = GSS_S_DEFECTIVE_TOKEN; + goto errout; + } + if (put_input_token(&ptr, mechListMIC, + tlen - (int)(ptr - t)) < 0) { + ret = GSS_S_DEFECTIVE_TOKEN; + goto errout; + } + } + ret = GSS_S_COMPLETE; +errout: + if (ret != GSS_S_COMPLETE) { + if (t) + free(t); + } else { + outbuf->length = ptr - t; + outbuf->value = (void *) t; + } + + return (ret); +} + +/* determine size of token */ +static int +g_token_size(gss_OID_const mech, unsigned int body_size) +{ + int hdrsize; + + /* + * Initialize the header size to the + * MECH_OID byte + the bytes needed to indicate the + * length of the OID + the OID itself. + * + * 0x06 [MECHLENFIELD] MECHDATA + */ + hdrsize = 1 + gssint_der_length_size(mech->length) + mech->length; + + /* + * Now add the bytes needed for the initial header + * token bytes: + * 0x60 + [DER_LEN] + HDRSIZE + */ + hdrsize += 1 + gssint_der_length_size(body_size + hdrsize); + + return (hdrsize + body_size); +} + +/* + * generate token header. + * + * Use DER Definite Length method per RFC2478 + * Use of indefinite length encoding will not be compatible + * with Microsoft or others that actually follow the spec. + */ +static int +g_make_token_header(gss_OID_const mech, + unsigned int body_size, + unsigned char **buf, + unsigned int totallen) +{ + int ret = 0; + unsigned int hdrsize; + unsigned char *p = *buf; + + hdrsize = 1 + gssint_der_length_size(mech->length) + mech->length; + + *(*buf)++ = HEADER_ID; + if ((ret = gssint_put_der_length(hdrsize + body_size, buf, totallen))) + return (ret); + + *(*buf)++ = MECH_OID; + if ((ret = gssint_put_der_length(mech->length, buf, + totallen - (int)(p - *buf)))) + return (ret); + TWRITE_STR(*buf, mech->elements, mech->length); + return (0); +} + +/* + * NOTE: This checks that the length returned by + * gssint_get_der_length() is not greater than the number of octets + * remaining, even though gssint_get_der_length() already checks, in + * theory. + */ +static int +g_get_tag_and_length(unsigned char **buf, int tag, + unsigned int buflen, unsigned int *outlen) +{ + unsigned char *ptr = *buf; + int ret = -1; /* pessimists, assume failure ! */ + unsigned int encoded_len; + int tmplen = 0; + + *outlen = 0; + if (buflen > 1 && *ptr == tag) { + ptr++; + tmplen = gssint_get_der_length(&ptr, buflen - 1, + &encoded_len); + if (tmplen < 0) { + ret = -1; + } else if ((unsigned int)tmplen > buflen - (ptr - *buf)) { + ret = -1; + } else + ret = 0; + } + *outlen = tmplen; + *buf = ptr; + return (ret); +} + +static int +g_verify_neg_token_init(unsigned char **buf_in, unsigned int cur_size) +{ + unsigned char *buf = *buf_in; + unsigned char *endptr = buf + cur_size; + int seqsize; + int ret = 0; + unsigned int bytes; + + /* + * Verify this is a NegotiationToken type token + * - check for a0(context specific identifier) + * - get length and verify that enoughd ata exists + */ + if (g_get_tag_and_length(&buf, CONTEXT, cur_size, &bytes) < 0) + return (G_BAD_TOK_HEADER); + + cur_size = bytes; /* should indicate bytes remaining */ + + /* + * Verify the next piece, it should identify this as + * a strucure of type NegTokenInit. + */ + if (*buf++ == SEQUENCE) { + if ((seqsize = gssint_get_der_length(&buf, cur_size, &bytes)) < 0) + return (G_BAD_TOK_HEADER); + /* + * Make sure we have the entire buffer as described + */ + if (seqsize > endptr - buf) + return (G_BAD_TOK_HEADER); + } else { + return (G_BAD_TOK_HEADER); + } + + cur_size = seqsize; /* should indicate bytes remaining */ + + /* + * Verify that the first blob is a sequence of mechTypes + */ + if (*buf++ == CONTEXT) { + if ((seqsize = gssint_get_der_length(&buf, cur_size, &bytes)) < 0) + return (G_BAD_TOK_HEADER); + /* + * Make sure we have the entire buffer as described + */ + if (seqsize > endptr - buf) + return (G_BAD_TOK_HEADER); + } else { + return (G_BAD_TOK_HEADER); + } + + /* + * At this point, *buf should be at the beginning of the + * DER encoded list of mech types that are to be negotiated. + */ + *buf_in = buf; + + return (ret); + +} + +/* verify token header. */ +static int +g_verify_token_header(gss_OID_const mech, + unsigned int *body_size, + unsigned char **buf_in, + int tok_type, + unsigned int toksize) +{ + unsigned char *buf = *buf_in; + int seqsize; + gss_OID_desc toid; + int ret = 0; + unsigned int bytes; + + if (toksize-- < 1) + return (G_BAD_TOK_HEADER); + + if (*buf++ != HEADER_ID) + return (G_BAD_TOK_HEADER); + + if ((seqsize = gssint_get_der_length(&buf, toksize, &bytes)) < 0) + return (G_BAD_TOK_HEADER); + + if ((seqsize + bytes) != toksize) + return (G_BAD_TOK_HEADER); + + if (toksize-- < 1) + return (G_BAD_TOK_HEADER); + + + if (*buf++ != MECH_OID) + return (G_BAD_TOK_HEADER); + + if (toksize-- < 1) + return (G_BAD_TOK_HEADER); + + toid.length = *buf++; + + if (toksize < toid.length) + return (G_BAD_TOK_HEADER); + else + toksize -= toid.length; + + toid.elements = buf; + buf += toid.length; + + if (!g_OID_equal(&toid, mech)) + ret = G_WRONG_MECH; + + /* + * G_WRONG_MECH is not returned immediately because it's more important + * to return G_BAD_TOK_HEADER if the token header is in fact bad + */ + if (toksize < 2) + return (G_BAD_TOK_HEADER); + else + toksize -= 2; + + if (!ret) { + *buf_in = buf; + *body_size = toksize; + } + + return (ret); +} + +/* + * Return non-zero if the oid is one of the kerberos mech oids, + * otherwise return zero. + * + * N.B. There are 3 oids that represent the kerberos mech: + * RFC-specified GSS_MECH_KRB5_OID, + * Old pre-RFC GSS_MECH_KRB5_OLD_OID, + * Incorrect MS GSS_MECH_KRB5_WRONG_OID + */ + +static int +is_kerb_mech(gss_OID oid) +{ + int answer = 0; + OM_uint32 minor; + extern const gss_OID_set_desc * const gss_mech_set_krb5_both; + + (void) gss_test_oid_set_member(&minor, + oid, (gss_OID_set)gss_mech_set_krb5_both, &answer); + + return (answer); +} === added directory '.pc/CVE-2014-4344.patch' === added directory '.pc/CVE-2014-4344.patch/src' === added directory '.pc/CVE-2014-4344.patch/src/lib' === added directory '.pc/CVE-2014-4344.patch/src/lib/gssapi' === added directory '.pc/CVE-2014-4344.patch/src/lib/gssapi/spnego' === added file '.pc/CVE-2014-4344.patch/src/lib/gssapi/spnego/spnego_mech.c' --- .pc/CVE-2014-4344.patch/src/lib/gssapi/spnego/spnego_mech.c 1970-01-01 00:00:00 +0000 +++ .pc/CVE-2014-4344.patch/src/lib/gssapi/spnego/spnego_mech.c 2014-08-12 11:29:31 +0000 @@ -0,0 +1,4175 @@ +/* + * Copyright (C) 2006,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 2004 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + * + * A module that implements the spnego security mechanism. + * It is used to negotiate the security mechanism between + * peers using the GSS-API. SPNEGO is specified in RFC 4178. + * + */ +/* + * 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. + */ +/* #pragma ident "@(#)spnego_mech.c 1.7 04/09/28 SMI" */ + +#include +#include +#include +#include +#include +#include +#include +#include "gssapiP_spnego.h" +#include + +#ifndef MAXHOSTNAMELEN +#define MAXHOSTNAMELEN 64 +#endif + +#undef g_token_size +#undef g_verify_token_header +#undef g_make_token_header + +#define HARD_ERROR(v) ((v) != GSS_S_COMPLETE && (v) != GSS_S_CONTINUE_NEEDED) +typedef const gss_OID_desc *gss_OID_const; + +/* der routines defined in libgss */ +extern unsigned int gssint_der_length_size(unsigned int); +extern int gssint_get_der_length(unsigned char **, unsigned int, + unsigned int*); +extern int gssint_put_der_length(unsigned int, unsigned char **, unsigned int); + + +/* private routines for spnego_mechanism */ +static spnego_token_t make_spnego_token(char *); +static gss_buffer_desc make_err_msg(char *); +static int g_token_size(gss_OID_const, unsigned int); +static int g_make_token_header(gss_OID_const, unsigned int, + unsigned char **, unsigned int); +static int g_verify_token_header(gss_OID_const, unsigned int *, + unsigned char **, + int, unsigned int); +static int g_verify_neg_token_init(unsigned char **, unsigned int); +static gss_OID get_mech_oid(OM_uint32 *, unsigned char **, size_t); +static gss_buffer_t get_input_token(unsigned char **, unsigned int); +static gss_OID_set get_mech_set(OM_uint32 *, unsigned char **, unsigned int); +static OM_uint32 get_req_flags(unsigned char **, OM_uint32, OM_uint32 *); +static OM_uint32 get_available_mechs(OM_uint32 *, gss_name_t, gss_cred_usage_t, + gss_const_key_value_set_t, + gss_cred_id_t *, gss_OID_set *); +static OM_uint32 get_negotiable_mechs(OM_uint32 *, spnego_gss_cred_id_t, + gss_cred_usage_t, gss_OID_set *); +static void release_spnego_ctx(spnego_gss_ctx_id_t *); +static void check_spnego_options(spnego_gss_ctx_id_t); +static spnego_gss_ctx_id_t create_spnego_ctx(void); +static int put_mech_set(gss_OID_set mechSet, gss_buffer_t buf); +static int put_input_token(unsigned char **, gss_buffer_t, unsigned int); +static int put_mech_oid(unsigned char **, gss_OID_const, unsigned int); +static int put_negResult(unsigned char **, OM_uint32, unsigned int); + +static OM_uint32 +process_mic(OM_uint32 *, gss_buffer_t, spnego_gss_ctx_id_t, + gss_buffer_t *, OM_uint32 *, send_token_flag *); +static OM_uint32 +handle_mic(OM_uint32 *, gss_buffer_t, int, spnego_gss_ctx_id_t, + gss_buffer_t *, OM_uint32 *, send_token_flag *); + +static OM_uint32 +init_ctx_new(OM_uint32 *, spnego_gss_cred_id_t, gss_ctx_id_t *, + send_token_flag *); +static OM_uint32 +init_ctx_nego(OM_uint32 *, spnego_gss_ctx_id_t, OM_uint32, gss_OID, + gss_buffer_t *, gss_buffer_t *, + OM_uint32 *, send_token_flag *); +static OM_uint32 +init_ctx_cont(OM_uint32 *, gss_ctx_id_t *, gss_buffer_t, + gss_buffer_t *, gss_buffer_t *, + OM_uint32 *, send_token_flag *); +static OM_uint32 +init_ctx_reselect(OM_uint32 *, spnego_gss_ctx_id_t, OM_uint32, + gss_OID, gss_buffer_t *, gss_buffer_t *, + OM_uint32 *, send_token_flag *); +static OM_uint32 +init_ctx_call_init(OM_uint32 *, spnego_gss_ctx_id_t, spnego_gss_cred_id_t, + gss_name_t, OM_uint32, OM_uint32, gss_buffer_t, + gss_OID *, gss_buffer_t, OM_uint32 *, OM_uint32 *, + OM_uint32 *, send_token_flag *); + +static OM_uint32 +acc_ctx_new(OM_uint32 *, gss_buffer_t, gss_ctx_id_t *, + spnego_gss_cred_id_t, gss_buffer_t *, + gss_buffer_t *, OM_uint32 *, send_token_flag *); +static OM_uint32 +acc_ctx_cont(OM_uint32 *, gss_buffer_t, gss_ctx_id_t *, + gss_buffer_t *, gss_buffer_t *, + OM_uint32 *, send_token_flag *); +static OM_uint32 +acc_ctx_vfy_oid(OM_uint32 *, spnego_gss_ctx_id_t, gss_OID, + OM_uint32 *, send_token_flag *); +static OM_uint32 +acc_ctx_call_acc(OM_uint32 *, spnego_gss_ctx_id_t, spnego_gss_cred_id_t, + gss_buffer_t, gss_OID *, gss_buffer_t, + OM_uint32 *, OM_uint32 *, gss_cred_id_t *, + OM_uint32 *, send_token_flag *); + +static gss_OID +negotiate_mech(gss_OID_set, gss_OID_set, OM_uint32 *); +static int +g_get_tag_and_length(unsigned char **, int, unsigned int, unsigned int *); + +static int +make_spnego_tokenInit_msg(spnego_gss_ctx_id_t, + int, + gss_buffer_t, + OM_uint32, gss_buffer_t, send_token_flag, + gss_buffer_t); +static int +make_spnego_tokenTarg_msg(OM_uint32, gss_OID, gss_buffer_t, + gss_buffer_t, send_token_flag, + gss_buffer_t); + +static OM_uint32 +get_negTokenInit(OM_uint32 *, gss_buffer_t, gss_buffer_t, + gss_OID_set *, OM_uint32 *, gss_buffer_t *, + gss_buffer_t *); +static OM_uint32 +get_negTokenResp(OM_uint32 *, unsigned char *, unsigned int, + OM_uint32 *, gss_OID *, gss_buffer_t *, gss_buffer_t *); + +static int +is_kerb_mech(gss_OID oid); + +/* SPNEGO oid structure */ +static const gss_OID_desc spnego_oids[] = { + {SPNEGO_OID_LENGTH, SPNEGO_OID}, +}; + +const gss_OID_desc * const gss_mech_spnego = spnego_oids+0; +static const gss_OID_set_desc spnego_oidsets[] = { + {1, (gss_OID) spnego_oids+0}, +}; +const gss_OID_set_desc * const gss_mech_set_spnego = spnego_oidsets+0; + +static int make_NegHints(OM_uint32 *, spnego_gss_cred_id_t, gss_buffer_t *); +static int put_neg_hints(unsigned char **, gss_buffer_t, unsigned int); +static OM_uint32 +acc_ctx_hints(OM_uint32 *, gss_ctx_id_t *, spnego_gss_cred_id_t, + gss_buffer_t *, OM_uint32 *, send_token_flag *); + +/* + * The Mech OID for SPNEGO: + * { iso(1) org(3) dod(6) internet(1) security(5) + * mechanism(5) spnego(2) } + */ +static struct gss_config spnego_mechanism = +{ + {SPNEGO_OID_LENGTH, SPNEGO_OID}, + NULL, + spnego_gss_acquire_cred, + spnego_gss_release_cred, + spnego_gss_init_sec_context, +#ifndef LEAN_CLIENT + spnego_gss_accept_sec_context, +#else + NULL, +#endif /* LEAN_CLIENT */ + NULL, /* gss_process_context_token */ + spnego_gss_delete_sec_context, /* gss_delete_sec_context */ + spnego_gss_context_time, /* gss_context_time */ + spnego_gss_get_mic, /* gss_get_mic */ + spnego_gss_verify_mic, /* gss_verify_mic */ + spnego_gss_wrap, /* gss_wrap */ + spnego_gss_unwrap, /* gss_unwrap */ + spnego_gss_display_status, + NULL, /* gss_indicate_mechs */ + spnego_gss_compare_name, + spnego_gss_display_name, + spnego_gss_import_name, + spnego_gss_release_name, + spnego_gss_inquire_cred, /* gss_inquire_cred */ + NULL, /* gss_add_cred */ +#ifndef LEAN_CLIENT + spnego_gss_export_sec_context, /* gss_export_sec_context */ + spnego_gss_import_sec_context, /* gss_import_sec_context */ +#else + NULL, /* gss_export_sec_context */ + NULL, /* gss_import_sec_context */ +#endif /* LEAN_CLIENT */ + NULL, /* gss_inquire_cred_by_mech */ + spnego_gss_inquire_names_for_mech, + spnego_gss_inquire_context, /* gss_inquire_context */ + NULL, /* gss_internal_release_oid */ + spnego_gss_wrap_size_limit, /* gss_wrap_size_limit */ + NULL, /* gssd_pname_to_uid */ + NULL, /* gss_userok */ + NULL, /* gss_export_name */ + spnego_gss_duplicate_name, /* gss_duplicate_name */ + NULL, /* gss_store_cred */ + spnego_gss_inquire_sec_context_by_oid, /* gss_inquire_sec_context_by_oid */ + spnego_gss_inquire_cred_by_oid, /* gss_inquire_cred_by_oid */ + spnego_gss_set_sec_context_option, /* gss_set_sec_context_option */ + spnego_gss_set_cred_option, /* gssspi_set_cred_option */ + NULL, /* gssspi_mech_invoke */ + spnego_gss_wrap_aead, + spnego_gss_unwrap_aead, + spnego_gss_wrap_iov, + spnego_gss_unwrap_iov, + spnego_gss_wrap_iov_length, + spnego_gss_complete_auth_token, + spnego_gss_acquire_cred_impersonate_name, + NULL, /* gss_add_cred_impersonate_name */ + spnego_gss_display_name_ext, + spnego_gss_inquire_name, + spnego_gss_get_name_attribute, + spnego_gss_set_name_attribute, + spnego_gss_delete_name_attribute, + spnego_gss_export_name_composite, + spnego_gss_map_name_to_any, + spnego_gss_release_any_name_mapping, + spnego_gss_pseudo_random, + spnego_gss_set_neg_mechs, + spnego_gss_inquire_saslname_for_mech, + spnego_gss_inquire_mech_for_saslname, + spnego_gss_inquire_attrs_for_mech, + spnego_gss_acquire_cred_from, + NULL, /* gss_store_cred_into */ + spnego_gss_acquire_cred_with_password, + spnego_gss_export_cred, + spnego_gss_import_cred, + NULL, /* gssspi_import_sec_context_by_mech */ + NULL, /* gssspi_import_name_by_mech */ + NULL, /* gssspi_import_cred_by_mech */ + spnego_gss_get_mic_iov, + spnego_gss_verify_mic_iov, + spnego_gss_get_mic_iov_length +}; + +#ifdef _GSS_STATIC_LINK +#include "mglueP.h" + +static int gss_spnegomechglue_init(void) +{ + struct gss_mech_config mech_spnego; + + memset(&mech_spnego, 0, sizeof(mech_spnego)); + mech_spnego.mech = &spnego_mechanism; + mech_spnego.mechNameStr = "spnego"; + mech_spnego.mech_type = GSS_C_NO_OID; + + return gssint_register_mechinfo(&mech_spnego); +} +#else +gss_mechanism KRB5_CALLCONV +gss_mech_initialize(void) +{ + return (&spnego_mechanism); +} + +MAKE_INIT_FUNCTION(gss_krb5int_lib_init); +MAKE_FINI_FUNCTION(gss_krb5int_lib_fini); +int gss_krb5int_lib_init(void); +#endif /* _GSS_STATIC_LINK */ + +int gss_spnegoint_lib_init(void) +{ +#ifdef _GSS_STATIC_LINK + return gss_spnegomechglue_init(); +#else + return 0; +#endif +} + +void gss_spnegoint_lib_fini(void) +{ +} + +/*ARGSUSED*/ +OM_uint32 KRB5_CALLCONV +spnego_gss_acquire_cred(OM_uint32 *minor_status, + gss_name_t desired_name, + OM_uint32 time_req, + gss_OID_set desired_mechs, + gss_cred_usage_t cred_usage, + gss_cred_id_t *output_cred_handle, + gss_OID_set *actual_mechs, + OM_uint32 *time_rec) +{ + return spnego_gss_acquire_cred_from(minor_status, desired_name, time_req, + desired_mechs, cred_usage, NULL, + output_cred_handle, actual_mechs, + time_rec); +} + +/*ARGSUSED*/ +OM_uint32 KRB5_CALLCONV +spnego_gss_acquire_cred_from(OM_uint32 *minor_status, + const gss_name_t desired_name, + OM_uint32 time_req, + const gss_OID_set desired_mechs, + gss_cred_usage_t cred_usage, + gss_const_key_value_set_t cred_store, + gss_cred_id_t *output_cred_handle, + gss_OID_set *actual_mechs, + OM_uint32 *time_rec) +{ + OM_uint32 status, tmpmin; + gss_OID_set amechs; + gss_cred_id_t mcred = NULL; + spnego_gss_cred_id_t spcred = NULL; + dsyslog("Entering spnego_gss_acquire_cred\n"); + + if (actual_mechs) + *actual_mechs = NULL; + + if (time_rec) + *time_rec = 0; + + /* We will obtain a mechglue credential and wrap it in a + * spnego_gss_cred_id_rec structure. Allocate the wrapper. */ + spcred = malloc(sizeof(spnego_gss_cred_id_rec)); + if (spcred == NULL) { + *minor_status = ENOMEM; + return (GSS_S_FAILURE); + } + spcred->neg_mechs = GSS_C_NULL_OID_SET; + + /* + * Always use get_available_mechs to collect a list of + * mechs for which creds are available. + */ + status = get_available_mechs(minor_status, desired_name, + cred_usage, cred_store, &mcred, + &amechs); + + if (actual_mechs && amechs != GSS_C_NULL_OID_SET) { + (void) gssint_copy_oid_set(&tmpmin, amechs, actual_mechs); + } + (void) gss_release_oid_set(&tmpmin, &amechs); + + if (status == GSS_S_COMPLETE) { + spcred->mcred = mcred; + *output_cred_handle = (gss_cred_id_t)spcred; + } else { + free(spcred); + *output_cred_handle = GSS_C_NO_CREDENTIAL; + } + + dsyslog("Leaving spnego_gss_acquire_cred\n"); + return (status); +} + +/*ARGSUSED*/ +OM_uint32 KRB5_CALLCONV +spnego_gss_release_cred(OM_uint32 *minor_status, + gss_cred_id_t *cred_handle) +{ + spnego_gss_cred_id_t spcred = NULL; + + dsyslog("Entering spnego_gss_release_cred\n"); + + if (minor_status == NULL || cred_handle == NULL) + return (GSS_S_CALL_INACCESSIBLE_WRITE); + + *minor_status = 0; + + if (*cred_handle == GSS_C_NO_CREDENTIAL) + return (GSS_S_COMPLETE); + + spcred = (spnego_gss_cred_id_t)*cred_handle; + *cred_handle = GSS_C_NO_CREDENTIAL; + gss_release_oid_set(minor_status, &spcred->neg_mechs); + gss_release_cred(minor_status, &spcred->mcred); + free(spcred); + + dsyslog("Leaving spnego_gss_release_cred\n"); + return (GSS_S_COMPLETE); +} + +static void +check_spnego_options(spnego_gss_ctx_id_t spnego_ctx) +{ + spnego_ctx->optionStr = gssint_get_modOptions( + (const gss_OID)&spnego_oids[0]); +} + +static spnego_gss_ctx_id_t +create_spnego_ctx(void) +{ + spnego_gss_ctx_id_t spnego_ctx = NULL; + spnego_ctx = (spnego_gss_ctx_id_t) + malloc(sizeof (spnego_gss_ctx_id_rec)); + + if (spnego_ctx == NULL) { + return (NULL); + } + + spnego_ctx->magic_num = SPNEGO_MAGIC_ID; + spnego_ctx->ctx_handle = GSS_C_NO_CONTEXT; + spnego_ctx->mech_set = NULL; + spnego_ctx->internal_mech = NULL; + spnego_ctx->optionStr = NULL; + spnego_ctx->DER_mechTypes.length = 0; + spnego_ctx->DER_mechTypes.value = NULL; + spnego_ctx->default_cred = GSS_C_NO_CREDENTIAL; + spnego_ctx->mic_reqd = 0; + spnego_ctx->mic_sent = 0; + spnego_ctx->mic_rcvd = 0; + spnego_ctx->mech_complete = 0; + spnego_ctx->nego_done = 0; + spnego_ctx->internal_name = GSS_C_NO_NAME; + spnego_ctx->actual_mech = GSS_C_NO_OID; + + check_spnego_options(spnego_ctx); + + return (spnego_ctx); +} + +/* + * Both initiator and acceptor call here to verify and/or create mechListMIC, + * and to consistency-check the MIC state. handle_mic is invoked only if the + * negotiated mech has completed and supports MICs. + */ +static OM_uint32 +handle_mic(OM_uint32 *minor_status, gss_buffer_t mic_in, + int send_mechtok, spnego_gss_ctx_id_t sc, + gss_buffer_t *mic_out, + OM_uint32 *negState, send_token_flag *tokflag) +{ + OM_uint32 ret; + + ret = GSS_S_FAILURE; + *mic_out = GSS_C_NO_BUFFER; + if (mic_in != GSS_C_NO_BUFFER) { + if (sc->mic_rcvd) { + /* Reject MIC if we've already received a MIC. */ + *negState = REJECT; + *tokflag = ERROR_TOKEN_SEND; + return GSS_S_DEFECTIVE_TOKEN; + } + } else if (sc->mic_reqd && !send_mechtok) { + /* + * If the peer sends the final mechanism token, it + * must send the MIC with that token if the + * negotiation requires MICs. + */ + *negState = REJECT; + *tokflag = ERROR_TOKEN_SEND; + return GSS_S_DEFECTIVE_TOKEN; + } + ret = process_mic(minor_status, mic_in, sc, mic_out, + negState, tokflag); + if (ret != GSS_S_COMPLETE) { + return ret; + } + if (sc->mic_reqd) { + assert(sc->mic_sent || sc->mic_rcvd); + } + if (sc->mic_sent && sc->mic_rcvd) { + ret = GSS_S_COMPLETE; + *negState = ACCEPT_COMPLETE; + if (*mic_out == GSS_C_NO_BUFFER) { + /* + * We sent a MIC on the previous pass; we + * shouldn't be sending a mechanism token. + */ + assert(!send_mechtok); + *tokflag = NO_TOKEN_SEND; + } else { + *tokflag = CONT_TOKEN_SEND; + } + } else if (sc->mic_reqd) { + *negState = ACCEPT_INCOMPLETE; + ret = GSS_S_CONTINUE_NEEDED; + } else if (*negState == ACCEPT_COMPLETE) { + ret = GSS_S_COMPLETE; + } else { + ret = GSS_S_CONTINUE_NEEDED; + } + return ret; +} + +/* + * Perform the actual verification and/or generation of mechListMIC. + */ +static OM_uint32 +process_mic(OM_uint32 *minor_status, gss_buffer_t mic_in, + spnego_gss_ctx_id_t sc, gss_buffer_t *mic_out, + OM_uint32 *negState, send_token_flag *tokflag) +{ + OM_uint32 ret, tmpmin; + gss_qop_t qop_state; + gss_buffer_desc tmpmic = GSS_C_EMPTY_BUFFER; + + ret = GSS_S_FAILURE; + if (mic_in != GSS_C_NO_BUFFER) { + ret = gss_verify_mic(minor_status, sc->ctx_handle, + &sc->DER_mechTypes, + mic_in, &qop_state); + if (ret != GSS_S_COMPLETE) { + *negState = REJECT; + *tokflag = ERROR_TOKEN_SEND; + return ret; + } + /* If we got a MIC, we must send a MIC. */ + sc->mic_reqd = 1; + sc->mic_rcvd = 1; + } + if (sc->mic_reqd && !sc->mic_sent) { + ret = gss_get_mic(minor_status, sc->ctx_handle, + GSS_C_QOP_DEFAULT, + &sc->DER_mechTypes, + &tmpmic); + if (ret != GSS_S_COMPLETE) { + gss_release_buffer(&tmpmin, &tmpmic); + *tokflag = NO_TOKEN_SEND; + return ret; + } + *mic_out = malloc(sizeof(gss_buffer_desc)); + if (*mic_out == GSS_C_NO_BUFFER) { + gss_release_buffer(&tmpmin, &tmpmic); + *tokflag = NO_TOKEN_SEND; + return GSS_S_FAILURE; + } + **mic_out = tmpmic; + sc->mic_sent = 1; + } + return GSS_S_COMPLETE; +} + +/* + * Initial call to spnego_gss_init_sec_context(). + */ +static OM_uint32 +init_ctx_new(OM_uint32 *minor_status, + spnego_gss_cred_id_t spcred, + gss_ctx_id_t *ctx, + send_token_flag *tokflag) +{ + OM_uint32 ret; + spnego_gss_ctx_id_t sc = NULL; + + sc = create_spnego_ctx(); + if (sc == NULL) + return GSS_S_FAILURE; + + /* determine negotiation mech set */ + ret = get_negotiable_mechs(minor_status, spcred, GSS_C_INITIATE, + &sc->mech_set); + if (ret != GSS_S_COMPLETE) + goto cleanup; + + /* Set an initial internal mech to make the first context token. */ + sc->internal_mech = &sc->mech_set->elements[0]; + + if (put_mech_set(sc->mech_set, &sc->DER_mechTypes) < 0) { + ret = GSS_S_FAILURE; + goto cleanup; + } + /* + * The actual context is not yet determined, set the output + * context handle to refer to the spnego context itself. + */ + sc->ctx_handle = GSS_C_NO_CONTEXT; + *ctx = (gss_ctx_id_t)sc; + sc = NULL; + *tokflag = INIT_TOKEN_SEND; + ret = GSS_S_CONTINUE_NEEDED; + +cleanup: + release_spnego_ctx(&sc); + return ret; +} + +/* + * Called by second and later calls to spnego_gss_init_sec_context() + * to decode reply and update state. + */ +static OM_uint32 +init_ctx_cont(OM_uint32 *minor_status, gss_ctx_id_t *ctx, gss_buffer_t buf, + gss_buffer_t *responseToken, gss_buffer_t *mechListMIC, + OM_uint32 *negState, send_token_flag *tokflag) +{ + OM_uint32 ret, tmpmin, acc_negState; + unsigned char *ptr; + spnego_gss_ctx_id_t sc; + gss_OID supportedMech = GSS_C_NO_OID; + + sc = (spnego_gss_ctx_id_t)*ctx; + *negState = REJECT; + *tokflag = ERROR_TOKEN_SEND; + + ptr = buf->value; + ret = get_negTokenResp(minor_status, ptr, buf->length, + &acc_negState, &supportedMech, + responseToken, mechListMIC); + if (ret != GSS_S_COMPLETE) + goto cleanup; + if (acc_negState == ACCEPT_DEFECTIVE_TOKEN && + supportedMech == GSS_C_NO_OID && + *responseToken == GSS_C_NO_BUFFER && + *mechListMIC == GSS_C_NO_BUFFER) { + /* Reject "empty" token. */ + ret = GSS_S_DEFECTIVE_TOKEN; + } + if (acc_negState == REJECT) { + *minor_status = ERR_SPNEGO_NEGOTIATION_FAILED; + map_errcode(minor_status); + *tokflag = NO_TOKEN_SEND; + ret = GSS_S_FAILURE; + goto cleanup; + } + /* + * nego_done is false for the first call to init_ctx_cont() + */ + if (!sc->nego_done) { + ret = init_ctx_nego(minor_status, sc, + acc_negState, + supportedMech, responseToken, + mechListMIC, + negState, tokflag); + } else if ((!sc->mech_complete && *responseToken == GSS_C_NO_BUFFER) || + (sc->mech_complete && *responseToken != GSS_C_NO_BUFFER)) { + /* Missing or spurious token from acceptor. */ + ret = GSS_S_DEFECTIVE_TOKEN; + } else if (!sc->mech_complete || + (sc->mic_reqd && + (sc->ctx_flags & GSS_C_INTEG_FLAG))) { + /* Not obviously done; we may decide we're done later in + * init_ctx_call_init or handle_mic. */ + *negState = ACCEPT_INCOMPLETE; + *tokflag = CONT_TOKEN_SEND; + ret = GSS_S_CONTINUE_NEEDED; + } else { + /* mech finished on last pass and no MIC required, so done. */ + *negState = ACCEPT_COMPLETE; + *tokflag = NO_TOKEN_SEND; + ret = GSS_S_COMPLETE; + } +cleanup: + if (supportedMech != GSS_C_NO_OID) + generic_gss_release_oid(&tmpmin, &supportedMech); + return ret; +} + +/* + * Consistency checking and mechanism negotiation handling for second + * call of spnego_gss_init_sec_context(). Call init_ctx_reselect() to + * update internal state if acceptor has counter-proposed. + */ +static OM_uint32 +init_ctx_nego(OM_uint32 *minor_status, spnego_gss_ctx_id_t sc, + OM_uint32 acc_negState, gss_OID supportedMech, + gss_buffer_t *responseToken, gss_buffer_t *mechListMIC, + OM_uint32 *negState, send_token_flag *tokflag) +{ + OM_uint32 ret; + + *negState = REJECT; + *tokflag = ERROR_TOKEN_SEND; + ret = GSS_S_DEFECTIVE_TOKEN; + /* + * Both supportedMech and negState must be present in first + * acceptor token. + */ + if (supportedMech == GSS_C_NO_OID) { + *minor_status = ERR_SPNEGO_NO_MECH_FROM_ACCEPTOR; + map_errcode(minor_status); + return GSS_S_DEFECTIVE_TOKEN; + } + if (acc_negState == ACCEPT_DEFECTIVE_TOKEN) { + *minor_status = ERR_SPNEGO_NEGOTIATION_FAILED; + map_errcode(minor_status); + return GSS_S_DEFECTIVE_TOKEN; + } + + /* + * If the mechanism we sent is not the mechanism returned from + * the server, we need to handle the server's counter + * proposal. There is a bug in SAMBA servers that always send + * the old Kerberos mech OID, even though we sent the new one. + * So we will treat all the Kerberos mech OIDS as the same. + */ + if (!(is_kerb_mech(supportedMech) && + is_kerb_mech(sc->internal_mech)) && + !g_OID_equal(supportedMech, sc->internal_mech)) { + ret = init_ctx_reselect(minor_status, sc, + acc_negState, supportedMech, + responseToken, mechListMIC, + negState, tokflag); + + } else if (*responseToken == GSS_C_NO_BUFFER) { + if (sc->mech_complete) { + /* + * Mech completed on first call to its + * init_sec_context(). Acceptor sends no mech + * token. + */ + *negState = ACCEPT_COMPLETE; + *tokflag = NO_TOKEN_SEND; + ret = GSS_S_COMPLETE; + } else { + /* + * Reject missing mech token when optimistic + * mech selected. + */ + *minor_status = ERR_SPNEGO_NO_TOKEN_FROM_ACCEPTOR; + map_errcode(minor_status); + ret = GSS_S_DEFECTIVE_TOKEN; + } + } else if (sc->mech_complete) { + /* Reject spurious mech token. */ + ret = GSS_S_DEFECTIVE_TOKEN; + } else { + *negState = ACCEPT_INCOMPLETE; + *tokflag = CONT_TOKEN_SEND; + ret = GSS_S_CONTINUE_NEEDED; + } + sc->nego_done = 1; + return ret; +} + +/* + * Handle acceptor's counter-proposal of an alternative mechanism. + */ +static OM_uint32 +init_ctx_reselect(OM_uint32 *minor_status, spnego_gss_ctx_id_t sc, + OM_uint32 acc_negState, gss_OID supportedMech, + gss_buffer_t *responseToken, gss_buffer_t *mechListMIC, + OM_uint32 *negState, send_token_flag *tokflag) +{ + OM_uint32 tmpmin; + size_t i; + + gss_delete_sec_context(&tmpmin, &sc->ctx_handle, + GSS_C_NO_BUFFER); + + /* Find supportedMech in sc->mech_set. */ + for (i = 0; i < sc->mech_set->count; i++) { + if (g_OID_equal(supportedMech, &sc->mech_set->elements[i])) + break; + } + if (i == sc->mech_set->count) + return GSS_S_DEFECTIVE_TOKEN; + sc->internal_mech = &sc->mech_set->elements[i]; + + /* + * Windows 2003 and earlier don't correctly send a + * negState of request-mic when counter-proposing a + * mechanism. They probably don't handle mechListMICs + * properly either. + */ + if (acc_negState != REQUEST_MIC) + return GSS_S_DEFECTIVE_TOKEN; + + sc->mech_complete = 0; + sc->mic_reqd = 1; + *negState = REQUEST_MIC; + *tokflag = CONT_TOKEN_SEND; + return GSS_S_CONTINUE_NEEDED; +} + +/* + * Wrap call to mechanism gss_init_sec_context() and update state + * accordingly. + */ +static OM_uint32 +init_ctx_call_init(OM_uint32 *minor_status, + spnego_gss_ctx_id_t sc, + spnego_gss_cred_id_t spcred, + gss_name_t target_name, + OM_uint32 req_flags, + OM_uint32 time_req, + gss_buffer_t mechtok_in, + gss_OID *actual_mech, + gss_buffer_t mechtok_out, + OM_uint32 *ret_flags, + OM_uint32 *time_rec, + OM_uint32 *negState, + send_token_flag *send_token) +{ + OM_uint32 ret, tmpret, tmpmin; + gss_cred_id_t mcred; + + mcred = (spcred == NULL) ? GSS_C_NO_CREDENTIAL : spcred->mcred; + ret = gss_init_sec_context(minor_status, + mcred, + &sc->ctx_handle, + target_name, + sc->internal_mech, + (req_flags | GSS_C_INTEG_FLAG), + time_req, + GSS_C_NO_CHANNEL_BINDINGS, + mechtok_in, + &sc->actual_mech, + mechtok_out, + &sc->ctx_flags, + time_rec); + if (ret == GSS_S_COMPLETE) { + sc->mech_complete = 1; + if (ret_flags != NULL) + *ret_flags = sc->ctx_flags; + /* + * Microsoft SPNEGO implementations expect an even number of + * token exchanges. So if we're sending a final token, ask for + * a zero-length token back from the server. Also ask for a + * token back if this is the first token or if a MIC exchange + * is required. + */ + if (*send_token == CONT_TOKEN_SEND && + mechtok_out->length == 0 && + (!sc->mic_reqd || + !(sc->ctx_flags & GSS_C_INTEG_FLAG))) { + /* The exchange is complete. */ + *negState = ACCEPT_COMPLETE; + ret = GSS_S_COMPLETE; + *send_token = NO_TOKEN_SEND; + } else { + /* Ask for one more hop. */ + *negState = ACCEPT_INCOMPLETE; + ret = GSS_S_CONTINUE_NEEDED; + } + return ret; + } + + if (ret == GSS_S_CONTINUE_NEEDED) + return ret; + + if (*send_token != INIT_TOKEN_SEND) { + *send_token = ERROR_TOKEN_SEND; + *negState = REJECT; + return ret; + } + + /* + * Since this is the first token, we can fall back to later mechanisms + * in the list. Since the mechanism list is expected to be short, we + * can do this with recursion. If all mechanisms produce errors, the + * caller should get the error from the first mech in the list. + */ + memmove(sc->mech_set->elements, sc->mech_set->elements + 1, + --sc->mech_set->count * sizeof(*sc->mech_set->elements)); + if (sc->mech_set->count == 0) + goto fail; + gss_release_buffer(&tmpmin, &sc->DER_mechTypes); + if (put_mech_set(sc->mech_set, &sc->DER_mechTypes) < 0) + goto fail; + tmpret = init_ctx_call_init(&tmpmin, sc, spcred, target_name, + req_flags, time_req, mechtok_in, + actual_mech, mechtok_out, ret_flags, + time_rec, negState, send_token); + if (HARD_ERROR(tmpret)) + goto fail; + *minor_status = tmpmin; + return tmpret; + +fail: + /* Don't output token on error from first call. */ + *send_token = NO_TOKEN_SEND; + *negState = REJECT; + return ret; +} + +/*ARGSUSED*/ +OM_uint32 KRB5_CALLCONV +spnego_gss_init_sec_context( + OM_uint32 *minor_status, + gss_cred_id_t claimant_cred_handle, + gss_ctx_id_t *context_handle, + gss_name_t target_name, + gss_OID mech_type, + OM_uint32 req_flags, + OM_uint32 time_req, + gss_channel_bindings_t input_chan_bindings, + gss_buffer_t input_token, + gss_OID *actual_mech, + gss_buffer_t output_token, + OM_uint32 *ret_flags, + OM_uint32 *time_rec) +{ + send_token_flag send_token = NO_TOKEN_SEND; + OM_uint32 tmpmin, ret, negState; + gss_buffer_t mechtok_in, mechListMIC_in, mechListMIC_out; + gss_buffer_desc mechtok_out = GSS_C_EMPTY_BUFFER; + spnego_gss_cred_id_t spcred = NULL; + spnego_gss_ctx_id_t spnego_ctx = NULL; + + dsyslog("Entering init_sec_context\n"); + + mechtok_in = mechListMIC_out = mechListMIC_in = GSS_C_NO_BUFFER; + negState = REJECT; + + /* + * This function works in three steps: + * + * 1. Perform mechanism negotiation. + * 2. Invoke the negotiated or optimistic mech's gss_init_sec_context + * function and examine the results. + * 3. Process or generate MICs if necessary. + * + * The three steps share responsibility for determining when the + * exchange is complete. If the selected mech completed in a previous + * call and no MIC exchange is expected, then step 1 will decide. If + * the selected mech completes in this call and no MIC exchange is + * expected, then step 2 will decide. If a MIC exchange is expected, + * then step 3 will decide. If an error occurs in any step, the + * exchange will be aborted, possibly with an error token. + * + * negState determines the state of the negotiation, and is + * communicated to the acceptor if a continuing token is sent. + * send_token is used to indicate what type of token, if any, should be + * generated. + */ + + /* Validate arguments. */ + if (minor_status != NULL) + *minor_status = 0; + if (output_token != GSS_C_NO_BUFFER) { + output_token->length = 0; + output_token->value = NULL; + } + if (minor_status == NULL || + output_token == GSS_C_NO_BUFFER || + context_handle == NULL) + return GSS_S_CALL_INACCESSIBLE_WRITE; + + if (actual_mech != NULL) + *actual_mech = GSS_C_NO_OID; + + /* Step 1: perform mechanism negotiation. */ + spcred = (spnego_gss_cred_id_t)claimant_cred_handle; + if (*context_handle == GSS_C_NO_CONTEXT) { + ret = init_ctx_new(minor_status, spcred, + context_handle, &send_token); + if (ret != GSS_S_CONTINUE_NEEDED) { + goto cleanup; + } + } else { + ret = init_ctx_cont(minor_status, context_handle, + input_token, &mechtok_in, + &mechListMIC_in, &negState, &send_token); + if (HARD_ERROR(ret)) { + goto cleanup; + } + } + + /* Step 2: invoke the selected or optimistic mechanism's + * gss_init_sec_context function, if it didn't complete previously. */ + spnego_ctx = (spnego_gss_ctx_id_t)*context_handle; + if (!spnego_ctx->mech_complete) { + ret = init_ctx_call_init( + minor_status, spnego_ctx, spcred, + target_name, req_flags, + time_req, mechtok_in, + actual_mech, &mechtok_out, + ret_flags, time_rec, + &negState, &send_token); + } + + /* Step 3: process or generate the MIC, if the negotiated mech is + * complete and supports MICs. */ + if (!HARD_ERROR(ret) && spnego_ctx->mech_complete && + (spnego_ctx->ctx_flags & GSS_C_INTEG_FLAG)) { + + ret = handle_mic(minor_status, + mechListMIC_in, + (mechtok_out.length != 0), + spnego_ctx, &mechListMIC_out, + &negState, &send_token); + } +cleanup: + if (send_token == INIT_TOKEN_SEND) { + if (make_spnego_tokenInit_msg(spnego_ctx, + 0, + mechListMIC_out, + req_flags, + &mechtok_out, send_token, + output_token) < 0) { + ret = GSS_S_FAILURE; + } + } else if (send_token != NO_TOKEN_SEND) { + if (make_spnego_tokenTarg_msg(negState, GSS_C_NO_OID, + &mechtok_out, mechListMIC_out, + send_token, + output_token) < 0) { + ret = GSS_S_FAILURE; + } + } + gss_release_buffer(&tmpmin, &mechtok_out); + if (ret == GSS_S_COMPLETE) { + /* + * Now, switch the output context to refer to the + * negotiated mechanism's context. + */ + *context_handle = (gss_ctx_id_t)spnego_ctx->ctx_handle; + if (actual_mech != NULL) + *actual_mech = spnego_ctx->actual_mech; + if (ret_flags != NULL) + *ret_flags = spnego_ctx->ctx_flags; + release_spnego_ctx(&spnego_ctx); + } else if (ret != GSS_S_CONTINUE_NEEDED) { + if (spnego_ctx != NULL) { + gss_delete_sec_context(&tmpmin, + &spnego_ctx->ctx_handle, + GSS_C_NO_BUFFER); + release_spnego_ctx(&spnego_ctx); + } + *context_handle = GSS_C_NO_CONTEXT; + } + if (mechtok_in != GSS_C_NO_BUFFER) { + gss_release_buffer(&tmpmin, mechtok_in); + free(mechtok_in); + } + if (mechListMIC_in != GSS_C_NO_BUFFER) { + gss_release_buffer(&tmpmin, mechListMIC_in); + free(mechListMIC_in); + } + if (mechListMIC_out != GSS_C_NO_BUFFER) { + gss_release_buffer(&tmpmin, mechListMIC_out); + free(mechListMIC_out); + } + return ret; +} /* init_sec_context */ + +/* We don't want to import KRB5 headers here */ +static const gss_OID_desc gss_mech_krb5_oid = + { 9, "\052\206\110\206\367\022\001\002\002" }; +static const gss_OID_desc gss_mech_krb5_wrong_oid = + { 9, "\052\206\110\202\367\022\001\002\002" }; + +/* + * verify that the input token length is not 0. If it is, just return. + * If the token length is greater than 0, der encode as a sequence + * and place in buf_out, advancing buf_out. + */ + +static int +put_neg_hints(unsigned char **buf_out, gss_buffer_t input_token, + unsigned int buflen) +{ + int ret; + + /* if token length is 0, we do not want to send */ + if (input_token->length == 0) + return (0); + + if (input_token->length > buflen) + return (-1); + + *(*buf_out)++ = SEQUENCE; + if ((ret = gssint_put_der_length(input_token->length, buf_out, + input_token->length))) + return (ret); + TWRITE_STR(*buf_out, input_token->value, input_token->length); + return (0); +} + +/* + * NegHints ::= SEQUENCE { + * hintName [0] GeneralString OPTIONAL, + * hintAddress [1] OCTET STRING OPTIONAL + * } + */ + +#define HOST_PREFIX "host@" +#define HOST_PREFIX_LEN (sizeof(HOST_PREFIX) - 1) + +static int +make_NegHints(OM_uint32 *minor_status, + spnego_gss_cred_id_t spcred, gss_buffer_t *outbuf) +{ + gss_buffer_desc hintNameBuf; + gss_name_t hintName = GSS_C_NO_NAME; + gss_name_t hintKerberosName; + gss_OID hintNameType; + OM_uint32 major_status; + OM_uint32 minor; + unsigned int tlen = 0; + unsigned int hintNameSize = 0; + unsigned char *ptr; + unsigned char *t; + + *outbuf = GSS_C_NO_BUFFER; + + if (spcred != NULL) { + major_status = gss_inquire_cred(minor_status, + spcred->mcred, + &hintName, + NULL, + NULL, + NULL); + if (major_status != GSS_S_COMPLETE) + return (major_status); + } + + if (hintName == GSS_C_NO_NAME) { + krb5_error_code code; + krb5int_access kaccess; + char hostname[HOST_PREFIX_LEN + MAXHOSTNAMELEN + 1] = HOST_PREFIX; + + code = krb5int_accessor(&kaccess, KRB5INT_ACCESS_VERSION); + if (code != 0) { + *minor_status = code; + return (GSS_S_FAILURE); + } + + /* this breaks mutual authentication but Samba relies on it */ + code = (*kaccess.clean_hostname)(NULL, NULL, + &hostname[HOST_PREFIX_LEN], + MAXHOSTNAMELEN); + if (code != 0) { + *minor_status = code; + return (GSS_S_FAILURE); + } + + hintNameBuf.value = hostname; + hintNameBuf.length = strlen(hostname); + + major_status = gss_import_name(minor_status, + &hintNameBuf, + GSS_C_NT_HOSTBASED_SERVICE, + &hintName); + if (major_status != GSS_S_COMPLETE) { + return (major_status); + } + } + + hintNameBuf.value = NULL; + hintNameBuf.length = 0; + + major_status = gss_canonicalize_name(minor_status, + hintName, + (gss_OID)&gss_mech_krb5_oid, + &hintKerberosName); + if (major_status != GSS_S_COMPLETE) { + gss_release_name(&minor, &hintName); + return (major_status); + } + gss_release_name(&minor, &hintName); + + major_status = gss_display_name(minor_status, + hintKerberosName, + &hintNameBuf, + &hintNameType); + if (major_status != GSS_S_COMPLETE) { + gss_release_name(&minor, &hintName); + return (major_status); + } + gss_release_name(&minor, &hintKerberosName); + + /* + * Now encode the name hint into a NegHints ASN.1 type + */ + major_status = GSS_S_FAILURE; + + /* Length of DER encoded GeneralString */ + tlen = 1 + gssint_der_length_size(hintNameBuf.length) + + hintNameBuf.length; + hintNameSize = tlen; + + /* Length of DER encoded hintName */ + tlen += 1 + gssint_der_length_size(hintNameSize); + + t = gssalloc_malloc(tlen); + if (t == NULL) { + *minor_status = ENOMEM; + goto errout; + } + + ptr = t; + + *ptr++ = CONTEXT | 0x00; /* hintName identifier */ + if (gssint_put_der_length(hintNameSize, + &ptr, tlen - (int)(ptr-t))) + goto errout; + + *ptr++ = GENERAL_STRING; + if (gssint_put_der_length(hintNameBuf.length, + &ptr, tlen - (int)(ptr-t))) + goto errout; + + memcpy(ptr, hintNameBuf.value, hintNameBuf.length); + ptr += hintNameBuf.length; + + *outbuf = (gss_buffer_t)malloc(sizeof(gss_buffer_desc)); + if (*outbuf == NULL) { + *minor_status = ENOMEM; + goto errout; + } + (*outbuf)->value = (void *)t; + (*outbuf)->length = ptr - t; + + t = NULL; /* don't free */ + + *minor_status = 0; + major_status = GSS_S_COMPLETE; + +errout: + if (t != NULL) { + free(t); + } + + gss_release_buffer(&minor, &hintNameBuf); + + return (major_status); +} + +/* + * Support the Microsoft NegHints extension to SPNEGO for compatibility with + * some versions of Samba. See: + * http://msdn.microsoft.com/en-us/library/cc247039(PROT.10).aspx + */ +static OM_uint32 +acc_ctx_hints(OM_uint32 *minor_status, + gss_ctx_id_t *ctx, + spnego_gss_cred_id_t spcred, + gss_buffer_t *mechListMIC, + OM_uint32 *negState, + send_token_flag *return_token) +{ + OM_uint32 tmpmin, ret; + gss_OID_set supported_mechSet; + spnego_gss_ctx_id_t sc = NULL; + + *mechListMIC = GSS_C_NO_BUFFER; + supported_mechSet = GSS_C_NO_OID_SET; + *return_token = NO_TOKEN_SEND; + *negState = REJECT; + *minor_status = 0; + + /* A hint request must be the first token received. */ + if (*ctx != GSS_C_NO_CONTEXT) + return GSS_S_DEFECTIVE_TOKEN; + + ret = get_negotiable_mechs(minor_status, spcred, GSS_C_ACCEPT, + &supported_mechSet); + if (ret != GSS_S_COMPLETE) + goto cleanup; + + ret = make_NegHints(minor_status, spcred, mechListMIC); + if (ret != GSS_S_COMPLETE) + goto cleanup; + + sc = create_spnego_ctx(); + if (sc == NULL) { + ret = GSS_S_FAILURE; + goto cleanup; + } + if (put_mech_set(supported_mechSet, &sc->DER_mechTypes) < 0) { + ret = GSS_S_FAILURE; + goto cleanup; + } + sc->internal_mech = GSS_C_NO_OID; + + *negState = ACCEPT_INCOMPLETE; + *return_token = INIT_TOKEN_SEND; + sc->firstpass = 1; + *ctx = (gss_ctx_id_t)sc; + sc = NULL; + ret = GSS_S_COMPLETE; + +cleanup: + release_spnego_ctx(&sc); + gss_release_oid_set(&tmpmin, &supported_mechSet); + + return ret; +} + +/* + * Set negState to REJECT if the token is defective, else + * ACCEPT_INCOMPLETE or REQUEST_MIC, depending on whether initiator's + * preferred mechanism is supported. + */ +static OM_uint32 +acc_ctx_new(OM_uint32 *minor_status, + gss_buffer_t buf, + gss_ctx_id_t *ctx, + spnego_gss_cred_id_t spcred, + gss_buffer_t *mechToken, + gss_buffer_t *mechListMIC, + OM_uint32 *negState, + send_token_flag *return_token) +{ + OM_uint32 tmpmin, ret, req_flags; + gss_OID_set supported_mechSet, mechTypes; + gss_buffer_desc der_mechTypes; + gss_OID mech_wanted; + spnego_gss_ctx_id_t sc = NULL; + + ret = GSS_S_DEFECTIVE_TOKEN; + der_mechTypes.length = 0; + der_mechTypes.value = NULL; + *mechToken = *mechListMIC = GSS_C_NO_BUFFER; + supported_mechSet = mechTypes = GSS_C_NO_OID_SET; + *return_token = ERROR_TOKEN_SEND; + *negState = REJECT; + *minor_status = 0; + + ret = get_negTokenInit(minor_status, buf, &der_mechTypes, + &mechTypes, &req_flags, + mechToken, mechListMIC); + if (ret != GSS_S_COMPLETE) { + goto cleanup; + } + ret = get_negotiable_mechs(minor_status, spcred, GSS_C_ACCEPT, + &supported_mechSet); + if (ret != GSS_S_COMPLETE) { + *return_token = NO_TOKEN_SEND; + goto cleanup; + } + /* + * Select the best match between the list of mechs + * that the initiator requested and the list that + * the acceptor will support. + */ + mech_wanted = negotiate_mech(supported_mechSet, mechTypes, negState); + if (*negState == REJECT) { + ret = GSS_S_BAD_MECH; + goto cleanup; + } + sc = (spnego_gss_ctx_id_t)*ctx; + if (sc != NULL) { + gss_release_buffer(&tmpmin, &sc->DER_mechTypes); + assert(mech_wanted != GSS_C_NO_OID); + } else + sc = create_spnego_ctx(); + if (sc == NULL) { + ret = GSS_S_FAILURE; + *return_token = NO_TOKEN_SEND; + goto cleanup; + } + sc->mech_set = supported_mechSet; + supported_mechSet = GSS_C_NO_OID_SET; + sc->internal_mech = mech_wanted; + sc->DER_mechTypes = der_mechTypes; + der_mechTypes.length = 0; + der_mechTypes.value = NULL; + + if (*negState == REQUEST_MIC) + sc->mic_reqd = 1; + + *return_token = INIT_TOKEN_SEND; + sc->firstpass = 1; + *ctx = (gss_ctx_id_t)sc; + ret = GSS_S_COMPLETE; +cleanup: + gss_release_oid_set(&tmpmin, &mechTypes); + gss_release_oid_set(&tmpmin, &supported_mechSet); + if (der_mechTypes.length != 0) + gss_release_buffer(&tmpmin, &der_mechTypes); + + return ret; +} + +static OM_uint32 +acc_ctx_cont(OM_uint32 *minstat, + gss_buffer_t buf, + gss_ctx_id_t *ctx, + gss_buffer_t *responseToken, + gss_buffer_t *mechListMIC, + OM_uint32 *negState, + send_token_flag *return_token) +{ + OM_uint32 ret, tmpmin; + gss_OID supportedMech; + spnego_gss_ctx_id_t sc; + unsigned int len; + unsigned char *ptr, *bufstart; + + sc = (spnego_gss_ctx_id_t)*ctx; + ret = GSS_S_DEFECTIVE_TOKEN; + *negState = REJECT; + *minstat = 0; + supportedMech = GSS_C_NO_OID; + *return_token = ERROR_TOKEN_SEND; + *responseToken = *mechListMIC = GSS_C_NO_BUFFER; + + ptr = bufstart = buf->value; +#define REMAIN (buf->length - (ptr - bufstart)) + if (REMAIN > INT_MAX) + return GSS_S_DEFECTIVE_TOKEN; + + /* + * Attempt to work with old Sun SPNEGO. + */ + if (*ptr == HEADER_ID) { + ret = g_verify_token_header(gss_mech_spnego, + &len, &ptr, 0, REMAIN); + if (ret) { + *minstat = ret; + return GSS_S_DEFECTIVE_TOKEN; + } + } + if (*ptr != (CONTEXT | 0x01)) { + return GSS_S_DEFECTIVE_TOKEN; + } + ret = get_negTokenResp(minstat, ptr, REMAIN, + negState, &supportedMech, + responseToken, mechListMIC); + if (ret != GSS_S_COMPLETE) + goto cleanup; + + if (*responseToken == GSS_C_NO_BUFFER && + *mechListMIC == GSS_C_NO_BUFFER) { + + ret = GSS_S_DEFECTIVE_TOKEN; + goto cleanup; + } + if (supportedMech != GSS_C_NO_OID) { + ret = GSS_S_DEFECTIVE_TOKEN; + goto cleanup; + } + sc->firstpass = 0; + *negState = ACCEPT_INCOMPLETE; + *return_token = CONT_TOKEN_SEND; +cleanup: + if (supportedMech != GSS_C_NO_OID) { + generic_gss_release_oid(&tmpmin, &supportedMech); + } + return ret; +#undef REMAIN +} + +/* + * Verify that mech OID is either exactly the same as the negotiated + * mech OID, or is a mech OID supported by the negotiated mech. MS + * implementations can list a most preferred mech using an incorrect + * krb5 OID while emitting a krb5 initiator mech token having the + * correct krb5 mech OID. + */ +static OM_uint32 +acc_ctx_vfy_oid(OM_uint32 *minor_status, + spnego_gss_ctx_id_t sc, gss_OID mechoid, + OM_uint32 *negState, send_token_flag *tokflag) +{ + OM_uint32 ret, tmpmin; + gss_mechanism mech = NULL; + gss_OID_set mech_set = GSS_C_NO_OID_SET; + int present = 0; + + if (g_OID_equal(sc->internal_mech, mechoid)) + return GSS_S_COMPLETE; + + mech = gssint_get_mechanism(sc->internal_mech); + if (mech == NULL || mech->gss_indicate_mechs == NULL) { + *minor_status = ERR_SPNEGO_NEGOTIATION_FAILED; + map_errcode(minor_status); + *negState = REJECT; + *tokflag = ERROR_TOKEN_SEND; + return GSS_S_BAD_MECH; + } + ret = mech->gss_indicate_mechs(minor_status, &mech_set); + if (ret != GSS_S_COMPLETE) { + *tokflag = NO_TOKEN_SEND; + map_error(minor_status, mech); + goto cleanup; + } + ret = gss_test_oid_set_member(minor_status, mechoid, + mech_set, &present); + if (ret != GSS_S_COMPLETE) + goto cleanup; + if (!present) { + *minor_status = ERR_SPNEGO_NEGOTIATION_FAILED; + map_errcode(minor_status); + *negState = REJECT; + *tokflag = ERROR_TOKEN_SEND; + ret = GSS_S_BAD_MECH; + } +cleanup: + gss_release_oid_set(&tmpmin, &mech_set); + return ret; +} +#ifndef LEAN_CLIENT +/* + * Wrap call to gss_accept_sec_context() and update state + * accordingly. + */ +static OM_uint32 +acc_ctx_call_acc(OM_uint32 *minor_status, spnego_gss_ctx_id_t sc, + spnego_gss_cred_id_t spcred, gss_buffer_t mechtok_in, + gss_OID *mech_type, gss_buffer_t mechtok_out, + OM_uint32 *ret_flags, OM_uint32 *time_rec, + gss_cred_id_t *delegated_cred_handle, + OM_uint32 *negState, send_token_flag *tokflag) +{ + OM_uint32 ret; + gss_OID_desc mechoid; + gss_cred_id_t mcred; + + if (sc->ctx_handle == GSS_C_NO_CONTEXT) { + /* + * mechoid is an alias; don't free it. + */ + ret = gssint_get_mech_type(&mechoid, mechtok_in); + if (ret != GSS_S_COMPLETE) { + *tokflag = NO_TOKEN_SEND; + return ret; + } + ret = acc_ctx_vfy_oid(minor_status, sc, &mechoid, + negState, tokflag); + if (ret != GSS_S_COMPLETE) + return ret; + } + + mcred = (spcred == NULL) ? GSS_C_NO_CREDENTIAL : spcred->mcred; + ret = gss_accept_sec_context(minor_status, + &sc->ctx_handle, + mcred, + mechtok_in, + GSS_C_NO_CHANNEL_BINDINGS, + &sc->internal_name, + mech_type, + mechtok_out, + &sc->ctx_flags, + time_rec, + delegated_cred_handle); + if (ret == GSS_S_COMPLETE) { +#ifdef MS_BUG_TEST + /* + * Force MIC to be not required even if we previously + * requested a MIC. + */ + char *envstr = getenv("MS_FORCE_NO_MIC"); + + if (envstr != NULL && strcmp(envstr, "1") == 0 && + !(sc->ctx_flags & GSS_C_MUTUAL_FLAG) && + sc->mic_reqd) { + + sc->mic_reqd = 0; + } +#endif + sc->mech_complete = 1; + if (ret_flags != NULL) + *ret_flags = sc->ctx_flags; + + if (!sc->mic_reqd || + !(sc->ctx_flags & GSS_C_INTEG_FLAG)) { + /* No MIC exchange required, so we're done. */ + *negState = ACCEPT_COMPLETE; + ret = GSS_S_COMPLETE; + } else { + /* handle_mic will decide if we're done. */ + ret = GSS_S_CONTINUE_NEEDED; + } + } else if (ret != GSS_S_CONTINUE_NEEDED) { + *negState = REJECT; + *tokflag = ERROR_TOKEN_SEND; + } + return ret; +} + +/*ARGSUSED*/ +OM_uint32 KRB5_CALLCONV +spnego_gss_accept_sec_context( + 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) +{ + OM_uint32 ret, tmpmin, negState; + send_token_flag return_token; + gss_buffer_t mechtok_in, mic_in, mic_out; + gss_buffer_desc mechtok_out = GSS_C_EMPTY_BUFFER; + spnego_gss_ctx_id_t sc = NULL; + spnego_gss_cred_id_t spcred = NULL; + int sendTokenInit = 0, tmpret; + + mechtok_in = mic_in = mic_out = GSS_C_NO_BUFFER; + + /* + * This function works in three steps: + * + * 1. Perform mechanism negotiation. + * 2. Invoke the negotiated mech's gss_accept_sec_context function + * and examine the results. + * 3. Process or generate MICs if necessary. + * + * Step one determines whether the negotiation requires a MIC exchange, + * while steps two and three share responsibility for determining when + * the exchange is complete. If the selected mech completes in this + * call and no MIC exchange is expected, then step 2 will decide. If a + * MIC exchange is expected, then step 3 will decide. If an error + * occurs in any step, the exchange will be aborted, possibly with an + * error token. + * + * negState determines the state of the negotiation, and is + * communicated to the acceptor if a continuing token is sent. + * return_token is used to indicate what type of token, if any, should + * be generated. + */ + + /* Validate arguments. */ + if (minor_status != NULL) + *minor_status = 0; + if (output_token != GSS_C_NO_BUFFER) { + output_token->length = 0; + output_token->value = NULL; + } + + if (minor_status == NULL || + output_token == GSS_C_NO_BUFFER || + context_handle == NULL) + return GSS_S_CALL_INACCESSIBLE_WRITE; + + if (input_token == GSS_C_NO_BUFFER) + return GSS_S_CALL_INACCESSIBLE_READ; + + /* Step 1: Perform mechanism negotiation. */ + sc = (spnego_gss_ctx_id_t)*context_handle; + spcred = (spnego_gss_cred_id_t)verifier_cred_handle; + if (sc == NULL || sc->internal_mech == GSS_C_NO_OID) { + /* Process an initial token or request for NegHints. */ + if (src_name != NULL) + *src_name = GSS_C_NO_NAME; + if (mech_type != NULL) + *mech_type = GSS_C_NO_OID; + if (time_rec != NULL) + *time_rec = 0; + if (ret_flags != NULL) + *ret_flags = 0; + if (delegated_cred_handle != NULL) + *delegated_cred_handle = GSS_C_NO_CREDENTIAL; + if (input_token->length == 0) { + ret = acc_ctx_hints(minor_status, + context_handle, spcred, + &mic_out, + &negState, + &return_token); + if (ret != GSS_S_COMPLETE) + goto cleanup; + sendTokenInit = 1; + ret = GSS_S_CONTINUE_NEEDED; + } else { + /* Can set negState to REQUEST_MIC */ + ret = acc_ctx_new(minor_status, input_token, + context_handle, spcred, + &mechtok_in, &mic_in, + &negState, &return_token); + if (ret != GSS_S_COMPLETE) + goto cleanup; + ret = GSS_S_CONTINUE_NEEDED; + } + } else { + /* Process a response token. Can set negState to + * ACCEPT_INCOMPLETE. */ + ret = acc_ctx_cont(minor_status, input_token, + context_handle, &mechtok_in, + &mic_in, &negState, &return_token); + if (ret != GSS_S_COMPLETE) + goto cleanup; + ret = GSS_S_CONTINUE_NEEDED; + } + + /* Step 2: invoke the negotiated mechanism's gss_accept_sec_context + * function. */ + sc = (spnego_gss_ctx_id_t)*context_handle; + /* + * Handle mechtok_in and mic_in only if they are + * present in input_token. If neither is present, whether + * this is an error depends on whether this is the first + * round-trip. RET is set to a default value according to + * whether it is the first round-trip. + */ + if (negState != REQUEST_MIC && mechtok_in != GSS_C_NO_BUFFER) { + ret = acc_ctx_call_acc(minor_status, sc, spcred, + mechtok_in, mech_type, &mechtok_out, + ret_flags, time_rec, + delegated_cred_handle, + &negState, &return_token); + } + + /* Step 3: process or generate the MIC, if the negotiated mech is + * complete and supports MICs. */ + if (!HARD_ERROR(ret) && sc->mech_complete && + (sc->ctx_flags & GSS_C_INTEG_FLAG)) { + + ret = handle_mic(minor_status, mic_in, + (mechtok_out.length != 0), + sc, &mic_out, + &negState, &return_token); + } +cleanup: + if (return_token == INIT_TOKEN_SEND && sendTokenInit) { + assert(sc != NULL); + tmpret = make_spnego_tokenInit_msg(sc, 1, mic_out, 0, + GSS_C_NO_BUFFER, + return_token, output_token); + if (tmpret < 0) + ret = GSS_S_FAILURE; + } else if (return_token != NO_TOKEN_SEND && + return_token != CHECK_MIC) { + tmpret = make_spnego_tokenTarg_msg(negState, + sc ? sc->internal_mech : + GSS_C_NO_OID, + &mechtok_out, mic_out, + return_token, + output_token); + if (tmpret < 0) + ret = GSS_S_FAILURE; + } + if (ret == GSS_S_COMPLETE) { + *context_handle = (gss_ctx_id_t)sc->ctx_handle; + if (sc->internal_name != GSS_C_NO_NAME && + src_name != NULL) { + *src_name = sc->internal_name; + sc->internal_name = GSS_C_NO_NAME; + } + release_spnego_ctx(&sc); + } else if (ret != GSS_S_CONTINUE_NEEDED) { + if (sc != NULL) { + gss_delete_sec_context(&tmpmin, &sc->ctx_handle, + GSS_C_NO_BUFFER); + release_spnego_ctx(&sc); + } + *context_handle = GSS_C_NO_CONTEXT; + } + gss_release_buffer(&tmpmin, &mechtok_out); + if (mechtok_in != GSS_C_NO_BUFFER) { + gss_release_buffer(&tmpmin, mechtok_in); + free(mechtok_in); + } + if (mic_in != GSS_C_NO_BUFFER) { + gss_release_buffer(&tmpmin, mic_in); + free(mic_in); + } + if (mic_out != GSS_C_NO_BUFFER) { + gss_release_buffer(&tmpmin, mic_out); + free(mic_out); + } + return ret; +} +#endif /* LEAN_CLIENT */ + + +/*ARGSUSED*/ +OM_uint32 KRB5_CALLCONV +spnego_gss_display_status( + OM_uint32 *minor_status, + OM_uint32 status_value, + int status_type, + gss_OID mech_type, + OM_uint32 *message_context, + gss_buffer_t status_string) +{ + dsyslog("Entering display_status\n"); + + *message_context = 0; + switch (status_value) { + case ERR_SPNEGO_NO_MECHS_AVAILABLE: + /* CSTYLED */ + *status_string = make_err_msg(_("SPNEGO cannot find " + "mechanisms to negotiate")); + break; + case ERR_SPNEGO_NO_CREDS_ACQUIRED: + /* CSTYLED */ + *status_string = make_err_msg(_("SPNEGO failed to acquire " + "creds")); + break; + case ERR_SPNEGO_NO_MECH_FROM_ACCEPTOR: + /* CSTYLED */ + *status_string = make_err_msg(_("SPNEGO acceptor did not " + "select a mechanism")); + break; + case ERR_SPNEGO_NEGOTIATION_FAILED: + /* CSTYLED */ + *status_string = make_err_msg(_("SPNEGO failed to negotiate a " + "mechanism")); + break; + case ERR_SPNEGO_NO_TOKEN_FROM_ACCEPTOR: + /* CSTYLED */ + *status_string = make_err_msg(_("SPNEGO acceptor did not " + "return a valid token")); + break; + default: + status_string->length = 0; + status_string->value = ""; + break; + } + + dsyslog("Leaving display_status\n"); + return (GSS_S_COMPLETE); +} + + +/*ARGSUSED*/ +OM_uint32 KRB5_CALLCONV +spnego_gss_import_name( + OM_uint32 *minor_status, + gss_buffer_t input_name_buffer, + gss_OID input_name_type, + gss_name_t *output_name) +{ + OM_uint32 status; + + dsyslog("Entering import_name\n"); + + status = gss_import_name(minor_status, input_name_buffer, + input_name_type, output_name); + + dsyslog("Leaving import_name\n"); + return (status); +} + +/*ARGSUSED*/ +OM_uint32 KRB5_CALLCONV +spnego_gss_release_name( + OM_uint32 *minor_status, + gss_name_t *input_name) +{ + OM_uint32 status; + + dsyslog("Entering release_name\n"); + + status = gss_release_name(minor_status, input_name); + + dsyslog("Leaving release_name\n"); + return (status); +} + +/*ARGSUSED*/ +OM_uint32 KRB5_CALLCONV +spnego_gss_duplicate_name( + OM_uint32 *minor_status, + const gss_name_t input_name, + gss_name_t *output_name) +{ + OM_uint32 status; + + dsyslog("Entering duplicate_name\n"); + + status = gss_duplicate_name(minor_status, input_name, output_name); + + dsyslog("Leaving duplicate_name\n"); + return (status); +} + +OM_uint32 KRB5_CALLCONV +spnego_gss_inquire_cred( + OM_uint32 *minor_status, + gss_cred_id_t cred_handle, + gss_name_t *name, + OM_uint32 *lifetime, + int *cred_usage, + gss_OID_set *mechanisms) +{ + OM_uint32 status; + spnego_gss_cred_id_t spcred = NULL; + gss_cred_id_t creds = GSS_C_NO_CREDENTIAL; + OM_uint32 tmp_minor_status; + OM_uint32 initiator_lifetime, acceptor_lifetime; + + dsyslog("Entering inquire_cred\n"); + + /* + * To avoid infinite recursion, if GSS_C_NO_CREDENTIAL is + * supplied we call gss_inquire_cred_by_mech() on the + * first non-SPNEGO mechanism. + */ + spcred = (spnego_gss_cred_id_t)cred_handle; + if (spcred == NULL) { + status = get_available_mechs(minor_status, + GSS_C_NO_NAME, + GSS_C_BOTH, + GSS_C_NO_CRED_STORE, + &creds, + mechanisms); + if (status != GSS_S_COMPLETE) { + dsyslog("Leaving inquire_cred\n"); + return (status); + } + + if ((*mechanisms)->count == 0) { + gss_release_cred(&tmp_minor_status, &creds); + gss_release_oid_set(&tmp_minor_status, mechanisms); + dsyslog("Leaving inquire_cred\n"); + return (GSS_S_DEFECTIVE_CREDENTIAL); + } + + assert((*mechanisms)->elements != NULL); + + status = gss_inquire_cred_by_mech(minor_status, + creds, + &(*mechanisms)->elements[0], + name, + &initiator_lifetime, + &acceptor_lifetime, + cred_usage); + if (status != GSS_S_COMPLETE) { + gss_release_cred(&tmp_minor_status, &creds); + dsyslog("Leaving inquire_cred\n"); + return (status); + } + + if (lifetime != NULL) + *lifetime = (*cred_usage == GSS_C_ACCEPT) ? + acceptor_lifetime : initiator_lifetime; + + gss_release_cred(&tmp_minor_status, &creds); + } else { + status = gss_inquire_cred(minor_status, spcred->mcred, + name, lifetime, + cred_usage, mechanisms); + } + + dsyslog("Leaving inquire_cred\n"); + + return (status); +} + +/*ARGSUSED*/ +OM_uint32 KRB5_CALLCONV +spnego_gss_compare_name( + OM_uint32 *minor_status, + const gss_name_t name1, + const gss_name_t name2, + int *name_equal) +{ + OM_uint32 status = GSS_S_COMPLETE; + dsyslog("Entering compare_name\n"); + + status = gss_compare_name(minor_status, name1, name2, name_equal); + + dsyslog("Leaving compare_name\n"); + return (status); +} + +/*ARGSUSED*/ +/*ARGSUSED*/ +OM_uint32 KRB5_CALLCONV +spnego_gss_display_name( + OM_uint32 *minor_status, + gss_name_t input_name, + gss_buffer_t output_name_buffer, + gss_OID *output_name_type) +{ + OM_uint32 status = GSS_S_COMPLETE; + dsyslog("Entering display_name\n"); + + status = gss_display_name(minor_status, input_name, + output_name_buffer, output_name_type); + + dsyslog("Leaving display_name\n"); + return (status); +} + + +/*ARGSUSED*/ +OM_uint32 KRB5_CALLCONV +spnego_gss_inquire_names_for_mech( + OM_uint32 *minor_status, + gss_OID mechanism, + gss_OID_set *name_types) +{ + OM_uint32 major, minor; + + dsyslog("Entering inquire_names_for_mech\n"); + /* + * We only know how to handle our own mechanism. + */ + if ((mechanism != GSS_C_NULL_OID) && + !g_OID_equal(gss_mech_spnego, mechanism)) { + *minor_status = 0; + return (GSS_S_FAILURE); + } + + major = gss_create_empty_oid_set(minor_status, name_types); + if (major == GSS_S_COMPLETE) { + /* Now add our members. */ + if (((major = gss_add_oid_set_member(minor_status, + (gss_OID) GSS_C_NT_USER_NAME, + name_types)) == GSS_S_COMPLETE) && + ((major = gss_add_oid_set_member(minor_status, + (gss_OID) GSS_C_NT_MACHINE_UID_NAME, + name_types)) == GSS_S_COMPLETE) && + ((major = gss_add_oid_set_member(minor_status, + (gss_OID) GSS_C_NT_STRING_UID_NAME, + name_types)) == GSS_S_COMPLETE)) { + major = gss_add_oid_set_member(minor_status, + (gss_OID) GSS_C_NT_HOSTBASED_SERVICE, + name_types); + } + + if (major != GSS_S_COMPLETE) + (void) gss_release_oid_set(&minor, name_types); + } + + dsyslog("Leaving inquire_names_for_mech\n"); + return (major); +} + +OM_uint32 KRB5_CALLCONV +spnego_gss_unwrap( + OM_uint32 *minor_status, + gss_ctx_id_t context_handle, + gss_buffer_t input_message_buffer, + gss_buffer_t output_message_buffer, + int *conf_state, + gss_qop_t *qop_state) +{ + OM_uint32 ret; + ret = gss_unwrap(minor_status, + context_handle, + input_message_buffer, + output_message_buffer, + conf_state, + qop_state); + + return (ret); +} + +OM_uint32 KRB5_CALLCONV +spnego_gss_wrap( + OM_uint32 *minor_status, + gss_ctx_id_t context_handle, + int conf_req_flag, + gss_qop_t qop_req, + gss_buffer_t input_message_buffer, + int *conf_state, + gss_buffer_t output_message_buffer) +{ + OM_uint32 ret; + ret = gss_wrap(minor_status, + context_handle, + conf_req_flag, + qop_req, + input_message_buffer, + conf_state, + output_message_buffer); + + return (ret); +} + +OM_uint32 KRB5_CALLCONV +spnego_gss_process_context_token( + OM_uint32 *minor_status, + const gss_ctx_id_t context_handle, + const gss_buffer_t token_buffer) +{ + OM_uint32 ret; + ret = gss_process_context_token(minor_status, + context_handle, + token_buffer); + + return (ret); +} + +OM_uint32 KRB5_CALLCONV +spnego_gss_delete_sec_context( + OM_uint32 *minor_status, + gss_ctx_id_t *context_handle, + gss_buffer_t output_token) +{ + OM_uint32 ret = GSS_S_COMPLETE; + spnego_gss_ctx_id_t *ctx = + (spnego_gss_ctx_id_t *)context_handle; + + *minor_status = 0; + + if (context_handle == NULL) + return (GSS_S_FAILURE); + + if (*ctx == NULL) + return (GSS_S_COMPLETE); + + /* + * If this is still an SPNEGO mech, release it locally. + */ + if ((*ctx)->magic_num == SPNEGO_MAGIC_ID) { + (void) gss_delete_sec_context(minor_status, + &(*ctx)->ctx_handle, + output_token); + (void) release_spnego_ctx(ctx); + } else { + ret = gss_delete_sec_context(minor_status, + context_handle, + output_token); + } + + return (ret); +} + +OM_uint32 KRB5_CALLCONV +spnego_gss_context_time( + OM_uint32 *minor_status, + const gss_ctx_id_t context_handle, + OM_uint32 *time_rec) +{ + OM_uint32 ret; + ret = gss_context_time(minor_status, + context_handle, + time_rec); + return (ret); +} +#ifndef LEAN_CLIENT +OM_uint32 KRB5_CALLCONV +spnego_gss_export_sec_context( + OM_uint32 *minor_status, + gss_ctx_id_t *context_handle, + gss_buffer_t interprocess_token) +{ + OM_uint32 ret; + ret = gss_export_sec_context(minor_status, + context_handle, + interprocess_token); + return (ret); +} + +OM_uint32 KRB5_CALLCONV +spnego_gss_import_sec_context( + OM_uint32 *minor_status, + const gss_buffer_t interprocess_token, + gss_ctx_id_t *context_handle) +{ + OM_uint32 ret; + ret = gss_import_sec_context(minor_status, + interprocess_token, + context_handle); + return (ret); +} +#endif /* LEAN_CLIENT */ + +OM_uint32 KRB5_CALLCONV +spnego_gss_inquire_context( + OM_uint32 *minor_status, + const gss_ctx_id_t context_handle, + gss_name_t *src_name, + gss_name_t *targ_name, + OM_uint32 *lifetime_rec, + gss_OID *mech_type, + OM_uint32 *ctx_flags, + int *locally_initiated, + int *opened) +{ + OM_uint32 ret = GSS_S_COMPLETE; + + ret = gss_inquire_context(minor_status, + context_handle, + src_name, + targ_name, + lifetime_rec, + mech_type, + ctx_flags, + locally_initiated, + opened); + + return (ret); +} + +OM_uint32 KRB5_CALLCONV +spnego_gss_wrap_size_limit( + OM_uint32 *minor_status, + const gss_ctx_id_t context_handle, + int conf_req_flag, + gss_qop_t qop_req, + OM_uint32 req_output_size, + OM_uint32 *max_input_size) +{ + OM_uint32 ret; + ret = gss_wrap_size_limit(minor_status, + context_handle, + conf_req_flag, + qop_req, + req_output_size, + max_input_size); + return (ret); +} + +OM_uint32 KRB5_CALLCONV +spnego_gss_get_mic( + OM_uint32 *minor_status, + const gss_ctx_id_t context_handle, + gss_qop_t qop_req, + const gss_buffer_t message_buffer, + gss_buffer_t message_token) +{ + OM_uint32 ret; + ret = gss_get_mic(minor_status, + context_handle, + qop_req, + message_buffer, + message_token); + return (ret); +} + +OM_uint32 KRB5_CALLCONV +spnego_gss_verify_mic( + OM_uint32 *minor_status, + const gss_ctx_id_t context_handle, + const gss_buffer_t msg_buffer, + const gss_buffer_t token_buffer, + gss_qop_t *qop_state) +{ + OM_uint32 ret; + ret = gss_verify_mic(minor_status, + context_handle, + msg_buffer, + token_buffer, + qop_state); + return (ret); +} + +OM_uint32 KRB5_CALLCONV +spnego_gss_inquire_sec_context_by_oid( + OM_uint32 *minor_status, + const gss_ctx_id_t context_handle, + const gss_OID desired_object, + gss_buffer_set_t *data_set) +{ + OM_uint32 ret; + ret = gss_inquire_sec_context_by_oid(minor_status, + context_handle, + desired_object, + data_set); + return (ret); +} + +OM_uint32 KRB5_CALLCONV +spnego_gss_inquire_cred_by_oid( + OM_uint32 *minor_status, + const gss_cred_id_t cred_handle, + const gss_OID desired_object, + gss_buffer_set_t *data_set) +{ + OM_uint32 ret; + spnego_gss_cred_id_t spcred = (spnego_gss_cred_id_t)cred_handle; + gss_cred_id_t mcred; + mcred = (spcred == NULL) ? GSS_C_NO_CREDENTIAL : spcred->mcred; + ret = gss_inquire_cred_by_oid(minor_status, + mcred, + desired_object, + data_set); + return (ret); +} + +OM_uint32 KRB5_CALLCONV +spnego_gss_set_cred_option( + OM_uint32 *minor_status, + gss_cred_id_t *cred_handle, + const gss_OID desired_object, + const gss_buffer_t value) +{ + OM_uint32 ret; + OM_uint32 tmp_minor_status; + spnego_gss_cred_id_t spcred = (spnego_gss_cred_id_t)*cred_handle; + gss_cred_id_t mcred; + + mcred = (spcred == NULL) ? GSS_C_NO_CREDENTIAL : spcred->mcred; + ret = gss_set_cred_option(minor_status, + &mcred, + desired_object, + value); + if (ret == GSS_S_COMPLETE && spcred == NULL) { + /* + * If the mechanism allocated a new credential handle, then + * we need to wrap it up in an SPNEGO credential handle. + */ + + spcred = malloc(sizeof(spnego_gss_cred_id_rec)); + if (spcred == NULL) { + gss_release_cred(&tmp_minor_status, &mcred); + *minor_status = ENOMEM; + return (GSS_S_FAILURE); + } + spcred->mcred = mcred; + spcred->neg_mechs = GSS_C_NULL_OID_SET; + *cred_handle = (gss_cred_id_t)spcred; + } + + return (ret); +} + +OM_uint32 KRB5_CALLCONV +spnego_gss_set_sec_context_option( + OM_uint32 *minor_status, + gss_ctx_id_t *context_handle, + const gss_OID desired_object, + const gss_buffer_t value) +{ + OM_uint32 ret; + ret = gss_set_sec_context_option(minor_status, + context_handle, + desired_object, + value); + return (ret); +} + +OM_uint32 KRB5_CALLCONV +spnego_gss_wrap_aead(OM_uint32 *minor_status, + gss_ctx_id_t context_handle, + int conf_req_flag, + gss_qop_t qop_req, + gss_buffer_t input_assoc_buffer, + gss_buffer_t input_payload_buffer, + int *conf_state, + gss_buffer_t output_message_buffer) +{ + OM_uint32 ret; + ret = gss_wrap_aead(minor_status, + context_handle, + conf_req_flag, + qop_req, + input_assoc_buffer, + input_payload_buffer, + conf_state, + output_message_buffer); + + return (ret); +} + +OM_uint32 KRB5_CALLCONV +spnego_gss_unwrap_aead(OM_uint32 *minor_status, + gss_ctx_id_t context_handle, + gss_buffer_t input_message_buffer, + gss_buffer_t input_assoc_buffer, + gss_buffer_t output_payload_buffer, + int *conf_state, + gss_qop_t *qop_state) +{ + OM_uint32 ret; + ret = gss_unwrap_aead(minor_status, + context_handle, + input_message_buffer, + input_assoc_buffer, + output_payload_buffer, + conf_state, + qop_state); + return (ret); +} + +OM_uint32 KRB5_CALLCONV +spnego_gss_wrap_iov(OM_uint32 *minor_status, + gss_ctx_id_t context_handle, + int conf_req_flag, + gss_qop_t qop_req, + int *conf_state, + gss_iov_buffer_desc *iov, + int iov_count) +{ + OM_uint32 ret; + ret = gss_wrap_iov(minor_status, + context_handle, + conf_req_flag, + qop_req, + conf_state, + iov, + iov_count); + return (ret); +} + +OM_uint32 KRB5_CALLCONV +spnego_gss_unwrap_iov(OM_uint32 *minor_status, + gss_ctx_id_t context_handle, + int *conf_state, + gss_qop_t *qop_state, + gss_iov_buffer_desc *iov, + int iov_count) +{ + OM_uint32 ret; + ret = gss_unwrap_iov(minor_status, + context_handle, + conf_state, + qop_state, + iov, + iov_count); + return (ret); +} + +OM_uint32 KRB5_CALLCONV +spnego_gss_wrap_iov_length(OM_uint32 *minor_status, + gss_ctx_id_t context_handle, + int conf_req_flag, + gss_qop_t qop_req, + int *conf_state, + gss_iov_buffer_desc *iov, + int iov_count) +{ + OM_uint32 ret; + ret = gss_wrap_iov_length(minor_status, + context_handle, + conf_req_flag, + qop_req, + conf_state, + iov, + iov_count); + return (ret); +} + + +OM_uint32 KRB5_CALLCONV +spnego_gss_complete_auth_token( + OM_uint32 *minor_status, + const gss_ctx_id_t context_handle, + gss_buffer_t input_message_buffer) +{ + OM_uint32 ret; + ret = gss_complete_auth_token(minor_status, + context_handle, + input_message_buffer); + return (ret); +} + +OM_uint32 KRB5_CALLCONV +spnego_gss_acquire_cred_impersonate_name(OM_uint32 *minor_status, + const gss_cred_id_t impersonator_cred_handle, + const gss_name_t desired_name, + OM_uint32 time_req, + gss_OID_set desired_mechs, + gss_cred_usage_t cred_usage, + gss_cred_id_t *output_cred_handle, + gss_OID_set *actual_mechs, + OM_uint32 *time_rec) +{ + OM_uint32 status; + gss_OID_set amechs = GSS_C_NULL_OID_SET; + spnego_gss_cred_id_t imp_spcred = NULL, out_spcred = NULL; + gss_cred_id_t imp_mcred, out_mcred; + + dsyslog("Entering spnego_gss_acquire_cred_impersonate_name\n"); + + if (actual_mechs) + *actual_mechs = NULL; + + if (time_rec) + *time_rec = 0; + + imp_spcred = (spnego_gss_cred_id_t)impersonator_cred_handle; + imp_mcred = imp_spcred ? imp_spcred->mcred : GSS_C_NO_CREDENTIAL; + if (desired_mechs == GSS_C_NO_OID_SET) { + status = gss_inquire_cred(minor_status, imp_mcred, NULL, NULL, + NULL, &amechs); + if (status != GSS_S_COMPLETE) + return status; + + desired_mechs = amechs; + } + + status = gss_acquire_cred_impersonate_name(minor_status, imp_mcred, + desired_name, time_req, + desired_mechs, cred_usage, + &out_mcred, actual_mechs, + time_rec); + + if (amechs != GSS_C_NULL_OID_SET) + (void) gss_release_oid_set(minor_status, &amechs); + + out_spcred = malloc(sizeof(spnego_gss_cred_id_rec)); + if (out_spcred == NULL) { + gss_release_cred(minor_status, &out_mcred); + *minor_status = ENOMEM; + return (GSS_S_FAILURE); + } + out_spcred->mcred = out_mcred; + out_spcred->neg_mechs = GSS_C_NULL_OID_SET; + *output_cred_handle = (gss_cred_id_t)out_spcred; + + dsyslog("Leaving spnego_gss_acquire_cred_impersonate_name\n"); + return (status); +} + +OM_uint32 KRB5_CALLCONV +spnego_gss_acquire_cred_with_password(OM_uint32 *minor_status, + const gss_name_t desired_name, + const gss_buffer_t password, + OM_uint32 time_req, + const gss_OID_set desired_mechs, + gss_cred_usage_t cred_usage, + gss_cred_id_t *output_cred_handle, + gss_OID_set *actual_mechs, + OM_uint32 *time_rec) +{ + OM_uint32 status, tmpmin; + gss_OID_set amechs = GSS_C_NULL_OID_SET; + gss_cred_id_t mcred = NULL; + spnego_gss_cred_id_t spcred = NULL; + + dsyslog("Entering spnego_gss_acquire_cred_with_password\n"); + + if (actual_mechs) + *actual_mechs = NULL; + + if (time_rec) + *time_rec = 0; + + status = get_available_mechs(minor_status, desired_name, + cred_usage, GSS_C_NO_CRED_STORE, + NULL, &amechs); + if (status != GSS_S_COMPLETE) + goto cleanup; + + status = gss_acquire_cred_with_password(minor_status, desired_name, + password, time_req, amechs, + cred_usage, &mcred, + actual_mechs, time_rec); + if (status != GSS_S_COMPLETE) + goto cleanup; + + spcred = malloc(sizeof(spnego_gss_cred_id_rec)); + if (spcred == NULL) { + *minor_status = ENOMEM; + status = GSS_S_FAILURE; + goto cleanup; + } + spcred->neg_mechs = GSS_C_NULL_OID_SET; + spcred->mcred = mcred; + mcred = GSS_C_NO_CREDENTIAL; + *output_cred_handle = (gss_cred_id_t)spcred; + +cleanup: + + (void) gss_release_oid_set(&tmpmin, &amechs); + (void) gss_release_cred(&tmpmin, &mcred); + + dsyslog("Leaving spnego_gss_acquire_cred_with_password\n"); + return (status); +} + +OM_uint32 KRB5_CALLCONV +spnego_gss_display_name_ext(OM_uint32 *minor_status, + gss_name_t name, + gss_OID display_as_name_type, + gss_buffer_t display_name) +{ + OM_uint32 ret; + ret = gss_display_name_ext(minor_status, + name, + display_as_name_type, + display_name); + return (ret); +} + + +OM_uint32 KRB5_CALLCONV +spnego_gss_inquire_name(OM_uint32 *minor_status, + gss_name_t name, + int *name_is_MN, + gss_OID *MN_mech, + gss_buffer_set_t *attrs) +{ + OM_uint32 ret; + ret = gss_inquire_name(minor_status, + name, + name_is_MN, + MN_mech, + attrs); + return (ret); +} + +OM_uint32 KRB5_CALLCONV +spnego_gss_get_name_attribute(OM_uint32 *minor_status, + gss_name_t name, + gss_buffer_t attr, + int *authenticated, + int *complete, + gss_buffer_t value, + gss_buffer_t display_value, + int *more) +{ + OM_uint32 ret; + ret = gss_get_name_attribute(minor_status, + name, + attr, + authenticated, + complete, + value, + display_value, + more); + return (ret); +} + +OM_uint32 KRB5_CALLCONV +spnego_gss_set_name_attribute(OM_uint32 *minor_status, + gss_name_t name, + int complete, + gss_buffer_t attr, + gss_buffer_t value) +{ + OM_uint32 ret; + ret = gss_set_name_attribute(minor_status, + name, + complete, + attr, + value); + return (ret); +} + +OM_uint32 KRB5_CALLCONV +spnego_gss_delete_name_attribute(OM_uint32 *minor_status, + gss_name_t name, + gss_buffer_t attr) +{ + OM_uint32 ret; + ret = gss_delete_name_attribute(minor_status, + name, + attr); + return (ret); +} + +OM_uint32 KRB5_CALLCONV +spnego_gss_export_name_composite(OM_uint32 *minor_status, + gss_name_t name, + gss_buffer_t exp_composite_name) +{ + OM_uint32 ret; + ret = gss_export_name_composite(minor_status, + name, + exp_composite_name); + return (ret); +} + +OM_uint32 KRB5_CALLCONV +spnego_gss_map_name_to_any(OM_uint32 *minor_status, + gss_name_t name, + int authenticated, + gss_buffer_t type_id, + gss_any_t *output) +{ + OM_uint32 ret; + ret = gss_map_name_to_any(minor_status, + name, + authenticated, + type_id, + output); + return (ret); +} + +OM_uint32 KRB5_CALLCONV +spnego_gss_release_any_name_mapping(OM_uint32 *minor_status, + gss_name_t name, + gss_buffer_t type_id, + gss_any_t *input) +{ + OM_uint32 ret; + ret = gss_release_any_name_mapping(minor_status, + name, + type_id, + input); + return (ret); +} + +OM_uint32 KRB5_CALLCONV +spnego_gss_pseudo_random(OM_uint32 *minor_status, + gss_ctx_id_t context, + int prf_key, + const gss_buffer_t prf_in, + ssize_t desired_output_len, + gss_buffer_t prf_out) +{ + OM_uint32 ret; + ret = gss_pseudo_random(minor_status, + context, + prf_key, + prf_in, + desired_output_len, + prf_out); + return (ret); +} + +OM_uint32 KRB5_CALLCONV +spnego_gss_set_neg_mechs(OM_uint32 *minor_status, + gss_cred_id_t cred_handle, + const gss_OID_set mech_list) +{ + OM_uint32 ret; + spnego_gss_cred_id_t spcred = (spnego_gss_cred_id_t)cred_handle; + + /* Store mech_list in spcred for use in negotiation logic. */ + gss_release_oid_set(minor_status, &spcred->neg_mechs); + ret = generic_gss_copy_oid_set(minor_status, mech_list, + &spcred->neg_mechs); + return (ret); +} + +#define SPNEGO_SASL_NAME "SPNEGO" +#define SPNEGO_SASL_NAME_LEN (sizeof(SPNEGO_SASL_NAME) - 1) + +OM_uint32 KRB5_CALLCONV +spnego_gss_inquire_mech_for_saslname(OM_uint32 *minor_status, + const gss_buffer_t sasl_mech_name, + gss_OID *mech_type) +{ + if (sasl_mech_name->length == SPNEGO_SASL_NAME_LEN && + memcmp(sasl_mech_name->value, SPNEGO_SASL_NAME, + SPNEGO_SASL_NAME_LEN) == 0) { + if (mech_type != NULL) + *mech_type = (gss_OID)gss_mech_spnego; + return (GSS_S_COMPLETE); + } + + return (GSS_S_BAD_MECH); +} + +OM_uint32 KRB5_CALLCONV +spnego_gss_inquire_saslname_for_mech(OM_uint32 *minor_status, + const gss_OID desired_mech, + gss_buffer_t sasl_mech_name, + gss_buffer_t mech_name, + gss_buffer_t mech_description) +{ + *minor_status = 0; + + if (!g_OID_equal(desired_mech, gss_mech_spnego)) + return (GSS_S_BAD_MECH); + + if (!g_make_string_buffer(SPNEGO_SASL_NAME, sasl_mech_name) || + !g_make_string_buffer("spnego", mech_name) || + !g_make_string_buffer("Simple and Protected GSS-API " + "Negotiation Mechanism", mech_description)) + goto fail; + + return (GSS_S_COMPLETE); + +fail: + *minor_status = ENOMEM; + return (GSS_S_FAILURE); +} + +OM_uint32 KRB5_CALLCONV +spnego_gss_inquire_attrs_for_mech(OM_uint32 *minor_status, + gss_const_OID mech, + gss_OID_set *mech_attrs, + gss_OID_set *known_mech_attrs) +{ + OM_uint32 major, tmpMinor; + + /* known_mech_attrs is handled by mechglue */ + *minor_status = 0; + + if (mech_attrs == NULL) + return (GSS_S_COMPLETE); + + major = gss_create_empty_oid_set(minor_status, mech_attrs); + if (GSS_ERROR(major)) + goto cleanup; + +#define MA_SUPPORTED(ma) do { \ + major = gss_add_oid_set_member(minor_status, \ + (gss_OID)ma, mech_attrs); \ + if (GSS_ERROR(major)) \ + goto cleanup; \ + } while (0) + + MA_SUPPORTED(GSS_C_MA_MECH_NEGO); + MA_SUPPORTED(GSS_C_MA_ITOK_FRAMED); + +cleanup: + if (GSS_ERROR(major)) + gss_release_oid_set(&tmpMinor, mech_attrs); + + return (major); +} + +OM_uint32 KRB5_CALLCONV +spnego_gss_export_cred(OM_uint32 *minor_status, + gss_cred_id_t cred_handle, + gss_buffer_t token) +{ + spnego_gss_cred_id_t spcred = (spnego_gss_cred_id_t)cred_handle; + + return (gss_export_cred(minor_status, spcred->mcred, token)); +} + +OM_uint32 KRB5_CALLCONV +spnego_gss_import_cred(OM_uint32 *minor_status, + gss_buffer_t token, + gss_cred_id_t *cred_handle) +{ + OM_uint32 ret; + spnego_gss_cred_id_t spcred; + gss_cred_id_t mcred; + + ret = gss_import_cred(minor_status, token, &mcred); + if (GSS_ERROR(ret)) + return (ret); + spcred = malloc(sizeof(*spcred)); + if (spcred == NULL) { + gss_release_cred(minor_status, &mcred); + *minor_status = ENOMEM; + return (GSS_S_FAILURE); + } + spcred->mcred = mcred; + spcred->neg_mechs = GSS_C_NULL_OID_SET; + *cred_handle = (gss_cred_id_t)spcred; + return (ret); +} + +OM_uint32 KRB5_CALLCONV +spnego_gss_get_mic_iov(OM_uint32 *minor_status, gss_ctx_id_t context_handle, + gss_qop_t qop_req, gss_iov_buffer_desc *iov, + int iov_count) +{ + return gss_get_mic_iov(minor_status, context_handle, qop_req, iov, + iov_count); +} + +OM_uint32 KRB5_CALLCONV +spnego_gss_verify_mic_iov(OM_uint32 *minor_status, gss_ctx_id_t context_handle, + gss_qop_t *qop_state, gss_iov_buffer_desc *iov, + int iov_count) +{ + return gss_verify_mic_iov(minor_status, context_handle, qop_state, iov, + iov_count); +} + +OM_uint32 KRB5_CALLCONV +spnego_gss_get_mic_iov_length(OM_uint32 *minor_status, + gss_ctx_id_t context_handle, gss_qop_t qop_req, + gss_iov_buffer_desc *iov, int iov_count) +{ + return gss_get_mic_iov_length(minor_status, context_handle, qop_req, iov, + iov_count); +} + +/* + * We will release everything but the ctx_handle so that it + * can be passed back to init/accept context. This routine should + * not be called until after the ctx_handle memory is assigned to + * the supplied context handle from init/accept context. + */ +static void +release_spnego_ctx(spnego_gss_ctx_id_t *ctx) +{ + spnego_gss_ctx_id_t context; + OM_uint32 minor_stat; + context = *ctx; + + if (context != NULL) { + (void) gss_release_buffer(&minor_stat, + &context->DER_mechTypes); + + (void) gss_release_oid_set(&minor_stat, &context->mech_set); + + (void) gss_release_name(&minor_stat, &context->internal_name); + + if (context->optionStr != NULL) { + free(context->optionStr); + context->optionStr = NULL; + } + free(context); + *ctx = NULL; + } +} + +/* + * Can't use gss_indicate_mechs by itself to get available mechs for + * SPNEGO because it will also return the SPNEGO mech and we do not + * want to consider SPNEGO as an available security mech for + * negotiation. For this reason, get_available_mechs will return + * all available mechs except SPNEGO. + * + * If a ptr to a creds list is given, this function will attempt + * to acquire creds for the creds given and trim the list of + * returned mechanisms to only those for which creds are valid. + * + */ +static OM_uint32 +get_available_mechs(OM_uint32 *minor_status, + gss_name_t name, gss_cred_usage_t usage, + gss_const_key_value_set_t cred_store, + gss_cred_id_t *creds, gss_OID_set *rmechs) +{ + unsigned int i; + int found = 0; + OM_uint32 major_status = GSS_S_COMPLETE, tmpmin; + gss_OID_set mechs, goodmechs; + + major_status = gss_indicate_mechs(minor_status, &mechs); + + if (major_status != GSS_S_COMPLETE) { + return (major_status); + } + + major_status = gss_create_empty_oid_set(minor_status, rmechs); + + if (major_status != GSS_S_COMPLETE) { + (void) gss_release_oid_set(minor_status, &mechs); + return (major_status); + } + + for (i = 0; i < mechs->count && major_status == GSS_S_COMPLETE; i++) { + if ((mechs->elements[i].length + != spnego_mechanism.mech_type.length) || + memcmp(mechs->elements[i].elements, + spnego_mechanism.mech_type.elements, + spnego_mechanism.mech_type.length)) { + + major_status = gss_add_oid_set_member(minor_status, + &mechs->elements[i], + rmechs); + if (major_status == GSS_S_COMPLETE) + found++; + } + } + + /* + * If the caller wanted a list of creds returned, + * trim the list of mechanisms down to only those + * for which the creds are valid. + */ + if (found > 0 && major_status == GSS_S_COMPLETE && creds != NULL) { + major_status = gss_acquire_cred_from(minor_status, name, + GSS_C_INDEFINITE, + *rmechs, usage, + cred_store, creds, + &goodmechs, NULL); + + /* + * Drop the old list in favor of the new + * "trimmed" list. + */ + (void) gss_release_oid_set(&tmpmin, rmechs); + if (major_status == GSS_S_COMPLETE) { + (void) gssint_copy_oid_set(&tmpmin, + goodmechs, rmechs); + (void) gss_release_oid_set(&tmpmin, &goodmechs); + } + } + + (void) gss_release_oid_set(&tmpmin, &mechs); + if (found == 0 || major_status != GSS_S_COMPLETE) { + *minor_status = ERR_SPNEGO_NO_MECHS_AVAILABLE; + map_errcode(minor_status); + if (major_status == GSS_S_COMPLETE) + major_status = GSS_S_FAILURE; + } + + return (major_status); +} + +/* + * Return a list of mechanisms we are willing to negotiate for a credential, + * taking into account the mech set provided with gss_set_neg_mechs if it + * exists. + */ +static OM_uint32 +get_negotiable_mechs(OM_uint32 *minor_status, spnego_gss_cred_id_t spcred, + gss_cred_usage_t usage, gss_OID_set *rmechs) +{ + OM_uint32 ret, tmpmin; + gss_cred_id_t creds = GSS_C_NO_CREDENTIAL, *credptr; + gss_OID_set cred_mechs = GSS_C_NULL_OID_SET; + gss_OID_set intersect_mechs = GSS_C_NULL_OID_SET; + unsigned int i; + int present; + + if (spcred == NULL) { + /* + * The default credentials were supplied. Return a list of all + * available mechs except SPNEGO. When initiating, trim this + * list to mechs we can acquire credentials for. + */ + credptr = (usage == GSS_C_INITIATE) ? &creds : NULL; + ret = get_available_mechs(minor_status, GSS_C_NO_NAME, usage, + GSS_C_NO_CRED_STORE, credptr, + rmechs); + gss_release_cred(&tmpmin, &creds); + return (ret); + } + + /* Get the list of mechs in the mechglue cred. */ + ret = gss_inquire_cred(minor_status, spcred->mcred, NULL, NULL, NULL, + &cred_mechs); + if (ret != GSS_S_COMPLETE) + return (ret); + + if (spcred->neg_mechs == GSS_C_NULL_OID_SET) { + /* gss_set_neg_mechs was never called; return cred_mechs. */ + *rmechs = cred_mechs; + *minor_status = 0; + return (GSS_S_COMPLETE); + } + + /* Compute the intersection of cred_mechs and spcred->neg_mechs, + * preserving the order in spcred->neg_mechs. */ + ret = gss_create_empty_oid_set(minor_status, &intersect_mechs); + if (ret != GSS_S_COMPLETE) { + gss_release_oid_set(&tmpmin, &cred_mechs); + return (ret); + } + + for (i = 0; i < spcred->neg_mechs->count; i++) { + gss_test_oid_set_member(&tmpmin, + &spcred->neg_mechs->elements[i], + cred_mechs, &present); + if (!present) + continue; + ret = gss_add_oid_set_member(minor_status, + &spcred->neg_mechs->elements[i], + &intersect_mechs); + if (ret != GSS_S_COMPLETE) + break; + } + + gss_release_oid_set(&tmpmin, &cred_mechs); + if (intersect_mechs->count == 0 || ret != GSS_S_COMPLETE) { + gss_release_oid_set(&tmpmin, &intersect_mechs); + *minor_status = ERR_SPNEGO_NO_MECHS_AVAILABLE; + map_errcode(minor_status); + return (GSS_S_FAILURE); + } + + *rmechs = intersect_mechs; + *minor_status = 0; + return (GSS_S_COMPLETE); +} + +/* following are token creation and reading routines */ + +/* + * If buff_in is not pointing to a MECH_OID, then return NULL and do not + * advance the buffer, otherwise, decode the mech_oid from the buffer and + * place in gss_OID. + */ +static gss_OID +get_mech_oid(OM_uint32 *minor_status, unsigned char **buff_in, size_t length) +{ + OM_uint32 status; + gss_OID_desc toid; + gss_OID mech_out = NULL; + unsigned char *start, *end; + + if (length < 1 || **buff_in != MECH_OID) + return (NULL); + + start = *buff_in; + end = start + length; + + (*buff_in)++; + toid.length = *(*buff_in)++; + + if ((*buff_in + toid.length) > end) + return (NULL); + + toid.elements = *buff_in; + *buff_in += toid.length; + + status = generic_gss_copy_oid(minor_status, &toid, &mech_out); + + if (status != GSS_S_COMPLETE) { + map_errcode(minor_status); + mech_out = NULL; + } + + return (mech_out); +} + +/* + * der encode the given mechanism oid into buf_out, advancing the + * buffer pointer. + */ + +static int +put_mech_oid(unsigned char **buf_out, gss_OID_const mech, unsigned int buflen) +{ + if (buflen < mech->length + 2) + return (-1); + *(*buf_out)++ = MECH_OID; + *(*buf_out)++ = (unsigned char) mech->length; + memcpy(*buf_out, mech->elements, mech->length); + *buf_out += mech->length; + return (0); +} + +/* + * verify that buff_in points to an octet string, if it does not, + * return NULL and don't advance the pointer. If it is an octet string + * decode buff_in into a gss_buffer_t and return it, advancing the + * buffer pointer. + */ +static gss_buffer_t +get_input_token(unsigned char **buff_in, unsigned int buff_length) +{ + gss_buffer_t input_token; + unsigned int len; + + if (g_get_tag_and_length(buff_in, OCTET_STRING, buff_length, &len) < 0) + return (NULL); + + input_token = (gss_buffer_t)malloc(sizeof (gss_buffer_desc)); + if (input_token == NULL) + return (NULL); + + input_token->length = len; + input_token->value = gssalloc_malloc(input_token->length); + + if (input_token->value == NULL) { + free(input_token); + return (NULL); + } + + (void) memcpy(input_token->value, *buff_in, input_token->length); + *buff_in += input_token->length; + return (input_token); +} + +/* + * verify that the input token length is not 0. If it is, just return. + * If the token length is greater than 0, der encode as an octet string + * and place in buf_out, advancing buf_out. + */ + +static int +put_input_token(unsigned char **buf_out, gss_buffer_t input_token, + unsigned int buflen) +{ + int ret; + + /* if token length is 0, we do not want to send */ + if (input_token->length == 0) + return (0); + + if (input_token->length > buflen) + return (-1); + + *(*buf_out)++ = OCTET_STRING; + if ((ret = gssint_put_der_length(input_token->length, buf_out, + input_token->length))) + return (ret); + TWRITE_STR(*buf_out, input_token->value, input_token->length); + return (0); +} + +/* + * verify that buff_in points to a sequence of der encoding. The mech + * set is the only sequence of encoded object in the token, so if it is + * a sequence of encoding, decode the mechset into a gss_OID_set and + * return it, advancing the buffer pointer. + */ +static gss_OID_set +get_mech_set(OM_uint32 *minor_status, unsigned char **buff_in, + unsigned int buff_length) +{ + gss_OID_set returned_mechSet; + OM_uint32 major_status; + int length; + unsigned int bytes; + OM_uint32 set_length; + unsigned char *start; + int i; + + if (**buff_in != SEQUENCE_OF) + return (NULL); + + start = *buff_in; + (*buff_in)++; + + length = gssint_get_der_length(buff_in, buff_length, &bytes); + if (length < 0 || buff_length - bytes < (unsigned int)length) + return NULL; + + major_status = gss_create_empty_oid_set(minor_status, + &returned_mechSet); + if (major_status != GSS_S_COMPLETE) + return (NULL); + + for (set_length = 0, i = 0; set_length < (unsigned int)length; i++) { + gss_OID_desc *temp = get_mech_oid(minor_status, buff_in, + buff_length - (*buff_in - start)); + if (temp == NULL) + break; + + major_status = gss_add_oid_set_member(minor_status, + temp, &returned_mechSet); + if (major_status == GSS_S_COMPLETE) { + set_length += returned_mechSet->elements[i].length +2; + if (generic_gss_release_oid(minor_status, &temp)) + map_errcode(minor_status); + } + } + + return (returned_mechSet); +} + +/* + * Encode mechSet into buf. + */ +static int +put_mech_set(gss_OID_set mechSet, gss_buffer_t buf) +{ + unsigned char *ptr; + unsigned int i; + unsigned int tlen, ilen; + + tlen = ilen = 0; + for (i = 0; i < mechSet->count; i++) { + /* + * 0x06 [DER LEN] [OID] + */ + ilen += 1 + + gssint_der_length_size(mechSet->elements[i].length) + + mechSet->elements[i].length; + } + /* + * 0x30 [DER LEN] + */ + tlen = 1 + gssint_der_length_size(ilen) + ilen; + ptr = gssalloc_malloc(tlen); + if (ptr == NULL) + return -1; + + buf->value = ptr; + buf->length = tlen; +#define REMAIN (buf->length - ((unsigned char *)buf->value - ptr)) + + *ptr++ = SEQUENCE_OF; + if (gssint_put_der_length(ilen, &ptr, REMAIN) < 0) + return -1; + for (i = 0; i < mechSet->count; i++) { + if (put_mech_oid(&ptr, &mechSet->elements[i], REMAIN) < 0) { + return -1; + } + } + return 0; +#undef REMAIN +} + +/* + * Verify that buff_in is pointing to a BIT_STRING with the correct + * length and padding for the req_flags. If it is, decode req_flags + * and return them, otherwise, return NULL. + */ +static OM_uint32 +get_req_flags(unsigned char **buff_in, OM_uint32 bodysize, + OM_uint32 *req_flags) +{ + unsigned int len; + + if (**buff_in != (CONTEXT | 0x01)) + return (0); + + if (g_get_tag_and_length(buff_in, (CONTEXT | 0x01), + bodysize, &len) < 0) + return GSS_S_DEFECTIVE_TOKEN; + + if (*(*buff_in)++ != BIT_STRING) + return GSS_S_DEFECTIVE_TOKEN; + + if (*(*buff_in)++ != BIT_STRING_LENGTH) + return GSS_S_DEFECTIVE_TOKEN; + + if (*(*buff_in)++ != BIT_STRING_PADDING) + return GSS_S_DEFECTIVE_TOKEN; + + *req_flags = (OM_uint32) (*(*buff_in)++ >> 1); + return (0); +} + +static OM_uint32 +get_negTokenInit(OM_uint32 *minor_status, + gss_buffer_t buf, + gss_buffer_t der_mechSet, + gss_OID_set *mechSet, + OM_uint32 *req_flags, + gss_buffer_t *mechtok, + gss_buffer_t *mechListMIC) +{ + OM_uint32 err; + unsigned char *ptr, *bufstart; + unsigned int len; + gss_buffer_desc tmpbuf; + + *minor_status = 0; + der_mechSet->length = 0; + der_mechSet->value = NULL; + *mechSet = GSS_C_NO_OID_SET; + *req_flags = 0; + *mechtok = *mechListMIC = GSS_C_NO_BUFFER; + + ptr = bufstart = buf->value; + if ((buf->length - (ptr - bufstart)) > INT_MAX) + return GSS_S_FAILURE; +#define REMAIN (buf->length - (ptr - bufstart)) + + err = g_verify_token_header(gss_mech_spnego, + &len, &ptr, 0, REMAIN); + if (err) { + *minor_status = err; + map_errcode(minor_status); + return GSS_S_FAILURE; + } + *minor_status = g_verify_neg_token_init(&ptr, REMAIN); + if (*minor_status) { + map_errcode(minor_status); + return GSS_S_FAILURE; + } + + /* alias into input_token */ + tmpbuf.value = ptr; + tmpbuf.length = REMAIN; + *mechSet = get_mech_set(minor_status, &ptr, REMAIN); + if (*mechSet == NULL) + return GSS_S_FAILURE; + + tmpbuf.length = ptr - (unsigned char *)tmpbuf.value; + der_mechSet->value = gssalloc_malloc(tmpbuf.length); + if (der_mechSet->value == NULL) + return GSS_S_FAILURE; + memcpy(der_mechSet->value, tmpbuf.value, tmpbuf.length); + der_mechSet->length = tmpbuf.length; + + err = get_req_flags(&ptr, REMAIN, req_flags); + if (err != GSS_S_COMPLETE) { + return err; + } + if (g_get_tag_and_length(&ptr, (CONTEXT | 0x02), + REMAIN, &len) >= 0) { + *mechtok = get_input_token(&ptr, len); + if (*mechtok == GSS_C_NO_BUFFER) { + return GSS_S_FAILURE; + } + } + if (g_get_tag_and_length(&ptr, (CONTEXT | 0x03), + REMAIN, &len) >= 0) { + *mechListMIC = get_input_token(&ptr, len); + if (*mechListMIC == GSS_C_NO_BUFFER) { + return GSS_S_FAILURE; + } + } + return GSS_S_COMPLETE; +#undef REMAIN +} + +static OM_uint32 +get_negTokenResp(OM_uint32 *minor_status, + unsigned char *buf, unsigned int buflen, + OM_uint32 *negState, + gss_OID *supportedMech, + gss_buffer_t *responseToken, + gss_buffer_t *mechListMIC) +{ + unsigned char *ptr, *bufstart; + unsigned int len; + int tmplen; + unsigned int tag, bytes; + + *negState = ACCEPT_DEFECTIVE_TOKEN; + *supportedMech = GSS_C_NO_OID; + *responseToken = *mechListMIC = GSS_C_NO_BUFFER; + ptr = bufstart = buf; +#define REMAIN (buflen - (ptr - bufstart)) + + if (g_get_tag_and_length(&ptr, (CONTEXT | 0x01), REMAIN, &len) < 0) + return GSS_S_DEFECTIVE_TOKEN; + if (*ptr++ == SEQUENCE) { + tmplen = gssint_get_der_length(&ptr, REMAIN, &bytes); + if (tmplen < 0 || REMAIN < (unsigned int)tmplen) + return GSS_S_DEFECTIVE_TOKEN; + } + if (REMAIN < 1) + tag = 0; + else + tag = *ptr++; + + if (tag == CONTEXT) { + tmplen = gssint_get_der_length(&ptr, REMAIN, &bytes); + if (tmplen < 0 || REMAIN < (unsigned int)tmplen) + return GSS_S_DEFECTIVE_TOKEN; + + if (g_get_tag_and_length(&ptr, ENUMERATED, + REMAIN, &len) < 0) + return GSS_S_DEFECTIVE_TOKEN; + + if (len != ENUMERATION_LENGTH) + return GSS_S_DEFECTIVE_TOKEN; + + if (REMAIN < 1) + return GSS_S_DEFECTIVE_TOKEN; + *negState = *ptr++; + + if (REMAIN < 1) + tag = 0; + else + tag = *ptr++; + } + if (tag == (CONTEXT | 0x01)) { + tmplen = gssint_get_der_length(&ptr, REMAIN, &bytes); + if (tmplen < 0 || REMAIN < (unsigned int)tmplen) + return GSS_S_DEFECTIVE_TOKEN; + + *supportedMech = get_mech_oid(minor_status, &ptr, REMAIN); + if (*supportedMech == GSS_C_NO_OID) + return GSS_S_DEFECTIVE_TOKEN; + + if (REMAIN < 1) + tag = 0; + else + tag = *ptr++; + } + if (tag == (CONTEXT | 0x02)) { + tmplen = gssint_get_der_length(&ptr, REMAIN, &bytes); + if (tmplen < 0 || REMAIN < (unsigned int)tmplen) + return GSS_S_DEFECTIVE_TOKEN; + + *responseToken = get_input_token(&ptr, REMAIN); + if (*responseToken == GSS_C_NO_BUFFER) + return GSS_S_DEFECTIVE_TOKEN; + + if (REMAIN < 1) + tag = 0; + else + tag = *ptr++; + } + if (tag == (CONTEXT | 0x03)) { + tmplen = gssint_get_der_length(&ptr, REMAIN, &bytes); + if (tmplen < 0 || REMAIN < (unsigned int)tmplen) + return GSS_S_DEFECTIVE_TOKEN; + + *mechListMIC = get_input_token(&ptr, REMAIN); + if (*mechListMIC == GSS_C_NO_BUFFER) + return GSS_S_DEFECTIVE_TOKEN; + + /* Handle Windows 2000 duplicate response token */ + if (*responseToken && + ((*responseToken)->length == (*mechListMIC)->length) && + !memcmp((*responseToken)->value, (*mechListMIC)->value, + (*responseToken)->length)) { + OM_uint32 tmpmin; + + gss_release_buffer(&tmpmin, *mechListMIC); + free(*mechListMIC); + *mechListMIC = NULL; + } + } + return GSS_S_COMPLETE; +#undef REMAIN +} + +/* + * der encode the passed negResults as an ENUMERATED type and + * place it in buf_out, advancing the buffer. + */ + +static int +put_negResult(unsigned char **buf_out, OM_uint32 negResult, + unsigned int buflen) +{ + if (buflen < 3) + return (-1); + *(*buf_out)++ = ENUMERATED; + *(*buf_out)++ = ENUMERATION_LENGTH; + *(*buf_out)++ = (unsigned char) negResult; + return (0); +} + +/* + * This routine compares the recieved mechset to the mechset that + * this server can support. It looks sequentially through the mechset + * and the first one that matches what the server can support is + * chosen as the negotiated mechanism. If one is found, negResult + * is set to ACCEPT_INCOMPLETE if it's the first mech, REQUEST_MIC if + * it's not the first mech, otherwise we return NULL and negResult + * is set to REJECT. The returned pointer is an alias into + * supported->elements and should not be freed. + * + * NOTE: There is currently no way to specify a preference order of + * mechanisms supported by the acceptor. + */ +static gss_OID +negotiate_mech(gss_OID_set supported, gss_OID_set received, + OM_uint32 *negResult) +{ + size_t i, j; + + for (i = 0; i < received->count; i++) { + gss_OID mech_oid = &received->elements[i]; + + /* Accept wrong mechanism OID from MS clients */ + if (g_OID_equal(mech_oid, &gss_mech_krb5_wrong_oid)) + mech_oid = (gss_OID)&gss_mech_krb5_oid; + + for (j = 0; j < supported->count; j++) { + if (g_OID_equal(mech_oid, &supported->elements[j])) { + *negResult = (i == 0) ? ACCEPT_INCOMPLETE : + REQUEST_MIC; + return &supported->elements[j]; + } + } + } + *negResult = REJECT; + return (NULL); +} + +/* + * the next two routines make a token buffer suitable for + * spnego_gss_display_status. These currently take the string + * in name and place it in the token. Eventually, if + * spnego_gss_display_status returns valid error messages, + * these routines will be changes to return the error string. + */ +static spnego_token_t +make_spnego_token(char *name) +{ + return (spnego_token_t)strdup(name); +} + +static gss_buffer_desc +make_err_msg(char *name) +{ + gss_buffer_desc buffer; + + if (name == NULL) { + buffer.length = 0; + buffer.value = NULL; + } else { + buffer.length = strlen(name)+1; + buffer.value = make_spnego_token(name); + } + + return (buffer); +} + +/* + * Create the client side spnego token passed back to gss_init_sec_context + * and eventually up to the application program and over to the server. + * + * Use DER rules, definite length method per RFC 2478 + */ +static int +make_spnego_tokenInit_msg(spnego_gss_ctx_id_t spnego_ctx, + int negHintsCompat, + gss_buffer_t mechListMIC, OM_uint32 req_flags, + gss_buffer_t data, send_token_flag sendtoken, + gss_buffer_t outbuf) +{ + int ret = 0; + unsigned int tlen, dataLen = 0; + unsigned int negTokenInitSize = 0; + unsigned int negTokenInitSeqSize = 0; + unsigned int negTokenInitContSize = 0; + unsigned int rspTokenSize = 0; + unsigned int mechListTokenSize = 0; + unsigned int micTokenSize = 0; + unsigned char *t; + unsigned char *ptr; + + if (outbuf == GSS_C_NO_BUFFER) + return (-1); + + outbuf->length = 0; + outbuf->value = NULL; + + /* calculate the data length */ + + /* + * 0xa0 [DER LEN] [mechTypes] + */ + mechListTokenSize = 1 + + gssint_der_length_size(spnego_ctx->DER_mechTypes.length) + + spnego_ctx->DER_mechTypes.length; + dataLen += mechListTokenSize; + + /* + * If a token from gss_init_sec_context exists, + * add the length of the token + the ASN.1 overhead + */ + if (data != NULL) { + /* + * Encoded in final output as: + * 0xa2 [DER LEN] 0x04 [DER LEN] [DATA] + * -----s--------|--------s2---------- + */ + rspTokenSize = 1 + + gssint_der_length_size(data->length) + + data->length; + dataLen += 1 + gssint_der_length_size(rspTokenSize) + + rspTokenSize; + } + + if (mechListMIC) { + /* + * Encoded in final output as: + * 0xa3 [DER LEN] 0x04 [DER LEN] [DATA] + * --s-- -----tlen------------ + */ + micTokenSize = 1 + + gssint_der_length_size(mechListMIC->length) + + mechListMIC->length; + dataLen += 1 + + gssint_der_length_size(micTokenSize) + + micTokenSize; + } + + /* + * Add size of DER encoding + * [ SEQUENCE { MechTypeList | ReqFLags | Token | mechListMIC } ] + * 0x30 [DER_LEN] [data] + * + */ + negTokenInitContSize = dataLen; + negTokenInitSeqSize = 1 + gssint_der_length_size(dataLen) + dataLen; + dataLen = negTokenInitSeqSize; + + /* + * negTokenInitSize indicates the bytes needed to + * hold the ASN.1 encoding of the entire NegTokenInit + * SEQUENCE. + * 0xa0 [DER_LEN] + data + * + */ + negTokenInitSize = 1 + + gssint_der_length_size(negTokenInitSeqSize) + + negTokenInitSeqSize; + + tlen = g_token_size(gss_mech_spnego, negTokenInitSize); + + t = (unsigned char *) gssalloc_malloc(tlen); + + if (t == NULL) { + return (-1); + } + + ptr = t; + + /* create the message */ + if ((ret = g_make_token_header(gss_mech_spnego, negTokenInitSize, + &ptr, tlen))) + goto errout; + + *ptr++ = CONTEXT; /* NegotiationToken identifier */ + if ((ret = gssint_put_der_length(negTokenInitSeqSize, &ptr, tlen))) + goto errout; + + *ptr++ = SEQUENCE; + if ((ret = gssint_put_der_length(negTokenInitContSize, &ptr, + tlen - (int)(ptr-t)))) + goto errout; + + *ptr++ = CONTEXT | 0x00; /* MechTypeList identifier */ + if ((ret = gssint_put_der_length(spnego_ctx->DER_mechTypes.length, + &ptr, tlen - (int)(ptr-t)))) + goto errout; + + /* We already encoded the MechSetList */ + (void) memcpy(ptr, spnego_ctx->DER_mechTypes.value, + spnego_ctx->DER_mechTypes.length); + + ptr += spnego_ctx->DER_mechTypes.length; + + if (data != NULL) { + *ptr++ = CONTEXT | 0x02; + if ((ret = gssint_put_der_length(rspTokenSize, + &ptr, tlen - (int)(ptr - t)))) + goto errout; + + if ((ret = put_input_token(&ptr, data, + tlen - (int)(ptr - t)))) + goto errout; + } + + if (mechListMIC != GSS_C_NO_BUFFER) { + *ptr++ = CONTEXT | 0x03; + if ((ret = gssint_put_der_length(micTokenSize, + &ptr, tlen - (int)(ptr - t)))) + goto errout; + + if (negHintsCompat) { + ret = put_neg_hints(&ptr, mechListMIC, + tlen - (int)(ptr - t)); + if (ret) + goto errout; + } else if ((ret = put_input_token(&ptr, mechListMIC, + tlen - (int)(ptr - t)))) + goto errout; + } + +errout: + if (ret != 0) { + if (t) + free(t); + t = NULL; + tlen = 0; + } + outbuf->length = tlen; + outbuf->value = (void *) t; + + return (ret); +} + +/* + * create the server side spnego token passed back to + * gss_accept_sec_context and eventually up to the application program + * and over to the client. + */ +static int +make_spnego_tokenTarg_msg(OM_uint32 status, gss_OID mech_wanted, + gss_buffer_t data, gss_buffer_t mechListMIC, + send_token_flag sendtoken, + gss_buffer_t outbuf) +{ + unsigned int tlen = 0; + unsigned int ret = 0; + unsigned int NegTokenTargSize = 0; + unsigned int NegTokenSize = 0; + unsigned int rspTokenSize = 0; + unsigned int micTokenSize = 0; + unsigned int dataLen = 0; + unsigned char *t; + unsigned char *ptr; + + if (outbuf == GSS_C_NO_BUFFER) + return (GSS_S_DEFECTIVE_TOKEN); + if (sendtoken == INIT_TOKEN_SEND && mech_wanted == GSS_C_NO_OID) + return (GSS_S_DEFECTIVE_TOKEN); + + outbuf->length = 0; + outbuf->value = NULL; + + /* + * ASN.1 encoding of the negResult + * ENUMERATED type is 3 bytes + * ENUMERATED TAG, Length, Value, + * Plus 2 bytes for the CONTEXT id and length. + */ + dataLen = 5; + + /* + * calculate data length + * + * If this is the initial token, include length of + * mech_type and the negotiation result fields. + */ + if (sendtoken == INIT_TOKEN_SEND) { + int mechlistTokenSize; + /* + * 1 byte for the CONTEXT ID(0xa0), + * 1 byte for the OID ID(0x06) + * 1 byte for OID Length field + * Plus the rest... (OID Length, OID value) + */ + mechlistTokenSize = 3 + mech_wanted->length + + gssint_der_length_size(mech_wanted->length); + + dataLen += mechlistTokenSize; + } + if (data != NULL && data->length > 0) { + /* Length of the inner token */ + rspTokenSize = 1 + gssint_der_length_size(data->length) + + data->length; + + dataLen += rspTokenSize; + + /* Length of the outer token */ + dataLen += 1 + gssint_der_length_size(rspTokenSize); + } + if (mechListMIC != NULL) { + + /* Length of the inner token */ + micTokenSize = 1 + gssint_der_length_size(mechListMIC->length) + + mechListMIC->length; + + dataLen += micTokenSize; + + /* Length of the outer token */ + dataLen += 1 + gssint_der_length_size(micTokenSize); + } + /* + * Add size of DER encoded: + * NegTokenTarg [ SEQUENCE ] of + * NegResult[0] ENUMERATED { + * accept_completed(0), + * accept_incomplete(1), + * reject(2) } + * supportedMech [1] MechType OPTIONAL, + * responseToken [2] OCTET STRING OPTIONAL, + * mechListMIC [3] OCTET STRING OPTIONAL + * + * size = data->length + MechListMic + SupportedMech len + + * Result Length + ASN.1 overhead + */ + NegTokenTargSize = dataLen; + dataLen += 1 + gssint_der_length_size(NegTokenTargSize); + + /* + * NegotiationToken [ CHOICE ]{ + * negTokenInit [0] NegTokenInit, + * negTokenTarg [1] NegTokenTarg } + */ + NegTokenSize = dataLen; + dataLen += 1 + gssint_der_length_size(NegTokenSize); + + tlen = dataLen; + t = (unsigned char *) gssalloc_malloc(tlen); + + if (t == NULL) { + ret = GSS_S_DEFECTIVE_TOKEN; + goto errout; + } + + ptr = t; + + /* + * Indicate that we are sending CHOICE 1 + * (NegTokenTarg) + */ + *ptr++ = CONTEXT | 0x01; + if (gssint_put_der_length(NegTokenSize, &ptr, dataLen) < 0) { + ret = GSS_S_DEFECTIVE_TOKEN; + goto errout; + } + *ptr++ = SEQUENCE; + if (gssint_put_der_length(NegTokenTargSize, &ptr, + tlen - (int)(ptr-t)) < 0) { + ret = GSS_S_DEFECTIVE_TOKEN; + goto errout; + } + + /* + * First field of the NegTokenTarg SEQUENCE + * is the ENUMERATED NegResult. + */ + *ptr++ = CONTEXT; + if (gssint_put_der_length(3, &ptr, + tlen - (int)(ptr-t)) < 0) { + ret = GSS_S_DEFECTIVE_TOKEN; + goto errout; + } + if (put_negResult(&ptr, status, tlen - (int)(ptr - t)) < 0) { + ret = GSS_S_DEFECTIVE_TOKEN; + goto errout; + } + if (sendtoken == INIT_TOKEN_SEND) { + /* + * Next, is the Supported MechType + */ + *ptr++ = CONTEXT | 0x01; + if (gssint_put_der_length(mech_wanted->length + 2, + &ptr, + tlen - (int)(ptr - t)) < 0) { + ret = GSS_S_DEFECTIVE_TOKEN; + goto errout; + } + if (put_mech_oid(&ptr, mech_wanted, + tlen - (int)(ptr - t)) < 0) { + ret = GSS_S_DEFECTIVE_TOKEN; + goto errout; + } + } + if (data != NULL && data->length > 0) { + *ptr++ = CONTEXT | 0x02; + if (gssint_put_der_length(rspTokenSize, &ptr, + tlen - (int)(ptr - t)) < 0) { + ret = GSS_S_DEFECTIVE_TOKEN; + goto errout; + } + if (put_input_token(&ptr, data, + tlen - (int)(ptr - t)) < 0) { + ret = GSS_S_DEFECTIVE_TOKEN; + goto errout; + } + } + if (mechListMIC != NULL) { + *ptr++ = CONTEXT | 0x03; + if (gssint_put_der_length(micTokenSize, &ptr, + tlen - (int)(ptr - t)) < 0) { + ret = GSS_S_DEFECTIVE_TOKEN; + goto errout; + } + if (put_input_token(&ptr, mechListMIC, + tlen - (int)(ptr - t)) < 0) { + ret = GSS_S_DEFECTIVE_TOKEN; + goto errout; + } + } + ret = GSS_S_COMPLETE; +errout: + if (ret != GSS_S_COMPLETE) { + if (t) + free(t); + } else { + outbuf->length = ptr - t; + outbuf->value = (void *) t; + } + + return (ret); +} + +/* determine size of token */ +static int +g_token_size(gss_OID_const mech, unsigned int body_size) +{ + int hdrsize; + + /* + * Initialize the header size to the + * MECH_OID byte + the bytes needed to indicate the + * length of the OID + the OID itself. + * + * 0x06 [MECHLENFIELD] MECHDATA + */ + hdrsize = 1 + gssint_der_length_size(mech->length) + mech->length; + + /* + * Now add the bytes needed for the initial header + * token bytes: + * 0x60 + [DER_LEN] + HDRSIZE + */ + hdrsize += 1 + gssint_der_length_size(body_size + hdrsize); + + return (hdrsize + body_size); +} + +/* + * generate token header. + * + * Use DER Definite Length method per RFC2478 + * Use of indefinite length encoding will not be compatible + * with Microsoft or others that actually follow the spec. + */ +static int +g_make_token_header(gss_OID_const mech, + unsigned int body_size, + unsigned char **buf, + unsigned int totallen) +{ + int ret = 0; + unsigned int hdrsize; + unsigned char *p = *buf; + + hdrsize = 1 + gssint_der_length_size(mech->length) + mech->length; + + *(*buf)++ = HEADER_ID; + if ((ret = gssint_put_der_length(hdrsize + body_size, buf, totallen))) + return (ret); + + *(*buf)++ = MECH_OID; + if ((ret = gssint_put_der_length(mech->length, buf, + totallen - (int)(p - *buf)))) + return (ret); + TWRITE_STR(*buf, mech->elements, mech->length); + return (0); +} + +/* + * NOTE: This checks that the length returned by + * gssint_get_der_length() is not greater than the number of octets + * remaining, even though gssint_get_der_length() already checks, in + * theory. + */ +static int +g_get_tag_and_length(unsigned char **buf, int tag, + unsigned int buflen, unsigned int *outlen) +{ + unsigned char *ptr = *buf; + int ret = -1; /* pessimists, assume failure ! */ + unsigned int encoded_len; + int tmplen = 0; + + *outlen = 0; + if (buflen > 1 && *ptr == tag) { + ptr++; + tmplen = gssint_get_der_length(&ptr, buflen - 1, + &encoded_len); + if (tmplen < 0) { + ret = -1; + } else if ((unsigned int)tmplen > buflen - (ptr - *buf)) { + ret = -1; + } else + ret = 0; + } + *outlen = tmplen; + *buf = ptr; + return (ret); +} + +static int +g_verify_neg_token_init(unsigned char **buf_in, unsigned int cur_size) +{ + unsigned char *buf = *buf_in; + unsigned char *endptr = buf + cur_size; + int seqsize; + int ret = 0; + unsigned int bytes; + + /* + * Verify this is a NegotiationToken type token + * - check for a0(context specific identifier) + * - get length and verify that enoughd ata exists + */ + if (g_get_tag_and_length(&buf, CONTEXT, cur_size, &bytes) < 0) + return (G_BAD_TOK_HEADER); + + cur_size = bytes; /* should indicate bytes remaining */ + + /* + * Verify the next piece, it should identify this as + * a strucure of type NegTokenInit. + */ + if (*buf++ == SEQUENCE) { + if ((seqsize = gssint_get_der_length(&buf, cur_size, &bytes)) < 0) + return (G_BAD_TOK_HEADER); + /* + * Make sure we have the entire buffer as described + */ + if (seqsize > endptr - buf) + return (G_BAD_TOK_HEADER); + } else { + return (G_BAD_TOK_HEADER); + } + + cur_size = seqsize; /* should indicate bytes remaining */ + + /* + * Verify that the first blob is a sequence of mechTypes + */ + if (*buf++ == CONTEXT) { + if ((seqsize = gssint_get_der_length(&buf, cur_size, &bytes)) < 0) + return (G_BAD_TOK_HEADER); + /* + * Make sure we have the entire buffer as described + */ + if (seqsize > endptr - buf) + return (G_BAD_TOK_HEADER); + } else { + return (G_BAD_TOK_HEADER); + } + + /* + * At this point, *buf should be at the beginning of the + * DER encoded list of mech types that are to be negotiated. + */ + *buf_in = buf; + + return (ret); + +} + +/* verify token header. */ +static int +g_verify_token_header(gss_OID_const mech, + unsigned int *body_size, + unsigned char **buf_in, + int tok_type, + unsigned int toksize) +{ + unsigned char *buf = *buf_in; + int seqsize; + gss_OID_desc toid; + int ret = 0; + unsigned int bytes; + + if (toksize-- < 1) + return (G_BAD_TOK_HEADER); + + if (*buf++ != HEADER_ID) + return (G_BAD_TOK_HEADER); + + if ((seqsize = gssint_get_der_length(&buf, toksize, &bytes)) < 0) + return (G_BAD_TOK_HEADER); + + if ((seqsize + bytes) != toksize) + return (G_BAD_TOK_HEADER); + + if (toksize-- < 1) + return (G_BAD_TOK_HEADER); + + + if (*buf++ != MECH_OID) + return (G_BAD_TOK_HEADER); + + if (toksize-- < 1) + return (G_BAD_TOK_HEADER); + + toid.length = *buf++; + + if (toksize < toid.length) + return (G_BAD_TOK_HEADER); + else + toksize -= toid.length; + + toid.elements = buf; + buf += toid.length; + + if (!g_OID_equal(&toid, mech)) + ret = G_WRONG_MECH; + + /* + * G_WRONG_MECH is not returned immediately because it's more important + * to return G_BAD_TOK_HEADER if the token header is in fact bad + */ + if (toksize < 2) + return (G_BAD_TOK_HEADER); + else + toksize -= 2; + + if (!ret) { + *buf_in = buf; + *body_size = toksize; + } + + return (ret); +} + +/* + * Return non-zero if the oid is one of the kerberos mech oids, + * otherwise return zero. + * + * N.B. There are 3 oids that represent the kerberos mech: + * RFC-specified GSS_MECH_KRB5_OID, + * Old pre-RFC GSS_MECH_KRB5_OLD_OID, + * Incorrect MS GSS_MECH_KRB5_WRONG_OID + */ + +static int +is_kerb_mech(gss_OID oid) +{ + int answer = 0; + OM_uint32 minor; + extern const gss_OID_set_desc * const gss_mech_set_krb5_both; + + (void) gss_test_oid_set_member(&minor, + oid, (gss_OID_set)gss_mech_set_krb5_both, &answer); + + return (answer); +} === added directory '.pc/CVE-2014-4345.patch' === added directory '.pc/CVE-2014-4345.patch/src' === added directory '.pc/CVE-2014-4345.patch/src/plugins' === added directory '.pc/CVE-2014-4345.patch/src/plugins/kdb' === added directory '.pc/CVE-2014-4345.patch/src/plugins/kdb/ldap' === added directory '.pc/CVE-2014-4345.patch/src/plugins/kdb/ldap/libkdb_ldap' === added file '.pc/CVE-2014-4345.patch/src/plugins/kdb/ldap/libkdb_ldap/ldap_principal2.c' --- .pc/CVE-2014-4345.patch/src/plugins/kdb/ldap/libkdb_ldap/ldap_principal2.c 1970-01-01 00:00:00 +0000 +++ .pc/CVE-2014-4345.patch/src/plugins/kdb/ldap/libkdb_ldap/ldap_principal2.c 2014-08-12 11:29:31 +0000 @@ -0,0 +1,1420 @@ +/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */ +/* plugins/kdb/ldap/libkdb_ldap/ldap_principal2.c */ +/* + * Copyright (c) 2004-2005, 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. + */ +/* + * Copyright 2006 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#include +#include "ldap_main.h" +#include "kdb_ldap.h" +#include "ldap_principal.h" +#include "princ_xdr.h" +#include "ldap_tkt_policy.h" +#include "ldap_pwd_policy.h" +#include "ldap_err.h" +#include + +extern char* principal_attributes[]; +extern char* max_pwd_life_attr[]; + +static char * +getstringtime(krb5_timestamp); + +krb5_error_code +berval2tl_data(struct berval *in, krb5_tl_data **out) +{ + *out = (krb5_tl_data *) malloc (sizeof (krb5_tl_data)); + if (*out == NULL) + return ENOMEM; + + (*out)->tl_data_length = in->bv_len - 2; + (*out)->tl_data_contents = (krb5_octet *) malloc + ((*out)->tl_data_length * sizeof (krb5_octet)); + if ((*out)->tl_data_contents == NULL) { + free (*out); + return ENOMEM; + } + + UNSTORE16_INT (in->bv_val, (*out)->tl_data_type); + memcpy ((*out)->tl_data_contents, in->bv_val + 2, (*out)->tl_data_length); + + return 0; +} + +/* + * look up a principal in the directory. + */ + +krb5_error_code +krb5_ldap_get_principal(krb5_context context, krb5_const_principal searchfor, + unsigned int flags, krb5_db_entry **entry_ptr) +{ + char *user=NULL, *filter=NULL, *filtuser=NULL; + unsigned int tree=0, ntrees=1, princlen=0; + krb5_error_code tempst=0, st=0; + char **values=NULL, **subtree=NULL, *cname=NULL; + LDAP *ld=NULL; + LDAPMessage *result=NULL, *ent=NULL; + krb5_ldap_context *ldap_context=NULL; + kdb5_dal_handle *dal_handle=NULL; + krb5_ldap_server_handle *ldap_server_handle=NULL; + krb5_principal cprinc=NULL; + krb5_boolean found=FALSE; + krb5_db_entry *entry = NULL; + + *entry_ptr = NULL; + + /* Clear the global error string */ + krb5_clear_error_message(context); + + if (searchfor == NULL) + return EINVAL; + + dal_handle = context->dal_handle; + ldap_context = (krb5_ldap_context *) dal_handle->db_context; + + CHECK_LDAP_HANDLE(ldap_context); + + if (is_principal_in_realm(ldap_context, searchfor) != 0) { + st = KRB5_KDB_NOENTRY; + krb5_set_error_message(context, st, + _("Principal does not belong to realm")); + goto cleanup; + } + + if ((st=krb5_unparse_name(context, searchfor, &user)) != 0) + goto cleanup; + + if ((st=krb5_ldap_unparse_principal_name(user)) != 0) + goto cleanup; + + filtuser = ldap_filter_correct(user); + if (filtuser == NULL) { + st = ENOMEM; + goto cleanup; + } + + princlen = strlen(FILTER) + strlen(filtuser) + 2 + 1; /* 2 for closing brackets */ + if ((filter = malloc(princlen)) == NULL) { + st = ENOMEM; + goto cleanup; + } + snprintf(filter, princlen, FILTER"%s))", filtuser); + + if ((st = krb5_get_subtree_info(ldap_context, &subtree, &ntrees)) != 0) + goto cleanup; + + GET_HANDLE(); + for (tree=0; tree < ntrees && !found; ++tree) { + + LDAP_SEARCH(subtree[tree], ldap_context->lrparams->search_scope, filter, principal_attributes); + for (ent=ldap_first_entry(ld, result); ent != NULL && !found; ent=ldap_next_entry(ld, ent)) { + + /* get the associated directory user information */ + if ((values=ldap_get_values(ld, ent, "krbprincipalname")) != NULL) { + int i; + + /* a wild-card in a principal name can return a list of kerberos principals. + * Make sure that the correct principal is returned. + * NOTE: a principalname k* in ldap server will return all the principals starting with a k + */ + for (i=0; values[i] != NULL; ++i) { + if (strcmp(values[i], user) == 0) { + found = TRUE; + break; + } + } + ldap_value_free(values); + + if (!found) /* no matching principal found */ + continue; + } + + if ((values=ldap_get_values(ld, ent, "krbcanonicalname")) != NULL) { + if (values[0] && strcmp(values[0], user) != 0) { + /* We matched an alias, not the canonical name. */ + if (flags & KRB5_KDB_FLAG_ALIAS_OK) { + st = krb5_ldap_parse_principal_name(values[0], &cname); + if (st != 0) + goto cleanup; + st = krb5_parse_name(context, cname, &cprinc); + if (st != 0) + goto cleanup; + } else /* No canonicalization, so don't return aliases. */ + found = FALSE; + } + ldap_value_free(values); + if (!found) + continue; + } + + entry = k5alloc(sizeof(*entry), &st); + if (entry == NULL) + goto cleanup; + if ((st = populate_krb5_db_entry(context, ldap_context, ld, ent, + cprinc ? cprinc : searchfor, + entry)) != 0) + goto cleanup; + } + ldap_msgfree(result); + result = NULL; + } /* for (tree=0 ... */ + + if (found) { + *entry_ptr = entry; + entry = NULL; + } else + st = KRB5_KDB_NOENTRY; + +cleanup: + ldap_msgfree(result); + krb5_ldap_free_principal(context, entry); + + if (filter) + free (filter); + + if (subtree) { + for (; ntrees; --ntrees) + if (subtree[ntrees-1]) + free (subtree[ntrees-1]); + free (subtree); + } + + if (ldap_server_handle) + krb5_ldap_put_handle_to_pool(ldap_context, ldap_server_handle); + + if (user) + free(user); + + if (filtuser) + free(filtuser); + + if (cname) + free(cname); + + if (cprinc) + krb5_free_principal(context, cprinc); + + return st; +} + +typedef enum{ ADD_PRINCIPAL, MODIFY_PRINCIPAL } OPERATION; +/* + * ptype is creating confusions. Additionally the logic + * surronding ptype is redundunt and can be achevied + * with the help of dn and containerdn members. + * so dropping the ptype member + */ + +typedef struct _xargs_t { + char *dn; + char *linkdn; + krb5_boolean dn_from_kbd; + char *containerdn; + char *tktpolicydn; +}xargs_t; + +static void +free_xargs(xargs_t xargs) +{ + if (xargs.dn) + free (xargs.dn); + if (xargs.linkdn) + free(xargs.linkdn); + if (xargs.containerdn) + free (xargs.containerdn); + if (xargs.tktpolicydn) + free (xargs.tktpolicydn); +} + +static krb5_error_code +process_db_args(krb5_context context, char **db_args, xargs_t *xargs, + OPERATION optype) +{ + int i=0; + krb5_error_code st=0; + char *arg=NULL, *arg_val=NULL; + char **dptr=NULL; + unsigned int arg_val_len=0; + + if (db_args) { + for (i=0; db_args[i]; ++i) { + arg = strtok_r(db_args[i], "=", &arg_val); + if (strcmp(arg, TKTPOLICY_ARG) == 0) { + dptr = &xargs->tktpolicydn; + } else { + if (strcmp(arg, USERDN_ARG) == 0) { + if (optype == MODIFY_PRINCIPAL || + xargs->dn != NULL || xargs->containerdn != NULL || + xargs->linkdn != NULL) { + st = EINVAL; + krb5_set_error_message(context, st, + _("%s option not supported"), + arg); + goto cleanup; + } + dptr = &xargs->dn; + } else if (strcmp(arg, CONTAINERDN_ARG) == 0) { + if (optype == MODIFY_PRINCIPAL || + xargs->dn != NULL || xargs->containerdn != NULL) { + st = EINVAL; + krb5_set_error_message(context, st, + _("%s option not supported"), + arg); + goto cleanup; + } + dptr = &xargs->containerdn; + } else if (strcmp(arg, LINKDN_ARG) == 0) { + if (xargs->dn != NULL || xargs->linkdn != NULL) { + st = EINVAL; + krb5_set_error_message(context, st, + _("%s option not supported"), + arg); + goto cleanup; + } + dptr = &xargs->linkdn; + } else { + st = EINVAL; + krb5_set_error_message(context, st, + _("unknown option: %s"), arg); + goto cleanup; + } + + xargs->dn_from_kbd = TRUE; + if (arg_val == NULL || strlen(arg_val) == 0) { + st = EINVAL; + krb5_set_error_message(context, st, + _("%s option value missing"), arg); + goto cleanup; + } + } + + if (arg_val == NULL) { + st = EINVAL; + krb5_set_error_message(context, st, + _("%s option value missing"), arg); + goto cleanup; + } + arg_val_len = strlen(arg_val) + 1; + + if (strcmp(arg, TKTPOLICY_ARG) == 0) { + if ((st = krb5_ldap_name_to_policydn (context, + arg_val, + dptr)) != 0) + goto cleanup; + } else { + *dptr = k5memdup(arg_val, arg_val_len, &st); + if (*dptr == NULL) + goto cleanup; + } + } + } + +cleanup: + return st; +} + +krb5int_access accessor; + +static krb5_error_code +asn1_encode_sequence_of_keys(krb5_key_data *key_data, krb5_int16 n_key_data, + krb5_int32 mkvno, krb5_data **code) +{ + krb5_error_code err; + ldap_seqof_key_data val; + + /* + * This should be pushed back into other library initialization + * code. + */ + err = kldap_ensure_initialized (); + if (err) + return err; + + val.key_data = key_data; + val.n_key_data = n_key_data; + val.mkvno = mkvno; + val.kvno = key_data[0].key_data_kvno; + + return accessor.asn1_ldap_encode_sequence_of_keys(&val, code); +} + +static krb5_error_code +asn1_decode_sequence_of_keys(krb5_data *in, krb5_key_data **out, + krb5_int16 *n_key_data, krb5_kvno *mkvno) +{ + krb5_error_code err; + ldap_seqof_key_data *p; + int i; + + /* + * This should be pushed back into other library initialization + * code. + */ + err = kldap_ensure_initialized (); + if (err) + return err; + + err = accessor.asn1_ldap_decode_sequence_of_keys(in, &p); + if (err) + return err; + + /* Set kvno and key_data_ver in each key_data element. */ + for (i = 0; i < p->n_key_data; i++) { + p->key_data[i].key_data_kvno = p->kvno; + p->key_data[i].key_data_ver = + (p->key_data[i].key_data_length[1] == 0) ? 1 : 2; + } + + *out = p->key_data; + *n_key_data = p->n_key_data; + *mkvno = p->mkvno; + free(p); + return 0; +} + + +/* Decoding ASN.1 encoded key */ +static struct berval ** +krb5_encode_krbsecretkey(krb5_key_data *key_data, int n_key_data, + krb5_kvno mkvno) { + struct berval **ret = NULL; + int currkvno; + int num_versions = 1; + int i, j, last; + krb5_error_code err = 0; + + if (n_key_data <= 0) + return NULL; + + /* Find the number of key versions */ + for (i = 0; i < n_key_data - 1; i++) + if (key_data[i].key_data_kvno != key_data[i + 1].key_data_kvno) + num_versions++; + + ret = (struct berval **) calloc (num_versions + 1, sizeof (struct berval *)); + if (ret == NULL) { + err = ENOMEM; + goto cleanup; + } + for (i = 0, last = 0, j = 0, currkvno = key_data[0].key_data_kvno; i < n_key_data; i++) { + krb5_data *code; + if (i == n_key_data - 1 || key_data[i + 1].key_data_kvno != currkvno) { + asn1_encode_sequence_of_keys (key_data+last, + (krb5_int16) i - last + 1, + mkvno, + &code); + ret[j] = malloc (sizeof (struct berval)); + if (ret[j] == NULL) { + err = ENOMEM; + goto cleanup; + } + /*CHECK_NULL(ret[j]); */ + ret[j]->bv_len = code->length; + ret[j]->bv_val = code->data; + j++; + last = i + 1; + + currkvno = key_data[i].key_data_kvno; + } + } + ret[num_versions] = NULL; + +cleanup: + + if (err != 0) { + if (ret != NULL) { + for (i = 0; i <= num_versions; i++) + if (ret[i] != NULL) + free (ret[i]); + free (ret); + ret = NULL; + } + } + + return ret; +} + +static krb5_error_code +tl_data2berval (krb5_tl_data *in, struct berval **out) +{ + *out = (struct berval *) malloc (sizeof (struct berval)); + if (*out == NULL) + return ENOMEM; + + (*out)->bv_len = in->tl_data_length + 2; + (*out)->bv_val = (char *) malloc ((*out)->bv_len); + if ((*out)->bv_val == NULL) { + free (*out); + return ENOMEM; + } + + STORE16_INT((*out)->bv_val, in->tl_data_type); + memcpy ((*out)->bv_val + 2, in->tl_data_contents, in->tl_data_length); + + return 0; +} + +krb5_error_code +krb5_ldap_put_principal(krb5_context context, krb5_db_entry *entry, + char **db_args) +{ + int l=0, kerberos_principal_object_type=0; + krb5_error_code st=0, tempst=0; + LDAP *ld=NULL; + LDAPMessage *result=NULL, *ent=NULL; + char *user=NULL, *subtree=NULL, *principal_dn=NULL; + char **values=NULL, *strval[10]={NULL}, errbuf[1024]; + char *filtuser=NULL; + struct berval **bersecretkey=NULL; + LDAPMod **mods=NULL; + krb5_boolean create_standalone_prinicipal=FALSE; + krb5_boolean krb_identity_exists=FALSE, establish_links=FALSE; + char *standalone_principal_dn=NULL; + krb5_tl_data *tl_data=NULL; + krb5_key_data **keys=NULL; + kdb5_dal_handle *dal_handle=NULL; + krb5_ldap_context *ldap_context=NULL; + krb5_ldap_server_handle *ldap_server_handle=NULL; + osa_princ_ent_rec princ_ent; + xargs_t xargs = {0}; + char *polname = NULL; + OPERATION optype; + krb5_boolean found_entry = FALSE; + + /* Clear the global error string */ + krb5_clear_error_message(context); + + SETUP_CONTEXT(); + if (ldap_context->lrparams == NULL || ldap_context->container_dn == NULL) + return EINVAL; + + /* get ldap handle */ + GET_HANDLE(); + + if (is_principal_in_realm(ldap_context, entry->princ) != 0) { + st = EINVAL; + krb5_set_error_message(context, st, _("Principal does not belong to " + "the default realm")); + goto cleanup; + } + + /* get the principal information to act on */ + if (((st=krb5_unparse_name(context, entry->princ, &user)) != 0) || + ((st=krb5_ldap_unparse_principal_name(user)) != 0)) + goto cleanup; + filtuser = ldap_filter_correct(user); + if (filtuser == NULL) { + st = ENOMEM; + goto cleanup; + } + + /* Identity the type of operation, it can be + * add principal or modify principal. + * hack if the entry->mask has KRB_PRINCIPAL flag set + * then it is a add operation + */ + if (entry->mask & KADM5_PRINCIPAL) + optype = ADD_PRINCIPAL; + else + optype = MODIFY_PRINCIPAL; + + if (((st=krb5_get_princ_type(context, entry, &kerberos_principal_object_type)) != 0) || + ((st=krb5_get_userdn(context, entry, &principal_dn)) != 0)) + goto cleanup; + + if ((st=process_db_args(context, db_args, &xargs, optype)) != 0) + goto cleanup; + + if (entry->mask & KADM5_LOAD) { + unsigned int tree = 0, ntrees = 0; + int numlentries = 0; + char **subtreelist = NULL, *filter = NULL; + + /* A load operation is special, will do a mix-in (add krbprinc + * attrs to a non-krb object entry) if an object exists with a + * matching krbprincipalname attribute so try to find existing + * object and set principal_dn. This assumes that the + * krbprincipalname attribute is unique (only one object entry has + * a particular krbprincipalname attribute). + */ + if (asprintf(&filter, FILTER"%s))", filtuser) < 0) { + filter = NULL; + st = ENOMEM; + goto cleanup; + } + + /* get the current subtree list */ + if ((st = krb5_get_subtree_info(ldap_context, &subtreelist, &ntrees)) != 0) + goto cleanup; + + found_entry = FALSE; + /* search for entry with matching krbprincipalname attribute */ + for (tree = 0; found_entry == FALSE && tree < ntrees; ++tree) { + result = NULL; + if (principal_dn == NULL) { + LDAP_SEARCH_1(subtreelist[tree], ldap_context->lrparams->search_scope, filter, principal_attributes, IGNORE_STATUS); + } else { + /* just look for entry with principal_dn */ + LDAP_SEARCH_1(principal_dn, LDAP_SCOPE_BASE, filter, principal_attributes, IGNORE_STATUS); + } + if (st == LDAP_SUCCESS) { + numlentries = ldap_count_entries(ld, result); + if (numlentries > 1) { + ldap_msgfree(result); + free(filter); + st = EINVAL; + krb5_set_error_message(context, st, + _("operation can not continue, " + "more than one entry with " + "principal name \"%s\" found"), + user); + goto cleanup; + } else if (numlentries == 1) { + found_entry = TRUE; + if (principal_dn == NULL) { + ent = ldap_first_entry(ld, result); + if (ent != NULL) { + /* setting principal_dn will cause that entry to be modified further down */ + if ((principal_dn = ldap_get_dn(ld, ent)) == NULL) { + ldap_get_option (ld, LDAP_OPT_RESULT_CODE, &st); + st = set_ldap_error (context, st, 0); + ldap_msgfree(result); + free(filter); + goto cleanup; + } + } + } + } + if (result) + ldap_msgfree(result); + } else if (st != LDAP_NO_SUCH_OBJECT) { + /* could not perform search, return with failure */ + st = set_ldap_error (context, st, 0); + free(filter); + goto cleanup; + } + /* + * If it isn't found then assume a standalone princ entry is to + * be created. + */ + } /* end for (tree = 0; principal_dn == ... */ + + free(filter); + + if (found_entry == FALSE && principal_dn != NULL) { + /* + * if principal_dn is null then there is code further down to + * deal with setting standalone_principal_dn. Also note that + * this will set create_standalone_prinicipal true for + * non-mix-in entries which is okay if loading from a dump. + */ + create_standalone_prinicipal = TRUE; + standalone_principal_dn = strdup(principal_dn); + CHECK_NULL(standalone_principal_dn); + } + } /* end if (entry->mask & KADM5_LOAD */ + + /* time to generate the DN information with the help of + * containerdn, principalcontainerreference or + * realmcontainerdn information + */ + if (principal_dn == NULL && xargs.dn == NULL) { /* creation of standalone principal */ + /* get the subtree information */ + if (entry->princ->length == 2 && entry->princ->data[0].length == strlen("krbtgt") && + strncmp(entry->princ->data[0].data, "krbtgt", entry->princ->data[0].length) == 0) { + /* if the principal is a inter-realm principal, always created in the realm container */ + subtree = strdup(ldap_context->lrparams->realmdn); + } else if (xargs.containerdn) { + if ((st=checkattributevalue(ld, xargs.containerdn, NULL, NULL, NULL)) != 0) { + if (st == KRB5_KDB_NOENTRY || st == KRB5_KDB_CONSTRAINT_VIOLATION) { + int ost = st; + st = EINVAL; + snprintf(errbuf, sizeof(errbuf), _("'%s' not found: "), + xargs.containerdn); + prepend_err_str(context, errbuf, st, ost); + } + goto cleanup; + } + subtree = strdup(xargs.containerdn); + } else if (ldap_context->lrparams->containerref && strlen(ldap_context->lrparams->containerref) != 0) { + /* + * Here the subtree should be changed with + * principalcontainerreference attribute value + */ + subtree = strdup(ldap_context->lrparams->containerref); + } else { + subtree = strdup(ldap_context->lrparams->realmdn); + } + CHECK_NULL(subtree); + + if (asprintf(&standalone_principal_dn, "krbprincipalname=%s,%s", + filtuser, subtree) < 0) + standalone_principal_dn = NULL; + CHECK_NULL(standalone_principal_dn); + /* + * free subtree when you are done using the subtree + * set the boolean create_standalone_prinicipal to TRUE + */ + create_standalone_prinicipal = TRUE; + free(subtree); + subtree = NULL; + } + + /* + * If the DN information is presented by the user, time to + * validate the input to ensure that the DN falls under + * any of the subtrees + */ + if (xargs.dn_from_kbd == TRUE) { + /* make sure the DN falls in the subtree */ + unsigned int tre=0, ntrees=0; + int dnlen=0, subtreelen=0; + char **subtreelist=NULL; + char *dn=NULL; + krb5_boolean outofsubtree=TRUE; + + if (xargs.dn != NULL) { + dn = xargs.dn; + } else if (xargs.linkdn != NULL) { + dn = xargs.linkdn; + } else if (standalone_principal_dn != NULL) { + /* + * Even though the standalone_principal_dn is constructed + * within this function, there is the containerdn input + * from the user that can become part of the it. + */ + dn = standalone_principal_dn; + } + + /* get the current subtree list */ + if ((st = krb5_get_subtree_info(ldap_context, &subtreelist, &ntrees)) != 0) + goto cleanup; + + for (tre=0; tre= subtreelen) && (strcasecmp((dn + dnlen - subtreelen), subtreelist[tre]) == 0)) { + outofsubtree = FALSE; + break; + } + } + } + + for (tre=0; tre < ntrees; ++tre) { + free(subtreelist[tre]); + } + + if (outofsubtree == TRUE) { + st = EINVAL; + krb5_set_error_message(context, st, + _("DN is out of the realm subtree")); + goto cleanup; + } + + /* + * dn value will be set either by dn, linkdn or the standalone_principal_dn + * In the first 2 cases, the dn should be existing and in the last case we + * are supposed to create the ldap object. so the below should not be + * executed for the last case. + */ + + if (standalone_principal_dn == NULL) { + /* + * If the ldap object is missing, this results in an error. + */ + + /* + * Search for krbprincipalname attribute here. + * This is to find if a kerberos identity is already present + * on the ldap object, in which case adding a kerberos identity + * on the ldap object should result in an error. + */ + char *attributes[]={"krbticketpolicyreference", "krbprincipalname", NULL}; + + LDAP_SEARCH_1(dn, LDAP_SCOPE_BASE, 0, attributes, IGNORE_STATUS); + if (st == LDAP_SUCCESS) { + ent = ldap_first_entry(ld, result); + if (ent != NULL) { + if ((values=ldap_get_values(ld, ent, "krbticketpolicyreference")) != NULL) { + ldap_value_free(values); + } + + if ((values=ldap_get_values(ld, ent, "krbprincipalname")) != NULL) { + krb_identity_exists = TRUE; + ldap_value_free(values); + } + } + ldap_msgfree(result); + } else { + st = set_ldap_error(context, st, OP_SEARCH); + goto cleanup; + } + } + } + + /* + * If xargs.dn is set then the request is to add a + * kerberos principal on a ldap object, but if + * there is one already on the ldap object this + * should result in an error. + */ + + if (xargs.dn != NULL && krb_identity_exists == TRUE) { + st = EINVAL; + snprintf(errbuf, sizeof(errbuf), + _("ldap object is already kerberized")); + krb5_set_error_message(context, st, "%s", errbuf); + goto cleanup; + } + + if (xargs.linkdn != NULL) { + /* + * link information can be changed using modprinc. + * However, link information can be changed only on the + * standalone kerberos principal objects. A standalone + * kerberos principal object is of type krbprincipal + * structural objectclass. + * + * NOTE: kerberos principals on an ldap object can't be + * linked to other ldap objects. + */ + if (optype == MODIFY_PRINCIPAL && + kerberos_principal_object_type != KDB_STANDALONE_PRINCIPAL_OBJECT) { + st = EINVAL; + snprintf(errbuf, sizeof(errbuf), + _("link information can not be set/updated as the " + "kerberos principal belongs to an ldap object")); + krb5_set_error_message(context, st, "%s", errbuf); + goto cleanup; + } + /* + * Check the link information. If there is already a link + * existing then this operation is not allowed. + */ + { + char **linkdns=NULL; + int j=0; + + if ((st=krb5_get_linkdn(context, entry, &linkdns)) != 0) { + snprintf(errbuf, sizeof(errbuf), + _("Failed getting object references")); + krb5_set_error_message(context, st, "%s", errbuf); + goto cleanup; + } + if (linkdns != NULL) { + st = EINVAL; + snprintf(errbuf, sizeof(errbuf), + _("kerberos principal is already linked to a ldap " + "object")); + krb5_set_error_message(context, st, "%s", errbuf); + for (j=0; linkdns[j] != NULL; ++j) + free (linkdns[j]); + free (linkdns); + goto cleanup; + } + } + + establish_links = TRUE; + } + + if (entry->mask & KADM5_LAST_SUCCESS) { + memset(strval, 0, sizeof(strval)); + if ((strval[0]=getstringtime(entry->last_success)) == NULL) + goto cleanup; + if ((st=krb5_add_str_mem_ldap_mod(&mods, "krbLastSuccessfulAuth", LDAP_MOD_REPLACE, strval)) != 0) { + free (strval[0]); + goto cleanup; + } + free (strval[0]); + } + + if (entry->mask & KADM5_LAST_FAILED) { + memset(strval, 0, sizeof(strval)); + if ((strval[0]=getstringtime(entry->last_failed)) == NULL) + goto cleanup; + if ((st=krb5_add_str_mem_ldap_mod(&mods, "krbLastFailedAuth", LDAP_MOD_REPLACE, strval)) != 0) { + free (strval[0]); + goto cleanup; + } + free(strval[0]); + } + + if (entry->mask & KADM5_FAIL_AUTH_COUNT) { + krb5_kvno fail_auth_count; + + fail_auth_count = entry->fail_auth_count; + if (entry->mask & KADM5_FAIL_AUTH_COUNT_INCREMENT) + fail_auth_count++; + + st = krb5_add_int_mem_ldap_mod(&mods, "krbLoginFailedCount", + LDAP_MOD_REPLACE, + fail_auth_count); + if (st != 0) + goto cleanup; + } else if (entry->mask & KADM5_FAIL_AUTH_COUNT_INCREMENT) { + int attr_mask = 0; + krb5_boolean has_fail_count; + + /* Check if the krbLoginFailedCount attribute exists. (Through + * krb5 1.8.1, it wasn't set in new entries.) */ + st = krb5_get_attributes_mask(context, entry, &attr_mask); + if (st != 0) + goto cleanup; + has_fail_count = ((attr_mask & KDB_FAIL_AUTH_COUNT_ATTR) != 0); + + /* + * If the client library and server supports RFC 4525, + * then use it to increment by one the value of the + * krbLoginFailedCount attribute. Otherwise, assert the + * (provided) old value by deleting it before adding. + */ +#ifdef LDAP_MOD_INCREMENT + if (ldap_server_handle->server_info->modify_increment && + has_fail_count) { + st = krb5_add_int_mem_ldap_mod(&mods, "krbLoginFailedCount", + LDAP_MOD_INCREMENT, 1); + if (st != 0) + goto cleanup; + } else { +#endif /* LDAP_MOD_INCREMENT */ + if (has_fail_count) { + st = krb5_add_int_mem_ldap_mod(&mods, + "krbLoginFailedCount", + LDAP_MOD_DELETE, + entry->fail_auth_count); + if (st != 0) + goto cleanup; + } + st = krb5_add_int_mem_ldap_mod(&mods, "krbLoginFailedCount", + LDAP_MOD_ADD, + entry->fail_auth_count + 1); + if (st != 0) + goto cleanup; +#ifdef LDAP_MOD_INCREMENT + } +#endif + } else if (optype == ADD_PRINCIPAL) { + /* Initialize krbLoginFailedCount in new entries to help avoid a + * race during the first failed login. */ + st = krb5_add_int_mem_ldap_mod(&mods, "krbLoginFailedCount", + LDAP_MOD_ADD, 0); + } + + if (entry->mask & KADM5_MAX_LIFE) { + if ((st=krb5_add_int_mem_ldap_mod(&mods, "krbmaxticketlife", LDAP_MOD_REPLACE, entry->max_life)) != 0) + goto cleanup; + } + + if (entry->mask & KADM5_MAX_RLIFE) { + if ((st=krb5_add_int_mem_ldap_mod(&mods, "krbmaxrenewableage", LDAP_MOD_REPLACE, + entry->max_renewable_life)) != 0) + goto cleanup; + } + + if (entry->mask & KADM5_ATTRIBUTES) { + if ((st=krb5_add_int_mem_ldap_mod(&mods, "krbticketflags", LDAP_MOD_REPLACE, + entry->attributes)) != 0) + goto cleanup; + } + + if (entry->mask & KADM5_PRINCIPAL) { + memset(strval, 0, sizeof(strval)); + strval[0] = user; + if ((st=krb5_add_str_mem_ldap_mod(&mods, "krbprincipalname", LDAP_MOD_REPLACE, strval)) != 0) + goto cleanup; + } + + if (entry->mask & KADM5_PRINC_EXPIRE_TIME) { + memset(strval, 0, sizeof(strval)); + if ((strval[0]=getstringtime(entry->expiration)) == NULL) + goto cleanup; + if ((st=krb5_add_str_mem_ldap_mod(&mods, "krbprincipalexpiration", LDAP_MOD_REPLACE, strval)) != 0) { + free (strval[0]); + goto cleanup; + } + free (strval[0]); + } + + if (entry->mask & KADM5_PW_EXPIRATION) { + memset(strval, 0, sizeof(strval)); + if ((strval[0]=getstringtime(entry->pw_expiration)) == NULL) + goto cleanup; + if ((st=krb5_add_str_mem_ldap_mod(&mods, "krbpasswordexpiration", + LDAP_MOD_REPLACE, + strval)) != 0) { + free (strval[0]); + goto cleanup; + } + free (strval[0]); + } + + if (entry->mask & KADM5_POLICY) { + memset(&princ_ent, 0, sizeof(princ_ent)); + for (tl_data=entry->tl_data; tl_data; tl_data=tl_data->tl_data_next) { + if (tl_data->tl_data_type == KRB5_TL_KADM_DATA) { + /* FIX ME: I guess the princ_ent should be freed after this call */ + if ((st = krb5_lookup_tl_kadm_data(tl_data, &princ_ent)) != 0) { + goto cleanup; + } + } + } + + if (princ_ent.aux_attributes & KADM5_POLICY) { + memset(strval, 0, sizeof(strval)); + if ((st = krb5_ldap_name_to_policydn (context, princ_ent.policy, &polname)) != 0) + goto cleanup; + strval[0] = polname; + if ((st=krb5_add_str_mem_ldap_mod(&mods, "krbpwdpolicyreference", LDAP_MOD_REPLACE, strval)) != 0) + goto cleanup; + } else { + st = EINVAL; + krb5_set_error_message(context, st, "Password policy value null"); + goto cleanup; + } + } else if (entry->mask & KADM5_LOAD && found_entry == TRUE) { + /* + * a load is special in that existing entries must have attrs that + * removed. + */ + + if ((st=krb5_add_str_mem_ldap_mod(&mods, "krbpwdpolicyreference", LDAP_MOD_REPLACE, NULL)) != 0) + goto cleanup; + } + + if (entry->mask & KADM5_POLICY_CLR) { + if ((st=krb5_add_str_mem_ldap_mod(&mods, "krbpwdpolicyreference", LDAP_MOD_DELETE, NULL)) != 0) + goto cleanup; + } + + if (entry->mask & KADM5_KEY_DATA || entry->mask & KADM5_KVNO) { + krb5_kvno mkvno; + + if ((st=krb5_dbe_lookup_mkvno(context, entry, &mkvno)) != 0) + goto cleanup; + bersecretkey = krb5_encode_krbsecretkey (entry->key_data, + entry->n_key_data, mkvno); + + if ((st=krb5_add_ber_mem_ldap_mod(&mods, "krbprincipalkey", + LDAP_MOD_REPLACE | LDAP_MOD_BVALUES, bersecretkey)) != 0) + goto cleanup; + + if (!(entry->mask & KADM5_PRINCIPAL)) { + memset(strval, 0, sizeof(strval)); + if ((strval[0]=getstringtime(entry->pw_expiration)) == NULL) + goto cleanup; + if ((st=krb5_add_str_mem_ldap_mod(&mods, + "krbpasswordexpiration", + LDAP_MOD_REPLACE, strval)) != 0) { + free (strval[0]); + goto cleanup; + } + free (strval[0]); + } + + /* Update last password change whenever a new key is set */ + { + krb5_timestamp last_pw_changed; + if ((st=krb5_dbe_lookup_last_pwd_change(context, entry, + &last_pw_changed)) != 0) + goto cleanup; + + memset(strval, 0, sizeof(strval)); + if ((strval[0] = getstringtime(last_pw_changed)) == NULL) + goto cleanup; + + if ((st=krb5_add_str_mem_ldap_mod(&mods, "krbLastPwdChange", + LDAP_MOD_REPLACE, strval)) != 0) { + free (strval[0]); + goto cleanup; + } + free (strval[0]); + } + + } /* Modify Key data ends here */ + + /* Set tl_data */ + if (entry->tl_data != NULL) { + int count = 0; + struct berval **ber_tl_data = NULL; + krb5_tl_data *ptr; + krb5_timestamp unlock_time; + for (ptr = entry->tl_data; ptr != NULL; ptr = ptr->tl_data_next) { + if (ptr->tl_data_type == KRB5_TL_LAST_PWD_CHANGE +#ifdef SECURID + || ptr->tl_data_type == KRB5_TL_DB_ARGS +#endif + || ptr->tl_data_type == KRB5_TL_KADM_DATA + || ptr->tl_data_type == KDB_TL_USER_INFO + || ptr->tl_data_type == KRB5_TL_CONSTRAINED_DELEGATION_ACL + || ptr->tl_data_type == KRB5_TL_LAST_ADMIN_UNLOCK) + continue; + count++; + } + if (count != 0) { + int j; + ber_tl_data = (struct berval **) calloc (count + 1, + sizeof (struct berval*)); + if (ber_tl_data == NULL) { + st = ENOMEM; + goto cleanup; + } + for (j = 0, ptr = entry->tl_data; ptr != NULL; ptr = ptr->tl_data_next) { + /* Ignore tl_data that are stored in separate directory + * attributes */ + if (ptr->tl_data_type == KRB5_TL_LAST_PWD_CHANGE +#ifdef SECURID + || ptr->tl_data_type == KRB5_TL_DB_ARGS +#endif + || ptr->tl_data_type == KRB5_TL_KADM_DATA + || ptr->tl_data_type == KDB_TL_USER_INFO + || ptr->tl_data_type == KRB5_TL_CONSTRAINED_DELEGATION_ACL + || ptr->tl_data_type == KRB5_TL_LAST_ADMIN_UNLOCK) + continue; + if ((st = tl_data2berval (ptr, &ber_tl_data[j])) != 0) + break; + j++; + } + if (st == 0) { + ber_tl_data[count] = NULL; + st=krb5_add_ber_mem_ldap_mod(&mods, "krbExtraData", + LDAP_MOD_REPLACE | + LDAP_MOD_BVALUES, ber_tl_data); + } + for (j = 0; ber_tl_data[j] != NULL; j++) { + free(ber_tl_data[j]->bv_val); + free(ber_tl_data[j]); + } + free(ber_tl_data); + if (st != 0) + goto cleanup; + } + if ((st=krb5_dbe_lookup_last_admin_unlock(context, entry, + &unlock_time)) != 0) + goto cleanup; + if (unlock_time != 0) { + /* Update last admin unlock */ + memset(strval, 0, sizeof(strval)); + if ((strval[0] = getstringtime(unlock_time)) == NULL) + goto cleanup; + + if ((st=krb5_add_str_mem_ldap_mod(&mods, "krbLastAdminUnlock", + LDAP_MOD_REPLACE, strval)) != 0) { + free (strval[0]); + goto cleanup; + } + free (strval[0]); + } + } + + /* Directory specific attribute */ + if (xargs.tktpolicydn != NULL) { + int tmask=0; + + if (strlen(xargs.tktpolicydn) != 0) { + st = checkattributevalue(ld, xargs.tktpolicydn, "objectclass", policyclass, &tmask); + CHECK_CLASS_VALIDITY(st, tmask, _("ticket policy object value: ")); + + strval[0] = xargs.tktpolicydn; + strval[1] = NULL; + if ((st=krb5_add_str_mem_ldap_mod(&mods, "krbticketpolicyreference", LDAP_MOD_REPLACE, strval)) != 0) + goto cleanup; + + } else { + /* if xargs.tktpolicydn is a empty string, then delete + * already existing krbticketpolicyreference attr */ + if ((st=krb5_add_str_mem_ldap_mod(&mods, "krbticketpolicyreference", LDAP_MOD_DELETE, NULL)) != 0) + goto cleanup; + } + + } + + if (establish_links == TRUE) { + memset(strval, 0, sizeof(strval)); + strval[0] = xargs.linkdn; + if ((st=krb5_add_str_mem_ldap_mod(&mods, "krbObjectReferences", LDAP_MOD_REPLACE, strval)) != 0) + goto cleanup; + } + + /* + * in case mods is NULL then return + * not sure but can happen in a modprinc + * so no need to return an error + * addprinc will at least have the principal name + * and the keys passed in + */ + if (mods == NULL) + goto cleanup; + + if (create_standalone_prinicipal == TRUE) { + memset(strval, 0, sizeof(strval)); + strval[0] = "krbprincipal"; + strval[1] = "krbprincipalaux"; + strval[2] = "krbTicketPolicyAux"; + + if ((st=krb5_add_str_mem_ldap_mod(&mods, "objectclass", LDAP_MOD_ADD, strval)) != 0) + goto cleanup; + + st = ldap_add_ext_s(ld, standalone_principal_dn, mods, NULL, NULL); + if (st == LDAP_ALREADY_EXISTS && entry->mask & KADM5_LOAD) { + /* a load operation must replace an existing entry */ + st = ldap_delete_ext_s(ld, standalone_principal_dn, NULL, NULL); + if (st != LDAP_SUCCESS) { + snprintf(errbuf, sizeof(errbuf), + _("Principal delete failed (trying to replace " + "entry): %s"), ldap_err2string(st)); + st = translate_ldap_error (st, OP_ADD); + krb5_set_error_message(context, st, "%s", errbuf); + goto cleanup; + } else { + st = ldap_add_ext_s(ld, standalone_principal_dn, mods, NULL, NULL); + } + } + if (st != LDAP_SUCCESS) { + snprintf(errbuf, sizeof(errbuf), _("Principal add failed: %s"), + ldap_err2string(st)); + st = translate_ldap_error (st, OP_ADD); + krb5_set_error_message(context, st, "%s", errbuf); + goto cleanup; + } + } else { + /* + * Here existing ldap object is modified and can be related + * to any attribute, so always ensure that the ldap + * object is extended with all the kerberos related + * objectclasses so that there are no constraint + * violations. + */ + { + char *attrvalues[] = {"krbprincipalaux", "krbTicketPolicyAux", NULL}; + int p, q, r=0, amask=0; + + if ((st=checkattributevalue(ld, (xargs.dn) ? xargs.dn : principal_dn, + "objectclass", attrvalues, &amask)) != 0) + goto cleanup; + + memset(strval, 0, sizeof(strval)); + for (p=1, q=0; p<=2; p<<=1, ++q) { + if ((p & amask) == 0) + strval[r++] = attrvalues[q]; + } + if (r != 0) { + if ((st=krb5_add_str_mem_ldap_mod(&mods, "objectclass", LDAP_MOD_ADD, strval)) != 0) + goto cleanup; + } + } + if (xargs.dn != NULL) + st=ldap_modify_ext_s(ld, xargs.dn, mods, NULL, NULL); + else + st = ldap_modify_ext_s(ld, principal_dn, mods, NULL, NULL); + + if (st != LDAP_SUCCESS) { + snprintf(errbuf, sizeof(errbuf), _("User modification failed: %s"), + ldap_err2string(st)); + st = translate_ldap_error (st, OP_MOD); + krb5_set_error_message(context, st, "%s", errbuf); + goto cleanup; + } + + if (entry->mask & KADM5_FAIL_AUTH_COUNT_INCREMENT) + entry->fail_auth_count++; + } + +cleanup: + if (user) + free(user); + + if (filtuser) + free(filtuser); + + free_xargs(xargs); + + if (standalone_principal_dn) + free(standalone_principal_dn); + + if (principal_dn) + free (principal_dn); + + if (polname != NULL) + free(polname); + + if (subtree) + free (subtree); + + if (bersecretkey) { + for (l=0; bersecretkey[l]; ++l) { + if (bersecretkey[l]->bv_val) + free (bersecretkey[l]->bv_val); + free (bersecretkey[l]); + } + free (bersecretkey); + } + + if (keys) + free (keys); + + ldap_mods_free(mods, 1); + krb5_ldap_put_handle_to_pool(ldap_context, ldap_server_handle); + return(st); +} + +krb5_error_code +krb5_read_tkt_policy(krb5_context context, krb5_ldap_context *ldap_context, + krb5_db_entry *entries, char *policy) +{ + krb5_error_code st=0; + int mask=0, omask=0; + int tkt_mask=(KDB_MAX_LIFE_ATTR | KDB_MAX_RLIFE_ATTR | KDB_TKT_FLAGS_ATTR); + krb5_ldap_policy_params *tktpoldnparam=NULL; + + if ((st=krb5_get_attributes_mask(context, entries, &mask)) != 0) + goto cleanup; + + if ((mask & tkt_mask) == tkt_mask) + goto cleanup; + + if (policy != NULL) { + st = krb5_ldap_read_policy(context, policy, &tktpoldnparam, &omask); + if (st && st != KRB5_KDB_NOENTRY) { + prepend_err_str(context, _("Error reading ticket policy. "), st, + st); + goto cleanup; + } + + st = 0; /* reset the return status */ + } + + if ((mask & KDB_MAX_LIFE_ATTR) == 0) { + if ((omask & KDB_MAX_LIFE_ATTR) == KDB_MAX_LIFE_ATTR) + entries->max_life = tktpoldnparam->maxtktlife; + else if (ldap_context->lrparams->max_life) + entries->max_life = ldap_context->lrparams->max_life; + } + + if ((mask & KDB_MAX_RLIFE_ATTR) == 0) { + if ((omask & KDB_MAX_RLIFE_ATTR) == KDB_MAX_RLIFE_ATTR) + entries->max_renewable_life = tktpoldnparam->maxrenewlife; + else if (ldap_context->lrparams->max_renewable_life) + entries->max_renewable_life = ldap_context->lrparams->max_renewable_life; + } + + if ((mask & KDB_TKT_FLAGS_ATTR) == 0) { + if ((omask & KDB_TKT_FLAGS_ATTR) == KDB_TKT_FLAGS_ATTR) + entries->attributes = tktpoldnparam->tktflags; + else if (ldap_context->lrparams->tktflags) + entries->attributes |= ldap_context->lrparams->tktflags; + } + krb5_ldap_free_policy(context, tktpoldnparam); + +cleanup: + return st; +} + +krb5_error_code +krb5_decode_krbsecretkey(krb5_context context, krb5_db_entry *entries, + struct berval **bvalues, + krb5_tl_data *userinfo_tl_data, krb5_kvno *mkvno) +{ + char *user=NULL; + int i=0, j=0, noofkeys=0; + krb5_key_data *key_data=NULL, *tmp; + krb5_error_code st=0; + + if ((st=krb5_unparse_name(context, entries->princ, &user)) != 0) + goto cleanup; + + for (i=0; bvalues[i] != NULL; ++i) { + krb5_int16 n_kd; + krb5_key_data *kd; + krb5_data in; + + if (bvalues[i]->bv_len == 0) + continue; + in.length = bvalues[i]->bv_len; + in.data = bvalues[i]->bv_val; + + st = asn1_decode_sequence_of_keys (&in, + &kd, + &n_kd, + mkvno); + + if (st != 0) { + const char *msg = error_message(st); + st = -1; /* Something more appropriate ? */ + krb5_set_error_message(context, st, _("unable to decode stored " + "principal key data (%s)"), + msg); + goto cleanup; + } + noofkeys += n_kd; + tmp = key_data; + /* Allocate an extra key data to avoid allocating zero bytes. */ + key_data = realloc(key_data, (noofkeys + 1) * sizeof (krb5_key_data)); + if (key_data == NULL) { + key_data = tmp; + st = ENOMEM; + goto cleanup; + } + for (j = 0; j < n_kd; j++) + key_data[noofkeys - n_kd + j] = kd[j]; + free (kd); + } + + entries->n_key_data = noofkeys; + entries->key_data = key_data; + +cleanup: + ldap_value_free_len(bvalues); + free (user); + return st; +} + +static char * +getstringtime(krb5_timestamp epochtime) +{ + struct tm tme; + char *strtime=NULL; + time_t posixtime = epochtime; + + strtime = calloc (50, 1); + if (strtime == NULL) + return NULL; + + if (gmtime_r(&posixtime, &tme) == NULL) + return NULL; + + strftime(strtime, 50, "%Y%m%d%H%M%SZ", &tme); + return strtime; +} === added directory '.pc/Use-TAILQ-macros-instead-of-CIRCLEQ-in-libdb2.patch' === added directory '.pc/Use-TAILQ-macros-instead-of-CIRCLEQ-in-libdb2.patch/src' === added directory '.pc/Use-TAILQ-macros-instead-of-CIRCLEQ-in-libdb2.patch/src/plugins' === added directory '.pc/Use-TAILQ-macros-instead-of-CIRCLEQ-in-libdb2.patch/src/plugins/kdb' === added directory '.pc/Use-TAILQ-macros-instead-of-CIRCLEQ-in-libdb2.patch/src/plugins/kdb/db2' === added directory '.pc/Use-TAILQ-macros-instead-of-CIRCLEQ-in-libdb2.patch/src/plugins/kdb/db2/libdb2' === added directory '.pc/Use-TAILQ-macros-instead-of-CIRCLEQ-in-libdb2.patch/src/plugins/kdb/db2/libdb2/mpool' === added file '.pc/Use-TAILQ-macros-instead-of-CIRCLEQ-in-libdb2.patch/src/plugins/kdb/db2/libdb2/mpool/mpool.c' --- .pc/Use-TAILQ-macros-instead-of-CIRCLEQ-in-libdb2.patch/src/plugins/kdb/db2/libdb2/mpool/mpool.c 1970-01-01 00:00:00 +0000 +++ .pc/Use-TAILQ-macros-instead-of-CIRCLEQ-in-libdb2.patch/src/plugins/kdb/db2/libdb2/mpool/mpool.c 2014-08-12 11:29:31 +0000 @@ -0,0 +1,513 @@ +/*- + * Copyright (c) 1990, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS 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. + */ + +#if defined(LIBC_SCCS) && !defined(lint) +static char sccsid[] = "@(#)mpool.c 8.7 (Berkeley) 11/2/95"; +#endif /* LIBC_SCCS and not lint */ + +#include +#include + +#include +#include +#include +#include +#include + +#include "db-int.h" +#include "mpool.h" + +static BKT *mpool_bkt __P((MPOOL *)); +static BKT *mpool_look __P((MPOOL *, db_pgno_t)); +static int mpool_write __P((MPOOL *, BKT *)); + +/* + * mpool_open -- + * Initialize a memory pool. + */ +MPOOL * +mpool_open(key, fd, pagesize, maxcache) + void *key; + int fd; + db_pgno_t pagesize, maxcache; +{ + struct stat sb; + MPOOL *mp; + int entry; + + /* + * Get information about the file. + * + * XXX + * We don't currently handle pipes, although we should. + */ + if (fstat(fd, &sb)) + return (NULL); + if (!S_ISREG(sb.st_mode)) { + errno = ESPIPE; + return (NULL); + } + + /* Allocate and initialize the MPOOL cookie. */ + if ((mp = (MPOOL *)calloc(1, sizeof(MPOOL))) == NULL) + return (NULL); + CIRCLEQ_INIT(&mp->lqh); + for (entry = 0; entry < HASHSIZE; ++entry) + CIRCLEQ_INIT(&mp->hqh[entry]); + mp->maxcache = maxcache; + mp->npages = sb.st_size / pagesize; + mp->pagesize = pagesize; + mp->fd = fd; + return (mp); +} + +/* + * mpool_filter -- + * Initialize input/output filters. + */ +void +mpool_filter(mp, pgin, pgout, pgcookie) + MPOOL *mp; + void (*pgin) __P((void *, db_pgno_t, void *)); + void (*pgout) __P((void *, db_pgno_t, void *)); + void *pgcookie; +{ + mp->pgin = pgin; + mp->pgout = pgout; + mp->pgcookie = pgcookie; +} + +/* + * mpool_new -- + * Get a new page of memory. + */ +void * +mpool_new(mp, pgnoaddr, flags) + MPOOL *mp; + db_pgno_t *pgnoaddr; + u_int flags; +{ + struct _hqh *head; + BKT *bp; + + if (mp->npages == MAX_PAGE_NUMBER) { + (void)fprintf(stderr, "mpool_new: page allocation overflow.\n"); + abort(); + } +#ifdef STATISTICS + ++mp->pagenew; +#endif + /* + * Get a BKT from the cache. Assign a new page number, attach + * it to the head of the hash chain, the tail of the lru chain, + * and return. + */ + if ((bp = mpool_bkt(mp)) == NULL) + return (NULL); + if (flags == MPOOL_PAGE_REQUEST) { + mp->npages++; + bp->pgno = *pgnoaddr; + } else + bp->pgno = *pgnoaddr = mp->npages++; + + bp->flags = MPOOL_PINNED | MPOOL_INUSE; + + head = &mp->hqh[HASHKEY(bp->pgno)]; + CIRCLEQ_INSERT_HEAD(head, bp, hq); + CIRCLEQ_INSERT_TAIL(&mp->lqh, bp, q); + return (bp->page); +} + +int +mpool_delete(mp, page) + MPOOL *mp; + void *page; +{ + struct _hqh *head; + BKT *bp; + + bp = (BKT *)((char *)page - sizeof(BKT)); + +#ifdef DEBUG + if (!(bp->flags & MPOOL_PINNED)) { + (void)fprintf(stderr, + "mpool_delete: page %d not pinned\n", bp->pgno); + abort(); + } +#endif + + /* Remove from the hash and lru queues. */ + head = &mp->hqh[HASHKEY(bp->pgno)]; + CIRCLEQ_REMOVE(head, bp, hq); + CIRCLEQ_REMOVE(&mp->lqh, bp, q); + + free(bp); + return (RET_SUCCESS); +} + +/* + * mpool_get + * Get a page. + */ +void * +mpool_get(mp, pgno, flags) + MPOOL *mp; + db_pgno_t pgno; + u_int flags; /* XXX not used? */ +{ + struct _hqh *head; + BKT *bp; + off_t off; + int nr; + +#ifdef STATISTICS + ++mp->pageget; +#endif + + /* Check for a page that is cached. */ + if ((bp = mpool_look(mp, pgno)) != NULL) { +#ifdef DEBUG + if (!(flags & MPOOL_IGNOREPIN) && bp->flags & MPOOL_PINNED) { + (void)fprintf(stderr, + "mpool_get: page %d already pinned\n", bp->pgno); + abort(); + } +#endif + /* + * Move the page to the head of the hash chain and the tail + * of the lru chain. + */ + head = &mp->hqh[HASHKEY(bp->pgno)]; + CIRCLEQ_REMOVE(head, bp, hq); + CIRCLEQ_INSERT_HEAD(head, bp, hq); + CIRCLEQ_REMOVE(&mp->lqh, bp, q); + CIRCLEQ_INSERT_TAIL(&mp->lqh, bp, q); + + /* Return a pinned page. */ + bp->flags |= MPOOL_PINNED; + return (bp->page); + } + + /* Get a page from the cache. */ + if ((bp = mpool_bkt(mp)) == NULL) + return (NULL); + + /* Read in the contents. */ +#ifdef STATISTICS + ++mp->pageread; +#endif + off = mp->pagesize * pgno; + if (off / mp->pagesize != pgno) { + /* Run past the end of the file, or at least the part we + can address without large-file support? */ + errno = E2BIG; + return NULL; + } + if (lseek(mp->fd, off, SEEK_SET) != off) + return (NULL); + + if ((nr = read(mp->fd, bp->page, mp->pagesize)) != mp->pagesize) { + if (nr > 0) { + /* A partial read is definitely bad. */ + errno = EINVAL; + return (NULL); + } else { + /* + * A zero-length reads, means you need to create a + * new page. + */ + memset(bp->page, 0, mp->pagesize); + } + } + + /* Set the page number, pin the page. */ + bp->pgno = pgno; + if (!(flags & MPOOL_IGNOREPIN)) + bp->flags = MPOOL_PINNED; + bp->flags |= MPOOL_INUSE; + + /* + * Add the page to the head of the hash chain and the tail + * of the lru chain. + */ + head = &mp->hqh[HASHKEY(bp->pgno)]; + CIRCLEQ_INSERT_HEAD(head, bp, hq); + CIRCLEQ_INSERT_TAIL(&mp->lqh, bp, q); + + /* Run through the user's filter. */ + if (mp->pgin != NULL) + (mp->pgin)(mp->pgcookie, bp->pgno, bp->page); + + return (bp->page); +} + +/* + * mpool_put + * Return a page. + */ +int +mpool_put(mp, page, flags) + MPOOL *mp; + void *page; + u_int flags; +{ + BKT *bp; + +#ifdef STATISTICS + ++mp->pageput; +#endif + bp = (BKT *)((char *)page - sizeof(BKT)); +#ifdef DEBUG + if (!(bp->flags & MPOOL_PINNED)) { + (void)fprintf(stderr, + "mpool_put: page %d not pinned\n", bp->pgno); + abort(); + } +#endif + bp->flags &= ~MPOOL_PINNED; + if (flags & MPOOL_DIRTY) + bp->flags |= flags & MPOOL_DIRTY; + return (RET_SUCCESS); +} + +/* + * mpool_close + * Close the buffer pool. + */ +int +mpool_close(mp) + MPOOL *mp; +{ + BKT *bp; + + /* Free up any space allocated to the lru pages. */ + while ((bp = mp->lqh.cqh_first) != (void *)&mp->lqh) { + CIRCLEQ_REMOVE(&mp->lqh, mp->lqh.cqh_first, q); + free(bp); + } + + /* Free the MPOOL cookie. */ + free(mp); + return (RET_SUCCESS); +} + +/* + * mpool_sync + * Sync the pool to disk. + */ +int +mpool_sync(mp) + MPOOL *mp; +{ + BKT *bp; + + /* Walk the lru chain, flushing any dirty pages to disk. */ + for (bp = mp->lqh.cqh_first; + bp != (void *)&mp->lqh; bp = bp->q.cqe_next) + if (bp->flags & MPOOL_DIRTY && + mpool_write(mp, bp) == RET_ERROR) + return (RET_ERROR); + + /* Sync the file descriptor. */ + return (fsync(mp->fd) ? RET_ERROR : RET_SUCCESS); +} + +/* + * mpool_bkt + * Get a page from the cache (or create one). + */ +static BKT * +mpool_bkt(mp) + MPOOL *mp; +{ + struct _hqh *head; + BKT *bp; + + /* If under the max cached, always create a new page. */ + if (mp->curcache < mp->maxcache) + goto new; + + /* + * If the cache is max'd out, walk the lru list for a buffer we + * can flush. If we find one, write it (if necessary) and take it + * off any lists. If we don't find anything we grow the cache anyway. + * The cache never shrinks. + */ + for (bp = mp->lqh.cqh_first; + bp != (void *)&mp->lqh; bp = bp->q.cqe_next) + if (!(bp->flags & MPOOL_PINNED)) { + /* Flush if dirty. */ + if (bp->flags & MPOOL_DIRTY && + mpool_write(mp, bp) == RET_ERROR) + return (NULL); +#ifdef STATISTICS + ++mp->pageflush; +#endif + /* Remove from the hash and lru queues. */ + head = &mp->hqh[HASHKEY(bp->pgno)]; + CIRCLEQ_REMOVE(head, bp, hq); + CIRCLEQ_REMOVE(&mp->lqh, bp, q); +#if defined(DEBUG) && !defined(DEBUG_IDX0SPLIT) + { void *spage; + spage = bp->page; + memset(bp, 0xff, sizeof(BKT) + mp->pagesize); + bp->page = spage; + } +#endif + bp->flags = 0; + return (bp); + } + +new: if ((bp = (BKT *)malloc(sizeof(BKT) + mp->pagesize)) == NULL) + return (NULL); +#ifdef STATISTICS + ++mp->pagealloc; +#endif +#if defined(DEBUG) || defined(PURIFY) || 1 + memset(bp, 0xff, sizeof(BKT) + mp->pagesize); +#endif + bp->page = (char *)bp + sizeof(BKT); + bp->flags = 0; + ++mp->curcache; + return (bp); +} + +/* + * mpool_write + * Write a page to disk. + */ +static int +mpool_write(mp, bp) + MPOOL *mp; + BKT *bp; +{ + off_t off; + +#ifdef STATISTICS + ++mp->pagewrite; +#endif + + /* Run through the user's filter. */ + if (mp->pgout) + (mp->pgout)(mp->pgcookie, bp->pgno, bp->page); + + off = mp->pagesize * bp->pgno; + if (off / mp->pagesize != bp->pgno) { + /* Run past the end of the file, or at least the part we + can address without large-file support? */ + errno = E2BIG; + return RET_ERROR; + } + if (lseek(mp->fd, off, SEEK_SET) != off) + return (RET_ERROR); + if (write(mp->fd, bp->page, mp->pagesize) != mp->pagesize) + return (RET_ERROR); + + bp->flags &= ~MPOOL_DIRTY; + return (RET_SUCCESS); +} + +/* + * mpool_look + * Lookup a page in the cache. + */ +static BKT * +mpool_look(mp, pgno) + MPOOL *mp; + db_pgno_t pgno; +{ + struct _hqh *head; + BKT *bp; + + head = &mp->hqh[HASHKEY(pgno)]; + for (bp = head->cqh_first; bp != (void *)head; bp = bp->hq.cqe_next) + if ((bp->pgno == pgno) && (bp->flags & MPOOL_INUSE)) { +#ifdef STATISTICS + ++mp->cachehit; +#endif + return (bp); + } +#ifdef STATISTICS + ++mp->cachemiss; +#endif + return (NULL); +} + +#ifdef STATISTICS +/* + * mpool_stat + * Print out cache statistics. + */ +void +mpool_stat(mp) + MPOOL *mp; +{ + BKT *bp; + int cnt; + char *sep; + + (void)fprintf(stderr, "%lu pages in the file\n", mp->npages); + (void)fprintf(stderr, + "page size %lu, cacheing %lu pages of %lu page max cache\n", + mp->pagesize, mp->curcache, mp->maxcache); + (void)fprintf(stderr, "%lu page puts, %lu page gets, %lu page new\n", + mp->pageput, mp->pageget, mp->pagenew); + (void)fprintf(stderr, "%lu page allocs, %lu page flushes\n", + mp->pagealloc, mp->pageflush); + if (mp->cachehit + mp->cachemiss) + (void)fprintf(stderr, + "%.0f%% cache hit rate (%lu hits, %lu misses)\n", + ((double)mp->cachehit / (mp->cachehit + mp->cachemiss)) + * 100, mp->cachehit, mp->cachemiss); + (void)fprintf(stderr, "%lu page reads, %lu page writes\n", + mp->pageread, mp->pagewrite); + + sep = ""; + cnt = 0; + for (bp = mp->lqh.cqh_first; + bp != (void *)&mp->lqh; bp = bp->q.cqe_next) { + (void)fprintf(stderr, "%s%d", sep, bp->pgno); + if (bp->flags & MPOOL_DIRTY) + (void)fprintf(stderr, "d"); + if (bp->flags & MPOOL_PINNED) + (void)fprintf(stderr, "P"); + if (++cnt == 10) { + sep = "\n"; + cnt = 0; + } else + sep = ", "; + + } + (void)fprintf(stderr, "\n"); +} +#endif === added file '.pc/Use-TAILQ-macros-instead-of-CIRCLEQ-in-libdb2.patch/src/plugins/kdb/db2/libdb2/mpool/mpool.h' --- .pc/Use-TAILQ-macros-instead-of-CIRCLEQ-in-libdb2.patch/src/plugins/kdb/db2/libdb2/mpool/mpool.h 1970-01-01 00:00:00 +0000 +++ .pc/Use-TAILQ-macros-instead-of-CIRCLEQ-in-libdb2.patch/src/plugins/kdb/db2/libdb2/mpool/mpool.h 2014-08-12 11:29:31 +0000 @@ -0,0 +1,117 @@ +/*- + * Copyright (c) 1991, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS 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. + * + * @(#)mpool.h 8.4 (Berkeley) 11/2/95 + */ + +#include "db-queue.h" + +/* + * The memory pool scheme is a simple one. Each in-memory page is referenced + * by a bucket which is threaded in up to two of three ways. All active pages + * are threaded on a hash chain (hashed by page number) and an lru chain. + * Inactive pages are threaded on a free chain. Each reference to a memory + * pool is handed an opaque MPOOL cookie which stores all of this information. + */ +#define HASHSIZE 128 +#define HASHKEY(pgno) ((pgno - 1) % HASHSIZE) + +/* The BKT structures are the elements of the queues. */ +typedef struct _bkt { + CIRCLEQ_ENTRY(_bkt) hq; /* hash queue */ + CIRCLEQ_ENTRY(_bkt) q; /* lru queue */ + void *page; /* page */ + db_pgno_t pgno; /* page number */ + +#define MPOOL_DIRTY 0x01 /* page needs to be written */ +#define MPOOL_PINNED 0x02 /* page is pinned into memory */ +#define MPOOL_INUSE 0x04 /* page address is valid */ + u_int8_t flags; /* flags */ +} BKT; + +typedef struct MPOOL { + CIRCLEQ_HEAD(_lqh, _bkt) lqh; /* lru queue head */ + /* hash queue array */ + CIRCLEQ_HEAD(_hqh, _bkt) hqh[HASHSIZE]; + db_pgno_t curcache; /* current number of cached pages */ + db_pgno_t maxcache; /* max number of cached pages */ + db_pgno_t npages; /* number of pages in the file */ + u_long pagesize; /* file page size */ + int fd; /* file descriptor */ + /* page in conversion routine */ + void (*pgin) __P((void *, db_pgno_t, void *)); + /* page out conversion routine */ + void (*pgout) __P((void *, db_pgno_t, void *)); + void *pgcookie; /* cookie for page in/out routines */ +#ifdef STATISTICS + u_long cachehit; + u_long cachemiss; + u_long pagealloc; + u_long pageflush; + u_long pageget; + u_long pagenew; + u_long pageput; + u_long pageread; + u_long pagewrite; +#endif +} MPOOL; + +#define MPOOL_IGNOREPIN 0x01 /* Ignore if the page is pinned. */ +#define MPOOL_PAGE_REQUEST 0x01 /* Allocate a new page with a + specific page number. */ +#define MPOOL_PAGE_NEXT 0x02 /* Allocate a new page with the next + page number. */ + +#define mpool_open kdb2_mpool_open +#define mpool_filter kdb2_mpool_filter +#define mpool_new kdb2_mpool_new +#define mpool_get kdb2_mpool_get +#define mpool_delete kdb2_mpool_delete +#define mpool_put kdb2_mpool_put +#define mpool_sync kdb2_mpool_sync +#define mpool_close kdb2_mpool_close +#define mpool_stat kdb2_mpool_stat + +__BEGIN_DECLS +MPOOL *mpool_open __P((void *, int, db_pgno_t, db_pgno_t)); +void mpool_filter __P((MPOOL *, void (*)(void *, db_pgno_t, void *), + void (*)(void *, db_pgno_t, void *), void *)); +void *mpool_new __P((MPOOL *, db_pgno_t *, u_int)); +void *mpool_get __P((MPOOL *, db_pgno_t, u_int)); +int mpool_delete __P((MPOOL *, void *)); +int mpool_put __P((MPOOL *, void *, u_int)); +int mpool_sync __P((MPOOL *)); +int mpool_close __P((MPOOL *)); +#ifdef STATISTICS +void mpool_stat __P((MPOOL *)); +#endif +__END_DECLS === modified file '.pc/applied-patches' --- .pc/applied-patches 2014-02-04 14:29:23 +0000 +++ .pc/applied-patches 2014-08-12 11:29:31 +0000 @@ -7,3 +7,9 @@ debian-local/0007-Add-substpdf-target.patch 0008-autoreconf.patch debian-local/0009-.gbp.conf.patch +avoid_mechglue_recursive_calls_new_symbols +Use-TAILQ-macros-instead-of-CIRCLEQ-in-libdb2.patch +CVE-2014-4341-4342.patch +CVE-2014-4343.patch +CVE-2014-4344.patch +CVE-2014-4345.patch === added directory '.pc/avoid_mechglue_recursive_calls_new_symbols' === added directory '.pc/avoid_mechglue_recursive_calls_new_symbols/src' === added directory '.pc/avoid_mechglue_recursive_calls_new_symbols/src/lib' === added directory '.pc/avoid_mechglue_recursive_calls_new_symbols/src/lib/gssapi' === added directory '.pc/avoid_mechglue_recursive_calls_new_symbols/src/lib/gssapi/mechglue' === added file '.pc/avoid_mechglue_recursive_calls_new_symbols/src/lib/gssapi/mechglue/g_initialize.c' --- .pc/avoid_mechglue_recursive_calls_new_symbols/src/lib/gssapi/mechglue/g_initialize.c 1970-01-01 00:00:00 +0000 +++ .pc/avoid_mechglue_recursive_calls_new_symbols/src/lib/gssapi/mechglue/g_initialize.c 2014-08-12 11:29:31 +0000 @@ -0,0 +1,1536 @@ +/* #pragma ident "@(#)g_initialize.c 1.36 05/02/02 SMI" */ + +/* + * Copyright 1996 by Sun Microsystems, 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 Sun Microsystems not be used + * in advertising or publicity pertaining to distribution of the software + * without specific, written prior permission. Sun Microsystems makes no + * representations about the suitability of this software for any + * purpose. It is provided "as is" without express or implied warranty. + * + * SUN MICROSYSTEMS DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO + * EVENT SHALL SUN MICROSYSTEMS 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. + */ + +/* + * This function will initialize the gssapi mechglue library + */ + +#include "mglueP.h" +#ifdef HAVE_STDLIB_H +#include +#endif +#ifdef HAVE_SYS_STAT_H +#include +#endif +#ifdef HAVE_SYS_PARAM_H +#include +#endif + +#include +#include +#include +#include + +#define M_DEFAULT "default" + +#include "k5-thread.h" +#include "k5-plugin.h" +#include "osconf.h" +#ifdef _GSS_STATIC_LINK +#include "gssapiP_krb5.h" +#include "gssapiP_spnego.h" +#endif + +#define MECH_SYM "gss_mech_initialize" +#define MECH_INTERPOSER_SYM "gss_mech_interposer" + +#ifndef MECH_CONF +#define MECH_CONF "/etc/gss/mech" +#endif + +/* Local functions */ +static void addConfigEntry(const char *oidStr, const char *oid, + const char *sharedLib, const char *kernMod, + const char *modOptions, const char *modType); +static gss_mech_info searchMechList(gss_const_OID); +static void loadConfigFile(const char *); +#if defined(_WIN32) +#ifndef MECH_KEY +#define MECH_KEY "SOFTWARE\\gss\\mech" +#endif +static time_t getRegKeyModTime(HKEY hBaseKey, const char *keyPath); +static time_t getRegConfigModTime(const char *keyPath); +static void getRegKeyValue(HKEY key, const char *keyPath, const char *valueName, void **data, DWORD *dataLen); +static void loadConfigFromRegistry(HKEY keyBase, const char *keyPath); +#endif +static void updateMechList(void); +static void initMechList(void); +static void loadInterMech(gss_mech_info aMech); +static void freeMechList(void); + +static OM_uint32 build_mechSet(void); +static void free_mechSet(void); + +/* + * list of mechanism libraries and their entry points. + * the list also maintains state of the mech libraries (loaded or not). + */ +static gss_mech_info g_mechList = NULL; +static gss_mech_info g_mechListTail = NULL; +static k5_mutex_t g_mechListLock = K5_MUTEX_PARTIAL_INITIALIZER; +static time_t g_confFileModTime = (time_t)0; + +static time_t g_mechSetTime = (time_t)0; +static gss_OID_set_desc g_mechSet = { 0, NULL }; +static k5_mutex_t g_mechSetLock = K5_MUTEX_PARTIAL_INITIALIZER; + +MAKE_INIT_FUNCTION(gssint_mechglue_init); +MAKE_FINI_FUNCTION(gssint_mechglue_fini); + +int +gssint_mechglue_init(void) +{ + int err; + +#ifdef SHOW_INITFINI_FUNCS + printf("gssint_mechglue_init\n"); +#endif + + add_error_table(&et_ggss_error_table); + + err = k5_mutex_finish_init(&g_mechSetLock); + err = k5_mutex_finish_init(&g_mechListLock); + +#ifdef _GSS_STATIC_LINK + err = gss_krb5int_lib_init(); + err = gss_spnegoint_lib_init(); +#endif + + err = gssint_mecherrmap_init(); + return err; +} + +void +gssint_mechglue_fini(void) +{ + if (!INITIALIZER_RAN(gssint_mechglue_init) || PROGRAM_EXITING()) { +#ifdef SHOW_INITFINI_FUNCS + printf("gssint_mechglue_fini: skipping\n"); +#endif + return; + } + +#ifdef SHOW_INITFINI_FUNCS + printf("gssint_mechglue_fini\n"); +#endif +#ifdef _GSS_STATIC_LINK + gss_spnegoint_lib_fini(); + gss_krb5int_lib_fini(); +#endif + k5_mutex_destroy(&g_mechSetLock); + k5_mutex_destroy(&g_mechListLock); + free_mechSet(); + freeMechList(); + remove_error_table(&et_ggss_error_table); + gssint_mecherrmap_destroy(); +} + +int +gssint_mechglue_initialize_library(void) +{ + return CALL_INIT_FUNCTION(gssint_mechglue_init); +} + +/* + * function used to reclaim the memory used by a gss_OID structure. + * This routine requires direct access to the mechList. + */ +OM_uint32 KRB5_CALLCONV +gss_release_oid(minor_status, oid) +OM_uint32 *minor_status; +gss_OID *oid; +{ + OM_uint32 major; + gss_mech_info aMech; + + if (minor_status == NULL) + return (GSS_S_CALL_INACCESSIBLE_WRITE); + + *minor_status = gssint_mechglue_initialize_library(); + if (*minor_status != 0) + return (GSS_S_FAILURE); + + k5_mutex_lock(&g_mechListLock); + aMech = g_mechList; + while (aMech != NULL) { + + /* + * look through the loaded mechanism libraries for + * gss_internal_release_oid until one returns success. + * gss_internal_release_oid will only return success when + * the OID was recognized as an internal mechanism OID. if no + * mechanisms recognize the OID, then call the generic version. + */ + if (aMech->mech && aMech->mech->gss_internal_release_oid) { + major = aMech->mech->gss_internal_release_oid( + minor_status, oid); + if (major == GSS_S_COMPLETE) { + k5_mutex_unlock(&g_mechListLock); + return (GSS_S_COMPLETE); + } + map_error(minor_status, aMech->mech); + } + aMech = aMech->next; + } /* while */ + k5_mutex_unlock(&g_mechListLock); + + return (generic_gss_release_oid(minor_status, oid)); +} /* gss_release_oid */ + + +/* + * this function will return an oid set indicating available mechanisms. + * The set returned is based on configuration file entries and + * NOT on the loaded mechanisms. This function does not check if any + * of these can actually be loaded. + * This routine needs direct access to the mechanism list. + * To avoid reading the configuration file each call, we will save a + * a mech oid set, and only update it once the file has changed. + */ +OM_uint32 KRB5_CALLCONV +gss_indicate_mechs(minorStatus, mechSet_out) +OM_uint32 *minorStatus; +gss_OID_set *mechSet_out; +{ + char *fileName; + struct stat fileInfo; + OM_uint32 status; + + /* Initialize outputs. */ + + if (minorStatus != NULL) + *minorStatus = 0; + + if (mechSet_out != NULL) + *mechSet_out = GSS_C_NO_OID_SET; + + /* Validate arguments. */ + if (minorStatus == NULL || mechSet_out == NULL) + return (GSS_S_CALL_INACCESSIBLE_WRITE); + + *minorStatus = gssint_mechglue_initialize_library(); + if (*minorStatus != 0) + return (GSS_S_FAILURE); + + fileName = MECH_CONF; + + /* + * If we have already computed the mechanisms supported and if it + * is still valid; make a copy and return to caller, + * otherwise build it first. + */ + if ((stat(fileName, &fileInfo) == 0 && + fileInfo.st_mtime > g_mechSetTime)) { + } /* if g_mechSet is out of date or not initialized */ + if (build_mechSet()) + return GSS_S_FAILURE; + + /* + * need to lock the g_mechSet in case someone tries to update it while + * I'm copying it. + */ + k5_mutex_lock(&g_mechSetLock); + status = generic_gss_copy_oid_set(minorStatus, &g_mechSet, mechSet_out); + k5_mutex_unlock(&g_mechSetLock); + return (status); +} /* gss_indicate_mechs */ + + +/* Call with g_mechSetLock held, or during final cleanup. */ +static void +free_mechSet(void) +{ + unsigned int i; + + if (g_mechSet.count != 0) { + for (i = 0; i < g_mechSet.count; i++) + free(g_mechSet.elements[i].elements); + free(g_mechSet.elements); + g_mechSet.elements = NULL; + g_mechSet.count = 0; + } +} + +static OM_uint32 +build_mechSet(void) +{ + gss_mech_info mList; + size_t i; + size_t count; + gss_OID curItem; + + /* + * lock the mutex since we will be updating + * the mechList structure + * we need to keep the lock while we build the mechanism list + * since we are accessing parts of the mechList which could be + * modified. + */ + k5_mutex_lock(&g_mechListLock); + +#if 0 + /* + * this checks for the case when we need to re-construct the + * g_mechSet structure, but the mechanism list is upto date + * (because it has been read by someone calling + * gssint_get_mechanism) + */ + if (fileInfo.st_mtime > g_confFileModTime) + { + g_confFileModTime = fileInfo.st_mtime; + loadConfigFile(fileName); + } +#endif + + updateMechList(); + + /* + * we need to lock the mech set so that no one else will + * try to read it as we are re-creating it + */ + k5_mutex_lock(&g_mechSetLock); + + /* if the oid list already exists we must free it first */ + free_mechSet(); + + /* determine how many elements to have in the list */ + mList = g_mechList; + count = 0; + while (mList != NULL) { + count++; + mList = mList->next; + } + + /* this should always be true, but.... */ + if (count > 0) { + g_mechSet.elements = + (gss_OID) calloc(count, sizeof (gss_OID_desc)); + if (g_mechSet.elements == NULL) { + k5_mutex_unlock(&g_mechSetLock); + k5_mutex_unlock(&g_mechListLock); + return (GSS_S_FAILURE); + } + + (void) memset(g_mechSet.elements, 0, + count * sizeof (gss_OID_desc)); + + /* now copy each oid element */ + count = 0; + for (mList = g_mechList; mList != NULL; mList = mList->next) { + /* Don't expose interposer mechanisms. */ + if (mList->is_interposer) + continue; + curItem = &(g_mechSet.elements[count]); + curItem->elements = (void*) + malloc(mList->mech_type->length); + if (curItem->elements == NULL) { + /* + * this is nasty - we must delete the + * part of the array already copied + */ + for (i = 0; i < count; i++) { + free(g_mechSet.elements[i]. + elements); + } + free(g_mechSet.elements); + g_mechSet.count = 0; + g_mechSet.elements = NULL; + k5_mutex_unlock(&g_mechSetLock); + k5_mutex_unlock(&g_mechListLock); + return (GSS_S_FAILURE); + } + g_OID_copy(curItem, mList->mech_type); + count++; + } + g_mechSet.count = count; + } + +#if 0 + g_mechSetTime = fileInfo.st_mtime; +#endif + k5_mutex_unlock(&g_mechSetLock); + k5_mutex_unlock(&g_mechListLock); + + return GSS_S_COMPLETE; +} + + +/* + * this function has been added for use by modules that need to + * know what (if any) optional parameters are supplied in the + * config file (MECH_CONF). + * It will return the option string for a specified mechanism. + * caller is responsible for freeing the memory + */ +char * +gssint_get_modOptions(oid) +const gss_OID oid; +{ + gss_mech_info aMech; + char *modOptions = NULL; + + if (gssint_mechglue_initialize_library() != 0) + return (NULL); + + /* make sure we have fresh data */ + k5_mutex_lock(&g_mechListLock); + updateMechList(); + + if ((aMech = searchMechList(oid)) == NULL || + aMech->optionStr == NULL) { + k5_mutex_unlock(&g_mechListLock); + return (NULL); + } + + if (aMech->optionStr) + modOptions = strdup(aMech->optionStr); + k5_mutex_unlock(&g_mechListLock); + + return (modOptions); +} /* gssint_get_modOptions */ + +/* + * determines if the mechList needs to be updated from file + * and performs the update. + * this functions must be called with a lock of g_mechListLock + */ +static void +updateMechList(void) +{ + gss_mech_info minfo; + +#if defined(_WIN32) + time_t lastConfModTime = getRegConfigModTime(MECH_KEY); + if (g_confFileModTime >= lastConfModTime) + return; + g_confFileModTime = lastConfModTime; + loadConfigFromRegistry(HKEY_CURRENT_USER, MECH_KEY); + loadConfigFromRegistry(HKEY_LOCAL_MACHINE, MECH_KEY); +#else /* _WIN32 */ + char *fileName; + struct stat fileInfo; + + fileName = MECH_CONF; + + /* check if mechList needs updating */ + if (stat(fileName, &fileInfo) != 0 || + g_confFileModTime >= fileInfo.st_mtime) + return; + g_confFileModTime = fileInfo.st_mtime; + loadConfigFile(fileName); +#endif /* !_WIN32 */ + + /* Load any unloaded interposer mechanisms immediately, to make sure we + * interpose other mechanisms before they are used. */ + for (minfo = g_mechList; minfo != NULL; minfo = minfo->next) { + if (minfo->is_interposer && minfo->mech == NULL) + loadInterMech(minfo); + } +} /* updateMechList */ + +/* Update the mech list from system configuration if we have never done so. + * Must be invoked with the g_mechListLock mutex held. */ +static void +initMechList(void) +{ + static int lazy_init = 0; + + if (lazy_init == 0) { + updateMechList(); + lazy_init = 1; + } +} + +static void +releaseMechInfo(gss_mech_info *pCf) +{ + gss_mech_info cf; + OM_uint32 minor_status; + + if (*pCf == NULL) { + return; + } + + cf = *pCf; + + if (cf->kmodName != NULL) + free(cf->kmodName); + if (cf->uLibName != NULL) + free(cf->uLibName); + if (cf->mechNameStr != NULL) + free(cf->mechNameStr); + if (cf->optionStr != NULL) + free(cf->optionStr); + if (cf->mech_type != GSS_C_NO_OID && + cf->mech_type != &cf->mech->mech_type) + generic_gss_release_oid(&minor_status, &cf->mech_type); + if (cf->mech != NULL && cf->freeMech) { + memset(cf->mech, 0, sizeof(*cf->mech)); + free(cf->mech); + } + if (cf->int_mech_type != GSS_C_NO_OID) + generic_gss_release_oid(&minor_status, &cf->int_mech_type); + + memset(cf, 0, sizeof(*cf)); + free(cf); + + *pCf = NULL; +} + +#ifdef _GSS_STATIC_LINK +/* + * Register a mechanism. Called with g_mechListLock held. + */ +int +gssint_register_mechinfo(gss_mech_info template) +{ + gss_mech_info cf, new_cf; + + new_cf = calloc(1, sizeof(*new_cf)); + if (new_cf == NULL) { + return ENOMEM; + } + + new_cf->dl_handle = template->dl_handle; + /* copy mech so we can rewrite canonical mechanism OID */ + new_cf->mech = (gss_mechanism)calloc(1, sizeof(struct gss_config)); + if (new_cf->mech == NULL) { + releaseMechInfo(&new_cf); + return ENOMEM; + } + *new_cf->mech = *template->mech; + if (template->mech_type != NULL) + new_cf->mech->mech_type = *(template->mech_type); + new_cf->mech_type = &new_cf->mech->mech_type; + new_cf->priority = template->priority; + new_cf->freeMech = 1; + new_cf->next = NULL; + + if (template->kmodName != NULL) { + new_cf->kmodName = strdup(template->kmodName); + if (new_cf->kmodName == NULL) { + releaseMechInfo(&new_cf); + return ENOMEM; + } + } + if (template->uLibName != NULL) { + new_cf->uLibName = strdup(template->uLibName); + if (new_cf->uLibName == NULL) { + releaseMechInfo(&new_cf); + return ENOMEM; + } + } + if (template->mechNameStr != NULL) { + new_cf->mechNameStr = strdup(template->mechNameStr); + if (new_cf->mechNameStr == NULL) { + releaseMechInfo(&new_cf); + return ENOMEM; + } + } + if (template->optionStr != NULL) { + new_cf->optionStr = strdup(template->optionStr); + if (new_cf->optionStr == NULL) { + releaseMechInfo(&new_cf); + return ENOMEM; + } + } + if (g_mechList == NULL) { + g_mechList = new_cf; + g_mechListTail = new_cf; + return 0; + } else if (new_cf->priority < g_mechList->priority) { + new_cf->next = g_mechList; + g_mechList = new_cf; + return 0; + } + + for (cf = g_mechList; cf != NULL; cf = cf->next) { + if (cf->next == NULL || + new_cf->priority < cf->next->priority) { + new_cf->next = cf->next; + cf->next = new_cf; + if (g_mechListTail == cf) { + g_mechListTail = new_cf; + } + break; + } + } + + return 0; +} +#endif /* _GSS_STATIC_LINK */ + +#define GSS_ADD_DYNAMIC_METHOD(_dl, _mech, _symbol) \ + do { \ + struct errinfo errinfo; \ + \ + memset(&errinfo, 0, sizeof(errinfo)); \ + if (krb5int_get_plugin_func(_dl, \ + #_symbol, \ + (void (**)())&(_mech)->_symbol, \ + &errinfo) || errinfo.code) \ + (_mech)->_symbol = NULL; \ + } while (0) + +/* + * If _symbol is undefined in the shared object but the shared object + * is linked against the mechanism glue, it's possible for dlsym() to + * return the mechanism glue implementation. Guard against that. + */ +#define GSS_ADD_DYNAMIC_METHOD_NOLOOP(_dl, _mech, _symbol) \ + do { \ + GSS_ADD_DYNAMIC_METHOD(_dl, _mech, _symbol); \ + if ((_mech)->_symbol == _symbol) \ + (_mech)->_symbol = NULL; \ + } while (0) + +static gss_mechanism +build_dynamicMech(void *dl, const gss_OID mech_type) +{ + gss_mechanism mech; + + mech = (gss_mechanism)calloc(1, sizeof(*mech)); + if (mech == NULL) { + return NULL; + } + + GSS_ADD_DYNAMIC_METHOD_NOLOOP(dl, mech, gss_acquire_cred); + GSS_ADD_DYNAMIC_METHOD_NOLOOP(dl, mech, gss_release_cred); + GSS_ADD_DYNAMIC_METHOD_NOLOOP(dl, mech, gss_init_sec_context); + GSS_ADD_DYNAMIC_METHOD_NOLOOP(dl, mech, gss_accept_sec_context); + GSS_ADD_DYNAMIC_METHOD_NOLOOP(dl, mech, gss_process_context_token); + GSS_ADD_DYNAMIC_METHOD_NOLOOP(dl, mech, gss_delete_sec_context); + GSS_ADD_DYNAMIC_METHOD_NOLOOP(dl, mech, gss_context_time); + GSS_ADD_DYNAMIC_METHOD_NOLOOP(dl, mech, gss_get_mic); + GSS_ADD_DYNAMIC_METHOD_NOLOOP(dl, mech, gss_verify_mic); + GSS_ADD_DYNAMIC_METHOD_NOLOOP(dl, mech, gss_wrap); + GSS_ADD_DYNAMIC_METHOD_NOLOOP(dl, mech, gss_unwrap); + GSS_ADD_DYNAMIC_METHOD_NOLOOP(dl, mech, gss_display_status); + GSS_ADD_DYNAMIC_METHOD_NOLOOP(dl, mech, gss_indicate_mechs); + GSS_ADD_DYNAMIC_METHOD_NOLOOP(dl, mech, gss_compare_name); + GSS_ADD_DYNAMIC_METHOD_NOLOOP(dl, mech, gss_display_name); + GSS_ADD_DYNAMIC_METHOD_NOLOOP(dl, mech, gss_import_name); + GSS_ADD_DYNAMIC_METHOD_NOLOOP(dl, mech, gss_release_name); + GSS_ADD_DYNAMIC_METHOD_NOLOOP(dl, mech, gss_inquire_cred); + GSS_ADD_DYNAMIC_METHOD_NOLOOP(dl, mech, gss_add_cred); + GSS_ADD_DYNAMIC_METHOD_NOLOOP(dl, mech, gss_export_sec_context); + GSS_ADD_DYNAMIC_METHOD_NOLOOP(dl, mech, gss_import_sec_context); + GSS_ADD_DYNAMIC_METHOD_NOLOOP(dl, mech, gss_inquire_cred_by_mech); + GSS_ADD_DYNAMIC_METHOD_NOLOOP(dl, mech, gss_inquire_names_for_mech); + GSS_ADD_DYNAMIC_METHOD_NOLOOP(dl, mech, gss_inquire_context); + GSS_ADD_DYNAMIC_METHOD(dl, mech, gss_internal_release_oid); + GSS_ADD_DYNAMIC_METHOD_NOLOOP(dl, mech, gss_wrap_size_limit); + GSS_ADD_DYNAMIC_METHOD_NOLOOP(dl, mech, gss_localname); + GSS_ADD_DYNAMIC_METHOD(dl, mech, gssspi_authorize_localname); + GSS_ADD_DYNAMIC_METHOD_NOLOOP(dl, mech, gss_export_name); + GSS_ADD_DYNAMIC_METHOD_NOLOOP(dl, mech, gss_duplicate_name); + GSS_ADD_DYNAMIC_METHOD_NOLOOP(dl, mech, gss_store_cred); + GSS_ADD_DYNAMIC_METHOD_NOLOOP(dl, mech, gss_inquire_sec_context_by_oid); + GSS_ADD_DYNAMIC_METHOD_NOLOOP(dl, mech, gss_inquire_cred_by_oid); + GSS_ADD_DYNAMIC_METHOD_NOLOOP(dl, mech, gss_set_sec_context_option); + GSS_ADD_DYNAMIC_METHOD(dl, mech, gssspi_set_cred_option); + GSS_ADD_DYNAMIC_METHOD_NOLOOP(dl, mech, gssspi_mech_invoke); + GSS_ADD_DYNAMIC_METHOD_NOLOOP(dl, mech, gss_wrap_aead); + GSS_ADD_DYNAMIC_METHOD_NOLOOP(dl, mech, gss_unwrap_aead); + GSS_ADD_DYNAMIC_METHOD_NOLOOP(dl, mech, gss_wrap_iov); + GSS_ADD_DYNAMIC_METHOD_NOLOOP(dl, mech, gss_unwrap_iov); + GSS_ADD_DYNAMIC_METHOD_NOLOOP(dl, mech, gss_wrap_iov_length); + GSS_ADD_DYNAMIC_METHOD_NOLOOP(dl, mech, gss_complete_auth_token); + /* Services4User (introduced in 1.8) */ + GSS_ADD_DYNAMIC_METHOD_NOLOOP(dl, mech, gss_acquire_cred_impersonate_name); + GSS_ADD_DYNAMIC_METHOD_NOLOOP(dl, mech, gss_add_cred_impersonate_name); + /* Naming extensions (introduced in 1.8) */ + GSS_ADD_DYNAMIC_METHOD_NOLOOP(dl, mech, gss_display_name_ext); + GSS_ADD_DYNAMIC_METHOD_NOLOOP(dl, mech, gss_inquire_name); + GSS_ADD_DYNAMIC_METHOD_NOLOOP(dl, mech, gss_get_name_attribute); + GSS_ADD_DYNAMIC_METHOD_NOLOOP(dl, mech, gss_set_name_attribute); + GSS_ADD_DYNAMIC_METHOD_NOLOOP(dl, mech, gss_delete_name_attribute); + GSS_ADD_DYNAMIC_METHOD_NOLOOP(dl, mech, gss_export_name_composite); + GSS_ADD_DYNAMIC_METHOD_NOLOOP(dl, mech, gss_map_name_to_any); + GSS_ADD_DYNAMIC_METHOD_NOLOOP(dl, mech, gss_release_any_name_mapping); + /* RFC 4401 (introduced in 1.8) */ + GSS_ADD_DYNAMIC_METHOD_NOLOOP(dl, mech, gss_pseudo_random); + /* RFC 4178 (introduced in 1.8; gss_get_neg_mechs not implemented) */ + GSS_ADD_DYNAMIC_METHOD_NOLOOP(dl, mech, gss_set_neg_mechs); + /* draft-ietf-sasl-gs2 */ + GSS_ADD_DYNAMIC_METHOD_NOLOOP(dl, mech, gss_inquire_saslname_for_mech); + GSS_ADD_DYNAMIC_METHOD_NOLOOP(dl, mech, gss_inquire_mech_for_saslname); + /* RFC 5587 */ + GSS_ADD_DYNAMIC_METHOD_NOLOOP(dl, mech, gss_inquire_attrs_for_mech); + GSS_ADD_DYNAMIC_METHOD(dl, mech, gss_acquire_cred_from); + GSS_ADD_DYNAMIC_METHOD(dl, mech, gss_store_cred_into); + GSS_ADD_DYNAMIC_METHOD(dl, mech, gssspi_acquire_cred_with_password); + GSS_ADD_DYNAMIC_METHOD(dl, mech, gss_export_cred); + GSS_ADD_DYNAMIC_METHOD(dl, mech, gss_import_cred); + GSS_ADD_DYNAMIC_METHOD(dl, mech, gssspi_import_sec_context_by_mech); + GSS_ADD_DYNAMIC_METHOD(dl, mech, gssspi_import_name_by_mech); + GSS_ADD_DYNAMIC_METHOD(dl, mech, gssspi_import_cred_by_mech); + + assert(mech_type != GSS_C_NO_OID); + + mech->mech_type = *(mech_type); + + return mech; +} + +#define RESOLVE_GSSI_SYMBOL(_dl, _mech, _psym, _nsym) \ + do { \ + struct errinfo errinfo; \ + memset(&errinfo, 0, sizeof(errinfo)); \ + if (krb5int_get_plugin_func(_dl, \ + "gssi" #_nsym, \ + (void (**)())&(_mech)->_psym \ + ## _nsym, \ + &errinfo) || errinfo.code) \ + (_mech)->_psym ## _nsym = NULL; \ + } while (0) + +/* Build an interposer mechanism function table from dl. */ +static gss_mechanism +build_interMech(void *dl, const gss_OID mech_type) +{ + gss_mechanism mech; + + mech = calloc(1, sizeof(*mech)); + if (mech == NULL) { + return NULL; + } + + RESOLVE_GSSI_SYMBOL(dl, mech, gss, _acquire_cred); + RESOLVE_GSSI_SYMBOL(dl, mech, gss, _release_cred); + RESOLVE_GSSI_SYMBOL(dl, mech, gss, _init_sec_context); + RESOLVE_GSSI_SYMBOL(dl, mech, gss, _accept_sec_context); + RESOLVE_GSSI_SYMBOL(dl, mech, gss, _process_context_token); + RESOLVE_GSSI_SYMBOL(dl, mech, gss, _delete_sec_context); + RESOLVE_GSSI_SYMBOL(dl, mech, gss, _context_time); + RESOLVE_GSSI_SYMBOL(dl, mech, gss, _get_mic); + RESOLVE_GSSI_SYMBOL(dl, mech, gss, _verify_mic); + RESOLVE_GSSI_SYMBOL(dl, mech, gss, _wrap); + RESOLVE_GSSI_SYMBOL(dl, mech, gss, _unwrap); + RESOLVE_GSSI_SYMBOL(dl, mech, gss, _display_status); + RESOLVE_GSSI_SYMBOL(dl, mech, gss, _indicate_mechs); + RESOLVE_GSSI_SYMBOL(dl, mech, gss, _compare_name); + RESOLVE_GSSI_SYMBOL(dl, mech, gss, _display_name); + RESOLVE_GSSI_SYMBOL(dl, mech, gss, _import_name); + RESOLVE_GSSI_SYMBOL(dl, mech, gss, _release_name); + RESOLVE_GSSI_SYMBOL(dl, mech, gss, _inquire_cred); + RESOLVE_GSSI_SYMBOL(dl, mech, gss, _add_cred); + RESOLVE_GSSI_SYMBOL(dl, mech, gss, _export_sec_context); + RESOLVE_GSSI_SYMBOL(dl, mech, gss, _import_sec_context); + RESOLVE_GSSI_SYMBOL(dl, mech, gss, _inquire_cred_by_mech); + RESOLVE_GSSI_SYMBOL(dl, mech, gss, _inquire_names_for_mech); + RESOLVE_GSSI_SYMBOL(dl, mech, gss, _inquire_context); + RESOLVE_GSSI_SYMBOL(dl, mech, gss, _internal_release_oid); + RESOLVE_GSSI_SYMBOL(dl, mech, gss, _wrap_size_limit); + RESOLVE_GSSI_SYMBOL(dl, mech, gss, _localname); + RESOLVE_GSSI_SYMBOL(dl, mech, gssspi, _authorize_localname); + RESOLVE_GSSI_SYMBOL(dl, mech, gss, _export_name); + RESOLVE_GSSI_SYMBOL(dl, mech, gss, _duplicate_name); + RESOLVE_GSSI_SYMBOL(dl, mech, gss, _store_cred); + RESOLVE_GSSI_SYMBOL(dl, mech, gss, _inquire_sec_context_by_oid); + RESOLVE_GSSI_SYMBOL(dl, mech, gss, _inquire_cred_by_oid); + RESOLVE_GSSI_SYMBOL(dl, mech, gss, _set_sec_context_option); + RESOLVE_GSSI_SYMBOL(dl, mech, gssspi, _set_cred_option); + RESOLVE_GSSI_SYMBOL(dl, mech, gssspi, _mech_invoke); + RESOLVE_GSSI_SYMBOL(dl, mech, gss, _wrap_aead); + RESOLVE_GSSI_SYMBOL(dl, mech, gss, _unwrap_aead); + RESOLVE_GSSI_SYMBOL(dl, mech, gss, _wrap_iov); + RESOLVE_GSSI_SYMBOL(dl, mech, gss, _unwrap_iov); + RESOLVE_GSSI_SYMBOL(dl, mech, gss, _wrap_iov_length); + RESOLVE_GSSI_SYMBOL(dl, mech, gss, _complete_auth_token); + /* Services4User (introduced in 1.8) */ + RESOLVE_GSSI_SYMBOL(dl, mech, gss, _acquire_cred_impersonate_name); + RESOLVE_GSSI_SYMBOL(dl, mech, gss, _add_cred_impersonate_name); + /* Naming extensions (introduced in 1.8) */ + RESOLVE_GSSI_SYMBOL(dl, mech, gss, _display_name_ext); + RESOLVE_GSSI_SYMBOL(dl, mech, gss, _inquire_name); + RESOLVE_GSSI_SYMBOL(dl, mech, gss, _get_name_attribute); + RESOLVE_GSSI_SYMBOL(dl, mech, gss, _set_name_attribute); + RESOLVE_GSSI_SYMBOL(dl, mech, gss, _delete_name_attribute); + RESOLVE_GSSI_SYMBOL(dl, mech, gss, _export_name_composite); + RESOLVE_GSSI_SYMBOL(dl, mech, gss, _map_name_to_any); + RESOLVE_GSSI_SYMBOL(dl, mech, gss, _release_any_name_mapping); + /* RFC 4401 (introduced in 1.8) */ + RESOLVE_GSSI_SYMBOL(dl, mech, gss, _pseudo_random); + /* RFC 4178 (introduced in 1.8; get_neg_mechs not implemented) */ + RESOLVE_GSSI_SYMBOL(dl, mech, gss, _set_neg_mechs); + /* draft-ietf-sasl-gs2 */ + RESOLVE_GSSI_SYMBOL(dl, mech, gss, _inquire_saslname_for_mech); + RESOLVE_GSSI_SYMBOL(dl, mech, gss, _inquire_mech_for_saslname); + /* RFC 5587 */ + RESOLVE_GSSI_SYMBOL(dl, mech, gss, _inquire_attrs_for_mech); + RESOLVE_GSSI_SYMBOL(dl, mech, gss, _acquire_cred_from); + RESOLVE_GSSI_SYMBOL(dl, mech, gss, _store_cred_into); + RESOLVE_GSSI_SYMBOL(dl, mech, gssspi, _acquire_cred_with_password); + RESOLVE_GSSI_SYMBOL(dl, mech, gss, _export_cred); + RESOLVE_GSSI_SYMBOL(dl, mech, gss, _import_cred); + RESOLVE_GSSI_SYMBOL(dl, mech, gssspi, _import_sec_context_by_mech); + RESOLVE_GSSI_SYMBOL(dl, mech, gssspi, _import_name_by_mech); + RESOLVE_GSSI_SYMBOL(dl, mech, gssspi, _import_cred_by_mech); + + mech->mech_type = *mech_type; + return mech; +} + +/* + * Concatenate an interposer mech OID and a real mech OID to create an + * identifier for the interposed mech. (The concatenation will not be a valid + * DER OID encoding, but the OID is only used internally.) + */ +static gss_OID +interposed_oid(gss_OID pre, gss_OID real) +{ + gss_OID o; + + o = (gss_OID)malloc(sizeof(gss_OID_desc)); + if (!o) + return NULL; + + o->length = pre->length + real->length; + o->elements = malloc(o->length); + if (!o->elements) { + free(o); + return NULL; + } + + memcpy(o->elements, pre->elements, pre->length); + memcpy((char *)o->elements + pre->length, real->elements, + real->length); + + return o; +} + +static void +loadInterMech(gss_mech_info minfo) +{ + struct plugin_file_handle *dl = NULL; + struct errinfo errinfo; + gss_OID_set (*isym)(const gss_OID); + gss_OID_set list; + gss_OID oid; + OM_uint32 min; + gss_mech_info mi; + size_t i; + + memset(&errinfo, 0, sizeof(errinfo)); + + if (krb5int_open_plugin(minfo->uLibName, &dl, &errinfo) != 0 || + errinfo.code != 0) { +#if 0 + (void) syslog(LOG_INFO, "libgss dlopen(%s): %s\n", + aMech->uLibName, dlerror()); +#endif + return; + } + + if (krb5int_get_plugin_func(dl, MECH_INTERPOSER_SYM, + (void (**)())&isym, &errinfo) != 0) + goto cleanup; + + /* Get a list of mechs to interpose. */ + list = (*isym)(minfo->mech_type); + if (!list) + goto cleanup; + minfo->mech = build_interMech(dl, minfo->mech_type); + if (minfo->mech == NULL) + goto cleanup; + minfo->freeMech = 1; + + /* Add interposer fields for each interposed mech. */ + for (i = 0; i < list->count; i++) { + /* Skip this mech if it doesn't exist or is already + * interposed. */ + oid = &list->elements[i]; + mi = searchMechList(oid); + if (mi == NULL || mi->int_mech_type != NULL) + continue; + + /* Construct a special OID to represent the interposed mech. */ + mi->int_mech_type = interposed_oid(minfo->mech_type, oid); + if (mi->int_mech_type == NULL) + continue; + + /* Save an alias to the interposer's function table. */ + mi->int_mech = minfo->mech; + } + (void)gss_release_oid_set(&min, &list); + + minfo->dl_handle = dl; + dl = NULL; + +cleanup: +#if 0 + if (aMech->mech == NULL) { + (void) syslog(LOG_INFO, "unable to initialize mechanism" + " library [%s]\n", aMech->uLibName); + } +#endif + if (dl != NULL) + krb5int_close_plugin(dl); + k5_clear_error(&errinfo); +} + +static void +freeMechList(void) +{ + gss_mech_info cf, next_cf; + + for (cf = g_mechList; cf != NULL; cf = next_cf) { + next_cf = cf->next; + releaseMechInfo(&cf); + } +} + +/* + * Determine the mechanism to use for a caller-specified mech OID. For the + * real mech OID of an interposed mech, return the interposed OID. For an + * interposed mech OID (which an interposer mech uses when re-entering the + * mechglue), return the real mech OID. The returned OID is an alias and + * should not be modified or freed. + */ +OM_uint32 +gssint_select_mech_type(OM_uint32 *minor, gss_const_OID oid, + gss_OID *selected_oid) +{ + gss_mech_info minfo; + OM_uint32 status; + + *selected_oid = GSS_C_NO_OID; + + if (gssint_mechglue_initialize_library() != 0) + return GSS_S_FAILURE; + + k5_mutex_lock(&g_mechListLock); + + /* Read conf file at least once so that interposer plugins have a + * chance of getting initialized. */ + initMechList(); + + minfo = g_mechList; + if (oid == GSS_C_NULL_OID) + oid = minfo->mech_type; + while (minfo != NULL) { + if (g_OID_equal(minfo->mech_type, oid)) { + if (minfo->int_mech_type != GSS_C_NO_OID) + *selected_oid = minfo->int_mech_type; + else + *selected_oid = minfo->mech_type; + status = GSS_S_COMPLETE; + goto done; + } else if ((minfo->int_mech_type != GSS_C_NO_OID) && + (g_OID_equal(minfo->int_mech_type, oid))) { + *selected_oid = minfo->mech_type; + status = GSS_S_COMPLETE; + goto done; + } + minfo = minfo->next; + } + status = GSS_S_BAD_MECH; + +done: + k5_mutex_unlock(&g_mechListLock); + return status; +} + +/* If oid is an interposed OID, return the corresponding real mech OID. If + * it's a real mech OID, return it unmodified. Otherwised return null. */ +gss_OID +gssint_get_public_oid(gss_const_OID oid) +{ + gss_mech_info minfo; + gss_OID public_oid = GSS_C_NO_OID; + + /* if oid is null -> then get default which is the first in the list */ + if (oid == GSS_C_NO_OID) + return GSS_C_NO_OID; + + if (gssint_mechglue_initialize_library() != 0) + return GSS_C_NO_OID; + + k5_mutex_lock(&g_mechListLock); + + for (minfo = g_mechList; minfo != NULL; minfo = minfo->next) { + if (minfo->is_interposer) + continue; + if (g_OID_equal(minfo->mech_type, oid) || + ((minfo->int_mech_type != GSS_C_NO_OID) && + (g_OID_equal(minfo->int_mech_type, oid)))) { + public_oid = minfo->mech_type; + break; + } + } + + k5_mutex_unlock(&g_mechListLock); + return public_oid; +} + +/* Translate a vector of oids (as from a union cred struct) into a set of + * public OIDs using gssint_get_public_oid. */ +OM_uint32 +gssint_make_public_oid_set(OM_uint32 *minor_status, gss_OID oids, int count, + gss_OID_set *public_set) +{ + OM_uint32 status, tmpmin; + gss_OID_set set; + gss_OID public_oid; + int i; + + *public_set = GSS_C_NO_OID_SET; + + status = generic_gss_create_empty_oid_set(minor_status, &set); + if (GSS_ERROR(status)) + return status; + + for (i = 0; i < count; i++) { + public_oid = gssint_get_public_oid(&oids[i]); + if (public_oid == GSS_C_NO_OID) + continue; + status = generic_gss_add_oid_set_member(minor_status, + public_oid, &set); + if (GSS_ERROR(status)) { + (void) generic_gss_release_oid_set(&tmpmin, &set); + return status; + } + } + + *public_set = set; + return GSS_S_COMPLETE; +} + +/* + * Register a mechanism. Called with g_mechListLock held. + */ + +/* + * given the mechanism type, return the mechanism structure + * containing the mechanism library entry points. + * will return NULL if mech type is not found + * This function will also trigger the loading of the mechanism + * module if it has not been already loaded. + */ +gss_mechanism +gssint_get_mechanism(gss_const_OID oid) +{ + gss_mech_info aMech; + gss_mechanism (*sym)(const gss_OID); + struct plugin_file_handle *dl; + struct errinfo errinfo; + + if (gssint_mechglue_initialize_library() != 0) + return (NULL); + + k5_mutex_lock(&g_mechListLock); + + /* Check if the mechanism is already loaded. */ + aMech = g_mechList; + if (oid == GSS_C_NULL_OID) + oid = aMech->mech_type; + while (aMech != NULL) { + if (g_OID_equal(aMech->mech_type, oid) && aMech->mech) { + k5_mutex_unlock(&g_mechListLock); + return aMech->mech; + } else if (aMech->int_mech_type != GSS_C_NO_OID && + g_OID_equal(aMech->int_mech_type, oid)) { + k5_mutex_unlock(&g_mechListLock); + return aMech->int_mech; + } + aMech = aMech->next; + } + + /* + * might need to re-read the configuration file before loading + * the mechanism to ensure we have the latest info. + */ + updateMechList(); + + aMech = searchMechList(oid); + + /* is the mechanism present in the list ? */ + if (aMech == NULL) { + k5_mutex_unlock(&g_mechListLock); + return ((gss_mechanism)NULL); + } + + /* has another thread loaded the mech */ + if (aMech->mech) { + k5_mutex_unlock(&g_mechListLock); + return (aMech->mech); + } + + memset(&errinfo, 0, sizeof(errinfo)); + + if (krb5int_open_plugin(aMech->uLibName, &dl, &errinfo) != 0 || + errinfo.code != 0) { +#if 0 + (void) syslog(LOG_INFO, "libgss dlopen(%s): %s\n", + aMech->uLibName, dlerror()); +#endif + k5_mutex_unlock(&g_mechListLock); + return ((gss_mechanism)NULL); + } + + if (krb5int_get_plugin_func(dl, MECH_SYM, (void (**)())&sym, + &errinfo) == 0) { + /* Call the symbol to get the mechanism table */ + aMech->mech = (*sym)(aMech->mech_type); + } else { + /* Try dynamic dispatch table */ + aMech->mech = build_dynamicMech(dl, aMech->mech_type); + aMech->freeMech = 1; + } + if (aMech->mech == NULL) { + (void) krb5int_close_plugin(dl); +#if 0 + (void) syslog(LOG_INFO, "unable to initialize mechanism" + " library [%s]\n", aMech->uLibName); +#endif + k5_mutex_unlock(&g_mechListLock); + return ((gss_mechanism)NULL); + } + + aMech->dl_handle = dl; + + k5_mutex_unlock(&g_mechListLock); + return (aMech->mech); +} /* gssint_get_mechanism */ + +/* + * this routine is used for searching the list of mechanism data. + * + * this needs to be called with g_mechListLock held. + */ +static gss_mech_info searchMechList(gss_const_OID oid) +{ + gss_mech_info aMech = g_mechList; + + /* if oid is null -> then get default which is the first in the list */ + if (oid == GSS_C_NULL_OID) + return (aMech); + + while (aMech != NULL) { + if (g_OID_equal(aMech->mech_type, oid)) + return (aMech); + aMech = aMech->next; + } + + /* none found */ + return ((gss_mech_info) NULL); +} /* searchMechList */ + +/* Return the first non-whitespace character starting from str. */ +static char * +skip_whitespace(char *str) +{ + while (isspace(*str)) + str++; + return str; +} + +/* Truncate str at the first whitespace character and return the first + * non-whitespace character after that point. */ +static char * +delimit_ws(char *str) +{ + while (*str != '\0' && !isspace(*str)) + str++; + if (*str != '\0') + *str++ = '\0'; + return skip_whitespace(str); +} + +/* Truncate str at the first occurrence of delimiter and return the first + * non-whitespace character after that point. */ +static char * +delimit(char *str, char delimiter) +{ + while (*str != '\0' && *str != delimiter) + str++; + if (*str != '\0') + *str++ = '\0'; + return skip_whitespace(str); +} + +/* + * loads the configuration file + * this is called while having a mutex lock on the mechanism list + * entries for libraries that have been loaded can't be modified + * mechNameStr and mech_type fields are not updated during updates + */ +static void +loadConfigFile(const char *fileName) +{ + char *sharedLib, *kernMod, *modOptions, *modType, *oid, *next; + char buffer[BUFSIZ], *oidStr; + FILE *confFile; + + if ((confFile = fopen(fileName, "r")) == NULL) { + return; + } + + (void) memset(buffer, 0, sizeof (buffer)); + while (fgets(buffer, BUFSIZ, confFile) != NULL) { + + /* ignore lines beginning with # */ + if (*buffer == '#') + continue; + + /* Parse out the name, oid, and shared library path. */ + oidStr = buffer; + oid = delimit_ws(oidStr); + if (*oid == '\0') + continue; + sharedLib = delimit_ws(oid); + if (*sharedLib == '\0') + continue; + next = delimit_ws(sharedLib); + + /* Parse out the kernel module name if present. */ + if (*next != '\0' && *next != '[' && *next != '<') { + kernMod = next; + next = delimit_ws(kernMod); + } else { + kernMod = NULL; + } + + /* Parse out the module options if present. */ + if (*next == '[') { + modOptions = next + 1; + next = delimit(modOptions, ']'); + } else { + modOptions = NULL; + } + + /* Parse out the module type if present. */ + if (*next == '<') { + modType = next + 1; + (void)delimit(modType, '>'); + } else { + modType = NULL; + } + + addConfigEntry(oidStr, oid, sharedLib, kernMod, modOptions, + modType); + } /* while */ + (void) fclose(confFile); +} /* loadConfigFile */ + +#if defined(_WIN32) + +static time_t +filetimeToTimet(const FILETIME *ft) +{ + ULARGE_INTEGER ull; + + ull.LowPart = ft->dwLowDateTime; + ull.HighPart = ft->dwHighDateTime; + return (time_t)(ull.QuadPart / 10000000ULL - 11644473600ULL); +} + +static time_t +getRegConfigModTime(const char *keyPath) +{ + time_t currentUserModTime = getRegKeyModTime(HKEY_CURRENT_USER, + keyPath); + time_t localMachineModTime = getRegKeyModTime(HKEY_LOCAL_MACHINE, + keyPath); + + return currentUserModTime > localMachineModTime ? currentUserModTime : + localMachineModTime; +} + +static time_t +getRegKeyModTime(HKEY hBaseKey, const char *keyPath) +{ + HKEY hConfigKey; + HRESULT rc; + int iSubKey = 0; + time_t modTime = 0, keyModTime; + FILETIME keyLastWriteTime; + char subKeyName[256]; + + if ((rc = RegOpenKeyEx(hBaseKey, keyPath, 0, KEY_ENUMERATE_SUB_KEYS, + &hConfigKey)) != ERROR_SUCCESS) { + /* TODO: log error message */ + return 0; + } + do { + int subKeyNameSize=sizeof(subKeyName)/sizeof(subKeyName[0]); + if ((rc = RegEnumKeyEx(hConfigKey, iSubKey++, subKeyName, + &subKeyNameSize, NULL, NULL, NULL, + &keyLastWriteTime)) != ERROR_SUCCESS) { + break; + } + keyModTime = filetimeToTimet(&keyLastWriteTime); + if (modTime < keyModTime) { + modTime = keyModTime; + } + } while (1); + RegCloseKey(hConfigKey); + return modTime; +} + +static void +getRegKeyValue(HKEY hKey, const char *keyPath, const char *valueName, + void **data, DWORD* dataLen) +{ + DWORD sizeRequired=*dataLen; + HRESULT hr; + /* Get data length required */ + if ((hr = RegGetValue(hKey, keyPath, valueName, RRF_RT_REG_SZ, NULL, + NULL, &sizeRequired)) != ERROR_SUCCESS) { + /* TODO: LOG registry error */ + return; + } + /* adjust data buffer size if necessary */ + if (*dataLen < sizeRequired) { + *dataLen = sizeRequired; + *data = realloc(*data, sizeRequired); + if (!*data) { + *dataLen = 0; + /* TODO: LOG OOM ERROR! */ + return; + } + } + /* get data */ + if ((hr = RegGetValue(hKey, keyPath, valueName, RRF_RT_REG_SZ, NULL, + *data, &sizeRequired)) != ERROR_SUCCESS) { + /* LOG registry error */ + return; + } +} + +static void +loadConfigFromRegistry(HKEY hBaseKey, const char *keyPath) +{ + HKEY hConfigKey; + DWORD iSubKey, nSubKeys, maxSubKeyNameLen, modTypeLen; + char *oidStr = NULL, *oid = NULL, *sharedLib = NULL, *kernMod = NULL; + char *modOptions = NULL, *modType = NULL; + DWORD oidStrLen = 0, oidLen = 0, sharedLibLen = 0, kernModLen = 0; + DWORD modOptionsLen = 0; + HRESULT rc; + + if ((rc = RegOpenKeyEx(hBaseKey, keyPath, 0, + KEY_ENUMERATE_SUB_KEYS|KEY_QUERY_VALUE, + &hConfigKey)) != ERROR_SUCCESS) { + /* TODO: log registry error */ + return; + } + + if ((rc = RegQueryInfoKey(hConfigKey, + NULL, /* lpClass */ + NULL, /* lpcClass */ + NULL, /* lpReserved */ + &nSubKeys, + &maxSubKeyNameLen, + NULL, /* lpcMaxClassLen */ + NULL, /* lpcValues */ + NULL, /* lpcMaxValueNameLen */ + NULL, /* lpcMaxValueLen */ + NULL, /* lpcbSecurityDescriptor */ + NULL /* lpftLastWriteTime */ )) != ERROR_SUCCESS) { + goto cleanup; + } + oidStr = malloc(++maxSubKeyNameLen); + if (!oidStr) { + goto cleanup; + } + for (iSubKey=0; iSubKeymech) { + generic_gss_release_oid(&minor, &mechOid); + return; + } + + /* + * If that's all, then this is a corrupt entry. Skip it. + */ + if (! *sharedLib) { + generic_gss_release_oid(&minor, &mechOid); + return; + } +#if defined(_WIN32) + sharedPath = sharedLib; +#else + if (sharedLib[0] == '/') + snprintf(sharedPath, sizeof(sharedPath), "%s", sharedLib); + else + snprintf(sharedPath, sizeof(sharedPath), "%s%s", + MECH_LIB_PREFIX, sharedLib); +#endif + /* + * are we creating a new mechanism entry or + * just modifying existing (non loaded) mechanism entry + */ + if (aMech) { + /* + * delete any old values and set new + * mechNameStr and mech_type are not modified + */ + if (aMech->kmodName) { + free(aMech->kmodName); + aMech->kmodName = NULL; + } + + if (aMech->optionStr) { + free(aMech->optionStr); + aMech->optionStr = NULL; + } + + if ((tmpStr = strdup(sharedPath)) != NULL) { + if (aMech->uLibName) + free(aMech->uLibName); + aMech->uLibName = tmpStr; + } + + if (kernMod) /* this is an optional parameter */ + aMech->kmodName = strdup(kernMod); + + if (modOptions) /* optional module options */ + aMech->optionStr = strdup(modOptions); + + /* the oid is already set */ + generic_gss_release_oid(&minor, &mechOid); + return; + } + + /* adding a new entry */ + aMech = calloc(1, sizeof (struct gss_mech_config)); + if (aMech == NULL) { + generic_gss_release_oid(&minor, &mechOid); + return; + } + aMech->mech_type = mechOid; + aMech->uLibName = strdup(sharedPath); + aMech->mechNameStr = strdup(oidStr); + aMech->freeMech = 0; + + /* check if any memory allocations failed - bad news */ + if (aMech->uLibName == NULL || aMech->mechNameStr == NULL) { + if (aMech->uLibName) + free(aMech->uLibName); + if (aMech->mechNameStr) + free(aMech->mechNameStr); + generic_gss_release_oid(&minor, &mechOid); + free(aMech); + return; + } + if (kernMod) /* this is an optional parameter */ + aMech->kmodName = strdup(kernMod); + + if (modOptions) + aMech->optionStr = strdup(modOptions); + + if (modType && strcmp(modType, "interposer") == 0) + aMech->is_interposer = 1; + + /* + * add the new entry to the end of the list - make sure + * that only complete entries are added because other + * threads might currently be searching the list. + */ + tmp = g_mechListTail; + g_mechListTail = aMech; + + if (tmp != NULL) + tmp->next = aMech; + + if (g_mechList == NULL) + g_mechList = aMech; +} + === modified file 'debian/changelog' --- debian/changelog 2014-04-09 11:11:43 +0000 +++ debian/changelog 2014-08-12 11:27:20 +0000 @@ -1,3 +1,39 @@ +krb5 (1.12+dfsg-2ubuntu5) trusty; urgency=low + + * Use ADD_METHOD_NOLOOP rather than ADD_METHOD for new GSS-API entry + points, avoids infinite recursive loop when a mechanism doesn't + provide an entry point and does include calls back into the mechglue + (LP: #1326500) + * Make libkadm5srv-mit8 be arch: any multi-arch: same to work around + upgrade bug (LP: #1334052) + * Use tailq macros to work around GCC 4.8 optimizer bug and prevent + infinite loop for database propagation (LP: #1347147) + + -- Sam Hartman Wed, 30 Jul 2014 21:06:49 -0400 + +krb5 (1.12+dfsg-2ubuntu4.2) trusty-security; urgency=medium + + * SECURITY UPDATE: denial of service via invalid tokens + - debian/patches/CVE-2014-4341-4342.patch: handle invalid tokens in + src/lib/gssapi/krb5/k5unseal.c, src/lib/gssapi/krb5/k5unsealiov.c. + - CVE-2014-4341 + - CVE-2014-4342 + * SECURITY UPDATE: denial of service via double-free in SPNEGO + - debian/patches/CVE-2014-4343.patch: fix double-free in + src/lib/gssapi/spnego/spnego_mech.c. + - CVE-2014-4343 + * SECURITY UPDATE: denial of service via null deref in SPNEGO acceptor + - debian/patches/CVE-2014-4344.patch: validate REMAIN in + src/lib/gssapi/spnego/spnego_mech.c. + - CVE-2014-4344 + * SECURITY UPDATE: denial of service and possible code execution in + kadmind with LDAP backend + - debian/patches/CVE-2014-4345.patch: fix off-by-one in + src/plugins/kdb/ldap/libkdb_ldap/ldap_principal2.c + - CVE-2014-4345 + + -- Marc Deslauriers Fri, 08 Aug 2014 14:58:49 -0400 + krb5 (1.12+dfsg-2ubuntu4) trusty; urgency=low * Add transitional libkadm5srv-mit8 package to help libapt === modified file 'debian/control' --- debian/control 2014-04-09 11:11:43 +0000 +++ debian/control 2014-08-12 11:24:31 +0000 @@ -292,7 +292,8 @@ Package: libkadm5srv-mit8 Section: oldlibs Priority: extra -Architecture: all +Architecture: any +Multi-Arch: same Depends: libkadm5srv-mit9 Description: transitional dummy package for libkadm5srv-mit9 This transitional dummy package is safe to remove. === added file 'debian/patches/CVE-2014-4341-4342.patch' --- debian/patches/CVE-2014-4341-4342.patch 1970-01-01 00:00:00 +0000 +++ debian/patches/CVE-2014-4341-4342.patch 2014-08-12 11:27:20 +0000 @@ -0,0 +1,168 @@ +From fb99962cbd063ac04c9a9d2cc7c75eab73f3533d Mon Sep 17 00:00:00 2001 +From: Greg Hudson +Date: Thu, 19 Jun 2014 13:49:16 -0400 +Subject: [PATCH] Handle invalid RFC 1964 tokens [CVE-2014-4341...] + +Detect the following cases which would otherwise cause invalid memory +accesses and/or integer underflow: + +* An RFC 1964 token being processed by an RFC 4121-only context + [CVE-2014-4342] + +* A header with fewer than 22 bytes after the token ID or an + incomplete checksum [CVE-2014-4341 CVE-2014-4342] + +* A ciphertext shorter than the confounder [CVE-2014-4341] + +* A declared padding length longer than the plaintext [CVE-2014-4341] + +If we detect a bad pad byte, continue on to compute the checksum to +avoid creating a padding oracle, but treat the checksum as invalid +even if it compares equal. + +CVE-2014-4341: + +In MIT krb5, an unauthenticated remote attacker with the ability to +inject packets into a legitimately established GSSAPI application +session can cause a program crash due to invalid memory references +when attempting to read beyond the end of a buffer. + + CVSSv2 Vector: AV:N/AC:M/Au:N/C:N/I:N/A:P/E:POC/RL:OF/RC:C + +CVE-2014-4342: + +In MIT krb5 releases krb5-1.7 and later, an unauthenticated remote +attacker with the ability to inject packets into a legitimately +established GSSAPI application session can cause a program crash due +to invalid memory references when reading beyond the end of a buffer +or by causing a null pointer dereference. + + CVSSv2 Vector: AV:N/AC:M/Au:N/C:N/I:N/A:P/E:POC/RL:OF/RC:C + +[tlyu@mit.edu: CVE summaries, CVSS] + +ticket: 7949 (new) +subject: Handle invalid RFC 1964 tokens [CVE-2014-4341 CVE-2014-4342] +taget_version: 1.12.2 +tags: pullup +--- + src/lib/gssapi/krb5/k5unseal.c | 41 +++++++++++++++++++++++++++++++-------- + src/lib/gssapi/krb5/k5unsealiov.c | 9 ++++++++- + 2 files changed, 41 insertions(+), 9 deletions(-) + +diff --git a/src/lib/gssapi/krb5/k5unseal.c b/src/lib/gssapi/krb5/k5unseal.c +index 30c12b9..0573958 100644 +--- a/src/lib/gssapi/krb5/k5unseal.c ++++ b/src/lib/gssapi/krb5/k5unseal.c +@@ -74,6 +74,7 @@ kg_unseal_v1(context, minor_status, ctx, ptr, bodysize, message_buffer, + int conflen = 0; + int signalg; + int sealalg; ++ int bad_pad = 0; + gss_buffer_desc token; + krb5_checksum cksum; + krb5_checksum md5cksum; +@@ -86,6 +87,7 @@ kg_unseal_v1(context, minor_status, ctx, ptr, bodysize, message_buffer, + krb5_ui_4 seqnum; + OM_uint32 retval; + size_t sumlen; ++ size_t padlen; + krb5_keyusage sign_usage = KG_USAGE_SIGN; + + if (toktype == KG_TOK_SEAL_MSG) { +@@ -93,18 +95,23 @@ kg_unseal_v1(context, minor_status, ctx, ptr, bodysize, message_buffer, + message_buffer->value = NULL; + } + +- /* get the sign and seal algorithms */ +- +- signalg = ptr[0] + (ptr[1]<<8); +- sealalg = ptr[2] + (ptr[3]<<8); +- + /* Sanity checks */ + +- if ((ptr[4] != 0xff) || (ptr[5] != 0xff)) { ++ if (ctx->seq == NULL) { ++ /* ctx was established using a newer enctype, and cannot process RFC ++ * 1964 tokens. */ ++ *minor_status = 0; ++ return GSS_S_DEFECTIVE_TOKEN; ++ } ++ ++ if ((bodysize < 22) || (ptr[4] != 0xff) || (ptr[5] != 0xff)) { + *minor_status = 0; + return GSS_S_DEFECTIVE_TOKEN; + } + ++ signalg = ptr[0] + (ptr[1]<<8); ++ sealalg = ptr[2] + (ptr[3]<<8); ++ + if ((toktype != KG_TOK_SEAL_MSG) && + (sealalg != 0xffff)) { + *minor_status = 0; +@@ -153,6 +160,11 @@ kg_unseal_v1(context, minor_status, ctx, ptr, bodysize, message_buffer, + return GSS_S_DEFECTIVE_TOKEN; + } + ++ if ((size_t)bodysize < 14 + cksum_len) { ++ *minor_status = 0; ++ return GSS_S_DEFECTIVE_TOKEN; ++ } ++ + /* get the token parameters */ + + if ((code = kg_get_seq_num(context, ctx->seq, ptr+14, ptr+6, &direction, +@@ -207,7 +219,20 @@ kg_unseal_v1(context, minor_status, ctx, ptr, bodysize, message_buffer, + plainlen = tmsglen; + + conflen = kg_confounder_size(context, ctx->enc->keyblock.enctype); +- token.length = tmsglen - conflen - plain[tmsglen-1]; ++ if (tmsglen < conflen) { ++ if (sealalg != 0xffff) ++ xfree(plain); ++ *minor_status = 0; ++ return(GSS_S_DEFECTIVE_TOKEN); ++ } ++ padlen = plain[tmsglen - 1]; ++ if (tmsglen - conflen < padlen) { ++ /* Don't error out yet, to avoid padding oracle attacks. We will ++ * treat this as a checksum failure later on. */ ++ padlen = 0; ++ bad_pad = 1; ++ } ++ token.length = tmsglen - conflen - padlen; + + if (token.length) { + if ((token.value = (void *) gssalloc_malloc(token.length)) == NULL) { +@@ -403,7 +428,7 @@ kg_unseal_v1(context, minor_status, ctx, ptr, bodysize, message_buffer, + + /* compare the computed checksum against the transmitted checksum */ + +- if (code) { ++ if (code || bad_pad) { + if (toktype == KG_TOK_SEAL_MSG) + gssalloc_free(token.value); + *minor_status = 0; +diff --git a/src/lib/gssapi/krb5/k5unsealiov.c b/src/lib/gssapi/krb5/k5unsealiov.c +index f7828b8..b654c66 100644 +--- a/src/lib/gssapi/krb5/k5unsealiov.c ++++ b/src/lib/gssapi/krb5/k5unsealiov.c +@@ -69,7 +69,14 @@ kg_unseal_v1_iov(krb5_context context, + return GSS_S_DEFECTIVE_TOKEN; + } + +- if (header->buffer.length < token_wrapper_len + 14) { ++ if (ctx->seq == NULL) { ++ /* ctx was established using a newer enctype, and cannot process RFC ++ * 1964 tokens. */ ++ *minor_status = 0; ++ return GSS_S_DEFECTIVE_TOKEN; ++ } ++ ++ if (header->buffer.length < token_wrapper_len + 22) { + *minor_status = 0; + return GSS_S_DEFECTIVE_TOKEN; + } +-- +2.0.3 + === added file 'debian/patches/CVE-2014-4343.patch' --- debian/patches/CVE-2014-4343.patch 1970-01-01 00:00:00 +0000 +++ debian/patches/CVE-2014-4343.patch 2014-08-12 11:27:20 +0000 @@ -0,0 +1,63 @@ +From f18ddf5d82de0ab7591a36e465bc24225776940f Mon Sep 17 00:00:00 2001 +From: David Woodhouse +Date: Tue, 15 Jul 2014 12:54:15 -0400 +Subject: [PATCH] Fix double-free in SPNEGO [CVE-2014-4343] + +In commit cd7d6b08 ("Verify acceptor's mech in SPNEGO initiator") the +pointer sc->internal_mech became an alias into sc->mech_set->elements, +which should be considered constant for the duration of the SPNEGO +context. So don't free it. + +CVE-2014-4343: + +In MIT krb5 releases 1.10 and newer, an unauthenticated remote +attacker with the ability to spoof packets appearing to be from a +GSSAPI acceptor can cause a double-free condition in GSSAPI initiators +(clients) which are using the SPNEGO mechanism, by returning a +different underlying mechanism than was proposed by the initiator. At +this stage of the negotiation, the acceptor is unauthenticated, and +the acceptor's response could be spoofed by an attacker with the +ability to inject traffic to the initiator. + +Historically, some double-free vulnerabilities can be translated into +remote code execution, though the necessary exploits must be tailored +to the individual application and are usually quite +complicated. Double-frees can also be exploited to cause an +application crash, for a denial of service. However, most GSSAPI +client applications are not vulnerable, as the SPNEGO mechanism is not +used by default (when GSS_C_NO_OID is passed as the mech_type argument +to gss_init_sec_context()). The most common use of SPNEGO is for +HTTP-Negotiate, used in web browsers and other web clients. Most such +clients are believed to not offer HTTP-Negotiate by default, instead +requiring a whitelist of sites for which it may be used to be +configured. If the whitelist is configured to only allow +HTTP-Negotiate over TLS connections ("https://"), a successful +attacker must also spoof the web server's SSL certificate, due to the +way the WWW-Authenticate header is sent in a 401 (Unauthorized) +response message. Unfortunately, many instructions for enabling +HTTP-Negotiate in common web browsers do not include a TLS +requirement. + + CVSSv2 Vector: AV:N/AC:H/Au:N/C:C/I:C/A:C/E:POC/RL:OF/RC:C + +[kaduk@mit.edu: CVE summary and CVSSv2 vector] + +ticket: 7969 (new) +target_version: 1.12.2 +tags: pullup +--- + src/lib/gssapi/spnego/spnego_mech.c | 1 - + 1 file changed, 1 deletion(-) + +Index: krb5-1.12+dfsg/src/lib/gssapi/spnego/spnego_mech.c +=================================================================== +--- krb5-1.12+dfsg.orig/src/lib/gssapi/spnego/spnego_mech.c 2014-08-07 15:29:11.639531607 -0400 ++++ krb5-1.12+dfsg/src/lib/gssapi/spnego/spnego_mech.c 2014-08-07 15:29:11.631531606 -0400 +@@ -787,7 +787,6 @@ + OM_uint32 tmpmin; + size_t i; + +- generic_gss_release_oid(&tmpmin, &sc->internal_mech); + gss_delete_sec_context(&tmpmin, &sc->ctx_handle, + GSS_C_NO_BUFFER); + === added file 'debian/patches/CVE-2014-4344.patch' --- debian/patches/CVE-2014-4344.patch 1970-01-01 00:00:00 +0000 +++ debian/patches/CVE-2014-4344.patch 2014-08-12 11:27:20 +0000 @@ -0,0 +1,46 @@ +From 524688ce87a15fc75f87efc8c039ba4c7d5c197b Mon Sep 17 00:00:00 2001 +From: Greg Hudson +Date: Tue, 15 Jul 2014 12:56:01 -0400 +Subject: [PATCH] Fix null deref in SPNEGO acceptor [CVE-2014-4344] + +When processing a continuation token, acc_ctx_cont was dereferencing +the initial byte of the token without checking the length. This could +result in a null dereference. + +CVE-2014-4344: + +In MIT krb5 1.5 and newer, an unauthenticated or partially +authenticated remote attacker can cause a NULL dereference and +application crash during a SPNEGO negotiation by sending an empty +token as the second or later context token from initiator to acceptor. +The attacker must provide at least one valid context token in the +security context negotiation before sending the empty token. This can +be done by an unauthenticated attacker by forcing SPNEGO to +renegotiate the underlying mechanism, or by using IAKERB to wrap an +unauthenticated AS-REQ as the first token. + + CVSSv2 Vector: AV:N/AC:L/Au:N/C:N/I:N/A:C/E:POC/RL:OF/RC:C + +[kaduk@mit.edu: CVE summary, CVSSv2 vector] + +ticket: 7970 (new) +subject: NULL dereference in SPNEGO acceptor for continuation tokens [CVE-2014-4344] +target_version: 1.12.2 +tags: pullup +--- + src/lib/gssapi/spnego/spnego_mech.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +Index: krb5-1.12+dfsg/src/lib/gssapi/spnego/spnego_mech.c +=================================================================== +--- krb5-1.12+dfsg.orig/src/lib/gssapi/spnego/spnego_mech.c 2014-08-07 15:29:18.755531797 -0400 ++++ krb5-1.12+dfsg/src/lib/gssapi/spnego/spnego_mech.c 2014-08-07 15:29:18.751531797 -0400 +@@ -1432,7 +1432,7 @@ + + ptr = bufstart = buf->value; + #define REMAIN (buf->length - (ptr - bufstart)) +- if (REMAIN > INT_MAX) ++ if (REMAIN == 0 || REMAIN > INT_MAX) + return GSS_S_DEFECTIVE_TOKEN; + + /* === added file 'debian/patches/CVE-2014-4345.patch' --- debian/patches/CVE-2014-4345.patch 1970-01-01 00:00:00 +0000 +++ debian/patches/CVE-2014-4345.patch 2014-08-12 11:27:20 +0000 @@ -0,0 +1,61 @@ +From 81c332e29f10887c6b9deb065f81ba259f4c7e03 Mon Sep 17 00:00:00 2001 +From: Tomas Kuthan +Date: Fri, 1 Aug 2014 15:25:50 +0200 +Subject: [PATCH] Fix LDAP key data segmentation [CVE-2014-4345] + +For principal entries having keys with multiple kvnos (due to use of +-keepold), the LDAP KDB module makes an attempt to store all the keys +having the same kvno into a single krbPrincipalKey attribute value. +There is a fencepost error in the loop, causing currkvno to be set to +the just-processed value instead of the next kvno. As a result, the +second and all following groups of multiple keys by kvno are each +stored in two krbPrincipalKey attribute values. Fix the loop to use +the correct kvno value. + +CVE-2014-4345: + +In MIT krb5, when kadmind is configured to use LDAP for the KDC +database, an authenticated remote attacker can cause it to perform an +out-of-bounds write (buffer overrun) by performing multiple cpw +-keepold operations. An off-by-one error while copying key +information to the new database entry results in keys sharing a common +kvno being written to different array buckets, in an array whose size +is determined by the number of kvnos present. After sufficient +iterations, the extra writes extend past the end of the +(NULL-terminated) array. The NULL terminator is always written after +the end of the loop, so no out-of-bounds data is read, it is only +written. + +Historically, it has been possible to convert an out-of-bounds write +into remote code execution in some cases, though the necessary +exploits must be tailored to the individual application and are +usually quite complicated. Depending on the allocated length of the +array, an out-of-bounds write may also cause a segmentation fault +and/or application crash. + + CVSSv2 Vector: AV:N/AC:M/Au:S/C:C/I:C/A:C/E:POC/RL:OF/RC:C + +[ghudson@mit.edu: clarified commit message] +[kaduk@mit.edu: CVE summary, CVSSv2 vector] + +ticket: 7980 (new) +target_version: 1.12.2 +tags: pullup +--- + src/plugins/kdb/ldap/libkdb_ldap/ldap_principal2.c | 3 ++- + 1 file changed, 2 insertions(+), 1 deletion(-) + +Index: krb5-1.12+dfsg/src/plugins/kdb/ldap/libkdb_ldap/ldap_principal2.c +=================================================================== +--- krb5-1.12+dfsg.orig/src/plugins/kdb/ldap/libkdb_ldap/ldap_principal2.c 2014-08-08 14:58:43.701796377 -0400 ++++ krb5-1.12+dfsg/src/plugins/kdb/ldap/libkdb_ldap/ldap_principal2.c 2014-08-08 14:58:43.693796376 -0400 +@@ -443,7 +443,8 @@ + j++; + last = i + 1; + +- currkvno = key_data[i].key_data_kvno; ++ if (i < n_key_data - 1) ++ currkvno = key_data[i + 1].key_data_kvno; + } + } + ret[num_versions] = NULL; === added file 'debian/patches/Use-TAILQ-macros-instead-of-CIRCLEQ-in-libdb2.patch' --- debian/patches/Use-TAILQ-macros-instead-of-CIRCLEQ-in-libdb2.patch 1970-01-01 00:00:00 +0000 +++ debian/patches/Use-TAILQ-macros-instead-of-CIRCLEQ-in-libdb2.patch 2014-08-12 11:24:31 +0000 @@ -0,0 +1,172 @@ +From c7bb9278ad12c9278f316479af56f9e952f4d650 Mon Sep 17 00:00:00 2001 +From: Greg Hudson +Date: Mon, 17 Feb 2014 00:18:41 -0500 +Subject: [PATCH] Use TAILQ macros instead of CIRCLEQ in libdb2 + +The optimizer in gcc 4.8.1 (but not the current gcc head revision) +breaks the queue.h CIRCLEQ macros, apparently due to an overzealous +strict aliasing deduction. Use TAILQ macros in the libdb2 mpool code +instead. + +(cherry picked from commit 26d874412983c4c9979a9f5e7bec51834ad4cda5) + +ticket: 7860 +version_fixed: 1.12.2 +status: resolved +--- + src/plugins/kdb/db2/libdb2/mpool/mpool.c | 43 +++++++++++++++----------------- + src/plugins/kdb/db2/libdb2/mpool/mpool.h | 8 +++--- + 2 files changed, 24 insertions(+), 27 deletions(-) + +Index: gss-infinite-loop/src/plugins/kdb/db2/libdb2/mpool/mpool.c +=================================================================== +--- gss-infinite-loop.orig/src/plugins/kdb/db2/libdb2/mpool/mpool.c 2014-07-30 21:02:43.442581194 -0400 ++++ gss-infinite-loop/src/plugins/kdb/db2/libdb2/mpool/mpool.c 2014-07-30 21:02:43.442581194 -0400 +@@ -81,9 +81,9 @@ + /* Allocate and initialize the MPOOL cookie. */ + if ((mp = (MPOOL *)calloc(1, sizeof(MPOOL))) == NULL) + return (NULL); +- CIRCLEQ_INIT(&mp->lqh); ++ TAILQ_INIT(&mp->lqh); + for (entry = 0; entry < HASHSIZE; ++entry) +- CIRCLEQ_INIT(&mp->hqh[entry]); ++ TAILQ_INIT(&mp->hqh[entry]); + mp->maxcache = maxcache; + mp->npages = sb.st_size / pagesize; + mp->pagesize = pagesize; +@@ -143,8 +143,8 @@ + bp->flags = MPOOL_PINNED | MPOOL_INUSE; + + head = &mp->hqh[HASHKEY(bp->pgno)]; +- CIRCLEQ_INSERT_HEAD(head, bp, hq); +- CIRCLEQ_INSERT_TAIL(&mp->lqh, bp, q); ++ TAILQ_INSERT_HEAD(head, bp, hq); ++ TAILQ_INSERT_TAIL(&mp->lqh, bp, q); + return (bp->page); + } + +@@ -168,8 +168,8 @@ + + /* Remove from the hash and lru queues. */ + head = &mp->hqh[HASHKEY(bp->pgno)]; +- CIRCLEQ_REMOVE(head, bp, hq); +- CIRCLEQ_REMOVE(&mp->lqh, bp, q); ++ TAILQ_REMOVE(head, bp, hq); ++ TAILQ_REMOVE(&mp->lqh, bp, q); + + free(bp); + return (RET_SUCCESS); +@@ -208,10 +208,10 @@ + * of the lru chain. + */ + head = &mp->hqh[HASHKEY(bp->pgno)]; +- CIRCLEQ_REMOVE(head, bp, hq); +- CIRCLEQ_INSERT_HEAD(head, bp, hq); +- CIRCLEQ_REMOVE(&mp->lqh, bp, q); +- CIRCLEQ_INSERT_TAIL(&mp->lqh, bp, q); ++ TAILQ_REMOVE(head, bp, hq); ++ TAILQ_INSERT_HEAD(head, bp, hq); ++ TAILQ_REMOVE(&mp->lqh, bp, q); ++ TAILQ_INSERT_TAIL(&mp->lqh, bp, q); + + /* Return a pinned page. */ + bp->flags |= MPOOL_PINNED; +@@ -261,8 +261,8 @@ + * of the lru chain. + */ + head = &mp->hqh[HASHKEY(bp->pgno)]; +- CIRCLEQ_INSERT_HEAD(head, bp, hq); +- CIRCLEQ_INSERT_TAIL(&mp->lqh, bp, q); ++ TAILQ_INSERT_HEAD(head, bp, hq); ++ TAILQ_INSERT_TAIL(&mp->lqh, bp, q); + + /* Run through the user's filter. */ + if (mp->pgin != NULL) +@@ -311,8 +311,8 @@ + BKT *bp; + + /* Free up any space allocated to the lru pages. */ +- while ((bp = mp->lqh.cqh_first) != (void *)&mp->lqh) { +- CIRCLEQ_REMOVE(&mp->lqh, mp->lqh.cqh_first, q); ++ while ((bp = mp->lqh.tqh_first) != NULL) { ++ TAILQ_REMOVE(&mp->lqh, mp->lqh.tqh_first, q); + free(bp); + } + +@@ -332,8 +332,7 @@ + BKT *bp; + + /* Walk the lru chain, flushing any dirty pages to disk. */ +- for (bp = mp->lqh.cqh_first; +- bp != (void *)&mp->lqh; bp = bp->q.cqe_next) ++ for (bp = mp->lqh.tqh_first; bp != NULL; bp = bp->q.tqe_next) + if (bp->flags & MPOOL_DIRTY && + mpool_write(mp, bp) == RET_ERROR) + return (RET_ERROR); +@@ -363,8 +362,7 @@ + * off any lists. If we don't find anything we grow the cache anyway. + * The cache never shrinks. + */ +- for (bp = mp->lqh.cqh_first; +- bp != (void *)&mp->lqh; bp = bp->q.cqe_next) ++ for (bp = mp->lqh.tqh_first; bp != NULL; bp = bp->q.tqe_next) + if (!(bp->flags & MPOOL_PINNED)) { + /* Flush if dirty. */ + if (bp->flags & MPOOL_DIRTY && +@@ -375,8 +373,8 @@ + #endif + /* Remove from the hash and lru queues. */ + head = &mp->hqh[HASHKEY(bp->pgno)]; +- CIRCLEQ_REMOVE(head, bp, hq); +- CIRCLEQ_REMOVE(&mp->lqh, bp, q); ++ TAILQ_REMOVE(head, bp, hq); ++ TAILQ_REMOVE(&mp->lqh, bp, q); + #if defined(DEBUG) && !defined(DEBUG_IDX0SPLIT) + { void *spage; + spage = bp->page; +@@ -450,7 +448,7 @@ + BKT *bp; + + head = &mp->hqh[HASHKEY(pgno)]; +- for (bp = head->cqh_first; bp != (void *)head; bp = bp->hq.cqe_next) ++ for (bp = head->tqh_first; bp != NULL; bp = bp->hq.tqe_next) + if ((bp->pgno == pgno) && (bp->flags & MPOOL_INUSE)) { + #ifdef STATISTICS + ++mp->cachehit; +@@ -494,8 +492,7 @@ + + sep = ""; + cnt = 0; +- for (bp = mp->lqh.cqh_first; +- bp != (void *)&mp->lqh; bp = bp->q.cqe_next) { ++ for (bp = mp->lqh.tqh_first; bp != NULL; bp = bp->q.tqe_next) { + (void)fprintf(stderr, "%s%d", sep, bp->pgno); + if (bp->flags & MPOOL_DIRTY) + (void)fprintf(stderr, "d"); +Index: gss-infinite-loop/src/plugins/kdb/db2/libdb2/mpool/mpool.h +=================================================================== +--- gss-infinite-loop.orig/src/plugins/kdb/db2/libdb2/mpool/mpool.h 2014-07-30 21:02:43.442581194 -0400 ++++ gss-infinite-loop/src/plugins/kdb/db2/libdb2/mpool/mpool.h 2014-07-30 21:02:43.442581194 -0400 +@@ -47,8 +47,8 @@ + + /* The BKT structures are the elements of the queues. */ + typedef struct _bkt { +- CIRCLEQ_ENTRY(_bkt) hq; /* hash queue */ +- CIRCLEQ_ENTRY(_bkt) q; /* lru queue */ ++ TAILQ_ENTRY(_bkt) hq; /* hash queue */ ++ TAILQ_ENTRY(_bkt) q; /* lru queue */ + void *page; /* page */ + db_pgno_t pgno; /* page number */ + +@@ -59,9 +59,9 @@ + } BKT; + + typedef struct MPOOL { +- CIRCLEQ_HEAD(_lqh, _bkt) lqh; /* lru queue head */ ++ TAILQ_HEAD(_lqh, _bkt) lqh; /* lru queue head */ + /* hash queue array */ +- CIRCLEQ_HEAD(_hqh, _bkt) hqh[HASHSIZE]; ++ TAILQ_HEAD(_hqh, _bkt) hqh[HASHSIZE]; + db_pgno_t curcache; /* current number of cached pages */ + db_pgno_t maxcache; /* max number of cached pages */ + db_pgno_t npages; /* number of pages in the file */ === added file 'debian/patches/avoid_mechglue_recursive_calls_new_symbols' --- debian/patches/avoid_mechglue_recursive_calls_new_symbols 1970-01-01 00:00:00 +0000 +++ debian/patches/avoid_mechglue_recursive_calls_new_symbols 2014-08-12 11:24:31 +0000 @@ -0,0 +1,20 @@ +Index: krb5/src/lib/gssapi/mechglue/g_initialize.c +=================================================================== +--- krb5.orig/src/lib/gssapi/mechglue/g_initialize.c 2014-06-04 15:24:20.491589000 -0400 ++++ krb5/src/lib/gssapi/mechglue/g_initialize.c 2014-06-04 15:28:14.392939775 -0400 +@@ -678,11 +678,11 @@ + GSS_ADD_DYNAMIC_METHOD_NOLOOP(dl, mech, gss_inquire_mech_for_saslname); + /* RFC 5587 */ + GSS_ADD_DYNAMIC_METHOD_NOLOOP(dl, mech, gss_inquire_attrs_for_mech); +- GSS_ADD_DYNAMIC_METHOD(dl, mech, gss_acquire_cred_from); +- GSS_ADD_DYNAMIC_METHOD(dl, mech, gss_store_cred_into); ++ GSS_ADD_DYNAMIC_METHOD_NOLOOP(dl, mech, gss_acquire_cred_from); ++ GSS_ADD_DYNAMIC_METHOD_NOLOOP(dl, mech, gss_store_cred_into); + GSS_ADD_DYNAMIC_METHOD(dl, mech, gssspi_acquire_cred_with_password); +- GSS_ADD_DYNAMIC_METHOD(dl, mech, gss_export_cred); +- GSS_ADD_DYNAMIC_METHOD(dl, mech, gss_import_cred); ++ GSS_ADD_DYNAMIC_METHOD_NOLOOP(dl, mech, gss_export_cred); ++ GSS_ADD_DYNAMIC_METHOD_NOLOOP(dl, mech, gss_import_cred); + GSS_ADD_DYNAMIC_METHOD(dl, mech, gssspi_import_sec_context_by_mech); + GSS_ADD_DYNAMIC_METHOD(dl, mech, gssspi_import_name_by_mech); + GSS_ADD_DYNAMIC_METHOD(dl, mech, gssspi_import_cred_by_mech); === modified file 'debian/patches/series' --- debian/patches/series 2014-02-04 14:29:23 +0000 +++ debian/patches/series 2014-08-12 11:28:10 +0000 @@ -7,3 +7,10 @@ debian-local/0007-Add-substpdf-target.patch 0008-autoreconf.patch debian-local/0009-.gbp.conf.patch +avoid_mechglue_recursive_calls_new_symbols +Use-TAILQ-macros-instead-of-CIRCLEQ-in-libdb2.patch +CVE-2014-4341-4342.patch +CVE-2014-4343.patch +CVE-2014-4344.patch +CVE-2014-4345.patch + === modified file 'src/lib/gssapi/krb5/k5unseal.c' --- src/lib/gssapi/krb5/k5unseal.c 2013-10-28 16:12:52 +0000 +++ src/lib/gssapi/krb5/k5unseal.c 2014-08-12 11:29:31 +0000 @@ -74,6 +74,7 @@ int conflen = 0; int signalg; int sealalg; + int bad_pad = 0; gss_buffer_desc token; krb5_checksum cksum; krb5_checksum md5cksum; @@ -86,6 +87,7 @@ krb5_ui_4 seqnum; OM_uint32 retval; size_t sumlen; + size_t padlen; krb5_keyusage sign_usage = KG_USAGE_SIGN; if (toktype == KG_TOK_SEAL_MSG) { @@ -93,18 +95,23 @@ message_buffer->value = NULL; } - /* get the sign and seal algorithms */ + /* Sanity checks */ + + if (ctx->seq == NULL) { + /* ctx was established using a newer enctype, and cannot process RFC + * 1964 tokens. */ + *minor_status = 0; + return GSS_S_DEFECTIVE_TOKEN; + } + + if ((bodysize < 22) || (ptr[4] != 0xff) || (ptr[5] != 0xff)) { + *minor_status = 0; + return GSS_S_DEFECTIVE_TOKEN; + } signalg = ptr[0] + (ptr[1]<<8); sealalg = ptr[2] + (ptr[3]<<8); - /* Sanity checks */ - - if ((ptr[4] != 0xff) || (ptr[5] != 0xff)) { - *minor_status = 0; - return GSS_S_DEFECTIVE_TOKEN; - } - if ((toktype != KG_TOK_SEAL_MSG) && (sealalg != 0xffff)) { *minor_status = 0; @@ -153,6 +160,11 @@ return GSS_S_DEFECTIVE_TOKEN; } + if ((size_t)bodysize < 14 + cksum_len) { + *minor_status = 0; + return GSS_S_DEFECTIVE_TOKEN; + } + /* get the token parameters */ if ((code = kg_get_seq_num(context, ctx->seq, ptr+14, ptr+6, &direction, @@ -207,7 +219,20 @@ plainlen = tmsglen; conflen = kg_confounder_size(context, ctx->enc->keyblock.enctype); - token.length = tmsglen - conflen - plain[tmsglen-1]; + if (tmsglen < conflen) { + if (sealalg != 0xffff) + xfree(plain); + *minor_status = 0; + return(GSS_S_DEFECTIVE_TOKEN); + } + padlen = plain[tmsglen - 1]; + if (tmsglen - conflen < padlen) { + /* Don't error out yet, to avoid padding oracle attacks. We will + * treat this as a checksum failure later on. */ + padlen = 0; + bad_pad = 1; + } + token.length = tmsglen - conflen - padlen; if (token.length) { if ((token.value = (void *) gssalloc_malloc(token.length)) == NULL) { @@ -403,7 +428,7 @@ /* compare the computed checksum against the transmitted checksum */ - if (code) { + if (code || bad_pad) { if (toktype == KG_TOK_SEAL_MSG) gssalloc_free(token.value); *minor_status = 0; === modified file 'src/lib/gssapi/krb5/k5unsealiov.c' --- src/lib/gssapi/krb5/k5unsealiov.c 2013-10-28 16:12:52 +0000 +++ src/lib/gssapi/krb5/k5unsealiov.c 2014-08-12 11:29:31 +0000 @@ -69,7 +69,14 @@ return GSS_S_DEFECTIVE_TOKEN; } - if (header->buffer.length < token_wrapper_len + 14) { + if (ctx->seq == NULL) { + /* ctx was established using a newer enctype, and cannot process RFC + * 1964 tokens. */ + *minor_status = 0; + return GSS_S_DEFECTIVE_TOKEN; + } + + if (header->buffer.length < token_wrapper_len + 22) { *minor_status = 0; return GSS_S_DEFECTIVE_TOKEN; } === modified file 'src/lib/gssapi/mechglue/g_initialize.c' --- src/lib/gssapi/mechglue/g_initialize.c 2013-10-28 16:12:52 +0000 +++ src/lib/gssapi/mechglue/g_initialize.c 2014-08-12 11:29:31 +0000 @@ -678,11 +678,11 @@ GSS_ADD_DYNAMIC_METHOD_NOLOOP(dl, mech, gss_inquire_mech_for_saslname); /* RFC 5587 */ GSS_ADD_DYNAMIC_METHOD_NOLOOP(dl, mech, gss_inquire_attrs_for_mech); - GSS_ADD_DYNAMIC_METHOD(dl, mech, gss_acquire_cred_from); - GSS_ADD_DYNAMIC_METHOD(dl, mech, gss_store_cred_into); + GSS_ADD_DYNAMIC_METHOD_NOLOOP(dl, mech, gss_acquire_cred_from); + GSS_ADD_DYNAMIC_METHOD_NOLOOP(dl, mech, gss_store_cred_into); GSS_ADD_DYNAMIC_METHOD(dl, mech, gssspi_acquire_cred_with_password); - GSS_ADD_DYNAMIC_METHOD(dl, mech, gss_export_cred); - GSS_ADD_DYNAMIC_METHOD(dl, mech, gss_import_cred); + GSS_ADD_DYNAMIC_METHOD_NOLOOP(dl, mech, gss_export_cred); + GSS_ADD_DYNAMIC_METHOD_NOLOOP(dl, mech, gss_import_cred); GSS_ADD_DYNAMIC_METHOD(dl, mech, gssspi_import_sec_context_by_mech); GSS_ADD_DYNAMIC_METHOD(dl, mech, gssspi_import_name_by_mech); GSS_ADD_DYNAMIC_METHOD(dl, mech, gssspi_import_cred_by_mech); === modified file 'src/lib/gssapi/spnego/spnego_mech.c' --- src/lib/gssapi/spnego/spnego_mech.c 2013-12-02 12:25:43 +0000 +++ src/lib/gssapi/spnego/spnego_mech.c 2014-08-12 11:29:31 +0000 @@ -787,7 +787,6 @@ OM_uint32 tmpmin; size_t i; - generic_gss_release_oid(&tmpmin, &sc->internal_mech); gss_delete_sec_context(&tmpmin, &sc->ctx_handle, GSS_C_NO_BUFFER); @@ -1433,7 +1432,7 @@ ptr = bufstart = buf->value; #define REMAIN (buf->length - (ptr - bufstart)) - if (REMAIN > INT_MAX) + if (REMAIN == 0 || REMAIN > INT_MAX) return GSS_S_DEFECTIVE_TOKEN; /* === modified file 'src/plugins/kdb/db2/libdb2/mpool/mpool.c' --- src/plugins/kdb/db2/libdb2/mpool/mpool.c 2010-01-03 17:54:04 +0000 +++ src/plugins/kdb/db2/libdb2/mpool/mpool.c 2014-08-12 11:29:31 +0000 @@ -81,9 +81,9 @@ /* Allocate and initialize the MPOOL cookie. */ if ((mp = (MPOOL *)calloc(1, sizeof(MPOOL))) == NULL) return (NULL); - CIRCLEQ_INIT(&mp->lqh); + TAILQ_INIT(&mp->lqh); for (entry = 0; entry < HASHSIZE; ++entry) - CIRCLEQ_INIT(&mp->hqh[entry]); + TAILQ_INIT(&mp->hqh[entry]); mp->maxcache = maxcache; mp->npages = sb.st_size / pagesize; mp->pagesize = pagesize; @@ -143,8 +143,8 @@ bp->flags = MPOOL_PINNED | MPOOL_INUSE; head = &mp->hqh[HASHKEY(bp->pgno)]; - CIRCLEQ_INSERT_HEAD(head, bp, hq); - CIRCLEQ_INSERT_TAIL(&mp->lqh, bp, q); + TAILQ_INSERT_HEAD(head, bp, hq); + TAILQ_INSERT_TAIL(&mp->lqh, bp, q); return (bp->page); } @@ -168,8 +168,8 @@ /* Remove from the hash and lru queues. */ head = &mp->hqh[HASHKEY(bp->pgno)]; - CIRCLEQ_REMOVE(head, bp, hq); - CIRCLEQ_REMOVE(&mp->lqh, bp, q); + TAILQ_REMOVE(head, bp, hq); + TAILQ_REMOVE(&mp->lqh, bp, q); free(bp); return (RET_SUCCESS); @@ -208,10 +208,10 @@ * of the lru chain. */ head = &mp->hqh[HASHKEY(bp->pgno)]; - CIRCLEQ_REMOVE(head, bp, hq); - CIRCLEQ_INSERT_HEAD(head, bp, hq); - CIRCLEQ_REMOVE(&mp->lqh, bp, q); - CIRCLEQ_INSERT_TAIL(&mp->lqh, bp, q); + TAILQ_REMOVE(head, bp, hq); + TAILQ_INSERT_HEAD(head, bp, hq); + TAILQ_REMOVE(&mp->lqh, bp, q); + TAILQ_INSERT_TAIL(&mp->lqh, bp, q); /* Return a pinned page. */ bp->flags |= MPOOL_PINNED; @@ -261,8 +261,8 @@ * of the lru chain. */ head = &mp->hqh[HASHKEY(bp->pgno)]; - CIRCLEQ_INSERT_HEAD(head, bp, hq); - CIRCLEQ_INSERT_TAIL(&mp->lqh, bp, q); + TAILQ_INSERT_HEAD(head, bp, hq); + TAILQ_INSERT_TAIL(&mp->lqh, bp, q); /* Run through the user's filter. */ if (mp->pgin != NULL) @@ -311,8 +311,8 @@ BKT *bp; /* Free up any space allocated to the lru pages. */ - while ((bp = mp->lqh.cqh_first) != (void *)&mp->lqh) { - CIRCLEQ_REMOVE(&mp->lqh, mp->lqh.cqh_first, q); + while ((bp = mp->lqh.tqh_first) != NULL) { + TAILQ_REMOVE(&mp->lqh, mp->lqh.tqh_first, q); free(bp); } @@ -332,8 +332,7 @@ BKT *bp; /* Walk the lru chain, flushing any dirty pages to disk. */ - for (bp = mp->lqh.cqh_first; - bp != (void *)&mp->lqh; bp = bp->q.cqe_next) + for (bp = mp->lqh.tqh_first; bp != NULL; bp = bp->q.tqe_next) if (bp->flags & MPOOL_DIRTY && mpool_write(mp, bp) == RET_ERROR) return (RET_ERROR); @@ -363,8 +362,7 @@ * off any lists. If we don't find anything we grow the cache anyway. * The cache never shrinks. */ - for (bp = mp->lqh.cqh_first; - bp != (void *)&mp->lqh; bp = bp->q.cqe_next) + for (bp = mp->lqh.tqh_first; bp != NULL; bp = bp->q.tqe_next) if (!(bp->flags & MPOOL_PINNED)) { /* Flush if dirty. */ if (bp->flags & MPOOL_DIRTY && @@ -375,8 +373,8 @@ #endif /* Remove from the hash and lru queues. */ head = &mp->hqh[HASHKEY(bp->pgno)]; - CIRCLEQ_REMOVE(head, bp, hq); - CIRCLEQ_REMOVE(&mp->lqh, bp, q); + TAILQ_REMOVE(head, bp, hq); + TAILQ_REMOVE(&mp->lqh, bp, q); #if defined(DEBUG) && !defined(DEBUG_IDX0SPLIT) { void *spage; spage = bp->page; @@ -450,7 +448,7 @@ BKT *bp; head = &mp->hqh[HASHKEY(pgno)]; - for (bp = head->cqh_first; bp != (void *)head; bp = bp->hq.cqe_next) + for (bp = head->tqh_first; bp != NULL; bp = bp->hq.tqe_next) if ((bp->pgno == pgno) && (bp->flags & MPOOL_INUSE)) { #ifdef STATISTICS ++mp->cachehit; @@ -494,8 +492,7 @@ sep = ""; cnt = 0; - for (bp = mp->lqh.cqh_first; - bp != (void *)&mp->lqh; bp = bp->q.cqe_next) { + for (bp = mp->lqh.tqh_first; bp != NULL; bp = bp->q.tqe_next) { (void)fprintf(stderr, "%s%d", sep, bp->pgno); if (bp->flags & MPOOL_DIRTY) (void)fprintf(stderr, "d"); === modified file 'src/plugins/kdb/db2/libdb2/mpool/mpool.h' --- src/plugins/kdb/db2/libdb2/mpool/mpool.h 2007-05-08 14:46:55 +0000 +++ src/plugins/kdb/db2/libdb2/mpool/mpool.h 2014-08-12 11:29:31 +0000 @@ -47,8 +47,8 @@ /* The BKT structures are the elements of the queues. */ typedef struct _bkt { - CIRCLEQ_ENTRY(_bkt) hq; /* hash queue */ - CIRCLEQ_ENTRY(_bkt) q; /* lru queue */ + TAILQ_ENTRY(_bkt) hq; /* hash queue */ + TAILQ_ENTRY(_bkt) q; /* lru queue */ void *page; /* page */ db_pgno_t pgno; /* page number */ @@ -59,9 +59,9 @@ } BKT; typedef struct MPOOL { - CIRCLEQ_HEAD(_lqh, _bkt) lqh; /* lru queue head */ + TAILQ_HEAD(_lqh, _bkt) lqh; /* lru queue head */ /* hash queue array */ - CIRCLEQ_HEAD(_hqh, _bkt) hqh[HASHSIZE]; + TAILQ_HEAD(_hqh, _bkt) hqh[HASHSIZE]; db_pgno_t curcache; /* current number of cached pages */ db_pgno_t maxcache; /* max number of cached pages */ db_pgno_t npages; /* number of pages in the file */ === modified file 'src/plugins/kdb/ldap/libkdb_ldap/ldap_principal2.c' --- src/plugins/kdb/ldap/libkdb_ldap/ldap_principal2.c 2013-10-28 16:12:52 +0000 +++ src/plugins/kdb/ldap/libkdb_ldap/ldap_principal2.c 2014-08-12 11:29:31 +0000 @@ -443,7 +443,8 @@ j++; last = i + 1; - currkvno = key_data[i].key_data_kvno; + if (i < n_key_data - 1) + currkvno = key_data[i + 1].key_data_kvno; } } ret[num_versions] = NULL;