/* GSSAPI SASL plugin
 * Leif Johansson
 * Rob Siemborski (SASL v2 Conversion)
 * $Id: gssapi.c,v 1.115 2011/11/21 15:12:35 mel Exp $
 */
/* 
 * Copyright (c) 1998-2003 Carnegie Mellon University.  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. The name "Carnegie Mellon University" must not be used to
 *    endorse or promote products derived from this software without
 *    prior written permission. For permission or any other legal
 *    details, please contact  
 *      Office of Technology Transfer
 *      Carnegie Mellon University
 *      5000 Forbes Avenue
 *      Pittsburgh, PA  15213-3890
 *      (412) 268-4387, fax: (412) 268-7395
 *      tech-transfer@andrew.cmu.edu
 *
 * 4. Redistributions of any form whatsoever must retain the following
 *    acknowledgment:
 *    "This product includes software developed by Computing Services
 *     at Carnegie Mellon University (http://www.cmu.edu/computing/)."
 *
 * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO
 * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
 * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY 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.
 */

#include <config.h>

#ifdef HAVE_GSSAPI_H
#include <gssapi.h>
#else
#include <gssapi/gssapi.h>
#endif

#ifdef WIN32
#  include <winsock2.h>

#  ifndef R_OK
#    define R_OK 04
#  endif
/* we also need io.h for access() prototype */
#  include <io.h>
#else
#  include <sys/param.h>
#  include <sys/socket.h>
#  include <netinet/in.h>
#  include <arpa/inet.h>
#  include <netdb.h>
#endif /* WIN32 */
#include <fcntl.h>
#include <stdio.h>
#include <sasl.h>
#include <saslutil.h>
#include <saslplug.h>

#include "plugin_common.h"

#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif

#include <errno.h>
#include <assert.h>

/*****************************  Common Section  *****************************/

static const char plugin_id[] = "$Id: gssapi.c,v 1.115 2011/11/21 15:12:35 mel Exp $";

static const char * GSSAPI_BLANK_STRING = "";

static gss_OID_desc gss_spnego_oid = { 6, (void *) "\x2b\x06\x01\x05\x05\x02" };

#if !defined(HAVE_GSS_C_NT_HOSTBASED_SERVICE) && !defined(GSS_C_NT_HOSTBASED_SERVICE)
extern gss_OID gss_nt_service_name;
#define GSS_C_NT_HOSTBASED_SERVICE gss_nt_service_name
#endif

#ifdef WANT_KERBEROS5_3DES
/* Check if CyberSafe flag is defined */
#ifdef CSF_GSS_C_DES3_FLAG
#define K5_MAX_SSF	112
#endif

/* Heimdal and MIT use the following */
#ifdef GSS_KRB5_CONF_C_QOP_DES3_KD
#define K5_MAX_SSF	112
#endif

#endif

#ifndef K5_MAX_SSF
/* All Kerberos implementations support DES */
#define K5_MAX_SSF	56
#endif

/* GSSAPI SASL Mechanism by Leif Johansson <leifj@matematik.su.se>
 * inspired by the kerberos mechanism and the gssapi_server and
 * gssapi_client from the heimdal distribution by Assar Westerlund
 * <assar@sics.se> and Johan Danielsson <joda@pdc.kth.se>. 
 * See the configure.in file for details on dependencies.
 *
 * Important contributions from Sam Hartman <hartmans@fundsxpress.com>.
 *
 * This code was tested with the following distributions of Kerberos:
 * Heimdal (http://www.pdc.kth.se/heimdal), MIT (http://web.mit.edu/kerberos/www/)
 * CyberSafe (http://www.cybersafe.com/) and SEAM.
 */

#ifdef GSS_USE_MUTEXES
#define GSS_LOCK_MUTEX(utils)  \
    if(((sasl_utils_t *)(utils))->mutex_lock(gss_mutex) != 0) { \
       return SASL_FAIL; \
    }

#define GSS_UNLOCK_MUTEX(utils) \
    if(((sasl_utils_t *)(utils))->mutex_unlock(gss_mutex) != 0) { \
        return SASL_FAIL; \
    }

static void *gss_mutex = NULL;
#else
#define GSS_LOCK_MUTEX(utils)
#define GSS_UNLOCK_MUTEX(utils)
#endif

typedef struct context {
    int state;

    gss_OID mech_type;		     /* GSS-SPNEGO or GSSAPI */
    int http_mode;		     /* use RFC 4559 compatible protocol? */
    
    gss_ctx_id_t gss_ctx;
    gss_name_t   client_name;
    gss_name_t   server_name;
    gss_cred_id_t server_creds;
    gss_cred_id_t client_creds;

    sasl_ssf_t limitssf, requiressf; /* application defined bounds, for the
					server */
    unsigned char qop;		     /* as allowed by GSSAPI */

    const sasl_utils_t *utils;
    
    /* layers buffering */
    decode_context_t decode_context;
    
    char *encode_buf;                /* For encoding/decoding mem management */
    char *decode_buf;
    char *decode_once_buf;
    unsigned encode_buf_len;
    unsigned decode_buf_len;
    unsigned decode_once_buf_len;
    buffer_info_t *enc_in_buf;
    
    char *out_buf;                   /* per-step mem management */
    unsigned out_buf_len;    
    
    char *authid; /* hold the authid between steps - server */
    const char *user;   /* hold the userid between steps - client */
} context_t;

enum {
    SASL_GSSAPI_STATE_AUTHNEG = 1,
    SASL_GSSAPI_STATE_SSFCAP = 2,
    SASL_GSSAPI_STATE_SSFREQ = 3,
    SASL_GSSAPI_STATE_AUTHENTICATED = 4
};

#define LAYER_CONFIDENTIALITY	4
#define LAYER_INTEGRITY		2
#define LAYER_NONE		1

/* sasl_gss_log: only logs status string returned from gss_display_status() */
#define sasl_gss_log(x,y,z) sasl_gss_seterror_(x,y,z,1)
#define sasl_gss_seterror(x,y,z) sasl_gss_seterror_(x,y,z,0)

static int
sasl_gss_seterror_(const sasl_utils_t *utils, OM_uint32 maj, OM_uint32 min,
		   int logonly)
{
    OM_uint32 maj_stat, min_stat;
    gss_buffer_desc msg;
    OM_uint32 msg_ctx;
    int ret;
    char *out = NULL;
    size_t len, curlen = 0;
    const char prefix[] = "GSSAPI Error: ";

    if (!utils) return SASL_OK;
    
    len = sizeof(prefix);
    ret = _plug_buf_alloc(utils, &out, &curlen, 256);
    if (ret != SASL_OK) return SASL_NOMEM;
    
    strcpy(out, prefix);
    
    msg_ctx = 0;
    while (1) {
	GSS_LOCK_MUTEX(utils);
	maj_stat = gss_display_status(&min_stat, maj,
				      GSS_C_GSS_CODE, GSS_C_NULL_OID,
				      &msg_ctx, &msg);
	GSS_UNLOCK_MUTEX(utils);
	
	if(GSS_ERROR(maj_stat)) {
	    if (logonly) {
		utils->log(utils->conn, SASL_LOG_FAIL,
			"GSSAPI Failure: (could not get major error message)");
	    } else {
		utils->seterror(utils->conn, 0,
				"GSSAPI Failure "
				"(could not get major error message)");
	    }
	    utils->free(out);
	    return SASL_OK;
	}
	
	len += len + msg.length;
	ret = _plug_buf_alloc(utils, &out, &curlen, len);
	
	if(ret != SASL_OK) {
	    utils->free(out);
	    return SASL_NOMEM;
	}
	
	strcat(out, msg.value);
	
	GSS_LOCK_MUTEX(utils);
	gss_release_buffer(&min_stat, &msg);
	GSS_UNLOCK_MUTEX(utils);
	
	if (!msg_ctx)
	    break;
    }
    
    /* Now get the minor status */
    
    len += 2;
    ret = _plug_buf_alloc(utils, &out, &curlen, len);
    if(ret != SASL_OK) {
	utils->free(out);
	return SASL_NOMEM;
    }
    
    strcat(out, " (");
    
    msg_ctx = 0;
    while (1) {
	GSS_LOCK_MUTEX(utils);
	maj_stat = gss_display_status(&min_stat, min,
				      GSS_C_MECH_CODE, GSS_C_NULL_OID,
				      &msg_ctx, &msg);
	GSS_UNLOCK_MUTEX(utils);
	
	if(GSS_ERROR(maj_stat)) {
	    if (logonly) {
		utils->log(utils->conn, SASL_LOG_FAIL,
			"GSSAPI Failure: (could not get minor error message)");
	    } else {
		utils->seterror(utils->conn, 0,
				"GSSAPI Failure "
				"(could not get minor error message)");
	    }
	    utils->free(out);
	    return SASL_OK;
	}
	
	len += len + msg.length;

	ret = _plug_buf_alloc(utils, &out, &curlen, len);
	if(ret != SASL_OK) {
	    utils->free(out);
	    return SASL_NOMEM;
	}
	
	strcat(out, msg.value);
	
	GSS_LOCK_MUTEX(utils);
	gss_release_buffer(&min_stat, &msg);
	GSS_UNLOCK_MUTEX(utils);
	
	if (!msg_ctx)
	    break;
    }
    
    len += 1;
    ret = _plug_buf_alloc(utils, &out, &curlen, len);
    if(ret != SASL_OK) {
	utils->free(out);
	return SASL_NOMEM;
    }
    
    strcat(out, ")");
    
    if (logonly) {
	utils->log(utils->conn, SASL_LOG_FAIL, out);
    } else {
	utils->seterror(utils->conn, 0, out);
    }
    utils->free(out);

    return SASL_OK;
}

