/*****************************************************************************/
/*
                               SesolaCGI.c

Secure Sockets Layer CGI variables.


VERSION HISTORY
---------------
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 "SESOLACGI"

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

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

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

extern char  ErrorSanityCheck[],
             SoftwareID[];

extern BIO_METHOD  *SesolaBioMemPtr;

extern WATCH_STRUCT  Watch;

/*****************************************************************************/
/*
Generate appropriate SSL-related CGI variables.
Called from CgiGenerateVariables().
*/ 

int SesolaCgiGenerateVariables
(
REQUEST_STRUCT *rqptr,
int VarType
)
{
   static $DESCRIPTOR (NumberFaoDsc, "!UL\0");

   int  status,
        KeySize;
   char  String [512];
   char  *cptr;
   $DESCRIPTOR (StringDsc, String);
   SESOLA_STRUCT  *sesolaptr;
   SSL  *SslPtr;
   SSL_CIPHER  *CipherPtr;
   X509  *ServerCertPtr;

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

   if (WATCHING(rqptr) && (WATCH_MODULE(WATCH_MOD_SESOLA)))
      WatchThis (rqptr, FI_LI, WATCH_MOD_SESOLA,
                 "SesolaCgiGenerateVariables() !UL", VarType);

   if (!rqptr->rqNet.SesolaPtr) return (SS$_NORMAL);

   sesolaptr = (SESOLA_STRUCT*)rqptr->rqNet.SesolaPtr;
   SslPtr = sesolaptr->SslPtr;
   CipherPtr = SSL_get_current_cipher (SslPtr);
   ServerCertPtr = SSL_get_certificate (SslPtr);

   if (sesolaptr->ClientCertPtr)
   {
      if (rqptr->rqAuth.ClientCertFingerprintPtr &&
         rqptr->rqAuth.ClientCertFingerprintPtr[0])
      {
         if (VMSnok (status =
             CgiVariable (rqptr, "AUTH_X509_FINGERPRINT",
                          rqptr->rqAuth.ClientCertFingerprintPtr, VarType)))
            return (status);
      }

      if (rqptr->rqAuth.ClientCertIssuerPtr &&
          rqptr->rqAuth.ClientCertIssuerPtr[0])
      {
         if (VMSnok (status =
             CgiVariable (rqptr, "AUTH_X509_ISSUER",
                          rqptr->rqAuth.ClientCertIssuerPtr, VarType)))
            return (status);
      }
   
      if (rqptr->rqAuth.ClientCertSubjectPtr &&
          rqptr->rqAuth.ClientCertSubjectPtr[0])
      {
         if (VMSnok (status =
             CgiVariable (rqptr, "AUTH_X509_SUBJECT",
                          rqptr->rqAuth.ClientCertSubjectPtr, VarType)))
            return (status);
      }

      cptr = (char*)SSL_CIPHER_get_name (CipherPtr);
      if (VMSnok (status =
          CgiVariable (rqptr, "AUTH_X509_CIPHER", cptr, VarType)))
         return (status);

      KeySize = SSL_CIPHER_get_bits (CipherPtr, NULL);
      sys$fao (&NumberFaoDsc, 0, &StringDsc, KeySize);
      if (VMSnok (status =
          CgiVariable (rqptr, "AUTH_X509_KEYSIZE", String, VarType)))
         return (status);
   }

   switch (rqptr->rqPathSet.SSLCGIvar)
   {
      case SESOLA_CGI_VAR_APACHE_MOD_SSL :
         return (SesolaCgiVariablesApacheModSsl (rqptr, VarType));
      case SESOLA_CGI_VAR_PURVEYOR :
         return (SesolaCgiVariablesPurveyor (rqptr, VarType));
      default :
         return (SS$_NORMAL);
   }
}

/*****************************************************************************/
/*
Generate some Apache mod_SSL style CGI variables.
*/ 

