/* Copyright (c) 1995,1996,1997 NEC Corporation.  All rights reserved.       */
/*                                                                           */
/* The redistribution, use and modification in source or binary forms of     */
/* this software is subject to the conditions set forth in the copyright     */
/* document ("Copyright") included with this distribution.                   */

/*
 * $Id: gss.c,v 1.48 1997/06/16 15:29:39 steve Exp $
 */

#include "socks5p.h"
#include "threads.h"
#include "buffer.h"
#include "addr.h"
#include "msg.h"
#include "gss.h"
#include "log.h"

#ifdef HAVE_LIBGSSAPI_KRB5

#include <gssapi/gssapi.h>
#include <gssapi/gssapi_generic.h>


#ifdef OLD_KERBEROS
#define PREFIX "rcmd/"
#else
#define PREFIX "rcmd@"
#endif

#define GENCAP_VERS    0
#define GENCAP_MTYP    1
#define GENCAP_EOFF    2

#define SETVERS(x, y) ((x)[0] = (u_char)(y))
#define SETMTYP(x, y) ((x)[1] = (u_char)(y))
#define SETELEN(x, y) do { u_short us = htons((short)(y)); memcpy((char *)((x)+GENCAP_EOFF), &us, sizeof(short)); } while (0)
#define GETELEN(x, y) do { memcpy(&(y), (char *)((x)+GENCAP_EOFF), sizeof(short)); (y) = ntohs((y));              } while (0)

#define ENCAP_NONE     0x00
#define ENCAP_INTEG    0x01
#define ENCAP_CONF     0x02

#ifndef GSSAPI_TIMEOUT
#define GSSAPI_TIMEOUT 15
#endif

IFTHREADED(static MUTEX_T krb_mutex = MUTEX_INITIALIZER;)
IFTHREADED(extern MUTEX_T gh_mutex;)
IFTHREADED(extern MUTEX_T env_mutex;)

/* Where we'll keep the stuff associated with this authentication method...  */
struct gssinfo {
    int options;
    gss_ctx_id_t context;
};

typedef struct gssinfo GssInfo;

/* Display all the info we can about code (w/type type).                     */
static void DisplayStatus(int code, int type) {
    OM_uint32 maj_stat, min_stat, msg_ctx = 0;
    gss_buffer_desc msg;

    for (;;) {
	maj_stat = gss_display_status(&min_stat, code, type, GSS_C_NULL_OID, &msg_ctx, &msg);
	S5LogUpdate(S5LogDefaultHandle, S5_LOG_ERROR, 0, "GSS: error: %s", (char *)msg.value); 
	(void) gss_release_buffer(&min_stat, &msg);
	
	if (!msg_ctx) break;
    }
}

/* Simple routine to pull out the error codes from major & minor or ioerr.   */
static void lsGssapiDisplayError(int major, int minor, int ioerr) {
    if (ioerr) {
	S5LogUpdate(S5LogDefaultHandle, S5_LOG_ERROR, 0, "GSS: io-error: %m");
    } else {
	DisplayStatus(major, GSS_C_GSS_CODE);
	DisplayStatus(minor, GSS_C_MECH_CODE);
    }
}

