/* Kerberos4 SASL plugin
 * Rob Siemborski
 * Tim Martin 
 * $Id: kerberos4.c,v 1.100 2009/03/10 16:27:52 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>
#include <stdlib.h>
#include <string.h>
#include <krb.h>

#ifdef WITH_DES
# ifdef WITH_SSL_DES
#  include <openssl/des.h>
# else
#  include <des.h>
# endif /* WITH_SSL_DES */
#endif /* WITH_DES */

#ifdef WIN32
# include <winsock2.h>
#elif defined(macintosh)
#include <kcglue_krb.h>
#else
# include <sys/param.h>
# include <sys/socket.h>
# include <netinet/in.h>
# include <arpa/inet.h>
# include <netdb.h>
#endif /* WIN32 */
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <fcntl.h>
#include <sasl.h>
#include <saslutil.h>
#include <saslplug.h>

#include <errno.h>
#include <ctype.h>

#include "plugin_common.h"

#ifdef macintosh
/*
 * krb.h doenst include some functions and mac compiler is picky
 * about declartions
 */
#include <extra_krb.h>
#include <sasl_kerberos4_plugin_decl.h>
#endif

#ifdef WIN32
/* This must be after sasl.h, saslutil.h */
# include "saslKERBEROSV4.h"

/* KClient doesn't define this */
typedef struct krb_principal {
    char name[ANAME_SZ];
    char instance[INST_SZ];
    char realm[REALM_SZ];
} krb_principal;

/* This isn't defined under WIN32.  For access() */
#ifndef R_OK
#define R_OK 04
#endif
/* we also need io.h for access() prototype */
#include <io.h>
#endif /* WIN32 */

#ifdef sun
/* gotta define gethostname ourselves on suns */
extern int gethostname(char *, int);
#endif

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

static const char plugin_id[] = "$Id: kerberos4.c,v 1.100 2009/03/10 16:27:52 mel Exp $";

#ifndef KEYFILE
#define KEYFILE "/etc/srvtab";
#endif

#define KRB_SECFLAG_NONE (1)
#define KRB_SECFLAG_INTEGRITY (2)
#define KRB_SECFLAG_ENCRYPTION (4)
#define KRB_SECFLAGS (7)
#define KRB_SECFLAG_CREDENTIALS (8)

#define KRB_DES_SECURITY_BITS (56)
#define KRB_INTEGRITY_BITS (1)

typedef enum Krb_sec {
    KRB_SEC_NONE = 0,
    KRB_SEC_INTEGRITY = 1,
    KRB_SEC_ENCRYPTION = 2
} Krb_sec_t;

typedef struct context {
    int state;
    
    int challenge;         /* this is the challenge (32 bit int) used 
			      for the authentication */
    
    char *service;                   /* kerberos service */
    char instance[ANAME_SZ];
    char pname[ANAME_SZ];
    char pinst[INST_SZ];
    char prealm[REALM_SZ];
    char *hostname;                  /* hostname */
    char *realm;                     /* kerberos realm */
    char *auth;                      /* */
    
    CREDENTIALS credentials;
    
    des_cblock key;                  /* session key */
    des_cblock session;              /* session key */
    
    des_key_schedule init_keysched;  /* key schedule for initialization */
    des_key_schedule enc_keysched;   /* encryption key schedule */
    des_key_schedule dec_keysched;   /* decryption key schedule */
    
    
    struct sockaddr_in ip_local;     /* local ip address and port.
					needed for layers */
    struct sockaddr_in ip_remote;    /* remote ip address and port.
					needed for layers */
    
    const sasl_utils_t *utils;       /* this is useful to have around */
    
    Krb_sec_t sec_type;
    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;

    decode_context_t decode_context;
    
    char *out_buf;                   /* per-step mem management */
    unsigned out_buf_len;
    
    const char *user;                      /* used by client */
    
    int secflags; /* client/server supports layers? */
    
    long time_sec; /* These are used to make sure we are getting */
    char time_5ms; /* strictly increasing timestamps */
    
} context_t;

#define KRB_LOCK_MUTEX(utils)  \
    if(((sasl_utils_t *)(utils))->mutex_lock(krb_mutex) != 0) { \
       ((sasl_utils_t *)(utils))->seterror(((sasl_utils_t *)(utils))->conn, \
                                           0, "error locking mutex"); \
			           return SASL_FAIL; \
                                }
#define KRB_UNLOCK_MUTEX(utils) \
    if(((sasl_utils_t *)(utils))->mutex_unlock(krb_mutex) != 0) { \
       ((sasl_utils_t *)(utils))->seterror(((sasl_utils_t *)(utils))->conn, \
                                           0, "error unlocking mutex"); \
			           return SASL_FAIL; \
                                }

/* Mutex for not-thread-safe kerberos 4 library */
static void *krb_mutex = NULL;
static char *srvtab = NULL;
static unsigned refcount = 0;