static int 
sasl_gss_encode(void *context, const struct iovec *invec, unsigned numiov,
		const char **output, unsigned *outputlen, int privacy)
{
    context_t *text = (context_t *)context;
    OM_uint32 maj_stat, min_stat;
    gss_buffer_t input_token, output_token;
    gss_buffer_desc real_input_token, real_output_token;
    int ret;
    struct buffer_info *inblob, bufinfo;
    
    if (!output) return SASL_BADPARAM;
    
    if (numiov > 1) {
	ret = _plug_iovec_to_buf(text->utils, invec, numiov, &text->enc_in_buf);
	if (ret != SASL_OK) return ret;
	inblob = text->enc_in_buf;
    } else {
	bufinfo.data = invec[0].iov_base;
	bufinfo.curlen = invec[0].iov_len;
	inblob = &bufinfo;
    }
    
    if (text->state != SASL_GSSAPI_STATE_AUTHENTICATED) return SASL_NOTDONE;
    
    input_token = &real_input_token;
    
    real_input_token.value  = inblob->data;
    real_input_token.length = inblob->curlen;
    
    output_token = &real_output_token;
    output_token->value = NULL;
    output_token->length = 0;
    
    GSS_LOCK_MUTEX(text->utils);
    maj_stat = gss_wrap (&min_stat,
			 text->gss_ctx,
			 privacy,
			 GSS_C_QOP_DEFAULT,
			 input_token,
			 NULL,
			 output_token);
    GSS_UNLOCK_MUTEX(text->utils);
    
    if (GSS_ERROR(maj_stat)) {
	sasl_gss_seterror(text->utils, maj_stat, min_stat);
	if (output_token->value) {
	    GSS_LOCK_MUTEX(text->utils);
	    gss_release_buffer(&min_stat, output_token);
	    GSS_UNLOCK_MUTEX(text->utils);
	}
	return SASL_FAIL;
    }
    
    if (output_token->value && output) {
	unsigned char * p;
	
	ret = _plug_buf_alloc(text->utils,
			      &(text->encode_buf),
			      &(text->encode_buf_len),
			      output_token->length + 4);
	
	if (ret != SASL_OK) {
	    GSS_LOCK_MUTEX(text->utils);
	    gss_release_buffer(&min_stat, output_token);
	    GSS_UNLOCK_MUTEX(text->utils);
	    return ret;
	}

	p = (unsigned char *) text->encode_buf;
	
	p[0] = (output_token->length>>24) & 0xFF;
	p[1] = (output_token->length>>16) & 0xFF;
	p[2] = (output_token->length>>8) & 0xFF;
	p[3] = output_token->length & 0xFF;

	memcpy(text->encode_buf + 4, output_token->value, output_token->length);
    }
    
    if (outputlen) {
	*outputlen = output_token->length + 4;
    }
    
    *output = text->encode_buf;
    
    if (output_token->value) {
	GSS_LOCK_MUTEX(text->utils);
	gss_release_buffer(&min_stat, output_token);
	GSS_UNLOCK_MUTEX(text->utils);
    }

    return SASL_OK;
}

static int gssapi_privacy_encode(void *context, const struct iovec *invec,
				 unsigned numiov, const char **output,
				 unsigned *outputlen)
{
    return sasl_gss_encode(context,invec,numiov,output,outputlen,1);
}

static int gssapi_integrity_encode(void *context, const struct iovec *invec,
				   unsigned numiov, const char **output,
				   unsigned *outputlen) 
{
    return sasl_gss_encode(context,invec,numiov,output,outputlen,0);
}

static int
gssapi_decode_packet(void *context,
		     const char *input,
		     unsigned inputlen,
		     char **output,
		     unsigned *outputlen)
{
    context_t *text = (context_t *) context;
    OM_uint32 maj_stat, min_stat;
    gss_buffer_t input_token, output_token;
    gss_buffer_desc real_input_token, real_output_token;
    int result;
    
    if (text->state != SASL_GSSAPI_STATE_AUTHENTICATED) {
	SETERROR(text->utils, "GSSAPI Failure");
	return SASL_NOTDONE;
    }
    
    input_token = &real_input_token; 
    real_input_token.value = (char *) input;
    real_input_token.length = inputlen;
    
    output_token = &real_output_token;
    output_token->value = NULL;
    output_token->length = 0;
    
    GSS_LOCK_MUTEX(text->utils);
    maj_stat = gss_unwrap (&min_stat,
			   text->gss_ctx,
			   input_token,
			   output_token,
			   NULL,
			   NULL);
    GSS_UNLOCK_MUTEX(text->utils);
    
    if (GSS_ERROR(maj_stat)) {
	sasl_gss_seterror(text->utils,maj_stat,min_stat);
	if (output_token->value) {
	    GSS_LOCK_MUTEX(text->utils);
	    gss_release_buffer(&min_stat, output_token);
	    GSS_UNLOCK_MUTEX(text->utils);
	}
	return SASL_FAIL;
    }
    
    if (outputlen) {
	*outputlen = output_token->length;
    }
    
    if (output_token->value) {
	if (output) {
	    result = _plug_buf_alloc(text->utils, &text->decode_once_buf,
				     &text->decode_once_buf_len,
				     *outputlen);
	    if (result != SASL_OK) {
		GSS_LOCK_MUTEX(text->utils);
		gss_release_buffer(&min_stat, output_token);
		GSS_UNLOCK_MUTEX(text->utils);
		return result;
	    }
	    *output = text->decode_once_buf;
	    memcpy(*output, output_token->value, *outputlen);
	}
	GSS_LOCK_MUTEX(text->utils);
	gss_release_buffer(&min_stat, output_token);
	GSS_UNLOCK_MUTEX(text->utils);
    }
    
    return SASL_OK;
}

static int gssapi_decode(void *context,
			 const char *input, unsigned inputlen,
			 const char **output, unsigned *outputlen)
{
    context_t *text = (context_t *) context;
    int ret;
    
    ret = _plug_decode(&text->decode_context, input, inputlen,
		       &text->decode_buf, &text->decode_buf_len, outputlen,
		       gssapi_decode_packet, text);
    
    *output = text->decode_buf;
    
    return ret;
}

static context_t *sasl_gss_new_context(const sasl_utils_t *utils)
{
    context_t *ret;
    
    ret = utils->malloc(sizeof(context_t));
    if(!ret) return NULL;
    
    memset(ret,0,sizeof(context_t));
    ret->utils = utils;
    
    return ret;
}

static int sasl_gss_free_context_contents(context_t *text)
{
    OM_uint32 maj_stat, min_stat;
    
    if (!text) return SASL_OK;
    
    GSS_LOCK_MUTEX(text->utils);

    if (text->gss_ctx != GSS_C_NO_CONTEXT) {
	maj_stat = gss_delete_sec_context(&min_stat,&text->gss_ctx,
					  GSS_C_NO_BUFFER);
	text->gss_ctx = GSS_C_NO_CONTEXT;
    }
    
    if (text->client_name != GSS_C_NO_NAME) {
	maj_stat = gss_release_name(&min_stat,&text->client_name);
	text->client_name = GSS_C_NO_NAME;
    }
    
    if (text->server_name != GSS_C_NO_NAME) {
	maj_stat = gss_release_name(&min_stat,&text->server_name);
	text->server_name = GSS_C_NO_NAME;
    }
    
    if ( text->server_creds != GSS_C_NO_CREDENTIAL) {
	maj_stat = gss_release_cred(&min_stat, &text->server_creds);
	text->server_creds = GSS_C_NO_CREDENTIAL;
    }

    if ( text->client_creds != GSS_C_NO_CREDENTIAL) {
	maj_stat = gss_release_cred(&min_stat, &text->client_creds);
	text->client_creds = GSS_C_NO_CREDENTIAL;
    }

    GSS_UNLOCK_MUTEX(text->utils);
    
    if (text->out_buf) {
	text->utils->free(text->out_buf);
	text->out_buf = NULL;
    }
    
    if (text->encode_buf) {
	text->utils->free(text->encode_buf);
	text->encode_buf = NULL;
    }
    
    if (text->decode_buf) {
	text->utils->free(text->decode_buf);
	text->decode_buf = NULL;
    }
    
    if (text->decode_once_buf) {
	text->utils->free(text->decode_once_buf);
	text->decode_once_buf = NULL;
    }
    
    if (text->enc_in_buf) {
	if(text->enc_in_buf->data) text->utils->free(text->enc_in_buf->data);
	text->utils->free(text->enc_in_buf);
	text->enc_in_buf = NULL;
    }

    _plug_decode_free(&text->decode_context);
    
    if (text->authid) { /* works for both client and server */
	text->utils->free(text->authid);
	text->authid = NULL;
    }

    return SASL_OK;

}

