/* ========================================================================
 * Copyright 2018-2022 Eduardo Chappa
 * Copyright 2008-2009 Mark Crispin
 * ========================================================================
 */

/*
 * Program:	SSL authentication/encryption module for Windows 9x and NT
 *
 * Author:	Mark Crispin
 *
 * Date:	22 September 1998
 * Last Edited:	8 November 2009
 *
 * Previous versions of this file were
 *
 * Copyright 1988-2008 University of Washington
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 */
#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;
  size_t bufsize;
  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;

#include "sslio.h"


/* Function prototypes */

static SSLSTREAM *ssl_start(TCPSTREAM *tstream,char *host,unsigned long flags);
static char *ssl_analyze_status (SECURITY_STATUS err,char *buf);
static char *ssl_getline_work (SSLSTREAM *stream,unsigned long *size,
			       long *contd);
static long ssl_abort (SSLSTREAM *stream);

/* 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 */
  ssl_getsize			/* return buffer size */			
};

				/* security function table */
static SecurityFunctionTable *sft = NIL;
static unsigned long ssltsz = 0;/* SSL maximum token length */


/* Define crypt32.dll stuff here in case a pre-IE5 Win9x system */

typedef DWORD (CALLBACK *CNTS) (DWORD,PCERT_NAME_BLOB,DWORD,LPSTR,DWORD);
typedef BOOL (CALLBACK *CGCC) (HCERTCHAINENGINE,PCCERT_CONTEXT,LPFILETIME,
			       HCERTSTORE,PCERT_CHAIN_PARA,DWORD,LPVOID,
			       PCCERT_CHAIN_CONTEXT *);
typedef BOOL (CALLBACK *CVCCP) (LPCSTR,PCCERT_CHAIN_CONTEXT,
				PCERT_CHAIN_POLICY_PARA,
				PCERT_CHAIN_POLICY_STATUS);
typedef VOID (CALLBACK *CFCC) (PCCERT_CHAIN_CONTEXT);
typedef BOOL (CALLBACK *CFCCX) (PCCERT_CONTEXT);

static CNTS certNameToStr = NIL;
static CGCC certGetCertificateChain = NIL;
static CVCCP certVerifyCertificateChainPolicy = NIL;
static CFCC certFreeCertificateChain = NIL;
static CFCCX certFreeCertificateContext = NIL;

/* One-time SSL initialization */

static int sslonceonly = 0;