static int kerberosv4_encode(void *context,
			     const struct iovec *invec,
			     unsigned numiov,
			     const char **output,
			     unsigned *outputlen)
{
    int len, ret;
    context_t *text = (context_t *)context;
    struct buffer_info *inblob, bufinfo;
    
    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;
    }
    
    ret = _plug_buf_alloc(text->utils, &(text->encode_buf),
			  &text->encode_buf_len, inblob->curlen+40);
    
    if(ret != SASL_OK) return ret;
    
    KRB_LOCK_MUTEX(text->utils);
    
    if (text->sec_type == KRB_SEC_ENCRYPTION) {
	/* Type incompatibility on 4th arg probably means you're
	   building against krb4 in MIT krb5, but got the OpenSSL
	   headers in your way. You need to not use openssl/des.h with
	   MIT kerberos. */
	len=krb_mk_priv(inblob->data, (text->encode_buf+4),
			inblob->curlen,  text->init_keysched, 
			&text->session, &text->ip_local,
			&text->ip_remote);
    } else if (text->sec_type == KRB_SEC_INTEGRITY) {
	len=krb_mk_safe(inblob->data, (text->encode_buf+4),
			inblob->curlen,
			&text->session, &text->ip_local, &text->ip_remote);
    } else {
	len = -1;
    }
    
    KRB_UNLOCK_MUTEX(text->utils);
    
    /* returns -1 on error */
    if (len==-1) return SASL_FAIL;
    
    /* now copy in the len of the buffer in network byte order */
    *outputlen=len+4;
    len=htonl(len);
    memcpy(text->encode_buf, &len, 4);
    
    /* Setup the const pointer */
    *output = text->encode_buf;
    
    return SASL_OK;
}

static int kerberosv4_decode_packet(void *context,
				    const char *input, unsigned inputlen,
				    char **output, unsigned *outputlen)
{
    context_t *text = (context_t *) context;
    int result;
    MSG_DAT data;
    
    memset(&data,0,sizeof(MSG_DAT));
    
    KRB_LOCK_MUTEX(text->utils);
    
    if (text->sec_type == KRB_SEC_ENCRYPTION) {
	result=krb_rd_priv(input, inputlen, text->init_keysched, 
			   &text->session, &text->ip_remote,
			   &text->ip_local, &data);
    } else if (text->sec_type == KRB_SEC_INTEGRITY) {
        result = krb_rd_safe(input, inputlen,
			     &text->session, &text->ip_remote,
			     &text->ip_local, &data);
    } else {
        KRB_UNLOCK_MUTEX(text->utils);
	text->utils->seterror(text->utils->conn, 0,
			      "KERBEROS_4 decode called with KRB_SEC_NONE");
	return SASL_FAIL;
    }
    
    KRB_UNLOCK_MUTEX(text->utils);
    
    /* see if the krb library gave us a failure */
    if (result != 0) {
	text->utils->seterror(text->utils->conn, 0, get_krb_err_txt(result));
	return SASL_FAIL;
    }
    
    /* check to make sure the timestamps are ok */
    if ((data.time_sec < text->time_sec) || /* if an earlier time */
	(((data.time_sec == text->time_sec) && /* or the exact same time */
	  (data.time_5ms < text->time_5ms)))) 
	{
	    text->utils->seterror(text->utils->conn, 0, "timestamps not ok");
	    return SASL_FAIL;
	}
    
    text->time_sec = data.time_sec;
    text->time_5ms = data.time_5ms;
    
    result = _plug_buf_alloc(text->utils, &text->decode_once_buf,
			     &text->decode_once_buf_len,
			     data.app_length + 1);
    if(result != SASL_OK)
	return result;
    
    *output = text->decode_once_buf;
    *outputlen = data.app_length;
    memcpy(*output, data.app_data, data.app_length);
    (*output)[*outputlen] = '\0';
    
    return SASL_OK;
}

static int kerberosv4_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,
		       kerberosv4_decode_packet, text);
    
    *output = text->decode_buf;
    
    return ret;
}

static int new_text(const sasl_utils_t *utils, context_t **text)
{
    context_t *ret = (context_t *) utils->malloc(sizeof(context_t));

    if (ret == NULL) {
	MEMERROR(utils);
	return SASL_NOMEM;
    }
    
    memset(ret, 0, sizeof(context_t));
    
    ret->state = 1;
    ret->utils = utils;
    
    *text = ret;
    
    return SASL_OK;
}

static void kerberosv4_common_mech_dispose(void *conn_context,
					   const sasl_utils_t *utils)
{
    context_t *text = (context_t *)conn_context;
    
    if(!text) return;
    
    _plug_decode_free(&text->decode_context);
    if (text->encode_buf) utils->free(text->encode_buf);
    if (text->decode_buf) utils->free(text->decode_buf);
    if (text->decode_once_buf) utils->free(text->decode_once_buf);
    if (text->out_buf) utils->free(text->out_buf);
    if (text->enc_in_buf) {
	if(text->enc_in_buf->data) utils->free(text->enc_in_buf->data);
	utils->free(text->enc_in_buf);
    }
    /* no need to free userid, it's just the interaction result */
    
    utils->free(text);
}

static void
kerberosv4_common_mech_free(void *glob_context __attribute__((unused)),
			    const sasl_utils_t *utils)
{
    if (krb_mutex) {
	utils->mutex_free(krb_mutex);
	krb_mutex = NULL; /* in case we need to re-use it */
    }
    refcount--;
    if (srvtab && !refcount) {
	utils->free(srvtab);
	srvtab = NULL;
    }
}

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