static void gssapi_common_mech_dispose(void *conn_context,
				       const sasl_utils_t *utils)
{
    sasl_gss_free_context_contents((context_t *)(conn_context));
    utils->free(conn_context);
}

static void gssapi_common_mech_free(void *global_context __attribute__((unused)),
				    const sasl_utils_t *utils)
{
#ifdef GSS_USE_MUTEXES
    if (gss_mutex) {
      utils->mutex_free(gss_mutex);
      gss_mutex=NULL;
    }
#endif
}

/*****************************  Server Section  *****************************/

static int 
gssapi_server_mech_new(void *glob_context __attribute__((unused)), 
		       sasl_server_params_t *params,
		       const char *challenge __attribute__((unused)), 
		       unsigned challen __attribute__((unused)),
		       void **conn_context)
{
    context_t *text;
    
    text = sasl_gss_new_context(params->utils);
    if (text == NULL) {
	MEMERROR(params->utils);
	return SASL_NOMEM;
    }
    
    text->gss_ctx = GSS_C_NO_CONTEXT;
    text->client_name = GSS_C_NO_NAME;
    text->server_name = GSS_C_NO_NAME;
    text->server_creds = GSS_C_NO_CREDENTIAL;
    text->client_creds = GSS_C_NO_CREDENTIAL;
    text->state = SASL_GSSAPI_STATE_AUTHNEG;
    
    text->http_mode = (params->flags & SASL_NEED_HTTP);

    *conn_context = text;
    
    return SASL_OK;
}

static int 
gssapi_server_mech_authneg(context_t *text,
			   sasl_server_params_t *params,
			   const char *clientin,
			   unsigned clientinlen,
			   const char **serverout,
			   unsigned *serveroutlen,
			   sasl_out_params_t *oparams __attribute__((unused)))
{
    gss_buffer_t input_token, output_token;
    gss_buffer_desc real_input_token, real_output_token;
    OM_uint32 maj_stat = 0, min_stat = 0;
    gss_buffer_desc name_token;
    int ret, equal = 0;
    unsigned out_flags = 0;
    gss_cred_id_t server_creds = (gss_cred_id_t) params->gss_creds;
    gss_buffer_desc name_without_realm;
    gss_name_t client_name_MN = NULL, without = NULL;
    gss_OID mech_type;
	
    input_token = &real_input_token;
    output_token = &real_output_token;
    output_token->value = NULL; output_token->length = 0;
    input_token->value = NULL; input_token->length = 0;
    
    if (text->server_name == GSS_C_NO_NAME) { /* only once */
	if (params->serverFQDN == NULL
	    || strlen(params->serverFQDN) == 0) {
	    SETERROR(text->utils, "GSSAPI Failure: no serverFQDN");
	    sasl_gss_free_context_contents(text);
	    return SASL_FAIL;
	}
	name_token.length = strlen(params->service) + 1 + strlen(params->serverFQDN);
	name_token.value = (char *)params->utils->malloc((name_token.length + 1) * sizeof(char));
	if (name_token.value == NULL) {
	    MEMERROR(text->utils);
	    sasl_gss_free_context_contents(text);
	    return SASL_NOMEM;
	}
	sprintf(name_token.value,"%s@%s", params->service, params->serverFQDN);

	GSS_LOCK_MUTEX(params->utils);
	maj_stat = gss_import_name (&min_stat,
				    &name_token,
				    GSS_C_NT_HOSTBASED_SERVICE,
				    &text->server_name);
	GSS_UNLOCK_MUTEX(params->utils);

	params->utils->free(name_token.value);
	name_token.value = NULL;

	if (GSS_ERROR(maj_stat)) {
	    sasl_gss_seterror(text->utils, maj_stat, min_stat);
	    sasl_gss_free_context_contents(text);
	    return SASL_FAIL;
	}

	if ( text->server_creds != GSS_C_NO_CREDENTIAL) {
	    GSS_LOCK_MUTEX(params->utils);
	    maj_stat = gss_release_cred(&min_stat, &text->server_creds);
	    GSS_UNLOCK_MUTEX(params->utils);
	    text->server_creds = GSS_C_NO_CREDENTIAL;
	}

	/* If caller didn't provide creds already */
	if ( server_creds == GSS_C_NO_CREDENTIAL) {
	    GSS_LOCK_MUTEX(params->utils);
	    maj_stat = gss_acquire_cred(&min_stat, 
					text->server_name,
					GSS_C_INDEFINITE, 
					GSS_C_NO_OID_SET,
					GSS_C_ACCEPT,
					&text->server_creds, 
					NULL, 
					NULL);
	    GSS_UNLOCK_MUTEX(params->utils);

	    if (GSS_ERROR(maj_stat)) {
		sasl_gss_seterror(text->utils, maj_stat, min_stat);
		sasl_gss_free_context_contents(text);
		return SASL_FAIL;
	    }
	    server_creds = text->server_creds;
	}
    }
	
    if (clientinlen) {
	real_input_token.value = (void *)clientin;
	real_input_token.length = clientinlen;
    }


    GSS_LOCK_MUTEX(params->utils);
    maj_stat =
	gss_accept_sec_context(&min_stat,
			       &(text->gss_ctx),
			       server_creds,
			       input_token,
			       GSS_C_NO_CHANNEL_BINDINGS,
			       &text->client_name,
			       &mech_type,
			       output_token,
			       &out_flags,
			       NULL,	/* context validity period */
			       &(text->client_creds));
    GSS_UNLOCK_MUTEX(params->utils);

    if (GSS_ERROR(maj_stat)) {
	sasl_gss_log(text->utils, maj_stat, min_stat);
	text->utils->seterror(text->utils->conn, SASL_NOLOG, "GSSAPI Failure: gss_accept_sec_context");
	if (output_token->value) {
	    GSS_LOCK_MUTEX(params->utils);
	    gss_release_buffer(&min_stat, output_token);
	    GSS_UNLOCK_MUTEX(params->utils);
	}
	sasl_gss_free_context_contents(text);
	return SASL_BADAUTH;
    }

    if (serveroutlen) {
	*serveroutlen = output_token->length;
    }
    if (output_token->value) {
	if (serverout) {
	    ret = _plug_buf_alloc(text->utils, &(text->out_buf),
				  &(text->out_buf_len), *serveroutlen);
	    if(ret != SASL_OK) {
		GSS_LOCK_MUTEX(params->utils);
		gss_release_buffer(&min_stat, output_token);
		GSS_UNLOCK_MUTEX(params->utils);
		return ret;
	    }
	    memcpy(text->out_buf, output_token->value, *serveroutlen);
	    *serverout = text->out_buf;
	}

	GSS_LOCK_MUTEX(params->utils);
	gss_release_buffer(&min_stat, output_token);
	GSS_UNLOCK_MUTEX(params->utils);
    } else {
	/* No output token, send an empty string */
	*serverout = GSSAPI_BLANK_STRING;
	*serveroutlen = 0;
    }

    if (maj_stat == GSS_S_CONTINUE_NEEDED) {
	/* Context isn't complete */
        return SASL_CONTINUE;
    }

    assert(maj_stat == GSS_S_COMPLETE);

    /* When GSS_Accept_sec_context returns GSS_S_COMPLETE, the server
       examines the context to ensure that it provides a level of protection
       permitted by the server's security policy.  In particular, if the
       integ_avail flag is not set in the context, then no security layer
       can be offered or accepted.  If the conf_avail flag is not set in the
       context, then no security layer with confidentiality can be offered
       or accepted. */
    if ((out_flags & GSS_C_INTEG_FLAG) == 0) {
	/* if the integ_avail flag is not set in the context,
	   then no security layer can be offered or accepted. */
	text->qop = LAYER_NONE;
    } else if ((out_flags & GSS_C_CONF_FLAG) == 0) {
	/* If the conf_avail flag is not set in the context,
	   then no security layer with confidentiality can be offered
	   or accepted. */
	text->qop = LAYER_NONE | LAYER_INTEGRITY;
    } else {
	text->qop = LAYER_NONE | LAYER_INTEGRITY | LAYER_CONFIDENTIALITY;
    }

    if ((params->props.security_flags & SASL_SEC_PASS_CREDENTIALS) &&
	(!(out_flags & GSS_C_DELEG_FLAG) ||
	 text->client_creds == GSS_C_NO_CREDENTIAL) ) 
	{
	    text->utils->seterror(text->utils->conn, SASL_LOG_WARN,
				  "GSSAPI warning: no credentials were passed");
	    /* continue with authentication */
	}

    GSS_LOCK_MUTEX(params->utils);
    maj_stat = gss_canonicalize_name(&min_stat,
				     text->client_name,
				     mech_type,
				     &client_name_MN);
    GSS_UNLOCK_MUTEX(params->utils);

    if (GSS_ERROR(maj_stat)) {
	SETERROR(text->utils, "GSSAPI Failure: gss_canonicalize_name");
	sasl_gss_free_context_contents(text);
	return SASL_BADAUTH;
    }

    name_token.value = NULL;
    name_without_realm.value = NULL;

    GSS_LOCK_MUTEX(params->utils);
    maj_stat = gss_display_name (&min_stat,
				 client_name_MN,
				 &name_token,
				 NULL);
    GSS_UNLOCK_MUTEX(params->utils);

    if (GSS_ERROR(maj_stat)) {
	SETERROR(text->utils, "GSSAPI Failure: gss_display_name");
	sasl_gss_free_context_contents(text);
	ret = SASL_BADAUTH;
	goto cleanup;
    }

    /* If the id contains a realm get the identifier for the user
       without the realm and see if it's the same id (i.e. 
       tmartin == tmartin@ANDREW.CMU.EDU. If this is the case we just want
       to return the id (i.e. just "tmartin" */
    if (strchr((char *) name_token.value, (int) '@') != NULL) {
	/* NOTE: libc malloc, as it is freed below by a gssapi internal
	 *       function! */
	name_without_realm.value = params->utils->malloc(strlen(name_token.value)+1);
	if (name_without_realm.value == NULL) {
	    MEMERROR(text->utils);
	    ret = SASL_NOMEM;
	    goto cleanup;
	}

	strcpy(name_without_realm.value, name_token.value);

	/* cut off string at '@' */
	(strchr(name_without_realm.value,'@'))[0] = '\0';

	name_without_realm.length = strlen( (char *) name_without_realm.value );

	GSS_LOCK_MUTEX(params->utils);
	maj_stat = gss_import_name (&min_stat,
				    &name_without_realm,
	    /* Solaris 8/9 gss_import_name doesn't accept GSS_C_NULL_OID here,
	       so use GSS_C_NT_USER_NAME instead if available.  */
#ifdef HAVE_GSS_C_NT_USER_NAME
				    GSS_C_NT_USER_NAME,
#else
				    GSS_C_NULL_OID,
#endif
				    &without);
	GSS_UNLOCK_MUTEX(params->utils);

	if (GSS_ERROR(maj_stat)) {
	    SETERROR(text->utils, "GSSAPI Failure: gss_import_name");
	    sasl_gss_free_context_contents(text);
	    ret = SASL_BADAUTH;
	    goto cleanup;
	}

	GSS_LOCK_MUTEX(params->utils);
	maj_stat = gss_compare_name(&min_stat,
				    client_name_MN,
				    without,
				    &equal);
	GSS_UNLOCK_MUTEX(params->utils);

	if (GSS_ERROR(maj_stat)) {
	    SETERROR(text->utils, "GSSAPI Failure: gss_compare_name");
	    sasl_gss_free_context_contents(text);
	    ret = SASL_BADAUTH;
	    goto cleanup;
	}

    } else {
	equal = 0;
    }

    if (equal) {
	text->authid = strdup(name_without_realm.value);
    } else {
	text->authid = strdup(name_token.value);
    }

    if (text->authid == NULL) {
	MEMERROR(params->utils);
	ret = SASL_NOMEM;
	goto cleanup;
    }

    if (text->http_mode) {
	/* HTTP doesn't do any ssf negotiation */
	text->state = SASL_GSSAPI_STATE_AUTHENTICATED;
	ret = SASL_OK;
    }
    else {
	/* Switch to ssf negotiation */
	text->state = SASL_GSSAPI_STATE_SSFCAP;
	ret = SASL_CONTINUE;
    }

  cleanup:
    if (client_name_MN) {
	GSS_LOCK_MUTEX(params->utils);
	gss_release_name(&min_stat, &client_name_MN);
	GSS_UNLOCK_MUTEX(params->utils);
    }
    if (name_token.value) {
	GSS_LOCK_MUTEX(params->utils);
	gss_release_buffer(&min_stat, &name_token);
	GSS_UNLOCK_MUTEX(params->utils);
    }
    if (name_without_realm.value) {
	params->utils->free(name_without_realm.value);
    }
    if (without) {
	GSS_LOCK_MUTEX(params->utils);
	gss_release_name(&min_stat, &without);
	GSS_UNLOCK_MUTEX(params->utils);
    }

    return ret;
}

