/*****************************************************************************/
/*
                               SesolaClient.c

Secure Sockets Layer client (peer) certificate.


VERSION HISTORY
---------------
06-MAR-2023  NHD  X509 Authorization parameters can now include
                    [IG:<integer>] will ignore client cert verification
                    error number returned during the verification process
                    (see prologue to AuthConfigParam())
11-FEB-2023  MGD  SesolaClientCert() new algorithm for determining client
                    certificate validity (see SesolaCertVerifyCallback())
10-AUG-2020  MGD  SesolaClientCert() propagate connect cert into client cert
                  SesolaClientCertEnd() both HTTP/1.n and HTTP/2
17-MAY-2020  MGD  bugfix; SesolaClientCertGet() SSL_VERIFY_POST_HANDSHAKE
19-JUN-2019  MGD  bugfix; SesolaClientCertGet() status 0 an issue
                  bugfix; SesolaClientCertGet() if (value <= 0) break;
17-APR-2019  MGD  bugfix; SesolaClientCert() allow pattern per 25-AUG-2015
10-OCT-2018  MGD  SesolaClientCertGet() retrieves the client certificate
                    by renegotiation for TLSv1.2 and earlier, or post
                    handshake for TVSv1.3 and later
27-JUL-2018  MGD  bugfix; X509_free() memory leak with ->ClientCertPtr
26-JAN-2018  MGD  SesolaClientCertUserName()
10-JUN-2017  MGD  SesolaClientCertRenegotiate() allow for pre- and post-
                    OpenSSL 1.1.0 due to MSIE11 (Edge) stalling on a read
                    after renegotiation (pre reverts to v11.0 and earlier code) 
                  bugfix; SesolaClientCertConditional() 'IS' processing
                  bugfix; SesolaClientCertRenegotiate() allow for low-level
                    (i.e. SSL) I/O errors (e.g. link disconnection)
23-JUL-2016  MGD  SesolaClientCertRenegotiate() rework due to OpenSSL v1.1.0
08-JUL-2016  MGD  bugfix; SesolaClientCert() move X509 RENEGOTIATE switch
                    HTTP/2 to HTTP/1.1 after SSL_get_peer_certificate()
05-JUN-2016  MGD  bugfix; SesolaClientCertRenegotiate() ensure application
                    data is cleared before renegotiate initiated
11-MAY-2016  MGD  bugfix; SesolaClientCert() just return status
20-DEC-2015  MGD  SesolaClientCertMetaCon()
27-SEP-2015  MGD  SesolaClientCert() if SesolaCertParseDn() does not return
                    the user DN record then try SesolaCertExtension()
                  SesolaClientCert() display client certificate extensions
25-AUG-2015  MGD  [ru:/CN=<pattern>] allows multiple to be selected between
                    (e.g. "[ru:/CN=user*]", "[ru:/CN=^^\[^/=\]*$]" )
                  [ru:..] escape characters using '\' (especially ']')
07-JUL-2013  MGD  SesolaWatchErrors() during renegotiation
21-AUG-2004  MGD  significant refinements to SSL processing
22-JUL-2004  MGD  SesolaClientCert() call RequestEnd() instead of
                  SesolaNetSesolaClientCert() on network read/write error
14-JAN-2003  MGD  DN record /email and /emailAddress
28-AUG-2002  MGD  add SHA1 fingerprint (everybody else has it ;^)
07-APR-2002  MGD  bugfix; SesolaClientCert() call SesolaNetRequestEnd()
                  after network error for v8.0 SesolaNet..() support
28-FEB-2002  MGD  bugfix; SesolaRenegotiateClientCert()
                  reset SSL state to SSL_ST_OK if renegotiation fails
21-OCT-2001  MGD  rework SESOLA.C
*/
/*****************************************************************************/

#ifdef WASD_VMS_V7
#undef _VMS__V6__SOURCE
#define _VMS__V6__SOURCE
#undef __VMS_VER
#define __VMS_VER 70000000
#undef __CRTL_VER
#define __CRTL_VER 70000000
#endif

/* standard C header files */
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/* VMS related header files */
#include <ssdef.h>
#include <stsdef.h>

/* application header files */
#define SESOLA_REQUIRED
#include "Sesola.h"

#define WASD_MODULE "SESOLACLIENT"

/***************************************/
#ifdef SESOLA  /* secure sockets layer */
/***************************************/

/********************/
/* external storage */
/********************/

extern int  ExitStatus,
            OpcomMessages,
            SesolaVerifyPeerDataMax;

extern char  ErrorSanityCheck[],
             SoftwareID[];

extern uchar  SesolaSessionId[16];

extern WATCH_STRUCT  Watch;

/*****************************************************************************/
/*
This function is used for two purposes, authorization via a client certificate,
and just getting the client certificate (for reports, etc.)

Get the client certificate associated with the request.  If none is available
on the first call then initiate an SSL renegotiation with the client to supply
one.

If not authorizing using this certificate then just call the original AST to
return the certificate via 'rqptr->NetIoPtr->SesolaPtr->ClientCertPtr' (or of
course no certificate if the pointer is NULL).

When authorizing (SESOLA_VERIFY_PEER_AUTH) and a certificate is available
(either on first call, i.e. session cached, or after renegotiation) then get a
fingerprint of the certificate that can be used to identify the client user.
Setting 'VerifyParam' to SESOLA_VERIFY_PEER_NONE (aka SSL_VERIFY_NONE) can be
used to "logout" the client from it's current authorization, allowing another
certficiate to be selected and used (via "?httpd=logout").  If a certificate is
available this function generates all the appropriate authorization, user
detail and certificate information.

Values that can be passed via the 'VerifyMode' argument.
SESOLA_VERIFY_PEER_AUTH      verify the cert, fail, use for WASD authentication
SESOLA_VERIFY_PEER_NONE      renegotiate without peer verification
SESOLA_VERIFY_PEER_OPTIONAL  get and verify the certificate (continue on fail)
SESOLA_VERIFY_PEER_REQUIRED  abort the connection if the cert does not verify
*/