static int cando_sec(sasl_security_properties_t *props,
		     int external_ssf,
		     int secflag)
{
    int need;
    int musthave;
    
    if(props->maxbufsize == 0) {
	need = musthave = 0;
    } else {
	need = props->max_ssf - external_ssf;
	musthave = props->min_ssf - external_ssf;
    }

    switch (secflag) {
    case KRB_SECFLAG_NONE:
	if (musthave <= 0)
	    return 1;
	break;
    case KRB_SECFLAG_INTEGRITY:
	if ((musthave <= KRB_INTEGRITY_BITS)
	    && (KRB_INTEGRITY_BITS <= need))
	    return 1;
	break;
    case KRB_SECFLAG_ENCRYPTION:
	if ((musthave <= KRB_DES_SECURITY_BITS)
	    && (KRB_DES_SECURITY_BITS <= need))
	    return 1;
	break;
    case KRB_SECFLAG_CREDENTIALS:
	if (props->security_flags & SASL_SEC_PASS_CREDENTIALS)
	    return 1;
	break;
    }
    return 0;
}

static int ipv4_ipfromstring(const sasl_utils_t *utils, const char *addr,
			     struct sockaddr_in *out) 
{
    struct sockaddr_storage ss;
    int result;
    
    result = _plug_ipfromstring(utils, addr,
				(struct sockaddr *)&ss, sizeof(ss));
    if (result != SASL_OK) {
	/* couldn't get local IP address */
	return result;
    }
    /* Kerberos_V4 supports only IPv4 */
    if (((struct sockaddr *)&ss)->sa_family != AF_INET)
	return SASL_FAIL;
    memcpy(out, &ss, sizeof(struct sockaddr_in));
    
    return SASL_OK;
}

#ifndef macintosh
static int
kerberosv4_server_mech_new(void *glob_context __attribute__((unused)),
			   sasl_server_params_t *sparams,
			   const char *challenge __attribute__((unused)),
			   unsigned challen __attribute__((unused)),
			   void **conn_context)
{
    return new_text(sparams->utils, (context_t **) conn_context);
}