/* Find out the service name we'll need later.  Use the peer's name if we    */
/* aren't a server, or our name if we are.  Either way, get the name of the  */
/* person on the "server" end of fd.                                         */
int lsGssapiServiceName(S5IOHandle fd, int server, gss_name_t *nameptr) {
    int len = sizeof(S5NetAddr);
    OM_uint32 status, trash;
    char domain[MAXNAMELEN];
    gss_buffer_desc buf;
    struct hostent *hp;
    S5NetAddr na;
    
    *domain = '\0';

    if ((server  && REAL(getsockname)(fd, &na.sa, &len) < 0) ||
	(!server && REAL(getpeername)(fd, &na.sa, &len) < 0)) {
	S5LogUpdate(S5LogDefaultHandle, S5_LOG_ERROR, 0, "GSS: Failed to get service name: %m");
	return -1;
    }
    
    MUTEX_LOCK(gh_mutex);

    if (!(hp = gethostbyaddr(lsAddr2Ptr(&na), lsAddrAddrSize(&na), na.sa.sa_family))) {
	S5LogUpdate(S5LogDefaultHandle, S5_LOG_ERROR, 0, "GSS: Failed to Reverse Map peer name");
	MUTEX_UNLOCK(gh_mutex);
	return -1;
    }

    /* Try hacking it into a fully qualified domain name, although I doubt   */
    /* it will work.  Actually who knows.                                    */
#ifdef HAVE_GETDOMAINNAME
    if (!strchr(hp->h_name, '.')) getdomainname(domain, MAXNAMELEN);
#endif

    if (!(buf.value = (char *)calloc(strlen(PREFIX) + strlen(hp->h_name) + strlen(domain) + 2, 1))) {
	S5LogUpdate(S5LogDefaultHandle, S5_LOG_ERROR, 0, "GSS: Failed to allocate space for peer name");
	MUTEX_UNLOCK(gh_mutex);
	return -1;
    }

    strcat(strcpy(buf.value, PREFIX), hp->h_name);
    MUTEX_UNLOCK(gh_mutex);    

    if (*domain != '\0') strcat(strcat(buf.value, "."), domain);
    S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(5), 0, "GSS: Importing name: %s", buf.value);
    buf.length = strlen(buf.value);


#ifdef OLD_KERBEROS
    status = gss_import_name(&trash, &buf, GSS_C_NULL_OID, nameptr);
#else
    status = gss_import_name(&trash, &buf, gss_nt_service_name, nameptr);
#endif
    gss_release_buffer(&trash, &buf);

    if (status != GSS_S_COMPLETE) {
	lsGssapiDisplayError(status, trash, 0);
	return -1;
    } 

    return 0;
}

#define GSSAPI_IOFLAGS S5_IOFLAGS_NBYTES|S5_IOFLAGS_TIMED|S5_IOFLAGS_RESTART

static int RecvToken(S5IOHandle fd, u_char type, gss_buffer_t tok, OM_uint32 *trash) {
    double timerm = (double)GSSAPI_TIMEOUT;
    char buf[4], *token = NULL;
    u_short len;
    int rval;

    /* Recv the header...                                                    */
    if ((rval = S5IORecv(fd, NULL, buf, 4, 0, GSSAPI_IOFLAGS, &timerm)) != 4) {
	if (rval == 0) SETSOCKETERROR(ETIMEDOUT);
	return -1;
    }

    /* Validate the header...                                                */
    if (buf[0] != (char)0x01 || buf[1] != (char)type) {
	S5LogUpdate(S5LogDefaultHandle, S5_LOG_WARNING, 0, "GSS: Bad Token");
	SETSOCKETERROR(EBADF);
	return -1;
    }

    GETELEN(buf, len);

    /* Allocate the token.                                                   */
    if (len > 0 && (token = (char *)malloc(len)) == NULL)  {
	S5LogUpdate(S5LogDefaultHandle, S5_LOG_WARNING, 0, "GSS: Recv Token: Malloc failed");
	SETSOCKETERROR(EBADF);
	return -1;
    }

    /* Recv the token itself.                                                */
    if (len > 0 && (rval = S5IORecv(fd, NULL, token, len, 0, GSSAPI_IOFLAGS, &timerm)) != len) {
	if (rval == 0) SETSOCKETERROR(ETIMEDOUT);
	return -1;
    }

    /* Fill it in...                                                         */
    tok->value  = token;
    tok->length = len;

    /* We're done...                                                         */
    S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(8), 0, "GSS: Read a token of length: %d", len);
    return 0;
}

