/*****************************************************************************/
/*
                                 Sesola.c


    THE GNU GENERAL PUBLIC LICENSE APPLIES DOUBLY TO ANYTHING TO DO WITH
                    AUTHENTICATION AND AUTHORIZATION!

    This package is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; version 2 of the License, or any later
    version.

>   This package is distributed in the hope that it will be useful,
>   but WITHOUT ANY WARRANTY; without even the implied warranty of
>   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
>   GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.


GOOD ADVICE FROM THE README OF THE APACHE MOD_SSL SOURCE
--------------------------------------------------------
You should be very sensible when using cryptography software, because just
running an SSL server _DOES NOT_ mean your system is then secure!  This is for
a number of reasons. The following questions illustrate some of the problems.

 o  SSL itself may not be secure. People think it is, do you?
 o  Does this code implement SSL correctly?
 o  Have the authors of the various components put in back doors?
 o  Does the code take appropriate measures to keep private keys private? 
    To what extent is your cooperation in this process required?
 o  Is your system physically secure?
 o  Is your system appropriately secured from intrusion over the network?
 o  Whom do you trust? Do you understand the trust relationship involved 
    in SSL certificates? Do your system administrators?
 o  Are your keys, and keys you trust, generated careful enough to
    avoid reverse engineering of the private keys?
 o  How do you obtain certificates, keys, and the like, securely?
 o  Can you trust your users to safeguard their private keys?
 o  Can you trust your browser to safeguard its generated private key?
  
If you can't answer these questions to your personal satisfaction, then you
usually have a problem.  Even if you can, you may still _NOT_ be secure.  Don't
blame the authors if it all goes horribly wrong.  Use it at your own risk!


GENERAL
-------
Be aware that export/import and/or use of cryptography software, or even just
providing cryptography hooks, is illegal in some parts of the world.  When you
re-distribute this package or even email patches/suggestions to the author or
other people PLEASE PAY CLOSE ATTENTION TO ANY APPLICABLE EXPORT/IMPORT LAWS.
The author of this package is not liable for any violations you make here.

Named "Sesola" to avoid any confusion and/or conflict with OpenSSL routines.

SSL I/O is implemented as a OpenSSL BIO_METHOD, named "Sesola_method". It
provides NON-BLOCKING SSL input/output. All routines that are part of this
functionality are named "Sesola_..." and are grouped towards the end of this
module.

To provide some of the facilities available in this module it was necessary in
places to directly access some of the internals of structures used by OpenSSL. 
Let's hope they don't change names too often!


CLIENT CERTIFICATE AUTHORIZATION
--------------------------------
Thanks to Ralf S.Engelschall (rse@engelschall.com) and Apache 'mod_ssl' package
for hints on how to do some of this (all bits broken in the 'technology
transfer' are mine :^)

Client certificates may be used for authorization, against a realm name "X509". 
This means that a client (user at a browser for instance) is prompted to supply
a X509 certificate as the authorization source.  No username/password is
required, and the usual such dialogs are not generated.


Remote-User - Fingerprint
-------------------------
By default WASD uses the FINGERPRINT OF THE CERTIFICATE (without the usual
byte-delimiting colons) as the identifying username of the client.  For
authorization purposes this username may used like any other (i.e. in rule
access restriction lists, added to group lists, etc.)  The usual certificate
fingerprint looks like

  10:6C:83:42:89:0A:17:03:AA:A5:17:31:7B:14:5B:F7

Such a 'fingerprint' username comprises 32 hexadecimal digits (without the
usual byte-delimiting colons) and looks like

  106C8342890A1703AAA517317B145BF7

Such a fingerprint-username is UNIQUE (based on the MD5 algorithm) and a USEFUL
REPRESENTATION OF THE CERTIFICATE AND USER of the client, even though lacking
slightly in admin-friendly information.  Some CGI variables (.e.g the
AUTH_X509... and AUTH_USER) provided more accessable information.  A CGI script
can easily be designed to capture (and perhaps email) this information for use
by the administrator in setting up authorization, etc.


Remote-User - Subject DN Record
-------------------------------
Alternatively, using the directives described immediately below, it is possible
to specify that the server should derive the remote-user information from a
particular record in the client certificate Subject Distinguished Name.  The
length of this identifying string is limited by AUTH_MAX_USERNAME_LENGTH
specified in AUTH.H and has all white-space converted to underscores
(white-space is not allowed for when processing authentication "usernames"). 
Any of the records identified in the 'SesolaCertDnRec' structure encoded below
may be used, but the more obvious candidates for such a use are /O=, /OU=,
/CN=, /S=, /UID= and /EMAIL=.

Note that when using this facility it is almost mandatory to put some further
access control on the certificate to prevent an unexpected, perhaps even a
user-generated and installed certificate's field contents from being used
(even considering CA verification is applied by default).  The following is an
example of such an HTTPD$AUTH entry.

  [X509]
  /VMS/* r+w,param="[ru:/CN=][is:/O=WASD\ HTTPd\ CA\ Cert]"


Client Cert Authorization Directives/Conditionals
-------------------------------------------------
Conditionals provide extra control on which certificates and their content can
and can't be used during client X509 authentication.  They contain strings that
set authentication parameters, or that are matched to specific elements of
certificate and it's negotiation, and only if matched are the corresponding
rules applied.  The conditionals may contain the '*' and '%' wildcards, and
optionally be negated by an '!' at the start of the conditional string.

Conditionals are passed to the X509 authenticator via the 'param=""' in an
authorization rule, and are delimited by '[' and ']'.  Directives that set
processing parameters must be one-per-directive.  Conditional matching
containing multiple, space-separated conditions may be included within one
'[...]'.  This behaves as a logical OR (i.e. the condition is true if only one
is matched). Multiple '[...]' conditionals may be included.  These act as a
logical AND (i.e. all must have at least one condition matched).  The result of
an entire conditional may be optionally negated by prefixing the '[' with a
'!'. Spaces must *not* be included in match strings.  Reserved characters (i.e.
spaces, exclamation marks and ']') may be escaped using a preceding backslash. 
Wildcards (asterisk and percent) cannot be escaped.

Once verified the following conditionals control whether that certificate is
allowed to be used for authentication.  When string matching note that delimit
wildcards are often required.  Matching is case-insensitive.  Note that the
'IS' and 'SU' conditionals each have two variants.  As always the '@' is
substituted here for '*' due to the issues with C comments.

[!]IS:/record=string  cert issuer DN record   (e.g. [is:/O=VeriSign\ Inc.])
[!]IS:string          cert entire issuer DN   (e.g. [is:@/O=VeriSign\ Inc./@])
[!]SU:/record=string  cert subject DN record  (e.g. [su:/CN=Mark\ Daniel])
[!]SU:string          cert entire subject DN  (e.g. [su:@/CN=Mark%Daniel@])
[!]KS:string          minimum keysize         (e.g. [ks:128])
[!]CI:string          negotiation cipher      (e.g. [ci:RC4-MD5])

Note that the "IS:" and "SU:" conditionals each have a "specific-record" and an
"entire-field" mode.  If the conditional string begins with a slash then it is
considered to be a match against a specified record's content within the field. 
If it begins with a wildcard then it is matched against the entire field's
content.

The following directives control how the certificate is to be processed.

DP:integer           set the CA verification depth
LT:integer           (re)set the authenticated session lifetime in minutes
RU:string            remote-user from certificate subject record (e.g. "/CN=")
TO:integer           set the session timeout in minutes ("EXPIRE" to expire)
VF:OPTIONAL          authorize even if the issuing CA cannot be verified
VF:REQUIRED          the issuing CA must be verified (default)
VF:NONE              do not get peer certificate (cancels any existing)

Example HTTPD$AUTH entries:

  ["Just an example!"=X509]

  /cgi-bin/show_cert_details r,\
  param="[VF:OPTIONAL_NO_CA]"

  /cgi-bin/show_verisign_details r,\
  param="[VF:OPTIONAL_NO_CA] [IS:@/O=VeriSign\ Inc./@]"

  /some/path/or/other r+w, \
  param="[DP:1] [TO:10] [SU:@/Email=Mark.Daniel@wasd.vsm.com.au]"

  /VMS/* r+w,param="[RU:/CN=]"

Remember the WATCH facility provides considerable detail during the processing
of all authorization mapping.


OPENSSL
-------
With the retirement of Eric Young from OpenSource software and the creation of
the OpenSSL organisation this module will be based on this group's packages and
in particular the the VMS port supported by Richard Levitte.

  richard@levitte.org
  http://www.free.lp.se/openssl/
  http://www.openssl.org/

The initial development was done using SSLeay v0.8.1
Modified for SSLeay 0.9.0b
A great leap forward with OpenSSL v0.9.3 (thanks Richard)
Tested against OpenSSL v0.9.4
Tested and modified for OpenSSL v0.9.5
Tested against OpenSSL v0.9.6
Tested against OpenSSL v0.9.6a
Tested against OpenSSL v0.9.6b
Tested against OpenSSL v0.9.6c
Tested against OpenSSL v0.9.6d
Tested against OpenSSL v0.9.6e
Tested against OpenSSL v0.9.6f
Tested against CPQ AXPVMS SSL V1.0-A
Tested and modified for OpenSSL v0.9.7-beta

A general hard-copy reference (and there aren't many around) that has been
interesting and informative is "SSL and TLS, Designing and Building Secure
Systems", Eric Rescorla, 2001, Addison-Wesley (ISBN-201-61598-3).
Though don't blame the book's author for the code author's shortcomings!


SSL COMMAND LINE OPTIONS
------------------------
The /SSL= qualifier allows certain SSL runtime parameters to be set.  The
default is support of SSLv2/SSLv3, certificate file provided by HTTPD$SSL_CERT,
all ciphers, and a session cache of 128.

  /SSL=2                  support only SSLv2 (pre-v6.0.0)
  /SSL=3                  support only SSLv3 (pre-v6.0.0)
  /SSL=23                 support both SSLv2 and SSLv3 (pre-v6.0.0)
  /SSL=SSLv2              support only SSLv2
  /SSL=noSSLv2            turn SSLv2 option off
  /SSL=SSLv3              support only SSLv3
  /SSL=noSSLv3            turn SSLv3 option off
  /SSL=(SSLv2,SSLv3)      support both SSLv2 and SSLv3
  /SSL=TLSv1              support only TLSv1 (etc.)
  /SSL=noTLSv1            turn TLSv1 option off
  /SSL=(OPTIONS=0xnn)     SSL_OP_.. options (see [.INCLUDE.OPENSSL]SSL.H)
  /SSL=(CACHE=integer)    set session cache size
  /SSL=(CAFILE=file)      location of default client verify CA certificate file
  /SSL=(CERT=file)        location of default OpenSSL-PEM certificate file
  /SSL=(CIPHER=list)      semi-colon-separated list of supported ciphers
  /SSL=(CACHE=integer)    size of session cache
  /SSL=(KEY=file)         location of default OpenSSL-PEM private key file
                          (if separate from the certificate information)
  /SSL=(TIMEOUT=integer)  set session cache timeout
  /SSL=(VERIFY=integer)   set client certificate CA chin verification depth

Multiple parameters may be included by separating with commas.  Example:

  /SSL=(CERT=HT_ROOT:[LOCAL]SITE.PEM,SSLV2,SSLV3,TIMEOUT=10)


CGI VARIABLES
-------------
CGI variables for scripting and SSI environments can be selectively generated
for "https:" requests.  See the mapping SETting "SSLCGI=" in MAPURL.C module. 
The Apache mod_SSL variables are based on the v2.7 documentation by Ralf S.
Engelschall (rse@engelschall.com).  The Purveyor variables are based on
Purveyor documentation.  The client certificate authentication AUTH_X509... are
always generated when there is X509 authentication.

  Client Certificate Authorization
  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  o  AUTH_X509_CIPHER ......... name of the cipher in use
  o  AUTH_X509_FINGERPRINT .... (MD5) fingerprint of X509 cert
  o  AUTH_X509_ISSUER ......... CA of client X509 cert
  o  AUTH_X509_KEYSIZE ........ 40, 56, 128, etc.
  o  AUTH_X509_SUBJECT ........ client details of X509 cert

  Apache mod_SSL -like SSL variables
  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  o  HTTPS .................... "true" indicating it's SSL
  o  SSL_PROTOCOL ............. version (e.g. "SSLv2", "SSLv3")
  o  SSL_SESSION_ID ........... hex-encoded SSL session ID
  o  SSL_CIPHER ............... cipher name (e.g. "RC4-MD5")
  o  SSL_CIPHER_EXPORT ........ "true" if export grade
  o  SSL_CIPHER_USEKEYSIZE .... bits cipher used for session
  o  SSL_CIPHER_ALGKEYSIZE .... bits cipher is capable of supporting
  o  SSL_CLIENT_M_VERSION ..... server cert version
  o  SSL_CLIENT_M_SERIAL ...... server cert serial number
  o  SSL_CLIENT_S_DN .......... subject cert distinguished name
  o  SSL_CLIENT_S_DN_x509 ..... subject cert DN components
  o  SSL_CLIENT_I_DN .......... issuer cert distinguished name
  o  SSL_CLIENT_I_DN_x509 ..... issuer cert DN components
  o  SSL_CLIENT_V_START ....... cert validity start date
  o  SSL_CLIENT_V_END ......... cert validity end date
  o  SSL_CLIENT_A_SIG ......... server cert signature algorithm
  o  SSL_CLIENT_A_KEY ......... server cert public key algorithm
  o  SSL_CLIENT_CERT .......... (see note in SesolaCgiVariablesApacheModSsl())
  o  SSL_SERVER_M_VERSION ..... server cert version
  o  SSL_SERVER_M_SERIAL ...... server cert serial number
  o  SSL_SERVER_S_DN .......... subject cert distinguished name
  o  SSL_SERVER_S_DN_x509 ..... subject cert DN components
  o  SSL_SERVER_I_DN .......... issuer cert distinguished name
  o  SSL_SERVER_I_DN_x509 ..... issuer cert DN components
  o  SSL_SERVER_V_START ....... cert validity start date
  o  SSL_SERVER_V_END ......... cert validity end date
  o  SSL_SERVER_A_SIG ......... server cert signature algorithm
  o  SSL_SERVER_A_KEY ......... server cert public key algorithm
  o  SSL_SERVER_CERT .......... (see note in SesolaCgiVariablesApacheModSsl())
  o  SSL_VERSION_INTERFACE .... WASD server software ID
  o  SSL_VERSION_LIBRARY ...... OpenSSL version

  "Purveyor"-like security/SSL variables
  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  o  SECURITY_STATUS .......... "NONE" or "SSL"
  o  SSL_CIPHER ............... name of the cipher in use
  o  SSL_CIPHER_KEYSIZE ....... 40, 56, 128, etc.
  o  SSL_CLIENT_CA ............ client cert authority
  o  SSL_CLIENT_DN ............ client cert distinguished name
  o  SSL_SERVER_CA ............ server cert authority
  o  SSL_SERVER_DN ............ server cert distinguished name
  o  SSL_VERSION .............. SSL version (e.g. "SSLv2")


LOGICAL/SYMBOL NAMES
--------------------
The following logical or symbol names can be used to provide runtime
information to the SSL functionality.  Configuration options provided by /SSL=
and [service] or /SERVICE= parameters override anything provided via these
logical/symbol names.

  HTTPD$SSL_CAFILE       location of default client verify CA certificate file
  HTTPD$SSL_CERT         location of default OpenSSL-PEM certificate file
  HTTPD$SSL_CIPHER       comma-separated list of default supported ciphers
  HTTPD$SSL_KEY          location of default OpenSSL-PEM private key file
  HTTPD$SSL_PARAMS       the equivalent functionality of the /SSL= qualifier


VERSION HISTORY
---------------
14-JAN-2003  MGD  DN record /email and /emailAddress
15-OCT-2002  MGD  use internal ceritifcate and configuration in demo mode
28-AUG-2002  MGD  add SHA1 fingerprint (everybody else has it ;^)
11-AUG-2002  MGD  refine SesolaReport() for obtaining service ciphers 
                  (OpenSSLv0.9.6f/0.9.7-beta break it),
                  built and tested against CPQ AXPVMS SSL V1.0-A,
                  internal PEM cert/key as fallback; mainly for VMS (Open)SSL
03-JUL-2002  MGD  refine SesolaReport() so it obtains the service certificate
                  indirectly removing the need for SSL_LOCL.H (OpenSSL 0.9.7)
17-APR-2002  MGD  bugfix; SesolaCertVerifyCallback()
02-JAN-2002  MGD  rework SSL network functions into SESOLANET.C,
                  add HTTP-SSL proxy (gateway) service initialization
27-OCT-2001  MGD  break up a burgeoning SSL module into more source files
29-SEP-2001  MGD  instance support
04-AUG-2001  MGD  support module WATCHing
01-JUL-2001  MGD  refine private key password request functionality
20-MAY-2001  MGD  add [RU:/xx=] to allow a site to specify a certificate
                  subject DN record as the authenticated remote-user,
                  change [IS:/name=string] and [SU:/name=string] to allow
                  individual issuer or subject DN records to be matched,
                  private key password optionally can now be supplied via
                  /DO=SSL=KEY=PASSWORD (see SesolaPrivateKeyPasswd())
11-APR-2001  MGD  remove http: check from SesolaAccept(),
                  bugfix; SesolaFree() BioPtr
02-APR-2001  MGD  refine SesolaClientCertVerifyCallback()
10-DEC-2000  MGD  client certificate and authorization support,
                  Richard Levitte in a recent email to vms-web-daemon@KJSL.COM
                  points out using SSL_CTX_use_certificate_chain_file() is more
                  versatile than SSL_CTX_use_certificate_file(),
                  bugfix; SesolaCgiVariablesApacheModSsl() 
22-NOV-2000  MGD  where a service-specific certificate is supplied and no
                  service-specific key assume the key is in the specific cert
17-OCT-2000  MGD  modify SSL initialization so that "fallback" conditions
                  (same port on same IP address) are more easily identified
26-SEP-2000  MGD  built and verified against OpenSSL 0.9.6
                  change 'boolean' to 'BOOL' to accomodate new OpenSSL typedef
26-AUG-2000  MGD  SesolaWatchPeek()
17-JUN-2000  MGD  modifications for SERVICE.C requirements
06-MAY-2000  MGD  added Apache mod_SSL style CGI variables
05-MAR-2000  MGD  tested against OpenSSL v0.9.5
                  (SSL_OP_NON_EXPORT_FIRST generates error message, removed),
                  use NetWriteFaol(), et.al.
23-DEC-1999  MGD  modify NEEDSTRUCT reported by DECC v6.2 
19-OCT-1999  MGD  SesolaInitServiceList() service errors not fatal at startup
11-SEP-1999  MGD  tested against OpenSSL v0.9.4,
                  SSLeay no longer supported
18-AUG-1999  MGD  add certificate CA and DN to SSL report,
                  bugfix; certificate/key pairs when initializing service
12-JUN-1999  MGD  refine SSL connection handling
26-MAY-1999  MGD  allow some SSL options to be set if desired,
                  set SSL_OP_NON_EXPORT_FIRST (for some certificate processing)
18-APR-1999  MGD  improve error reporting during certificate loading
03-APR-1999  MGD  support OpenSSL 0.9.3
                  (with initial, backward support for SSLeay 0.8 & 0.9), 
                  add WATCH callbacks
31-MAR-1999  MGD  bugfix; report request from non-SSL port
20-JAN-1999  MGD  report format refinement
07-NOV-1998  MGD  WATCH facility
24-OCT-1998  MGD  per-service context (implies per-service certificate)
01-JUN-1998  MGD  builds OK with SSLeay 0.9.0b
25-JAN-1998  MGD  initial development for v5.0, SSLeay v0.8.1
*/
/*****************************************************************************/