static int kerberosv4_server_mech_step(void *conn_context,
				       sasl_server_params_t *sparams,
				       const char *clientin,
				       unsigned clientinlen,
				       const char **serverout,
				       unsigned *serveroutlen,
				       sasl_out_params_t *oparams)
{
    context_t *text = (context_t *) conn_context;
    int result;

    *serverout = NULL;
    *serveroutlen = 0;
    
    switch (text->state) {

    case 1: {
	/* random 32-bit number */
	int randocts, nchal;
	
	/* shouldn't we check for erroneous client input here?!? */
	
	sparams->utils->rand(sparams->utils->rpool,(char *) &randocts ,
			     sizeof(randocts));    
	text->challenge=randocts; 
	nchal = htonl(text->challenge);
	
	result = _plug_buf_alloc(text->utils, &text->out_buf,
				 &text->out_buf_len, 5);
	if (result != SASL_OK) return result;
	
	memcpy(text->out_buf,&nchal,4);
	*serverout = text->out_buf;
	*serveroutlen = 4;
	
	text->state = 2;

	return SASL_CONTINUE;
    }
    
    case 2: {
	int nchal;
	unsigned char sout[8];  
	AUTH_DAT ad;
	KTEXT_ST ticket;
	unsigned lup;
	struct sockaddr_in addr;
	char *dot;
	
	/* received authenticator */
	
	/* create ticket */
	if (clientinlen > MAX_KTXT_LEN) {
	    text->utils->seterror(text->utils->conn,0,
				  "request larger than maximum ticket size");
	    return SASL_FAIL;
	}
	
	ticket.length=clientinlen;
	for (lup = 0; lup < clientinlen; lup++)      
	    ticket.dat[lup] = clientin[lup];
	
	KRB_LOCK_MUTEX(sparams->utils);
	
	text->realm = krb_realmofhost(sparams->serverFQDN);
	
	/* get instance */
	strncpy (text->instance, krb_get_phost (sparams->serverFQDN),
		 sizeof (text->instance));
	
	KRB_UNLOCK_MUTEX(sparams->utils);
	
	text->instance[sizeof(text->instance)-1] = 0;

	/* At some sites, krb_get_phost() sensibly but
	 * atypically returns FQDNs, versus the first component,
	 * which is what we need for RFC2222 section 7.1
	 */
	dot = strchr(text->instance, '.');
	if (dot) *dot = '\0';

	memset(&addr, 0, sizeof(struct sockaddr_in));
	
#ifndef KRB4_IGNORE_IP_ADDRESS
	/* (we ignore IP addresses in krb4 tickets at CMU to facilitate moving
	   from machine to machine) */
	
	/* get ip number in addr*/
	result = ipv4_ipfromstring(sparams->utils, sparams->ipremoteport, &addr);
	if (result != SASL_OK || !sparams->ipremoteport) {
	    SETERROR(text->utils, "couldn't get remote IP address");
	    return result;
	}
#endif
	
	/* check ticket */
	
	KRB_LOCK_MUTEX(sparams->utils);
	result = krb_rd_req(&ticket, (char *) sparams->service, text->instance, 
			    addr.sin_addr.s_addr, &ad, srvtab);
	KRB_UNLOCK_MUTEX(sparams->utils);
	
	if (result) { /* if fails mechanism fails */
	    text->utils->seterror(text->utils->conn,0,
				  "krb_rd_req failed service=%s instance=%s error code=%s (%i)",
				  sparams->service, text->instance,get_krb_err_txt(result),result);
	    return SASL_BADAUTH;
	}
	
	/* 8 octets of data
	 * 1-4 checksum+1
	 * 5 security layers
	 * 6-8max cipher text buffer size
	 * use DES ECB in the session key
	 */
	
	nchal=htonl(text->challenge+1);
	memcpy(sout, &nchal, 4);
	sout[4]= 0;
	if (cando_sec(&sparams->props, sparams->external_ssf,
		      KRB_SECFLAG_NONE))
	    sout[4] |= KRB_SECFLAG_NONE;
	if (cando_sec(&sparams->props, sparams->external_ssf,
		      KRB_SECFLAG_INTEGRITY))
	    sout[4] |= KRB_SECFLAG_INTEGRITY;
	if (cando_sec(&sparams->props, sparams->external_ssf,
		      KRB_SECFLAG_ENCRYPTION))
	    sout[4] |= KRB_SECFLAG_ENCRYPTION;
	if (cando_sec(&sparams->props, sparams->external_ssf,
		      KRB_SECFLAG_CREDENTIALS))
	    sout[4] |= KRB_SECFLAG_CREDENTIALS;

	if(sparams->props.maxbufsize) {
	    int tmpmaxbuf = (sparams->props.maxbufsize > 0xFFFFFF) ? 0xFFFFFF : sparams->props.maxbufsize;

	    sout[5]=((tmpmaxbuf >> 16) & 0xFF);
	    sout[6]=((tmpmaxbuf >> 8) & 0xFF);
	    sout[7]=(tmpmaxbuf & 0xFF);
	} else {
            /* let's say we can support up to 64K */
	    /* no inherent inability with our layers to support more */

	    sout[5]=0x00;  /* max ciphertext buffer size */
	    sout[6]=0xFF;
	    sout[7]=0xFF;
	}
    
	memcpy(text->session, ad.session, 8);
	memcpy(text->pname, ad.pname, sizeof(text->pname));
	memcpy(text->pinst, ad.pinst, sizeof(text->pinst));
	memcpy(text->prealm, ad.prealm, sizeof(text->prealm));
	des_key_sched(&ad.session, text->init_keysched);
	
	/* make keyschedule for encryption and decryption */
	des_key_sched(&ad.session, text->enc_keysched);
	des_key_sched(&ad.session, text->dec_keysched);
	
	des_ecb_encrypt((des_cblock *)sout,
			(des_cblock *)sout,
			text->init_keysched,
			DES_ENCRYPT);
	
	result = _plug_buf_alloc(text->utils, &text->out_buf,
				 &text->out_buf_len, 9);
	if(result != SASL_OK)
	    return result;
	
	memcpy(text->out_buf,&sout,8);
	*serverout = text->out_buf;
	*serveroutlen = 8;
	
	text->state = 3;

	return SASL_CONTINUE;
    }
    
    case 3: {
	int result;
	int testnum;
	int flag;
	unsigned char *in;
	
	if ((clientinlen == 0) || (clientinlen % 8 != 0)) {
	    text->utils->seterror(text->utils->conn,0,
				  "Response to challengs is not a multiple of 8 octets (a DES block)");
	    return SASL_FAIL;	
	}
	
	/* we need to make a copy because des does in place decrpytion */
	in = sparams->utils->malloc(clientinlen + 1);
	if (in == NULL) {
	    MEMERROR(sparams->utils);
	    return SASL_NOMEM;
	}
	
	memcpy(in, clientin, clientinlen);
	in[clientinlen] = '\0';
	
	/* decrypt; verify checksum */
	
	des_pcbc_encrypt((des_cblock *)in,
			 (des_cblock *)in,
			 clientinlen,
			 text->init_keysched,
			 &text->session,
			 DES_DECRYPT);
	
	testnum = (in[0]*256*256*256)+(in[1]*256*256)+(in[2]*256)+in[3];
	
	if (testnum != text->challenge) {
	    SETERROR(sparams->utils, "incorrect response to challenge");
	    return SASL_BADAUTH;
	}
	
	if (!cando_sec(&sparams->props, sparams->external_ssf,
		       in[4] & KRB_SECFLAGS)) {
	    SETERROR(sparams->utils,
		     "invalid security property specified");
	    return SASL_BADPROT;
	}
	
	oparams->encode = &kerberosv4_encode;
	oparams->decode = &kerberosv4_decode;
	
	switch (in[4] & KRB_SECFLAGS) {
	case KRB_SECFLAG_NONE:
	    text->sec_type = KRB_SEC_NONE;
	    oparams->encode = NULL;
	    oparams->decode = NULL;
	    oparams->mech_ssf = 0;
	    break;
	case KRB_SECFLAG_INTEGRITY:
	    text->sec_type = KRB_SEC_INTEGRITY;
	    oparams->mech_ssf = KRB_INTEGRITY_BITS;
	    break;
	case KRB_SECFLAG_ENCRYPTION:
	    text->sec_type = KRB_SEC_ENCRYPTION;
	    oparams->mech_ssf = KRB_DES_SECURITY_BITS;
	    break;
	default:
	    /* Mark that we tried */
	    oparams->mech_ssf = 2;
	    SETERROR(sparams->utils, "not a supported encryption layer");
	    return SASL_BADPROT;
	}
	
	/* get ip data */
	/* get ip number in addr*/
	result = ipv4_ipfromstring(sparams->utils,
				   sparams->iplocalport, &(text->ip_local));
	if (result != SASL_OK) {
	    SETERROR(sparams->utils, "couldn't get local ip address");
	    /* couldn't get local IP address */
	    return result;
	}
	
	result = ipv4_ipfromstring(sparams->utils,
				   sparams->ipremoteport, &(text->ip_remote));
	if (result != SASL_OK) {
	    SETERROR(sparams->utils, "couldn't get remote ip address");
	    /* couldn't get remote IP address */
	    return result;
	}
	
	/* fill in oparams */
	oparams->maxoutbuf = (in[5] << 16) + (in[6] << 8) + in[7];
	if(oparams->mech_ssf) {
	    /* FIXME: Likely to be too large */
	    oparams->maxoutbuf -= 50;
	}
	
	if (sparams->canon_user) {
	    char *user=NULL, *authid=NULL;
	    size_t ulen = 0, alen = strlen(text->pname);
	    int ret, cflag = SASL_CU_AUTHID | SASL_CU_EXTERNALLY_VERIFIED;
	    
	    if (text->pinst[0]) {
		alen += strlen(text->pinst) + 1 /* for the . */;
	    }
	    flag = 0;
	    if (strcmp(text->realm, text->prealm)) {
		alen += strlen(text->prealm) + 1 /* for the @ */;
		flag = 1;
	    }
	    
	    authid = sparams->utils->malloc(alen + 1);
	    if (!authid) {
		MEMERROR(sparams->utils);
		return SASL_NOMEM;
	    }
	    
	    strcpy(authid, text->pname);
	    if (text->pinst[0]) {
		strcat(authid, ".");
		strcat(authid, text->pinst);
	    }
	    if (flag) {
		strcat(authid, "@");
		strcat(authid, text->prealm);
	    }
	    
	    if (in[8]) {
		user = sparams->utils->malloc(strlen((char *) in + 8) + 1);
		if (!user) {
		    MEMERROR(sparams->utils);
		    return SASL_NOMEM;
		}
		
		strcpy(user, (char *) in + 8);
		ulen = strlen(user);
	    } else {
	    	cflag |= SASL_CU_AUTHZID;
	    }
	    
	    ret = sparams->canon_user(sparams->utils->conn, authid, alen,
				      cflag, oparams);
	    sparams->utils->free(authid);
	    if (ret != SASL_OK) {
		if (user)
		    sparams->utils->free(user);
		return ret;
	    }
	    
	    if (user) {
	    	ret = sparams->canon_user(sparams->utils->conn, user, ulen,
				      SASL_CU_AUTHZID, oparams);
	    
		sparams->utils->free(user);
	    }
	    
	    if (ret != SASL_OK) return ret;
	}
	
	/* nothing more to do; authenticated */
	oparams->doneflag = 1;
	oparams->param_version = 0;
	
	/* used by layers */
	_plug_decode_init(&text->decode_context, text->utils,
			  (sparams->props.maxbufsize > 0xFFFFFF) ? 0xFFFFFF :
			  sparams->props.maxbufsize);
	
	sparams->utils->free(in);

	return SASL_OK;
    }
    
    default:
	sparams->utils->log(NULL, SASL_LOG_ERR,
			    "Invalid Kerberos server step %d\n", text->state);
	return SASL_FAIL;
    }
    
    return SASL_FAIL; /* should never get here */
}