static int SendToken(S5IOHandle fd, u_char type, gss_buffer_t tok, OM_uint32 *trash) {
    double timerm = (double)GSSAPI_TIMEOUT;
    u_short len = (u_short)tok->length;
    char buf[4];
    int sval;

    /* Fill in the header.                                                   */
    SETVERS(buf, 0x01);
    SETMTYP(buf, type);
    SETELEN(buf, len);

    /* Send the header.                                                      */
    if ((sval = S5IOSend(fd, NULL, buf, 4, 0, GSSAPI_IOFLAGS, &timerm)) != 4) {
	if (sval == 0) SETSOCKETERROR(ETIMEDOUT);
	return -1;
    }

    /* Send the token.                                                       */
    if (tok->length && (sval = S5IOSend(fd, NULL, tok->value, tok->length, 0, GSSAPI_IOFLAGS, &timerm)) != tok->length) {
	if (sval == 0) SETSOCKETERROR(ETIMEDOUT);
	return -1;
    }

    /* We're done.                                                           */
    S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(8), 0, "GSS: Sent a token of length: %d", tok->length);
    return 0;
}

/* Encode ibuf into obuf...                                                  */
static int lsGssapiEncodePacket(S5Packet *ibuf, S5Packet *obuf, void *context) {
    GssInfo *ginfo = (GssInfo *)context;
    gss_buffer_desc in, out;
    OM_uint32 status, trash;
    int state;

    /* We can only send 2^16 bytes at a time, so only encapsulate 2^15       */
    if (ibuf->len > (1 << 15)) ibuf->len = (1 << 15);

    /* Initial setup...                                                      */
    in.value   = ibuf->data;
    in.length  = ibuf->len;
    out.value  = NULL;
    out.length = 0;

    S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "GSS: Encoding a message of length: %d", ibuf->len);
    MUTEX_LOCK(krb_mutex);

    /* Try to seal the message...XXX It would be nice to not have to malloc  */
    /* & free the token, but the RFC is vague about what happens if you pass */
    /* in a preallocated token & the kerberos implementation I looked at     */
    /* just ignores it...                                                    */
    if ((status = gss_seal(&trash, ginfo->context, ginfo->options, GSS_C_QOP_DEFAULT, &in, &state, &out)) != GSS_S_COMPLETE) {
	lsGssapiDisplayError(status, trash, 0);
	MUTEX_UNLOCK(krb_mutex);
	return -1;
    }

    MUTEX_UNLOCK(krb_mutex);

    /* Make sure the message didn't get too big...                           */
    if (out.length >= (1 << 16)) {
	S5LogUpdate(S5LogDefaultHandle, S5_LOG_WARNING, 0, "GSS: Encoded message too big to be encapsulated");
	gss_release_buffer(&trash, &out);
	return -1;
    }

    /* Allocate a buffer to hold the message in case we can't write it       */
    /* all...XXX may want to realloc [or do nothing] if buffer is already    */
    /* there [and big enough].                                               */
    if ((obuf->data = (char *)malloc(out.length + 4)) == NULL) {
	S5LogUpdate(S5LogDefaultHandle, S5_LOG_WARNING, 0, "GSS: Out of memory when making header");
	gss_release_buffer(&trash, &out);
	return -1;
    }

    /* Setup the buffer...                                                   */
    SETVERS(obuf->data, 0x01);
    SETMTYP(obuf->data, 0x03);
    SETELEN(obuf->data, out.length);
    memcpy(obuf->data+4, out.value, out.length);
    obuf->len = out.length + 4;
    obuf->off = 0;

    gss_release_buffer(&trash, &out);

    /* We're done...                                                         */
    S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "GSS: Done encoding message, output length: %d", obuf->len);
    return ibuf->len;
}