#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 "SESOLA"

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

/* if there is no certificate/key file it will fallback to using this */
char  SesolaInternalPEM [] =
"\
-----BEGIN CERTIFICATE-----\n\
MIIEmzCCBASgAwIBAgIBATANBgkqhkiG9w0BAQQFADCBxDELMAkGA1UEBhMCQVUx\n\
CzAJBgNVBAgTAlNBMREwDwYDVQQHEwhBZGVsYWlkZTEbMBkGA1UEChMSV0FTRCBI\n\
VFRQZCBDQSBDZXJ0MSYwJAYDVQQLEx1JbnRlcm5hbCAoZmFsbGJhY2spIENlcnQg\n\
T25seTEkMCIGA1UEAxMbV0FTRCBWTVMgSHlwZXJ0ZXh0IFNlcnZpY2VzMSowKAYJ\n\
KoZIhvcNAQkBFhtNYXJrLkRhbmllbEB3YXNkLnZzbS5jb20uYXUwHhcNMDIwODEx\n\
MjMwNzExWhcNMjIwODEyMjMwNzExWjCByDELMAkGA1UEBhMCQVUxCzAJBgNVBAgT\n\
AlNBMREwDwYDVQQHEwhBZGVsYWlkZTEfMB0GA1UEChMWV0FTRCBIVFRQZCBTZXJ2\n\
ZXIgQ2VydDEmMCQGA1UECxMdSW50ZXJuYWwgKGZhbGxiYWNrKSBDZXJ0IE9ubHkx\n\
JDAiBgNVBAMTG1dBU0QgVk1TIEh5cGVydGV4dCBTZXJ2aWNlczEqMCgGCSqGSIb3\n\
DQEJARYbTWFyay5EYW5pZWxAd2FzZC52c20uY29tLmF1MFwwDQYJKoZIhvcNAQEB\n\
BQADSwAwSAJBAL7HIJ1JcMBxpYDOZPG37CGbWgoMdGQuxaAhWVFaqJEmfuFbvKTd\n\
3s7IIoHUdCw7COHdDxDot3vyrFaCvy4zWp8CAwEAAaOCAdkwggHVMAkGA1UdEwQC\n\
MAAwCwYDVR0PBAQDAgXgMCoGCWCGSAGG+EIBBAQdFhtodHRwOi8vd3d3LnZzbS5j\n\
b20uYXU6ODAwMC8wLAYJYIZIAYb4QgENBB8WHVdBU0QgaXMgR05VLWxpY2Vuc2Vk\n\
IGZyZWV3YXJlMB0GA1UdDgQWBBQuyHy9/HGm5wU3cN17GhHzDzNeEzCB8QYDVR0j\n\
BIHpMIHmgBQsHpyosA2GNithw9EruoGDq1mKsaGByqSBxzCBxDELMAkGA1UEBhMC\n\
QVUxCzAJBgNVBAgTAlNBMREwDwYDVQQHEwhBZGVsYWlkZTEbMBkGA1UEChMSV0FT\n\
RCBIVFRQZCBDQSBDZXJ0MSYwJAYDVQQLEx1JbnRlcm5hbCAoZmFsbGJhY2spIENl\n\
cnQgT25seTEkMCIGA1UEAxMbV0FTRCBWTVMgSHlwZXJ0ZXh0IFNlcnZpY2VzMSow\n\
KAYJKoZIhvcNAQkBFhtNYXJrLkRhbmllbEB3YXNkLnZzbS5jb20uYXWCAQAwJgYD\n\
VR0RBB8wHYEbTWFyay5EYW5pZWxAd2FzZC52c20uY29tLmF1MCYGA1UdEgQfMB2B\n\
G01hcmsuRGFuaWVsQHdhc2QudnNtLmNvbS5hdTANBgkqhkiG9w0BAQQFAAOBgQCg\n\
FSJz6xgiIFWWLW40uLtHi2D0GpSGLqyrRzzqRc/tPXcFW6iibsdT+N1uJvClJrKa\n\
SogDuO3iUkwhX4pDTScxkSemmYit6E9+nnTiDifF8zRbvjU4omCpUSCLOMKQBqFV\n\
8c/VF2SK7GHBfsLujYlhFpq6VhMBF/50Fkybemco3A==\n\
-----END CERTIFICATE-----\n\
-----BEGIN RSA PRIVATE KEY-----\n\
MIIBOQIBAAJBAL7HIJ1JcMBxpYDOZPG37CGbWgoMdGQuxaAhWVFaqJEmfuFbvKTd\n\
3s7IIoHUdCw7COHdDxDot3vyrFaCvy4zWp8CAwEAAQJAfN36o9ggu2TnDZKJkYhv\n\
PmPfH/qc58GRSkjpnAz5jd5/6UgBZkXmjplOy0k3dANgxyQqErRJvD2RJWMF0qHf\n\
sQIhAPn3kWdRnP1eyl7/DhK8xl7aB62DgbSuJ+FNQ+IboOlXAiEAw2HbOAa9uLbi\n\
/RfW0XXiY2O32AZuuZvBDpCkV94So/kCIHTz7xUfK0ukuRy/Sw9bQZkJfAQj/mDS\n\
Bxiz9OnqsVvbAiA7K//AUApVTs4f6IBen10YzLJ48jnGbK1jQ9sB4XezwQIgBDT9\n\
TrsfJQlPy6Sk5UBxRV3h9OTqw8azr2eg6KBmS40=\n\
-----END RSA PRIVATE KEY-----\n\
";

/******************/
/* global storage */
/******************/

BOOL  ProtocolHttpsAvailable = true,
      ProtocolHttpsConfigured,
      SesolaVerifyCAConfigured,
      SesolaPrivateKeyPasswdRequested;

int  SesolaDefaultVerifyDepth = SESOLA_DEFAULT_VERIFY_DEPTH,
     SesolaSessionCacheSize = SESOLA_DEFAULT_CACHE_SIZE,
     SesolaSessionCacheTimeout = SESOLA_DEFAULT_CACHE_TIMEOUT,
     SesolaOptionsOff,
     SesolaOptions,
     SesolaPrivateKeyPasswdAttempt,
     SesolaSSLversion;

char  *SesolaDefaultCertPtr,
      *SesolaDefaultCipherListPtr,
      *SesolaDefaultKeyPtr,
      *SesolaDefaultCaFilePtr,
      *SesolaSSLversionStringPtr;

char  SesolaParams [256] = "";

char  ErrorSslPort [] = "This is an SSL (&quot;https:&quot;) port.",
      HttpdSesola [] = " SSL";

static struct SesolaCertDnRecStruct
{
   char *name;
   int length;
}
SesolaCertDnRec [] =
{
   { "/C=", 3 },   /* countryName */
   { "/ST=", 4 },  /* stateOrProvinceName */
   { "/SP=", 4 },  /* stateOrProvinceName */
   { "/L=", 3 },   /* localityName */
   { "/O=", 3 },   /* organizationName */
   { "/OU=", 4 },  /* organizationalUnitName */
   { "/CN=", 4 },  /* commonName */
   { "/T=", 3 },   /* title */
   { "/I=", 3 },   /* initials */
   { "/G=", 3 },   /* givenName */
   { "/S=", 3 },   /* surname */
   { "/D=", 3 },   /* description */
   { "/UID=", 5 },  /* uniqueIdentifier */
   { "/Email=", 7 }, /* pkcs9_emailAddress */
   { "/emailAddress=", 13 }, /* pkcs9_emailAddress */
   { NULL, 0 }
};

BIO_METHOD  *SesolaBioMemPtr;

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

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

extern BOOL  CliDemo;

extern int  ExitStatus,
            InstanceNodeConfig,
            InstanceNodeCurrent,
            OpcomMessages;

extern unsigned long  SysPrvMask[];

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

extern ACCOUNTING_STRUCT  *AccountingPtr;
extern CONFIG_STRUCT  Config;
extern HTTPD_GBLSEC  *HttpdGblSecPtr;
extern HTTPD_PROCESS  HttpdProcess;
extern MSG_STRUCT  Msgs;
extern SERVICE_STRUCT  *ServiceListHead;
extern WATCH_STRUCT  Watch;

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

SesolaInit ()

{
   char  *cptr, *sptr;

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

   if (WATCH_MODULE(WATCH_MOD_SESOLA))
      WatchThis (NULL, FI_LI, WATCH_MOD_SESOLA, "SesolaInit()");

   WriteFaoStdout ("%!AZ-I-SSL, !AZ\n",
                   Utility, SSLeay_version(SSLEAY_VERSION));

   /* command-line parameters, if none then use HTTPD$SSL_PARAMS */
   cptr = SesolaParams;
   if (!*cptr) cptr = getenv (CONFIG_SSL_PARAMS);
   if (!cptr) cptr = "";

   if (strsame (cptr, "/NOSSL", -1))
   {
      WriteFaoStdout ("%!AZ-W-SSL, disabled via /NOSSL\n", Utility);
      return;
   }

   while (*cptr)
   {
      while (*cptr && (*cptr == '(' || *cptr == ',' || *cptr == ')')) cptr++;
      sptr = cptr;
      while (*cptr && *cptr != ',' && *cptr != ')') cptr++;
      if (*cptr) *cptr++ = '\0';
      if (!*sptr) break;
      if (Debug) fprintf (stdout, "sptr |%s|\n", sptr); 

      /* the 'digits' are for backward compatibility with pre-v6.0.0 */
      if (strsame (sptr, "2", -1))
         SesolaSSLversion |= SESOLA_SSLV2;
      else
      if (strsame (sptr, "3", -1))
         SesolaSSLversion |= SESOLA_SSLV3;
      else
      if (strsame (sptr, "23", -1))
         SesolaSSLversion |= SESOLA_SSLV2 | SESOLA_SSLV3;
      else
      if (strsame (sptr, "SSLv2", -1))
         SesolaSSLversion |= SESOLA_SSLV2;
      else
      if (strsame (sptr, "noSSLv2", -1))
         SesolaOptionsOff |= SSL_OP_NO_SSLv2;
      else
      if (strsame (sptr, "SSLv3", -1))
         SesolaSSLversion |= SESOLA_SSLV3;
      else
      if (strsame (sptr, "noSSLv3", -1))
         SesolaOptionsOff |= SSL_OP_NO_SSLv3;
      else
      if (strsame (sptr, "SSLv2/v3", -1))
         SesolaSSLversion |= SESOLA_SSLV2 | SESOLA_SSLV3;
      else
      if (strsame (sptr, "TLSv1", -1))
         SesolaSSLversion |= SESOLA_TLSV1;
      else
      if (strsame (sptr, "noTLSv1", -1))
         SesolaOptionsOff |= SSL_OP_NO_TLSv1;
      else
      if (strsame (sptr, "CAFILE=", 7))
         SesolaDefaultCaFilePtr = sptr + 7;
      else
      if (strsame (sptr, "CERT=", 5))
         SesolaDefaultCertPtr = sptr + 5;
      else
      if (strsame (sptr, "CIPHER=", 7))
         SesolaDefaultCipherListPtr = sptr + 7;
      else
      if (strsame (sptr, "CACHE=", 6))
         SesolaSessionCacheSize = atoi(sptr+6);
      else
      if (strsame (sptr, "KEY=", 4))
         SesolaDefaultKeyPtr = sptr + 4;
      else
      if (strsame (sptr, "OPTIONS=", 8))
      {
         if (strsame (sptr+8, "ALL", 4))
            SesolaOptions = SSL_OP_ALL;
         else
         /* a tough one to enter because of the signed nature of strol() */
         if (strsame (sptr+8, "NETSCAPE", 8))
            SesolaOptions != SSL_OP_NETSCAPE_DEMO_CIPHER_CHANGE_BUG;
         else
            SesolaOptions = strtol (sptr+8, NULL, 16);
      }
      else
      if (strsame (sptr, "TIMEOUT=", 8))
         SesolaSessionCacheTimeout = atoi(sptr+8);
      else
      if (strsame (sptr, "VERIFY=", 7))
         SesolaDefaultVerifyDepth = atoi(sptr+7);
      else
      {
         WriteFaoStdout ("%!AZ-E-SSL, unknown SSL parameter\n \\!AZ\\\n",
                         Utility, sptr);
         exit (STS$K_ERROR | STS$M_INHIB_MSG);
      }
   }

   /* default is to support both SSL v2 and v3 */
   if (!(SesolaSSLversion & SESOLA_SSLV2) &&
       !(SesolaSSLversion & SESOLA_SSLV3) &&
       !(SesolaSSLversion & SESOLA_TLSV1))
      SesolaSSLversion = SESOLA_SSLV2 | SESOLA_SSLV3;

   if ((SesolaSSLversion & SESOLA_SSLV2) &&
       (SesolaSSLversion & SESOLA_SSLV3))
      SesolaSSLversionStringPtr = "SSLv2/v3";
   else
   if (SesolaSSLversion & SESOLA_SSLV2)
      SesolaSSLversionStringPtr = "SSLv2";
   else
   if (SesolaSSLversion & SESOLA_SSLV3)
      SesolaSSLversionStringPtr = "SSLv3";
   else
   if (SesolaSSLversion & SESOLA_TLSV1)
      SesolaSSLversionStringPtr = "TLSv1";
   WriteFaoStdout ("%!AZ-I-SSL, protocol(s) !AZ\n",
                   Utility, SesolaSSLversionStringPtr);

   /* lots and lots of non-determinate stuff */
   RAND_seed (HttpdGblSecPtr, sizeof(HTTPD_GBLSEC));

   SSL_load_error_strings ();

   SSL_library_init (); 

   if (!SesolaDefaultCertPtr)
      SesolaDefaultCertPtr = getenv (CONFIG_SSL_CERT);

   if (!SesolaDefaultKeyPtr)
      SesolaDefaultKeyPtr = getenv (CONFIG_SSL_KEY);

   if (!SesolaDefaultCipherListPtr)
      SesolaDefaultCipherListPtr = getenv (CONFIG_SSL_CIPHER);

   if (!SesolaDefaultCaFilePtr)
      SesolaDefaultCaFilePtr = getenv (CONFIG_SSL_CAFILE);

   /* inter-process session cache (if multiple per-node "instances") */
   if (InstanceNodeConfig > 1) SesolaCacheInit ();

   SesolaBioMemPtr = BIO_new (BIO_s_mem());
   if (!SesolaBioMemPtr)
   {
      WriteFaoStdout ("%!AZ-E-SSL, BIO_new(BIO_s_mem()) failed\n", Utility);
      exit (STS$K_ERROR | STS$M_INHIB_MSG);
   }
}