int SesolaClientCert
(
REQUEST_STRUCT *rqptr,
int VerifyParam,
REQUEST_AST AstFunction
)
{
   int  idx, number, status, version,
        IgnoreErrorCount,
        IgnoreErrorPrevCount,
        SessionHits,
        SessionTimeout,
        SessionTimeCSec,
        SessionTimeoutCSec,
        VerifyMode,
        WatchThisCat;
   char  *aptr, *cptr, *sptr, *zptr,
         *CurrentPtr,
         *DigestTypePtr;
   char  CertFingerprintMD5 [64],
         CertFingerprintSHA1 [64],
         CertFingerprintSHA256 [128],
         CertIssuer [512],
         CertSubject [512],
         String [256],
         TimeString [32],
         TimeoutString [32],
         UserDetails [256];
   SESOLA_STRUCT  *sesolaptr;
   SSL_SESSION  *SessionPtr;
   struct tm  *tmptr;

   /*********/
   /* begin */
   /*********/

   if (WATCHMOD (rqptr, WATCH_MOD_SESOLA))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_SESOLA,
                 "SesolaClientCert() !&A !UL", AstFunction, VerifyParam);

   /* network errors? */
   if (rqptr->NetIoPtr->ReadStatus && VMSnok (rqptr->NetIoPtr->ReadStatus))
      return (rqptr->NetIoPtr->ReadStatus);
   if (rqptr->NetIoPtr->WriteStatus && VMSnok (rqptr->NetIoPtr->WriteStatus))
      return (rqptr->NetIoPtr->WriteStatus);

   sesolaptr = SesolaRequestPtr (rqptr);

   version = SSL_version (sesolaptr->SslPtr);
   SessionPtr = SSL_get_session (sesolaptr->SslPtr);

   if (VerifyParam != SESOLA_VERIFY_AST)
   {
      /* initial call, not AST delivery - must have an AST address */
      if (!AstFunction)
         ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI);

      /* set the peer verification type */
      sesolaptr->CertVerifyMode = VerifyParam;
   }

   /* mask off any WASD-specific bits */
   VerifyMode = sesolaptr->CertVerifyMode & SESOLA_VERIFY_PEER_MASK;

   IgnoreErrorCount = 0;

   if (sesolaptr->CertVerifyMode == SESOLA_VERIFY_PEER_AUTH)
   {
      /*****************/
      /* authorization */
      /*****************/

      /*
         Check for directives about how to make the verification.
         We must do this every time, whether or not an actual renegotiation
         will take place, to set things like session timeouts, etc.
         I've tried to make it as efficient as possible.
      */
      if ((cptr = rqptr->rqAuth.PathParameterPtr)[0])
      {
         while (*cptr)
         {
            while (*cptr && *cptr != '[')
            {
               if (cptr[0] == '\\' && cptr[1]) cptr++;
               if (*cptr) cptr++;
            }
            if (!*cptr) break;
            switch (*(ULONGPTR)cptr)
            {
               case '[dp:' :
               case '[DP:' :

                  /*****************************/
                  /* set CA verification depth */
                  /*****************************/

                  cptr += sizeof(unsigned long);
                  if (isdigit (*cptr) || *cptr == '-')
                  {
                     number = atoi(cptr);
                     SSL_set_verify_depth (sesolaptr->SslPtr, number);
                  }
                  break;

               case '[ig:' :
               case '[IG:' :

                  /**************************/
                  /* ignore this error code */
                  /**************************/

                  /* actually processed below */
                  cptr += sizeof(unsigned long);
                  break;

               case '[lt:' :
               case '[LT:' :

                  /************************/
                  /* set session lifetime */
                  /************************/

                  cptr += sizeof(unsigned long);
                  if (MATCH4 (cptr, "expi") ||
                      MATCH4 (cptr, "EXPI"))
                     sesolaptr->SessionTimeoutMinutes = -1;
                  else
                  if (isdigit (*cptr) || *cptr == '-')
                     sesolaptr->SessionLifetimeMinutes = atoi(cptr);
                  break;

               case '[ru:' :
               case '[RU:' :

                  /***************************/
                  /* source or 'remote-user' */
                  /***************************/

                  zptr = (sptr = sesolaptr->X509RemoteUserDnRecord) +
                         sizeof(sesolaptr->X509RemoteUserDnRecord)-1;
                  cptr += 4;
                  while (*cptr && !ISLWS(*cptr) && *cptr != ']' && sptr < zptr)
                  {
                     if (cptr[0] == '\\' && cptr[1]) cptr++;
                     *sptr++ = *cptr++;
                  }
                  *sptr = '\0';
                  break;

               case '[to:' :
               case '[TO:' :

                  /***********************/
                  /* set session timeout */
                  /***********************/

                  cptr += sizeof(unsigned long);
                  if (MATCH4 (cptr, "expi") ||
                      MATCH4 (cptr, "EXPI"))
                     sesolaptr->SessionTimeoutMinutes = -1;
                  else
                  if (isdigit (*cptr) || *cptr == '-')
                     sesolaptr->SessionTimeoutMinutes = atoi(cptr);
                  break;

               case '[vf:' :
               case '[VF:' :

                  /************************************/
                  /* type of client cert verification */
                  /************************************/

                  cptr += sizeof(unsigned long);
                  switch (*(ULONGPTR)cptr)
                  {
                     case 'none' :
                     case 'NONE' :
                        /* [VF:NONE] */
                        VerifyMode = SSL_VERIFY_NONE;
                        break;
                     case 'opti' :
                     case 'OPTI' :
                        /* [VF:OPTIONAL] */
                        sesolaptr->X509optionalNoCa = true;
                        VerifyMode = SSL_VERIFY_PEER;
                        break;
                     case 'requ' :
                     case 'REQU' :
                        /* [VF:REQUIRED] */
                        VerifyMode = SSL_VERIFY_PEER |
                                     SSL_VERIFY_FAIL_IF_NO_PEER_CERT;
                        break;
                  }

                  if (version >= TLS1_3_VERSION)
                     VerifyMode |= SSL_VERIFY_POST_HANDSHAKE;

                  break;

               default :

                  /* encountered some other directive, note it's location */
                  sesolaptr->X509ConditionalPtr = cptr;
                  /* this *must* be done after the notation! */
                  cptr += sizeof(unsigned long);
            }
         }
      }
   }

   /* if the rule wants it pre-expired then ignore any cached certificate */
   if (sesolaptr->SessionTimeoutMinutes < 0 &&
       VerifyParam != SESOLA_VERIFY_AST)
   {
      if (sesolaptr->ClientCertPtr) X509_free (sesolaptr->ClientCertPtr);
      sesolaptr->ClientCertPtr = NULL;
   }
   else
   if (sesolaptr->CertVerifyMode == SESOLA_VERIFY_PEER_NONE)
   {
      if (sesolaptr->ClientCertPtr) X509_free (sesolaptr->ClientCertPtr);
      sesolaptr->ClientCertPtr = NULL;
   }
   else
   if (!sesolaptr->ClientCertPtr)
      sesolaptr->ClientCertPtr = SSL_get_peer_certificate (sesolaptr->SslPtr);

   if (WATCHMOD (rqptr, WATCH_MOD_SESOLA))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_SESOLA,
                 "->ClientCertPtr !&X", sesolaptr->ClientCertPtr);

   if (!sesolaptr->ClientCertPtr)
   {
      /***********************************************/ 
      /* no client certficiate (currently) available */
      /***********************************************/ 

      if (HTTP2_REQUEST(rqptr))
      {
         /* RFC7540 9.2.1 */
         if (WATCHPNT(rqptr) && (WATCH_CATEGORY(WATCH_HTTP2) ||
                                 WATCH_CATEGORY(WATCH_SESOLA) ||
                                 WATCH_CATEGORY(WATCH_AUTH)))
            WatchThis (WATCHITM(rqptr), WATCH_SESOLA,
                       "X509 RENEGOTIATE switch HTTP/2 to HTTP/1.1");
         Http2Error (rqptr->Http2Ptr, HTTP2_ERROR_HTTP11, FI_LI);
         rqptr->rqResponse.HttpStatus = 101;
         return (SS$_ABORT);
      }

      if (!SSL_get_secure_renegotiation_support(sesolaptr->SslPtr))
      {
         if (WATCHPNT(rqptr) && (WATCH_CATEGORY(WATCH_SESOLA) ||
                                 WATCH_CATEGORY(WATCH_AUTH)))
            WatchThis (WATCHITM(rqptr), WATCH_SESOLA,
                       "CLIENT does not support SECURE RENEGOTIATION");
         rqptr->rqResponse.HttpStatus = 500;
         return (SS$_ABORT);
      }

      if (VerifyParam == SESOLA_VERIFY_AST)
      {
         /* call from SesolaClientCertGet(), no certificate! */
         if (sesolaptr->CertVerifyMode == SESOLA_VERIFY_PEER_AUTH)
         {
            /*****************/
            /* authorization */
            /*****************/

            /* no certificate - no authentication! */
            rqptr->rqAuth.FinalStatus = AUTH_DENIED_BY_LOGIN;
         }

         /* called as an AST, therefore call the original AST address */
         SysDclAst (sesolaptr->ClientCertAstFunction, rqptr);

         return (SS$_NORMAL);
      }

      /* let's renegotiate with the client, trying to get a certificate */
      sesolaptr->ClientCertAstFunction = AstFunction;

      sesolaptr->CertVerifyCallbackCount = 0;

      SSL_set_verify (sesolaptr->SslPtr, VerifyMode,
                      &SesolaCertVerifyCallback);

      /* provide the request pointer for the verify callback */
      SSL_set_ex_data (sesolaptr->SslPtr, 0, sesolaptr);

      SesolaClientCertGet (sesolaptr);

      if (sesolaptr->CertVerifyMode == SESOLA_VERIFY_PEER_AUTH)
      {
         /*****************/
         /* authorization */
         /*****************/

         /* returning AUTH_PENDING, activate authorization AST function */
         rqptr->rqAuth.AstFunction = rqptr->rqAuth.AstFunctionBuffer;
         rqptr->rqAuth.FinalStatus = AUTH_PENDING;
      }

      /* the faux "waiting for user labels" status indicates renegotiation */
      return (SS$_WAITUSRLBL);
   }

   /*************************************/
   /* yes, we have a client certificate */
   /*************************************/

   /* if a [LT:integer] was set earlier then now's the time to apply it */
   if (sesolaptr->SessionLifetimeMinutes)
   {
      /*
         Hmmm, not sure if this is the absolutely best thing to do!
         Now that we've got a certificate make it a little easier on the
         client by extending the session timeout so the user will not need
         to respecify the certificate too often provided the session is
         continually used (updated each request by resetting the session
         timestamp).
      */
      SSL_SESSION_set_time (SessionPtr, time(NULL));
   }
   /* a [TO:integer] value will override a [LT:integer] one */
   if (sesolaptr->SessionTimeoutMinutes)
      SSL_SESSION_set_timeout (SessionPtr,
                               sesolaptr->SessionTimeoutMinutes*60);
   else
   if (sesolaptr->SessionLifetimeMinutes)
      SSL_SESSION_set_timeout (SessionPtr,
                               sesolaptr->SessionLifetimeMinutes*60);

   WatchThisCat = 0;
   if (WATCH_CAT && WATCHPNT(rqptr))
   {
      if WATCH_CATEGORY(WATCH_SESOLA)
         WatchThisCat =  WATCH_SESOLA;
      else
      if WATCH_CATEGORY(WATCH_AUTH)
         WatchThisCat = WATCH_AUTH;
   }

   if (WatchThisCat ||
       sesolaptr->CertVerifyMode == SESOLA_VERIFY_PEER_AUTH)
   {
      X509_NAME_oneline (X509_get_issuer_name(sesolaptr->ClientCertPtr),
                         CertIssuer, sizeof(CertIssuer));

      X509_NAME_oneline (X509_get_subject_name(sesolaptr->ClientCertPtr),
                         CertSubject, sizeof(CertSubject));

      DigestTypePtr = SesolaCertFingerprint (sesolaptr->ClientCertPtr,
                                             &EVP_md5,
                                             CertFingerprintMD5,
                                             sizeof(CertFingerprintMD5));
      if (*DigestTypePtr)
         DigestTypePtr = SesolaCertFingerprint (sesolaptr->ClientCertPtr,
                                                &EVP_sha1,
                                                CertFingerprintSHA1,
                                                sizeof(CertFingerprintSHA1));
   }

   if (WATCH_CAT && WatchThisCat)
   {
      SesolaCertFingerprint (sesolaptr->ClientCertPtr,
                             &EVP_sha256,
                             CertFingerprintSHA256,
                             sizeof(CertFingerprintSHA256));

      SessionTimeCSec = SSL_SESSION_get_time (SessionPtr);
      SessionTimeout = SSL_SESSION_get_timeout (SessionPtr);
      SessionTimeoutCSec = SessionTimeCSec + SessionTimeout;
      tmptr = localtime (&SessionTimeCSec);
      if (!strftime (TimeString, sizeof(TimeString),
                     "%b %d %T %Y", tmptr))
         strzcpy (TimeString, "strftime() error", sizeof(TimeString));
      tmptr = localtime (&SessionTimeoutCSec);
      if (!strftime (TimeoutString, sizeof(TimeoutString),
                     "%b %d %T %Y", tmptr))
         strzcpy (TimeoutString, "strftime() error", sizeof(TimeoutString));
      SessionHits = SSL_CTX_sess_hits (sesolaptr->SslCtx);

      WatchThis (WATCHITM(rqptr), WatchThisCat, "X509 client certificate");
      SesolaCertWatch (sesolaptr->ClientCertPtr);
      WatchDataFormatted ("SESSION: !UL hit!%s since !AZ, timeout (!SL) at !AZ\n",
                          SessionHits, TimeString, SessionTimeout / 60,
                          TimeoutString);
   }

   if (sesolaptr->CertVerifyMode != SESOLA_VERIFY_PEER_AUTH)
   {
      if (VerifyParam == SESOLA_VERIFY_AST)
      {
         /* call from SesolaClientCertGet() */
         SysDclAst (sesolaptr->ClientCertAstFunction, rqptr);
      }
      return (SS$_NORMAL);
   }

   /*****************/
   /* authorization */
   /*****************/

   if (sesolaptr->CertVerifyCount &&
       sesolaptr->CertVerifyCount <= IGNORE_ERROR_MAX)
   {
      if ((cptr = rqptr->rqAuth.PathParameterPtr)[0])
      {
         while (*cptr)
         {
            while (*cptr && *cptr != '[')
            {
               if (cptr[0] == '\\' && cptr[1]) cptr++;
               if (*cptr) cptr++;
            }
            if (!*cptr) break;
            switch (*(ULONGPTR)cptr)
            {
               case '[ig:' :
               case '[IG:' :

                  /**************************/
                  /* ignore this error code */
                  /**************************/

                  IgnoreErrorPrevCount = IgnoreErrorCount;
                  cptr += sizeof(unsigned long);
                  if (isdigit (*cptr))
                  {
                     number = atoi(cptr);
                     for (idx = 0; idx < sesolaptr->CertVerifyCount; idx++)
                     {
                        if (number == sesolaptr->CertVerifyCode[idx] ||
                            number == -sesolaptr->CertVerifyCode[idx])
                        {
                           /* note code has already been seen by negating */
                           sesolaptr->CertVerifyCode[idx] = -number;
                           IgnoreErrorCount++;
                        }
                     }
                     /* report this problem being ignored */
                     if (WATCH_CAT && WatchThisCat)
                        if (IgnoreErrorCount > IgnoreErrorPrevCount)
                           WatchThis (WATCHITM(rqptr), WatchThisCat,
                                      "IGNORE !SL \"!AZ\"", number,
                                      X509_verify_cert_error_string(number));
                  }
                  break;

               default :

                  /* some other directive, note it's location */
                  cptr += sizeof(unsigned long);
            }
         }
      }
   }

   if (sesolaptr->CertVerifyCount)
   {
      if (sesolaptr->CertVerifyCount > IGNORE_ERROR_MAX)
      {
         /* way, way too many problems with the cert */
         if (WATCH_CAT && WatchThisCat)
            WatchThis (WATCHITM(rqptr), WatchThisCat, "EXCEDE !SL > !SL",
                       sesolaptr->CertVerifyCount, IGNORE_ERROR_MAX);
         SSL_set_verify_result (sesolaptr->SslPtr,
                                X509_V_ERR_APPLICATION_VERIFICATION);
      }
      else
      if (sesolaptr->CertVerifyCount > IgnoreErrorCount)
      {
         /* not ignored problem(s) with the cert */
         if (WATCH_CAT && WatchThisCat)
         {
            for (idx = 0; idx < sesolaptr->CertVerifyCount; idx++)
            {
               /* report this problem */
               if ((number = sesolaptr->CertVerifyCode[idx]) > 0)
               {
                  WatchThis (WATCHITM(rqptr), WatchThisCat,
                             "FAILED !SL \"!AZ\"", number,
                             X509_verify_cert_error_string(number));
                  /* cancel any further instances of this error code */
                  for (; idx < sesolaptr->CertVerifyCount; idx++)
                     if (number == sesolaptr->CertVerifyCode[idx])
                        sesolaptr->CertVerifyCode[idx] = -number;
               }
            }
         }
         SSL_set_verify_result (sesolaptr->SslPtr,
                                X509_V_ERR_APPLICATION_VERIFICATION);
      }
      else
         SSL_set_verify_result (sesolaptr->SslPtr, X509_V_OK);
   }
   else
      SSL_set_verify_result (sesolaptr->SslPtr, X509_V_OK);

   if (SSL_get_verify_result (sesolaptr->SslPtr) != X509_V_OK)
   {
      rqptr->rqAuth.FinalStatus = AUTH_DENIED_BY_LOGIN;
      if (VerifyParam == SESOLA_VERIFY_AST)
      {
         /* call from SesolaClientCertGet() */
         SysDclAst (sesolaptr->ClientCertAstFunction, rqptr);
      }
      /* cancel the cached session by adjusting the timeout backwards */
      SSL_SESSION_set_timeout (SessionPtr, -1);
      return (SS$_NORMAL);
   }

   if (!*DigestTypePtr)
   {
      /* hmmm, problem in generating the fingerprint */
      rqptr->rqAuth.FinalStatus = AUTH_DENIED_BY_OTHER;
      if (VerifyParam == SESOLA_VERIFY_AST)
      {
         /* call from SesolaClientCertGet() */
         SysDclAst (sesolaptr->ClientCertAstFunction, rqptr);
      }
      /* cancel the cached session by adjusting the timeout backwards */
      SSL_SESSION_set_timeout (SessionPtr, -1);
      return (SS$_NORMAL);
   }

   /******************/
   /* authenticated! */
   /******************/

   /* X509 authentication, full r+w access implied */
   rqptr->rqAuth.UserCan = AUTH_READWRITE_ACCESS;
   rqptr->rqAuth.FinalStatus = SS$_NORMAL;

   rqptr->rqAuth.ClientCertIssuerLength = strlen(CertIssuer);
   rqptr->rqAuth.ClientCertIssuerPtr =
      VmGetHeap (rqptr, rqptr->rqAuth.ClientCertIssuerLength+1);
   strzcpy (rqptr->rqAuth.ClientCertIssuerPtr, CertIssuer,
            rqptr->rqAuth.ClientCertIssuerLength+1);

   rqptr->rqAuth.ClientCertSubjectLength = strlen(CertSubject);
   rqptr->rqAuth.ClientCertSubjectPtr =
      VmGetHeap (rqptr, rqptr->rqAuth.ClientCertSubjectLength+1);
   strzcpy (rqptr->rqAuth.ClientCertSubjectPtr, CertSubject,
            rqptr->rqAuth.ClientCertSubjectLength+1);

   /* if a conditional was detected during an earlier phase */
   if (sesolaptr->X509ConditionalPtr)
   {
      if (!SesolaClientCertConditional (rqptr, sesolaptr->X509ConditionalPtr))
      {
         rqptr->rqAuth.FinalStatus = AUTH_DENIED_BY_OTHER;
         if (VerifyParam == SESOLA_VERIFY_AST)
         {
            /* call from SesolaClientCertGet() */
            SysDclAst (sesolaptr->ClientCertAstFunction, rqptr);
         }
         /* cancel the cached session by adjusting the timeout backwards */
         SSL_SESSION_set_timeout (SessionPtr, -1);
         return (SS$_NORMAL);
      }
   }

   /* derive the user details from the /CN and /EMAIL of the subject */
   zptr = (sptr = UserDetails) + sizeof(UserDetails)-1;
   cptr = SesolaCertParseDn (rqptr->rqAuth.ClientCertSubjectPtr, "/CN=");
   if (cptr)
   {
      while (*cptr && *cptr != '=') cptr++;
      if (*cptr) cptr++;
      while (*cptr && sptr < zptr) *sptr++ = *cptr++;
   }
   cptr = SesolaCertParseDn (rqptr->rqAuth.ClientCertSubjectPtr, "/Email=");
   if (!cptr)
      cptr = SesolaCertParseDn (rqptr->rqAuth.ClientCertSubjectPtr,
                                "/emailAddress=");
   if (cptr)
   {
      while (*cptr && *cptr != '=') cptr++;
      if (*cptr)
      {
         cptr++;
         if (sptr < zptr) *sptr++ = ',';
         if (sptr < zptr) *sptr++ = ' ';
         while (*cptr && sptr < zptr) *sptr++ = *cptr++;
      }
   }
   *sptr = '\0';
   rqptr->rqAuth.UserDetailsLength = sptr - UserDetails;
   rqptr->rqAuth.UserDetailsPtr =
      VmGetHeap (rqptr, rqptr->rqAuth.UserDetailsLength+1);
   strzcpy (rqptr->rqAuth.UserDetailsPtr, UserDetails,
            rqptr->rqAuth.UserDetailsLength+1);

   rqptr->rqAuth.ClientCertFingerprintLength = strlen(CertFingerprintMD5);
   rqptr->rqAuth.ClientCertFingerprintPtr =
      VmGetHeap (rqptr, rqptr->rqAuth.ClientCertFingerprintLength+1);
   strzcpy (rqptr->rqAuth.ClientCertFingerprintPtr, CertFingerprintMD5,
            rqptr->rqAuth.ClientCertFingerprintLength+1);

   zptr = (sptr = rqptr->RemoteUser) + sizeof(rqptr->RemoteUser)-1;
   if (sesolaptr->X509RemoteUserDnRecord[0])
   {
      cptr = SesolaCertParseDn (rqptr->rqAuth.ClientCertSubjectPtr,
                                sesolaptr->X509RemoteUserDnRecord);
      if (!cptr)
         cptr = SesolaCertName (sesolaptr->ClientCertPtr,
                                sesolaptr->X509RemoteUserDnRecord);
      if (!cptr)
         cptr = SesolaCertExtension (sesolaptr->ClientCertPtr,
                                     sesolaptr->X509RemoteUserDnRecord);
      if (cptr)
      {     
         /* check for wildcard pattern */
         for (aptr = sesolaptr->X509RemoteUserDnRecord;
              *aptr && *aptr != '*' && *aptr != '^';
              aptr++);

         /* if something like (DTAG) /CN=User-Id* */
         if (*aptr)
            while (*cptr && *cptr != '=') cptr++;
         else
            for (aptr = sesolaptr->X509RemoteUserDnRecord;
                 *aptr && *cptr;
                 aptr++, cptr++);

         while (*cptr && *cptr != '_' && !isalnum(*cptr)) cptr++;

         while (*cptr && sptr < zptr)
         {
            /* convert white-space to underscores */
            if (ISLWS(*cptr))
               *sptr++ = '_';
            else
               *sptr++ = *cptr;
            cptr++;
         }
      }
   }
   else
   {
      /* "remote user name" is derived from fingerprint without the colons */
      cptr = rqptr->rqAuth.ClientCertFingerprintPtr;
      while (*cptr && sptr < zptr)
      {
        if (*cptr == ':')
           cptr++;
        else
           *sptr++ = *cptr++;
      }
   }
   /* overflow does not truncate, it empties (does not authorize)!! */
   if (sptr >= zptr)
   {
      *sptr = '\0';
      if (WATCH_CAT && WatchThisCat)
         WatchThis (WATCHITM(rqptr), WatchThisCat,
                    "X509 overflow at !UL bytes REMOTE_USER !AZ",
                    sizeof(rqptr->RemoteUser)-1, rqptr->RemoteUser);
      sptr = rqptr->RemoteUser;
   }
   *sptr = '\0';
   rqptr->RemoteUserLength = sptr - rqptr->RemoteUser;
   if (rqptr->RemoteUserLength)
      strzcpy (rqptr->RemoteUserPassword, "anystringwilldo",
               sizeof(rqptr->RemoteUserPassword));

   if (WATCH_CAT && WatchThisCat)
      WatchThis (WATCHITM(rqptr), WatchThisCat,
                 "X509 client certificate REMOTE_USER !AZ",
                 rqptr->RemoteUser[0] ? rqptr->RemoteUser : "(none)");

   if (VerifyParam == SESOLA_VERIFY_AST)
   {
      /* call from SesolaClientCertGet() */
      SysDclAst (sesolaptr->ClientCertAstFunction, rqptr);
   }
   return (SS$_NORMAL);
}

