/*
 * Program:	SSL authentication/encryption module for Windows 2000
 *
 * Author:	Mark Crispin
 *		Networks and Distributed Computing
 *		Computing & Communications
 *		University of Washington
 *		Administration Building, AG-44
 *		Seattle, WA  98195
 *		Internet: MRC@CAC.Washington.EDU
 *
 * Date:	22 September 1998
 * Last Edited:	28 November 2000
 * 
 * The IMAP toolkit provided in this Distribution is
 * Copyright 2000 University of Washington.
 * The full text of our legal notices is contained in the file called
 * CPYRIGHT, included with this Distribution.
 */

#define SECURITY_WIN32
#include <sspi.h>
#include <schannel.h>

#define SSLBUFLEN 8192


/* SSL I/O stream */

typedef struct ssl_stream {
  TCPSTREAM *tcpstream;		/* TCP stream */
  CredHandle cred;		/* SSL credentials */
  CtxtHandle context;		/* SSL context */
				/* stream encryption sizes */
  SecPkgContext_StreamSizes sizes;
  int ictr;			/* input counter */
  char *iptr;			/* input pointer */
  int iextractr;		/* extra input counter */
  char *iextraptr;		/* extra input pointer */
  char *ibuf;			/* input buffer */
  char *obuf;			/* output buffer */
} SSLSTREAM;


/* SSL stdio stream */

typedef struct ssl_stdiostream {
  SSLSTREAM *sslstream;		/* SSL stream */
  int octr;			/* output counter */
  char *optr;			/* output pointer */
  char obuf[SSLBUFLEN];		/* output buffer */
} SSLSTDIOSTREAM;


/* SSL driver */

struct ssl_driver {		/* must parallel NETDRIVER in mail.h */
  SSLSTREAM *(*open) (char *host,char *service,unsigned long port);
  SSLSTREAM *(*aopen) (NETMBX *mb,char *service,char *usrbuf);
  char *(*getline) (SSLSTREAM *stream);
  long (*getbuffer) (SSLSTREAM *stream,unsigned long size,char *buffer);
  long (*soutr) (SSLSTREAM *stream,char *string);
  long (*sout) (SSLSTREAM *stream,char *string,unsigned long size);
  void (*close) (SSLSTREAM *stream);
  char *(*host) (SSLSTREAM *stream);
  char *(*remotehost) (SSLSTREAM *stream);
  unsigned long (*port) (SSLSTREAM *stream);
  char *(*localhost) (SSLSTREAM *stream);
};

/* Function prototypes */

void ssl_onceonlyinit (void);
SSLSTREAM *ssl_open (char *host,char *service,unsigned long port);
SSLSTREAM *ssl_aopen (NETMBX *mb,char *service,char *usrbuf);
char *ssl_getline (SSLSTREAM *stream);
long ssl_getbuffer (SSLSTREAM *stream,unsigned long size,char *buffer);
long ssl_getdata (SSLSTREAM *stream);
long ssl_soutr (SSLSTREAM *stream,char *string);
long ssl_sout (SSLSTREAM *stream,char *string,unsigned long size);
void ssl_close (SSLSTREAM *stream);
long ssl_abort (SSLSTREAM *stream);
char *ssl_host (SSLSTREAM *stream);
char *ssl_remotehost (SSLSTREAM *stream);
unsigned long ssl_port (SSLSTREAM *stream);
char *ssl_localhost (SSLSTREAM *stream);
long auth_plain_valid (void);
long auth_plain_client (authchallenge_t challenger,authrespond_t responder,
			NETMBX *mb,void *stream,unsigned long *trial,
			char *user);
char *auth_plain_server (authresponse_t responder,int argc,char *argv[]);
void Server_init (char *server,char *service,char *altservice,char *sasl,
                  void *clkint,void *kodint,void *hupint,void *trmint);
long Server_input_wait (long seconds);
char *ssl_start_tls (char *server);
SSLSTDIOSTREAM *ssl_server_init (char *server);
int ssl_getchar (void);
char *ssl_gets (char *s,int n);
int ssl_putchar (int c);
int ssl_puts (char *s);
int ssl_flush (void);

/* Secure Sockets Layer network driver dispatch */

static struct ssl_driver ssldriver = {
  ssl_open,			/* open connection */
  ssl_aopen,			/* open preauthenticated connection */
  ssl_getline,			/* get a line */
  ssl_getbuffer,		/* get a buffer */
  ssl_soutr,			/* output pushed data */
  ssl_sout,			/* output string */
  ssl_close,			/* close connection */
  ssl_host,			/* return host name */
  ssl_remotehost,		/* return remote host name */
  ssl_port,			/* return port number */
  ssl_localhost			/* return local host name */
};

static unsigned long ssltsz = 0;/* SSL maximum token length */
				/* non-NIL if doing SSL primary I/O */
static SSLSTDIOSTREAM *sslstdio = NIL;
static char *start_tls = NIL;	/* non-NIL if start TLS requested */


/* Secure sockets layer authenticator */

AUTHENTICATOR auth_ssl = {
  AU_AUTHUSER,			/* allow authuser */
  "PLAIN",			/* authenticator name */
  auth_plain_valid,		/* check if valid */
  auth_plain_client,		/* client method */
  auth_plain_server,		/* server method */
  NIL				/* next authenticator */
};