/*****************************************************************************/
/*
Called during service configuration to initialize an SSL service.  SSL services
are either created from scratch or if sharing an IP address and port with
another SSL service are "cloned".
*/

BOOL SesolaInitService
(
SERVICE_STRUCT *svptr,
SERVICE_STRUCT *csvptr
)
{
   BOOL  CipherListSupplied,
         KeySupplied,
         VerifyCAConfigured;
   int  value,
        VerifyMode;
   unsigned long  ErrorNumber;
   char  *cptr;
   RSA  *RsaPtr;
   SESOLA_CONTEXT  *scptr, *ssptr;
   SSL_CTX  *SslCtx;
   X509  *CertPtr;

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

   if (WATCH_MODULE(WATCH_MOD_SESOLA))
      WatchThis (NULL, FI_LI, WATCH_MOD_SESOLA,
                 "SesolaInitService() !&X !&X", svptr, csvptr);

   if (csvptr)
   {
      /*********/
      /* clone */
      /*********/

      /* initialize this new service by "cloning" a "fallback" one */
      if (!csvptr->SSLserverPtr) return (false);
      svptr->SSLserverPtr = VmGet (sizeof(SESOLA_CONTEXT)); 
      memcpy (svptr->SSLserverPtr,
              csvptr->SSLserverPtr,
              sizeof(SESOLA_CONTEXT));
      return (true);
   }

   WriteFaoStdout ("%!AZ-I-SSL, !AZ\n", Utility, svptr->ServerHostPort);

   if (!(scptr = (SESOLA_CONTEXT*)svptr->SSLserverPtr)) return (false);

   if (CliDemo)
   {
      scptr->Version = SesolaSSLversion;
      scptr->CaFilePtr = scptr->CertFilePtr =
         scptr->CipherListPtr = scptr->KeyFilePtr = "";
   }
   else
   {
      if (scptr->CertFile[0])
         scptr->CertFilePtr = scptr->CertFile;
      else
      if (SesolaDefaultCertPtr)
         scptr->CertFilePtr = SesolaDefaultCertPtr;
      else
         scptr->CertFilePtr = "";

      if (scptr->KeyFile[0])
      {
         KeySupplied = true;
         scptr->KeyFilePtr = scptr->KeyFile;
      }
      else
      {
         if (scptr->CertFile[0])
         {
            /* service has a specific certificate, assume the key's in that */
            scptr->KeyFilePtr = scptr->CertFile;
         }
         else
         if (SesolaDefaultKeyPtr)
         {
            /* use the separately specified key */
            scptr->KeyFilePtr = SesolaDefaultKeyPtr;
         }
         else
         {
            /* if no default key then assume it's in the certificate file */
            KeySupplied = false;
            scptr->KeyFilePtr = scptr->CertFilePtr;
         }
      }

      if (scptr->CipherList[0])
      {
         CipherListSupplied = true;
         scptr->CipherListPtr = scptr->CipherList;
      }
      else
      {
         if (SesolaDefaultCipherListPtr)
         {
            CipherListSupplied = true;
            scptr->CipherListPtr = SesolaDefaultCipherListPtr;
         }
         else
         {
            CipherListSupplied = false;
            scptr->CipherListPtr = "";
         }
      }

      if (scptr->CaFile[0])
      {
         VerifyCAConfigured = true;
         scptr->CaFilePtr = scptr->CaFile;
      }
      else
      {
         if (SesolaDefaultCaFilePtr)
         {
            VerifyCAConfigured = true;
            scptr->CaFilePtr = SesolaDefaultCaFilePtr;
         }
         else
         {
            VerifyCAConfigured = false;
            scptr->CaFilePtr = "";
         }
      }
      if (scptr->CaFilePtr[0])
         /* set the global boolean as well (never reset it!) */
         VerifyCAConfigured = SesolaVerifyCAConfigured = true;
      else
         VerifyCAConfigured = false;

      if (scptr->CertFilePtr[0])
         WriteFaoStdout ("-!AZ-I-CERT, !AZ\n", Utility, scptr->CertFilePtr);
      else
         WriteFaoStdout ("-!AZ-W-CERT, using internal certificate\n", Utility);

      if (KeySupplied)
         WriteFaoStdout ("-!AZ-I-KEY, !AZ\n", Utility, scptr->KeyFilePtr);

      if (CipherListSupplied)
         WriteFaoStdout ("-!AZ-I-CIPHER, !AZ\n", Utility, scptr->CipherListPtr);

      if (VerifyCAConfigured)
         WriteFaoStdout ("-!AZ-I-CAFILE, !AZ\n", Utility, scptr->CaFilePtr);

      if (strsame (scptr->VersionString, "SSLV2/V3", -1))
         scptr->Version = SESOLA_SSLV2 | SESOLA_SSLV3;
      else
      if (strsame (scptr->VersionString, "SSLV3", -1))
         scptr->Version = SESOLA_SSLV3;
      else
      if (strsame (scptr->VersionString, "SSLV2", -1))
         scptr->Version = SESOLA_SSLV2;
      else
      if (strsame (scptr->VersionString, "TLSV1", -1))
         scptr->Version = SESOLA_TLSV1;
      else
      if (scptr->VersionString[0])
      {
         WriteFaoStdout ("!AZ-W-SSL, unknown protocol\n\ \\!AZ\\\n",
                         Utility, scptr->VersionString);
         return (false);
      }
      else
         scptr->Version = SesolaSSLversion;
   }

   if ((scptr->Version & SESOLA_SSLV2) &&
       (scptr->Version & SESOLA_SSLV3))
   {
      SslCtx = SSL_CTX_new (SSLv23_method());
      scptr->VersionStringPtr = "SSLV2/V3";
   }
   else
   if (scptr->Version & SESOLA_SSLV2)
   {
      SslCtx = SSL_CTX_new (SSLv2_method());
      scptr->VersionStringPtr = "SSLV2";
   }
   else
   if (scptr->Version & SESOLA_SSLV3)
   {
      SslCtx = SSL_CTX_new (SSLv3_method());
      scptr->VersionStringPtr = "SSLV3";
   }
   else
   if (scptr->Version & SESOLA_TLSV1)
   {
      SslCtx = SSL_CTX_new (TLSv1_method());
      scptr->VersionStringPtr = "TLSV1";
   }
   if (!SslCtx)
   {
      SesolaPrintOpenSslErrorList ();
      ErrorExitVmsStatus (0, "SSL_CTX_new()", FI_LI);
   }

   if (scptr->CertFilePtr[0])
      value = SSL_CTX_use_certificate_chain_file (SslCtx,
                                                  scptr->CertFilePtr);
   else
   {
      /* read the fallback certificate */
      char  Buffer [256];
      BIO_puts (SesolaBioMemPtr, SesolaInternalPEM);
      CertPtr = PEM_read_bio_X509 (SesolaBioMemPtr, NULL, NULL, NULL);
      if (CertPtr)
      {
         value = SSL_CTX_use_certificate (SslCtx, CertPtr);
         X509_free (CertPtr);
      }
      else
         value = 0;
      /* empty anything not read */
      while (BIO_gets (SesolaBioMemPtr, Buffer, sizeof(Buffer)) > 0);
   }

   if (!value)
   {
      SesolaPrintOpenSslErrorList ();
      return (false);
   }

   SesolaPrivateKeyPasswdAttempt = 1;
   for (;;)
   {
      /* set private key password callback */
      SSL_CTX_set_default_passwd_cb (SslCtx, &SesolaPrivateKeyPasswd);
      SSL_CTX_set_default_passwd_cb_userdata (SslCtx, scptr);
      SesolaPrivateKeyPasswdRequested = false;

      if (scptr->KeyFilePtr[0])
         value = SSL_CTX_use_RSAPrivateKey_file (SslCtx,
                                                 scptr->KeyFilePtr,
                                                 SSL_FILETYPE_PEM);
      else
      {
         /* read the fallback private key */
         char  Buffer [256];
         BIO_puts (SesolaBioMemPtr, SesolaInternalPEM);
         RsaPtr = PEM_read_bio_RSAPrivateKey (SesolaBioMemPtr, NULL,
                                              &SesolaPrivateKeyPasswd, scptr);
         if (RsaPtr)
         {
            value = SSL_CTX_use_RSAPrivateKey (SslCtx, RsaPtr);
            RSA_free (RsaPtr);
         }
         else
            value = 0;
         /* empty anything not read */
         while (BIO_gets (SesolaBioMemPtr, Buffer, sizeof(Buffer)) > 0);
      }

      /* reset private key password callback */
      SSL_CTX_set_default_passwd_cb (SslCtx, NULL);
      SSL_CTX_set_default_passwd_cb_userdata (SslCtx, NULL);

      /* inter-process session cache (if multiple per-node "instances") */
      if (InstanceNodeConfig > 1)
      {
         SSL_CTX_sess_set_new_cb (SslCtx, &SesolaCacheAddRecord);
         SSL_CTX_sess_set_get_cb (SslCtx, &SesolaCacheFindRecord);
         SSL_CTX_sess_set_remove_cb (SslCtx, &SesolaCacheRemoveRecord);
      }

      if (value)
      {
         if (SesolaPrivateKeyPasswdRequested && OpcomMessages)
            WriteFaoOpcom ("%!AZ-I-PKPASSWD, password accepted", Utility);
         break;
      }

      ErrorNumber = ERR_peek_error ();
      if (Debug)
         fprintf (stdout, "%08x %02x %03x %03x\n",
                  ErrorNumber, ERR_GET_LIB(ErrorNumber),
                  ERR_GET_FUNC(ErrorNumber), ERR_GET_REASON(ErrorNumber));
      SesolaPrintOpenSslErrorList ();

      if (SesolaPrivateKeyPasswdRequested &&
          ERR_GET_LIB(ErrorNumber) == ERR_LIB_EVP &&
          ERR_GET_FUNC(ErrorNumber) == EVP_F_EVP_DECRYPTFINAL &&
          ERR_GET_REASON(ErrorNumber) == EVP_R_BAD_DECRYPT)
      {
         if (++SesolaPrivateKeyPasswdAttempt <= SESOLA_PKPASSWD_ATTEMPTS)
         {
            /* give the controlling ControlLocalCommand() a chance to notice */
            sleep (2);
            continue;
         }
      }

      if (OpcomMessages)
         WriteFaoOpcom ("-!AZ-E-PKPASSWD, password not accepted", Utility);
      return (false);
   }

   value = SSL_CTX_check_private_key (SslCtx);
   if (!value)
   {
      SesolaPrintOpenSslErrorList ();
      return (false);
   }

   if (scptr->CipherListPtr[0])
      SSL_CTX_set_cipher_list (SslCtx, scptr->CipherListPtr);

   SSL_CTX_sess_set_cache_size (SslCtx, SesolaSessionCacheSize);
   SSL_CTX_set_timeout (SslCtx, SesolaSessionCacheTimeout*60);

   if (scptr->VerifyPeer)
      VerifyMode = SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT;
   else
      VerifyMode = SSL_VERIFY_NONE;
   SSL_CTX_set_verify (SslCtx, VerifyMode, &SesolaCertVerifyCallback);

   SSL_CTX_set_verify_depth (SslCtx, SesolaDefaultVerifyDepth);

   if (scptr->CaFilePtr[0])
   {
      value = SSL_CTX_load_verify_locations (SslCtx,
                                             scptr->CaFilePtr,
                                             NULL);
      if (value) value = SSL_CTX_set_default_verify_paths (SslCtx);
      if (!value)
      {
         SesolaPrintOpenSslErrorList ();
         WriteFaoStdout ("-!AZ-W-SSL, peer verification not enabled\n",
                         Utility);
         return (false);
      }
   }

   /* for option values, see [.INCLUDE.OPENSSL]SSL.H */
   if (SesolaOptions)
   {
      WriteFaoStdout ("-!AZ-I-SSL, options 0x!8XL\n", Utility, SesolaOptions);
      SSL_CTX_set_options (SslCtx, SesolaOptions);
   }
   else
   {
      /* see [.APPS]S_SERVER.C */
      SSL_CTX_set_options (SslCtx, SSL_OP_ALL);
      SSL_CTX_set_options (SslCtx, SesolaOptionsOff);
      /* no longer an option in v0.9.5 */
      /** SSL_CTX_set_options (SslCtx, SSL_OP_NON_EXPORT_FIRST); **/
   }

   ((SESOLA_CONTEXT*)svptr->SSLserverPtr)->SslCtx = (void*)SslCtx;

   return (true);
}

/*****************************************************************************/
/*
Configure the basics of the SSL client service (i.e. can originate outgoing SSL
sessions with SSL servers).  Used by the HTTP-SSL proxy (gateway) service.
*/

BOOL SesolaInitClientService (SERVICE_STRUCT *svptr)