/*****************************************************************************/
/*
Called by RequestEnd2() to dispose of any client certificate reference.
*/

void SesolaClientCertEnd (REQUEST_STRUCT *rqptr)

{
   HTTP2_STRUCT  *h2ptr;
   SESOLA_STRUCT  *sesolaptr;

   /*********/
   /* begin */
   /*********/

   if (WATCHMOD (rqptr, WATCH_MOD_SESOLA))
      WatchThis (WATCHALL, WATCH_MOD_SESOLA, "SesolaClientCertEnd()");

   if (!(sesolaptr = (SESOLA_STRUCT*)rqptr->NetIoPtr->SesolaPtr))
   {
      if (!HTTP2_REQUEST(rqptr)) return;
      h2ptr = rqptr->Http2Ptr;
      if (!(sesolaptr = (SESOLA_STRUCT*)h2ptr->NetIoPtr->SesolaPtr)) return;
   }
   if (WATCHMOD (rqptr, WATCH_MOD_SESOLA))
      WatchThis (WATCHALL, WATCH_MOD_SESOLA, "->ClientCertPtr !8XL",
                 sesolaptr->ClientCertPtr);

   if (!sesolaptr->ClientCertPtr) return;

   X509_free (sesolaptr->ClientCertPtr);
   sesolaptr->ClientCertPtr = NULL;
}