/* One-time SSL initialization */

static int sslonceonly = 0;

void ssl_onceonlyinit (void)
{
  if (!sslonceonly++) {		/* only need to call it once */
    ULONG np;
    SecPkgInfo *pp;
    int i;
				/* get security library */
    if (!EnumerateSecurityPackages (&np,&pp)) {
				/* look for an SSL package */
      for (i = 0; (i < (int) np); i++) if (!strcmp (pp[i].Name,UNISP_NAME)) {
				/* note maximum token size and name */
	ssltsz = pp[i].cbMaxToken;
				/* link in the SSL driver */
	mail_parameters (NIL,SET_ALTDRIVER,(void *) &ssldriver);
	mail_parameters (NIL,SET_ALTDRIVERNAME,(void *) "ssl");
	mail_parameters (NIL,SET_ALTOPTIONNAME,(void *) "novalidate-cert");
				/* set name and ports for services */
	mail_parameters (NIL,SET_ALTIMAPNAME,(void *) "*imaps");
	mail_parameters (NIL,SET_ALTIMAPPORT,(void *) 993);
	mail_parameters (NIL,SET_ALTPOPNAME,(void *) "*pop3s");
	mail_parameters (NIL,SET_ALTPOPPORT,(void *) 995);
	mail_parameters (NIL,SET_ALTNNTPNAME,(void *) "*nntps");
	mail_parameters (NIL,SET_ALTNNTPPORT,(void *) 563);
	mail_parameters (NIL,SET_ALTSMTPNAME,(void *) "*smtps");
	mail_parameters (NIL,SET_ALTSMTPPORT,(void *) 465);
	return;			/* all done */
      }
    }
  }
}

/* SSL open
 * Accepts: host name
 *	    contact service name
 *	    contact port number
 * Returns: SSL stream if success else NIL
 */