void ssl_onceonlyinit (void)
{
  if (!sslonceonly++) {		/* only need to call it once */
    HINSTANCE lib;
    FARPROC pi;
    ULONG np;
    SecPkgInfo *pp;
    int i;
				/* get security library */
    if (((lib = LoadLibrary ("schannel.dll")) ||
	 (lib = LoadLibrary ("security.dll"))) &&
	(pi = GetProcAddress (lib,SECURITY_ENTRYPOINT)) &&
	(sft = (SecurityFunctionTable *) pi ()) &&
	!(sft->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;
				/* apply runtime linkage */
	mail_parameters (NIL,SET_SSLDRIVER,(void *) &ssldriver);
	mail_parameters (NIL,SET_SSLSTART,(void *) ssl_start);
	if ((lib = LoadLibrary ("crypt32.dll")) &&
	    (certGetCertificateChain = (CGCC)
	     GetProcAddress (lib,"CertGetCertificateChain")) &&
	    (certVerifyCertificateChainPolicy = (CVCCP)
	     GetProcAddress (lib,"CertVerifyCertificateChainPolicy")) &&
	    (certFreeCertificateChain = (CFCC)
	     GetProcAddress (lib,"CertFreeCertificateChain")) &&
	    (certFreeCertificateContext = (CFCCX)
	     GetProcAddress (lib,"CertFreeCertificateContext")))
	  certNameToStr = (CNTS) GetProcAddress (lib,"CertNameToStrA");
	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)
{
  TCPSTREAM *stream = tcp_open (host,service,port);
  return stream ? ssl_start (stream,host,port) : NIL;
}

#ifdef SP_PROT_SSL3
  #ifdef MIN_ENCRYPTION
  #undef MIN_ENCRYPTION
  #endif /* MIN_ENCRYPTION */
  #define MIN_ENCRYPTION SP_PROT_SSL3
  #ifdef MAX_ENCRYPTION
  #undef MAX_ENCRYPTION
  #endif /* MAX_ENCRYPTION */
  #define MAX_ENCRYPTION SP_PROT_SSL3
#endif /* SP_PROT_SSL3 */
#ifdef SP_PROT_TLS1
  #ifndef MIN_ENCRYPTION
  #define MIN_ENCRYPTION SP_PROT_TLS1
  #endif /* MIN_ENCRYPTION */
  #ifdef MAX_ENCRYPTION
  #undef MAX_ENCRYPTION
  #endif /* MAX_ENCRYPTION */
  #define MAX_ENCRYPTION SP_PROT_TLS1
#endif /* SP_PROT_TLS1 */
#ifdef SP_PROT_TLS1_1
  #ifndef MIN_ENCRYPTION
  #define MIN_ENCRYPTION SP_PROT_TLS1_1
  #endif /* MIN_ENCRYPTION */
  #ifdef MAX_ENCRYPTION
  #undef MAX_ENCRYPTION
  #endif /* MAX_ENCRYPTION */
  #define MAX_ENCRYPTION SP_PROT_TLS1_1
#endif /* SP_PROT_TLS1_1 */
#ifdef SP_PROT_TLS1_2
  #ifndef MIN_ENCRYPTION
  #define MIN_ENCRYPTION SP_PROT_TLS1_2
  #endif /* MIN_ENCRYPTION */
  #ifdef MAX_ENCRYPTION
  #undef MAX_ENCRYPTION
  #endif /* MAX_ENCRYPTION */
  #define MAX_ENCRYPTION SP_PROT_TLS1_2
#endif /* SP_PROT_TLS1_2 */

typedef struct ssl_versions_s {
        char *name;
        int   version;
} SSL_VERSIONS_S;

SSL_VERSIONS_S ssl_versions[] = {
   { "no_min", MIN_ENCRYPTION },
#ifdef SP_PROT_SSL3
   { "ssl3",   SP_PROT_SSL3 },
#endif /* SP_PROT_SSL3 */
#ifdef SP_PROT_TLS1
   { "tls1",   SP_PROT_TLS1 },
#endif /* SP_PROT_TLS1 */
#ifdef SP_PROT_TLS1_1
   { "tls1_1", SP_PROT_TLS1_1 },
#endif /* SP_PROT_TLS1_1 */
#ifdef SP_PROT_TLS1_2
   { "tls1_2", SP_PROT_TLS1_2 },
#endif /* SP_PROT_TLS1_2 */
   { "no_max", MAX_ENCRYPTION },  /* set this last in the list */
   { NULL, 0 },
};

int
pith_ssl_encryption_version(char *s)
{
  int i;

  if (s == NULL || *s == '\0')
     return -1;

  for (i = 0; ssl_versions[i].name != NULL; i++)
      if (strcmp(ssl_versions[i].name, s) == 0)
	break;

  if (strcmp(s, "no_max") == 0) i--;

  return ssl_versions[i].name != NULL ? ssl_versions[i].version : -1;
}

/* 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 */
}

/* Start SSL/TLS negotiations
 * Accepts: open TCP stream of session
 *	    user's host name
 *	    flags
 * Returns: SSL stream if success else NIL
 */

static SSLSTREAM *ssl_start (TCPSTREAM *tstream,char *host,unsigned long flags)
{
  SECURITY_STATUS e;
  ULONG a;
  TimeStamp t;
  SecBuffer ibuf[2],obuf[1];
  SecBufferDesc ibufs,obufs;
  SCHANNEL_CRED tlscred;
  CERT_CONTEXT *cert = NIL;
  CERT_CHAIN_PARA chparam;
  CERT_CHAIN_CONTEXT *chain;
  SSL_EXTRA_CERT_CHAIN_POLICY_PARA policy;
  CERT_CHAIN_POLICY_PARA polparam;
  CERT_CHAIN_POLICY_STATUS status;
  char tmp[MAILTMPLEN],certname[256];
  char *reason = NIL;
  ULONG req = 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 |
	ISC_REQ_MANUAL_CRED_VALIDATION;
  LPSTR usage[] = {
    szOID_PKIX_KP_SERVER_AUTH,
    szOID_SERVER_GATED_CRYPTO,
    szOID_SGC_NETSCAPE
  };
  PWSTR whost = NIL;
  char *buf = (char *) fs_get (ssltsz);
  unsigned long size = 0;
  int minv = *(int *) mail_parameters(NULL, GET_ENCRYPTION_RANGE_MIN, NULL);
  int maxv = *(int *) mail_parameters(NULL, GET_ENCRYPTION_RANGE_MAX, NULL);
  int i, client_request;
  sslcertificatequery_t scq =
    (sslcertificatequery_t) mail_parameters (NIL,GET_SSLCERTIFICATEQUERY,NIL);
  sslfailure_t sf = (sslfailure_t) mail_parameters (NIL,GET_SSLFAILURE,NIL);
  SSLSTREAM *stream = (SSLSTREAM *) memset (fs_get (sizeof (SSLSTREAM)),0,
					    sizeof (SSLSTREAM));
  stream->tcpstream = tstream;	/* bind TCP stream */
				/* initialize TLS credential */
  memset (&tlscred,0,sizeof (SCHANNEL_CRED));
  tlscred.dwVersion = SCHANNEL_CRED_VERSION;
  client_request = (flags & NET_TRYTLS1) ? SP_PROT_TLS1
		    : (flags & NET_TRYTLS1_1) ? SP_PROT_TLS1_1
		    : (flags & NET_TRYTLS1_2) ? SP_PROT_TLS1_2
		    : 0;
  /*
   * if no special request, negotiate the maximum the client is configured
   * to negotiate.
   */
  if(client_request == 0)
    client_request = maxv;

  if(client_request < minv || client_request > maxv)
    return NIL;         /* out of range? bail out */

  if (flags & NET_TRYTLS1) tlscred.grbitEnabledProtocols = SP_PROT_TLS1;
  else if (flags & NET_TRYTLS1_1) tlscred.grbitEnabledProtocols = SP_PROT_TLS1_1;
  else if (flags & NET_TRYTLS1_2) tlscred.grbitEnabledProtocols = SP_PROT_TLS1_2;
  else tlscred.grbitEnabledProtocols = 0; /* use default TLS, see https://docs.microsoft.com/en-us/security/engineering/solving-tls1-problem */

				/* acquire credentials */
  if (sft->AcquireCredentialsHandle
      (NIL,UNISP_NAME,SECPKG_CRED_OUTBOUND,NIL,(flags & NET_TLSCLIENT) ?
       &tlscred : NIL,NIL,NIL,&stream->cred,&t)
      != SEC_E_OK) reason = "Acquire credentials handle failed";
  else while (!reason) {	/* 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 = sft->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 (!tcp_sout (stream->tcpstream,obuf[0].pvBuffer,obuf[0].cbBuffer))
	reason = "Unexpected TCP output disconnect";
				/* free the buffer */
      sft->FreeContextBuffer (obuf[0].pvBuffer);
    }
    if (!reason) switch (e) {	/* negotiation state */
    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))
	reason = "Unexpected TCP input disconnect";
      else {
	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;

    case SEC_E_OK:		/* success, 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 (certNameToStr && !(flags & NET_NOVALIDATECERT)) {
				/* need validation, make wchar of host */
	if (!((size = MultiByteToWideChar (CP_ACP,0,host,-1,NIL,0)) &&
	      (whost = (PWSTR) fs_get (size*sizeof (WCHAR))) &&
	      MultiByteToWideChar (CP_ACP,0,host,-1,whost,size)))
	  fatal ("Can't make wchar of host name!");
				/* get certificate */
	if ((sft->QueryContextAttributes
	     (&stream->context,SECPKG_ATTR_REMOTE_CERT_CONTEXT,&cert) !=
	     SEC_E_OK) || !cert) {
	  reason = "*Unable to get certificate";
	  strcpy (certname,"<no certificate>");
	}
	else {			/* get certificate subject name */
	  (*certNameToStr) (X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
			    &cert->pCertInfo->Subject,CERT_X500_NAME_STR,
			    certname,255);
				/* build certificate chain */
	  memset (&chparam,0,sizeof (chparam));
	  chparam.cbSize = sizeof (chparam);
	  chparam.RequestedUsage.dwType = USAGE_MATCH_TYPE_OR;
	  chparam.RequestedUsage.Usage.rgpszUsageIdentifier = usage;
	  chparam.RequestedUsage.Usage.cUsageIdentifier =
	    sizeof (usage) / sizeof (LPSTR);
	  if (!(*certGetCertificateChain)
	      (NIL,cert,NIL,cert->hCertStore,&chparam,NIL,NIL,&chain))
	    reason = ssl_analyze_status (GetLastError (),tmp);
	  else {		/* validate certificate chain */
	    memset (&policy,0,sizeof (SSL_EXTRA_CERT_CHAIN_POLICY_PARA));
	    policy.cbStruct = sizeof (SSL_EXTRA_CERT_CHAIN_POLICY_PARA);
	    policy.dwAuthType = AUTHTYPE_SERVER;
	    policy.fdwChecks = NIL;
	    policy.pwszServerName = whost;
	    memset (&polparam,0,sizeof (polparam));
	    polparam.cbSize = sizeof (polparam);
	    polparam.pvExtraPolicyPara = &policy;
	    memset (&status,0,sizeof (status));
	    status.cbSize = sizeof (status);
	    if (!(*certVerifyCertificateChainPolicy)
		(CERT_CHAIN_POLICY_SSL,chain,&polparam,&status))
	      reason = ssl_analyze_status (GetLastError (),tmp);
	    else if (status.dwError)
	      reason = ssl_analyze_status (status.dwError,tmp);
	    (*certFreeCertificateChain) (chain);
	  }
	  (*certFreeCertificateContext) (cert);
	}
	if (whost) fs_give ((void **) &whost);

	if (reason) {		/* got an error? */
				/* application callback */
	  if (scq) reason = (*scq) ((*reason == '*') ? reason + 1 : reason,
				    host,certname) ? NIL : "";
	  else if (*certname) {	/* error message to return via mm_log() */
	    sprintf (buf,"*%.128s: %.255s",
		     (*reason == '*') ? reason + 1 : reason,certname);
	    reason = buf;
	  }
	}
      }
      if (reason ||
	  (reason = ssl_analyze_status
	   (sft->QueryContextAttributes
	    (&stream->context,SECPKG_ATTR_STREAM_SIZES,&stream->sizes),buf)))
	break;			/* error in certificate or getting sizes */
      fs_give ((void **) &buf);	/* flush temporary buffer */
				/* make maximum-sized buffers */
      stream->bufsize = stream->sizes.cbHeader +
	stream->sizes.cbMaximumMessage + stream->sizes.cbTrailer;
      if (stream->sizes.cbMaximumMessage < SSLBUFLEN)
	fatal ("cbMaximumMessage is less than SSLBUFLEN!");
      else if (stream->sizes.cbMaximumMessage < 16384) {
	sprintf (tmp,"WINDOWS BUG: cbMaximumMessage = %ld, should be 16384",
		 (long) stream->sizes.cbMaximumMessage);
	mm_log (tmp,NIL);
      }
      stream->ibuf = (char *) fs_get (stream->bufsize);
      stream->obuf = (char *) fs_get (stream->bufsize);
      return stream;
    default:
      reason = ssl_analyze_status (e,buf);
    }
  }
  ssl_close (stream);		/* failed to do SSL */
  stream = NIL;			/* no stream returned */
  switch (*reason) {		/* analyze reason */
  case '*':			/* certificate failure */
    ++reason;			/* skip over certificate failure indication */
				/* pass to error callback */
    if (sf) (*sf) (host,reason,flags);
    else {			/* no error callback, build error message */
      sprintf (tmp,"Certificate failure for %.80s: %.512s",host,reason);
      mm_log (tmp,ERROR);
    }
  case '\0':			/* user answered no to certificate callback */
    if (flags & NET_TRYSSL)	/* return dummy stream to stop tryssl */
      stream = (SSLSTREAM *) memset (fs_get (sizeof (SSLSTREAM)),0,
				     sizeof (SSLSTREAM));
    break;
  default:			/* non-certificate failure */
    if (flags & NET_TRYSSL);	/* no error output if tryssl */
				/* pass to error callback */
    else if (sf) (*sf) (host,reason,flags);
    else {			/* no error callback, build error message */
      sprintf (tmp,"TLS/SSL failure for %.80s: %.512s",host,reason);
      mm_log (tmp,ERROR);
    }
    break;
  }
  fs_give ((void **) &buf);	/* flush temporary buffer */
  return stream;
}