/*****************************************************************************/
/*
Scan the authorization parameter string evaluating the conditions!  Return true
or false.  Anything it cannot understand it ignores!  (and yes, it does look a
little like MapUrl_Conditional() :^)
*/

BOOL SesolaClientCertConditional
(
REQUEST_STRUCT *rqptr,
char *ConditionalPtr
)
{
   BOOL  NegateThisCondition,
         NegateEntireConditional,
         Result,
         SoFarSoGood,
         WatchThisOne;
   int  AlgKeySize,
        ConditionalCount,
        MinKeySize,
        UseKeySize;
   char  *cptr, *csptr, *sptr, *zptr,
         *CipherNamePtr, 
         *CurrentPtr,
         *VersionNamePtr;
   char  Scratch [AUTH_MAX_PATH_PARAM_LENGTH+1];
   SESOLA_STRUCT  *sesolaptr;
   SSL_CIPHER  *CipherPtr;

   /*********/
   /* begin */
   /*********/

   if (WATCHMOD (rqptr, WATCH_MOD_SESOLA))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_SESOLA,
                 "SesolaClientCertConditional() !&Z\n", ConditionalPtr);

   sesolaptr = (SESOLA_STRUCT*)rqptr->NetIoPtr->SesolaPtr;

   if (WATCHPNT(rqptr) && (WATCH_CATEGORY(WATCH_SESOLA) ||
                             WATCH_CATEGORY(WATCH_AUTH)))
      WatchThisOne = true;
   else
      WatchThisOne = false;

   CurrentPtr = NULL;
   ConditionalCount = 0;
   NegateEntireConditional = NegateThisCondition = SoFarSoGood = false;
   cptr = ConditionalPtr;
   while (*cptr)
   {
      while (ISLWS(*cptr)) cptr++;
      if (!*cptr) break;

      if (*cptr == '[' || SAME2(cptr,'!['))
      {
         CurrentPtr = cptr;
         if (*cptr == '!')
         {
            NegateEntireConditional = true;
            cptr++;
         }
         else
            NegateEntireConditional = false;
         cptr++;
         ConditionalCount = 0;
         SoFarSoGood = false;
         continue;
      }
      if (*cptr == ']')
      {
         cptr++;
         if (NegateEntireConditional)
         {
            SoFarSoGood = !SoFarSoGood;
            NegateEntireConditional = false;
         }
         if (ConditionalCount && !SoFarSoGood)
         {
            cptr = "";
            break;
         }
         continue;
      }
      if (SoFarSoGood)
      {
         if (NegateEntireConditional)
         {
            SoFarSoGood = !SoFarSoGood;
            NegateEntireConditional = false;
         }
         /* at least one OK, skip to the end of the conditional */
         while (*cptr && *cptr != ']') cptr++;
         if (!*cptr) break;
      }

      if (!CurrentPtr) CurrentPtr = cptr;
      NegateThisCondition = Result = false;
      zptr = (sptr = Scratch) + sizeof(Scratch)-1;

      if (*cptr == '!')
      {
         cptr++;
         NegateThisCondition = true;
      }

      switch (*(USHORTPTR)cptr)
      {
         case 'ci' :
         case 'CI' :

            /***************/
            /* Cipher Name */
            /***************/

            ConditionalCount++;
            cptr += 3;
            while (*cptr && !ISLWS(*cptr) && *cptr != ']' && sptr < zptr)
            {
               if (cptr[0] == '\\' && cptr[1]) cptr++;
               if (*cptr) *sptr++ = *cptr++;
            }
            *sptr = '\0';
            if (!CipherPtr)
               CipherPtr = SSL_get_current_cipher (sesolaptr->SslPtr);
            if (!CipherPtr)
            {
               CipherNamePtr = "";
               AlgKeySize = UseKeySize = 0;
            }
            if (!CipherNamePtr)
               CipherNamePtr = (char*)SSL_CIPHER_get_name (CipherPtr);
            if (!CipherNamePtr) CipherNamePtr = "";
            Result = StringMatch (rqptr, CipherNamePtr, Scratch);
            break;

         case 'is' :
         case 'IS' :

            /******************/
            /* Cert Issuer DN */
            /******************/

            ConditionalCount++;
            cptr += 3;
            while (*cptr && !ISLWS(*cptr) && *cptr != ']' && sptr < zptr)
            {
               if (cptr[0] == '\\' && cptr[1]) cptr++;
               if (*cptr) *sptr++ = *cptr++;
            }
            *sptr = '\0';

            csptr = rqptr->rqAuth.ClientCertIssuerPtr;
            /* if it begins with DN record name then confine to that record */
            if (*(sptr = Scratch) == '/')
            {
               /* must begin with something like "/CN=string" */
               if (!SesolaCertParseDn (csptr, sptr))
                  Result = false;
               else
               {
                  /* found the (example) "/CN=", skip over BOTH */
                  while (*sptr && *sptr != '=') sptr++;
                  if (*sptr) sptr++;
                  while (*csptr && *csptr != '=') csptr++;
                  if (*csptr) csptr++;
                  /* now search only the returned DN record value */
                  Result = StringMatch (rqptr, csptr, sptr);
               }
            }
            else
            {
               /* search the entire DN */
               Result = StringMatch (rqptr, csptr, sptr);
            }
            break;

         case 'ks' :
         case 'KS' :

            /*****************/
            /* User Key Size */
            /*****************/

            ConditionalCount++;
            cptr += 3;
            while (*cptr && !ISLWS(*cptr) && *cptr != ']' && sptr < zptr)
            {
               if (cptr[0] == '\\' && cptr[1]) cptr++;
               if (*cptr) *sptr++ = *cptr++;
            }
            *sptr = '\0';
            MinKeySize = atoi(Scratch);
            if (MinKeySize < 0) MinKeySize = 0;
            if (!CipherPtr)
               CipherPtr = SSL_get_current_cipher (sesolaptr->SslPtr);
            if (!CipherPtr)
               UseKeySize = 0;
            else
               UseKeySize = SSL_CIPHER_get_bits(CipherPtr, &AlgKeySize);
            Result = UseKeySize >= MinKeySize;
            break;

         case 'su' :
         case 'SU' :

            /*******************/
            /* Cert Subject DN */
            /*******************/

            ConditionalCount++;
            cptr += 3;
            while (*cptr && !ISLWS(*cptr) && *cptr != ']' && sptr < zptr)
            {
               if (cptr[0] == '\\' && cptr[1]) cptr++;
               if (*cptr) *sptr++ = *cptr++;
            }
            *sptr = '\0';

            csptr = rqptr->rqAuth.ClientCertSubjectPtr;
            /* if it begins with DN record name then confine to that record */
            if (*(sptr = Scratch) == '/')
            {
               /* must begin with something like "/CN=string" */
               if (!(csptr = SesolaCertParseDn (csptr, sptr)))
                  Result = false;
               else
               {
                  /* found the (example) "/CN=", skip over BOTH */
                  while (*sptr && *sptr != '=') sptr++;
                  if (*sptr) sptr++;
                  while (*csptr && *csptr != '=') csptr++;
                  if (*csptr) csptr++;
                  /* now search only the returned DN record value */
                  Result = StringMatch (rqptr, csptr, sptr);
               }
            }
            else
            {
               /* search the entire DN */
               Result = StringMatch (rqptr, csptr, sptr);
            }
            break;

         default :

            /***********************************/
            /* unknown (or 'VF', etc.), ignore */
            /***********************************/

            if (WATCH_CAT && WatchThisOne)
               WatchDataFormatted ("IGNORE !AZ\n", CurrentPtr);
            while (*cptr && !ISLWS(*cptr) && *cptr != ']')
            {
               if (cptr[0] == '\\' && cptr[1]) cptr++;
               if (*cptr) cptr++;
            }
            continue;
      }

      if (NegateThisCondition)
         SoFarSoGood = SoFarSoGood || !Result;
      else
         SoFarSoGood = SoFarSoGood || Result;

      if (WATCH_CAT && WatchThisOne)
          WatchDataFormatted ("!AZ !AZ\n",
                              SoFarSoGood ? "PASS" : "FAIL", CurrentPtr);
      CurrentPtr = NULL;
   }

   if (!ConditionalCount)
      SoFarSoGood = true;
   else
   if (NegateEntireConditional)
      SoFarSoGood = !SoFarSoGood;

   if (WATCH_CAT && WatchThisOne)
       WatchDataFormatted ("!AZ conditional\n",
                           SoFarSoGood ? "PASSED" : "FAILED");

   return (SoFarSoGood);
}