SSLSTREAM *ssl_open (char *host,char *service,unsigned long port)
{
  SECURITY_STATUS e;
  ULONG a;
  TimeStamp t;
  SecBuffer ibuf[2],obuf[1];
  SecBufferDesc ibufs,obufs;
  SSLSTREAM *stream = NIL;
  ULONG req = ISC_REQ_MUTUAL_AUTH |
    ISC_REQ_REPLAY_DETECT | ISC_REQ_SEQUENCE_DETECT |
      ISC_REQ_CONFIDENTIALITY | ISC_REQ_USE_SESSION_KEY |
	ISC_REQ_ALLOCATE_MEMORY | ISC_REQ_STREAM | ISC_REQ_EXTENDED_ERROR +
	  ((port & NET_ALTOPT) ? ISC_REQ_MANUAL_CRED_VALIDATION : 0);
  int done = 0;
  TCPSTREAM *ts = tcp_open (host,service,port);
  int failed = T;
  if (ts) {			/* got a TCPSTREAM? */
    char *buf = (char *) fs_get (ssltsz);
    unsigned long size = 0;
				/* instantiate SSLSTREAM */
    (stream = (SSLSTREAM *) memset (fs_get (sizeof (SSLSTREAM)),0,
				    sizeof (SSLSTREAM)))->tcpstream = ts;
				/* acquire credentials */
    if (AcquireCredentialsHandle
	(NIL,UNISP_NAME,SECPKG_CRED_OUTBOUND,NIL,NIL,NIL,NIL,&stream->cred,&t)
	== SEC_E_OK) {
      while (!done) {		/* negotiate negotiate security context */
				/* initialize buffers */
	ibuf[0].cbBuffer = size; ibuf[0].pvBuffer = buf;
	ibuf[1].cbBuffer = 0; ibuf[1].pvBuffer = NIL;
	obuf[0].cbBuffer = 0; obuf[0].pvBuffer = NIL;
	ibuf[0].BufferType = obuf[0].BufferType = SECBUFFER_TOKEN;
	ibuf[1].BufferType = SECBUFFER_EMPTY;
				/* initialize buffer descriptors */
	ibufs.ulVersion = obufs.ulVersion = SECBUFFER_VERSION;
	ibufs.cBuffers = 2; obufs.cBuffers = 1;
	ibufs.pBuffers = ibuf; obufs.pBuffers = obuf;

				/* negotiate security */
	e = InitializeSecurityContext
	  (&stream->cred,size ? &stream->context : NIL,host,req,0,
	   SECURITY_NETWORK_DREP,size ? &ibufs : NIL,0,&stream->context,&obufs,
	   &a,&t);
				/* have an output buffer we need to send? */
	if (obuf[0].pvBuffer && obuf[0].cbBuffer) {
	  if ((done >= 0) &&	/* do so if in good state */
	      !tcp_sout (stream->tcpstream,obuf[0].pvBuffer,
			 obuf[0].cbBuffer)) done = -1;
				/* free the buffer */
	  FreeContextBuffer (obuf[0].pvBuffer);
	}
	switch (e) {		/* negotiation state */
	case SEC_E_NO_AUTHENTICATING_AUTHORITY:
	  mm_log ("No authority could be contacted for authentication",ERROR);
	  done = -1;
	  break;
	case SEC_E_WRONG_PRINCIPAL:
	  mm_log ("Principal name incorrect",ERROR);
	  done = -1;
	  break;
	case SEC_E_UNTRUSTED_ROOT:
	  mm_log ("Certificate chain issued by untrusted authority",ERROR);
	  done = -1;
	  break;
	case SEC_E_CERT_EXPIRED:
	  mm_log ("Certificate has expired",ERROR);
	  done = -1;
	  break;
	case SEC_E_INVALID_TOKEN:
	  mm_log ("Invalid token, probably not an SSL server",ERROR);
	  done = -1;
	  break;

	case SEC_E_OK:
	  done = T;		/* got security context, all done */
				/* any data to be regurgitated? */
	  if (ibuf[1].BufferType == SECBUFFER_EXTRA) {
				/* yes, set this as the new data */
	    memmove (stream->tcpstream->iptr = stream->tcpstream->ibuf,
		     buf + size - ibuf[1].cbBuffer,ibuf[1].cbBuffer);
	    stream->tcpstream->ictr = ibuf[1].cbBuffer;
	  }
	  if (QueryContextAttributes
	      (&stream->context,SECPKG_ATTR_STREAM_SIZES,&stream->sizes) ==
	      SEC_E_OK) {	/* get stream sizes */
				/* maximum SSL buffer size */
	    size_t i = stream->sizes.cbHeader +
	      stream->sizes.cbMaximumMessage + stream->sizes.cbTrailer;
				/* make buffers */
	    stream->ibuf = (char *) fs_get (i);
	    stream->obuf = (char *) fs_get (i);
	    failed = NIL;	/* mark success */
	  }
	  break;
	case SEC_I_INCOMPLETE_CREDENTIALS:
	  break;		/* server wants client auth */
	case SEC_I_CONTINUE_NEEDED:
	  if (size) {		/* continue, read any data? */
				/* yes, anything regurgiated back to us? */
	    if (ibuf[1].BufferType == SECBUFFER_EXTRA) {
				/* yes, set this as the new data */
	      memmove (buf,buf + size - ibuf[1].cbBuffer,ibuf[1].cbBuffer);
	      size = ibuf[1].cbBuffer;
	      break;
	    }
	    size = 0;		/* otherwise, read more stuff from server */
	  }
	case SEC_E_INCOMPLETE_MESSAGE:
				/* need to read more data from server */
	  if (tcp_getdata (stream->tcpstream)) {
	    memcpy (buf+size,stream->tcpstream->iptr,stream->tcpstream->ictr);
	    size += stream->tcpstream->ictr;
				/* empty it from TCP's buffers */
	    stream->tcpstream->iptr += stream->tcpstream->ictr;
	    stream->tcpstream->ictr = 0;
	  }
	  else {
	    mm_log ("Unexpected disconnect during SSL negotiation",ERROR);
	    done = -1;
	  }
	  break;
	default:		/* anything else is an error */
	  sprintf (buf,"Unexpected SChannel error %lx - report this",e);
	  mm_log (buf,ERROR);
	  done = -1;
	  break;
	}
      }
    }

    if (failed) {		/* failed to negotiate SSL */
      if (!(port & 0x80000000)){/* only give error if not silent */
	sprintf (buf,"Can't establish SSL session to %.80s/%.80s,%ld",
		 host,service ? service : "SSL",port & 0xffff);
	mm_log (buf,ERROR);
      }
      ssl_close (stream);	/* failed to do SSL */
    }
    fs_give ((void **) &buf);	/* flush temporary buffer */
  }
  return failed ? NIL :  stream;
}

  
/* SSL authenticated open
 * Accepts: host name
 *	    service name
 *	    returned user name buffer
 * Returns: SSL stream if success else NIL
 */

SSLSTREAM *ssl_aopen (NETMBX *mb,char *service,char *usrbuf)
{
  return NIL;			/* don't use this mechanism with SSL */
}

/* SSL receive line
 * Accepts: SSL stream
 * Returns: text line string or NIL if failure
 */

char *ssl_getline (SSLSTREAM *stream)
{
  int n,m;
  char *st,*ret,*stp;
  char c = '\0';
  char d;
				/* make sure have data */
  if (!ssl_getdata (stream)) return NIL;
  st = stream->iptr;		/* save start of string */
  n = 0;			/* init string count */
  while (stream->ictr--) {	/* look for end of line */
    d = *stream->iptr++;	/* slurp another character */
    if ((c == '\015') && (d == '\012')) {
      ret = (char *) fs_get (n--);
      memcpy (ret,st,n);	/* copy into a free storage string */
      ret[n] = '\0';		/* tie off string with null */
      return ret;
    }
    n++;			/* count another character searched */
    c = d;			/* remember previous character */
  }
				/* copy partial string from buffer */
  memcpy ((ret = stp = (char *) fs_get (n)),st,n);
				/* get more data from the net */
  if (!ssl_getdata (stream)) fs_give ((void **) &ret);
				/* special case of newline broken by buffer */
  else if ((c == '\015') && (*stream->iptr == '\012')) {
    stream->iptr++;		/* eat the line feed */
    stream->ictr--;
    ret[n - 1] = '\0';		/* tie off string with null */
  }
				/* else recurse to get remainder */
  else if (st = ssl_getline (stream)) {
    ret = (char *) fs_get (n + 1 + (m = strlen (st)));
    memcpy (ret,stp,n);		/* copy first part */
    memcpy (ret + n,st,m);	/* and second part */
    fs_give ((void **) &stp);	/* flush first part */
    fs_give ((void **) &st);	/* flush second part */
    ret[n + m] = '\0';		/* tie off string with null */
  }
  return ret;
}