{
   BOOL  CipherListSupplied;
   int  value;
   SSL_CTX  *SslCtx;
   SESOLA_CONTEXT  *scptr;

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

   if (WATCH_MODULE(WATCH_MOD_SESOLA))
      WatchThis (NULL, FI_LI, WATCH_MOD_SESOLA, "SesolaInitClientService()");

   if (!svptr->SSLclientPtr)
   {
      /* no client-specific context, use any server context */
      if (svptr->SSLserverPtr)
      {
         svptr->SSLclientPtr = VmGet (sizeof(SESOLA_CONTEXT)); 
         memcpy (svptr->SSLclientPtr,
                 svptr->SSLserverPtr,
                 sizeof(SESOLA_CONTEXT));
      }
   }

   if (!(scptr = (SESOLA_CONTEXT*)svptr->SSLclientPtr)) return (false);

   if (scptr->CipherList[0])
   {
      CipherListSupplied = true;
      scptr->CipherListPtr = scptr->CipherList;
   }
   else
   {
      if (SesolaDefaultCipherListPtr)
      {
         CipherListSupplied = true;
         scptr->CipherListPtr = SesolaDefaultCipherListPtr;
      }
      else
      {
         CipherListSupplied = false;
         scptr->CipherListPtr = "";
      }
   }

   if (scptr->CaFile[0])
      scptr->CaFilePtr = scptr->CaFile;
   else
   if (SesolaDefaultCaFilePtr)
      scptr->CaFilePtr = SesolaDefaultCaFilePtr;
   else
      scptr->CaFilePtr = "";

   if (CipherListSupplied)
      WriteFaoStdout ("-!AZ-I-CIPHER, (a/client) !AZ\n",
                      Utility, scptr->CipherListPtr);

   if (scptr->VerifyCA)
      WriteFaoStdout ("-!AZ-I-CAFILE, (a/client) !AZ\n",
                      Utility, scptr->CaFilePtr);

   if (strsame (scptr->VersionString, "SSLV2/V3", -1))
      scptr->Version = SESOLA_SSLV2 | SESOLA_SSLV3;
   else
   if (strsame (scptr->VersionString, "SSLV3", -1))
      scptr->Version = SESOLA_SSLV3;
   else
   if (strsame (scptr->VersionString, "SSLV2", -1))
      scptr->Version = SESOLA_SSLV2;
   else
   if (strsame (scptr->VersionString, "TLSV1", -1))
      scptr->Version = SESOLA_TLSV1;
   else
   if (scptr->VersionString[0])
   {
      WriteFaoStdout ("-!AZ-W-SSL, (a/client) unknown protocol\n\ \\!AZ\\\n",
                      Utility, scptr->VersionString);
      return (false);
   }
   else
      scptr->Version = SesolaSSLversion;

   if ((scptr->Version & SESOLA_SSLV2) &&
       (scptr->Version & SESOLA_SSLV3))
   {
      SslCtx = SSL_CTX_new (SSLv23_method());
      scptr->VersionStringPtr = "SSLV2/V3";
   }
   else
   if (scptr->Version & SESOLA_SSLV2)
   {
      SslCtx = SSL_CTX_new (SSLv2_method());
      scptr->VersionStringPtr = "SSLV2";
   }
   else
   if (scptr->Version & SESOLA_SSLV3)
   {
      SslCtx = SSL_CTX_new (SSLv3_method());
      scptr->VersionStringPtr = "SSLV3";
   }
   else
   if (scptr->Version & SESOLA_TLSV1)
   {
      SslCtx = SSL_CTX_new (TLSv1_method());
      scptr->VersionStringPtr = "TLSV1";
   }
   if (!SslCtx)
   {
      SesolaPrintOpenSslErrorList ();
      ErrorExitVmsStatus (0, "SSL_CTX_new()", FI_LI);
   }

   if (scptr->CipherListPtr[0])
      SSL_CTX_set_cipher_list (SslCtx, scptr->CipherListPtr);

   SSL_CTX_sess_set_cache_size (SslCtx, SesolaSessionCacheSize);
   SSL_CTX_set_timeout (SslCtx, SesolaSessionCacheTimeout*60);

   if (scptr->VerifyCA)
      SSL_CTX_set_verify (SslCtx, SSL_VERIFY_CLIENT_ONCE,
                          &SesolaCertVerifyCallback);
   else
      SSL_CTX_set_verify (SslCtx, SSL_VERIFY_NONE,
                          &SesolaCertVerifyCallback);

   SSL_CTX_set_verify_depth (SslCtx, SesolaDefaultVerifyDepth);

   if (scptr->CaFilePtr[0])
   {
      value = SSL_CTX_load_verify_locations (SslCtx,
                                             scptr->CaFilePtr,
                                             NULL);
      if (value) value = SSL_CTX_set_default_verify_paths (SslCtx);
      if (!value)
      {
         SesolaPrintOpenSslErrorList ();
         WriteFaoStdout (
"%-AZ-W-SSL, (a/client) CA verification not enabled\n",
                         Utility);
         return (false);
      }
   }

   /* for option values, see [.INCLUDE.OPENSSL]SSL.H */
   if (SesolaOptions)
   {
      WriteFaoStdout ("-!AZ-I-SSL, (a/client) options 0x!8XL\n",
                      Utility, SesolaOptions);
      SSL_CTX_set_options (SslCtx, SesolaOptions);
   }
   else
   {
      /* see [.APPS]S_SERVER.C */
      SSL_CTX_set_options (SslCtx, SSL_OP_ALL);
      SSL_CTX_set_options (SslCtx, SesolaOptionsOff);
      /* no longer an option in v0.9.5 */
      /** SSL_CTX_set_options (SslCtx, SSL_OP_NON_EXPORT_FIRST); **/
   }

   ((SESOLA_CONTEXT*)svptr->SSLclientPtr)->SslCtx = (void*)SslCtx;

   return (true);
}

/*****************************************************************************/
/*
If the private key in HTTPD$SSL_CERT, HTTPD$SSL_KEY, or the equivalent, does
not contain an embedded password (which is by far less "secure" against key
stealing) the private key callback set above results in this function being
called.  Output a message to the console, to the monitor status message buffer,
and optionally if enabled to OPCOM, requesting that a password be manually
supplied.  The password is input at the command line using the control
/DO=SSL=KEY=PASSWORD directive, which prompts for the password and then writes
it into the global section shared memory control directive buffer.  This
function continues to poll that buffer at one second intervals looking for the
indicator of the  password.  When found it copies it into the 'PasswdBuffer'
and returns the length.  If not supplied it eventually times-out resulting in
the startup being cancelled (which should then restart).  Note that POLLING
MUST BE USED because user-mode ASTs are disabled during service (network)
initialization, rendering lock delivery and the like possibilities of
notification unusable (at this mode) during this period.
*/

int SesolaPrivateKeyPasswd
(
char *PasswdBuffer,
int SizeOfPasswdBuffer,
int UnknownParam,
void *UserData
)
{
   int  cnt,
        PasswdLength;
   char  *cptr, *sptr, *zptr;
   SERVICE_STRUCT *svptr;

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

   if (WATCH_MODULE(WATCH_MOD_SESOLA))
      WatchThis (NULL, FI_LI, WATCH_MOD_SESOLA,
                 "SesolaPrivateKeyPasswd() !UL !UL",
                 SizeOfPasswdBuffer, UserData);

   /* indicates a private key password was required for this service */
   SesolaPrivateKeyPasswdRequested = true;

   svptr = (SERVICE_STRUCT*)UserData;

   /* ensure we start with a nice empty buffer */
   memset (HttpdGblSecPtr->PkPasswd, 0, sizeof(HttpdGblSecPtr->PkPasswd));
   PasswdBuffer[0] = '\0';

   /* wait for the password to magically appear!! (crude but effective) */
   for (cnt = SESOLA_PKPASSWD_REQUEST_SECONDS; cnt; cnt--)
   {
      if (!(cnt % 60))
      {
         WriteFaoStdout (
"%!AZ-I-PKPASSWD, !20%D, request !UL/!UL for private key password, !AZ//!AZ\n",
             Utility, 0,
             SesolaPrivateKeyPasswdAttempt, SESOLA_PKPASSWD_ATTEMPTS,
             svptr->RequestSchemeNamePtr, svptr->ServerHostPort);

         WriteFao (HttpdGblSecPtr->StatusMessage,
                   sizeof(HttpdGblSecPtr->StatusMessage), NULL,
"%!AZ-I-PKPASSWD, !20%D, \
!AZ request !UL/!UL for private key password, !AZ//!AZ",
             Utility, 0, InstanceNodeCurrent > 1 ? HttpdProcess.PrcNam : "",
             SesolaPrivateKeyPasswdAttempt, SESOLA_PKPASSWD_ATTEMPTS,
             svptr->RequestSchemeNamePtr, svptr->ServerHostPort);

         /* if any OPCOM at all is enabled then output this message */
         if (OpcomMessages)
            WriteFaoOpcom (
"%!AZ-I-PKPASSWD, request !UL/!UL for private key password, !AZ//!AZ",
             Utility,
             SesolaPrivateKeyPasswdAttempt, SESOLA_PKPASSWD_ATTEMPTS,
             svptr->RequestSchemeNamePtr, svptr->ServerHostPort);
      }

      sleep (1);

      /* if a password was supplied whilst sleeping */
      if (HttpdGblSecPtr->PkPasswd[0]) break;
   }
   HttpdGblSecPtr->StatusMessage[0] = '\0';

   if (!cnt) 
   {
      /* timed-out waiting for a response */
      ControlMessage ("timeout waiting for private key password");
      sleep (1);
      exit (SS$_CANCEL);
   }

   zptr = (sptr = PasswdBuffer) + SizeOfPasswdBuffer;
   for (cptr = HttpdGblSecPtr->PkPasswd;
        *cptr && sptr < zptr;
        *sptr++ = *cptr++);
   /* overflow just cancels the password */
   if (sptr > zptr) sptr = PasswdBuffer;
   *sptr = '\0';
   PasswdLength = sptr - PasswdBuffer;

   for (sptr = PasswdBuffer; *sptr && isspace(*sptr); sptr++);
   if (!*sptr && sptr > PasswdBuffer)
   {
      /* password comprising all spaces is used to abort the startup */
      ControlMessage ("empty private key password, aborting startup");
      /* just exit the process */
      ExitStatus = SS$_ABORT;
      HttpdExit (&ExitStatus);
      /* cancel any startup messages provided for the monitor */
      HttpdGblSecPtr->StatusMessage[0] = '\0';
      sys$delprc (0, 0);
   }

   /* remove the actual password from the control buffer */
   memset (HttpdGblSecPtr->PkPasswd, 0, sizeof(HttpdGblSecPtr->PkPasswd));
   ControlMessage ("password received");

   return (PasswdLength);
}

/*****************************************************************************/
/*
Session ID is seeded with two longwords of 64bit VMS time, then some
jiggery-pokery.  This should be non-predictable, non-sequential enough.
*/

SesolaSessionId (SSL *SslPtr)

{
   static unsigned long  SesolaSessionIdContext [4];

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

   if (WATCH_MODULE(WATCH_MOD_SESOLA))
      WatchThis (NULL, FI_LI, WATCH_MOD_SESOLA, "SesolaSessionId()");

   sys$gettim(&SesolaSessionIdContext[0]);
   SesolaSessionIdContext[3] += SesolaSessionIdContext[0];
   SesolaSessionIdContext[2] -= SesolaSessionIdContext[1];
   SesolaSessionIdContext[1] ^= SesolaSessionIdContext[2];
   /* unique even if within a single VMS "tick" */
   SesolaSessionIdContext[0] <<= 1;
   SSL_set_session_id_context (SslPtr, &SesolaSessionIdContext,
                                       sizeof(SesolaSessionIdContext));

   if (WATCH_MODULE(WATCH_MOD_SESOLA))
      WatchThis (NULL, FI_LI, WATCH_MOD_SESOLA,
                 "!16&H", &SesolaSessionIdContext);
}

/*****************************************************************************/
/*
OpenSSL calls this function at each stage during certificate verification
processing (client or CA).  It is used to report on that progress and to add
other processing as required.
*/

int SesolaCertVerifyCallback
(
int OkValue,
/* void* for convenience in compiling non-SSL version */
void *StoreCtxPtr
)
{
   int  ErrorNumber,
        ErrorDepth,
        VerifyDepth,
        VerifyMode,
        WatchThisType;
   char  String [512];
   REQUEST_STRUCT  *rqptr;
   SESOLA_STRUCT  *sesolaptr;
   SSL  *SslPtr;
   X509  *CertPtr;
   X509_STORE_CTX  *CtxPtr;

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

   if (WATCH_MODULE(WATCH_MOD_SESOLA))
      WatchThis (NULL, FI_LI, WATCH_MOD_SESOLA,
                 "SesolaCertVerifyCallback() !UL", OkValue);

   CtxPtr = (X509_STORE_CTX*)StoreCtxPtr;
   SslPtr = (SSL*)X509_STORE_CTX_get_app_data (CtxPtr);
   rqptr = (REQUEST_STRUCT*)SSL_get_ex_data (SslPtr, 0);

   CertPtr = X509_STORE_CTX_get_current_cert (CtxPtr);
   ErrorNumber = X509_STORE_CTX_get_error (CtxPtr);
   ErrorDepth = X509_STORE_CTX_get_error_depth (CtxPtr);
      
   VerifyMode = SSL_get_verify_mode (SslPtr);
   if (WATCH_MODULE(WATCH_MOD_SESOLA))
      WatchThis (NULL, FI_LI, WATCH_MOD_SESOLA, "VERIFY !UL", VerifyMode);
   if (!VerifyMode)
      return (1);
   else
   if (VerifyMode & SSL_VERIFY_PEER)
      sesolaptr = (SESOLA_STRUCT*)rqptr->rqNet.SesolaPtr;
   else
   if (VerifyMode & SSL_VERIFY_CLIENT_ONCE)
      sesolaptr = (SESOLA_STRUCT*)rqptr->ProxyTaskPtr->SesolaPtr;

   if (!sesolaptr)
   {
      ErrorNoticed (SS$_BUGCHECK, ErrorSanityCheck, FI_LI);
      return (0);
   }

   VerifyDepth = sesolaptr->CertVerifyDepth;

   sesolaptr->CertVerifyCallbackCount++;

   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 (WATCH_CAT && WatchThisType)
   {
      WatchThis (rqptr, FI_LI, WatchThisType,
                 "X509 VERIFY callback !UL !AZ !AZ",
                 sesolaptr->CertVerifyCallbackCount,
                 !(VerifyMode & SSL_VERIFY_FAIL_IF_NO_PEER_CERT) ?
                    "(no fail on error)" : "(fail on error)",
                 OkValue ? "OK" : "NOT-OK");
      X509_NAME_oneline (X509_get_issuer_name(CertPtr),
                         String, sizeof(String));
      WatchDataFormatted ("issuer: !AZ\n", String);
      X509_NAME_oneline (X509_get_subject_name(CertPtr),
                        String, sizeof(String));
      WatchDataFormatted ("subject: !AZ\n", String);
      ASN1_UTCTIME_print (SesolaBioMemPtr, X509_get_notBefore(CertPtr));
      BIO_gets (SesolaBioMemPtr, String, sizeof(String));
      WatchDataFormatted ("notBefore: !AZ\n", String);
      ASN1_UTCTIME_print (SesolaBioMemPtr, X509_get_notAfter(CertPtr));
      BIO_gets (SesolaBioMemPtr, String, sizeof(String));
      WatchDataFormatted ("notAfter: !AZ\n", String);
      SesolaCertFingerprint (CertPtr, &EVP_sha1, String, sizeof(String));
      WatchDataFormatted ("SHA1: !AZ\n", String);
      SesolaCertFingerprint (CertPtr, &EVP_md5, String, sizeof(String));
      WatchDataFormatted ("MD5: !AZ\n", String);
   }

   if (OkValue)
      sesolaptr->CertVerifyFailed = false;
   else
   {
      sesolaptr->CertVerifyFailed = true;
      if (WATCH_CAT && WatchThisType)
         WatchThis (rqptr, FI_LI, WatchThisType,
                    "X509 VERIFY error, !UL \"!AZ\"",
                    ErrorNumber, X509_verify_cert_error_string(ErrorNumber));
      if (ErrorDepth > VerifyDepth)
         if (WATCH_CAT && WatchThisType)
            WatchThis (rqptr, FI_LI, WatchThisType,
                       "X509 VERIFY certificate chain too long (!UL>!UL)",
                       ErrorDepth, VerifyDepth);
   }


   /* always OK if verifying peer certificate but not for authorization */
   if ((VerifyMode & SSL_VERIFY_PEER) &&
       !(VerifyMode & SSL_VERIFY_FAIL_IF_NO_PEER_CERT))
      return (1);

   return (OkValue);
}

/*****************************************************************************/
/*
Print out the OpenSSL error list.  For reporting errors associated with
certificate loading.  Based on [.CRYPTO.ERR]ERR_PRN.C.  The error number (based
on [.CRYPTO.ERR]ERR.H ERR_PACK macro) is a bit-vector comprising 31..24 (8
bits) the library code, 23..12 (12 bits) the function code 11..0 (12 bits)
the reason code.  A fatal error is the decimal value 32 (bit 6) and is used in
various bit-wise operations.
*/

SesolaPrintOpenSslErrorList ()