static int kerberosv4_mech_avail(void *glob_context __attribute__((unused)),
				 sasl_server_params_t *sparams,
				 void **conn_context __attribute__((unused))) 
{
    struct sockaddr_in addr;
    
    if (!sparams->iplocalport || !sparams->ipremoteport
	|| ipv4_ipfromstring(sparams->utils,
			     sparams->iplocalport, &addr) != SASL_OK
	|| ipv4_ipfromstring(sparams->utils,
			     sparams->ipremoteport, &addr) != SASL_OK) {
	SETERROR(sparams->utils,
		 "KERBEROS_V4 unavailable due to lack of IPv4 information");
	return SASL_NOMECH;
    }
    
    return SASL_OK;
}


static sasl_server_plug_t kerberosv4_server_plugins[] = 
{
    {
	"KERBEROS_V4",			/* mech_name */
	KRB_DES_SECURITY_BITS,		/* max_ssf */
	SASL_SEC_NOPLAINTEXT
	| SASL_SEC_NOACTIVE
	| SASL_SEC_NOANONYMOUS
	| SASL_SEC_MUTUAL_AUTH,		/* security_flags */
	SASL_FEAT_SERVER_FIRST
	| SASL_FEAT_ALLOWS_PROXY,	/* features */
	NULL,				/* glob_context */
	&kerberosv4_server_mech_new,	/* mech_new */
	&kerberosv4_server_mech_step,	/* mech_step */
	&kerberosv4_common_mech_dispose,/* mech_dispose */
	&kerberosv4_common_mech_free,	/* mech_free */
	NULL,				/* setpass */
	NULL,				/* user_query */
	NULL,				/* idle */
	&kerberosv4_mech_avail,		/* mech_avail */
	NULL				/* spare */
    }
};
#endif /* macintosh */