/* SSL receive buffer
 * Accepts: SSL stream
 *	    size in bytes
 *	    buffer to read into
 * Returns: T if success, NIL otherwise
 */

long ssl_getbuffer (SSLSTREAM *stream,unsigned long size,char *buffer)
{
  unsigned long n;
  while (size > 0) {		/* until request satisfied */
    if (!ssl_getdata (stream)) return NIL;
    n = min (size,stream->ictr);/* number of bytes to transfer */
				/* do the copy */
    memcpy (buffer,stream->iptr,n);
    buffer += n;		/* update pointer */
    stream->iptr += n;
    size -= n;			/* update # of bytes to do */
    stream->ictr -= n;
  }
  buffer[0] = '\0';		/* tie off string */
  return T;
}

/* SSL receive data
 * Accepts: TCP/IP stream
 * Returns: T if success, NIL otherwise
 */

long ssl_getdata (SSLSTREAM *stream)
{
  SECURITY_STATUS status;
  SecBuffer buf[4];
  SecBufferDesc msg;
  size_t n = 0;
  size_t i;
  while (stream->ictr < 1) {	/* decrypted buffer empty? */
    do {			/* yes, make sure have data from TCP */
      if (stream->iextractr) {	/* have previous unread data? */
	memcpy (stream->ibuf + n,stream->iextraptr,stream->iextractr);
	n += stream->iextractr;	/* update number of bytes read */
	stream->iextractr = 0;	/* no more extra data */
      }
      else {			/* read from TCP */
	if (!tcp_getdata (stream->tcpstream)) return ssl_abort (stream);
				/* maximum amount of data to copy */
	if (!(i = min (stream->sizes.cbMaximumMessage - n,
		       stream->tcpstream->ictr)))
	  fatal ("incomplete SecBuffer already cbMaximumMessage");
				/* do the copy */
	memcpy (stream->ibuf + n,stream->tcpstream->iptr,i);
	stream->tcpstream->iptr += i;
	stream->tcpstream->ictr -= i;
	n += i;			/* update number of bytes to decrypt */
      }
      buf[0].cbBuffer = n;	/* first SecBuffer gets data */
      buf[0].pvBuffer = stream->ibuf;
      buf[0].BufferType = SECBUFFER_DATA;
				/* subsequent ones are for spares */
      buf[1].BufferType = buf[2].BufferType = buf[3].BufferType =
	SECBUFFER_EMPTY;
      msg.ulVersion = SECBUFFER_VERSION;
      msg.cBuffers = 4;		/* number of SecBuffers */
      msg.pBuffers = buf;	/* first SecBuffer */
    } while ((status = DecryptMessage
	      (&stream->context,&msg,0,NIL)) == SEC_E_INCOMPLETE_MESSAGE);
    switch (status) {
    case SEC_E_OK:		/* won */
    case SEC_I_RENEGOTIATE:	/* won but lost it after this buffer */
				/* hunt for a buffer */
      for (i = 0; (i < 4) && (buf[i].BufferType != SECBUFFER_DATA) ; i++);
      if (i < 4) {		/* found a buffer? */
				/* yes, set up pointer and counter */
	stream->iptr = buf[i].pvBuffer;
	stream->ictr = buf[i].cbBuffer;
				/* any unprocessed data? */
	while (++i < 4) if (buf[i].BufferType == SECBUFFER_EXTRA) {
				/* yes, note for next time around */
	  stream->iextraptr = buf[i].pvBuffer;
	  stream->iextractr = buf[i].cbBuffer;
	}
      }
      break;
    default:			/* anything else means we've lost */
      return ssl_abort (stream);
    }
  }
  return LONGT;
}

/* SSL send string as record
 * Accepts: SSL stream
 *	    string pointer
 * Returns: T if success else NIL
 */

long ssl_soutr (SSLSTREAM *stream,char *string)
{
  return ssl_sout (stream,string,(unsigned long) strlen (string));
}


/* SSL send string
 * Accepts: SSL stream
 *	    string pointer
 *	    byte count
 * Returns: T if success else NIL
 */