/* Decode ibuf into obuf...                                                  */
static int lsGssapiDecodePacket(S5Packet *ibuf, S5Packet *obuf, void *context) {
    GssInfo *ginfo = (GssInfo *)context;
    gss_buffer_desc in, out;
    OM_uint32 status, trash;
    u_char ver, mtype;
    int state, qop;
    u_short elen;

    /* Pull off the header...                                                */
    ver   = ibuf->data[GENCAP_VERS];
    mtype = ibuf->data[GENCAP_MTYP];
    GETELEN(ibuf->data, elen);

    if ((ibuf->off - 4 != elen) || (ver != 0x01) || (mtype != 0x03)) {
	S5LogUpdate(S5LogDefaultHandle, S5_LOG_WARNING, 0, "GSS: Bad header on packet, ignoring.");
	return -1;
    }

    /* Setup the gss buffers.                                                */
    in.value   = ibuf->data + 4;
    in.length  = ibuf->off  - 4;
    out.value  = NULL;
    out.length = 0;

    S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "GSS: Decoding a message of length: %d", ibuf->off);
    MUTEX_LOCK(krb_mutex);

    /* Try to unseal the message...                                          */
    if ((status = gss_unseal(&trash, ginfo->context, &in, &out, &state, &qop)) != GSS_S_COMPLETE) {
	lsGssapiDisplayError(status, trash, 0);
	MUTEX_UNLOCK(krb_mutex);
	return -1;
    }
    
    MUTEX_UNLOCK(krb_mutex);

    /* It worked, so we're done -- just copy out the pointer and the length. */
    if ((obuf->data = (char *)malloc(out.length)) == NULL) {
	S5LogUpdate(S5LogDefaultHandle, S5_LOG_WARNING, 0, "GSS: Out of memory when copying data");
	gss_release_buffer(&trash, &out);
	return -1;
    }
    memcpy(obuf->data, out.value, out.length);
    obuf->len  = out.length;
    obuf->off  = 0;
    
    gss_release_buffer(&trash, &out);

    S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "GSS: Done decoding message, output length: %d", obuf->len);
    return obuf->len;
}

/* Encode or decode ibuf into obuf.                                          */
static int lsGssapiCodePacket(S5Packet *ibuf, S5Packet *obuf, int direction, void *context) {
    if (direction == S5_ENCODE) {
	return lsGssapiEncodePacket(ibuf, obuf, context);
    } else {
	return lsGssapiDecodePacket(ibuf, obuf, context);
    }
}

/* Check to see if a whole encapsulated packet is available...               */
static int lsGssapiCheckPacket(S5Packet *buf, void *context) {
    int len;
    u_short tmp;

    if (!buf->data) return 4;
    if ((len = buf->len - 4) < 0) return -len;

    GETELEN(buf->data, tmp);
    if (len < tmp) return tmp - len;
    return 0;
}

/* Cleanup the context we created...                                         */
static int lsGssapiCleanContext(void *context) {
    if (context) {
	OM_uint32 mstatus;

	MUTEX_LOCK(krb_mutex);
	gss_delete_sec_context(&mstatus, &((GssInfo *)context)->context, NULL);
	MUTEX_UNLOCK(krb_mutex);
	free(context);
    }

    return 1;
}

/* The GSS-API authentication method (kerberos5) for the client...           */
int lsGssapiCliAuth(S5IOHandle fd, S5AuthInfo *ainfo, char *name) {
    OM_uint32 status, trash, junk, ret_flags = GSS_C_CONF_FLAG | GSS_C_INTEG_FLAG;
    gss_buffer_desc sendtok, recvtok, *token_ptr = GSS_C_NO_BUFFER;
    gss_ctx_id_t context = GSS_C_NO_CONTEXT;
    u_char enc = ENCAP_NONE;
    gss_name_t server;
#if defined(sun) && defined(__svr4__)
    S5IOHandle junksd = S5InvalidIOHandle;
#endif
    int ioerr = 0;

    recvtok.value  = NULL;
    recvtok.length = 0;
    sendtok.value  = NULL;
    sendtok.length = 0;
    MUTEX_LOCK(krb_mutex);

    /* Find out the name of the service we'll be authenticating with...      */
    if (lsGssapiServiceName(fd, 0, &server) < 0) {
	S5LogUpdate(S5LogDefaultHandle, S5_LOG_WARNING, 0, "GSS: Failed to determine service name");
	MUTEX_UNLOCK(krb_mutex);
	return AUTH_FAIL;
    }

    S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "GSS: Imported name");

    /* As long as we need to keep sending some context info, and there's no  */
    /* errors, keep sending it...                                            */
    for (;;) {
	status = gss_init_sec_context(&trash, GSS_C_NO_CREDENTIAL, &context, server,
				      GSS_C_NULL_OID, GSS_C_MUTUAL_FLAG | GSS_C_REPLAY_FLAG,
				      0, NULL, token_ptr, NULL, &sendtok, &ret_flags,
				      NULL);
	
	if (token_ptr != GSS_C_NO_BUFFER) gss_release_buffer(&junk, &recvtok);
	if (status != GSS_S_COMPLETE && status != GSS_S_CONTINUE_NEEDED) {
	    gss_release_buffer(&junk, &sendtok);
	    break;
	}

	if (sendtok.length != 0) {
	    if ((ioerr = (SendToken(fd, 0x01, &sendtok, &trash) < 0))) {
		gss_release_buffer(&junk, &sendtok);
		break;
	    }
	}

	gss_release_buffer(&junk, &sendtok);
	if (status != GSS_S_CONTINUE_NEEDED)                             break;
	if ((ioerr = (RecvToken(fd, 0x01, &recvtok, &trash) < 0)))       break;

	token_ptr = &recvtok;
    }

    /* Solaris has a bug in dup function so we dup here to grab the socket   */
    /* used by gssapi.							     */