static int 
gssapi_server_mech_ssfcap(context_t *text,
			  sasl_server_params_t *params,
			  const char *clientin __attribute__((unused)),
			  unsigned clientinlen,
			  const char **serverout,
			  unsigned *serveroutlen,
			  sasl_out_params_t *oparams __attribute__((unused)))
{
    gss_buffer_t input_token, output_token;
    gss_buffer_desc real_input_token, real_output_token;
    OM_uint32 maj_stat = 0, min_stat = 0;
    unsigned char sasldata[4];
    int ret;

    input_token = &real_input_token;
    output_token = &real_output_token;
    output_token->value = NULL; output_token->length = 0;
    

    if (clientinlen != 0) {
	SETERROR(text->utils, "GSSAPI server is not expecting data at this stage");
	sasl_gss_free_context_contents(text);
	return SASL_BADAUTH;
    }

    /* we have to decide what sort of encryption/integrity/etc.,
       we support */
    if (params->props.max_ssf < params->external_ssf) {
	text->limitssf = 0;
    } else {
	text->limitssf = params->props.max_ssf - params->external_ssf;
    }
    if (params->props.min_ssf < params->external_ssf) {
	text->requiressf = 0;
    } else {
	text->requiressf = params->props.min_ssf - params->external_ssf;
    }

    /* build up our security properties token */
    if (text->requiressf != 0 &&
	(text->qop & (LAYER_INTEGRITY|LAYER_CONFIDENTIALITY))) {
	if (params->props.maxbufsize > 0xFFFFFF) {
	    /* make sure maxbufsize isn't too large */
	    /* maxbufsize = 0xFFFFFF */
	    sasldata[1] = sasldata[2] = sasldata[3] = 0xFF;
	} else {
	    sasldata[1] = (params->props.maxbufsize >> 16) & 0xFF;
	    sasldata[2] = (params->props.maxbufsize >> 8) & 0xFF;
	    sasldata[3] = (params->props.maxbufsize >> 0) & 0xFF;
	}
    } else {
	/* From RFC 4752: "The client verifies that the server maximum buffer is 0
	   if the server does not advertise support for any security layer." */
	sasldata[1] = sasldata[2] = sasldata[3] = 0;
    }

    sasldata[0] = 0;
    if(text->requiressf != 0 && !params->props.maxbufsize) {
	params->utils->seterror(params->utils->conn, 0,
				"GSSAPI needs a security layer but one is forbidden");
	return SASL_TOOWEAK;
    }

    if (text->requiressf == 0) {
	sasldata[0] |= LAYER_NONE; /* authentication */
    }
    if ((text->qop & LAYER_INTEGRITY) &&
	text->requiressf <= 1 &&
	text->limitssf >= 1 &&
	params->props.maxbufsize) {
	sasldata[0] |= LAYER_INTEGRITY;
    }
    if ((text->qop & LAYER_CONFIDENTIALITY) &&
	text->requiressf <= K5_MAX_SSF &&
	text->limitssf >= K5_MAX_SSF &&
	params->props.maxbufsize) {
	sasldata[0] |= LAYER_CONFIDENTIALITY;
    }

    /* Remember what we want and can offer */
    text->qop = sasldata[0];

    real_input_token.value = (void *)sasldata;
    real_input_token.length = 4;

    GSS_LOCK_MUTEX(params->utils);
    maj_stat = gss_wrap(&min_stat,
			text->gss_ctx,
			0, /* Just integrity checking here */
			GSS_C_QOP_DEFAULT,
			input_token,
			NULL,
			output_token);
    GSS_UNLOCK_MUTEX(params->utils);

    if (GSS_ERROR(maj_stat)) {
	sasl_gss_seterror(text->utils, maj_stat, min_stat);
	if (output_token->value) {
	    GSS_LOCK_MUTEX(params->utils);
	    gss_release_buffer(&min_stat, output_token);
	    GSS_UNLOCK_MUTEX(params->utils);
	}
	sasl_gss_free_context_contents(text);
	return SASL_FAIL;
    }


    if (serveroutlen)
	*serveroutlen = output_token->length;
    if (output_token->value) {
	if (serverout) {
	    ret = _plug_buf_alloc(text->utils, &(text->out_buf),
				  &(text->out_buf_len), *serveroutlen);
	    if(ret != SASL_OK) {
		GSS_LOCK_MUTEX(params->utils);
		gss_release_buffer(&min_stat, output_token);
		GSS_UNLOCK_MUTEX(params->utils);
		return ret;
	    }
	    memcpy(text->out_buf, output_token->value, *serveroutlen);
	    *serverout = text->out_buf;
	}

	GSS_LOCK_MUTEX(params->utils);
	gss_release_buffer(&min_stat, output_token);
	GSS_UNLOCK_MUTEX(params->utils);
    }

    /* Wait for ssf request and authid */
    text->state = SASL_GSSAPI_STATE_SSFREQ; 
	
    return SASL_CONTINUE;
}