{
   int  LineNumber,
        Flags;
   unsigned long  ErrorNumber;
   char  *FileNamePtr,
         *DataStringPtr;
   char  Buffer [256];

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

   if (WATCH_MODULE(WATCH_MOD_SESOLA))
      WatchThis (NULL, FI_LI, WATCH_MOD_SESOLA,
                 "SesolaPrintOpenSslErrorList()");

   while (ErrorNumber =
          ERR_get_error_line_data (&FileNamePtr, &LineNumber,
                                   &DataStringPtr, &Flags))
   {
      WriteFaoStdout (
"-!AZ-W-SSL, \"!AZ\" (!AZ !SL)!&?\"\r\r!AZ!&?\"\r\r\n",
         Utility,
         ERR_error_string (ErrorNumber, Buffer),
         FileNamePtr, LineNumber,
         ((Flags & ERR_TXT_STRING)),
         ((Flags & ERR_TXT_STRING) ? DataStringPtr : ""),
         ((Flags & ERR_TXT_STRING)));
   }
}

/*****************************************************************************/
/*
Provides OpenSSL status information at various states of SSL processing via the
WATCH facility.
*/

#if WATCH_CAT

SesolaWatchInfoCallback
(
SSL *SslPtr,
int WhereExactly,
int value
)
{
   int  item;
   char  *cptr;
   REQUEST_STRUCT  *rqptr;

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

   if (WATCH_MODULE(WATCH_MOD_SESOLA))
      WatchThis (NULL, FI_LI, WATCH_MOD_SESOLA,
                 "SesolaWatchInfoCallback() !UL !SL", WhereExactly, value);

   rqptr = (REQUEST_STRUCT*)SSL_get_ex_data (SslPtr, 0);

   switch (WhereExactly & ~SSL_ST_MASK)
   {
      case SSL_ST_CONNECT : cptr = "SSL_CONNECT "; break;
      case SSL_ST_ACCEPT : cptr = "SSL_ACCEPT "; break;
      case SSL_ST_INIT : cptr = "SSL_INIT "; break;
      case SSL_ST_BEFORE : cptr = "SSL_BEFORE "; break;
      case SSL_ST_OK : cptr = "SSL_OK "; break;
      case SSL_ST_RENEGOTIATE : cptr = "SSL_RENEGOTIATE "; break;
      default : cptr = "";
   }

   switch (WhereExactly & SSL_ST_MASK)
   {
      case SSL_CB_ALERT :
         WatchThis (rqptr, FI_LI, WATCH_SESOLA, "!AZalert !AZ !AZ:!AZ",
                    cptr, (WhereExactly & SSL_CB_READ) ? "read" : "write",
                    SSL_alert_type_string_long(value),
                    SSL_alert_desc_string_long(value));
         break;
      case SSL_CB_LOOP :
         WatchThis (rqptr, FI_LI, WATCH_SESOLA, "!AZ!AZ",
                    cptr, SSL_state_string_long(SslPtr));
         break;
      case SSL_CB_EXIT :
         if (value == 0)
            WatchThis (rqptr, FI_LI, WATCH_SESOLA,
                       "!AZalert failed in !AZ",
                       cptr, SSL_state_string_long(SslPtr));
         else
         if (value < 0)
            WatchThis (rqptr, FI_LI, WATCH_SESOLA,
                       "!AZerror/blocking in !AZ",
                       cptr, SSL_state_string_long(SslPtr));
         break;
      case SSL_CB_READ :
         WatchThis (rqptr, FI_LI, WATCH_SESOLA, "!AZread !AZ",
                    cptr, SSL_state_string_long(SslPtr));
         break;
      case SSL_CB_WRITE :
         WatchThis (rqptr, FI_LI, WATCH_SESOLA, "!AZwrite !AZ",
                    cptr, SSL_state_string_long(SslPtr));
         break;
      case SSL_CB_HANDSHAKE_START :
         WatchThis (rqptr, FI_LI, WATCH_SESOLA, "!AZstart handshake", cptr);
         break;
      case SSL_CB_HANDSHAKE_DONE :
         WatchThis (rqptr, FI_LI, WATCH_SESOLA, "!AZend handshake", cptr);
         break;
      default :
         WatchThis (rqptr, FI_LI, WATCH_SESOLA, "!&X !AZ",
                    WhereExactly, SSL_state_string_long(SslPtr));
   }
}

#endif /* WATCH_CAT */

/*****************************************************************************/
/*
Provides OpenSSL Input/Output information each time an SSL read or write occurs
via the WATCH facility.  These macros can be found in [.CRYPTO.BIO]BIO.H
*/

int SesolaWatchBioCallback
(
BIO *bioptr,
int cmd,
char *argp,
int argi,
long argl,
long retval
)
{
#if WATCH_CAT

   REQUEST_STRUCT  *rqptr;

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

   rqptr = (REQUEST_STRUCT*)BIO_get_callback_arg (bioptr);

   if (WATCH_MODULE(WATCH_MOD_SESOLA))
   {
      WatchThis (rqptr, FI_LI, WATCH_MOD_SESOLA,
                 "SesolaWatchBioCallback() !&X !&X !SL !SL !SL",
                 cmd, argp, argi, argl, retval);

      switch (cmd & 0xf)
      {
         case BIO_CB_READ : 
            WatchThis (rqptr, FI_LI, WATCH_MOD_SESOLA,
                       "!&?after\rbefore\r READ !UL/!SL!&?\r (non-blocking)\r",
                       cmd & BIO_CB_RETURN, argi, retval, retval >= 0);
            break;
         case BIO_CB_WRITE : 
            WatchThis (rqptr, FI_LI, WATCH_MOD_SESOLA,
                       "!&?after\rbefore\r WRITE !UL/!SL!&?\r (non-blocking)\r",
                       cmd & BIO_CB_RETURN, argi, retval, retval >= 0);
            break;
         case BIO_CB_FREE : 
            WatchThis (rqptr, FI_LI, WATCH_MOD_SESOLA,
                       "!&?after\rbefore\r FREE !SL !SL",
                       cmd & BIO_CB_RETURN, argi, retval);
            break;
         case BIO_CB_CTRL : 
            WatchThis (rqptr, FI_LI, WATCH_MOD_SESOLA,
                       "!&?after\rbefore\r CTRL !SL !SL",
                       cmd & BIO_CB_RETURN, argi, retval);
            break;
         default : 
            WatchThis (rqptr, FI_LI, WATCH_MOD_SESOLA,
                       "!&?after\rbefore\r !&X !UL !SL",
                       cmd & BIO_CB_RETURN, cmd, argi, retval);
      }
      return (retval);
   }

   if (!(cmd & BIO_CB_RETURN)) return (retval);

   switch (cmd & 0xf)
   {
      case BIO_CB_READ : 
         WatchThis (rqptr, FI_LI, WATCH_SESOLA,
                    "READ !UL/!SL!&?\r (non-blocking)\r",
                    argi, retval, retval >= 0);
         break;
      case BIO_CB_WRITE : 
         WatchThis (rqptr, FI_LI, WATCH_SESOLA,
                    "WRITE !UL/!SL!&?\r (non-blocking)\r",
                    argi, retval, retval >= 0);
         break;
      case BIO_CB_FREE : 
         WatchThis (rqptr, FI_LI, WATCH_SESOLA,
                    "FREE !SL !SL", argi, retval);
         break;
      case BIO_CB_CTRL : 
         WatchThis (rqptr, FI_LI, WATCH_SESOLA,
                    "CTRL !SL !SL", argi, retval);
         break;
      default : 
         WatchThis (rqptr, FI_LI, WATCH_SESOLA,
                    "!&X !UL !SL", cmd, argi, retval);
   }

#endif /* WATCH_CAT */
   return (retval);
}

/*****************************************************************************/
/*
Provide WATCH information after final SSL session establishment show version
and cipher in use.
*/

SesolaWatchSession (SESOLA_STRUCT *sesolaptr)

{
#if WATCH_CAT

   int  AlgKeySize,
        UseKeySize;
   char  *CipherNamePtr,
         *VersionNamePtr;
   REQUEST_STRUCT  *rqptr;
   SSL  *SslPtr;
   SSL_CIPHER  *CipherPtr;

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

   if (!(rqptr = sesolaptr->RequestPtr))
      rqptr = sesolaptr->ProxyTaskPtr->RequestPtr;

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

   SslPtr = sesolaptr->SslPtr;
   CipherPtr = SSL_get_current_cipher (SslPtr);

   if (!(VersionNamePtr = (char*)SSL_get_version (SslPtr)))
      VersionNamePtr = "(null)";
   if (!(CipherPtr = SSL_get_current_cipher (SslPtr)))
   {
      CipherNamePtr = "(null)";
      AlgKeySize = UseKeySize = 0;
   }
   else
   {
      CipherNamePtr = (char*)SSL_CIPHER_get_name (CipherPtr);
      UseKeySize = SSL_CIPHER_get_bits(CipherPtr, &AlgKeySize);
   }

   WatchThis (rqptr, FI_LI, WATCH_SESOLA,
              "SESSION cached:!AZ protocol:!AZ cipher:!AZ keysize:!UL/!UL",
              SslPtr->hit ? "yes" : "no",
              VersionNamePtr, CipherNamePtr, UseKeySize, AlgKeySize);

#endif /* WATCH_CAT */
}

/*****************************************************************************/
/*
Provide som OpenSSL-internal error information.
(Based on code from [.CRYPTO.ERR]ERR_PRN.C)
*/

SesolaWatchErrors (SESOLA_STRUCT *sesolaptr)

{
#if WATCH_CAT

   int  cnt, flags,
        LineNumber;
   unsigned long  ErrorNumber;
   char  ErrorString [256],
         ModuleName [256];
   char  *cptr, *cptr1, *cptr2, *cptr3, *sptr,
         *FileNamePtr, *DataPtr;
   REQUEST_STRUCT  *rqptr;

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

   if (!(rqptr = sesolaptr->RequestPtr))
      if (sesolaptr->ProxyTaskPtr)
         rqptr = sesolaptr->ProxyTaskPtr->RequestPtr;

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

   while (cnt = ERR_get_error_line_data (&FileNamePtr, &LineNumber,
                                         &DataPtr, &flags))
   {
      ERR_error_string_n (cnt, ErrorString, sizeof(ErrorString));
      for (cptr1 = ErrorString; *cptr1 && *cptr1 != ':'; cptr1++);
      if (*cptr1) cptr1++;
      while (*cptr1 && *cptr1 != ':') cptr1++;
      if (*cptr1) cptr1++;
      for (cptr2 = cptr1; *cptr2 && *cptr2 != ':'; cptr2++);
      if (*cptr2) *cptr2++ = '\0';
      for (cptr3 = cptr2; *cptr3 && *cptr3 != ':'; cptr3++);
      if (*cptr3) *cptr3++ = '\0';
      for (cptr = FileNamePtr; *cptr; cptr++);
      while (cptr > FileNamePtr && *cptr != ']') cptr--;
      if (*cptr == ']') cptr++;
      sptr = ModuleName;
      while (*cptr && *cptr != ';') *sptr++ = *cptr++;
      *sptr = '\0';
      WatchThis (rqptr, FI_LI, WATCH_SESOLA,
                 "!AZ !AZ !AZ !AZ!&? \r\r(!AZ:!UL)",
                 cptr1, cptr2, cptr3,
                 flags & ERR_TXT_STRING ? DataPtr : "",
                 flags & ERR_TXT_STRING, ModuleName, LineNumber);
   }
#endif /* WATCH_CAT */
}

/*****************************************************************************/
/*
Peek at selected SesolaStruct data.  The 'rqptr' is the request doing the
peeking, the 'rqeptr' is the request being peeked at.
*/

SesolaWatchPeek
(
REQUEST_STRUCT *rqptr,
REQUEST_STRUCT *rqeptr
)
{
   static char  SesolaFao [] = 
"!33<->SslPtr!> protocol:!AZ cipher:!AZ keysize:!UL/!UL\n\
!33<->ClientCertPtr!> !&@\n\
!33<->CertVerifyFailed!> !&B\n\
!33<->ClientAstFunction!> !&A\n\
!33<->NetChannel!> !UL (!AZ)\n\
!33<->ReadInProgress!> !&B\n\
!33<->ReadCompleted!> !&B\n\
!33<->ReadAstFunction!> !&A\n\
!33<->ReadSesolaFunction!> !&A\n\
!33<->ReadDataSize!> !UL\n\
!33<->ReadIOsb!> !&S !UL\n\
!33<->WriteInProgress!> !&B\n\
!33<->WriteCompleted!> !&B\n\
!33<->WriteAstFunction!> !&A\n\
!33<->WriteSesolaFunction!> !&A\n\
!33<->WriteDataLength!> !UL\n\
!33<->WriteIOsb!> !&S !UL\n\
!33<->HTTPduringHandshake!> !&B\n\
!33<->RenegotiateState!> !UL\n\
!33<->ShutdownState!> !UL\n\
\n";

   int  status,
        AlgKeySize,
        UseKeySize;
   unsigned short  Length;
   unsigned long  *vecptr;
   unsigned long  FaoVector [32];
   char  *CipherNamePtr,
         *VersionNamePtr;
   char  Buffer [2048],
         CertFingerprintMD5 [64],
         CertFingerprintSHA1 [64],
         NetDevName [64],
         IssuerString [256],
         NotAfterString [32],
         SubjectString [256];
   SESOLA_STRUCT  *sesolaptr;
   SSL  *SslPtr;
   SSL_CIPHER  *CipherPtr;

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

   if (WATCHING(rqptr) && (WATCH_MODULE(WATCH_MOD_SESOLA)))
      WatchThis (rqptr, FI_LI, WATCH_MOD_SESOLA, "SesolaWatchPeek()\n");

   if (!rqeptr->rqNet.SesolaPtr) return;

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

   NetGetBgDevice (sesolaptr->NetChannel, NetDevName, sizeof(NetDevName));

   SslPtr = sesolaptr->SslPtr;
   if (!(VersionNamePtr = (char*)SSL_get_version (SslPtr)))
      VersionNamePtr = "(null)";
   if (!(CipherPtr = SSL_get_current_cipher (SslPtr)))
   {
      CipherNamePtr = "(null)";
      AlgKeySize = UseKeySize = 0;
   }
   else
   {
      CipherNamePtr = (char*)SSL_CIPHER_get_name (CipherPtr);
      UseKeySize = SSL_CIPHER_get_bits(CipherPtr, &AlgKeySize);
   }

   if (sesolaptr->ClientCertPtr)
   {
      X509_NAME_oneline (X509_get_issuer_name(sesolaptr->ClientCertPtr),
                         IssuerString, sizeof(SubjectString));
      X509_NAME_oneline (X509_get_subject_name(sesolaptr->ClientCertPtr),
                         SubjectString, sizeof(SubjectString));
      SesolaCertFingerprint (sesolaptr->ClientCertPtr, &EVP_sha1,
                             CertFingerprintSHA1, sizeof(CertFingerprintSHA1));
      SesolaCertFingerprint (sesolaptr->ClientCertPtr, &EVP_md5,
                             CertFingerprintMD5, sizeof(CertFingerprintMD5));
      ASN1_UTCTIME_print (SesolaBioMemPtr,
                          X509_get_notAfter(sesolaptr->ClientCertPtr));
      BIO_gets (SesolaBioMemPtr, NotAfterString, sizeof(NotAfterString));
   }

   vecptr = FaoVector;
   *vecptr++ = VersionNamePtr;
   *vecptr++ = CipherNamePtr;
   *vecptr++ = UseKeySize;
   *vecptr++ = AlgKeySize;

   if (sesolaptr->ClientCertPtr)
   {
      *vecptr++ =
"issuer=!AZ\n\
!33<!> subject=!AZ\n\
!33<!> notAfter=!AZ\n\
!33<!> SHA1=!AZ\n\
!33<!> MD5=!AZ\n";
      *vecptr++ = IssuerString;
      *vecptr++ = SubjectString;
      *vecptr++ = NotAfterString;
      *vecptr++ = CertFingerprintSHA1;
      *vecptr++ = CertFingerprintMD5;
   }
   else
      *vecptr++ = "0x00000000";
   *vecptr++ = sesolaptr->CertVerifyFailed;
   *vecptr++ = sesolaptr->ClientCertAstFunction;

   *vecptr++ = sesolaptr->NetChannel;
   *vecptr++ = NetDevName+1;
   *vecptr++ = sesolaptr->ReadInProgress;
   *vecptr++ = sesolaptr->ReadCompleted;
   *vecptr++ = sesolaptr->ReadAstFunction;
   *vecptr++ = sesolaptr->ReadSesolaFunction;
   *vecptr++ = sesolaptr->ReadDataSize;
   *vecptr++ = sesolaptr->ReadIOsb.Status;
   *vecptr++ = sesolaptr->ReadIOsb.Count;
   *vecptr++ = sesolaptr->WriteInProgress;
   *vecptr++ = sesolaptr->WriteCompleted;
   *vecptr++ = sesolaptr->WriteAstFunction;
   *vecptr++ = sesolaptr->WriteSesolaFunction;
   *vecptr++ = sesolaptr->WriteDataLength;
   *vecptr++ = sesolaptr->WriteIOsb.Status;
   *vecptr++ = sesolaptr->WriteIOsb.Count;
   *vecptr++ = sesolaptr->HTTPduringHandshake;
   *vecptr++ = sesolaptr->RenegotiateState;
   *vecptr++ = sesolaptr->ShutdownState;

   status = WriteFaol (Buffer, sizeof(Buffer), &Length, SesolaFao, &FaoVector);
   if (VMSnok (status) || status == SS$_BUFFEROVF)
      ErrorNoticed (status, "WriteFaol()", FI_LI);
   NetWrite (rqptr, 0, Buffer, Length);
}