long ssl_sout (SSLSTREAM *stream,char *string,unsigned long size)
{
  SecBuffer buf[4];
  SecBufferDesc msg;
  char *s = stream->ibuf;
  size_t n = 0;
  while (size) {		/* until satisfied request */
				/* header */
    buf[0].BufferType = SECBUFFER_STREAM_HEADER;
    memset (buf[0].pvBuffer = stream->obuf,0,
	    buf[0].cbBuffer = stream->sizes.cbHeader);
				/* message (up to maximum size) */
    buf[1].BufferType = SECBUFFER_DATA;
    memcpy (buf[1].pvBuffer = stream->obuf + stream->sizes.cbHeader,string,
	    buf[1].cbBuffer = min (size,stream->sizes.cbMaximumMessage));
				/* trailer */
    buf[2].BufferType = SECBUFFER_STREAM_TRAILER;
    memset (buf[2].pvBuffer = ((char *) buf[1].pvBuffer) + buf[1].cbBuffer,0,
	    buf[2].cbBuffer = stream->sizes.cbTrailer);
				/* spare */
    buf[3].BufferType = SECBUFFER_EMPTY;
    msg.ulVersion = SECBUFFER_VERSION;
    msg.cBuffers = 4;		/* number of SecBuffers */
    msg.pBuffers = buf;		/* first SecBuffer */
    string += buf[1].cbBuffer;
    size -= buf[1].cbBuffer;	/* this many bytes processed */
				/* encrypt and send message */
    if ((EncryptMessage
	 (&stream->context,0,&msg,NIL) != SEC_E_OK) ||
	!tcp_sout (stream->tcpstream,stream->obuf,
		   buf[0].cbBuffer + buf[1].cbBuffer + buf[2].cbBuffer))
      return ssl_abort (stream);/* encryption or sending failed */
  }
  return LONGT;
}

/* SSL close
 * Accepts: SSL stream
 */

void ssl_close (SSLSTREAM *stream)
{
  ssl_abort (stream);		/* nuke the stream */
  fs_give ((void **) &stream);	/* flush the stream */
}


/* SSL abort stream
 * Accepts: SSL stream
 * Returns: NIL always
 */

long ssl_abort (SSLSTREAM *stream)
{
  if (stream->tcpstream) {	/* close TCP stream */
    DeleteSecurityContext (&stream->context);
    FreeCredentialHandle (&stream->cred);
    tcp_close (stream->tcpstream);
    stream->tcpstream = NIL;
  }
  if (stream->ibuf) fs_give ((void **) &stream->ibuf);
  if (stream->obuf) fs_give ((void **) &stream->obuf);
  return NIL;
}

/* SSL get host name
 * Accepts: SSL stream
 * Returns: host name for this stream
 */

char *ssl_host (SSLSTREAM *stream)
{
  return tcp_host (stream->tcpstream);
}


/* SSL get remote host name
 * Accepts: SSL stream
 * Returns: host name for this stream
 */

char *ssl_remotehost (SSLSTREAM *stream)
{
  return tcp_remotehost (stream->tcpstream);
}


/* SSL return port for this stream
 * Accepts: SSL stream
 * Returns: port number for this stream
 */

unsigned long ssl_port (SSLSTREAM *stream)
{
  return tcp_port (stream->tcpstream);
}


/* SSL get local host name
 * Accepts: SSL stream
 * Returns: local host name
 */

char *ssl_localhost (SSLSTREAM *stream)
{
  return tcp_localhost (stream->tcpstream);
}

/* Client authenticator
 * Accepts: challenger function
 *	    responder function
 *	    parsed network mailbox structure
 *	    stream argument for functions
 *	    pointer to current trial count
 *	    returned user name
 * Returns: T if success, NIL otherwise, number of trials incremented if retry
 */

long auth_plain_client (authchallenge_t challenger,authrespond_t responder,
			NETMBX *mb,void *stream,unsigned long *trial,
			char *user)
{
  char *s,*t,*u,pwd[MAILTMPLEN];
  void *chal;
  unsigned long cl,sl;
  if (!mb->altflag)		/* snarl if not secure session */
    mm_log ("SECURITY PROBLEM: insecure server advertised AUTH=PLAIN",WARN);
				/* get initial (empty) challenge */
  if (chal = (*challenger) (stream,&cl)) {
    fs_give ((void **) &chal);
    if (cl) {			/* abort if non-empty challenge */
      (*responder) (stream,NIL,0);
      *trial = 0;		/* cancel subsequent attempts */
      return T;			/* will get a BAD response back */
    }
				/* prompt user */
    mm_login (mb,user,pwd,*trial);
    if (!pwd[0]) {		/* user requested abort */
      (*responder) (stream,NIL,0);
      *trial = 0;		/* cancel subsequent attempts */
      return T;			/* will get a BAD response back */
    }
    t = s = (char *) fs_get (sl = strlen (mb->authuser) + strlen (user) +
			     strlen (pwd) + 2);
				/* copy authorization id */
    if (mb->authuser[0]) for (u = user; *u; *t++ = *u++);
    *t++ = '\0';		/* delimiting NUL */
				/* copy authentication id */
    for (u = mb->authuser[0] ? mb->authuser : user; *u; *t++ = *u++);
    *t++ = '\0';		/* delimiting NUL */
				/* copy password */
    for (u = pwd; *u; *t++ = *u++);
				/* send credentials */
    if ((*responder) (stream,s,sl) && !(chal = (*challenger) (stream,&cl))) {
      fs_give ((void **) &s);	/* free response */
      ++*trial;			/* can try again if necessary */
      return T;			/* check the authentication */
    }
    fs_give ((void **) &s);	/* free response */
  }
  if (chal) fs_give ((void **) &chal);
  *trial = 65565;		/* don't retry */
  return NIL;			/* failed */
}

/* Check if PLAIN valid on this system
 * Returns: T, always
 */