static int 
gssapi_server_mech_ssfreq(context_t *text,
			  sasl_server_params_t *params,
			  const char *clientin,
			  unsigned clientinlen,
			  const char **serverout __attribute__((unused)),
			  unsigned *serveroutlen __attribute__((unused)),
			  sasl_out_params_t *oparams)
{
    gss_buffer_t input_token, output_token;
    gss_buffer_desc real_input_token, real_output_token;
    OM_uint32 maj_stat = 0, min_stat = 0;
    OM_uint32 max_input;
    int layerchoice;
	
    input_token = &real_input_token;
    output_token = &real_output_token;
    output_token->value = NULL; output_token->length = 0;

    real_input_token.value = (void *)clientin;
    real_input_token.length = clientinlen;

    GSS_LOCK_MUTEX(params->utils);
    maj_stat = gss_unwrap(&min_stat,
			  text->gss_ctx,
			  input_token,
			  output_token,
			  NULL,
			  NULL);
    GSS_UNLOCK_MUTEX(params->utils);

    if (GSS_ERROR(maj_stat)) {
	sasl_gss_seterror(text->utils, maj_stat, min_stat);
	sasl_gss_free_context_contents(text);
	return SASL_FAIL;
    }

    if (output_token->length < 4) {
	SETERROR(text->utils,
		 "token too short");
	GSS_LOCK_MUTEX(params->utils);
	gss_release_buffer(&min_stat, output_token);
	GSS_UNLOCK_MUTEX(params->utils);
	sasl_gss_free_context_contents(text);
	return SASL_FAIL;
    }

    layerchoice = (int)(((char *)(output_token->value))[0]);
    if (layerchoice == LAYER_NONE &&
	(text->qop & LAYER_NONE)) { /* no encryption */
	oparams->encode = NULL;
	oparams->decode = NULL;
	oparams->mech_ssf = 0;
    } else if (layerchoice == LAYER_INTEGRITY &&
	       (text->qop & LAYER_INTEGRITY)) { /* integrity */
	oparams->encode = &gssapi_integrity_encode;
	oparams->decode = &gssapi_decode;
	oparams->mech_ssf = 1;
    } else if ((layerchoice == LAYER_CONFIDENTIALITY ||
		/* For compatibility with broken clients setting both bits */
		layerchoice == (LAYER_CONFIDENTIALITY|LAYER_INTEGRITY)) &&
	       (text->qop & LAYER_CONFIDENTIALITY)) { /* privacy */
	oparams->encode = &gssapi_privacy_encode;
	oparams->decode = &gssapi_decode;
	/* FIX ME: Need to extract the proper value here */
	oparams->mech_ssf = K5_MAX_SSF;
    } else {
	/* not a supported encryption layer */
	SETERROR(text->utils,
		 "protocol violation: client requested invalid layer");
	/* Mark that we attempted negotiation */
	oparams->mech_ssf = 2;
	if (output_token->value) {
	    GSS_LOCK_MUTEX(params->utils);
	    gss_release_buffer(&min_stat, output_token);
	    GSS_UNLOCK_MUTEX(params->utils);
	}
	sasl_gss_free_context_contents(text);
	return SASL_FAIL;
    }

    if (output_token->length > 4) {
	int ret;

	ret = params->canon_user(params->utils->conn,
				 ((char *) output_token->value) + 4,
				 (output_token->length - 4) * sizeof(char),
				 SASL_CU_AUTHZID, oparams);
	
	if (ret != SASL_OK) {
	    sasl_gss_free_context_contents(text);
	    return ret;
	}
    }
	
    /* No matter what, set the rest of the oparams */

    oparams->maxoutbuf =
	(((unsigned char *) output_token->value)[1] << 16) |
	(((unsigned char *) output_token->value)[2] << 8) |
	(((unsigned char *) output_token->value)[3] << 0);

    if (oparams->mech_ssf) {
	maj_stat = gss_wrap_size_limit( &min_stat,
					text->gss_ctx,
					1,
					GSS_C_QOP_DEFAULT,
					(OM_uint32) oparams->maxoutbuf,
					&max_input);

	if(max_input > oparams->maxoutbuf) {
	    /* Heimdal appears to get this wrong */
	    oparams->maxoutbuf -= (max_input - oparams->maxoutbuf);
	} else {
	    /* This code is actually correct */
	    oparams->maxoutbuf = max_input;
	}    
    }
	
    GSS_LOCK_MUTEX(params->utils);
    gss_release_buffer(&min_stat, output_token);
    GSS_UNLOCK_MUTEX(params->utils);

    text->state = SASL_GSSAPI_STATE_AUTHENTICATED;

    /* used by layers */
    _plug_decode_init(&text->decode_context,
		      text->utils,
		      (params->props.maxbufsize > 0xFFFFFF) ? 0xFFFFFF :
		      params->props.maxbufsize);
	
    return SASL_OK;
}

static int 
gssapi_server_mech_step(void *conn_context,
			sasl_server_params_t *params,
			const char *clientin,
			unsigned clientinlen,
			const char **serverout,
			unsigned *serveroutlen,
			sasl_out_params_t *oparams)
{
    context_t *text = (context_t *) conn_context;
    int ret;

    if (!serverout) {
	PARAMERROR(text->utils);
	return SASL_BADPARAM;
    }

    *serverout = NULL;
    *serveroutlen = 0;

    if (text == NULL) return SASL_BADPROT;

    params->utils->log(NULL, SASL_LOG_DEBUG,
		       "GSSAPI server step %d\n", text->state);

    switch (text->state) {

    case SASL_GSSAPI_STATE_AUTHNEG:
	ret = gssapi_server_mech_authneg(text, params, clientin, clientinlen,
					 serverout, serveroutlen, oparams);
	if (ret != SASL_CONTINUE || *serveroutlen) break;

	/* Pretend that we just got an empty response from the client */
	clientinlen = 0;

	/* fall through */

    case SASL_GSSAPI_STATE_SSFCAP:
	ret = gssapi_server_mech_ssfcap(text, params, clientin, clientinlen,
					serverout, serveroutlen, oparams);
	break;

    case SASL_GSSAPI_STATE_SSFREQ:
	ret = gssapi_server_mech_ssfreq(text, params, clientin, clientinlen,
					serverout, serveroutlen, oparams);
	break;

    default:
	params->utils->log(NULL, SASL_LOG_ERR,
			   "Invalid GSSAPI server step %d\n", text->state);
	return SASL_FAIL;
    }

    if (ret == SASL_OK) {
	ret = params->canon_user(params->utils->conn,
				 text->authid,
				 0, /* strlen(text->authid) */
				 (oparams->user ? 0 : SASL_CU_AUTHZID)
				 | SASL_CU_AUTHID | SASL_CU_EXTERNALLY_VERIFIED,
				 oparams);

	if (ret != SASL_OK) {
	    sasl_gss_free_context_contents(text);
	    return ret;
	}

	if (text->client_creds != GSS_C_NO_CREDENTIAL)	{
	    oparams->client_creds =  &text->client_creds;
	}
	else {
	    oparams->client_creds = NULL;
	}

	oparams->doneflag = 1;
    }
    
    return ret;
}

static sasl_server_plug_t gssapi_server_plugins[] = 
{
    {
	"GSSAPI",			/* mech_name */
	K5_MAX_SSF,			/* max_ssf */
	SASL_SEC_NOPLAINTEXT
	| SASL_SEC_NOACTIVE
	| SASL_SEC_NOANONYMOUS
	| SASL_SEC_MUTUAL_AUTH		/* security_flags */
	| SASL_SEC_PASS_CREDENTIALS,
	SASL_FEAT_WANT_CLIENT_FIRST
	| SASL_FEAT_ALLOWS_PROXY
	| SASL_FEAT_DONTUSE_USERPASSWD,	/* features */
	NULL,				/* glob_context */
	&gssapi_server_mech_new,	/* mech_new */
	&gssapi_server_mech_step,	/* mech_step */
	&gssapi_common_mech_dispose,	/* mech_dispose */
	&gssapi_common_mech_free,	/* mech_free */
	NULL,				/* setpass */
	NULL,				/* user_query */
	NULL,				/* idle */
	NULL,				/* mech_avail */
	NULL				/* spare */
    }
#ifdef HAVE_GSS_SPNEGO
    ,{
	"GSS-SPNEGO",			/* mech_name */
	K5_MAX_SSF,			/* max_ssf */
	SASL_SEC_NOPLAINTEXT
	| SASL_SEC_NOACTIVE
	| SASL_SEC_NOANONYMOUS
	| SASL_SEC_MUTUAL_AUTH		/* security_flags */
	| SASL_SEC_PASS_CREDENTIALS,
	SASL_FEAT_WANT_CLIENT_FIRST
	| SASL_FEAT_ALLOWS_PROXY
	| SASL_FEAT_DONTUSE_USERPASSWD
	| SASL_FEAT_SUPPORTS_HTTP,	/* features */
	NULL,				/* glob_context */
	&gssapi_server_mech_new,	/* mech_new */
	&gssapi_server_mech_step,	/* mech_step */
	&gssapi_common_mech_dispose,	/* mech_dispose */
	&gssapi_common_mech_free,	/* mech_free */
	NULL,				/* setpass */
	NULL,				/* user_query */
	NULL,				/* idle */
	NULL,				/* mech_avail */
	NULL				/* spare */
    }
#endif
};