/*****************************************************************************/
/*
Brief report on this or a virtual service SSL context statistics and any
current session information.
*/

SesolaReport
(
REQUEST_STRUCT *rqptr,
REQUEST_AST NextTaskFunction,
char *VirtualHostPort
)
{
   static char  VirtualFao [] =
"<FORM ACTION=\"!AZ\">\n\
<P><TABLE CELLPADDING=3 CELLSPACING=0 BORDER=1>\n\
<TR><TH>Virtual SSL Server</TH></TR>\n\
<TR><TD><NOBR>\n\
<SELECT NAME=virtual>\n\
<OPTION!&?\r SELECT\r VALUE=\"\">!&?none configured\rfor this service\r\n";

   static char  ServerInfoFao [] =
"</SELECT>\n\
<INPUT TYPE=submit VALUE=\" select \">\n\
</NOBR>\n\
</TD></TR>\n\
</TABLE>\n\
</FORM>\n\
<P><TABLE CELLPADDING=3 CELLSPACING=0 BORDER=1>\n\
<TR><TH>!AZ</TH></TR>\n\
<TR><TD>\n\
<TABLE CELLPADDING=0 CELLSPACING=5 BORDER=0>\n\
<TR><TH ALIGN=right>Version:</TH><TD>!AZ</TD></TR>\n\
<TR><TH ALIGN=right>Build:</TH><TD>!AZ</TD></TR>\n\
<TR><TH ALIGN=right>Protocol:</TH><TD>!AZ</TD></TR>\n\
<TR><TH ALIGN=right>Server&nbsp;Certificate:</TH><TD>!AZ</TD></TR>\n\
<TR><TH></TH><TD>\n\
<TABLE CELLPADDING=0 CELLSPACING=2 BORDER=0>\n\
<TR><TH ALIGN=right VALIGN=top>Issuer:&nbsp;</TH><TD><PRE>!AZ</PRE></TD></TR>\n\
<TR><TH ALIGN=right VALIGN=top>Subject:&nbsp;</TH><TD><PRE>!AZ</PRE></TD></TR>\n\
<TR><TH ALIGN=right>Expires:&nbsp;</TH><TD>!AZ</TD></TR>\n\
<TR><TH ALIGN=right VALIGN=top>SHA1:&nbsp;</TH><TD><TT>!AZ</TT></TD></TR>\n\
<TR><TH ALIGN=right VALIGN=top>MD5:&nbsp;</TH><TD><TT>!AZ</TT></TD></TR>\n\
</TABLE>\n\
</TD></TR>\n\
<TR><TH ALIGN=right>Private&nbsp;Key:</TH><TD>!AZ</TD></TR>\n\
<TR><TH ALIGN=right>CA&nbsp;Verify&nbsp;File:</TH><TD>!&@</TD></TR>\n\
<TR><TH ALIGN=right>CA&nbsp;Verify&nbsp;Depth:</TH><TD>!&@</TD></TR>\n\
<TR><TH ALIGN=right>Cipher&nbsp;List:</TH><TD>!AZ</TD></TR>\n\
<TR><TH ALIGN=right VALIGN=top>Supported&nbsp;Ciphers:</TH><TD VALIGN=top>\n\
<TABLE CELLPADDING=0 CELLSPACING=0 BORDER=0>\n\
<TR><TH></TH><TH ALIGN=left>Version</TH><TH ALIGN=left>Name</TH></TR>\n";

   static char  CiphersFao [] =
"<TR><TH ALIGN=right>!UL. &nbsp;</TH>\
<TD><TT>!AZ</TT> &nbsp;</TD>\
<TD><TT>!AZ</TT> &nbsp;</TD>\
</TR>\n";

   static char  EndCiphersFao [] =
"</TABLE>\n\
</TD></TR>\n\
<TR><TH ALIGN=right VALIGN=top>Service&nbsp;Accepts:</TH><TD VALIGN=top>\
<TABLE CELLPADDING=0 CELLSPACING=0 BORDER=0>\n\
<TR><TH ALIGN=right>Total:</TH><TD ALIGN=left> &nbsp;!UL</TD></TR>\n\
<TR><TH ALIGN=right> &nbsp;Completed:</TH><TD ALIGN=left> &nbsp;!UL</TD></TR>\n\
</TABLE>\n\
<TR><TH ALIGN=right VALIGN=top>Session&nbsp;Cache:</TH><TD VALIGN=top>\
<TABLE CELLPADDING=0 CELLSPACING=0 BORDER=0>\n\
<TR><TH ALIGN=right>Size:</TH><TD ALIGN=left> &nbsp;!UL</TD></TR>\n\
<TR><TH ALIGN=right>Current:</TH><TD ALIGN=left> &nbsp;!UL</TD></TR>\n\
<TR><TH ALIGN=right>Full:</TH><TD ALIGN=left> &nbsp;!UL</TD></TR>\n\
<TR><TH ALIGN=right>Hits:</TH><TD ALIGN=left> &nbsp;!UL</TD></TR>\n\
<TR><TH ALIGN=right>Misses:</TH><TD ALIGN=left> &nbsp;!UL</TD></TR>\n\
<TR><TH ALIGN=right>&nbsp;Timeouts:</TH><TD ALIGN=left> &nbsp;!UL</TD></TR>\n\
</TABLE>\n\
</TD></TR>\n";

   static char  EndServerInfoFao [] =
"</TABLE>\n\
</TD></TR>\n\
</TABLE>\n";

   static char  CurrentSessionFao [] =
"<P><TABLE CELLPADDING=3 CELLSPACING=0 BORDER=1>\n\
<TR><TH>Current Session</TH></TR>\n\
<TR><TD VALIGN=top>\n\
<TABLE CELLPADDING=0 CELLSPACING=5 BORDER=0>\n\
<TR><TH ALIGN=right>Session:</TH>\
<TD>!UL hit!%s since !AZ, timeout (!SL) at !AZ</TD></TR>\n";

   static char  ClientCertFao [] =
"<TR><TH ALIGN=right VALIGN=top>Client&nbsp;Certificate:</TH><TD>\n\
<TABLE CELLPADDING=0 CELLSPACING=2 BORDER=0>\n\
<TR><TH ALIGN=right VALIGN=top>Issuer:&nbsp;</TH><TD><PRE>!AZ</PRE></TD></TR>\n\
<TR><TH ALIGN=right VALIGN=top>Subject:&nbsp;</TH><TD><PRE>!AZ</PRE></TD></TR>\n\
<TR><TH ALIGN=right>Begins:&nbsp;</TH><TD>!AZ</TD></TR>\n\
<TR><TH ALIGN=right>Expires:&nbsp;</TH><TD>!AZ</TD></TR>\n\
<TR><TH ALIGN=right VALIGN=top>SHA1:&nbsp;</TH><TD><TT>!AZ</TT></TD></TR>\n\
<TR><TH ALIGN=right VALIGN=top>MD5:&nbsp;</TH><TD><TT>!AZ</TT></TD></TR>\n\
</TABLE>\n\
</TD></TR>\n";

   static char  ClientCertNoneFao [] =
"<TR><TH ALIGN=right>Client&nbsp;Certificate:</TH>\
<TD>!AZ HREF=\"!AZ\">view a client certificate!AZ</TD></TR>\n";

   static char  CurrentSessionEndFao [] =
"</TABLE>\n\
</TD></TR>\n";

   static char  EndPageFao [] =
"</TABLE>\n\
</BODY>\n\
</HTML>\n";

   int  status,
        AlgKeySize,
        Count,
        HttpsCount,
        SessionHits,
        SessionTimeout,
        SessionTimeCSec,
        SessionTimeoutCSec,
        Total,
        UseKeySize;
   char  *cptr, *sptr, *zptr;
   unsigned long  BinaryTime [2],
                  /* WARNING: really does need to be fairly large!! */
                  FaoVector [256],
                  ResultTime [2];
   unsigned long  *vecptr;
   char  CertString [256],
         CertCaString [256],
         CertDnString [256],
         CertFingerprintMD5 [64],
         CertFingerprintSHA1 [64],
         CertNotAfterString [32],
         CertNotBeforeString [32],
         TimeString [32],
         TimeoutString [32];
   SESOLA_STRUCT  *sesolaptr;
   SERVICE_STRUCT  *svptr,
                   *tsvptr;
   struct tm  *tmptr;
   SESOLA_CONTEXT  *scptr;
   SSL  *SslPtr;
   SSL_CTX  *SslCtx;
   SSL_CIPHER  *CipherPtr;
   SSL_SESSION  *SessionPtr;
   STACK_OF(SSL_CIPHER)  *CipherStackPtr;
   X509  *ServerCertPtr;

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

   /* as this is also called as an AST (!!) avoid WATCHing parameters */
   if (WATCHING(rqptr) && (WATCH_MODULE(WATCH_MOD_SESOLA)))
      WatchThis (rqptr, FI_LI, WATCH_MOD_SESOLA, "SesolaReport()");

   if (!(sesolaptr = (SESOLA_STRUCT*)rqptr->rqNet.SesolaPtr))
   {
      rqptr->rqResponse.HttpStatus = 403;
      ErrorGeneral (rqptr, "Only available via an SSL service!", FI_LI);
      SysDclAst (NextTaskFunction, rqptr);
      return;
   }

   /* unbuffer these if called from an SSL renegotiate AST (see below) */
   if (sesolaptr->ReportNextTaskFunction)
   {
      NextTaskFunction = sesolaptr->ReportNextTaskFunction;
      VirtualHostPort = sesolaptr->ReportVirtualHostPort;
   }

   /* now (!!) we can report "parameters" */
   if (WATCHING(rqptr) && (WATCH_MODULE(WATCH_MOD_SESOLA)))
      WatchThis (rqptr, FI_LI, WATCH_MOD_SESOLA, "!&A !&Z",
                 NextTaskFunction, VirtualHostPort);

   if (VirtualHostPort[0])
   {
      for (svptr = ServiceListHead; svptr; svptr = svptr->NextPtr)
         if (strsame (svptr->ServerHostPort, VirtualHostPort, -1)) break;
      if (!svptr)
      {
         rqptr->rqResponse.HttpStatus = 403;
         ErrorGeneral (rqptr, "No such virtual service.", FI_LI);
         SysDclAst (NextTaskFunction, rqptr);
         return;
      }
   }
   else
      svptr = rqptr->ServicePtr;

   if (svptr->RequestScheme != SCHEME_HTTPS ||
       !((SESOLA_CONTEXT*)svptr->SSLserverPtr)->SslCtx)
   {
      rqptr->rqResponse.HttpStatus = 403;
      ErrorGeneral (rqptr, "Only available for an SSL service!", FI_LI);
      SysDclAst (NextTaskFunction, rqptr);
      return;
   }

   if (strsame (rqptr->rqHeader.PathInfoPtr, ADMIN_REPORT_SSL_CLIENT, -1))
   {
      /*
         Include any client certificate information.
         Note that if SesolaClientCert() completes asynchronously this 
         function will be called again as an AST - with only one argument!!
         (the 'rqptr')  This is not a major issue (apart for the purists ;^)
         as the 'NextTaskFunction' VirtualHostPort is buffered and
         unbuffered around such a call here.  (I could have made it a variable
         length argument function, but this should work just as well!)
      */
      if (!sesolaptr->ReportClientCertState)
      {
         sesolaptr->ReportClientCertState = 1;
         sesolaptr->ReportNextTaskFunction = NextTaskFunction;
         strzcpy (sesolaptr->ReportVirtualHostPort, VirtualHostPort,
                  sizeof(sesolaptr->ReportVirtualHostPort));
         /* using renegotiation, cancel any existing certificate */
         status = SesolaClientCert (rqptr, SESOLA_VERIFY_PEER_NONE,
                                    &SesolaReport);
         if (status == SESOLA_VERIFY_PEER_PENDING) return;
      }
      if (sesolaptr->ReportClientCertState == 1)
      {
         sesolaptr->ReportClientCertState = 2;
         /* using (more) renegotiation, get a(nother) certificate */
         status = SesolaClientCert (rqptr, SESOLA_VERIFY_PEER_OPTIONAL,
                                    &SesolaReport);
         if (status == SESOLA_VERIFY_PEER_PENDING) return;
      }
   }

   /**********/
   /* report */
   /**********/

   /* the sesola service context */
   scptr = (SESOLA_CONTEXT*)svptr->SSLserverPtr;

   /* this is the service's context - not the current session's! */
   SslCtx = ((SESOLA_CONTEXT*)svptr->SSLserverPtr)->SslCtx;

   /* get the service's certificate and ciphers - not the current session's! */
   SslPtr = SSL_new (SslCtx);
   if (!SslPtr)
   {
      ErrorNoticed (0, "SSL_new() failed", FI_LI); 
      ErrorGeneral (rqptr, "SSL_new() failed.", FI_LI);
      SysDclAst (NextTaskFunction, rqptr);
      return;
   }
   ServerCertPtr = SSL_get_certificate (SslPtr);
   CipherStackPtr = SSL_get_ciphers (SslPtr);
   SSL_free (SslPtr);

   /* 'ServerCertPtr' has been initialized above */
   X509_NAME_oneline (X509_get_issuer_name(ServerCertPtr),
                      CertString, sizeof(CertString));
   SesolaReportFormatCertDn (CertString, CertCaString, sizeof(CertCaString));

   X509_NAME_oneline (X509_get_subject_name(ServerCertPtr),
                      CertString, sizeof(CertString));
   SesolaReportFormatCertDn (CertString, CertDnString, sizeof(CertDnString));

   ASN1_UTCTIME_print (SesolaBioMemPtr, X509_get_notAfter(ServerCertPtr));
   BIO_gets (SesolaBioMemPtr, CertNotAfterString, sizeof(CertNotAfterString));

   SesolaCertFingerprint (ServerCertPtr, &EVP_sha1,
                          CertFingerprintSHA1, sizeof(CertFingerprintSHA1));
   SesolaCertFingerprint (ServerCertPtr, &EVP_md5,
                          CertFingerprintMD5, sizeof(CertFingerprintMD5));

   /* count up the number of virtual SSL services configured */
   HttpsCount = 0;
   for (tsvptr = ServiceListHead; tsvptr; tsvptr = tsvptr->NextPtr)
      if (tsvptr->RequestScheme == SCHEME_HTTPS &&
          ((SESOLA_CONTEXT*)svptr->SSLserverPtr)->SslCtx)
         HttpsCount++;

   rqptr->rqResponse.PreExpired = PRE_EXPIRE_ADMIN;
   RESPONSE_HEADER_200_HTML (rqptr);
   AdminPageTitle (rqptr, "SSL Report");

   vecptr = FaoVector;
   *vecptr++ = ADMIN_REPORT_SSL;
   *vecptr++ = VirtualHostPort[0];
   *vecptr++ = (HttpsCount == 1);
   status = NetWriteFaol (rqptr, VirtualFao, &FaoVector);
   if (VMSnok (status)) ErrorNoticed (status, "NetWriteFaol()", FI_LI);

   if (HttpsCount > 1)
   {
      for (tsvptr = ServiceListHead; tsvptr; tsvptr = tsvptr->NextPtr)
      {
         if (tsvptr->RequestScheme != SCHEME_HTTPS ||
             !((SESOLA_CONTEXT*)tsvptr->SSLserverPtr)->SslCtx)
            continue;

         vecptr = FaoVector;
         *vecptr++ = tsvptr->ServerHostPort;
         *vecptr++ = strsame (tsvptr->ServerHostPort, VirtualHostPort, -1);
         *vecptr++ = tsvptr->ServerHostPort;

         status = NetWriteFaol (rqptr,
                     "<OPTION VALUE=\"!AZ\"!&? SELECTED\r\r>!AZ\n",
                     &FaoVector);
         if (VMSnok (status)) ErrorNoticed (status, "NetWriteFaol()", FI_LI);
      }
   }

   vecptr = FaoVector;

   *vecptr++ = svptr->ServerHostPort;
   *vecptr++ = SSLeay_version (SSLEAY_VERSION);
   cptr = (char*)SSLeay_version (SSLEAY_BUILT_ON);
   if (sptr = strstr (cptr, "uilt on"))
   {
      cptr = sptr + 7;
      while (*cptr && (*cptr == ' ' || *cptr == ':')) cptr++;
   }
   *vecptr++ = cptr;
   *vecptr++ = SesolaSSLversionStringPtr;
   if (scptr->CertFilePtr[0])
      *vecptr++ = scptr->CertFilePtr;
   else
      *vecptr++ = "<I>(internal)</I>";
   *vecptr++ = CertCaString;
   *vecptr++ = CertDnString;
   *vecptr++ = CertNotAfterString;
   *vecptr++ = CertFingerprintSHA1;
   *vecptr++ = CertFingerprintMD5;
   if (scptr->KeyFilePtr[0])
      *vecptr++ = scptr->KeyFilePtr;
   else
      *vecptr++ = "<I>(internal)</I>";
   if (scptr->CaFilePtr[0])
   {
      *vecptr++ =
"!AZ&nbsp;\
&nbsp;&nbsp;[<A HREF=\"!AZ?do=report&virtual=!AZ\">report</A>]\
&nbsp;&nbsp;[<A HREF=\"!AZ?virtual=!AZ\">list</A>]\
&nbsp;&nbsp;[<A HREF=\"!AZ?do=edit&virtual=!AZ\">edit</A>]";
      *vecptr++ = scptr->CaFilePtr;
      *vecptr++ = ADMIN_REPORT_SSL_CA;
      *vecptr++ = VirtualHostPort;
      *vecptr++ = ADMIN_REPORT_SSL_CA;
      *vecptr++ = VirtualHostPort;
      *vecptr++ = ADMIN_REVISE_SSL_CA;
      *vecptr++ = VirtualHostPort;
      *vecptr++ = "!UL";
      *vecptr++ = SSL_CTX_get_verify_depth (SslCtx);
   }
   else
   {
      *vecptr++ = "<I>(none)</I>";
      *vecptr++ = "<I>(none)</I>";
   }

   if (scptr->CipherListPtr[0])
      *vecptr++ = scptr->CipherListPtr;
   else
      *vecptr++ = "<I>(none)</I>";

   status = NetWriteFaol (rqptr, ServerInfoFao, &FaoVector);
   if (VMSnok (status)) ErrorNoticed (status, "NetWriteFaol()", FI_LI);

   if (svptr->RequestScheme != SCHEME_HTTPS)
   {
      status = NetWriteFaol (rqptr, "</BODY>\n</HTML>\n", NULL);
      if (VMSnok (status)) ErrorNoticed (status, "NetWriteFaol()", FI_LI);
      return;
   }

   /*********************/
   /* supported ciphers */
   /*********************/

   /* 'CipherStackPtr' has been initialized above */
   Total = sk_SSL_CIPHER_num (CipherStackPtr);
   for (Count = 0; Count < Total; Count++)
   {
      CipherPtr = (SSL_CIPHER *)sk_value (CipherStackPtr, Count);

      vecptr = FaoVector;
      *vecptr++ = Count + 1;
      *vecptr++ = SSL_CIPHER_get_version (CipherPtr);
      *vecptr++ = SSL_CIPHER_get_name (CipherPtr);

      status = NetWriteFaol (rqptr, CiphersFao, &FaoVector);
      if (VMSnok (status)) ErrorNoticed (status, "NetWriteFaol()", FI_LI);
   }

   vecptr = FaoVector;

   *vecptr++ = SSL_CTX_sess_accept (SslCtx);
   *vecptr++ = SSL_CTX_sess_accept_good (SslCtx);
   *vecptr++ = SSL_CTX_sess_get_cache_size (SslCtx);
   *vecptr++ = SSL_CTX_sess_number (SslCtx);
   *vecptr++ = SSL_CTX_sess_cache_full (SslCtx);
   *vecptr++ = SSL_CTX_sess_hits (SslCtx);
   *vecptr++ = SSL_CTX_sess_misses (SslCtx);
   *vecptr++ = SSL_CTX_sess_timeouts (SslCtx);

   status = NetWriteFaol (rqptr, EndCiphersFao, &FaoVector);
   if (VMSnok (status)) ErrorNoticed (status, "NetWriteFaol()", FI_LI);

   if (InstanceNodeConfig > 1) SesolaCacheStats (rqptr);

   status = NetWriteFaol (rqptr, EndServerInfoFao, NULL);
   if (VMSnok (status)) ErrorNoticed (status, "NetWriteFaol()", FI_LI);

   if (svptr->RequestScheme == SCHEME_HTTPS)
   {
      /*******************/
      /* current session */
      /*******************/

      SessionPtr = SSL_get_session (sesolaptr->SslPtr);
      SessionTimeCSec = SSL_get_time (SessionPtr);
      SessionTimeout = SSL_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");

      vecptr = FaoVector;

      *vecptr++ = SSL_CTX_sess_hits (sesolaptr->SslCtx);
      *vecptr++ = TimeString;
      *vecptr++ = SessionTimeout / 60;
      *vecptr++ = TimeoutString;

      status = NetWriteFaol (rqptr, CurrentSessionFao, &FaoVector);
      if (VMSnok (status)) ErrorNoticed (status, "NetWriteFaol()", FI_LI);

      if (!sesolaptr->ClientCertPtr)
      {
         if (!sesolaptr->ReportNextTaskFunction)
            status = NetWriteFao (rqptr, ClientCertNoneFao,
                        "[<A", ADMIN_REPORT_SSL_CLIENT, "</A>]");
         else
            status = NetWriteFao (rqptr, ClientCertNoneFao,
                        "<!--", "", "--><I>(none)</I>");
         if (VMSnok (status)) ErrorNoticed (status, "NetWriteFaol()", FI_LI);
      }
      else
      {
         X509_NAME_oneline (X509_get_issuer_name(sesolaptr->ClientCertPtr),
                            CertString, sizeof(CertString));
         SesolaReportFormatCertDn (CertString, CertCaString,
                                   sizeof(CertCaString));

         X509_NAME_oneline (X509_get_subject_name(sesolaptr->ClientCertPtr),
                            CertString, sizeof(CertString));
         SesolaReportFormatCertDn (CertString, CertDnString,
                                   sizeof(CertDnString));

         ASN1_UTCTIME_print (SesolaBioMemPtr,
                             X509_get_notAfter(sesolaptr->ClientCertPtr));
         BIO_gets (SesolaBioMemPtr, CertNotAfterString,
                   sizeof(CertNotAfterString));

         ASN1_UTCTIME_print (SesolaBioMemPtr,
                             X509_get_notBefore(sesolaptr->ClientCertPtr));
         BIO_gets (SesolaBioMemPtr, CertNotBeforeString,
                   sizeof(CertNotBeforeString));

         SesolaCertFingerprint (sesolaptr->ClientCertPtr, &EVP_sha1,
                                CertFingerprintSHA1, sizeof(CertFingerprintSHA1));
         SesolaCertFingerprint (sesolaptr->ClientCertPtr, &EVP_md5,
                                CertFingerprintMD5, sizeof(CertFingerprintMD5));

         vecptr = FaoVector;
         *vecptr++ = CertCaString;
         *vecptr++ = CertDnString;
         *vecptr++ = CertNotBeforeString;
         *vecptr++ = CertNotAfterString;
         *vecptr++ = CertFingerprintSHA1;
         *vecptr++ = CertFingerprintMD5;

         status = NetWriteFaol (rqptr, ClientCertFao, &FaoVector);
         if (VMSnok (status)) ErrorNoticed (status, "NetWriteFaol()", FI_LI);
      }

      /* use the Apache SSL variables as a basis for session information */
      rqptr->rqPathSet.CgiPrefixPtr = "";
      status = SesolaCgiVariablesApacheModSsl (rqptr, CGI_VARIABLE_STREAM);
      if (VMSnok (status))
      {
         SysDclAst (NextTaskFunction, rqptr);
         return;
      }
      cptr = rqptr->rqCgi.BufferPtr;
      for (;;)
      {
         if (!*(short*)cptr) break;        
         vecptr = FaoVector;
         cptr += sizeof(short);
         if (memcmp (cptr, "SSL_", 4))
         {
            while (*cptr) cptr++;
            cptr++;
            continue;
         }
         zptr = sptr = (cptr += 4);
         if (*sptr != '=') *zptr++ = *sptr++;
         while (*sptr && *sptr != '=') *zptr++ = tolower(*sptr++);
         *vecptr++ = sptr - cptr;
         *vecptr++ = cptr;
         if (*sptr) sptr++;
         cptr = sptr;
         while (*sptr) sptr++;
         *vecptr++ = sptr - cptr;
         *vecptr++ = cptr;
         NetWriteFaol (rqptr,
"<TR><TH ALIGN=right VALIGN=top>!#AZ:</TH><TD>!#&;AZ</TD></TR>\n",
                       &FaoVector);
         cptr = ++sptr;
      }

      status = NetWriteFaol (rqptr, CurrentSessionEndFao, NULL);
      if (VMSnok (status)) ErrorNoticed (status, "NetWriteFaol()", FI_LI);
   }

   status = NetWriteFaol (rqptr, EndPageFao, NULL);
   if (VMSnok (status)) ErrorNoticed (status, "NetWriteFaol()", FI_LI);

   SysDclAst (NextTaskFunction, rqptr);
}

