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

Secure Sockets Layer client (peer) certificate.


VERSION HISTORY
---------------
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_V6
#undef _VMS_V6_SOURCE
#define _VMS_V6_SOURCE
#undef __VMS_VER
#define __VMS_VER 60000000
#undef __CRTL_VER
#define __CRTL_VER 60000000
#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 */
/********************/

#ifdef DBUG
extern BOOL Debug;
#else
#define Debug 0 
#endif

extern int  ExitStatus,
            OpcomMessages;

extern char  ErrorSanityCheck[],
             ServerHostPort[],
             SoftwareID[],
             Utility[];

extern BIO_METHOD  *SesolaBioMemPtr;

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->rqNet.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_NEVER     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  status, number, value,
        SessionHits,
        SessionTimeout,
        SessionTimeCSec,
        SessionTimeoutCSec,
        VerifyMode,
        WatchThisType;
   char  *cptr, *sptr, *zptr,
         *CurrentPtr,
         *DigestTypePtr;
   char  CertFingerprintMD5 [64],
         CertFingerprintSHA1 [64],
         CertIssuer [512],
         CertSubject [512],
         String [256],
         TimeString [32],
         TimeoutString [32],
         UserDetails [256];
   SESOLA_STRUCT  *sesolaptr;
   SSL_SESSION  *SessionPtr;
   struct tm  *tmptr;

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

   if (WATCHING(rqptr) && (WATCH_MODULE(WATCH_MOD_SESOLA)))
      WatchThis (rqptr, FI_LI, WATCH_MOD_SESOLA,
                 "SesolaClientCert() !&A !UL", AstFunction, VerifyParam);

   sesolaptr = (SESOLA_STRUCT*)rqptr->rqNet.SesolaPtr;

   /* network errors? */
   if (VMSnok (sesolaptr->ReadIOsb.Status))
   {
      SesolaNetRequestEnd (rqptr);
      return (sesolaptr->ReadIOsb.Status);
   }
   if (VMSnok (sesolaptr->WriteIOsb.Status))
   {
      SesolaNetRequestEnd (rqptr);
      return (sesolaptr->WriteIOsb.Status);
   }

   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;

   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 == '\\') cptr++;
               if (*cptr) cptr++;
            }
            if (!*cptr) break;
            switch (*(unsigned long*)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 '[lt:' :
               case '[LT:' :

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

                  cptr += sizeof(unsigned long);
                  if (*(unsigned long*)cptr == 'expi' ||
                      *(unsigned long*)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)
                     *sptr++ = *cptr++;
                  *sptr = '\0';
                  break;

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

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

                  cptr += sizeof(unsigned long);
                  if (*(unsigned long*)cptr == 'expi' ||
                      *(unsigned long*)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 (*(unsigned long*)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;
                  }
                  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)
      sesolaptr->ClientCertPtr = NULL;
   else
   if (sesolaptr->CertVerifyMode == SESOLA_VERIFY_PEER_NONE)
      sesolaptr->ClientCertPtr = NULL;
   else
      sesolaptr->ClientCertPtr = SSL_get_peer_certificate (sesolaptr->SslPtr);
   if (Debug) fprintf (stdout, "ClientCertPtr: %d\n", sesolaptr->ClientCertPtr);

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

      if (VerifyParam == SESOLA_VERIFY_AST)
      {
         /* call from SesolaClientCertRenegotiate(), 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;

      if (Debug) fprintf (stdout, "VerifyMode: %%x%04x\n", VerifyMode);
      SSL_set_verify (sesolaptr->SslPtr, VerifyMode,
                      &SesolaCertVerifyCallback);

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

      SesolaClientCertRenegotiate (rqptr);

      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;
      }

      return (SESOLA_VERIFY_PEER_PENDING);
   }

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

   WatchThisType = 0;
   if (WATCH_CAT && WATCHING(rqptr))
   {
      if WATCH_CATEGORY(WATCH_SESOLA)
         WatchThisType =  WATCH_SESOLA;
      else
      if WATCH_CATEGORY(WATCH_AUTH)
         WatchThisType = WATCH_AUTH;
   }

   if (WatchThisType ||
       sesolaptr->CertVerifyMode == SESOLA_VERIFY_PEER_AUTH)
   {
      X509_NAME_oneline (X509_get_issuer_name(sesolaptr->ClientCertPtr),
                         CertIssuer, sizeof(CertIssuer));
      if (Debug) fprintf (stdout, "issuer |%s|\n", CertIssuer);

      X509_NAME_oneline (X509_get_subject_name(sesolaptr->ClientCertPtr),
                         CertSubject, sizeof(CertSubject));
      if (Debug) fprintf (stdout, "subject |%s|\n", CertSubject);

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

   if (WATCH_CAT && WatchThisType)
   {
      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))
         strcpy (TimeString, "strftime() error");
      tmptr = localtime (&SessionTimeoutCSec);
      if (!strftime (TimeoutString, sizeof(TimeoutString),
                     "%b %d %T %Y", tmptr))
         strcpy (TimeoutString, "strftime() error");
      SessionHits = SSL_CTX_sess_hits (sesolaptr->SslCtx);

      WatchThis (rqptr, FI_LI, WatchThisType,
                 "X509 client certificate !AZ",
                 VerifyParam == SESOLA_VERIFY_AST ?
                    "RENEGOTIATED" : "CACHED");
      WatchDataFormatted ("issuer: !AZ\n", CertIssuer);
      WatchDataFormatted ("subject: !AZ\n", CertSubject);
      WatchDataFormatted ("fingerprint: !AZ (MD5)\n", CertFingerprintMD5);
      WatchDataFormatted ("             !AZ (SHA1)\n", CertFingerprintSHA1);
      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 SesolaClientCertRenegotiate() */
         SysDclAst (sesolaptr->ClientCertAstFunction, rqptr);
      }
      return (SS$_NORMAL);
   }

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

   if (Debug)
      fprintf (stdout, "SSL_get_verify_result() %d\n",
               SSL_get_verify_result(sesolaptr->SslPtr));

   if (sesolaptr->X509optionalNoCa)
      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 SesolaClientCertRenegotiate() */
         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 SesolaClientCertRenegotiate() */
         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);
   strcpy (rqptr->rqAuth.ClientCertIssuerPtr, CertIssuer);

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

   /* 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 SesolaClientCertRenegotiate() */
            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 = SesolaParseCertDn (rqptr->rqAuth.ClientCertSubjectPtr, "/CN=");
   if (cptr)
   {
      while (*cptr && *cptr != '=') cptr++;
      if (*cptr) cptr++;
      while (*cptr && sptr < zptr) *sptr++ = *cptr++;
      if (sptr < zptr) *sptr++ = ',';
      if (sptr < zptr) *sptr++ = ' ';
   }
   cptr = SesolaParseCertDn (rqptr->rqAuth.ClientCertSubjectPtr, "/Email=");
   if (!cptr)
      cptr = SesolaParseCertDn (rqptr->rqAuth.ClientCertSubjectPtr,
                                "/emailAddress=");
   if (cptr)
   {
      while (*cptr && *cptr != '=') cptr++;
      if (*cptr) cptr++;
      while (*cptr && sptr < zptr) *sptr++ = *cptr++;
   }
   *sptr = '\0';
   if (Debug) fprintf (stdout, "UserDetails |%s|\n", UserDetails);
   rqptr->rqAuth.UserDetailsLength = sptr - UserDetails;
   rqptr->rqAuth.UserDetailsPtr =
      VmGetHeap (rqptr, rqptr->rqAuth.UserDetailsLength+1);
   strcpy (rqptr->rqAuth.UserDetailsPtr, UserDetails);

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

   zptr = (sptr = rqptr->RemoteUser) + sizeof(rqptr->RemoteUser)-1;
   if (sesolaptr->X509RemoteUserDnRecord[0])
   {
      cptr = SesolaParseCertDn (rqptr->rqAuth.ClientCertSubjectPtr,
                                sesolaptr->X509RemoteUserDnRecord);
      if (cptr)
      {     
         while (*cptr && *cptr != '=') cptr++;
         if (*cptr) cptr++;
         while (*cptr && sptr < zptr)
         {
            /* convert white-space to underscores */
            if (!ISLWS(*cptr))
            {
               *sptr++ = *cptr++;
               continue;
            }
            *sptr++ = '_';
            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 = rqptr->RemoteUser;
   *sptr = '\0';
   rqptr->RemoteUserLength = sptr - rqptr->RemoteUser;
   if (rqptr->RemoteUserLength)
      strcpy (rqptr->RemoteUserPassword, "anystringwilldo");

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

/*****************************************************************************/
/*
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_REALM_PARAM_LENGTH+1];
   SESOLA_STRUCT  *sesolaptr;
   SSL_CIPHER  *CipherPtr;

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

   if (WATCHING(rqptr) && (WATCH_MODULE(WATCH_MOD_SESOLA)))
      WatchThis (rqptr, FI_LI, WATCH_MOD_SESOLA,
                 "SesolaClientCertConditional() !&Z\n", ConditionalPtr);

   sesolaptr = (SESOLA_STRUCT*)rqptr->rqNet.SesolaPtr;

   if (WATCHING(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 == '[' || *(unsigned short*)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 (*(unsigned short*)cptr)
      {
         case 'ci' :
         case 'CI' :

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

            ConditionalCount++;
            cptr += 3;
            while (*cptr && !ISLWS(*cptr) && *cptr != ']' && sptr < zptr)
            {
               if (*cptr == '\\') 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 == '\\') 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 (!(csptr = SesolaParseCertDn (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 == '\\') 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 == '\\') 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 = SesolaParseCertDn (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 == '\\') 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 (Debug)
      fprintf (stdout, "%d %d %d\n",
               ConditionalCount, NegateEntireConditional, SoFarSoGood);

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

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

   return (SoFarSoGood);
}

/*****************************************************************************/
/*
Initiate an SSL "renegotiate" (state 1) and then an SSL "accept" (state 2)
sequence to give the client an opportunity to supply a client certificate.
Due to the non-blocking I/O used by WASD this function will be called multiple
times to complete the SSL renegotiate/accept dialog.  Note that this can only
be called after the *entire* request (header and body) has been read from the
SSL stream.
*/

SesolaClientCertRenegotiate (REQUEST_STRUCT *rqptr)

{
   int  status,
        value,
        VerifyMode;
   char  *cptr;
   SESOLA_STRUCT  *sesolaptr;

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

   if (WATCHING(rqptr) && (WATCH_MODULE(WATCH_MOD_SESOLA)))
      WatchThis (rqptr, FI_LI, WATCH_MOD_SESOLA,
                 "SesolaClientCertRenegotiate()");

   sesolaptr = (SESOLA_STRUCT*)rqptr->rqNet.SesolaPtr;

   if (!sesolaptr->RenegotiateState)
   {
      if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_SESOLA))
      {
         VerifyMode = SSL_get_verify_mode (sesolaptr->SslPtr);
         switch (VerifyMode)
         {
            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;
            default : "unknown!";
         }
         WatchThis (rqptr, FI_LI, WATCH_SESOLA,
                    "X509 RENEGOTIATE client certificate verify:!AZ", cptr);
      }

      /* different session ID */
      SesolaSessionId (sesolaptr->SslPtr);

      sesolaptr->RenegotiateState = 1;
      SSL_renegotiate (sesolaptr->SslPtr);
   }

   sesolaptr->ReadSesolaFunction =
      sesolaptr->WriteSesolaFunction = &SesolaClientCertRenegotiate;
   if (Debug) fprintf (stdout, " %d\n", sesolaptr->RenegotiateState);

   if (Debug)
      fprintf (stdout, "SSL_get_state() %x\n",
               SSL_get_state (sesolaptr->SslPtr));

   for (;;)
   {
      value = SSL_do_handshake (sesolaptr->SslPtr);
      if (Debug) fprintf (stdout, "SSL_do_handshake() %d\n", value);

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

      if (value <= 0)
      {
         if (Debug) ERR_print_errors_fp (stdout);
         if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_SESOLA))
            WatchThis (rqptr, FI_LI, WATCH_SESOLA,
                       "X509 RENEGOTIATE client certificate FAILURE");

         sesolaptr->RenegotiateState = 0;
         sesolaptr->SslPtr->state = SSL_ST_OK;
         SesolaClientCert (rqptr, SESOLA_VERIFY_AST, NULL);
         return;
      }
      if (sesolaptr->RenegotiateState == 2)
      {
         sesolaptr->RenegotiateState = 0;
         break;
      }

      sesolaptr->RenegotiateState = 2;
      /* no SSL_set_state() and SSL_set_accept_state() does too much! */
      sesolaptr->SslPtr->state = SSL_ST_ACCEPT;
   }

   if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_SESOLA))
      SesolaWatchSession (sesolaptr);

   SesolaClientCert (rqptr, SESOLA_VERIFY_AST, NULL);
}

/*****************************************************************************/
/*
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 */
/*********************/

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

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