int gssapiv2_server_plug_init(
#ifndef HAVE_GSSKRB5_REGISTER_ACCEPTOR_IDENTITY
    const sasl_utils_t *utils __attribute__((unused)),
#else
    const sasl_utils_t *utils,
#endif 
    int maxversion,
    int *out_version,
    sasl_server_plug_t **pluglist,
    int *plugcount)
{
#ifdef HAVE_GSSKRB5_REGISTER_ACCEPTOR_IDENTITY
    const char *keytab = NULL;
    char keytab_path[1024];
    unsigned int rl;
#endif
    
    if (maxversion < SASL_SERVER_PLUG_VERSION) {
	return SASL_BADVERS;
    }
    
#ifdef HAVE_GSSKRB5_REGISTER_ACCEPTOR_IDENTITY
    /* unfortunately, we don't check for readability of keytab if it's
       the standard one, since we don't know where it is */
    
    /* FIXME: This code is broken */
    
    utils->getopt(utils->getopt_context, "GSSAPI", "keytab", &keytab, &rl);
    if (keytab != NULL) {
	if (access(keytab, R_OK) != 0) {
	    utils->log(NULL, SASL_LOG_ERR,
		       "Could not find keytab file: %s: %m",
		       keytab, errno);
	    return SASL_FAIL;
	}
	
	if(strlen(keytab) > 1024) {
	    utils->log(NULL, SASL_LOG_ERR,
		       "path to keytab is > 1024 characters");
	    return SASL_BUFOVER;
	}
	
	strncpy(keytab_path, keytab, 1024);
	
	gsskrb5_register_acceptor_identity(keytab_path);
    }
#endif
    
    *out_version = SASL_SERVER_PLUG_VERSION;
    *pluglist = gssapi_server_plugins;
#ifdef HAVE_GSS_SPNEGO
    *plugcount = 2;
#else
    *plugcount = 1;  
#endif

#ifdef GSS_USE_MUTEXES
    if (!gss_mutex) {
       gss_mutex = utils->mutex_alloc();
       if (!gss_mutex) {
           return SASL_FAIL;
       }
    }
#endif
    
    return SASL_OK;
}

/*****************************  Client Section  *****************************/

static int gssapi_client_mech_new(void *glob_context,
				  sasl_client_params_t *params,
				  void **conn_context)
{
    context_t *text;
    
    /* holds state are in */
    text = sasl_gss_new_context(params->utils);
    if (text == NULL) {
	MEMERROR(params->utils);
	return SASL_NOMEM;
    }
    
    text->state = SASL_GSSAPI_STATE_AUTHNEG;
    text->mech_type = (gss_OID) glob_context;
    text->gss_ctx = GSS_C_NO_CONTEXT;
    text->client_name = GSS_C_NO_NAME;
    text->server_creds = GSS_C_NO_CREDENTIAL;
    text->client_creds  = GSS_C_NO_CREDENTIAL;

    text->http_mode = (params->flags & SASL_NEED_HTTP);

    *conn_context = text;
    
    return SASL_OK;
}