int SesolaCgiVariablesApacheModSsl
(
REQUEST_STRUCT *rqptr,
int VarType
)
{
   static char  HexDigits [] = "0123456789abcdef";
   static $DESCRIPTOR (NumberFaoDsc, "!UL\0");

   int  cnt, status,
        AlgKeySize,
        ObjNid,
        UseKeySize;
   char  *cptr, *sptr, *zptr,
         *CurrentRecordPtr;
   char  CertFingerprint [64],
         String [512],
         VariableName [64],
         VariableValue [256];
   $DESCRIPTOR (StringDsc, String);
   $DESCRIPTOR (VariableNameDsc, VariableName);
   SESOLA_STRUCT  *sesolaptr;
   SSL  *SslPtr;
   SSL_CIPHER  *CipherPtr;
   SSL_SESSION  *SessionPtr;
   X509  *ClientCertPtr,
         *ServerCertPtr;

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

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

   sesolaptr = (SESOLA_STRUCT*)rqptr->rqNet.SesolaPtr;
   SslPtr = sesolaptr->SslPtr;
   SessionPtr = SSL_get_session (SslPtr);
   CipherPtr = SSL_get_current_cipher (SslPtr);
   ServerCertPtr = SSL_get_certificate (SslPtr);

   if (!SessionPtr || !CipherPtr || !ServerCertPtr)
   {
      if (WATCHING(rqptr) && (WATCH_MODULE(WATCH_MOD_SESOLA)))
         WatchThis (rqptr, FI_LI, WATCH_MOD_SESOLA,
                    "!&X !&X !&X", SessionPtr, CipherPtr, ServerCertPtr);
      ErrorNoticed (SS$_BUGCHECK, ErrorSanityCheck, FI_LI);
      return (SS$_BUGCHECK);
   }

   /***************/
   /* basic stuff */
   /***************/

/**
{
   int  dlen;
   unsigned char  I2dData [1024*10];
   unsigned char  *i2dptr;

   i2dptr = I2dData;
   dlen = i2d_SSL_SESSION (SessionPtr, &i2dptr);

   sprintf (String, "%d", dlen);
   if (VMSnok (status =
       CgiVariable (rqptr, "SSL_SESSION", String, VarType)))
      return (status);
}
**/

   if (VMSnok (status =
       CgiVariable (rqptr, "HTTPS", "true", VarType)))
      return (status);

   cptr = (char*)SSL_get_version (SslPtr);
   if (VMSnok (status =
       CgiVariable (rqptr, "SSL_PROTOCOL", cptr, VarType)))
      return (status);

   zptr = (sptr = String) + sizeof(String)-1;
   for (cnt = 0; cnt < SessionPtr->session_id_length; cnt++)
   {
      if (sptr >= zptr) break;
      *sptr++ = HexDigits[(SessionPtr->session_id[cnt] >> 4) & 0x0f];
      if (sptr >= zptr) break;
      *sptr++ = HexDigits[SessionPtr->session_id[cnt] & 0x0f];
   }
   *sptr = '\0';
   if (VMSnok (status =
       CgiVariable (rqptr, "SSL_SESSION_ID", String, VarType)))
      return (status);

   /* data structure in [.SSL]SSL.H */
   if (VMSnok (status =
       CgiVariable (rqptr, "SSL_CIPHER", (char*)CipherPtr->name, VarType)))
      return (status);

   UseKeySize = SSL_CIPHER_get_bits(CipherPtr, &AlgKeySize);

   /* same as "mod_SSL" v2.5 */
   if (UseKeySize < 56)
      if (VMSnok (status =
          CgiVariable (rqptr, "SSL_CIPHER_EXPORT", "true", VarType)))
         return (status);

   sys$fao (&NumberFaoDsc, 0, &StringDsc, UseKeySize);
   if (VMSnok (status =
       CgiVariable (rqptr, "SSL_CIPHER_USEKEYSIZE", String, VarType)))
      return (status);

   sys$fao (&NumberFaoDsc, 0, &StringDsc, AlgKeySize);
   if (VMSnok (status =
       CgiVariable (rqptr, "SSL_CIPHER_ALGKEYSIZE", String, VarType)))
      return (status);

   if (VMSnok (status =
       CgiVariable (rqptr, "SSL_VERSION_INTERFACE",
                            APACHE_MOD_SSL_VERSION_INTERFACE, VarType)))
      return (status);

   if (VMSnok (status =
       CgiVariable (rqptr, "SSL_VERSION_LIBRARY",
                    (char*)SSLeay_version(SSLEAY_VERSION), VarType)))
      return (status);

   /****************/
   /* client stuff */
   /****************/

   if (ClientCertPtr = sesolaptr->ClientCertPtr)
   {
      sys$fao (&NumberFaoDsc, 0, &StringDsc, X509_get_version(ClientCertPtr)+1);
      if (VMSnok (status =
          CgiVariable (rqptr, "SSL_CLIENT_M_VERSION", String, VarType)))
         return (status);

      i2a_ASN1_INTEGER (SesolaBioMemPtr, X509_get_serialNumber(ClientCertPtr));
      BIO_gets (SesolaBioMemPtr, String, sizeof(String));
      if (VMSnok (status =
          CgiVariable (rqptr, "SSL_CLIENT_M_SERIAL", String, VarType)))
         return (status);

      X509_NAME_oneline (X509_get_subject_name(ClientCertPtr),
                         String, sizeof(String));
      if (VMSnok (status =
          CgiVariable (rqptr, "SSL_CLIENT_S_DN", String, VarType)))
         return (status);

      sptr = VariableName;
      for (cptr = "SSL_CLIENT_S_DN_"; *cptr; *sptr++ = *cptr++);
      for (;;)
      {
         cptr = SesolaParseCertDn (String, NULL);
         if (!cptr) break;
         if (*cptr == '/') cptr++;
         zptr = (sptr = VariableName+16) + sizeof(VariableName)-17;
         while (*cptr && *cptr != '=' && sptr < zptr) *sptr++ = *cptr++;
         if (!*cptr) break;
         cptr++;
         *sptr = '\0';
         zptr = (sptr = VariableValue) + sizeof(VariableValue)-1;
         while (*cptr && sptr < zptr) *sptr++ = *cptr++;
         *sptr = '\0';
         if (VMSnok (status =
             CgiVariable (rqptr, VariableName, VariableValue, VarType)))
            return (status);
      }

      X509_NAME_oneline (X509_get_issuer_name(ClientCertPtr),
                         String, sizeof(String));
      if (VMSnok (status =
          CgiVariable (rqptr, "SSL_CLIENT_I_DN", String, VarType)))
         return (status);

      sptr = VariableName;
      for (cptr = "SSL_CLIENT_I_DN_"; *cptr; *sptr++ = *cptr++);
      for (;;)
      {
         cptr = SesolaParseCertDn (String, NULL);
         if (!cptr) break;
         if (*cptr == '/') cptr++;
         zptr = (sptr = VariableName+16) + sizeof(VariableName)-17;
         while (*cptr && *cptr != '=' && sptr < zptr) *sptr++ = *cptr++;
         if (!*cptr) break;
         cptr++;
         *sptr = '\0';
         zptr = (sptr = VariableValue) + sizeof(VariableValue)-1;
         while (*cptr && sptr < zptr) *sptr++ = *cptr++;
         *sptr = '\0';
         if (VMSnok (status =
             CgiVariable (rqptr, VariableName, VariableValue, VarType)))
            return (status);
      }

      ASN1_UTCTIME_print (SesolaBioMemPtr, X509_get_notBefore(ClientCertPtr));
      BIO_gets (SesolaBioMemPtr, String, sizeof(String));
      if (VMSnok (status =
          CgiVariable (rqptr, "SSL_CLIENT_V_START", String, VarType)))
         return (status);

      ASN1_UTCTIME_print (SesolaBioMemPtr, X509_get_notAfter(ClientCertPtr));
      BIO_gets (SesolaBioMemPtr, String, sizeof(String));
      if (VMSnok (status =
          CgiVariable (rqptr, "SSL_CLIENT_V_END", String, VarType)))
         return (status);

      ObjNid = OBJ_obj2nid (ClientCertPtr->cert_info->signature->algorithm);
      if (VMSnok (status =
          CgiVariable (rqptr, "SSL_CLIENT_A_SIG", 
             ObjNid == NID_undef ? "UNKNOWN" : (char*)OBJ_nid2ln(ObjNid),
             VarType)))
         return (status);

      ObjNid = OBJ_obj2nid (ClientCertPtr->cert_info->key->algor->algorithm);
      if (VMSnok (status =
          CgiVariable (rqptr, "SSL_CLIENT_A_KEY",
             ObjNid == NID_undef ? "UNKNOWN" : (char*)OBJ_nid2ln(ObjNid),
             VarType)))
         return (status);
   }

#if APACHE_MOD_SSL_SERVER_CERT
   /*
      This variable is larger than can be support in a DCL symbol.
      It can be support in a CGIplus variable stream, but I imagine
      has a limited range of uses and so is not generated by default.
   */
   if (VarType == CGI_VARIABLE_STREAM)
   {
      PEM_write_bio_X509(SesolaBioMemPtr, ClientCertPtr);
      BIO_read (SesolaBioMemPtr, String, sizeof(String));
      if (VMSnok (status =
          CgiVariable (rqptr, "SSL_CLIENT_CERT", String, VarType)))
         return (status);
   }
#endif

   /****************/
   /* server stuff */
   /****************/

   sys$fao (&NumberFaoDsc, 0, &StringDsc, X509_get_version(ServerCertPtr)+1);
   if (VMSnok (status =
       CgiVariable (rqptr, "SSL_SERVER_M_VERSION", String, VarType)))
      return (status);

   i2a_ASN1_INTEGER (SesolaBioMemPtr, X509_get_serialNumber(ServerCertPtr));
   BIO_gets (SesolaBioMemPtr, String, sizeof(String));
   if (VMSnok (status =
       CgiVariable (rqptr, "SSL_SERVER_M_SERIAL", String, VarType)))
      return (status);

   X509_NAME_oneline (X509_get_subject_name(ServerCertPtr),
                      String, sizeof(String));
   if (VMSnok (status =
       CgiVariable (rqptr, "SSL_SERVER_S_DN", String, VarType)))
      return (status);

   sptr = VariableName;
   for (cptr = "SSL_SERVER_S_DN_"; *cptr; *sptr++ = *cptr++);
   for (;;)
   {
      cptr = SesolaParseCertDn (String, NULL);
      if (!cptr) break;
      if (*cptr == '/') cptr++;
      zptr = (sptr = VariableName+16) + sizeof(VariableName)-17;
      while (*cptr && *cptr != '=' && sptr < zptr) *sptr++ = *cptr++;
      if (!*cptr) break;
      cptr++;
      *sptr = '\0';
      zptr = (sptr = VariableValue) + sizeof(VariableValue)-1;
      while (*cptr && sptr < zptr) *sptr++ = *cptr++;
      *sptr = '\0';
      if (VMSnok (status =
          CgiVariable (rqptr, VariableName, VariableValue, VarType)))
         return (status);
   }

   X509_NAME_oneline (X509_get_issuer_name(ServerCertPtr),
                      String, sizeof(String));
   if (VMSnok (status =
       CgiVariable (rqptr, "SSL_SERVER_I_DN", String, VarType)))
      return (status);

   sptr = VariableName;
   for (cptr = "SSL_SERVER_I_DN_"; *cptr; *sptr++ = *cptr++);
   for (;;)
   {
      cptr = SesolaParseCertDn (String, NULL);
      if (!cptr) break;
      if (*cptr == '/') cptr++;
      zptr = (sptr = VariableName+16) + sizeof(VariableName)-17;
      while (*cptr && *cptr != '=' && sptr < zptr) *sptr++ = *cptr++;
      if (!*cptr) break;
      cptr++;
      *sptr = '\0';
      zptr = (sptr = VariableValue) + sizeof(VariableValue)-1;
      while (*cptr && sptr < zptr) *sptr++ = *cptr++;
      *sptr = '\0';
      if (VMSnok (status =
          CgiVariable (rqptr, VariableName, VariableValue, VarType)))
         return (status);
   }

   ASN1_UTCTIME_print (SesolaBioMemPtr, X509_get_notBefore(ServerCertPtr));
   BIO_gets (SesolaBioMemPtr, String, sizeof(String));
   if (VMSnok (status =
       CgiVariable (rqptr, "SSL_SERVER_V_START", String, VarType)))
      return (status);

   ASN1_UTCTIME_print (SesolaBioMemPtr, X509_get_notAfter(ServerCertPtr));
   BIO_gets (SesolaBioMemPtr, String, sizeof(String));
   if (VMSnok (status =
       CgiVariable (rqptr, "SSL_SERVER_V_END", String, VarType)))
      return (status);

   ObjNid = OBJ_obj2nid (ServerCertPtr->cert_info->signature->algorithm);
   if (VMSnok (status =
       CgiVariable (rqptr, "SSL_SERVER_A_SIG", 
          ObjNid == NID_undef ? "UNKNOWN" : (char*)OBJ_nid2ln(ObjNid),
          VarType)))
      return (status);

   ObjNid = OBJ_obj2nid (ServerCertPtr->cert_info->key->algor->algorithm);
   if (VMSnok (status =
       CgiVariable (rqptr, "SSL_SERVER_A_KEY",
          ObjNid == NID_undef ? "UNKNOWN" : (char*)OBJ_nid2ln(ObjNid),
          VarType)))
      return (status);

#if APACHE_MOD_SSL_SERVER_CERT
   /*
      This variable is larger than can be support in a DCL symbol.
      It can be support in a CGIplus variable stream, but I imagine
      has a limited range of uses and so is not generated by default.
   */
   if (VarType == CGI_VARIABLE_STREAM)
   {
      PEM_write_bio_X509(SesolaBioMemPtr, ServerCertPtr);
      BIO_read (SesolaBioMemPtr, String, sizeof(String));
      if (VMSnok (status =
          CgiVariable (rqptr, "SSL_SERVER_CERT", String, VarType)))
         return (status);
   }
#endif

   return (SS$_NORMAL);
}