#if defined(sun) && defined(__svr4__)
    if (junksd == S5InvalidIOHandle) junksd = REAL(dup)(STDERR_FILENO);
    else REAL(dup2)(STDERR_FILENO, junksd);
#endif

    /* We're done with the server's name, so we can release it.              */
    gss_release_name(status?&junk:&trash, &server);

    /* Make sure there wasn't an error...                                    */
    if (ioerr || status != GSS_S_COMPLETE) {
	lsGssapiDisplayError(status, trash, ioerr);
	gss_delete_sec_context(&trash, &context, NULL);
	MUTEX_UNLOCK(krb_mutex);
        return AUTH_FAIL;
    }

    /* Find out what kind of encapsulation we can/should do.                 */
    MUTEX_LOCK(env_mutex);
    if      ((ret_flags & GSS_C_CONF_FLAG)  &&  getenv("SOCKS5_ENCRYPT"))  enc = ENCAP_CONF;
    else if ((ret_flags & GSS_C_INTEG_FLAG) && !getenv("SOCKS5_NOINTCHK")) enc = ENCAP_INTEG;
    MUTEX_UNLOCK(env_mutex);

    sendtok.value  = &enc;
    sendtok.length = 1;

    /* Send the server a message asking to do that method...                 */
    if (SendToken(fd, 0x02, &sendtok, &trash) < 0) {
	lsGssapiDisplayError(0, 0, 1);
	gss_delete_sec_context(&trash, &context, NULL);
	MUTEX_UNLOCK(krb_mutex);
        return AUTH_FAIL;
    }

    /* Find out what the server thinks we should do...                       */
    if (RecvToken(fd, 0x02, &recvtok, &trash) < 0) {
	lsGssapiDisplayError(0, 0, 1);
	gss_delete_sec_context(&trash, &context, NULL);
	MUTEX_UNLOCK(krb_mutex);
        return AUTH_FAIL;
    }

    enc = *(char *)recvtok.value;
    gss_release_buffer(&trash, &recvtok);

    /* If we'll be doing some kind of encapsulation, store the type and the  */
    /* context.                                                              */
    if (enc != 0x00) {
	ainfo->context = (void *)malloc(sizeof(GssInfo));

	if (!ainfo->context) {
	    S5LogUpdate(S5LogDefaultHandle, S5_LOG_WARNING, 0, "GSS: Malloc failed");
	    gss_delete_sec_context(&trash, &context, NULL);
	    MUTEX_UNLOCK(krb_mutex);
	    return AUTH_FAIL;
	}

	((GssInfo *)ainfo->context)->options = (enc == ENCAP_CONF)?1:0;
	((GssInfo *)ainfo->context)->context = context;
	ainfo->clean  = lsGssapiCleanContext;
	ainfo->check  = lsGssapiCheckPacket;
	ainfo->encode = lsGssapiCodePacket;
    } else {
	/* Otherwise delete the context...                                   */
	gss_delete_sec_context(&trash, &context, NULL);
    }

    /* We're done, so we can return...                                       */
    MUTEX_UNLOCK(krb_mutex);
    return AUTH_OK;
}

