text: /* ========================================================================
   * Copyright 2018-2020 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 */
  