long auth_plain_valid (void)
{
  return T;			/* PLAIN is otherwise valid */
}


/* Server authenticator
 * Accepts: responder function
 *	    argument count
 *	    argument vector
 * Returns: authenticated user name or NIL
 */

char *auth_plain_server (authresponse_t responder,int argc,char *argv[])
{
  char *ret = NIL;
  char *user,*aid,*pass;
  unsigned long len;
				/* get user name */
  if (aid = (*responder) ("",0,&len)) {
				/* note: responders null-terminate */
    if ((((unsigned long) ((user = aid + strlen (aid) + 1) - aid)) < len) &&
	(((unsigned long) ((pass = user + strlen (user) + 1) - aid)) < len) &&
	(((unsigned long) ((pass + strlen (pass)) - aid)) == len) &&
	(*aid ? server_login (aid,pass,user,argc,argv) :
	 server_login (user,pass,NIL,argc,argv))) ret = myusername ();
    fs_give ((void **) &aid);
  }
  return ret;
}

/* Init server for SSL
 * Accepts: server name for syslog or NIL
 *	    /etc/services service name or NIL
 *	    alternate /etc/services service name or NIL
 *	    SASL service name or NIL
 *	    clock interrupt handler
 *	    kiss-of-death interrupt handler
 *	    hangup interrupt handler
 *	    termination interrupt handler
 */

void server_init (char *server,char *service,char *altservice,char *sasl,
                  void *clkint,void *kodint,void *hupint,void *trmint)
{
  struct servent *sv;
  long port;
  if (server) {			/* set server name in syslog */
    openlog (server,LOG_PID,LOG_MAIL);
    fclose (stderr);		/* possibly save a process ID */
  }
  /* Use SSL if alt service, or if server starts with "s" and not service */
  if (service && altservice && ((port = tcp_serverport ()) >= 0) &&
      (((sv = getservbyname (altservice,"tcp")) &&
	(port == ntohs (sv->s_port))) ||
       ((*server == 's') && (!(sv = getservbyname (service,"tcp")) ||
			     (port != ntohs (sv->s_port))))))
    sslstdio = ssl_server_init (server);
  else auth_ssl.server = NIL;	/* server forbids PLAIN if not SSL */
				/* now call c-client's version */
  Server_init (NIL,service,altservice,sasl,clkint,kodint,hupint,trmint);
}

				/* link to the real one */
#define server_init Server_init

/* Wait for stdin input
 * Accepts: timeout in seconds
 * Returns: T if have input on stdin, else NIL
 */

long server_input_wait (long seconds)
{
  SECURITY_STATUS status;
  SecBuffer buf[4];
  SecBufferDesc msg;
  struct timeval tmo;
  fd_set fds,efd;
  size_t n = 0;
  size_t i;
  SSLSTREAM *stream;
  if (!sslstdio) return Server_input_wait (seconds);
				/* if no input available in buffer */
  while ((stream = sslstdio->sslstream)->ictr <= 0) {
    do {
      if (stream->iextractr) {	/* have previous unread data? */
	memcpy (stream->ibuf + n,stream->iextraptr,stream->iextractr);
	n += stream->iextractr;	/* update number of bytes read */
	stream->iextractr = 0;	/* no more extra data */
      }
      else {			/* if nothing in TCP buffer */
	if (stream->tcpstream->ictr < 1) {
	  FD_ZERO (&fds);	/* initialize selection vector */
	  FD_ZERO (&efd);	/* initialize selection vector */
	  FD_SET (stream->tcpstream->tcpsi,&fds);
	  FD_SET (stream->tcpstream->tcpsi,&efd);
	  tmo.tv_sec = seconds; tmo.tv_usec = 0;
				/* sniff and block until timeout */
	  if (!select (stream->tcpstream->tcpsi+1,&fds,0,&efd,&tmo))
	    return NIL;
	}
				/* now read that data */
	if (!tcp_getdata (stream->tcpstream)) return LONGT;
				/* maximum amount of data to copy */
	if (!(i = min (stream->sizes.cbMaximumMessage - n,
		       stream->tcpstream->ictr)))
	  fatal ("incomplete SecBuffer already cbMaximumMessage");
				/* do the copy */
	memcpy (stream->ibuf + n,stream->tcpstream->iptr,i);
	stream->tcpstream->iptr += i;
	stream->tcpstream->ictr -= i;
	n += i;			/* update number of bytes to decrypt */
      }
      buf[0].cbBuffer = n;	/* first SecBuffer gets data */
      buf[0].pvBuffer = stream->ibuf;
      buf[0].BufferType = SECBUFFER_DATA;
				/* subsequent ones are for spares */
      buf[1].BufferType = buf[2].BufferType = buf[3].BufferType =
	SECBUFFER_EMPTY;
      msg.ulVersion = SECBUFFER_VERSION;
      msg.cBuffers = 4;		/* number of SecBuffers */
      msg.pBuffers = buf;	/* first SecBuffer */
    } while ((status = DecryptMessage
	      (&stream->context,&msg,0,NIL)) == SEC_E_INCOMPLETE_MESSAGE);

    switch (status) {
    case SEC_E_OK:		/* won */
    case SEC_I_RENEGOTIATE:	/* won but lost it after this buffer */
				/* hunt for a buffer */
      for (i = 0; (i < 4) && (buf[i].BufferType != SECBUFFER_DATA) ; i++);
      if (i < 4) {		/* found a buffer? */
				/* yes, set up pointer and counter */
	stream->iptr = buf[i].pvBuffer;
	stream->ictr = buf[i].cbBuffer;
				/* any unprocessed data? */
	while (++i < 4) if (buf[i].BufferType == SECBUFFER_EXTRA) {
				/* yes, note for next time around */
	  stream->iextraptr = buf[i].pvBuffer;
	  stream->iextractr = buf[i].cbBuffer;
	}
      }
      break;
    default:			/* anything else means we're sick */
      return LONGT;
    }
  }
  return LONGT;
}

				/* link to the other one */