/* The GSS-API authentication method (kerberos5) for the server...           */
int lsGssapiSrvAuth(S5IOHandle fd, S5AuthInfo *ainfo, char *name) {
    gss_cred_id_t creds  = GSS_C_NO_CREDENTIAL;
    gss_ctx_id_t context = GSS_C_NO_CONTEXT;
    gss_buffer_desc sendtok, recvtok;
    OM_uint32 status, trash, junk, rflags;
    gss_name_t client, server;
    gss_OID doid;
    u_char enc;
    int ioerr;

    recvtok.value  = NULL;
    recvtok.length = 0;
    sendtok.value  = NULL;
    sendtok.length = 0;
    MUTEX_LOCK(krb_mutex);

    /* Find the name of the service we'll be using...                        */
    if (lsGssapiServiceName(fd, 1, &server) < 0) {
	S5LogUpdate(S5LogDefaultHandle, S5_LOG_WARNING, 0, "GSS: Failed to determine service name");
	MUTEX_UNLOCK(krb_mutex);
	return AUTH_FAIL;
    }

    /* Acquire the initial credentials...                                    */
    S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "GSS: Imported name");
    status = gss_acquire_cred(&trash, server, 0, GSS_C_NULL_OID_SET, GSS_C_ACCEPT, &creds, NULL, NULL);
    gss_release_name(&trash, &server);

    /* Make sure everythings OK.                                             */
    if (status != GSS_S_COMPLETE) {
	lsGssapiDisplayError(status, trash, 0);
	gss_delete_sec_context(&trash, &context, NULL);
	MUTEX_UNLOCK(krb_mutex);
	return AUTH_FAIL;
    }

    /* Keep accepting tokens and handling them as until we're done or        */
    /* there's an error...                                                   */
    for (;;) {
	if ((ioerr = RecvToken(fd, 0x01, &recvtok, &trash)) < 0)         break;

	status = gss_accept_sec_context(&trash, (gss_ctx_id_t *)&context, creds,
					&recvtok, GSS_C_NO_CHANNEL_BINDINGS,
					&client, &doid, &sendtok, &rflags, NULL,
					NULL);

	gss_release_buffer(&junk, &recvtok);

	if (status != GSS_S_COMPLETE && status != GSS_S_CONTINUE_NEEDED) {
	    gss_release_buffer(&junk, &sendtok);
	    break;
	}

	if (sendtok.length != 0) {
	    if ((ioerr = SendToken(fd, 0x01, &sendtok, &trash)) < 0) {
 		gss_release_buffer(&junk, &sendtok);
	        break;
	    }
	}

	gss_release_buffer(&junk, &sendtok);
	if (status != GSS_S_CONTINUE_NEEDED)                             break;
    }

    /* Find out if we're done or if there was an error...If so, handle it.   */
    if (ioerr || status != GSS_S_COMPLETE) {
	lsGssapiDisplayError(status, trash, ioerr);
	gss_delete_sec_context(&trash, &context, NULL);	
	MUTEX_UNLOCK(krb_mutex);
	return AUTH_FAIL;
    }
    
    S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "GSS: Done Authenticating; Displaying Name");

    /* Get the name into a somewhat readable form.                           */
    if ((status = gss_display_name(&trash, client, &sendtok, &doid)) != GSS_S_COMPLETE) {
	lsGssapiDisplayError(status, trash, 0);
	gss_delete_sec_context(&trash, &context, NULL);
	MUTEX_UNLOCK(krb_mutex);
	return AUTH_FAIL;
    }

    /* Copy the name into the space that was provided...                     */
    if (sendtok.length >= S5_USERNAME_SIZE) sendtok.length = S5_USERNAME_SIZE-1;
    strncpy(name, sendtok.value, sendtok.length);
    gss_release_buffer(&trash, &sendtok);

    /* Find out what the client wants to do encapsulation wise.              */
    if (RecvToken(fd, 0x02, &recvtok, &trash) < 0) {
	lsGssapiDisplayError(0, 0, 1);
	gss_delete_sec_context(&trash, &context, NULL);
	MUTEX_UNLOCK(krb_mutex);
	return AUTH_FAIL;
    }
    S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "GSS: Client asked for encapsulation #%d", (int)*(char *)recvtok.value);

    MUTEX_LOCK(env_mutex);
    if (getenv("SOCKS5_FORCE_ENCRYPT")) *(char *)recvtok.value = ENCAP_CONF;
    MUTEX_UNLOCK(env_mutex);

    /* Make sure we can do what we've decided we need to do...               */
    if (*(char *)recvtok.value == ENCAP_INTEG && !(rflags & GSS_C_INTEG_FLAG)) {
	S5LogUpdate(S5LogDefaultHandle, S5_LOG_WARNING, 0, "GSS: Underlying Mechansim does not support plain integrity checking -- trying encryption");
	*(char *)recvtok.value = ENCAP_CONF;
    }

    if (*(char *)recvtok.value == ENCAP_CONF && !(rflags & GSS_C_CONF_FLAG)) {
	S5LogUpdate(S5LogDefaultHandle, S5_LOG_WARNING, 0, "GSS: Underlying Mechansim does not support encryption");
	gss_delete_sec_context(&trash, &context, NULL);	
	MUTEX_UNLOCK(krb_mutex);
	return AUTH_FAIL;
    }

    /* Send back the decided encapsulation methode to the client.            */
    enc = *(char *)recvtok.value;
    if (SendToken(fd, 0x02, &recvtok, &trash) < 0) {
	lsGssapiDisplayError(0, 0, 1);
	gss_delete_sec_context(&trash, &context, NULL);
	MUTEX_UNLOCK(krb_mutex);
	return AUTH_FAIL;
    }
    S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "GSS: Server Chose encapsulation #%d", enc);
    gss_release_buffer(&trash, &recvtok);

    /* As long as we're doing some kind of encapsulation, keep a context     */
    /* around with the gssapi context in it and a byte saying what           */
    /* encapsulation we will be doing.                                       */
    if (enc != 0x00) {
	ainfo->context = (void *)malloc(sizeof(GssInfo));

	if (!ainfo->context) {
	    S5LogUpdate(S5LogDefaultHandle, S5_LOG_WARNING, 0, "GSS: Malloc failed");
	    gss_delete_sec_context(&trash, &context, NULL);
	    MUTEX_UNLOCK(krb_mutex);
	    return AUTH_FAIL;
	}

	((GssInfo *)ainfo->context)->options = (enc == ENCAP_CONF)?1:0;
	((GssInfo *)ainfo->context)->context = context;
	ainfo->clean  = lsGssapiCleanContext;
	ainfo->check  = lsGssapiCheckPacket;
	ainfo->encode = lsGssapiCodePacket;
    } else {
	/* Otherwise, get rid of the context, we won't need it anymore...    */
	gss_delete_sec_context(&trash, &context, NULL);
    }

    /* Everything's ok, so unlock the mutex and return...                    */
    MUTEX_UNLOCK(krb_mutex);
    return AUTH_OK;
}

#else
/* Without Kerberos, we don't do anything.                                   */
int lsGssapiCliAuth(S5IOHandle fd, S5AuthInfo *ainfo, char *namep) {
    S5LogUpdate(S5LogDefaultHandle, S5_LOG_ERROR, 0, "GSS: Not compiled");
    return AUTH_FAIL;
}

int lsGssapiSrvAuth(S5IOHandle fd, S5AuthInfo *ainfo, char *namep) {
    S5LogUpdate(S5LogDefaultHandle, S5_LOG_ERROR, 0, "GSS: Not compiled");
    return AUTH_FAIL;
}
#endif