/* Generate error text from SSL error code
 * Accepts: SSL status
 *	    scratch buffer
 * Returns: text if error status, else NIL
 */

static char *ssl_analyze_status (SECURITY_STATUS err,char *buf)
{
  switch (err) {
  case SEC_E_OK:		/* no error */
  case SEC_I_CONTINUE_NEEDED:
  case SEC_I_INCOMPLETE_CREDENTIALS:
  case SEC_E_INCOMPLETE_MESSAGE:
    return NIL;
  case SEC_E_NO_AUTHENTICATING_AUTHORITY:
    mm_log ("unexpected SEC_E_NO_AUTHENTICATING_AUTHORITY",NIL);
    return "*No authority could be contacted for authentication";
  case SEC_E_WRONG_PRINCIPAL:
    mm_log ("unexpected SEC_E_WRONG_PRINCIPAL",NIL);
  case CERT_E_CN_NO_MATCH:
    return "*Server name does not match certificate";
  case SEC_E_UNTRUSTED_ROOT:
    mm_log ("unexpected SEC_E_UNTRUSTED_ROOT",NIL);
  case CERT_E_UNTRUSTEDROOT:
    return "*Self-signed certificate or untrusted authority";
  case SEC_E_CERT_EXPIRED:
    mm_log ("unexpected SEC_E_CERT_EXPIRED",NIL);
  case CERT_E_EXPIRED:
    return "*Certificate has expired";
  case CERT_E_REVOKED:
    return "*Certificate revoked";
  case SEC_E_INVALID_TOKEN:
    return "Invalid token, probably not an SSL server";
  case SEC_E_UNSUPPORTED_FUNCTION:
    return "SSL not supported on this machine - upgrade your system software";
  }
  sprintf (buf,"Unexpected SSPI or certificate error %lx - report this",err);
  return buf;
}