#define server_input_wait Server_input_wait


/* Start TLS
 * Accepts: /etc/services service name
 * Returns: cpystr'd error string if TLS failed, else NIL for success
 */

char *ssl_start_tls (char *server)
{
  if (start_tls) return cpystr ("TLS already started");
  if (sslstdio) return cpystr ("Already in an SSL session");
  start_tls = server;		/* start TLS now */
  return NIL;
}

/* Init server for SSL
 * Accepts: server name
 * Returns: SSL stdio stream always
 */

SSLSTDIOSTREAM *ssl_server_init (char *server)
{
  SECURITY_STATUS e;
  ULONG a;
  TimeStamp t;
  SecBuffer ibuf[2],obuf[1];
  SecBufferDesc ibufs,obufs;
  SSLSTDIOSTREAM *ret;
  SSLSTREAM *stream = (SSLSTREAM *) memset (fs_get (sizeof (SSLSTREAM)),0,
					    sizeof (SSLSTREAM));
  ULONG req = ASC_REQ_REPLAY_DETECT | ASC_REQ_SEQUENCE_DETECT |
    ASC_REQ_CONFIDENTIALITY | ASC_REQ_USE_SESSION_KEY |
    ASC_REQ_ALLOCATE_MEMORY | ASC_REQ_STREAM | ASC_REQ_EXTENDED_ERROR;
  int done = 0;
  int failed = T;
  int repeat = 0;
  char spn[MAILTMPLEN];
  char *buf = (char *) fs_get (ssltsz);
  unsigned long size;
  ssl_onceonlyinit ();		/* make sure algorithms added */
  sprintf (spn,"%s/%s",(char *) mail_parameters (NIL,GET_SERVICENAME,NIL),
	   tcp_serverhost ());
				/* create dummy TCPSTREAM */
  stream->tcpstream = (TCPSTREAM *) memset (fs_get (sizeof (TCPSTREAM)),0,
					    sizeof (TCPSTREAM));
				/* in case someone cares */
  stream->tcpstream->host = cpystr ("");
  stream->tcpstream->tcpso = 1;	/* standard output */
				/* get credentials and initial client data */
  if ((AcquireCredentialsHandle
       (spn,UNISP_NAME,SECPKG_CRED_INBOUND,NIL,NIL,NIL,NIL,&stream->cred,&t) ==
       SEC_E_OK) && tcp_getdata (stream->tcpstream)) {
				/* transfer initial client data */
    memcpy (buf,stream->tcpstream->iptr,size = stream->tcpstream->ictr);
				/* empty it from TCP's buffers */
    stream->tcpstream->iptr += stream->tcpstream->ictr;
    stream->tcpstream->ictr = 0;
    while (!done) {		/* negotiate negotiate security context */
				/* initialize buffers */
      ibuf[0].cbBuffer = size; ibuf[0].pvBuffer = buf;
      ibuf[1].cbBuffer = 0; ibuf[1].pvBuffer = NIL;
      obuf[0].cbBuffer = 0; obuf[0].pvBuffer = NIL;
      ibuf[0].BufferType = obuf[0].BufferType = SECBUFFER_TOKEN;
      ibuf[1].BufferType = SECBUFFER_EMPTY;
				/* initialize buffer descriptors */
      ibufs.ulVersion = obufs.ulVersion = SECBUFFER_VERSION;
      ibufs.cBuffers = 2; obufs.cBuffers = 1;
      ibufs.pBuffers = ibuf; obufs.pBuffers = obuf;
				/* negotiate security */
      e = AcceptSecurityContext
	(&stream->cred,repeat ? &stream->context : NIL,&ibufs,req,
	 SECURITY_NETWORK_DREP,&stream->context,&obufs,&a,&t);
      repeat = T;		/* next call will use the context */
				/* have an output buffer we need to send? */
      if (obuf[0].pvBuffer && obuf[0].cbBuffer) {
	if ((done >= 0) &&	/* do so if in good state */
	    !tcp_sout (stream->tcpstream,obuf[0].pvBuffer,
		       obuf[0].cbBuffer)) done = -1;
				/* free the buffer */
	FreeContextBuffer (obuf[0].pvBuffer);
      }

      switch (e) {		/* negotiation state */
      case SEC_E_OK:
	done = T;		/* got security context, all done */
				/* any data to be regurgitated? */
	if (ibuf[1].BufferType == SECBUFFER_EXTRA) {
				/* yes, set this as the new data */
	    memmove (stream->tcpstream->iptr = stream->tcpstream->ibuf,
		     buf + size - ibuf[1].cbBuffer,ibuf[1].cbBuffer);
	    stream->tcpstream->ictr = ibuf[1].cbBuffer;
	  }
	if (QueryContextAttributes
	    (&stream->context,SECPKG_ATTR_STREAM_SIZES,&stream->sizes) ==
	    SEC_E_OK) {		/* get stream sizes */
				/* maximum SSL buffer size */
	  size_t i = stream->sizes.cbHeader +
	    stream->sizes.cbMaximumMessage + stream->sizes.cbTrailer;
				/* make buffers */
	  stream->ibuf = (char *) fs_get (i);
	  stream->obuf = (char *) fs_get (i);
	  failed = NIL;	/* mark success */
	}
	break;
      case SEC_I_CONTINUE_NEEDED:
	if (size) {		/* continue, read any data? */
				/* yes, anything regurgiated back to us? */
	  if (ibuf[1].BufferType == SECBUFFER_EXTRA) {
				/* yes, set this as the new data */
	    memmove (buf,buf + size - ibuf[1].cbBuffer,ibuf[1].cbBuffer);
	    size = ibuf[1].cbBuffer;
	    break;
	  }
	  size = 0;		/* otherwise, read more stuff from server */
	}
      case SEC_E_INCOMPLETE_MESSAGE:
				/* need to read more data from server */
	if (tcp_getdata (stream->tcpstream)) {
	  memcpy (buf+size,stream->tcpstream->iptr,stream->tcpstream->ictr);
	  size += stream->tcpstream->ictr;
				/* empty it from TCP's buffers */
	  stream->tcpstream->iptr += stream->tcpstream->ictr;
	  stream->tcpstream->ictr = 0;
	  break;
	}
      default:			/* anything else is an error */
	done = -1;
	break;
      }
    }
  }

  fs_give ((void **) &buf);	/* flush temporary buffer */
  if (failed) {			/* failed to negotiate SSL */
    ssl_close (stream);		/* punt stream */
    exit (1);			/* punt this program too */
  }
  ret = (SSLSTDIOSTREAM *)	/* success, return SSL stdio stream */
    memset (fs_get (sizeof(SSLSTDIOSTREAM)),0,sizeof(SSLSTDIOSTREAM));
  ret->sslstream = stream;	/* stream to do SSL I/O */
  ret->octr = SSLBUFLEN;	/* available space in output buffer */
  ret->optr = ret->obuf;	/* current output buffer pointer */
  return ret;
}