/*****************************************************************************/
/*
Derived from [.CRYPTO.X509]X509_LU.C using hints from X509_STORE_add_cert().
List the CA certificates in the client certificate verification store.
*/

SesolaReportCA
(
REQUEST_STRUCT *rqptr,
REQUEST_AST NextTaskFunction,
char *VirtualHostPort
)
{
   static char  BeginPageFao [] =
"<P><TABLE CELLPADDING=3 CELLSPACING=0 BORDER=1>\n\
<TR><TH>!AZ</TH></TR>\n\
<TR><TD>\n\
<TABLE CELLPADDING=0 CELLSPACING=5 BORDER=0>\n\
<TR><TH ALIGN=right>CA&nbsp;Verify&nbsp;File:</TH><TD>!AZ</TD></TR>\n\
<TR><TH ALIGN=right>CA&nbsp;Verify&nbsp;Depth:</TH><TD>!UL</TD></TR>\n";

   static char  CertStoreNull [] =
"<TR><TD><I>&nbsp;(none)</I></TD></TR>\n";

   static char  CertFao [] =
"<TR><TD COLSPAN=2><HR SIZE=1 WIDTH=100% ALIGN=left NOSHADE></TD></TR>\n\
<TR><TD COLSPAN=2>\n\
<TABLE CELLPADDING=0 CELLSPACING=2 BORDER=0>\n\
<TR><TH VALIGN=top>!UL.&nbsp;&nbsp;</TH>\
<TH ALIGN=right VALIGN=top>Issuer:&nbsp;</TH>\
<TD><PRE>!AZ</PRE></TD></TR>\n\
<TR><TH></TH><TH ALIGN=right VALIGN=top>Subject:&nbsp;</TH>\
<TD><PRE>!AZ</PRE></TD></TR>\n\
<TR><TH></TH><TH ALIGN=right>Begins:&nbsp;</TH><TD>!AZ</TD></TR>\n\
<TR><TH></TH><TH ALIGN=right>Expires:&nbsp;</TH><TD>!AZ</TD></TR>\n\
</TABLE>\n\
</TD></TR>\n";

   static char  EndPageFao [] =
"<TR><TD COLSPAN=2><HR SIZE=1 WIDTH=100% ALIGN=left NOSHADE></TD></TR>\n\
<TR><TD COLSPAN=2><NOBR>\
[<A HREF=\"!AZ?do=edit&virtual=!AZ\">Edit</A>]&nbsp;&nbsp;\
[<A HREF=\"!AZ?virtual=!AZ\">List</A>]&nbsp;&nbsp;\
[<A HREF=\"!AZ\">Reload</A>]\
</NOBR></TD></TR>\n\
</TABLE>\n\
</TD></TR>\n\
</TABLE>\n\
</BODY>\n\
</HTML>\n";

   int  idx,
        status,
        Count;
   unsigned long  FaoVector [16];
   unsigned long  *vecptr;
   char  CertString [256],
         CertCaString [256],
         CertDnString [256],
         CertNotAfterString [32],
         CertNotBeforeString [32];
   SERVICE_STRUCT  *svptr;
   SESOLA_CONTEXT  *scptr;
   SSL_CTX  *SslCtx;
   X509  *CertPtr;
   X509_OBJECT  *CertObjectPtr;
   X509_STORE  *CertStorePtr;

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

   if (WATCHING(rqptr) && (WATCH_MODULE(WATCH_MOD_SESOLA)))
      WatchThis (rqptr, FI_LI, WATCH_MOD_SESOLA, "SesolaReportCA() !&A !AZ",
                 NextTaskFunction, VirtualHostPort);

   if (VirtualHostPort[0])
   {
      for (svptr = ServiceListHead; svptr; svptr = svptr->NextPtr)
         if (strsame (svptr->ServerHostPort, VirtualHostPort, -1)) break;
      if (!svptr)
      {
         rqptr->rqResponse.HttpStatus = 403;
         ErrorGeneral (rqptr, "No such virtual service.", FI_LI);
         SysDclAst (NextTaskFunction, rqptr);
         return;
      }
   }
   else
      svptr = rqptr->ServicePtr;

   if (svptr->RequestScheme != SCHEME_HTTPS)
   {
      rqptr->rqResponse.HttpStatus = 403;
      ErrorGeneral (rqptr, "Only available for an SSL service!", FI_LI);
      SysDclAst (NextTaskFunction, rqptr);
      return;
   }

   /* the sesola service context */
   scptr = (SESOLA_CONTEXT*)svptr->SSLserverPtr;

   /* this is the service's context - not the current session's! */
   SslCtx = ((SESOLA_CONTEXT*)svptr->SSLserverPtr)->SslCtx;

   rqptr->rqResponse.PreExpired = PRE_EXPIRE_ADMIN;
   RESPONSE_HEADER_200_HTML (rqptr);
   AdminPageTitle (rqptr, "SSL Report, CA Verification");

   vecptr = FaoVector;
   *vecptr++ = svptr->ServerHostPort;
   *vecptr++ = scptr->CaFilePtr;
   *vecptr++ = SSL_CTX_get_verify_depth (SslCtx);
   status = NetWriteFaol (rqptr, BeginPageFao, &FaoVector);
   if (VMSnok (status)) ErrorNoticed (status, "NetWriteFaol()", FI_LI);

   if (!(CertStorePtr = SSL_CTX_get_cert_store (SslCtx)))
   {
      status = NetWriteFaol (rqptr, CertStoreNull, NULL);
      if (VMSnok (status)) ErrorNoticed (status, "NetWriteFao()", FI_LI);
   }
   else
   {
      Count = 0;
      for (idx = 0; idx < sk_X509_OBJECT_num(CertStorePtr->objs); idx++)
      {
         CertObjectPtr = sk_X509_OBJECT_value (CertStorePtr->objs, idx);
         if (CertObjectPtr->type != X509_LU_X509) continue;

         Count++;
         CertPtr = (X509*)CertObjectPtr->data.x509;

         X509_NAME_oneline (X509_get_issuer_name(CertPtr),
                            CertString, sizeof(CertString));
         SesolaReportFormatCertDn (CertString, CertCaString,
                                   sizeof(CertCaString));

         X509_NAME_oneline (X509_get_subject_name(CertPtr),
                            CertString, sizeof(CertString));
         SesolaReportFormatCertDn (CertString, CertDnString,
                                   sizeof(CertDnString));

         ASN1_UTCTIME_print (SesolaBioMemPtr, X509_get_notAfter(CertPtr));
         BIO_gets (SesolaBioMemPtr, CertNotAfterString,
                   sizeof(CertNotAfterString));

         ASN1_UTCTIME_print (SesolaBioMemPtr, X509_get_notBefore(CertPtr));
         BIO_gets (SesolaBioMemPtr, CertNotBeforeString,
                   sizeof(CertNotBeforeString));

         vecptr = FaoVector;
         *vecptr++ = Count;
         *vecptr++ = CertCaString;
         *vecptr++ = CertDnString;
         *vecptr++ = CertNotBeforeString;
         *vecptr++ = CertNotAfterString;

         status = NetWriteFaol (rqptr, CertFao, &FaoVector);
         if (VMSnok (status)) ErrorNoticed (status, "NetWriteFaol()", FI_LI);
      }
   }

   vecptr = FaoVector;
   *vecptr++ = ADMIN_REVISE_SSL_CA;
   *vecptr++ = svptr->ServerHostPort;
   *vecptr++ = ADMIN_REPORT_SSL_CA;
   *vecptr++ = svptr->ServerHostPort;
   *vecptr++ = ADMIN_CONTROL_SSL_CA_LOAD;

   status = NetWriteFaol (rqptr, EndPageFao, &FaoVector);
   if (VMSnok (status)) ErrorNoticed (status, "NetWriteFaol()", FI_LI);

   SysDclAst (NextTaskFunction, rqptr);
}