static int gssapi_client_mech_step(void *conn_context,
				   sasl_client_params_t *params,
				   const char *serverin,
				   unsigned serverinlen,
				   sasl_interact_t **prompt_need,
				   const char **clientout,
				   unsigned *clientoutlen,
				   sasl_out_params_t *oparams)
{
    context_t *text = (context_t *)conn_context;
    gss_buffer_t input_token, output_token;
    gss_buffer_desc real_input_token, real_output_token;
    OM_uint32 maj_stat = 0, min_stat = 0;
    OM_uint32 max_input;
    gss_buffer_desc name_token;
    int ret;
    OM_uint32 req_flags = 0, out_req_flags = 0;
    input_token = &real_input_token;
    output_token = &real_output_token;
    output_token->value = NULL;
    input_token->value = NULL; 
    input_token->length = 0;
    gss_cred_id_t client_creds = (gss_cred_id_t)params->gss_creds;
    
    *clientout = NULL;
    *clientoutlen = 0;
    
    params->utils->log(NULL, SASL_LOG_DEBUG,
		       "GSSAPI client step %d", text->state);

    switch (text->state) {

    case SASL_GSSAPI_STATE_AUTHNEG:
	/* try to get the userid */
	if (text->user == NULL) {
	    int user_result = SASL_OK;
	    
	    user_result = _plug_get_userid(params->utils, &text->user,
					   prompt_need);
	    
	    if ((user_result != SASL_OK) && (user_result != SASL_INTERACT)) {
		sasl_gss_free_context_contents(text);
		return user_result;
	    }
		    
	    /* free prompts we got */
	    if (prompt_need && *prompt_need) {
		params->utils->free(*prompt_need);
		*prompt_need = NULL;
	    }
		    
	    /* if there are prompts not filled in */
	    if (user_result == SASL_INTERACT) {
		/* make the prompt list */
		int result =
		    _plug_make_prompts(params->utils, prompt_need,
				       user_result == SASL_INTERACT ?
				       "Please enter your authorization name" : NULL, NULL,
				       NULL, NULL,
				       NULL, NULL,
				       NULL, NULL, NULL,
				       NULL, NULL, NULL);
		if (result != SASL_OK) return result;
		
		return SASL_INTERACT;
	    }
	}
	    
	if (text->server_name == GSS_C_NO_NAME) { /* only once */
	    if (params->serverFQDN == NULL
		|| strlen(params->serverFQDN) == 0) {
		SETERROR(text->utils, "GSSAPI Failure: no serverFQDN");
		sasl_gss_free_context_contents(text);
		return SASL_FAIL;
	    }
	    name_token.length = strlen(params->service) + 1 + strlen(params->serverFQDN);
	    name_token.value = (char *)params->utils->malloc((name_token.length + 1) * sizeof(char));
	    if (name_token.value == NULL) {
		sasl_gss_free_context_contents(text);
		return SASL_NOMEM;
	    }
	    
	    sprintf(name_token.value,"%s@%s", params->service, params->serverFQDN);
	    
	    GSS_LOCK_MUTEX(params->utils);
	    maj_stat = gss_import_name (&min_stat,
					&name_token,
					GSS_C_NT_HOSTBASED_SERVICE,
					&text->server_name);
	    GSS_UNLOCK_MUTEX(params->utils);
	    
	    params->utils->free(name_token.value);
	    name_token.value = NULL;
	    
	    if (GSS_ERROR(maj_stat)) {
		sasl_gss_seterror(text->utils, maj_stat, min_stat);
		sasl_gss_free_context_contents(text);
		return SASL_FAIL;
	    }
	}
	    
	if (serverinlen == 0)
	    input_token = GSS_C_NO_BUFFER;

	if (serverinlen) {
	    real_input_token.value = (void *)serverin;
	    real_input_token.length = serverinlen;
	}
	else if (text->gss_ctx != GSS_C_NO_CONTEXT ) {
	    /* This can't happen under GSSAPI: we have a non-null context
	     * and no input from the server.  However, thanks to Imap,
	     * which discards our first output, this happens all the time.
	     * Throw away the context and try again. */
	    GSS_LOCK_MUTEX(params->utils);
	    maj_stat = gss_delete_sec_context (&min_stat,&text->gss_ctx,GSS_C_NO_BUFFER);
	    GSS_UNLOCK_MUTEX(params->utils);
	    text->gss_ctx = GSS_C_NO_CONTEXT;
	}

	/* Setup req_flags properly */
	req_flags = GSS_C_INTEG_FLAG;
	if (params->props.max_ssf > params->external_ssf) {
	    /* We are requesting a security layer */
	    req_flags |= GSS_C_MUTUAL_FLAG | GSS_C_SEQUENCE_FLAG;
	    /* Any SSF bigger than 1 is confidentiality. */
	    /* Let's check if the client of the API requires confidentiality,
	       and it wasn't already provided by an external layer */
	    if (params->props.max_ssf - params->external_ssf > 1) {
		/* We want to try for privacy */
		req_flags |= GSS_C_CONF_FLAG;
	    }
	}

	if (params->props.security_flags & SASL_SEC_PASS_CREDENTIALS) {
	    req_flags = req_flags |  GSS_C_DELEG_FLAG;
	}

	GSS_LOCK_MUTEX(params->utils);
	maj_stat = gss_init_sec_context(&min_stat,
					client_creds, /* GSS_C_NO_CREDENTIAL */
					&text->gss_ctx,
					text->server_name,
					text->mech_type,
					req_flags,
					0,
					GSS_C_NO_CHANNEL_BINDINGS,
					input_token,
					NULL,
					output_token,
					&out_req_flags,
					NULL);
	GSS_UNLOCK_MUTEX(params->utils);
	
	if (GSS_ERROR(maj_stat)) {
	    sasl_gss_seterror(text->utils, maj_stat, min_stat);
	    if (output_token->value) {
		GSS_LOCK_MUTEX(params->utils);
		gss_release_buffer(&min_stat, output_token);
		GSS_UNLOCK_MUTEX(params->utils);
	    }
	    sasl_gss_free_context_contents(text);
	    return SASL_FAIL;
	}

	if ((out_req_flags & GSS_C_INTEG_FLAG) == 0) {
	    /* if the integ_avail flag is not set in the context,
	       then no security layer can be offered or accepted. */
	    text->qop = LAYER_NONE;
	} else if ((out_req_flags & GSS_C_CONF_FLAG) == 0) {
	    /* If the conf_avail flag is not set in the context,
	       then no security layer with confidentiality can be offered
	       or accepted. */
	    text->qop = LAYER_NONE | LAYER_INTEGRITY;
	} else {
	    text->qop = LAYER_NONE | LAYER_INTEGRITY | LAYER_CONFIDENTIALITY;
	}

	if ((out_req_flags & GSS_C_DELEG_FLAG) != (req_flags & GSS_C_DELEG_FLAG)) {
	    text->utils->seterror(text->utils->conn, SASL_LOG_WARN, "GSSAPI warning: no credentials were passed");
	    /* not a fatal error */
	}
  	    
	*clientoutlen = output_token->length;
	    
	if (output_token->value) {
	    if (clientout) {
		ret = _plug_buf_alloc(text->utils, &(text->out_buf),
				      &(text->out_buf_len), *clientoutlen);
		if(ret != SASL_OK) {
		    GSS_LOCK_MUTEX(params->utils);
		    gss_release_buffer(&min_stat, output_token);
		    GSS_UNLOCK_MUTEX(params->utils);
		    return ret;
		}
		memcpy(text->out_buf, output_token->value, *clientoutlen);
		*clientout = text->out_buf;
	    }
	    
	    GSS_LOCK_MUTEX(params->utils);
	    gss_release_buffer(&min_stat, output_token);
	    GSS_UNLOCK_MUTEX(params->utils);
	}
	
	if (maj_stat == GSS_S_COMPLETE) {
	    GSS_LOCK_MUTEX(params->utils);
	    maj_stat = gss_inquire_context(&min_stat,
					   text->gss_ctx,
					   &text->client_name,
					   NULL,       /* targ_name */
					   NULL,       /* lifetime */
					   NULL,       /* mech */
					   /* FIX ME: Should check the resulting flags here */
					   NULL,       /* flags */
					   NULL,       /* local init */
					   NULL);      /* open */
	    GSS_UNLOCK_MUTEX(params->utils);
	    
	    if (GSS_ERROR(maj_stat)) {
		sasl_gss_seterror(text->utils, maj_stat, min_stat);
		sasl_gss_free_context_contents(text);
		return SASL_FAIL;
	    }
	    
	    name_token.length = 0;
	    GSS_LOCK_MUTEX(params->utils);
	    maj_stat = gss_display_name(&min_stat,
					text->client_name,
					&name_token,
					NULL);
	    GSS_UNLOCK_MUTEX(params->utils);
	    
	    if (GSS_ERROR(maj_stat)) {
		if (name_token.value) {
		    GSS_LOCK_MUTEX(params->utils);
		    gss_release_buffer(&min_stat, &name_token);
		    GSS_UNLOCK_MUTEX(params->utils);
		}
		SETERROR(text->utils, "GSSAPI Failure");
		sasl_gss_free_context_contents(text);
		return SASL_FAIL;
	    }
	    
	    if (text->user && text->user[0]) {
		ret = params->canon_user(params->utils->conn,
					 text->user, 0,
					 SASL_CU_AUTHZID, oparams);
		if (ret == SASL_OK) 
		    ret = params->canon_user(params->utils->conn,
					     name_token.value, 0,
					     SASL_CU_AUTHID, oparams);
	    } else {
		ret = params->canon_user(params->utils->conn,
					 name_token.value, 0,
					 SASL_CU_AUTHID | SASL_CU_AUTHZID,
					 oparams);
	    }
	    GSS_LOCK_MUTEX(params->utils);
	    gss_release_buffer(&min_stat, &name_token);
	    GSS_UNLOCK_MUTEX(params->utils);
	    
	    if (ret != SASL_OK) return ret;
	    
	    if (text->http_mode) {
		/* HTTP doesn't do any ssf negotiation */
		text->state = SASL_GSSAPI_STATE_AUTHENTICATED;
		oparams->doneflag = 1;
		return SASL_OK;
	    }

	    /* Switch to ssf negotiation */
	    text->state = SASL_GSSAPI_STATE_SSFCAP;
	}
	
	return SASL_CONTINUE;

    case SASL_GSSAPI_STATE_SSFCAP: {
	sasl_security_properties_t *secprops = &(params->props);
	unsigned int alen, external = params->external_ssf;
	sasl_ssf_t need, allowed;
	char serverhas, mychoice;
	
	real_input_token.value = (void *) serverin;
	real_input_token.length = serverinlen;
	
	GSS_LOCK_MUTEX(params->utils);
	maj_stat = gss_unwrap(&min_stat,
			      text->gss_ctx,
			      input_token,
			      output_token,
			      NULL,
			      NULL);
	GSS_UNLOCK_MUTEX(params->utils);
	
	if (GSS_ERROR(maj_stat)) {
	    sasl_gss_seterror(text->utils, maj_stat, min_stat);
	    sasl_gss_free_context_contents(text);
	    if (output_token->value) {
		GSS_LOCK_MUTEX(params->utils);
		gss_release_buffer(&min_stat, output_token);
		GSS_UNLOCK_MUTEX(params->utils);
	    }
	    return SASL_FAIL;
	}
	
	if (output_token->length != 4) {
	    SETERROR(text->utils,
		     (output_token->length < 4) ? "token too short" : "token too long");
	    GSS_LOCK_MUTEX(params->utils);
	    gss_release_buffer(&min_stat, output_token);
	    GSS_UNLOCK_MUTEX(params->utils);
	    sasl_gss_free_context_contents(text);
	    return SASL_FAIL;
	}

	/* taken from kerberos.c */
	if (secprops->min_ssf > (K5_MAX_SSF + external)) {
	    return SASL_TOOWEAK;
	} else if (secprops->min_ssf > secprops->max_ssf) {
	    return SASL_BADPARAM;
	}
	
	/* need bits of layer -- sasl_ssf_t is unsigned so be careful */
	if (secprops->max_ssf >= external) {
	    allowed = secprops->max_ssf - external;
	} else {
	    allowed = 0;
	}
	if (secprops->min_ssf >= external) {
	    need = secprops->min_ssf - external;
	} else {
	    /* good to go */
	    need = 0;
	}

	/* bit mask of server support */
	serverhas = ((char *)output_token->value)[0];
	
	/* use the strongest layer available */
	if ((text->qop & LAYER_CONFIDENTIALITY) &&
	    allowed >= K5_MAX_SSF &&
	    need <= K5_MAX_SSF &&
	    (serverhas & LAYER_CONFIDENTIALITY)) {
	    
	    const char *ad_compat;

	    /* encryption */
	    oparams->encode = &gssapi_privacy_encode;
	    oparams->decode = &gssapi_decode;
	    /* FIX ME: Need to extract the proper value here */
	    oparams->mech_ssf = K5_MAX_SSF;
	    mychoice = LAYER_CONFIDENTIALITY;

	    if (serverhas & LAYER_INTEGRITY) {
		/* should we send an AD compatible choice of security layers? */
		params->utils->getopt(params->utils->getopt_context,
				      "GSSAPI",
				      "ad_compat",
				      &ad_compat,
				      NULL);
		if (ad_compat &&
		    (ad_compat[0] == '1' || ad_compat[0] == 'y' ||
		     (ad_compat[0] == 'o' && ad_compat[1] == 'n') ||
		     ad_compat[0] == 't')) {
		    mychoice = LAYER_INTEGRITY|LAYER_CONFIDENTIALITY;
		}
	    }
	} else if ((text->qop & LAYER_INTEGRITY) &&
		    allowed >= 1 &&
		    need <= 1 &&
		    (serverhas & LAYER_INTEGRITY)) {
	    /* integrity */
	    oparams->encode = &gssapi_integrity_encode;
	    oparams->decode = &gssapi_decode;
	    oparams->mech_ssf = 1;
	    mychoice = LAYER_INTEGRITY;
	} else if ((text->qop & LAYER_NONE) &&
		   need <= 0 && (serverhas & LAYER_NONE)) {
	    /* no layer */
	    oparams->encode = NULL;
	    oparams->decode = NULL;
	    oparams->mech_ssf = 0;
	    mychoice = LAYER_NONE;
	} else {
	    /* there's no appropriate layering for us! */
	    sasl_gss_free_context_contents(text);
	    return SASL_TOOWEAK;
	}
	
        oparams->maxoutbuf =
	    (((unsigned char *) output_token->value)[1] << 16) |
            (((unsigned char *) output_token->value)[2] << 8) |
            (((unsigned char *) output_token->value)[3] << 0);

	if (oparams->mech_ssf) {
            maj_stat = gss_wrap_size_limit( &min_stat,
                                            text->gss_ctx,
                                            1,
                                            GSS_C_QOP_DEFAULT,
                                            (OM_uint32) oparams->maxoutbuf,
                                            &max_input);

	    if (max_input > oparams->maxoutbuf) {
		/* Heimdal appears to get this wrong */
		oparams->maxoutbuf -= (max_input - oparams->maxoutbuf);
	    } else {
		/* This code is actually correct */
		oparams->maxoutbuf = max_input;
	    }
	}
	
	GSS_LOCK_MUTEX(params->utils);
	gss_release_buffer(&min_stat, output_token);
	GSS_UNLOCK_MUTEX(params->utils);
	
	/* oparams->user is always set, due to canon_user requirements.
	 * Make sure the client actually requested it though, by checking
	 * if our context was set.
	 */
	if (text->user && text->user[0]) {
	    alen = strlen(oparams->user);
	} else {
	    alen = 0;
	}
	
	input_token->length = 4 + alen;
	input_token->value =
	    (char *)params->utils->malloc((input_token->length + 1)*sizeof(char));
	if (input_token->value == NULL) {
	    sasl_gss_free_context_contents(text);
	    return SASL_NOMEM;
	}
	
	if (alen)
	    memcpy((char *)input_token->value+4,oparams->user,alen);

	/* build up our security properties token */
	if (mychoice > 1) {
	    if (params->props.maxbufsize > 0xFFFFFF) {
		/* make sure maxbufsize isn't too large */
		/* maxbufsize = 0xFFFFFF */
		((unsigned char *)input_token->value)[1] = 0xFF;
		((unsigned char *)input_token->value)[2] = 0xFF;
		((unsigned char *)input_token->value)[3] = 0xFF;
	    } else {
		((unsigned char *)input_token->value)[1] = 
		    (params->props.maxbufsize >> 16) & 0xFF;
		((unsigned char *)input_token->value)[2] = 
		    (params->props.maxbufsize >> 8) & 0xFF;
		((unsigned char *)input_token->value)[3] = 
		    (params->props.maxbufsize >> 0) & 0xFF;
	    }
	} else {
	    ((unsigned char *)input_token->value)[1] = 0;
	    ((unsigned char *)input_token->value)[2] = 0;
	    ((unsigned char *)input_token->value)[3] = 0;
	}
	((unsigned char *)input_token->value)[0] = mychoice;
	
	GSS_LOCK_MUTEX(params->utils);
	maj_stat = gss_wrap (&min_stat,
			     text->gss_ctx,
			     0, /* Just integrity checking here */
			     GSS_C_QOP_DEFAULT,
			     input_token,
			     NULL,
			     output_token);
	GSS_UNLOCK_MUTEX(params->utils);
	
	params->utils->free(input_token->value);
	input_token->value = NULL;
	
	if (GSS_ERROR(maj_stat)) {
	    sasl_gss_seterror(text->utils, maj_stat, min_stat);
	    if (output_token->value) {
		GSS_LOCK_MUTEX(params->utils);
		gss_release_buffer(&min_stat, output_token);
		GSS_UNLOCK_MUTEX(params->utils);
	    }
	    sasl_gss_free_context_contents(text);
	    return SASL_FAIL;
	}
	
	if (clientoutlen) {
	    *clientoutlen = output_token->length;
	}
	if (output_token->value) {
	    if (clientout) {
		ret = _plug_buf_alloc(text->utils,
				      &(text->out_buf),
				      &(text->out_buf_len),
				      *clientoutlen);
		if (ret != SASL_OK) {
		    GSS_LOCK_MUTEX(params->utils);
		    gss_release_buffer(&min_stat, output_token);
		    GSS_UNLOCK_MUTEX(params->utils);
		    return ret;
		}
		memcpy(text->out_buf, output_token->value, *clientoutlen);
		*clientout = text->out_buf;
	    }
	    
	    GSS_LOCK_MUTEX(params->utils);
	    gss_release_buffer(&min_stat, output_token);
	    GSS_UNLOCK_MUTEX(params->utils);

	}
	
	text->state = SASL_GSSAPI_STATE_AUTHENTICATED;
	
	oparams->doneflag = 1;
	
	/* used by layers */
	_plug_decode_init(&text->decode_context, text->utils,
			  (params->props.maxbufsize > 0xFFFFFF) ? 0xFFFFFF :
			  params->props.maxbufsize);
	
	return SASL_OK;
    }
	
    default:
	params->utils->log(NULL, SASL_LOG_ERR,
			   "Invalid GSSAPI client step %d\n", text->state);
	return SASL_FAIL;
    }
    
    return SASL_FAIL; /* should never get here */
}