/* SSL receive line
 * Accepts: SSL stream
 * Returns: text line string or NIL if failure
 */

char *ssl_getline (SSLSTREAM *stream)
{
  unsigned long n,contd;
  char *ret = ssl_getline_work (stream,&n,&contd);
  if (ret && contd) {		/* got a line needing continuation? */
    STRINGLIST *stl = mail_newstringlist ();
    STRINGLIST *stc = stl;
    do {			/* collect additional lines */
      stc->text.data = (unsigned char *) ret;
      stc->text.size = n;
      stc = stc->next = mail_newstringlist ();
      ret = ssl_getline_work (stream,&n,&contd);
    } while (ret && contd);
    if (ret) {			/* stash final part of line on list */
      stc->text.data = (unsigned char *) ret;
      stc->text.size = n;
				/* determine how large a buffer we need */
      for (n = 0, stc = stl; stc; stc = stc->next) n += stc->text.size;
      ret = fs_get (n + 1);	/* copy parts into buffer */
      for (n = 0, stc = stl; stc; n += stc->text.size, stc = stc->next)
	memcpy (ret + n,stc->text.data,stc->text.size);
      ret[n] = '\0';
    }
    mail_free_stringlist (&stl);/* either way, done with list */
  }
  return ret;
}

/* SSL receive line or partial line
 * Accepts: SSL stream
 *	    pointer to return size
 *	    pointer to return continuation flag
 * Returns: text line string, size and continuation flag, or NIL if failure
 */