/*****************************************************************************/
/*
Generate some Purveyor style SSL-related CGI variables.
*/ 

int SesolaCgiVariablesPurveyor
(
REQUEST_STRUCT *rqptr,
int VarType
)
{
   static $DESCRIPTOR (NumberFaoDsc, "!UL\0");

   int  status,
        KeySize;
   char  String [512];
   char  *cptr;
   $DESCRIPTOR (StringDsc, String);
   SESOLA_STRUCT  *sesolaptr;
   SSL  *SslPtr;
   SSL_CIPHER  *CipherPtr;
   SSL_SESSION  *SessionPtr;
   X509  *ServerCertPtr;

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

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

   sesolaptr = (SESOLA_STRUCT*)rqptr->rqNet.SesolaPtr;
   SslPtr = sesolaptr->SslPtr;
   SessionPtr = SSL_get_session (SslPtr);
   CipherPtr = SSL_get_current_cipher (SslPtr);
   ServerCertPtr = SSL_get_certificate (SslPtr);

   if (!SessionPtr || !CipherPtr || !ServerCertPtr)
   {
      if (WATCHING(rqptr) && (WATCH_MODULE(WATCH_MOD_SESOLA)))
         WatchThis (rqptr, FI_LI, WATCH_MOD_SESOLA,
                    "!&X !&X !&X", SessionPtr, CipherPtr, ServerCertPtr);
      ErrorNoticed (SS$_BUGCHECK, ErrorSanityCheck, FI_LI);
      return (SS$_BUGCHECK);
   }

   cptr = (char*)SSL_CIPHER_get_name (CipherPtr);
   if (VMSnok (status =
       CgiVariable (rqptr, "SSL_CIPHER", cptr, VarType)))
      return (status);

   KeySize = SSL_CIPHER_get_bits (CipherPtr, NULL);
   sys$fao (&NumberFaoDsc, 0, &StringDsc, KeySize);
   if (VMSnok (status =
       CgiVariable (rqptr, "SSL_CIPHER_KEYSIZE", String, VarType)))
      return (status);

   if (!sesolaptr->ClientCertPtr)
   {
      if (VMSnok (status =
         CgiVariable (rqptr, "SSL_CLIENT_CA", "NONE", VarType)))
         return (status);

      if (VMSnok (status =
          CgiVariable (rqptr, "SSL_CLIENT_DN", "NONE", VarType)))
         return (status);
   }
   else
   {
      X509_NAME_oneline (X509_get_issuer_name(sesolaptr->ClientCertPtr),
                         String, sizeof(String));
      if (VMSnok (status =
          CgiVariable (rqptr, "SSL_CLIENT_CA", String, VarType)))
         return (status);

      X509_NAME_oneline (X509_get_subject_name(sesolaptr->ClientCertPtr),
                         String, sizeof(String));
      if (VMSnok (status =
          CgiVariable (rqptr, "SSL_CLIENT_DN", String, VarType)))
         return (status);
   }

   if (rqptr->RemoteUser[0] && rqptr->rqAuth.SourceRealm == AUTH_SOURCE_X509)
      cptr = "TRUE";
   else
      cptr = "FALSE";
   if (VMSnok (status =
       CgiVariable (rqptr, "SSL_CLIENT_AUTHENTICATED", cptr, VarType)))
      return (status);

   if (VMSnok (status =
       CgiVariable (rqptr, "SECURITY_STATUS", "SSL", VarType)))
      return (status);

   X509_NAME_oneline (X509_get_issuer_name(ServerCertPtr),
                      String, sizeof(String));
   if (VMSnok (status =
       CgiVariable (rqptr, "SSL_SERVER_CA", String, VarType)))
      return (status);

   X509_NAME_oneline (X509_get_subject_name(ServerCertPtr),
                      String, sizeof(String));
   if (VMSnok (status =
       CgiVariable (rqptr, "SSL_SERVER_DN", String, VarType)))
      return (status);

   cptr = (char*)SSL_get_version (SslPtr);
   if (VMSnok (status =
       CgiVariable (rqptr, "SSL_VERSION", cptr, VarType)))
      return (status);

   return (SS$_NORMAL);
}

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

/* external storage */
extern WATCH_STRUCT  Watch;

SesolaCgiGenerateVariables
(
REQUEST_STRUCT *rqptr,
int VarType
)
{
   int  status;

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

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

   if (VMSnok (status =
       CgiVariable (rqptr, "SECURITY_STATUS", "NONE", VarType)))
      return (status);

   return (SS$_NORMAL);
}

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

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