int kerberos4_server_plug_init(const sasl_utils_t *utils,
			       int maxversion,
			       int *out_version,
			       sasl_server_plug_t **pluglist,
			       int *plugcount)
{
#ifdef macintosh
    return SASL_BADVERS;
#else
    const char *ret;
    unsigned int rl;
    
    if (maxversion < SASL_SERVER_PLUG_VERSION) {
	return SASL_BADVERS;
    }
    
    
    if (!krb_mutex) {
	krb_mutex = utils->mutex_alloc();
	if(!krb_mutex) {
	    return SASL_FAIL;
	}
    }
    
    if (!srvtab) {	
	utils->getopt(utils->getopt_context,
		      "KERBEROS_V4", "srvtab", &ret, &rl);
	
	if (ret == NULL) {
	    ret = KEYFILE;
	    rl = strlen(ret);
	}
	
	srvtab = utils->malloc(sizeof(char) * (rl + 1));
	if(!srvtab) {
	    MEMERROR(utils);
	    return SASL_NOMEM;
	}
	
	strcpy(srvtab, ret);
    }
    
    refcount++;
    
    /* fail if we can't open the srvtab file */
    if (access(srvtab, R_OK) != 0) {
	utils->log(NULL, SASL_LOG_ERR,
		   "can't access srvtab file %s: %m", srvtab, errno);
	if(!(--refcount)) {
	    utils->free(srvtab);
	    srvtab=NULL;
	}
	return SASL_FAIL;
    }
    
    *out_version = SASL_SERVER_PLUG_VERSION;
    *pluglist = kerberosv4_server_plugins;
    *plugcount = 1;
    
    return SASL_OK;
#endif
}

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

static int
kerberosv4_client_mech_new(void *glob_context __attribute__((unused)), 
			   sasl_client_params_t *params,
			   void **conn_context)
{
    return new_text(params->utils, (context_t **) conn_context);
}