/*****************************************************************************/
/*
Command-line CA verification file reload function.
*/

SesolaControlReloadCA ()

{
   int  cnt, value,
        ErrorCount,
        SSLcount,
        ReloadCount;
   SERVICE_STRUCT  *svptr;
   SESOLA_CONTEXT  *scptr;
   SSL_CTX  *SslCtx;
   X509_STORE  *CertStorePtr;

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

   if (WATCH_MODULE(WATCH_MOD_SESOLA))
      WatchThis (NULL, FI_LI, WATCH_MOD_SESOLA, "SesolaControlReloadCA()");

   /* enable SYSPRV to allow access to a possibly protected file */
   sys$setprv (1, &SysPrvMask, 0, 0);

   ErrorCount = SSLcount = ReloadCount = 0;
   for (svptr = ServiceListHead; svptr; svptr = svptr->NextPtr)
   {
      for (cnt = 0; cnt < 2; cnt++)
      {
         if (cnt)
            scptr = (SESOLA_CONTEXT*)svptr->SSLclientPtr;
         else
            scptr = (SESOLA_CONTEXT*)svptr->SSLserverPtr;

         if (!scptr) continue;
         if (!scptr->SslCtx) continue;

         SSLcount++;
         if (!scptr->CaFilePtr[0]) continue;

         SslCtx = (SSL_CTX*)scptr->SslCtx;
         if (CertStorePtr = SslCtx->cert_store)
         {
            /* empty the current CA certificate store */
            X509_STORE_free (CertStorePtr);
            SslCtx->cert_store = X509_STORE_new ();
         }
         /* load the file in afresh */
         value = SSL_CTX_load_verify_locations (SslCtx,
                                                scptr->CaFilePtr,
                                                NULL);
         if (value) value = SSL_CTX_set_default_verify_paths (SslCtx);
         if (value)
            ReloadCount++;
         else
         {
            ErrorCount++;
            WriteFaoStdout ("%!AZ-W-SSL, !AZ\n \\!AZ\\\n",
                            Utility, svptr->ServerHostPort, scptr->CaFilePtr);
            SesolaPrintOpenSslErrorList ();
            WriteFaoStdout ("%!AZ-W-SSL, client verification not enabled\n",
                            Utility);
         }
      }
   }

   sys$setprv (0, &SysPrvMask, 0, 0);

   WriteFaoStdout (
"%!AZ-I-SSL, reloaded CAs for !UL of !UL SSL contexts!&@\n",
                   Utility, ReloadCount, SSLcount,
                   ErrorCount ? ", !UL error!%s" : "", ErrorCount);

   if (OpcomMessages & OPCOM_HTTPD ||
       OpcomMessages & OPCOM_AUTHORIZATION)
      WriteFaoOpcom (
"%!AZ-I-SSL, reloaded CAs for !UL of !UL SSL contexts!&@",
                     Utility, ReloadCount, SSLcount,
                     ErrorCount ? ", !UL error!%s" : "", ErrorCount);
}

/*****************************************************************************/
/*
Just put a newline the certificate DN format at each component (meant to be
placed inside <PRE></PRE>.
*/

SesolaReportFormatCertDn
(
char *InputDn,
char *OutputDn,
int SizeOfOutputDn
)
{
   char  *cptr, *sptr, *zptr;

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

   if (WATCH_MODULE(WATCH_MOD_SESOLA))
      WatchThis (NULL, FI_LI, WATCH_MOD_SESOLA,
                 "SesolaReportFormatCertDn() !AZ", InputDn);

   zptr = (sptr = OutputDn) + SizeOfOutputDn - 1;
   for (;;)
   {
      cptr = SesolaParseCertDn (InputDn, NULL);
      if (!cptr) break;
      if (sptr > OutputDn && sptr < zptr) *sptr++ = '\n';
      while (*cptr && sptr < zptr) *sptr++ = *cptr++;
   }
   *sptr = '\0';

   if (WATCH_MODULE(WATCH_MOD_SESOLA))
      WatchThis (NULL, FI_LI, WATCH_MOD_SESOLA, "!AZ", OutputDn);
}

/*****************************************************************************/
/*
Performs two functions parsing X.509 Distinguished Names.

First, if supplied with a DN record string using 'RecordNamePtr' it searches
for the matching record in the string supplied by 'StringPtr'.  If 
found it returns a pointer to the full record (e.g. "/OU=Testing Only").  If
not found (and any other condition) it returns a NULL.  'RecordNamePtr' can be
"/OU=", "/CN=", etc., with or without any string following on from the equate
symbol.

The second use is to progressively parse a DN string, returning each of the
full records with each call, until the string is exhausted and a NULL is
returned.  To reset the parse call with 'StringPtr' a NULL.

Needless-to-say (because of the use of static storage) this function is not
reentrant!!  Maximum record value size is 127 characters.
*/ 

char* SesolaParseCertDn
(
char *DnPtr,
char *RecordNamePtr
)
{
   static char  *ContextPtr = NULL;
   static char  RecordValue [128];

   int  RecordNameLength;
   char  *cptr, *sptr, *zptr;
   struct SesolaCertDnRecStruct  *cdnptr;

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

   if (WATCH_MODULE(WATCH_MOD_SESOLA))
      WatchThis (NULL, FI_LI, WATCH_MOD_SESOLA,
                 "SesolaParseCertDn() !AZ !AZ", DnPtr, RecordNamePtr);

   if (!ContextPtr) ContextPtr = DnPtr;
   if (!(cptr = ContextPtr)) return (ContextPtr = NULL);
   if (!*cptr) return (ContextPtr = NULL);

   if (RecordNamePtr)
   {
      /* search the subject string for the specified record name */
      for (sptr = RecordNamePtr; *sptr && *sptr != '='; sptr++);
      if (*sptr) sptr++;
      RecordNameLength = sptr - RecordNamePtr;
      while (*cptr)
      {
         while (*cptr && *cptr != '/') cptr++;
         if (!*cptr) break;
         for (cdnptr = &SesolaCertDnRec; cdnptr->name; cdnptr++)
            if (strsame (cptr, cdnptr->name, cdnptr->length)) break;
         if (cdnptr->name &&
             RecordNameLength == cdnptr->length &&
             strsame (cdnptr->name, RecordNamePtr, RecordNameLength))
            break;
         cptr++;
      }
   }

   zptr = (sptr = RecordValue) + sizeof(RecordValue);
   while (*cptr && sptr < zptr)
   {
      if (*cptr == '/' && sptr < zptr) *sptr++ = *cptr++;
      while (*cptr && *cptr != '/' && sptr < zptr) *sptr++ = *cptr++;
      if (!*cptr) break;
      /* check it's a recognized record delimiter */
      for (cdnptr = &SesolaCertDnRec; cdnptr->name; cdnptr++)
         if (strsame (cptr, cdnptr->name, cdnptr->length)) break;
      if (cdnptr->name) break;
   }
   /* if the buffer overflowed then just set an empty string! */
   if (sptr >= zptr) sptr = RecordValue;
   *sptr = '\0';
   if (WATCH_MODULE(WATCH_MOD_SESOLA))
      WatchThis (NULL, FI_LI, WATCH_MOD_SESOLA, "!AZ", RecordValue);

   ContextPtr = cptr;
   /* if a one-shot search just reset the context pointer */
   if (RecordNamePtr) ContextPtr = NULL;

   return (RecordValue);
}

/*****************************************************************************/
/*
Creates a "fingerprint" of the certificate.  'Colon' should be either a colon
character (':') or a null character ('\0').  The first produces the common,
printable fingerprint.  The second just a string of hex digits used for
certificate identification (a sort of hash).  Return a string describing the
digest algorithm (MD5) or an empty string indicating an error of some sort.
*/

char* SesolaCertFingerprint
(
/* void* for convenience in compiling non-SSL version */
void *CertPtr,
EVP_MD* (*DigestFunction)(void),
char *BufferPtr,
int SizeOfBuffer
)
{
   static char  HexDigits [] = "0123456789ABCDEF";

   int  idx,
        CertDigestLength;
   char  *cptr, *sptr, *zptr;
   unsigned char  CertDigest [EVP_MAX_MD_SIZE];
   EVP_MD  *DigestPtr;

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

   if (WATCH_MODULE(WATCH_MOD_SESOLA))
      WatchThis (NULL, FI_LI, WATCH_MOD_SESOLA, "SesolaCertFingerprint()");

   if (SizeOfBuffer <= 0) return ("");
   zptr = (sptr = BufferPtr) + SizeOfBuffer - 1;
   DigestPtr = (*DigestFunction)();
   if (X509_digest ((X509*)CertPtr, DigestPtr, CertDigest, &CertDigestLength))
   {
      for (idx = 0; idx < CertDigestLength; idx++)
      {
         if (idx && sptr < zptr) *sptr++ = ':';
         if (sptr < zptr) *sptr++ = HexDigits[(CertDigest[idx] & 0xf0) >> 4];
         if (sptr < zptr) *sptr++ = HexDigits[(CertDigest[idx] & 0x0f)];
      }
      *sptr = '\0';
      return ((char*)OBJ_nid2sn (EVP_MD_type (DigestPtr)));
   }
   cptr = "X509_digest() ERROR!";
   while (*cptr && sptr < zptr) *sptr++ = *cptr++;
   *sptr = '\0';
   return ("");
}

/*****************************************************************************/
/*
This is basically for inclusion in the WATCH report header.
*/

char* SesolaVersion ()

{
   static char  String [64];
   static $DESCRIPTOR (StringDsc, String);
   static $DESCRIPTOR (VersionFaoDsc, "!AZ (!AZ)\n\0");

   char  *cptr, *sptr;

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

   if (WATCH_MODULE(WATCH_MOD_SESOLA))
      WatchThis (NULL, FI_LI, WATCH_MOD_SESOLA, "SesolaVersion()");

   if (String[0]) return (String);

   cptr = (char*)SSLeay_version (SSLEAY_BUILT_ON);
   if (sptr = strstr (cptr, "uilt on"))
   {
      cptr = sptr + 7;
      while (*cptr && (*cptr == ' ' || *cptr == ':')) cptr++;
   }

   sys$fao (&VersionFaoDsc, 0, &StringDsc,
            SSLeay_version (SSLEAY_VERSION), cptr);
   return (String);
}

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

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

/* required global storage */
BOOL  ProtocolHttpsAvailable = false,
      ProtocolHttpsConfigured,
      SesolaVerifyCAConfigured;
/* i.e. disabled */
int  SesolaSSLversion = -1;
char  HttpdSesola [] = "",
      SesolaParams [256] = "";


/* external storage */
extern char  ErrorSanityCheck[];
extern char  Utility[];
extern WATCH_STRUCT  Watch;

SesolaInit ()
{
   if (SesolaSSLversion <= 0) return;
   WriteFaoStdout ("%!AZ-E-SSL, non-SSL version\n", Utility);
   exit (STS$K_ERROR | STS$M_INHIB_MSG);
}

SesolaInitService
(
SERVICE_STRUCT *svptr,
SERVICE_STRUCT *csvptr
)
{
   return;
}

BOOL SesolaInitClientService (SERVICE_STRUCT *svptr)
{
   return (true);
}

SesolaClientCert
(
REQUEST_STRUCT *rqptr,
int VerifyMode,
REQUEST_AST AstFunction
)
{
   return (AUTH_DENIED_BY_OTHER);
}

SesolaReport
(
REQUEST_STRUCT *rqptr,
REQUEST_AST NextTaskFunction,
char *VirtualHostPort
)
{
   if (WATCHING(rqptr) && (WATCH_MODULE(WATCH_MOD_SESOLA)))
      WatchThis (rqptr, FI_LI, WATCH_MOD_SESOLA, "SesolaReport()");

   rqptr->rqResponse.HttpStatus = 403;
   ErrorGeneral (rqptr, "This is a non-SSL version.", FI_LI);
   SysDclAst (NextTaskFunction, rqptr);
}

SesolaAdminReloadCA
(
REQUEST_STRUCT *rqptr,
REQUEST_AST NextTaskFunction
)
{
   if (WATCHING(rqptr) && (WATCH_MODULE(WATCH_MOD_SESOLA)))
      WatchThis (rqptr, FI_LI, WATCH_MOD_SESOLA, "SesolaAdminReloadCA()");

   rqptr->rqResponse.HttpStatus = 403;
   ErrorGeneral (rqptr, "This is a non-SSL version.", FI_LI);
   SysDclAst (NextTaskFunction, rqptr);
}

char* SesolaControlReloadCA ()
{
   if (WATCH_MODULE(WATCH_MOD_SESOLA))
      WatchThis (NULL, FI_LI, WATCH_MOD_SESOLA, "SesolaControlReloadCA()");

   return ("!this is a non-SSL version");
}

SesolaReportCA
(
REQUEST_STRUCT *rqptr,
REQUEST_AST NextTaskFunction,
char *VirtualHostPort
)
{
   if (WATCHING(rqptr) && (WATCH_MODULE(WATCH_MOD_SESOLA)))
      WatchThis (rqptr, FI_LI, WATCH_MOD_SESOLA, "SesolaReportCA()");

   rqptr->rqResponse.HttpStatus = 403;
   ErrorGeneral (rqptr, "This is a non-SSL version.", FI_LI);
   SysDclAst (NextTaskFunction, rqptr);
}

char* SesolaVersion ()
{
   if (WATCH_MODULE(WATCH_MOD_SESOLA))
      WatchThis (NULL, FI_LI, WATCH_MOD_SESOLA, "SesolaVersion()");

   return ("");
}

SesolaWatchPeek
(
REQUEST_STRUCT *rqptr,
REQUEST_STRUCT *rqeptr
)
{
   if (WATCHING(rqptr) && (WATCH_MODULE(WATCH_MOD_SESOLA)))
      WatchThis (rqptr, FI_LI, WATCH_MOD_SESOLA, "SesolaWatchPeek()");

   return;
}

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

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