/*****************************************************************************/
/*
Get an X509 certificate from the client, or try to anyway.

With TLSv1.2 and earlier:  Initiate an SSL renegotiate to give the client an
opportunity to supply a client certificate.

With TLSv1.3 (OpenSSL 1.1.1 and later):  Renegotation is no longer available
and it is done by having the service enable the TLSv1.3 post handshake
authentication extension that provides a client certificate.

Due to the non-blocking I/O used by WASD this function will be called multiple
times to complete the handshake.  Ensure pending request data (typically from a
PROPFIND, PUT or POST) is cleared before attempting to renegotiate.

As there is lots of negotiation going on underneath all the asynchronous I/O in
this behaviour some fine-grained inspection of the connection state is needed. 
Essentially at each handshake I/O the state is checked for client certificate
exchange.  This sets a flag and the next "SSL negotiation finished
successfully" message considered certificate negotiation concluded.
*/

void SesolaClientCertGet (SESOLA_STRUCT *sesolaptr)

{
   static char  buf[1];

   int  sane, state, value, verify, version;
   char  *cptr;
   REQUEST_STRUCT  *rqptr;

   /*********/
   /* begin */
   /*********/

   rqptr = sesolaptr->RequestPtr;

   if (WATCHMOD (rqptr, WATCH_MOD_SESOLA))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_SESOLA,
                 "SesolaClientCertGet() !&B !&B",
                 sesolaptr->SslStateFunction == &SesolaClientCertGet,
                 sesolaptr->ReadClientCert);

   if (sesolaptr->VerifyPeerDataSize &&
       sesolaptr->VerifyPeerDataCount < sesolaptr->VerifyPeerDataSize)
   {
      if ((value = SesolaClientRequestData (sesolaptr)) < 0)
      {
         SesolaWatchErrors (rqptr);
         SesolaClientCert (rqptr, SESOLA_VERIFY_AST, NULL);
         return;
      }
      /* if waiting on I/O */
      if (value == 0) return;
   }

   version = SSL_version (sesolaptr->SslPtr);

   if (sesolaptr->SslStateFunction != &SesolaClientCertGet)
   {
      /*********/
      /* start */
      /*********/

      if (rqptr->rqHeader.ContentLength64)
      {
         if ((value = SesolaClientRequestData (sesolaptr)) < 0)
         {
            SesolaWatchErrors (rqptr);
            SesolaClientCert (rqptr, SESOLA_VERIFY_AST, NULL);
            return;
         }
         /* if waiting on I/O */
         if (value == 0) return;
      }

      if (WATCHING (rqptr, WATCH_SESOLA))
      {
         verify = SSL_get_verify_mode (sesolaptr->SslPtr);
         switch (verify)
         {
            case SSL_VERIFY_NONE :
                 cptr = "NONE"; break;
            case SSL_VERIFY_PEER :
                 cptr = "OPTIONAL"; break;
            case (SSL_VERIFY_PEER |
                  SSL_VERIFY_FAIL_IF_NO_PEER_CERT) :
                 cptr = "REQUIRED"; break;
            case (SSL_VERIFY_PEER |
                  SSL_VERIFY_POST_HANDSHAKE) :
                 cptr = "OPTIONAL-POST_HANDSHAKE"; break;
            case (SSL_VERIFY_PEER |
                  SSL_VERIFY_FAIL_IF_NO_PEER_CERT |
                  SSL_VERIFY_POST_HANDSHAKE) :
                 cptr = "REQUIRED-POST_HANDSHAKE"; break;
            default : cptr = "unknown!";
         }
         WatchThis (WATCHITM(rqptr), WATCH_SESOLA,
                    "X509 client certificate VERIFY:!AZ", cptr);
         SesolaWatchErrors (rqptr);
      }

      if (version >= TLS1_3_VERSION)
      {
         if (SSL_verify_client_post_handshake (sesolaptr->SslPtr) != 1)
         {
            SesolaWatchErrors (rqptr);
            SesolaClientCert (rqptr, SESOLA_VERIFY_AST, NULL);
            return;
         }
      }
      else
      {
         /* <= TLS1_2_VERSION */
         SSL_set_session_id_context (sesolaptr->SslPtr, SesolaSessionId,
                                     SSL_MAX_SSL_SESSION_ID_LENGTH);
         SSL_renegotiate (sesolaptr->SslPtr);
      }

      sesolaptr->ReadClientCert = false;
      sesolaptr->X509CertRequested = true;
      sesolaptr->SslStateFunction = &SesolaClientCertGet;
   }

   for (sane = 16; sane; sane--)
   {
      /*************/
      /* handshake */
      /*************/

      /* allow for low-level SSL I/O here (e.g. link disconnection) */
      if (VMSnok(sesolaptr->ReadIOsb.Status)) break;
      if (VMSnok(sesolaptr->WriteIOsb.Status)) break;

      value = SSL_do_handshake (sesolaptr->SslPtr);

      if (WATCHING (rqptr, WATCH_SESOLA))
      {
         WatchThis (WATCHITM(rqptr), WATCH_SESOLA,
                    "SSL_do_handshake() !SL !SL",
                    value, SSL_get_error(sesolaptr->SslPtr,value));
         SesolaWatchErrors (rqptr);
      }

      /*****************/
      /* current state */
      /*****************/

      if (WATCHING (rqptr, WATCH_SESOLA))
          WatchThis (WATCHITM(rqptr), WATCH_SESOLA,
                     "STATE !UL \"!AZ\" !AZ cert:!&B",
                     SSL_get_state (sesolaptr->SslPtr),
                     SSL_state_string (sesolaptr->SslPtr),
                     SSL_state_string_long (sesolaptr->SslPtr),
                     sesolaptr->ReadClientCert);

      state = SSL_get_state (sesolaptr->SslPtr);

      if (state == TLS_ST_SR_CERT)
         sesolaptr->ReadClientCert = true;
      else
      if (state == TLS_ST_SR_CERT_VRFY)
         sesolaptr->ReadClientCert = true;
      else
      if (state == TLS_ST_OK)
         if (sesolaptr->ReadClientCert)
            break;

      /* tweak the client into providing data (per Apache mod_ssl) */
      SSL_peek (sesolaptr->SslPtr, buf, sizeof(buf));

      /* if non-blocking IO in progress just return and wait for delivery */
      if (sesolaptr->ReadInProgress ||
          sesolaptr->WriteInProgress)
      {
         /****************/
         /* wait for I/O */
         /****************/

         if (WATCHMOD (rqptr, WATCH_MOD_SESOLA))
             WatchThis (WATCHITM(rqptr), WATCH_MOD_SESOLA,
                        "READ:!&?yes\rno\r WRITE:!&?yes\rno\r",
                        sesolaptr->ReadInProgress, sesolaptr->WriteInProgress);
         return;
      }

      if (WATCHING (rqptr, WATCH_SESOLA))
         SesolaWatchErrors (rqptr);

      /* if the handshake was broken */
      if (value <= 0) break;
   }

   /************/
   /* finished */
   /************/

   if (!sane) ErrorNoticed (rqptr, SS$_BUGCHECK, NULL, FI_LI);

   if (WATCHING (rqptr, WATCH_SESOLA))
      SesolaWatchSession (sesolaptr);

   sesolaptr->ReadClientCert = false;
   sesolaptr->SslStateFunction = NULL;
   SesolaClientCert (rqptr, SESOLA_VERIFY_AST, NULL);
}