static const unsigned long gssapi_required_prompts[] = {
    SASL_CB_LIST_END
};  

static sasl_client_plug_t gssapi_client_plugins[] = 
{
    {
	"GSSAPI",			/* mech_name */
	K5_MAX_SSF,			/* max_ssf */
	SASL_SEC_NOPLAINTEXT
	| SASL_SEC_NOACTIVE
	| SASL_SEC_NOANONYMOUS
	| SASL_SEC_MUTUAL_AUTH 
	| SASL_SEC_PASS_CREDENTIALS,    /* security_flags */
	SASL_FEAT_NEEDSERVERFQDN
	| SASL_FEAT_WANT_CLIENT_FIRST
	| SASL_FEAT_ALLOWS_PROXY,	/* features */
	gssapi_required_prompts,	/* required_prompts */
	GSS_C_NO_OID,			/* glob_context */
	&gssapi_client_mech_new,	/* mech_new */
	&gssapi_client_mech_step,	/* mech_step */
	&gssapi_common_mech_dispose,	/* mech_dispose */
	&gssapi_common_mech_free,	/* mech_free */
	NULL,				/* idle */
	NULL,				/* spare */
	NULL				/* spare */
    }
#ifdef HAVE_GSS_SPNEGO
    ,{
	"GSS-SPNEGO",			/* mech_name */
	K5_MAX_SSF,			/* max_ssf */
	SASL_SEC_NOPLAINTEXT
	| SASL_SEC_NOACTIVE
	| SASL_SEC_NOANONYMOUS
	| SASL_SEC_MUTUAL_AUTH 
	| SASL_SEC_PASS_CREDENTIALS,    /* security_flags */
	SASL_FEAT_NEEDSERVERFQDN
	| SASL_FEAT_WANT_CLIENT_FIRST
	| SASL_FEAT_ALLOWS_PROXY
	| SASL_FEAT_SUPPORTS_HTTP,	/* features */
	gssapi_required_prompts,	/* required_prompts */
	&gss_spnego_oid,		/* glob_context */
	&gssapi_client_mech_new,	/* mech_new */
	&gssapi_client_mech_step,	/* mech_step */
	&gssapi_common_mech_dispose,	/* mech_dispose */
	&gssapi_common_mech_free,	/* mech_free */
	NULL,				/* idle */
	NULL,				/* spare */
	NULL				/* spare */
    }
#endif
};

int gssapiv2_client_plug_init(const sasl_utils_t *utils __attribute__((unused)), 
			      int maxversion,
			      int *out_version, 
			      sasl_client_plug_t **pluglist,
			      int *plugcount)
{
    if (maxversion < SASL_CLIENT_PLUG_VERSION) {
	SETERROR(utils, "Version mismatch in GSSAPI");
	return SASL_BADVERS;
    }
    
    *out_version = SASL_CLIENT_PLUG_VERSION;
    *pluglist = gssapi_client_plugins;
#ifdef HAVE_GSS_SPNEGO
    *plugcount = 2;
#else
    *plugcount = 1;
#endif

#ifdef GSS_USE_MUTEXES
    if(!gss_mutex) {
      gss_mutex = utils->mutex_alloc();
      if(!gss_mutex) {
        return SASL_FAIL;
      }
    }
#endif
    
    return SASL_OK;
}