/* Get character
 * Returns: character or EOF
 */

int ssl_getchar (void)
{
  if (!sslstdio) return getchar ();
  if (!ssl_getdata (sslstdio->sslstream)) return EOF;
				/* one last byte available */
  sslstdio->sslstream->ictr--;
  return (int) *(sslstdio->sslstream->iptr)++;
}


/* Get string
 * Accepts: destination string pointer
 *	    number of bytes available
 * Returns: destination string pointer or NIL if EOF
 */

char *ssl_gets (char *s,int n)
{
  int i,c;
  if (start_tls) {		/* doing a start TLS? */
				/* yes, allow PLAIN authenticator again */
    auth_ssl.server = auth_plain_server;
				/* enter the mode */
    sslstdio = ssl_server_init (start_tls);
    start_tls = NIL;		/* don't do this again */
  }
  if (!sslstdio) return fgets (s,n,stdin);
  for (i = c = 0, n-- ; (c != '\n') && (i < n); sslstdio->sslstream->ictr--) {
    if ((sslstdio->sslstream->ictr <= 0) && !ssl_getdata (sslstdio->sslstream))
      return NIL;		/* read error */
    c = s[i++] = *(sslstdio->sslstream->iptr)++;
  }
  s[i] = '\0';			/* tie off string */
  return s;
}

/* Put character
 * Accepts: character
 * Returns: character written or EOF
 */

int ssl_putchar (int c)
{
  if (!sslstdio) return putchar (c);
				/* flush buffer if full */
  if (!sslstdio->octr && ssl_flush ()) return EOF;
  sslstdio->octr--;		/* count down one character */
  *sslstdio->optr++ = c;	/* write character */
  return c;			/* return that character */
}


/* Put string
 * Accepts: destination string pointer
 * Returns: 0 or EOF if error
 */

int ssl_puts (char *s)
{
  if (!sslstdio) return fputs (s,stdout);
  while (*s) {			/* flush buffer if full */
    if (!sslstdio->octr && ssl_flush ()) return EOF;
    *sslstdio->optr++ = *s++;	/* write one more character */
    sslstdio->octr--;		/* count down one character */
  }
  return 0;			/* success */
}


/* Flush output
 * Returns: 0 or EOF if error
 */

int ssl_flush (void)
{
  if (!sslstdio) return fflush (stdout);
				/* force out buffer */
  if (!ssl_sout (sslstdio->sslstream,sslstdio->obuf,
		 SSLBUFLEN - sslstdio->octr)) return EOF;
				/* renew output buffer */
  sslstdio->optr = sslstdio->obuf;
  sslstdio->octr = SSLBUFLEN;
  return 0;			/* success */
}