/*****************************************************************************/
/*
Request data being sent by the client (e.g. PROFIND/PUT/POST) will interfere
with renegotiation so read and buffer this (up to a reasonable quantity). 
Reinsert this into the application stream in SesolaNetIoRead().  Return -1 to
indicate an error, 0 that application data is still being read, or the number
of bytes in the application data (at the end of the read(s)).  Renegotiation
does not proceed until non-zero is returned.
*/

int SesolaClientRequestData (SESOLA_STRUCT *sesolaptr)

{
   int  size, value;
   uchar  *aptr;
   REQUEST_STRUCT  *rqptr;
   SESOLA_CONTEXT  *ctxptr;

   /*********/
   /* begin */
   /*********/

   rqptr = sesolaptr->RequestPtr;

   if (WATCHING (rqptr, WATCH_SESOLA))
      WatchThis (WATCHITM(rqptr), WATCH_SESOLA,
                 "BUFFER client data !UL !UL/!UL",
                 rqptr->rqHeader.ContentLength64,
                 sesolaptr->VerifyPeerDataCount,
                 sesolaptr->VerifyPeerDataSize);

   if (sesolaptr->VerifyPeerDataSize &&
       sesolaptr->VerifyPeerDataCount >= sesolaptr->VerifyPeerDataSize)
      return (sesolaptr->VerifyPeerDataCount);

   if (!sesolaptr->VerifyPeerDataSize)
   {
      /* per-service data max falling back to global data max */
      ctxptr = (SESOLA_CONTEXT*)rqptr->ServicePtr->SSLserverPtr;
      if (!(size = ctxptr->VerifyDataMax)) size = SesolaVerifyPeerDataMax;

      value = rqptr->rqHeader.ContentLength64;
      if (value > size)
      {
         if (WATCHING (rqptr, WATCH_SESOLA))
            WatchThis (WATCHITM(rqptr), WATCH_SESOLA,
                       "X509 RENEGOTIATE request data exceeds !UL kBytes",
                       size / 1024);
         return (sesolaptr->VerifyPeerDataCount = -1);
      }

      sesolaptr->VerifyPeerDataSize = value;
      /* global memory used (SesolaNetRead() is request-agnostic) */
      sesolaptr->VerifyPeerDataPtr = sesolaptr->VerifyPeerReadPtr =
         aptr = VmGet (value);
   }

   while (sesolaptr->VerifyPeerDataCount < sesolaptr->VerifyPeerDataSize)
   {
      size = sesolaptr->VerifyPeerDataSize - sesolaptr->VerifyPeerDataCount;
      aptr = sesolaptr->VerifyPeerDataPtr + sesolaptr->VerifyPeerDataCount;

      value = SSL_read (sesolaptr->SslPtr, aptr, size);

      if (WATCHMOD (rqptr, WATCH_MOD_SESOLA))
      {
         WatchThis (WATCHITM(rqptr), WATCH_MOD_SESOLA,
                    "SSL_read() !SL !SL",
                    value, SSL_get_error(sesolaptr->SslPtr,value));
         SesolaWatchErrors (rqptr);
      }

      /* if non-blocking IO in progress just return and wait for delivery */
      if (sesolaptr->ReadInProgress ||
          sesolaptr->WriteInProgress)
      {
         if (WATCHMOD (rqptr, WATCH_MOD_SESOLA))
            WatchThis (WATCHITM(rqptr), WATCH_MOD_SESOLA,
                       "READ:!&?yes\rno\r WRITE:!&?yes\rno\r",
                       sesolaptr->ReadInProgress, sesolaptr->WriteInProgress);
         return (0);
      }

      if (value <= 0)
      {
         if (WATCHING (rqptr, WATCH_SESOLA))
            WatchThis (WATCHITM(rqptr), WATCH_SESOLA,
                       "X509 RENEGOTIATE request data read FAILURE");
         SesolaWatchErrors (rqptr);
         return (sesolaptr->VerifyPeerDataCount = -1);
      }

      sesolaptr->VerifyPeerDataCount += value;

      if (WATCHMOD (rqptr, WATCH_MOD_SESOLA))
         WatchThis (WATCHITM(rqptr), WATCH_MOD_SESOLA,
                    "content:!UL/!UL !UL/!UL", value, size,
                    sesolaptr->VerifyPeerDataCount,
                    sesolaptr->VerifyPeerDataSize);
   }

   return (sesolaptr->VerifyPeerDataCount);
}