static char *ssl_getline_work (SSLSTREAM *stream,unsigned long *size,
			       long *contd)
{
  unsigned long n;
  char *s,*ret,c,d;
  *contd = NIL;			/* assume no continuation */
				/* make sure have data */
  if (!ssl_getdata (stream)) return NIL;
  for (s = stream->iptr, n = 0, c = '\0'; stream->ictr--; n++, c = d) {
    d = *stream->iptr++;	/* slurp another character */
    if ((c == '\015') && (d == '\012')) {
      ret = (char *) fs_get (n--);
      memcpy (ret,s,*size = n);	/* copy into a free storage string */
      ret[n] = '\0';		/* tie off string with null */
      return ret;
    }
  }
				/* copy partial string from buffer */
  memcpy ((ret = (char *) fs_get (n)),s,*size = 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[*size = --n] = '\0';	/* tie off string with null */
  }
  else *contd = LONGT;		/* continuation needed */
  return ret;
}

char *ssl_getsize(SSLSTREAM* stream, unsigned long size)
{
    char *ret = NIL;
    unsigned long got = 0L, need = size, n;

    do {
        if (!ssl_getdata(stream)) return ret;      /* return what we have */
        n = stream->ictr < need ? stream->ictr : need;
        fs_resize((void **)&ret, (got + n + 1)*sizeof(char));
        memcpy(ret + got, stream->iptr, n);
        ret[got + n] = '\0';
        got += n;
        need -= n;
        stream->iptr += n;
        stream->ictr -= n;
    } while (need > 0);

    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)
{
  while (stream->ictr < 1) {	/* decrypted buffer empty? */
    SECURITY_STATUS status;
    SecBuffer buf[4];
    SecBufferDesc msg;
    size_t i;
    size_t n = 0;		/* initially no bytes to decrypt */
    if (!stream->tcpstream) return NIL;
    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->bufsize - n,stream->tcpstream->ictr)))
	  fatal ("incomplete SecBuffer exceeds maximum buffer size");
				/* 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 = ((DECRYPT_MESSAGE_FN) sft->Reserved4)
	      (&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;
  size_t n;
  if (!stream->tcpstream) return NIL;
				/* until request satisfied */
  for (s = stream->ibuf,n = 0; size;) {
				/* 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,SSLBUFLEN));
				/* 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 ((((ENCRYPT_MESSAGE_FN) sft->Reserved3)
	 (&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
 */

static long ssl_abort (SSLSTREAM *stream)
{
  if (stream->tcpstream) {	/* close TCP stream */
    sft->DeleteSecurityContext (&stream->context);
    sft->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 stream ? tcp_host (stream->tcpstream) : "UNKNOWN";
}


/* 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);
}

#include "ssl_none.c"		/* currently no server support */