static int kerberosv4_client_mech_step(void *conn_context,
				       sasl_client_params_t *cparams,
				       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;
    KTEXT_ST authent;
    int ret;

    *clientout = NULL;
    *clientoutlen = 0;
    
    authent.length = MAX_KTXT_LEN;
    
    switch (text->state) {

    case 1: {
	/* We should've just recieved a 32-bit number in network byte order.
	 * We want to reply with an authenticator. */
	int result;
	KTEXT_ST ticket;
	char *dot;
	
	memset(&ticket, 0L, sizeof(ticket));
	ticket.length = MAX_KTXT_LEN;   
	
	if (serverinlen != 4) {
	    text->utils->seterror(text->utils->conn, 0,
				  "server challenge not 4 bytes long");
	    return SASL_BADPROT; 
	}
	
	memcpy(&text->challenge, serverin, 4);
	
	text->challenge=ntohl(text->challenge); 
	
	if (cparams->serverFQDN == NULL) {
	    cparams->utils->log(NULL, SASL_LOG_ERR,
				"no 'serverFQDN' set");
	    SETERROR(text->utils, "paramater error");
	    return SASL_BADPARAM;
	}
	if (cparams->service == NULL) {
	    cparams->utils->log(NULL, SASL_LOG_ERR,
				"no 'service' set");
	    SETERROR(text->utils, "paramater error");
	    return SASL_BADPARAM;
	}
	
	KRB_LOCK_MUTEX(cparams->utils);
	text->realm=krb_realmofhost(cparams->serverFQDN);
	text->hostname=(char *) cparams->serverFQDN;
	
	/* the instance of the principal we're authenticating with */
	strncpy (text->instance, krb_get_phost (cparams->serverFQDN), 
		 sizeof (text->instance));
	
	/* text->instance is NULL terminated unless it was too long */
	text->instance[sizeof(text->instance)-1] = '\0';

	/* At some sites, krb_get_phost() sensibly but
	 * atypically returns FQDNs, versus the first component,
	 * which is what we need for RFC2222 section 7.1
	 */
	dot = strchr(text->instance, '.');
	if (dot) *dot = '\0';
	
#ifndef macintosh
	if ((result = krb_mk_req(&ticket, (char *) cparams->service, 
				 text->instance, text->realm, text->challenge)))
#else
	    memset(&text->credentials,0,sizeof(text->credentials));
	if (kcglue_krb_mk_req(ticket.dat,
			      &ticket.length,
			      cparams->service,
			      text->instance,
			      text->realm,
			      text->challenge,
			      &text->credentials.session,
			      text->credentials.pname,
			      text->credentials.pinst) != 0)
#endif
	    {
		KRB_UNLOCK_MUTEX(cparams->utils);
		
		text->utils->seterror(text->utils->conn,SASL_NOLOG,
				      "krb_mk_req() failed");
		
		cparams->utils->log(NULL, SASL_LOG_ERR, 
				    "krb_mk_req() failed: %s (%d)",
				    get_krb_err_txt(result), result);
		return SASL_FAIL;
	    }
	
	KRB_UNLOCK_MUTEX(cparams->utils);
	
	ret = _plug_buf_alloc(text->utils, &(text->out_buf),
			      &(text->out_buf_len), ticket.length);
	if (ret != SASL_OK) return ret;
	
	memcpy(text->out_buf, ticket.dat, ticket.length);
	
	*clientout = text->out_buf;
	*clientoutlen = ticket.length;
	
	text->state = 2;

	return SASL_CONTINUE;
    }
    
    /* challenge #2 */
    case 2: {
	int need = 0;
	int musthave = 0;
	int testnum;
	int nchal;    
	unsigned char *sout = NULL;
	unsigned len;
	unsigned char in[8];
	int result;
	int servermaxbuf;
	char *buf;
	int user_result = SASL_OK;
	
	/* try to get the authid */
	if (text->user == NULL) {
	    user_result = _plug_get_userid(cparams->utils, &text->user,
					   prompt_need);
	    
	    if (user_result != SASL_OK && user_result != SASL_INTERACT)
		return user_result;
	}
	
	/* free prompts we got */
	if (prompt_need && *prompt_need) {
	    cparams->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(cparams->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;
	}
	
	/* must be 8 octets */
	if (serverinlen!=8) {
	    SETERROR(cparams->utils,
		     "server response not 8 bytes long");
	    return SASL_BADAUTH;
	}
	
	memcpy(in, serverin, 8);
	
#ifndef macintosh
	/* get credentials */
	KRB_LOCK_MUTEX(cparams->utils);
	result = krb_get_cred((char *)cparams->service,
			      text->instance,
			      text->realm,
			      &text->credentials);
	KRB_UNLOCK_MUTEX(cparams->utils);
	
	if(result != 0) {
	    cparams->utils->log(NULL, SASL_LOG_ERR,
				"krb_get_cred() failed: %s (%d)",
				get_krb_err_txt(result), result);
	    SETERROR(cparams->utils, "krb_get_cred() failed");
	    return SASL_BADAUTH;
	}
#endif
	memcpy(text->session, text->credentials.session, 8);
	
	/* make key schedule for encryption and decryption */
	des_key_sched(&text->session, text->init_keysched);
	des_key_sched(&text->session, text->enc_keysched);
	des_key_sched(&text->session, text->dec_keysched);
	
	/* decrypt from server */
	des_ecb_encrypt((des_cblock *)in, (des_cblock *)in,
			text->init_keysched, DES_DECRYPT);
	
	/* convert to 32bit int */
	testnum = (in[0]*256*256*256)+(in[1]*256*256)+(in[2]*256)+in[3];
	
	/* verify data 1st 4 octets must be equal to chal+1 */
	if (testnum != text->challenge+1) {
	    SETERROR(cparams->utils,"server response incorrect");
	    return SASL_BADAUTH;
	}
	
	/* construct 8 octets
	 * 1-4 - original checksum
	 * 5 - bitmask of sec layer
	 * 6-8 max buffer size
	 */
	if (cparams->props.min_ssf > 
	    KRB_DES_SECURITY_BITS + cparams->external_ssf) {
	    SETERROR(cparams->utils,
		     "minimum ssf too strong for this mechanism");
	    return SASL_TOOWEAK;
	} else if (cparams->props.min_ssf > cparams->props.max_ssf) {
	    SETERROR(cparams->utils,
		     "minimum ssf larger than maximum ssf");
	    return SASL_BADPARAM;
	}
	
	/* create stuff to send to server */
	sout = (char *)
	    cparams->utils->malloc(9+(text->user ? strlen(text->user) : 0)+9);
	if (!sout) {
	    MEMERROR(cparams->utils);
	    return SASL_NOMEM;
	}
	
	nchal = htonl(text->challenge);
	memcpy(sout, &nchal, 4);
	
	/* need bits of layer */
	if(cparams->props.maxbufsize == 0) {
	    need = musthave = 0;
	} else {
	    need = cparams->props.max_ssf - cparams->external_ssf;
	    musthave = cparams->props.min_ssf - cparams->external_ssf;
	}
	
	oparams->decode = &kerberosv4_decode;
	oparams->encode = &kerberosv4_encode;
	
	if ((in[4] & KRB_SECFLAG_ENCRYPTION)
	    && (need>=56) && (musthave <= 56)) {
	    /* encryption */
	    text->sec_type = KRB_SEC_ENCRYPTION;
	    oparams->mech_ssf = 56;
	    sout[4] = KRB_SECFLAG_ENCRYPTION;
	    /* using encryption layer */
	} else if ((in[4] & KRB_SECFLAG_INTEGRITY)
		   && (need >= 1) && (musthave <= 1)) {
	    /* integrity */
	    text->sec_type = KRB_SEC_INTEGRITY;
	    oparams->mech_ssf=1;
	    sout[4] = KRB_SECFLAG_INTEGRITY;
	    /* using integrity layer */
	} else if ((in[4] & KRB_SECFLAG_NONE) && (musthave <= 0)) {
	    /* no layer */
	    text->sec_type = KRB_SEC_NONE;
	    oparams->encode=NULL;
	    oparams->decode=NULL;
	    oparams->mech_ssf=0;
	    sout[4] = KRB_SECFLAG_NONE;
	} else {
	    /* Mark that we tried */
	    oparams->mech_ssf=2;
	    SETERROR(cparams->utils,
		     "unable to agree on layers with server");
	    return SASL_BADPROT;
	}
	
	servermaxbuf = in[5]*256*256+in[6]*256+in[7];
	oparams->maxoutbuf = servermaxbuf;
	if (oparams->mech_ssf) {
	    /* FIXME: Likely to be too large */
	    oparams->maxoutbuf -= 50;
	}
	
	if(cparams->props.maxbufsize) {
	    int tmpmaxbuf = ( cparams->props.maxbufsize > 0xFFFFFF ) ? 0xFFFFFF : cparams->props.maxbufsize;

	    sout[5]=((tmpmaxbuf >> 16) & 0xFF);
	    sout[6]=((tmpmaxbuf >> 8) & 0xFF);
	    sout[7]=(tmpmaxbuf & 0xFF);
	} else {
            /* let's say we can support up to 64K */
	    /* no inherent inability with our layers to support more */

	    sout[5]=0x00;  /* max ciphertext buffer size */
	    sout[6]=0xFF;
	    sout[7]=0xFF;
	}
	
	sout[8] = 0x00; /* just to be safe */
	
	/* append userid */
	len = 9;			/* 8 + trailing NULL */
	if (text->user) {
	    strcpy((char *)sout + 8, text->user);
	    len += strlen(text->user);
	}
	
	/* append 0 based octets so is multiple of 8 */
	while(len % 8) {
	    sout[len]=0;
	    len++;
	}
	sout[len]=0;
	
	des_pcbc_encrypt((des_cblock *)sout,
			 (des_cblock *)sout,
			 len,
			 text->init_keysched,
			 (des_cblock *)text->session,
			 DES_ENCRYPT);
	
	result = _plug_buf_alloc(text->utils, &text->out_buf,
				 &text->out_buf_len, len);
	if (result != SASL_OK)  return result;
	
	memcpy(text->out_buf, sout, len);
	
	*clientout = text->out_buf;
	*clientoutlen = len;
	
	/* nothing more to do; should be authenticated */
	if(cparams->iplocalport) {   
	    result = ipv4_ipfromstring(cparams->utils,
				       cparams->iplocalport,
				       &(text->ip_local));
	    if (result != SASL_OK) {
		/* couldn't get local IP address */
		return result;
	    }
	}
	
	if (cparams->ipremoteport) {
	    result = ipv4_ipfromstring(cparams->utils,
				       cparams->ipremoteport,
				       &(text->ip_remote));
	    if (result != SASL_OK) {
		/* couldn't get local IP address */
		return result;
	    }
	}
	
	buf = cparams->utils->malloc(strlen(text->credentials.pname)
				     + strlen(text->credentials.pinst)
				     + 2);
	if (!buf) {
	    MEMERROR(cparams->utils);
	    return SASL_NOMEM;
	}
	strcpy(buf, text->credentials.pname);
	if (text->credentials.pinst[0]) {
	    strcat(buf, ".");
	    strcat(buf, text->credentials.pinst);
	}
	
	if (text->user && !text->user[0]) {
	    text->user = NULL;
	}
	
	ret = cparams->canon_user(cparams->utils->conn, buf, 0,
				  SASL_CU_AUTHID, oparams);
	if (ret != SASL_OK) {
	    cparams->utils->free(buf);
	    cparams->utils->free(sout);
	    return ret;
	}
	
	if (!text->user) {
	    /* 0 in length fields means use strlen() */
	    ret = cparams->canon_user(cparams->utils->conn, buf, 0,
				      SASL_CU_AUTHZID, oparams);
	} else {
	    ret = cparams->canon_user(cparams->utils->conn, text->user, 0,
				      SASL_CU_AUTHZID, oparams);
	}
	
	cparams->utils->free(buf);
	
	oparams->doneflag = 1;
	oparams->param_version = 0;
	
	/* used by layers */
	_plug_decode_init(&text->decode_context, text->utils,
			  (cparams->props.maxbufsize > 0xFFFFFF) ? 0xFFFFFF :
			  cparams->props.maxbufsize);
	
	if (sout) cparams->utils->free(sout);
	
	return SASL_OK;
    }
    
    default:
	cparams->utils->log(NULL, SASL_LOG_ERR,
			    "Invalid Kerberos client step %d\n", text->state);
	return SASL_FAIL;
    }

    return SASL_FAIL; /* should never get here */
}

static const long kerberosv4_required_prompts[] = {
    SASL_CB_LIST_END
};

static sasl_client_plug_t kerberosv4_client_plugins[] = 
{
    {
	"KERBEROS_V4",			/* mech_name */
	KRB_DES_SECURITY_BITS,		/* max_ssf */
	SASL_SEC_NOPLAINTEXT
	| SASL_SEC_NOACTIVE
	| SASL_SEC_NOANONYMOUS
	| SASL_SEC_MUTUAL_AUTH,		/* security_flags */
	SASL_FEAT_NEEDSERVERFQDN
	| SASL_FEAT_SERVER_FIRST
	| SASL_FEAT_ALLOWS_PROXY,	/* features */
	kerberosv4_required_prompts,	/* required_prompts */
	NULL,				/* glob_context */
	&kerberosv4_client_mech_new,	/* mech_new */
	&kerberosv4_client_mech_step,	/* mech_step */
	&kerberosv4_common_mech_dispose,/* mech_dispose */
	&kerberosv4_common_mech_free,	/* mech_free */
	NULL,				/* idle */
	NULL,				/* spare */
	NULL				/* spare */
    }
};

int kerberos4_client_plug_init(const sasl_utils_t *utils,
			       int maxversion,
			       int *out_version,
			       sasl_client_plug_t **pluglist,
			       int *plugcount)
{
    if (maxversion < SASL_CLIENT_PLUG_VERSION) {
	SETERROR(utils, "Wrong KERBEROS_V4 version");
	return SASL_BADVERS;
    }
    
    if(!krb_mutex) {
	krb_mutex = utils->mutex_alloc();
	if(!krb_mutex) {
	    return SASL_FAIL;
	}
    }
    
    *out_version = SASL_CLIENT_PLUG_VERSION;
    *pluglist = kerberosv4_client_plugins;
    *plugcount = 1;
    
    refcount++;
    
    return SASL_OK;
}