/*****************************************************************************/
/*
MetaConEvaluate() has hit an "X509:" conditional.  Process this and return true
if the conditional is met, false if not met.  The conditional can be empty in
which case the availability of an X509 client certificate is tested, returning
true if one is, false if not.  The conditional can also supply a keyword,
equate symbol, and optional wildcard or regex.  The keyword is progressively
searched for in the client certificate subject, certificate name and then in
the certificate extensions (in much the same way as authorisation X509).  If a
keyword has no parameter then the directive just tests for the presence of the
keyword in the certificate.  A parameter is a wildcard or regex which is
matched against the content corresponding to the keyword, returning true if it
matches, false if it does not.
*/
                                    
BOOL SesolaClientCertMetaCon
(
REQUEST_STRUCT *rqptr,
METACON_LINE *mclptr,
int WatchThisOne
)
{
   BOOL  result;
   char  *cptr, *sptr, *zptr;
   char  keyword [256];
   SESOLA_STRUCT  *sesolaptr;

   /*********/
   /* begin */
   /*********/

   if (WATCHMOD (rqptr, WATCH_MOD_SESOLA))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_SESOLA,
                 "SesolaClientCertMetaCon() !AZ", mclptr->BufferPtr);

   sesolaptr = SesolaRequestPtr (rqptr);

   if (sesolaptr == NULL)
   {
      /* not SSL service so X509 certificate impossible - test with "ssl:" */
      if (WatchThisOne) WatchDataFormatted ("X509 requires an SSL service!!\n");
      return (false);
   }

   /* parse the keyword */
   cptr = mclptr->BufferPtr;
   zptr = (sptr = keyword) + sizeof(keyword)-1;
   while (*cptr && *cptr != '=' && sptr < zptr) *sptr++ = *cptr++; 
   *sptr = '\0';
   while (*cptr && *cptr != '=') cptr++;
   /* |cptr| now points to any keyword parameter (wildcard or regex) */
   if (*cptr == '=') cptr++;

   if (sesolaptr->X509CertRequested)
   {
      /* has been requested (at SSL connect or previous renegotiation) */
      rqptr->X509ClientCertMeta = false;
      if (sesolaptr->ClientCertPtr == NULL)
      {
         /* no certificate supplied - if just testing for one */
         if (!keyword[0]) return (false);
         /* wanting to match to X509 certificate (i.e. keyword supplied) */
         return (false);
      }
   }
   else
   {
      /* certificate has not (yet) been requested (via renegotiation) */
      rqptr->X509ClientCertMeta = true;
      return (false);
   }

   /* if no keyword then just testing for an X509 certificate */
   if (keyword[0]) return (true);

   /* search for the keyword */
   sptr = SesolaCertParseDn (rqptr->rqAuth.ClientCertSubjectPtr, keyword);
   if (!sptr)
      sptr = SesolaCertName (sesolaptr->ClientCertPtr, keyword);
   if (!sptr)
      sptr = SesolaCertExtension (sesolaptr->ClientCertPtr, keyword);

   /* if keyword not found */
   if (!sptr)
   {
      if (WatchThisOne) WatchDataFormatted ("Keyword \"!AZ\" not found.\n");
      return (false);
   }

   /* if keyword has no parameter */
   if (!*cptr) return (true);

   result = StringMatchAndRegex (rqptr, sptr, cptr,
                                 SMATCH_GREEDY_REGEX,
                                 mclptr->RegexPregPtr,
                                 NULL);
   return (result);
}

/*****************************************************************************/
/*
Return a pointer to a null-terminated string containing the common name from
any client certificate present.
*/

char* SesolaClientCertRemoteUser (REQUEST_STRUCT *rqptr)

{
   char  *bptr, *cptr, *sptr;
   SESOLA_STRUCT  *sesolaptr;

   /*********/
   /* begin */
   /*********/

   if (WATCHMOD (rqptr, WATCH_MOD_SESOLA))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_SESOLA,
                 "SesolaClientCertRemoteUser()");

   sesolaptr = SesolaRequestPtr (rqptr);

   if (!sesolaptr) return (NULL);

   if (!sesolaptr->ClientCertPtr) return (NULL);

   cptr = SesolaCertParseDn (rqptr->rqAuth.ClientCertSubjectPtr, "/CN=");
   if (!cptr) return (cptr);

   for (sptr = (cptr += 4); *sptr; sptr++);
   bptr = sptr = VmGetHeap (rqptr, sptr-cptr+1);
   while (*cptr) *sptr++ = *cptr++;
   *sptr = '\0';

   return (bptr);
}

/*****************************************************************************/
/*
For compilations without SSL these functions provide LINKage stubs for the
rest of the HTTPd modules, allowing for just recompiling the Sesola module to
integrate the SSL functionality.
*/

/*********************/
#else  /* not SESOLA */
/*********************/

extern char  ErrorSanityCheck[];

void SesolaClientCertEnd (REQUEST_STRUCT *rqptr)
{
   /* always called by RequestEnd(); empty function; just return */
}

SesolaClientCertGet (void *sesolaptr)
{
   ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI);
}

char* SesolaClientCertRemoteUser (void *sesolaptr)
{
   ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI);
}

BOOL SesolaClientCertMetaCon
(
REQUEST_STRUCT *rqptr,
METACON_LINE *mclptr,
int WatchThisOne
)
{
   ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI);
}

/************************/
#endif  /* ifdef SESOLA */
/************************/

/*****************************************************************************/

