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


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 VMS port supported in large part by Richard Levitte.

  levitte@openssl.org
  http://www.openssl.org/~levitte/

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
Tested and (minor) modification for OpenSSL v0.9.8
Continued testing and configuration against OpenSSL v1.0.n
Some code changes and conditional compilation required for OpenSSL v1.1.0(-pre6)
Some code changes and conditional compilation required for OpenSSL v1.1.1(n)
Some code changes and conditional compilation required for OpenSSL v3.0

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!

2016 Update: some fifteen years later there is a substantial number more tomes.


OPENSSL 3.0
-----------
Due to explicit backward-compatibility with OpenSSL 1.1.1, WASD can be built
against OpenSSL 3.0 with a minimum of shim (see SESOLA123 and SESOLA321 below).
Object code built under either can be linked on the other and executed.


LINKING/BUILDING AGAINST OLDER OPENSSL
--------------------------------------
Minimum supported OpenSSL version is v1.1.1.


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=.

Where multiple records can exist (e.g. common name) a pattern to match can be
used to select one from between them.  For example, to select a common name for
remote user that uniformly begins with "WASD":

  [ru:/CN=wasd*]

To eliminate any record containing "obvious" non-user characters, use a regular
expression to selectively eliminate those records.  For example, the following
configuration excludes records containing values with a slash or equal symbol
(note the escaped reserved '[' and ']' characters):

  [ru:/CN=^^\[^/=\]*$]

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 WASD_CONFIG_AUTH entry.

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


Remote-User - Subject Alternative Name
--------------------------------------
The "Subject Alternative Name" (SAN) X509 V3 extension, a common extension for
providing identifying data in a certificate, can also be used to derive the
remote user string.  In fact any extension data may be so used.  The basic
syntax for this field is the full extension name, and the short-hand
equivalent, shown below:

  [X509]
  /VMS/* r+w,param="[ru:X509v3_subject_Alternative_Name]"
  /VMS/* r+w,param="[ru:X509v3_SAN]"

The SAN extension (in common with many others) may contain multiple data
elements, each with a leading name, a colon, and a (if multi line)
carriage-control terminated value.  WASD parses these into unqiue fields (and
from there into unique CGI variables/DCL symbols) using keywords fixed in
function SesolaCertKeyword() and site configurable logical name 
WASD_X509_EXTENSION_KEYWORDS value.  To select one of these fields, for example
the common (Microsoft) user principal name (UPN), append the required field
name to the extension name as shown in the following example (includes
"shorthand" equivalents, along with the underscore and equate variants).
Note that the identifying names are case-insensitive.

  [X509]
  /VMS/* r+w,param="[ru:X509V3_Subject_Alternative_Name_UserPrincipalName]"
  /VMS/* r+w,param="[ru:X509V3_Subject_Alternative_Name=UserPrincipalName]"
  /VMS/* r+w,param="[ru:X509v3_SAN_UPN]"
  /VMS/* r+w,param="[ru:X509v3_SAN=UPN]"
  /VMS/* r+w,param="[ru:X509V3_Subject_Alternative_Name_rfc822Name]"
  /VMS/* r+w,param="[ru:X509V3_Subject_Alternative_Name=rfc822Name]"
  /VMS/* r+w,param="[ru:X509v3_SAN_822]"
  /VMS/* r+w,param="[ru:X509v3_SAN=822]"

Object Identifiers (OIDs) may be used for either record and field name (if an
unknown otherName) by prefixing with "OID_".  For example, the SAN may be
alternatively selected, and the (Microsoft) user principal name, as in the
following examples.

  [X509]
  /VMS/* r+w,param="[ru:OID_2_5_29_17]"
  /VMS/* r+w,param="[ru:OID_2_5_29_17_UPN]"
  /VMS/* r+w,param="[ru:OID_2_5_29_17=UPN]"
  /VMS/* r+w,param="[ru:X509v3_SAN_OID_1_3_6_1_20_2_3]"
  /VMS/* r+w,param="[ru:X509v3_SAN_OID=1_3_6_1_20_2_3]"


X509 Extensions
---------------
X509 certificate extensions are in general visible from WATCH and accessible
via CGI variables (when enabled using SET SSLCGI=apache_mod_ssl_extens and
SSLCGI=apache_mod_ssl_client path mappings).  The identifying names derived
from X509 extensions are built of the alphanumerics in the element names. 
Non-alphanumerics (e.g. spaces) have underscores substituted.   Multiple
underscore are compressed into singles.  Where elements have identical names
the first multiple has TWO underscores and the digit two appended, the second
mutiple, two underscores and three appended, etc.  Some examples:

  WWW_SSL_CLIENT_E_X509V3_CERTIFICATE_POLICIES ==
  "Policy: 1.3.6.1.4.1.13569.10.20.4.1.1.  CPS: http://pki.sda.za/tst/pki."
  WWW_SSL_CLIENT_E_X509V3_CERTIFICATE_POLICIES_CPS ==
  "http://pki.sda.za/tst/pki"
  WWW_SSL_CLIENT_E_X509V3_CERTIFICATE_POLICIES_POLICY ==
  "1.3.6.1.4.1.13569.10.20.4.1.1"

  WWW_SSL_CLIENT_E_X509V3_CRL_DISTRIBUTION_POINTS ==
  ".Full Name:. URI:http://pki.sda.za/tst/pki/crl/TSTGLOBAL_CA-Class_D-\
  User1(1).crl. URI:ldap:///CN=CA%20-%20Class%20D%20-%20User1\
  (1),CN=TM88,CN=PDC,CN=P%20Key%20Services,CN=Services,CN=Config\
  uration,DC=tst,DC=loc?certificateRevocationList?base?objectClass=cRLDis\
  tributionPoint."
  WWW_SSL_CLIENT_E_X509V3_CRL_DISTRIBUTION_POINTS_FULL_NAME == ""
  WWW_SSL_CLIENT_E_X509V3_CRL_DISTRIBUTION_POINTS_URI ==
  "http://pki.sda.za/tst/pki/crl/TSTGLOBAL_CA-Class_D-User1(1).crl"
  WWW_SSL_CLIENT_E_X509V3_CRL_DISTRIBUTION_POINTS_URI__2 ==
  "ldap:///CN=CA%20-%20Class%20D%20-%20User1(1),CN=TM88,\
  CN=PDC,CN=P20Key%20Services,CN=Services,CN=Configuration,DC=tst,\
  DC=loc?certificateRevocationList?base?objectClass=cRLDistributionPoint"


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 (PERHAPS!)
IG:integer           ignore this OpenSSL error code (from X509_VFY.H)
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 WASD_CONFIG_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.


FORWARD SECRECY
----------------
Forward secrecy, sometimes known as perfect forward secrecy (PFS), is a
property of key-agreement protocols ensuring that a session key derived from a
set of long-term keys cannot be compromised if one of the long-term keys is
compromised in the future.

OpenSSL supports forward secrecy using Diffie-Hellman key exchange with
elliptic curve cryptography and this relies on generating emphemeral keys based
on unique, "strong" primes.  These are expensive to generate and so this is
done infrequently, often during software build or installation.

In the case of WASD, to maximise flexibility, these "strong" primes are stored
in external PEM-format files, by default located in the WASD_LOCAL: directory,
or optionally located using the logical name WASD_DH_PARAM:.  These files are
only briefly accessed during server startup SSL initialisation and the content
later used during network connection SSL negotiation to generate the required
ephemeral keys.  Each file contains one prime for a certain key size, 512,
1024, etc., generated using the OpenSSL dhparam utility.

  $ openssl dhparam -out dh_param_<bits>.pem <bits>

  $ openssl dhparam -out dh_param_512.pem   512
  $ openssl dhparam -out dh_param_1024.pem 1024
  $ openssl dhparam -out dh_param_2048.pem 2048

  https://www.openssl.org/docs/ssl/SSL_CTX_set_tmp_dh.html

NOTE: Ephemeral keys form part of PFS, the others being selection and ordering
of server ciphers, and ensuring the server determines the cipher used
(+OP_CIPHER_SERVER_PREFERENCE).

Further Note: There are also emphemeral RSA keys too but ...

  "It is therefore strongly recommended to not use ephemeral RSA key exchange
   and use DHE (Ephemeral Diffie-Hellman) key exchange instead in order to
   achieve forward secrecy"!

   https://www.openssl.org/docs/ssl/SSL_CTX_set_tmp_rsa_callback.html


SESSION TICKETS
---------------
RFC 5077 extends TLS via use of session tickets, instead of using session IDs
and associated server cache.  It defines a way to resume a TLS session without
requiring that session-specific state is stored at the TLS server.

When using session tickets, the TLS server stores its session-specific state in
a session ticket and sends the session ticket to the TLS client for storing.
The client resumes a TLS session by sending the session ticket to the server,
and the server resumes the TLS session according to the session-specific state
in the ticket.  The session ticket is encrypted and authenticated by the
server, and the server verifies its validity before using its contents.

So as not to compromise Perfect Forward Secrecy, among other considerations,
the encryption keys for Session IDs need to be changed periodically, with a
maximum of daily suggested (by RFC4507 and confirmed by RFC5077).  WASD does
this using the DLM and an internal /DO= "TICKET=USE" command, distributing a 48
byte key structure to all instances, per-node and cluster-wide as applicable. 
The 48 byte set of keys is (re)generated daily at midnight (local time) and
then distributed to all instances.  At that point all extant session tickets
are invalidated and require reissue.  The command-line $ HTTPD /DO=TICKET=KEY
will perform the same task ad hoc.  Session tickets have a maximum 24 hour
lifetime (~86340 seconds).

Cluster-wide instances are specially accomodated ensuring only one node's
InstanceSupervisor() TICKET=USE command is actually applied.

This relies on a 64 byte Lock Value Block (VMS V8.2 and later).  Where the 64
bytes LVB is not supported, neither is cluster-wide session keys and tickets.


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 WASD_SSL_CERT,
selected ciphers, and a session cache of 128.

  /SSL=TLSvALL            support all available TLS protocol versions
  /SSL=TLSv1              support only TLSv1 (etc.)
  /SSL=noTLSv1            turn TLSv1 option off
  /SSL=TLSv1.1            support only TLSv1.1
  /SSL=noTLSv1.1          turn TLSv1.1 option off
  /SSL=TLSv1.2            support only TLSv1.2
  /SSL=noTLSv1.2          turn TLSv1.2 option off
  /SSL=TLSv1.3            support only TLSv1.3
  /SSL=noTLSv1.3          turn TLSv1.3 option off
  /SSL=(CACHE=integer)    maximum number of records in session cache
  /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=(ICACHE=SIZE=integer)    maximum number of records in instance cache
  /SSL=(ICACHE=RECORD=integer)  size of record in instance cache (bytes)
  /SSL=(KEY=file)         location of default OpenSSL-PEM private key file
                          (if separate from the certificate information)
  /SSL=noSNI              disable Server Name Indication (SNI)
  /SSL=(+OP_ALL,-OP_ALL,+<keyword>,-<keyword>) options on and off
  /SSL=(OPTIONS=0xnn)     hex SSL_OP_.. options (see [.INCLUDE.OPENSSL]SSL.H)
  /SSL=(OPTIONS=+0xnn)    OR in hex SSL_OP_.. options
  /SSL=(OPTIONS=-0xnn)    AND out hex SSL_OP_.. options
  /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=WASD_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_TLS_SNI .............. supplied Server Name Indication string
  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")

  Certificate Extension variables
  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  o  SSL_CLIENT_E_<name> ......  name=value for client certificate
  o  SSL_SERVER_E_<name> ......  name=value for server certificate


LOGICAL NAMES
-------------
The following logical 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 names.

  WASD_SSL_CAFILE       location of default client verify CA certificate file
  WASD_SSL_CERT         location of default OpenSSL-PEM certificate file
  WASD_SSL_CIPHER       comma-separated list of default supported ciphers
  WASD_SSL_KEY          location of default OpenSSL-PEM private key file
  WASD_SSL_PARAMS       the equivalent functionality of the /SSL= qualifier

  WASD_OPENSSL_MEM_TRACK  see SesolaInit()
  WASD_VM_OPENSSL         see VmOpenSslInit()


VERSION HISTORY
---------------
31-JUL-2023  MGD  bugfix; SesolaWatchPeek() do NOT SSL_free()!
07-JUN-2023  MGD  OpenSSL v1.1.n emulate v3.0.n OSSL_default_ciphersuites()
                    and OSSL_default_cipher_list()
                  OpenSSL TLS 1.3 requires SSL_CTX_set_cipher_suites()
22-MAR-2023  MGD  bugfix; braindead SesolaServiceSameCA()
11-FEB-2023  MGD  rework SesolaVersion()
                  remove support prior to OpenSSL 1.1.1
19-MAY-2022  MGD  SESOLA123 and SESOLA321 to allow OpenSSL-3.0 and
                    OpenSSL-1.1.1 to be built using the same object code
                  SesolaServiceSameCA() mitigate OpenSSL-3.0 expense
18-FEB-2022  MGD  refactor TLS/SSL build integers
                  remove support prior to OpenSSL 1.1.0
06-JUL-2021  MGD  bugfix; SesoalReport() get client certificate
19-APR-2021  MGD  [ServiceSSLcert] beginning '+' forces SesolaMkCert()
08-AUG-2020  MGD  keep connect cert (->VerifyPeer) distinct from client cert
                  bugfix; SesolaInitService() enable ALPN for TLSv1.3
02-AUG-2020  MGD  bugfix; SesolaSNICallback() needs to propagate newly set
                    context client verify parameters to SSL-specific
19-JUL-2020  MGD  static fallback cert replaced by dynamic SesolaMkCert()
18-FEB-2020  MGD  more OpenSSL memory instrumentation
19-JAN-2020  MGD  verified against VSI SSL111 product
10-OCT-2018  MGD  verified against OpenSSL v1.0.2 && v1.1.0 && v1.1.1
                    TLSv1.3 operational (though clients currently sparse)
03-JUN-2018  MGD  SesolaControlReloadCerts() undoes SesolaControlReloadService()
                    because it didn't actually work :-/
02-FEB-2018  MGD  SesolaControlReloadService() (was SesolaControlReloadCerts())
                    and uses latest configuration (e.g. certificate name)
                    allowing (SSL parameter) modification without restart
10-OCT-2017  MGD  SesolaReport..() allow reporting using an HTTP service
02-AUG-2017  MGD  bugfix; rationalise as OpenSSL_version[_num]() becomes
                    confused catering for OpenSSL v1.0.2 && v1.1.0 && v1.1.1
23-JUL-2017  MGD  bugfix; SesolaOptionsAsString() doh!
09-JUN-2017  MGD  SesolaInitService() when SSL_CTX_set_tmp_dh_callback() is
                    enabled (DH_PARAM_*..PEM files present) ensure flag
                    SSL_OP_CIPHER_SERVER_PREFERENCE is implicitly set
                  SesolaOptionsAsString() teases out options into string
09-MAY-2017  MGD  bugfix; SesolaSessionTicketUseKey() sigh some more :-[
28-APR-2017  MGD  SesolaSessionTicketUseKey() rework multi-instance/cluster
                    (sigh! yes again; the lack of a test cluster these days)
16-MAR-2017  MGD  TLSv1.3, noTLSv1.3 (for OpenSSL 1.1.1 and later)
                  SesolaControlReloadCerts() implements /DO=SSL=CERT=LOAD
                  bugfix; SesolaControlReloadCA() do not proactively
                    X509_STORE_free() (leaves a dangling pointer?)
14-JAN-2017  MGD  [ServiceSSLcert] specification can contain wildcard(s)
30-DEC-2016  MGD  bugfix; SesolaSNICallback() port elimination
06-SEP-2016  MGD  sesola.h |#include "openssl/rand.h"| to fix OpenSSL v1.1.0
                    static link error against rand_bytes() and rand_seed()
25-AUG-2016  MGD  minimum supported OpenSSL version is now v1.0.0
                    which precludes HP SSL V1.4 (at least)
                    (and WASD for VAX must be close to its last SSL-gasp too!)
                  OpenSSL v1.1.0 required code changes including
                    #if (OPENSSL_VERSION_NUMBER < 0x10100000L) in Sesola..()
                    modules, and introducing a version dependent build
                  use SSL_cache_hit() instead of ->hit
                  use SSL_CTX_get/set_cert_store() instead of ->cert_store
                  use SSL_SESSION_get_id() instead of ->session_id..
14-JUN-2016  MGD  SesolaSessionTicket..() refresh and coordinate the
                    TLS session ticket key cluster-wide using the DLM
                  [SSLsessionCacheMax] default (of zero) now disables
                    in favour of the more efficient Session Ticket
                  SesolaReport() kludge for UTC flavoured timestamps
05-JUN-2016  MGD  [SSLverifyPeerDataMax] configuration directive
22-APR-2016  MGD  bugfix; SesolaInit() session cache max -1 disables cache
07-FEB-2016  MGD  SSL_CTX_set_ecdh_auto() set elliptic curves selection
                  SesolaTmpDHCallback() further refinement
05-OCT-2015  MGD  SesolaReport() list certificate extensions
                  SesolaTmpDHCallback() improve DH*.PEM flexibility
25-AUG-2015  MGD  SesolaCertParseDn() strncmp() not strsame()
                  SesolaCertParseDn() select on pattern match
04-AUG-2015  MGD  ALPN negotiation support for HTTP/2
22-FEB-2015  MGD  strict transport security (RFC6707)
29-JAN-2015  MGD  SesolaInitOptions() expand options keywords to include
                    most SSL_OP_.. flags using the OpenSSL flag #define as the
                    keyword minus the "SSL_" (e.g. OP_CIPHER_SERVER_PREFERENCE)
                  SesolaTmpDHCallback() for ephemeral keys enabling "perfect
                    forward secrecy"
                  increase SESOLA_SSL_ACCEPT_MAX, SESOLA_SSL_CONNECT_MAX
                    and SESOLA_SSL_SHUTDOWN_MAX
                  updated the internal fallback PEM cert/key
20-DEC-2014  MGD  SesolaInitService() and SesolaInitClientService()
                    if cipher list begins '+', '-' or '!' append it to default
30-NOV-2014  MGD  include SHA256 fingerprint
19-NOV-2014  MGD  bugfix; SSL_CTX_set_default_passwd_cb_userdata()
16-NOV-2014  MGD  NOTE: TLSv1, TLSv1.1, TLSv1.2 now ENABLED by default
                        SSLv2 and SSLv3 are now DISABLED by default
                        (as recommended post-POODLE)
                  WASD_CONFIG_GLOBAL [SSL..] directives
                  /SSL=(TLSvALL,TLSv1.1,noTLSv1.1,TLSv1.2,noTLSv1.2)
                    removed /SSL=(2|3|23) which must be altered to SSLv2, etc.
                  updated the internal fallback PEM cert/key
                  bugfix; SSLv23_method() appears to be a Swiss-army knife
                    significant rework of SSL version configuration
13-APR-2014  MGD  SesolaVersion() include header origin and link data
14-SEP-2013  MGD  rework SSL options parameters (and reporting) /SSL=
                    +OP_ALL, -OP_ALL, +OP_LEG_REN, -OP_LEG_REN,
                    +OP_NO_SES_RES, -OP_NO_SES_RES, +OP_SING_DH, -OP_SING_DH
                  establish a default cipher list based on best practise
                  SSL_get_certificate() bug workaround in SesolaReport()
                  TLS1 Server Name Indication (SNI) extension (RFC 4366)
                    /SSL=NOSNI parameter can disable
                  establish and use a separate list of SSL services
                    to expedite SSL service activities such as SNI
                  default set options SSL_OP_SINGLE_DH_USE and
                    SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION
                  if session cache size is zero then
                    SSL_CTX_sess_set_cache_mode() SSL_SESS_CACHE_OFF
                  SesolaReport() add "Fingerprint:" to client cert data
                  bugfix; SesolaInitService() (or refinement)
                    SSL_CTX_set_session_id_context() against each service
                  bugfix; SesolaReport() session hh:mm:ss minute
02-DEC-2012  MGD  bugfix; SesolaInit() translate WASD_SSL_CIPHER logical name
                  report SSL_CTX_set_cipher_list() error
01-JUL-2012  MGD  bugfix; SesolaCertParseDn() return NULL if record not found
02-SEP-2011  MGD  SesolaInit() default is SSLv2 off and SSLv3/TLSv1 on
13-FEB-2008  MGD  SesolaReport() rework "Current Session" 'Session:'
28-MAR-2006  MGD  added SSL=ICACHE=SIZE= and SSL=ICACHE=RECORD=
                    to allow configuration of instance SSL session cache
                  move SesolaCacheInit() out after AuthConfigInit() so that
                    the record size can be increased if X509 realm is used
08-JUL-2005  MGD  OpenSSL v0.9.8 changed macro name EVP_F_EVP_DECRYPTFINAL
25-MAY-2005  MGD  SesolaWatchBioCallback() make >= 0 complete
19-APR-2005  MGD  SesolaInitService() no longer needs to clone
21-AUG-2004  MGD  significant refinements to SSL processing
22-JUL-2004  MGD  persistent connections over SSL
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 FaolToNet(), 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_V7
#undef _VMS__V6__SOURCE
#define _VMS__V6__SOURCE
#undef __VMS_VER
#define __VMS_VER 70000000
#undef __CRTL_VER
#define __CRTL_VER 70000000
#endif

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

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

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

/* SSL_OP_ALLOW_CLIENT_RENEGOTIATION does not exist in 1.1.1n */
/* see [.SSL]SSL_CONF.C */
#define SESOLA_ALLOW_CLIENT_RENEGOTIATION (1 << 8)

/* SSL_OP_IGNORE_UNEXPECTED_EOF does not exist in 1.1.1n */
/* see [.SSL]SSL_CONF.C */
#define SESOLA_OP_IGNORE_UNEXPECTED_EOF (1 << 7)

#define WASD_MODULE "SESOLA"

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

#ifndef SESOLA123
#define SESOLA123 0
#endif

#ifndef SESOLA321
#define SESOLA321 0
#endif

/*************************/
#if SESOLA123 || SESOLA321
/*************************/

/************/
#if SESOLA123
/************/

/*
When compiled having SESOLA123 defined creates an object module containing
these functions which wrap the OpenSSL 3.0.n equivalent functions.  The build
procedure then links this object module into the executable when linking
against OpenSSL 3.0.n shared/library providing these wrapped functions.
See BUILD_HTTPD.COM
*/

/* these two OpenSSL-3.0 macros */
#undef EVP_MD_type
#undef SSL_get_peer_certificate

int EVP_MD_type(const EVP_MD *md)
{
   return (EVP_MD_get_type(md));
}

X509 *SSL_get_peer_certificate(const SSL *s)
{
   return (SSL_get1_peer_certificate(s));
   int ch = to_lower('a');
}

/********************/
#endif /* SESOLA123 */
/********************/

/************/
#if SESOLA321
/************/

/*
When compiled having SESOLA321 defined creates an object module containing
these functions which wrap the OpenSSL 1.1.1n equivalent functions.  The build
procedure then links this object module into the executable when linking
against OpenSSL 1.1.1n shared/library providing these wrapped functions.
See BUILD_HTTPD.COM
*/

/* these two OpenSSL-3.0 macros */
#undef EVP_MD_type
#undef SSL_get_peer_certificate

int EVP_MD_get_type(const EVP_MD *md)
{
   return (EVP_MD_type(md));
}

X509 *SSL_get1_peer_certificate(const SSL *s)
{
   return (SSL_get_peer_certificate(s));
}

/* from [.openssl-3_0_8.ssl]ssl_ciph.c */
const char *OSSL_default_cipher_list (void)
{
   return ("ALL:!COMPLEMENTOFDEFAULT:!eNULL");
}

/* from [.openssl-3_0_8.ssl]ssl_ciph.c */
const char *OSSL_default_ciphersuites (void)
{
   return ("TLS_AES_256_GCM_SHA384:"
           "TLS_CHACHA20_POLY1305_SHA256:"
           "TLS_AES_128_GCM_SHA256");
}

/********************/
#endif /* SESOLA321 */
/********************/

#if TO_LOWER_UPPER
/* required to complete the link */
int ToLowerCase [1],
    ToUpperCase [1];
#endif

/********************************/
#else /* SESOLA123 || SESOLA321 */
/********************************/

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

BOOL  ProtocolHttpsAvailable = true,
      ProtocolHttpsConfigured = false,
      ProtocolHttpsDisabled = false,
      SesolaMemTrack,
      SesolaMemVmTrack,
      SesolaPrivateKeyPasswdRequested,
      SesolaSNI = true,
      SesolaVerifyCAConfigured,
      SesolaVerifyPeer;

int  SesolaDefaultVerifyDepth = SESOLA_DEFAULT_VERIFY_DEPTH,
     SesolaGblSecStructSize = sizeof(struct SesolaGblSecStruct),
     SesolaSessionCacheSize,
     SesolaSessionCacheTimeout = SESOLA_DEFAULT_CACHE_TIMEOUT,
     SesolaSessionLifetime,
     SesolaPrivateKeyPasswdAttempt,
     SesolaSessionTicketHit,
     SesolaSessionTicketMiss,
     SesolaSessionTicketNew,
     SesolaVersionBitmap,
     SesolaVerifyPeerDataMax;

/* OpenSSL 3.0 bumped options to 64 bits */
uint64  SesolaSSLoptions,
        SesolaVersionOptions;

/* the link deposits an integer in this */
globalvalue int  SESOLA_SHARE;

ulong  SesolaOpenSslVersionNumber,
       SesolaFreeCount,
       SesolaMallocCount,
       SesolaPrevFreeCount,
       SesolaPrevMallocCount,
       SesolaPrevReallocCount,
       SesolaReallocCount;
static ulong   LastLookTime64;

int64  SesolaFreeBytes,
       SesolaInUseBytes,
       SesolaMallocBytes,
       SesolaMaxBytes,
       SesolaPrevAvailBytes,
       SesolaPrevFreeBytes,
       SesolaPrevInUseBytes,
       SesolaPrevMallocBytes,
       SesolaPrevMaxBytes;

char  *SesolaDefaultCertPtr,
      *SesolaDefaultCipherListPtr,
      *SesolaDefaultCipherSuitesPtr,
      *SesolaDefaultKeyPtr,
      *SesolaDefaultCaFilePtr,
      *SesolaDefaultStrictTransSecPtr,
      *SesolaTmpDHbitsPtr;

char  SesolaParams [256],
      SesolaVersionString [64];

int  SesolaParamsSize = sizeof(SesolaParams);

/* the ticket key (being randomised) is also used as the session ID */
uchar  /* size of tick_key_name + tick_hmac_key + tick_aes_key */
       SesolaTicketKey [48];
int  SesolaTicketKeySuperDay;
static int64  SesolaTicketKeyTime64;

/* the session ID is generated using shared session ticket key data */
uchar SesolaSessionId [16];  /* < SSL_MAX_SSL_SESSION_ID_LENGTH (32) */

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

BIO  *SesolaBioMemPtr;

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

extern BOOL  CliDemo,
             Http2Enabled,
             HttpdServerStartup,
             ServiceWwwImplied;

extern int64  HttpdTime64;

extern int  EfnWait,
            ExitStatus,
            HttpdTickSecond,
            InstanceNodeConfig,
            InstanceNodeCurrent,
            OpcomMessages,
            SesolaCacheRecordMax,
            SesolaCacheRecordSize;

extern ushort  HttpdTime7[];

extern int64  HttpdStartTime64;

extern ulong  SysPrvMask[];

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

extern ACCOUNTING_STRUCT  *AccountingPtr;
extern CONFIG_STRUCT  Config;
extern HTTPD_GBLSEC  *HttpdGblSecPtr;
extern HTTPD_PROCESS  HttpdProcess;
extern MSG_STRUCT  Msgs;
extern LIST_HEAD  ServiceList;
extern WATCH_STRUCT  Watch;

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

SesolaInit ()

{
   int  size;
   char  ch;
   char  *cptr, *sptr, *zptr,
         *SslParamsPtr;
   char  buf [256];
   CONFIG_STRUCT  *cfptr;

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

   if (WATCH_MODULE(WATCH_MOD_SESOLA))
      WatchThis (WATCHALL, WATCH_MOD_SESOLA, "SesolaInit()");

   if (SysTrnLnm (WASD_OPENSSL_MEM_TRACK))
   {
      SesolaMemTrack = true;
      SesolaMemVmTrack = VmOpenSslInit ();
      if (!CRYPTO_set_mem_functions (SesolaMalloc,
                                     SesolaRealloc,
                                     SesolaFree))
         ErrorExitVmsStatus (0, "CRYPTO_set_mem_functions()", FI_LI);
   }
   else
   if (VmOpenSslInit ())  /* "WASD_VM_OPENSSL" */
   {
      /* OpenSSL memory management using WASD's own VM.C module */
      if (!CRYPTO_set_mem_functions (VmOpenSslMalloc,
                                     VmOpenSslRealloc,
                                     VmOpenSslFree))
         ErrorExitVmsStatus (0, "CRYPTO_set_mem_functions()", FI_LI);
   }

   FaoToStdout ("%HTTPD-I-SSL, !AZ\n", SesolaVersion());

   if (SesolaOpenSslVersionNumber < 0x10000000L)
   {
      FaoToStdout ("%HTTPD-W-SSL, unsupported version (minimum 1.0.0)\n");
      ProtocolHttpsDisabled = true;
      return;
   }

   /* command-line parameters */
   SslParamsPtr = SesolaParams;

   if (strsame (SslParamsPtr, "/NOSSL", -1))
   {
      ProtocolHttpsDisabled = true;
      FaoToStdout ("%HTTPD-W-SSL, disabled via /NOSSL\n");
      return;
   }

   cfptr = &Config;

   if (cfptr->cfSesola.Disabled)
   {
      ProtocolHttpsDisabled = true;
      FaoToStdout ("%HTTPD-W-SSL, disabled via [SecureSocket]\n");
      return;
   }

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

   OPENSSL_init_ssl (0, NULL); 

   SesolaSSLoptions = SSL_OP_ALL;
   SesolaSSLoptions |= SSL_OP_SINGLE_DH_USE;
   SesolaSSLoptions |= SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION;
   if (SesolaOpenSslVersionNumber >= 0x30000000L)
   {
#if SESOLA_DEFAULT_CLIENT_RENEGOTIATION
      /* SSL_OP_ALLOW_CLIENT_RENEGOTIATION does not exist in 1.1.1n */
      SesolaSSLoptions |= SESOLA_ALLOW_CLIENT_RENEGOTIATION;
                       /* ^^^^^^ */
#endif
   }

   /*********************************/
   /* WASD_CONFIG_GLOBAL directives */
   /*********************************/

   if (cfptr->cfSesola.ProtocolVersion[0])
      SesolaInitContext (cfptr->cfSesola.ProtocolVersion, NULL);

   if (cfptr->cfSesola.CipherList[0])
      SesolaDefaultCipherListPtr = cfptr->cfSesola.CipherList;

   if (cfptr->cfSesola.CipherSuites[0])
      SesolaDefaultCipherSuitesPtr = cfptr->cfSesola.CipherSuites;

   if (cfptr->cfSesola.ServerCert[0])
      SesolaDefaultCertPtr = cfptr->cfSesola.ServerCert;

   if (cfptr->cfSesola.PrivateKey[0])
      SesolaDefaultKeyPtr = cfptr->cfSesola.PrivateKey;

   if (cfptr->cfSesola.ProtocolOptions[0])
      SesolaInitOptions (cfptr->cfSesola.ProtocolOptions, &SesolaSSLoptions);
   else
      SesolaInitOptions (SesolaDefaultOptions, &SesolaSSLoptions);

   if (cfptr->cfSesola.StrictTransSec[0])
      SesolaDefaultStrictTransSecPtr = cfptr->cfSesola.StrictTransSec;

   if (cfptr->cfSesola.SessionCacheMax < 0)
      SesolaSessionCacheSize = 0;
   else
   if (cfptr->cfSesola.SessionCacheMax)
      SesolaSessionCacheSize = cfptr->cfSesola.SessionCacheMax;

   if (cfptr->cfSesola.VerifyPeer)
      SesolaVerifyPeer = true;
   else
      SesolaVerifyPeer = false;

   if (cfptr->cfSesola.VerifyPeerDataMax < 0)
      SesolaVerifyPeerDataMax = 0;
   else
   if (cfptr->cfSesola.VerifyPeerDataMax)
      SesolaVerifyPeerDataMax = cfptr->cfSesola.VerifyPeerDataMax;
   else
      SesolaVerifyPeerDataMax = SESOLA_VERIFY_PEER_DATA_MAX_DEFAULT;
   SesolaVerifyPeerDataMax *= 1024;  /* in kBytes */

   if (cfptr->cfSesola.InstanceSessionCacheMax)
      SesolaCacheRecordMax = cfptr->cfSesola.InstanceSessionCacheMax;
   if (cfptr->cfSesola.InstanceSessionCacheSize)
      SesolaCacheRecordSize = cfptr->cfSesola.InstanceSessionCacheSize;

   if (cfptr->cfSesola.VerifyPeer)
      if (!(SesolaDefaultVerifyDepth = cfptr->cfSesola.VerifyPeerDepth))
         SesolaDefaultVerifyDepth = 1;

   if (cfptr->cfSesola.VerifyPeerCAFile[0])
      SesolaDefaultCaFilePtr = cfptr->cfSesola.VerifyPeerCAFile;
   else
   if (cptr = SysTrnLnm (CONFIG_SSL_CAFILE))
   {
      SesolaDefaultCaFilePtr = VmGet (size = strlen(cptr)+1);
      strzcpy (SesolaDefaultCaFilePtr, cptr, size);
   }

   SesolaSessionLifetime = cfptr->cfSesola.SessionLifetime;

   /*********************************************/
   /* command-line overrides WASD_CONFIG_GLOBAL */
   /*********************************************/

   /* lots of ill-defined historical hodge-podge catered for here */
   cptr = SslParamsPtr;
   while (*cptr)
   {
      while (*cptr == '(' || *cptr == ',' || *cptr == ')') cptr++;
      sptr = cptr;
      while (*cptr && *cptr != ',' && *cptr != ')') cptr++;
      if (ch = *(zptr = cptr)) *cptr++ = '\0';
      if (!*sptr) break;

      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, "SUITES=", 7))
         SesolaDefaultCipherSuitesPtr = sptr + 7;
      else
      if (strsame (sptr, "CACHE=", 6))
         SesolaSessionCacheSize = atoi(sptr+6);
      else
      if (strsame (sptr, "ICACHE=SIZE=", 12))
         SesolaCacheRecordMax = atoi(sptr+12);
      else
      if (strsame (sptr, "ICACHE=RECORD=", 14))
         SesolaCacheRecordSize = atoi(sptr+14);
      else
      if (strsame (sptr, "KEY=", 4))
         SesolaDefaultKeyPtr = sptr + 4;
      else
      if (strsame (sptr, "OPTIONS=", 8))
      {
         /* special case kludge - allow for parentheses :-( */
         sptr += 8;
         if (*sptr == '(')
         {
            *zptr = ch;
            cptr = ++sptr;
            while (*cptr && *cptr != ')') cptr++;
            if (ch = *(zptr = cptr)) *cptr++ = '\0';
         }
         SesolaInitOptions (sptr, &SesolaSSLoptions);
      }
      else
      if (strsame (sptr, "+OP_", 4) ||
          strsame (sptr, "+0x", 3))
         SesolaInitOptions (sptr, &SesolaSSLoptions);
      else
      if (strsame (sptr, "-OP_", 4) ||
          strsame (sptr, "-0x", 3))
         SesolaInitOptions (sptr, &SesolaSSLoptions);
      else
      if (strsame (sptr, "SNI", -1))
         SesolaSNI = true;
      else
      if (strsame (sptr, "NOSNI", -1))
         SesolaSNI = false;
      else
      if (strsame (sptr, "STRICT=", 7))
         SesolaDefaultStrictTransSecPtr = sptr + 7;
      else
      if (strsame (sptr, "TIMEOUT=", 8))
         SesolaSessionCacheTimeout = atoi(sptr+8);
      else
      if (strsame (sptr, "VERIFY=", 7))
         SesolaDefaultVerifyDepth = atoi(sptr+7);
      else
      /* if not an SSL version parameter */
      if (!(strsame(sptr,"SSLv",4) ||
            strsame(sptr,"noSSLv",6) ||
            strsame(sptr,"TLSv",4) ||
            strsame(sptr,"noTLSv",6)))
      {
         FaoToStdout ("%HTTPD-E-SSL, unknown SSL parameter\n \\!AZ\\\n", sptr);
         exit (STS$K_ERROR | STS$M_INHIB_MSG);
      }

      *zptr = ch;
   }

   /* process the /SSL= protocol version keywords */
   SesolaInitContext (SslParamsPtr, NULL);

   /************/
   /* finalise */
   /************/

   /* if no protocol preferences expressed in WASD_CONFIG_GLOBAL or /SSL= */
   if (!SesolaVersionBitmap) SesolaInitContext (SesolaDefaultProtocol, NULL);

   FaoToStdout ("-SSL-I-PROTOCOL, !AZ\n", SesolaVersionString);

   FaoToStdout ("-SSL-I-OPTIONS, 0x!10@XQ; !AZ\n",
                &SesolaSSLoptions, SesolaOptionsAsString(&SesolaSSLoptions));

   FaoToStdout ("-SSL-!AZ-SNI, Server Name Indication !AZ\n",
                SesolaSNI ? "I" : "W",
                SesolaSNI ? "enabled" : "disabled");

   /* initialise emphemeral DH params */
   SesolaTmpDHCallback (NULL, 0, 0);

   /* initialise the session ticket key */
   SesolaSessionTicketNewKey ();

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

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

   if (!SesolaDefaultCipherListPtr)
      if (SesolaDefaultCipherListPtr = SysTrnLnm (CONFIG_SSL_CIPHER))
         if (cptr = SysTrnLnm (SesolaDefaultCipherListPtr))
         {
            SesolaDefaultCipherListPtr = VmGet (size = strlen(cptr)+1);
            strzcpy (SesolaDefaultCipherListPtr, cptr, size);
         }

   if (!SesolaDefaultCipherSuitesPtr)
      if (SesolaDefaultCipherSuitesPtr = SysTrnLnm (CONFIG_SSL_SUITES))
         if (cptr = SysTrnLnm (SesolaDefaultCipherSuitesPtr))
         {
            SesolaDefaultCipherSuitesPtr = VmGet (size = strlen(cptr)+1);
            strzcpy (SesolaDefaultCipherSuitesPtr, cptr, size);
         }

   /*
    * A single, global memory BIO can be used when puts(), gets() and other
    * OpenSSL string operations are required provided; 1) they are properly
    * BIO_reset() after use (and before if you're belt and braces), and 2)
    * only used within a (single) AST context (i.e. no concurrent usage).
    */
   SesolaBioMemPtr = BIO_new (BIO_s_mem());
   if (!SesolaBioMemPtr)
   {
      FaoToStdout ("-SSL-E-BIO, BIO_new(BIO_s_mem()) failed\n");
      exit (STS$K_ERROR | STS$M_INHIB_MSG);
   }
}

/*****************************************************************************/
/*
Establish an SSL context using a (WASD) protocol configuration string (from
either the command-line or service protocol configuration field.  It ignores
(non-protocol) keywords it doesn't recognise.  If a service structure is
supplied it sets that service's SSL context and protocol version string.  If no
service struct is passed it configures global values.  Returns the number of
protocols configured.
*/

void SesolaInitContext
(
char* ConfigString,
SESOLA_CONTEXT *scptr
)
{
   int  SSLversion,
        SSLoptions,
        TLSmax,
        TLSmin,
        VersionCount;
   uint  bit;
   char  ch;
   char  *cptr, *sptr, *zptr;
   SSL_CTX  *SslCtx;

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

   if (WATCH_MODULE(WATCH_MOD_SESOLA))
      WatchThis (WATCHALL, WATCH_MOD_SESOLA,
                 "SesolaInitContext() !&Z", ConfigString);

   if (ProtocolHttpsDisabled) return;

   SslCtx = NULL;
   SSLoptions = SSLversion = 0;

   cptr = ConfigString;
   while (*cptr)
   {
      /* kludge: will see but NO need to allow for (..) */
      while (*cptr == '(' || *cptr == ',' || *cptr == ')') cptr++;
      sptr = cptr;
      while (*cptr && *cptr != ',' && *cptr != ')') cptr++;
      if (ch = *(zptr = cptr)) *cptr++ = '\0';
      if (!*sptr) break;

      if (strsame (sptr, "SSLv2", -1) ||
          strsame (sptr, "+SSLv2", -1) ||
          strsame (sptr, "noSSLv2", -1) ||
          strsame (sptr, "-SSLv2", -1) ||
          strsame (sptr, "SSLv3", -1) ||
          strsame (sptr, "+SSLv3", -1) ||
          strsame (sptr, "noSSLv3", -1) ||
          strsame (sptr, "-SSLv3", -1) ||
          strsame (sptr, "SSLv2v3", -1) ||
          strsame (sptr, "+SSLv2v3", -1))
         FaoToStdout ("-SSL-W-PROTOCOL, keyword obsolete\n \\!AZ\\\n", sptr);
      else
      if (strsame (sptr, "TLSvALL", -1))
      {
         /* all (known) TLS protocol versions */
         SSLversion |= SESOLA_TLSV1;
         SSLversion |= SESOLA_TLSV1_1;
         SSLversion |= SESOLA_TLSV1_2;
         SSLversion |= SESOLA_TLSV1_3;
      }
      else
      if (strsame (sptr, "TLSv1", -1) ||
          strsame (sptr, "+TLSv1", -1))
         SSLversion |= SESOLA_TLSV1;
      else
      if (strsame (sptr, "noTLSv1", -1) ||
          strsame (sptr, "-TLSv1", -1))
         SSLversion &= ~SESOLA_TLSV1;
      else
      /* TLS 1.1 and TLS 1.2 */
      if (strsame (sptr, "TLSv1.1", -1) ||
          strsame (sptr, "+TLSv1.1", -1) ||
          strsame (sptr, "TLSv1_1", -1))
         SSLversion |= SESOLA_TLSV1_1;
      else
      if ((strsame (sptr, "noTLSv1.1", -1) ||
           strsame (sptr, "-TLSv1.1", -1) ||
           strsame (sptr, "noTLSv1_1", -1) ||
           strsame (sptr, "-TLSv1_1", -1)))
         SSLversion &= ~SESOLA_TLSV1_1;
      else
      if (strsame (sptr, "TLSv1.2", -1) ||
          strsame (sptr, "+TLSv1.2", -1) ||
          strsame (sptr, "TLSv1_2", -1))
         SSLversion |= SESOLA_TLSV1_2;
      else
      if ((strsame (sptr, "noTLSv1.2", -1) ||
           strsame (sptr, "-TLSv1.2", -1) ||
           strsame (sptr, "noTLSv1_2", -1) ||
           strsame (sptr, "-TLSv1_2", -1)))
         SSLversion &= ~SESOLA_TLSV1_2;
      else
      /* TLS 1.3 */
      if (strsame (sptr, "TLSv1.3", -1) ||
          strsame (sptr, "+TLSv1.3", -1) ||
          strsame (sptr, "TLSv1_3", -1))
         SSLversion |= SESOLA_TLSV1_3;
      else
      if ((strsame (sptr, "noTLSv1.3", -1) ||
           strsame (sptr, "-TLSv1.3", -1) ||
           strsame (sptr, "noTLSv1_3", -1) ||
           strsame (sptr, "-TLSv1_3", -1)))
         SSLversion &= ~SESOLA_TLSV1_3;
      else
      if (strsame(sptr,"SSLv",4) ||
          strsame(sptr,"+SSLv",5) ||
          strsame(sptr,"TLSv",4) ||
          strsame(sptr,"+TLSv",5) ||
          strsame(sptr,"noSSLv",6) ||
          strsame(sptr,"-SSLv",5) ||
          strsame(sptr,"noTLSv",6) ||
          strsame(sptr,"-TLSv",5))
         FaoToStdout ("-SSL-W-PROTOCOL, unknown keyword\n \\!AZ\\\n", sptr);

      *zptr = ch;
   }

   VersionCount = 0;
   for (bit = 1; bit; bit = bit << 1)
      if (SSLversion & bit) VersionCount++;

   if (!VersionCount && scptr)
   {
      /* when configuring a service use the server default */
      SSLversion = SesolaVersionBitmap;
      for (bit = 1; bit; bit = bit << 1)
         if (SSLversion & bit) VersionCount++;
   }

   if (!VersionCount) return;

   if (VersionCount > 1)
   {
      if (!(SSLversion & SESOLA_TLSV1)) SSLoptions |= SSL_OP_NO_TLSv1;
      if (!(SSLversion & SESOLA_TLSV1_1)) SSLoptions |= SSL_OP_NO_TLSv1_1;
      if (!(SSLversion & SESOLA_TLSV1_2)) SSLoptions |= SSL_OP_NO_TLSv1_2;
      if (!(SSLversion & SESOLA_TLSV1_3)) SSLoptions |= SSL_OP_NO_TLSv1_3;
   }

   TLSmin = TLSmax = 0;
   if (SSLversion & SESOLA_TLSV1) TLSmin = TLSmax = TLS1_VERSION;
   if (SSLversion & SESOLA_TLSV1_1 && !TLSmin) TLSmin = TLS1_1_VERSION;
   if (SSLversion & SESOLA_TLSV1_1) TLSmax = TLS1_1_VERSION;
   if (SSLversion & SESOLA_TLSV1_2 && !TLSmin) TLSmin = TLS1_2_VERSION;
   if (SSLversion & SESOLA_TLSV1_2) TLSmax = TLS1_2_VERSION;
   if (SSLversion & SESOLA_TLSV1_3 && !TLSmin) TLSmin = TLS1_3_VERSION;
   if (SSLversion & SESOLA_TLSV1_3) TLSmax = TLS1_3_VERSION;

   SslCtx = SSL_CTX_new (TLS_method());

   if (SslCtx == NULL)
   {
      SesolaPrintOpenSslErrorList ();
      ErrorExitVmsStatus (0, "SSL_CTX_new()", FI_LI);
   }

   if (!SSL_CTX_set_min_proto_version (SslCtx, TLSmin))
   {
      SesolaPrintOpenSslErrorList ();
      ErrorExitVmsStatus (0, "SSL_CTX_set_min_proto_version()", FI_LI);
   }

   if (!SSL_CTX_set_max_proto_version (SslCtx, TLSmax))
   {
      SesolaPrintOpenSslErrorList ();
      ErrorExitVmsStatus (0, "SSL_CTX_set_max_proto_version()", FI_LI);
   }

   if (scptr)
      cptr = scptr->VersionStringPtr = VmGet(sizeof(SesolaVersionString)); 
   else
      /* this string will be the default config for any service */
      cptr = SesolaVersionString;
   *(sptr = cptr) = '\0';

   if (SSLversion & SESOLA_TLSV1)
      sptr += sprintf(sptr, "%sTLSv1", *cptr ? "," : "");
   if (SSLversion & SESOLA_TLSV1_1)
      sptr += sprintf(sptr, "%sTLSv1.1", *cptr ? "," : "");
   if (SSLversion & SESOLA_TLSV1_2)
      sptr += sprintf(sptr, "%sTLSv1.2", *cptr ? "," : "");
   if (SSLversion & SESOLA_TLSV1_3)
      sptr += sprintf(sptr, "%sTLSv1.3", *cptr ? "," : "");
   if (!*cptr) ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI);

   if (SslCtx)
   {
      if (scptr)
      {
         /* initialising a service */
         scptr->SslCtx = SslCtx;
         scptr->VersionBitmap = SSLversion;
         scptr->VersionOptions = SSLoptions;
      }
      else
      {
         /* initialising Secure Sockets capability */
         free (SslCtx);
         /* first from WASD_CONFIG_GLOBAL then second from /SSL= */
         if (SSLversion) SesolaVersionBitmap = SSLversion;
         if (SSLoptions) SesolaVersionOptions = SSLoptions;
      }
   }
}

/*****************************************************************************/
/*
Return an integer representing the OpenSSL options bitmask.
If the OptionsPtr is provided then use and set this value.
*/

uint SesolaInitOptions
(
char* ConfigString,
uint64 *OptionsPtr
)
{
   uint64  options;
   char  ch;
   char  *cptr, *sptr, *zptr;

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

   if (WATCH_MODULE(WATCH_MOD_SESOLA))
      WatchThis (WATCHALL, WATCH_MOD_SESOLA,
                 "SesolaInitOptions() !&Z", ConfigString);

   if (OptionsPtr)
      options = *OptionsPtr;
   else
      options = 0;

   cptr = ConfigString;
   while (*cptr)
   {
      /* kludge: never see parentheses */
      while (*cptr == ',') cptr++;
      sptr = cptr;
      while (*cptr && *cptr != ',') cptr++;
      if (ch = *(zptr = cptr)) *cptr++ = '\0';
      if (!*sptr) break;

      if (strsame(sptr,"0x",2))
         /* will fully overwrite and therefore if used then use first */
         options = (ulong)strtoul (sptr, NULL, 16);
      else
      if (strsame(sptr,"+0x",3))
         /* bitwise OR this into the options */
         options |= (ulong)strtoul (sptr+1, NULL, 16);
      else
      if (strsame(sptr,"-0x",3))
         /* bitwise AND this from the options */
         options &= ~(ulong)strtoul (sptr+1, NULL, 16);
      else
      if (strsame (sptr, "+OP_ALL", -1))
         options |= SSL_OP_ALL;
      else
      if (strsame (sptr, "-OP_ALL", -1))
         options &= ~SSL_OP_ALL;
      else
      if (strsame (sptr, "+OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION", -1) ||
          strsame (sptr, "+OP_LEG_REN", -1))
         options |= SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION;
      else
      if (strsame (sptr, "-OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION", -1) ||
          strsame (sptr, "-OP_LEG_REN", -1))
         options &= ~SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION;
      else
      if (strsame (sptr, "+OP_CIPHER_SERVER_PREFERENCE", -1))
         options |= SSL_OP_CIPHER_SERVER_PREFERENCE;
      else
      if (strsame (sptr, "-OP_CIPHER_SERVER_PREFERENCE", -1))
         options &= ~SSL_OP_CIPHER_SERVER_PREFERENCE;
      else
      if (strsame (sptr, "+OP_LEGACY_SERVER_CONNECT", -1))
         options |= SSL_OP_LEGACY_SERVER_CONNECT;
      else
      if (strsame (sptr, "-OP_LEGACY_SERVER_CONNECT", -1))
         options &= ~SSL_OP_LEGACY_SERVER_CONNECT;
      else
      if (strsame (sptr, "+OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION", -1) ||
          strsame (sptr, "+OP_NO_SES_RES", -1))
         options |= SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION;
      else
      if (strsame (sptr, "-OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION", -1) ||
          strsame (sptr, "-OP_NO_SES_RES", -1))
         options &= ~SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION;
      else
      if (strsame (sptr, "+OP_ALLOW_CLIENT_RENEGOTIATION", -1))
         options |= SESOLA_ALLOW_CLIENT_RENEGOTIATION;
      else
      if (strsame (sptr, "+SSL_OP_ALLOW", -1))
         options |= SESOLA_ALLOW_CLIENT_RENEGOTIATION;
      else
      if (strsame (sptr, "-OP_ALLOW_CLIENT_RENEGOTIATION", -1))
         options &= ~SESOLA_ALLOW_CLIENT_RENEGOTIATION;
      else
      if (strsame (sptr, "-OP_ALLOW", -1))
         options &= ~SESOLA_ALLOW_CLIENT_RENEGOTIATION;
      else
      if (strsame (sptr, "+SSL_OP_IGNORE_UNEXPECTED_EOF", -1))
         options |= SESOLA_OP_IGNORE_UNEXPECTED_EOF;
      else
      if (strsame (sptr, "+SSL_OP_IGNORE", -1))
         options |= SESOLA_OP_IGNORE_UNEXPECTED_EOF;
      else
      if (strsame (sptr, "-SSL_OP_IGNORE_UNEXPECTED_EOF", -1))
         options &= ~SESOLA_OP_IGNORE_UNEXPECTED_EOF;
      else
      if (strsame (sptr, "-OP_IGNORE", -1))
         options &= ~SESOLA_OP_IGNORE_UNEXPECTED_EOF;
      else
      if (strsame (sptr, "+OP_NO_TICKET", -1))
         options |= SSL_OP_NO_TICKET;
      else
      if (strsame (sptr, "-OP_NO_TICKET", -1))
         options &= ~SSL_OP_NO_TICKET;
      else
      if (strsame (sptr, "+OP_TLS_ROLLBACK_BUG", -1))
         options |= SSL_OP_TLS_ROLLBACK_BUG;
      else
      if (strsame (sptr, "-OP_TLS_ROLLBACK_BUG", -1))
         options &= ~SSL_OP_TLS_ROLLBACK_BUG;
      else
      if (*sptr != '+' && *sptr != '-' && !strsame(sptr,"0x",2))
         FaoToStdout ("-SSL-W-OPTIONS, unknown keyword\n \\!AZ\\\n", sptr);

      *zptr = ch;
   }

   if (OptionsPtr) *OptionsPtr = options;
   return (options);
}

/*****************************************************************************/
/*
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".  Can also be called to implement server
"certificate reload", for which the only approach seems to be to tear-down the
existing SSL context and create a completely new one - we'll see!
*/

BOOL SesolaInitService (SERVICE_STRUCT *svptr)

{
   BOOL  CipherListSupplied,
         CipherSuitesSupplied,
         KeySupplied,
         StrictTransSecSupplied,
         VerifyCAConfigured;
   int  status, value,
        VerifyMode;
   uint  options;
   ulong  ErrorNumber;
   char  *aptr, *cptr, *pemptr, *sptr;
   RSA  *RsaPtr;
   SERVICE_STRUCT  *tsvptr;
   SESOLA_CONTEXT  *scptr, *ssptr;
   SSL_CTX  *SslCtx;
   X509_STORE  *CertStorePtr;
   X509  *CertPtr;

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

   if (WATCH_MODULE(WATCH_MOD_SESOLA))
      WatchThis (WATCHALL, WATCH_MOD_SESOLA,
                 "SesolaInitService() !&X", svptr->SSLserverPtr);

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

   if (ProtocolHttpsDisabled)
   {
      FaoToStdout ("-HTTPD-W-SSL, disabled\n");
      return (false);
   }

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

   if (CliDemo)
   {
      scptr->CaFilePtr = scptr->CertFilePtr =
         scptr->CipherListPtr = scptr->CipherSuitesPtr =
         scptr->KeyFilePtr = scptr->StrictTransSecPtr = "";
      VerifyMode = SSL_VERIFY_NONE;
   }
   else
   {
      SesolaInitCertFile (svptr);

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

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

      CipherSuitesSupplied = false;
      if (scptr->CipherSuites[0])
      {
         CipherSuitesSupplied = true;
         scptr->CipherSuitesPtr = scptr->CipherSuites;
      }
      else
      {
         if (SesolaDefaultCipherSuitesPtr)
         {
            CipherSuitesSupplied = true;
            scptr->CipherSuitesPtr = SesolaDefaultCipherSuitesPtr;
         }
         else
            scptr->CipherSuitesPtr = "";
      }

      if (SesolaVerifyPeer)
         VerifyMode = SSL_VERIFY_PEER |
                      SSL_VERIFY_FAIL_IF_NO_PEER_CERT;
      else
      if (scptr->VerifyPeer == SESOLA_VERIFY_PEER_REQUIRED)
         VerifyMode = SSL_VERIFY_PEER |
                      SSL_VERIFY_FAIL_IF_NO_PEER_CERT;
      else
      if (scptr->VerifyPeer == SESOLA_VERIFY_PEER_OPTIONAL)
         VerifyMode = SSL_VERIFY_PEER;
      else
         VerifyMode = SSL_VERIFY_NONE;

      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])
      {
         if (VMSnok (status = OdsFileExists (NULL, scptr->CaFilePtr)))
         {
            FaoToStdout ("-SSL-E-CA, !AZ !&S\n", scptr->CaFilePtr, status);
            return (false);
         }
         /* set the global boolean as well (never reset it!) */
         VerifyCAConfigured = SesolaVerifyCAConfigured = true;
      }
      else
      {
         VerifyCAConfigured = SesolaVerifyCAConfigured = false;
         VerifyMode = SSL_VERIFY_NONE;
      }

      if (isdigit(scptr->StrictTransSec[0]))
      {
         StrictTransSecSupplied = true;
         scptr->StrictTransSecPtr = scptr->StrictTransSec;
      }
      else
      {
         if (SesolaDefaultStrictTransSecPtr &&
             isdigit(*SesolaDefaultStrictTransSecPtr))
         {
            StrictTransSecSupplied = true;
            scptr->StrictTransSecPtr = SesolaDefaultStrictTransSecPtr;
         }
         else
         {
            StrictTransSecSupplied = false;
            scptr->StrictTransSecPtr = "";
         }
      }

      if (scptr->VersionString[0])
         FaoToStdout ("-SSL-I-PROTOCOL, !AZ\n", scptr->VersionString);

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

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

      if (CipherSuitesSupplied)
         FaoToStdout ("-SSL-I-SUITES, !AZ\n", scptr->CipherSuitesPtr);

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

      if (scptr->StrictTransSecPtr[0])
         FaoToStdout ("-SSL-I-STRICT, HSTS transport security enabled\n");
      else
         FaoToStdout ("-SSL-W-STRICT, HSTS transport security disabled\n");

      if (VerifyMode)
      {
         if (SesolaVerifyPeer)
            cptr = "FAIL_IF_NO_PEER_CERT_AT_CONNECT";
         else
         if (VerifyMode == SSL_VERIFY_PEER)
            cptr = "OPTIONAL_PEER_CERT";
         else
         if (VerifyMode == SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT)
            cptr = "FAIL_IF_NO_PEER_CERT";
         FaoToStdout ("-SSL-I-VERIFY, !AZ\n", cptr);
      }
   }

   SesolaInitContext (scptr->VersionString, svptr->SSLserverPtr);

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

   cptr = scptr->CertFilePtr;
   if (*cptr && *cptr != '+')
      value = SSL_CTX_use_certificate_chain_file (SslCtx, cptr);
   else
   {
      if (*cptr == '+' && isalpha(*(cptr+1)))
         pemptr = SesolaMkCert(cptr+1);
      else
         pemptr = SesolaMkCert (svptr->ServerHostName);

      BIO_reset (SesolaBioMemPtr);
      BIO_puts (SesolaBioMemPtr, pemptr);
      CertPtr = PEM_read_bio_X509 (SesolaBioMemPtr, NULL, NULL, NULL);
      if (CertPtr)
      {
         value = SSL_CTX_use_certificate (SslCtx, CertPtr);
         X509_free (CertPtr);
      }
      else
         value = 0;
      BIO_reset (SesolaBioMemPtr);
      scptr->KeyFilePtr = "";
   }

   if (!value)
   {
      SesolaPrintOpenSslErrorList ();
      scptr->SslCtx = (void*)NULL;
      return (false);
   }

   /* combination of SSL version disables and configured discrete options */
   if (scptr->VersionOptions)
      options = scptr->VersionOptions | SesolaSSLoptions;
   else
      options = SesolaVersionOptions | SesolaSSLoptions;

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

      if (scptr->KeyFilePtr[0])
         value = SSL_CTX_use_RSAPrivateKey_file (SslCtx,
                                                 scptr->KeyFilePtr,
                                                 SSL_FILETYPE_PEM);
      else
      {
         BIO_reset (SesolaBioMemPtr);
         BIO_puts (SesolaBioMemPtr, pemptr);
         RsaPtr = PEM_read_bio_RSAPrivateKey (SesolaBioMemPtr, NULL,
                                              &SesolaPrivateKeyPasswd, scptr);
         if (RsaPtr)
         {
            value = SSL_CTX_use_RSAPrivateKey (SslCtx, RsaPtr);
            RSA_free (RsaPtr);
         }
         else
            value = 0;
         BIO_reset (SesolaBioMemPtr);
      }

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

      if (value)
      {
         if (SesolaPrivateKeyPasswdRequested && OpcomMessages)
            FaoToOpcom ("%HTTPD-I-PKPASSWD, password accepted");
         break;
      }

      ErrorNumber = ERR_peek_error ();
      SesolaPrintOpenSslErrorList ();

      if (SesolaPrivateKeyPasswdRequested &&
          ERR_GET_LIB(ErrorNumber) == ERR_LIB_EVP &&
          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) FaoToOpcom ("-SSL-E-PKPASSWD, password not accepted");
      scptr->SslCtx = (void*)NULL;
      return (false);
   }

   value = SSL_CTX_check_private_key (SslCtx);
   if (!value)
   {
      SesolaPrintOpenSslErrorList ();
      scptr->SslCtx = (void*)NULL;
      return (false);
   }

   if (SSL_CTX_get_max_proto_version (SslCtx) == TLS1_3_VERSION)
   {
      cptr = scptr->CipherSuitesPtr;
      if (!*cptr || *cptr == '*')
         scptr->CipherSuitesPtr = (const char*)OSSL_default_ciphersuites ();
      else
      if (*cptr && (*cptr == '+' || *cptr == '-' || *cptr == '!'))
      {
         /* modifying the default cipher suites */
         aptr = (const char*)OSSL_default_ciphersuites ();
         sptr = VmGet (strlen(aptr)+strlen(cptr)+4);
         sprintf (sptr, "%s :: %s", aptr, cptr);
         scptr->CipherSuitesPtr = cptr = sptr;
      }

      if (!SSL_CTX_set_ciphersuites (SslCtx, scptr->CipherSuitesPtr))
      {
         SesolaPrintOpenSslErrorList ();
         scptr->SslCtx = (void*)NULL;
         return (false);
      }
   }

   if (SSL_CTX_get_min_proto_version (SslCtx) == TLS1_2_VERSION ||
       SSL_CTX_get_min_proto_version (SslCtx) == TLS1_1_VERSION ||
       SSL_CTX_get_min_proto_version (SslCtx) == TLS1_VERSION)
   {
      cptr = scptr->CipherListPtr;
      if (!*cptr || *cptr == '*')
         scptr->CipherListPtr = (const char*)OSSL_default_cipher_list ();
      else
      if (*cptr == '+' || *cptr == '-' || *cptr == '!')
      {
         /* modifying the default cipher list */
         aptr = (const char*)OSSL_default_cipher_list ();
         sptr = VmGet (strlen(aptr)+strlen(cptr)+4);
         sprintf (sptr, "%s :: %s", aptr, cptr);
         scptr->CipherListPtr = cptr = sptr;
      }

      if (!SSL_CTX_set_cipher_list (SslCtx, scptr->CipherListPtr))
      {
         SesolaPrintOpenSslErrorList ();
         scptr->SslCtx = (void*)NULL;
         return (false);
      }
   }

   if (SesolaTmpDHbitsPtr)
   {
#ifdef SSL_CTX_set_ecdh_auto
      SSL_CTX_set_ecdh_auto (SslCtx, 1);
#endif
      SSL_CTX_set_tmp_dh_callback (SslCtx, SesolaTmpDHCallback);
      options |= SSL_OP_CIPHER_SERVER_PREFERENCE;
   }

   if (SesolaSessionCacheSize)
   {
      if (SesolaSessionCacheSize < SESOLA_DEFAULT_CACHE_RECORD_MAX)
         SesolaSessionCacheSize = SESOLA_DEFAULT_CACHE_RECORD_MAX;

      SSL_CTX_set_session_cache_mode (SslCtx, SSL_SESS_CACHE_SERVER);
      SSL_CTX_sess_set_cache_size (SslCtx, SesolaSessionCacheSize);
      if (scptr->SessionLifetime)
         SSL_CTX_set_timeout (SslCtx, scptr->SessionLifetime);
      else
      if (SesolaSessionLifetime)
         SSL_CTX_set_timeout (SslCtx, SesolaSessionLifetime);
      else
         SSL_CTX_set_timeout (SslCtx, SesolaSessionCacheTimeout*60);

      /* 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);
      }
   }
   else
   {
      SSL_CTX_set_session_cache_mode (SslCtx, SSL_SESS_CACHE_OFF);
      if (scptr->SessionLifetime)
         SSL_CTX_set_timeout (SslCtx, scptr->SessionLifetime);
      else
      if (SesolaSessionLifetime)
         SSL_CTX_set_timeout (SslCtx, SesolaSessionLifetime);
   }

   if (!(options & SSL_OP_NO_TICKET))
   {
#ifndef SESOLA_SESSION_TICKET_CALLBACK
#define SESOLA_SESSION_TICKET_CALLBACK 1
#endif
#if SESOLA_SESSION_TICKET_CALLBACK
      SSL_CTX_set_tlsext_ticket_key_cb (SslCtx, SesolaSessionTicketCallback);
#else
      SSL_CTX_set_tlsext_ticket_keys (SslCtx, SesolaTicketKey,
                                              sizeof(SesolaTicketKey));
#endif
   }

   if (SesolaVerifyPeer)
   {
      /*
         Global config verify peer can only apply to TLS v1.3
         (due to "early data" availibility?)
      */
      if (SSL_CTX_get_min_proto_version (SslCtx) == TLS1_3_VERSION)
      {
         SSL_CTX_set_verify (SslCtx, VerifyMode, &SesolaCertVerifyCallback);

         if (scptr->VerifyDepth)
            SSL_CTX_set_verify_depth (SslCtx, scptr->VerifyDepth);
         else
            SSL_CTX_set_verify_depth (SslCtx, SesolaDefaultVerifyDepth);
      }
      else
      {
         SSL_CTX_set_verify (SslCtx, VerifyMode, &SesolaCertVerifyCallback);

         if (scptr->VerifyDepth)
            SSL_CTX_set_verify_depth (SslCtx, scptr->VerifyDepth);
         else
            SSL_CTX_set_verify_depth (SslCtx, SesolaDefaultVerifyDepth);
      }
   }
   else
   {
      SSL_CTX_set_verify (SslCtx, VerifyMode, &SesolaCertVerifyCallback);

      if (scptr->VerifyDepth)
         SSL_CTX_set_verify_depth (SslCtx, scptr->VerifyDepth);
      else
         SSL_CTX_set_verify_depth (SslCtx, SesolaDefaultVerifyDepth);
   }

   if (scptr->CaFilePtr[0])
   {
      if (CertStorePtr = SesolaServiceSameCA (scptr->CaFilePtr))
         SSL_CTX_set1_cert_store (SslCtx, CertStorePtr);
      else
      {
         value = SSL_CTX_load_verify_locations (SslCtx,
                                                scptr->CaFilePtr,
                                                NULL);
         if (value) value = SSL_CTX_set_default_verify_paths (SslCtx);
         if (!value)
         {
            SesolaPrintOpenSslErrorList ();
            FaoToStdout ("-SSL-W-VERIFY, peer verification not enabled\n");
            scptr->SslCtx = (void*)NULL;
            return (false);
         }
      }
   }

   /* the session ID ctx needs to be shared between instances or tickets fail */
   if (!SSL_CTX_set_session_id_context (SslCtx, SesolaSessionId,
                                        sizeof(SesolaSessionId)))
   {
      SesolaPrintOpenSslErrorList ();
      FaoToStdout ("-SSL-W-SESSID, set CTX session ID failed\n");
      scptr->SslCtx = (void*)NULL;
      return (false);
   }

   if (SesolaSNI)
   {
      /* enable Server Name Indication */
      if (!SSL_CTX_set_tlsext_servername_callback (SslCtx, SesolaSNICallback))
      {
         SesolaSNI = false;
         FaoToStdout ("%HTTPD-E-SSL, SNI unsupported by SSL library\n");
      }
      else
         SSL_CTX_set_tlsext_servername_arg (SslCtx, 0);
   }

   if (scptr->VersionBitmap & SESOLA_TLSV1_2 ||
       scptr->VersionBitmap & SESOLA_TLSV1_3)
   {
      /* enable early stages of ClientHello processing on the server */ 
      SSL_CTX_set_client_hello_cb (SslCtx, SesolaHelloCallback, NULL);

      /* enable Application Level Protocol Negotiation */
      SSL_CTX_set_alpn_select_cb (SslCtx, SesolaAlpnCallback, NULL);
   }

   SSL_CTX_set_options (SslCtx, options);

   return (true);
}

/*****************************************************************************/
/*
Look for a service using the same CA file and return a pointer to the cert
store used by that.  Return NULL if a matching CA file not found.

OpenSSL-3.0 SSL_CTX_load_verify_locations() is significantly more expensive
than OpenSSL-1.1.1, so only ..load_verify_locations() when necessary and
SSL_CTX_set1_cert_store() for any other using the same CA file.
*/

X509_STORE* SesolaServiceSameCA (char* captr)

{
   int  ObjectCount;
   SERVICE_STRUCT  *svptr;
   SESOLA_CONTEXT  *scptr;
   SSL_CTX  *SslCtx;
   X509_STORE  *CertStorePtr;
   STACK_OF(X509_OBJECT) *ObjectsPtr;

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

   LIST_ITERATE (svptr, &ServiceList)
   {
      if (svptr->SchemeType != SCHEME_HTTPS) continue;
      if (!(scptr = (SESOLA_CONTEXT*)svptr->SSLserverPtr)) continue;
      if (!scptr->CaFilePtr) continue;
      if (strcasecmp (scptr->CaFilePtr, captr)) continue;
      SslCtx = ((SESOLA_CONTEXT*)svptr->SSLserverPtr)->SslCtx;
      if (!(CertStorePtr = SSL_CTX_get_cert_store (SslCtx))) continue;
      if (!(ObjectsPtr = X509_STORE_get0_objects (CertStorePtr))) continue;
      ObjectCount = sk_X509_OBJECT_num (ObjectsPtr);
      if (ObjectCount > 0) return (CertStorePtr);
   }
   return (NULL);
}

/*****************************************************************************/
/*
Return the number of TLS services in the global list.
*/

int SesolaServiceCount ()

{
   int  count;
   SERVICE_STRUCT  *svptr;

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

   count = 0;
   LIST_ITERATE (svptr, &ServiceList)
      if (svptr->SchemeType == SCHEME_HTTPS) count++;
   return (count);
}

/*****************************************************************************/
/*
Command-line service certificate reload function.

It seems to be able to be done with impunity and as often as might be
necessary.  In-progress TLS requests continue unaffected.  Hmmm.  Too good to
be true?  Perhaps.  Of course, OpenSSL objects, including the likes of service
"contexts" and connections in-progress, are reference-counted and so can be
free()d while in use, with resources returned to pools when the references drop
to zero.  In the case of memory, this may not reappear as unused until some
time after the reset.
*/

void SesolaControlReloadCerts ()

{
   int  ErrorCount,
        SSLcount,
        ReloadCount;
   char  *cptr;
   SESOLA_CONTEXT  *scptr;
   SERVICE_STRUCT  *svptr;

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

   if (WATCH_MODULE(WATCH_MOD_SESOLA))
      WatchThis (WATCHALL, WATCH_MOD_SESOLA, "SesolaControlReloadCerts()");

   /* enable SYSPRV to allow access to protected (certificate) files */
   sys$setprv (1, &SysPrvMask, 0, 0);

   ErrorCount = SSLcount = ReloadCount = 0;
   LIST_ITERATE (svptr, &ServiceList)
   {
      if (svptr->SchemeType != SCHEME_HTTPS) continue;
      SSLcount++;

      scptr = (SESOLA_CONTEXT*)svptr->SSLserverPtr;
      if (!scptr) continue;
      if (!scptr->SslCtx) continue;
      SSL_CTX_free ((SSL_CTX*)scptr->SslCtx);
      scptr->SslCtx = NULL;

      if (SesolaInitService (svptr))
         ReloadCount++;
      else
         ErrorCount++;
   }

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

   if (WATCH_CATEGORY (WATCH_INTERNAL))
      WatchThis (WATCHALL, WATCH_INTERNAL,
                 "SSL (re)load !UL of !UL SSL certificates!&@",
                 ReloadCount, SSLcount,
                 ErrorCount ? ", !UL error!%s" : "", ErrorCount);

   FaoToStdout ("%HTTPD-I-SSL, \
(re)load !UL of !UL SSL certificates!&@\n",
                ReloadCount, SSLcount,
                ErrorCount ? ", !UL error!%s" : "", ErrorCount);

   if (OpcomMessages & OPCOM_HTTPD ||
       OpcomMessages & OPCOM_AUTHORIZATION)
      FaoToOpcom ("%HTTPD-I-SSL, \
(re)load !UL of !UL SSL certificates!&@",
                  ReloadCount, SSLcount,
                  ErrorCount ? ", !UL error!%s" : "", ErrorCount);
}

/*****************************************************************************/
/*
Parse the (potentially wildcarded) certificate file specification into an
actual certificate file name.  The token ':*' inserts the port, and '*' inserts
the service host name (ODS-5 compatible, to make it ODS-2 compatible use '**'). 
Any other character is inserted as-is.  Examples:

  wasd_local:*_bundle.crt -> wasd_local:the^.host^.name_bundle.crt
  wasd_local:*_:*_bundle.crt -> wasd_local:the^.host^.name_443_bundle.crt
  wasd_local:**_bundle.crt -> wasd_local:the_host_name_bundle.crt
*/

void SesolaInitCertFile (SERVICE_STRUCT *svptr)

{
   BOOL  NameHit,
         PortHit;
   char  CertFile [256];
   char  *aptr, *cptr, *sptr, *zptr;
   SESOLA_CONTEXT  *scptr;

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

   if (WATCH_MODULE(WATCH_MOD_SESOLA))
      WatchThis (WATCHALL, WATCH_MOD_SESOLA, "SesolaInitCertFile()");

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

   if (scptr->CertFile[0])
      scptr->CertFilePtr = scptr->CertFile;
   else
   if (SesolaDefaultCertPtr)
      scptr->CertFilePtr = SesolaDefaultCertPtr;
   else
      scptr->CertFilePtr = "";

   if (strchr(scptr->CertFilePtr,'*'))
   {
      NameHit = PortHit = false;
      zptr = (sptr = CertFile) + sizeof(CertFile)-1;
      cptr = scptr->CertFilePtr;
      while (*cptr && sptr < zptr)
      {
         if (NameHit && !PortHit && *(USHORTPTR)cptr == ':*')
         {
            PortHit = true;
            cptr += 2;
            for (aptr = svptr->ServerPortString;
                 *aptr && sptr < zptr;
                 *sptr++ = *aptr++);
         }
         else
         if (!NameHit && *cptr == '*')
         {
            NameHit = true;
            cptr++;
            if (*cptr == '*')
            {
               /* ODS-2 compatibility */
               cptr++;
               for (aptr = svptr->ServerHostName; *aptr && sptr < zptr; aptr++)
                  if (*aptr == '.')
                     *sptr++ = '_';
                  else
                     *sptr++ = *aptr;
            }
            else
            {
               /* ODS-5 compatibility */
               for (aptr = svptr->ServerHostName;
                    *aptr && sptr < zptr;
                    *sptr++ = *aptr++);
            }
         }
         else
            *sptr++ = *cptr++;
      }
      *sptr = '\0';
      scptr->CertFilePtr = VmGet (sptr - CertFile + 1);
      memcpy (scptr->CertFilePtr, CertFile, sptr - CertFile);
   }

   FaoToStdout ("-SSL-I-CERT, !AZ\n", scptr->CertFilePtr &&
                                      scptr->CertFilePtr[0] ? 
                                      scptr->CertFilePtr :
                                      "none - make ex nihilo");
}

/*****************************************************************************/
/*
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,
         CipherSuitesSupplied,
         KeySupplied;
   int  value;
   ulong  ErrorNumber;
   char  *aptr, *cptr, *pemptr, *sptr;
   RSA  *RsaPtr;
   SSL_CTX  *SslCtx;
   SESOLA_CONTEXT  *scptr;
   X509_STORE  *CertStorePtr;
   X509  *CertPtr;

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

   if (WATCH_MODULE(WATCH_MOD_SESOLA))
      WatchThis (WATCHALL, WATCH_MOD_SESOLA, "SesolaInitClientService()");

   if (ProtocolHttpsDisabled)
   {
      FaoToStdout ("-HTTPD-W-SSL, disabled\n");
      return (false);
   }

   if (!svptr->SSLclientPtr)
   {
      /* no client-specific context, use any associated 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->CertFile[0])
      scptr->CertFilePtr = scptr->CertFile;
   else
   if (SesolaDefaultCertPtr)
      scptr->CertFilePtr = SesolaDefaultCertPtr;
   else
      scptr->CertFilePtr = "";


   KeySupplied = false;
   if (scptr->KeyFile[0])
   {
      KeySupplied = true;
      scptr->KeyFilePtr = scptr->KeyFile;
   }
   else
   {
      if (scptr->CertFilePtr[0])
      {
         /* service has a specific certificate, assume the key's in that */
         if (scptr->CertFilePtr[0] == '+')
            scptr->KeyFilePtr = "";
         else
            scptr->KeyFilePtr = scptr->CertFilePtr;
      }
      else
      if (SesolaDefaultKeyPtr)
      {
         /* use the separately specified key */
         KeySupplied = true;
         scptr->KeyFilePtr = SesolaDefaultKeyPtr;
      }
      else
      {
         /* if no default key then assume it's in the certificate file */
         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->CipherSuites[0])
   {
      CipherSuitesSupplied = true;
      scptr->CipherSuitesPtr = scptr->CipherSuites;
   }
   else
   {
      if (SesolaDefaultCipherSuitesPtr)
      {
         CipherSuitesSupplied = true;
         scptr->CipherSuitesPtr = SesolaDefaultCipherSuitesPtr;
      }
      else
      {
         CipherSuitesSupplied = false;
         scptr->CipherSuitesPtr = "";
      }
   }

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

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

   if (CipherSuitesSupplied)
      FaoToStdout ("-SSL-I-SUITES, (a/client) !AZ\n", scptr->CipherSuitesPtr);

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

   SesolaInitContext (scptr->VersionString, svptr->SSLclientPtr);

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

   if (scptr->VersionStringPtr)
      FaoToStdout ("-SSL-I-PROTOCOL, (a/client) !AZ\n",
                   scptr->VersionStringPtr);

   cptr = scptr->CertFilePtr;
   if (*cptr && *cptr != '+')
      value = SSL_CTX_use_certificate_chain_file (SslCtx, cptr);
   else
   {
      if (*cptr == '+' && isalpha(*(cptr+1)))
         pemptr = SesolaMkCert(cptr+1);
      else
         pemptr = SesolaMkCert (svptr->ServerHostName);

      BIO_reset (SesolaBioMemPtr);
      BIO_puts (SesolaBioMemPtr, pemptr);
      CertPtr = PEM_read_bio_X509 (SesolaBioMemPtr, NULL, NULL, NULL);
      if (CertPtr)
      {
         value = SSL_CTX_use_certificate (SslCtx, CertPtr);
         X509_free (CertPtr);
      }
      else
         value = 0;
      BIO_reset (SesolaBioMemPtr);
      scptr->KeyFilePtr = "";
   }

   if (!value)
   {
      SesolaPrintOpenSslErrorList ();
      scptr->SslCtx = (void*)NULL;
      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, svptr);
      SesolaPrivateKeyPasswdRequested = false;

      if (scptr->KeyFilePtr[0])
         value = SSL_CTX_use_RSAPrivateKey_file (SslCtx,
                                                 scptr->KeyFilePtr,
                                                 SSL_FILETYPE_PEM);
      else
      {
         BIO_reset (SesolaBioMemPtr);
         BIO_puts (SesolaBioMemPtr, pemptr);
         RsaPtr = PEM_read_bio_RSAPrivateKey (SesolaBioMemPtr, NULL,
                                              &SesolaPrivateKeyPasswd, scptr);
         if (RsaPtr)
         {
            value = SSL_CTX_use_RSAPrivateKey (SslCtx, RsaPtr);
            RSA_free (RsaPtr);
         }
         else
            value = 0;
         BIO_reset (SesolaBioMemPtr);
      }

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

      if (value)
      {
         if (SesolaPrivateKeyPasswdRequested && OpcomMessages)
            FaoToOpcom ("%HTTPD-I-PKPASSWD, password accepted");
         break;
      }

      ErrorNumber = ERR_peek_error ();
      SesolaPrintOpenSslErrorList ();

      if (SesolaPrivateKeyPasswdRequested &&
          ERR_GET_LIB(ErrorNumber) == ERR_LIB_EVP &&
          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) FaoToOpcom ("-SSL-E-PKPASSWD, password not accepted");
      scptr->SslCtx = (void*)NULL;
      return (false);
   }

   value = SSL_CTX_check_private_key (SslCtx);
   if (!value)
   {
      SesolaPrintOpenSslErrorList ();
      scptr->SslCtx = (void*)NULL;
      return (false);
   }

   if (SSL_CTX_get_max_proto_version (SslCtx) == TLS1_3_VERSION)
   {
      cptr = scptr->CipherSuitesPtr;
      if (!*cptr || *cptr == '*')
         scptr->CipherSuitesPtr = (const char*)OSSL_default_ciphersuites ();
      else
      if (*cptr && (*cptr == '+' || *cptr == '-' || *cptr == '!'))
      {
         /* modifying the default cipher suites */
         aptr = (const char*)OSSL_default_ciphersuites ();
         sptr = VmGet (strlen(aptr)+strlen(cptr)+4);
         sprintf (sptr, "%s :: %s", aptr, cptr);
         scptr->CipherSuitesPtr = cptr = sptr;
      }

      if (!SSL_CTX_set_ciphersuites (SslCtx, scptr->CipherSuitesPtr))
      {
         SesolaPrintOpenSslErrorList ();
         scptr->SslCtx = (void*)NULL;
         return (false);
      }
   }

   if (SSL_CTX_get_min_proto_version (SslCtx) == TLS1_2_VERSION ||
       SSL_CTX_get_min_proto_version (SslCtx) == TLS1_1_VERSION ||
       SSL_CTX_get_min_proto_version (SslCtx) == TLS1_VERSION)
   {
      cptr = scptr->CipherListPtr;
      if (!*cptr || *cptr == '*')
         scptr->CipherListPtr = (const char*)OSSL_default_cipher_list ();
      else
      if (*cptr && (*cptr == '+' || *cptr == '-' || *cptr == '!'))
      {
         /* modifying the default cipher suites */
         aptr = (const char*)OSSL_default_cipher_list ();
         sptr = VmGet (strlen(aptr)+strlen(cptr)+4);
         sprintf (sptr, "%s :: %s", aptr, cptr);
         scptr->CipherListPtr = cptr = sptr;
      }

      if (!SSL_CTX_set_cipher_list (SslCtx, scptr->CipherListPtr))
      {
         SesolaPrintOpenSslErrorList ();
         scptr->SslCtx = (void*)NULL;
         return (false);
      }
   }

   if (SesolaSessionCacheSize)
   {
      if (SesolaSessionCacheSize < SESOLA_DEFAULT_CACHE_RECORD_MAX)
         SesolaSessionCacheSize = SESOLA_DEFAULT_CACHE_RECORD_MAX;

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

      /* 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);
      }
   }
   else
      SSL_CTX_set_session_cache_mode (SslCtx, SSL_SESS_CACHE_OFF);

   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])
   {
      if (CertStorePtr = SesolaServiceSameCA (scptr->CaFilePtr))
         SSL_CTX_set1_cert_store (SslCtx, CertStorePtr);
      else
      {
         value = SSL_CTX_load_verify_locations (SslCtx,
                                                scptr->CaFilePtr,
                                                NULL);
         if (value) value = SSL_CTX_set_default_verify_paths (SslCtx);
         if (!value)
         {
            SesolaPrintOpenSslErrorList ();
            FaoToStdout ("-SSL-W-SSL, (a/client) CA verification not enabled\n");
            scptr->SslCtx = (void*)NULL;
            return (false);
         }
      }
   }

   /* combination of SSL version disables and configured discrete options */
   if (scptr->VersionOptions)
      SSL_CTX_set_options (SslCtx, (scptr->VersionOptions | SesolaSSLoptions));
   else
      SSL_CTX_set_options (SslCtx, (SesolaVersionOptions | SesolaSSLoptions));

   return (true);
}

/*****************************************************************************/
/*
If the private key in WASD_SSL_CERT, WASD_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 (WATCHALL, 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))
      {
         FaoToStdout ("%HTTPD-I-PKPASSWD, \
!20%D, request !UL/!UL for private key password, !AZ//!AZ\n",
             0, SesolaPrivateKeyPasswdAttempt, SESOLA_PKPASSWD_ATTEMPTS,
             svptr->SchemeNamePtr, svptr->ServerHostPort);

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

         /* if any OPCOM at all is enabled then output this message */
         if (OpcomMessages)
            FaoToOpcom ("%HTTPD-I-PKPASSWD, \
request !UL/!UL for private key password, !AZ//!AZ",
             SesolaPrivateKeyPasswdAttempt, SESOLA_PKPASSWD_ATTEMPTS,
             svptr->SchemeNamePtr, 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);
}

/*****************************************************************************/
/*
Generate fresh random data used with the session ticket key and session ID
context.  As the ticket key name (first 16 bytes) will be public make the
session ID the same data.  Return a pointer to the key data.
*/

uchar* SesolaSessionTicketNewKey ()

{
   /*********/
   /* begin */
   /*********/

   if (WATCH_MODULE(WATCH_MOD_SESOLA))
      WatchThis (WATCHALL, WATCH_MOD_SESOLA, "SesolaSessionTicketNewKey()");

   RAND_bytes (SesolaTicketKey, sizeof(SesolaTicketKey));
   memmove (SesolaSessionId, SesolaTicketKey, sizeof(SesolaSessionId));

   return (SesolaTicketKey);
}

/*****************************************************************************/
/*
Propagate supplied session ticket key data to global storage and applicable
services.  Return a pointer to the key data.  The ticket random data is also
used (with offset) to provide the session ID context.  The session ID context
must be common across instances for session ticket reuse to be successful.  As
the ticket key name (first 16 bytes) will be public make the session ID the
same data.  Can be directly called by InstanceSessionTicketKey() and
(indirectly) by ControlHttpdAst() when being propagated over multiiple
instances (including cluster-wide).
*/

uchar* SesolaSessionTicketUseKey (uchar *keyptr)

{
   int  idx;
   ulong  options;
   SERVICE_STRUCT  *svptr;
   SSL_CTX  *SslCtx;

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

   if (WATCH_MODULE(WATCH_MOD_SESOLA))
      WatchThis (WATCHALL, WATCH_MOD_SESOLA, "SesolaSessionTicketUseKey()");

   if (WATCH_CATEGORY(WATCH_SESOLA))
   {
      WatchThis (WATCHALL, WATCH_SESOLA, "SESSION TICKET key");
      WatchDataDump (keyptr, sizeof(SesolaTicketKey));
   }

   /* null-terminated string then 48 bytes of session key */
   if (strsame (keyptr, CONTROL_SESSION_TICKET_KEY, -1))
      keyptr += sizeof(CONTROL_SESSION_TICKET_KEY);
   /* else no null-terminated string, just the 48 bytes of session key */

   /* note the date-day the key was refreshed by InstanceSupervisor() */
   SesolaTicketKeySuperDay = HttpdTime7[2];

   /* time of this most recent refresh */
   SesolaTicketKeyTime64 = HttpdTime64;

   /* if the key is empty (16 byte lock value block) */
   if (!*(ULONGPTR)keyptr) keyptr = SesolaSessionTicketNewKey();

   memmove (SesolaTicketKey, keyptr, sizeof(SesolaTicketKey));
   memmove (SesolaSessionId, keyptr, sizeof(SesolaSessionId));

   /* propagate session ticket key to required services */
   LIST_ITERATE (svptr, &ServiceList)
   {
      if (svptr->SchemeType != SCHEME_HTTPS) continue;
      if (SslCtx = ((SESOLA_CONTEXT*)svptr->SSLserverPtr)->SslCtx)
      {
         options = SSL_CTX_get_options (SslCtx);
         if (!(options & SSL_OP_NO_TICKET))
         {
#if !SESOLA_SESSION_TICKET_CALLBACK
            SSL_CTX_set_tlsext_ticket_keys (SslCtx, SesolaTicketKey,
                                                    sizeof(SesolaTicketKey));
#endif
            /* the session ID ctx also needs to be shared between instances */
            SSL_CTX_set_session_id_context (SslCtx, SesolaSessionId,
                                            sizeof(SesolaSessionId));
         }
      }
   }

   return (SesolaTicketKey);
}

/*****************************************************************************/
/*
Session tickets, defined in RFC5077, provide an enhanced session resumption
capability where the server implementation is not required to maintain per
session state.  The callback function will be called for every client
instigated TLS session when session ticket extension is presented in the TLS
hello message.  It is the responsibility of this function to create or retrieve
the cryptographic parameters and to maintain their state.  This function, in
conjunction with SesolaSessionTicketNewKey(), SesolaSessionTicketUseKey() and
InstanceSessionTicketKey() manage coherent usage of the session ticket keys
allowing multi-instance, cluster-wide if required, session resumption using
tickets, and refreshes those ticket keys once a day.
*/

#if SESOLA_SESSION_TICKET_CALLBACK

int SesolaSessionTicketCallback
(
SSL *SslPtr,
uchar *KeyName,
uchar *iv,
EVP_CIPHER_CTX *ectx,
HMAC_CTX *hctx,
int enc
)
{
#ifndef tlsext_tick_md
#ifdef OPENSSL_NO_SHA256
#define tlsext_tick_md  EVP_sha1
#else
#define tlsext_tick_md  EVP_sha256
#endif
#endif

   static  uchar  *AesKeyPtr = SesolaTicketKey + 32,
                  *HmacKeyPtr = SesolaTicketKey + 16,
                  *NameKeyPtr = SesolaTicketKey;

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

   if (WATCH_MODULE(WATCH_MOD_SESOLA))
      WatchThis (WATCHALL, WATCH_MOD_SESOLA,
                 "SesolaSessionTicketCallback() !UL !16&H", enc, KeyName);

   if (enc)
   {
      /******************/
      /* create session */
      /******************/

      memcpy (KeyName, NameKeyPtr, 16);
      RAND_bytes (iv, 16);

      if (!EVP_EncryptInit_ex (ectx, EVP_aes_128_cbc(), NULL, AesKeyPtr, iv))
         return (-1);

      if (!HMAC_Init_ex (hctx, HmacKeyPtr, 16, tlsext_tick_md(), NULL))
         return (-1);

      SesolaSessionTicketNew++;
   }
   else
   {
      /********************/
      /* retrieve session */
      /********************/

      if (memcmp (KeyName, NameKeyPtr, 16))
      {
         if (WATCH_MODULE(WATCH_MOD_SESOLA))
            WatchThis (WATCHALL, WATCH_MOD_SESOLA,
                       "!16&H !!= !16&H", KeyName, NameKeyPtr);
         SesolaSessionTicketMiss++;
         return (0);
      }

      if (!HMAC_Init_ex (hctx, HmacKeyPtr, 16, tlsext_tick_md(), NULL))
         return (-1);

      if (!EVP_DecryptInit_ex (ectx, EVP_aes_128_cbc(), NULL, AesKeyPtr, iv))
         return (-1);

      SesolaSessionTicketHit++;
   }

   return (1);
}

#endif /* SESOLA_SESSION_TICKET_CALLBACK */

/*****************************************************************************/
/*
Temporary DH params supporting ephemeral keys (for "perfect forward security").
See explanation in module prologue under section "FORWARD SECRECY".
*/

DH* SesolaTmpDHCallback
(
SSL *SslPtr,
int IsExport,
int KeyLength
)
{
#define BITS_MAX 5

                                                    /* future proofing? :-} */
   static int  bits [BITS_MAX] = { 512, 1024, 2048, 4096, 8192 };
   static DH  *bitdh [BITS_MAX];
   static int  maxidx;
   static ulong  PrevTickSecond;

   int  keybit, idx, size;
   char  buf [256];
   DH  *dhptr;
   REQUEST_STRUCT  *rqptr;

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

   if (WATCH_MODULE(WATCH_MOD_SESOLA))
      WatchThis (WATCHALL, WATCH_MOD_SESOLA,
                 "SesolaTmpDHCallback() !UL", KeyLength);

   if (!SslPtr)
   {
      /**************/
      /* initialise */
      /**************/

      char  *sptr;

      /* enable SYSPRV allowing access to should-be protected files */
      sys$setprv (1, &SysPrvMask, 0, 0);

      for (idx = 0; idx < BITS_MAX; idx++) bitdh[idx] = NULL;
      for (idx = 0; idx < BITS_MAX; idx++)
      {
         FILE  *pfile;
         sprintf (buf, "WASD_LOCAL:DH_PARAM_%d.PEM", bits[idx]);
         if ((pfile = fopen (buf, "r")) == NULL) continue;
         bitdh[idx] = PEM_read_DHparams (pfile, NULL, NULL, NULL);
         fclose (pfile);
         maxidx = idx + 1;
      }

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

      if (maxidx)
      {
         *(sptr = buf) = '\0';
         for (idx = 0; idx < maxidx; idx++)
            if (bitdh[idx] != NULL)
               sptr += sprintf (sptr, "%s%d", buf[0] ? "," : "", bits[idx]);
         strzcat (buf, " bits", sizeof(buf));
         SesolaTmpDHbitsPtr = VmGet (size = strlen(buf)+1);
         strzcpy (SesolaTmpDHbitsPtr, buf, size);
         FaoToStdout ("-SSL-I-DH, ephemeral DH param !AZ\n", buf);
      }
      else
         FaoToStdout ("-SSL-W-DH, no ephemeral DH param\n");

      return ((DH*)SesolaTmpDHbitsPtr);
   }

   /************/
   /* callback */
   /************/

   rqptr = (REQUEST_STRUCT*)SSL_get_app_data (SslPtr);

   keybit = 0;
   dhptr = NULL;
   for (idx = 0; idx < maxidx; idx++)
   {
      if (bitdh[idx] == NULL) continue;
      if (bits[idx] >= KeyLength)
      {
         if (WATCHING (rqptr, WATCH_SESOLA))
            WatchThis (WATCHITM(rqptr), WATCH_SESOLA,
                       "DH key requested !UL got !UL bits",
                       KeyLength, bits[idx]);
         return (bitdh[idx]);
      }
      keybit = bits[idx];
      dhptr = bitdh[idx];
   }     

   if (WATCHING (rqptr, WATCH_SESOLA))
      WatchThis (WATCHITM(rqptr), WATCH_SESOLA,
                 "DH key requested !UL got !UL bits",
                 KeyLength, dhptr ? keybit : 0);

   return (dhptr);
}

/*****************************************************************************/
/*
Callback during the early stages of ClientHello processing on the server. 
Allow for various extension processing by on-calling as required.
*/

int SesolaHelloCallback
(
SSL *SslPtr,
int *al,
void *arg
)
{
   int  retval;

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

   if (WATCH_MODULE(WATCH_MOD_SESOLA))
      WatchThis (WATCHALL, WATCH_MOD_SESOLA, "SesolaHelloCallback()");

   retval = SesolaAcmeTls1 (SslPtr, al, arg);

   return (retval);
}

/*****************************************************************************/
/*
Implement Application-Layer Protocol Negotiation (ALPN), a TLS extension.
ALPN allows the application layer to negotiate which protocol should be
performed over a secure connection in a manner which avoids additional round
trips and which is independent of the application layer protocols.
*/

int SesolaAlpnCallback
(
SSL *SslPtr,
uchar **out,
uchar *outlen,
uchar *in,
uint inlen,
void *arg
)
{
   BOOL  acme01, h2, http11;
   uint  cnt, retval;
   char  *cptr, *sptr, *zptr;
   REQUEST_STRUCT  *rqptr;
   SESOLA_STRUCT  *sesolaptr;

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

   sesolaptr = (SESOLA_STRUCT*)SSL_get_ex_data (SslPtr, 0);
   rqptr = (REQUEST_STRUCT*)sesolaptr->RequestPtr;
   acme01 = h2 = http11 = false;

   /* vector of 8-bit, length prefixed byte strings */
   for (cptr = in; cptr < in + inlen; cptr += cnt)
   {
      cnt = *cptr++;
      if (WATCHING (rqptr, WATCH_SESOLA))
         WatchThis (WATCHITM(rqptr), WATCH_SESOLA, "ALPN !#AZ", cnt, cptr);
      if (cnt == 2 && MATCH2 (cptr, "h2")) h2 = true;
      if (cnt == 8 && MATCH8 (cptr, "http/1.1")) http11 = true;
      if (cnt == 10 && MATCH10 (cptr, "acme-tls/1")) acme01 = true;
   }

   retval = SSL_TLSEXT_ERR_NOACK;
   if (acme01)
   {
      *out = sesolaptr->AlpnProtocol = "acme-tls/1";
      *outlen = 10;
      SesolaAcmeCertify (sesolaptr);
      retval = SSL_TLSEXT_ERR_OK;
   }

   if (retval == SSL_TLSEXT_ERR_NOACK)
   {
      if (h2 && Http2Enabled && rqptr->ServicePtr->Http2Enabled)
      {
         *out = sesolaptr->AlpnProtocol = "h2";
         *outlen = 2;
         retval = SSL_TLSEXT_ERR_OK;
      }
   }

   if (retval == SSL_TLSEXT_ERR_NOACK)
   {
      if (http11)
      {
         *out = sesolaptr->AlpnProtocol = "http/1.1";
         *outlen = 8;
         retval = SSL_TLSEXT_ERR_OK;
      }
   }

   if (WATCHING (rqptr, WATCH_SESOLA))
      WatchThis (WATCHITM(rqptr), WATCH_SESOLA, "OK !AZ",
                 sesolaptr->AlpnProtocol ? sesolaptr->AlpnProtocol : "(none)");

   return (retval);
}

/*****************************************************************************/
/*
Implement TLS1 Server Name Indication (SNI) callback (from RFC 4366).
Searches the SSL service list for a matching name (certificates are issued
against a host name, not against a name:port combination).  The first name
matched results in the SSL context being set/changed.  Then continue and set
the virtual service if available (significantly more efficient than doing it
using ServiceFindVirtual() later).
*/

int SesolaSNICallback
(
SSL *SslPtr,
int *UnusedArg1,
void *UnusedArg2
)
{
   BOOL  wwwName;
   int  snlen,
        ServerPort,
        VerifyDepth,
        VerifyMode;
   char  *cptr, *sptr, *snptr, *zptr;
   REQUEST_STRUCT  *rqptr;
   SERVICE_STRUCT  *svptr;
   SESOLA_STRUCT  *sesolaptr;
   SSL_CTX  *ctx, *SslCtx;
   X509_STORE_CTX  *VerifyCallback;

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

   sesolaptr = (SESOLA_STRUCT*)SSL_get_ex_data (SslPtr, 0);

   snptr = SSL_get_servername (SslPtr, TLSEXT_NAMETYPE_host_name);

   if (WATCHMOD (sesolaptr, WATCH_MOD_SESOLA))
      WatchThis (WATCHITM(sesolaptr), WATCH_MOD_SESOLA,
                 "SesolaSNICallback() !&Z", snptr);

   if (!snptr) return (SSL_TLSEXT_ERR_OK);

   if (sesolaptr->SNIserviceSet) return (SSL_TLSEXT_ERR_OK);

   if (ServiceWwwImplied)
      wwwName = SAME4(snptr,'www.');
   else
      wwwName = false;

   rqptr = (REQUEST_STRUCT*)sesolaptr->RequestPtr;

   /* note the original service port number */
   ServerPort = rqptr->ServicePtr->ServerPort;

   /* used to emulate the Apache SSL_TLS_SNI variable */
   zptr = (sptr = sesolaptr->SNIServerName) +
          sizeof(sesolaptr->SNIServerName)-1;
   for (cptr = snptr; *cptr && sptr < zptr; *sptr++ = *cptr++);
   *sptr = '\0';
   snlen = cptr - snptr;

   LIST_ITERATE (svptr, &ServiceList)
   {
      if (WATCHMOD (sesolaptr, WATCH_MOD_SESOLA))
         WatchThis (WATCHITM(sesolaptr), WATCH_MOD_SESOLA,
                    "!&Z !&B !UL !UL !XL",
                    svptr->ServerHostName, 
                    svptr->SchemeType == SCHEME_HTTPS,
                    ServerPort, svptr->ServerPort,
                    svptr->SSLserverPtr ?
                       ((SESOLA_CONTEXT*)svptr->SSLserverPtr)->SslCtx : 0);

      if (svptr->SchemeType != SCHEME_HTTPS) continue;
      if (!((SESOLA_CONTEXT*)svptr->SSLserverPtr)->SslCtx) continue;

      if (!wwwName || snlen != svptr->ServerHostNameLength + 4)
         if (snlen != svptr->ServerHostNameLength)
            continue;

      /* match the SNI name to the service name */
      sptr = snptr;
      if (wwwName && !MATCH4 (sptr, "www.")) sptr += 4;
      cptr = svptr->ServerHostName;
      if (snlen >= 8)
      {
         if (!MATCH8 (sptr, cptr)) continue;
         sptr += 8;
         cptr += 8;
      }
      while (*cptr && *sptr && *cptr == *sptr)
      {
         cptr++;
         sptr++;
      }
      if (*sptr || *cptr) continue;

      /* match to the original port */
      if (ServerPort != svptr->ServerPort) continue;

      if (WATCHING (rqptr, WATCH_SESOLA))
         WatchThis (WATCHITM(rqptr), WATCH_SESOLA, "SNI match !AZ,!UL",
                    snptr, svptr->ServerPort);

      if (svptr == rqptr->ServicePtr) return (SSL_TLSEXT_ERR_OK);

      sesolaptr->SNIserviceSet = true;

      /* set the SSL context */
      SslCtx = ((SESOLA_CONTEXT*)svptr->SSLserverPtr)->SslCtx;
      SSL_set_SSL_CTX (SslPtr, SslCtx);

      /* propagate newly set context client verify to SSL-specific */
      VerifyMode = SSL_CTX_get_verify_mode (SslCtx);
      VerifyDepth = SSL_CTX_get_verify_depth (SslCtx);
      VerifyCallback = SSL_CTX_get_verify_callback (SslCtx);
      SSL_set_verify (SslPtr, VerifyMode, VerifyCallback);
      SSL_set_verify_depth (SslPtr, VerifyDepth);

      /* change to this service */
      rqptr->ServicePtr = svptr;
      sesolaptr->NetIoPtr->ServicePtr = svptr;
      if (WATCHPNT(rqptr))
      {
         if (WATCH_CATEGORY(WATCH_SESOLA))
            WatchThis (WATCHITM(rqptr), WATCH_SESOLA, "SNI VIRTUAL !AZ",
                       svptr->ServerHostPort);
         else
         if (WATCH_CATEGORY(WATCH_CONNECT))
            WatchThis (WATCHITM(rqptr), WATCH_CONNECT, "VIRTUAL (SNI) !AZ",
                       svptr->ServerHostPort); 
      }
      return (SSL_TLSEXT_ERR_OK);
   }

   if (WATCHING (rqptr, WATCH_SESOLA))
      if (!sesolaptr->SNIctxSet)
         WatchThis (WATCHITM(rqptr), WATCH_SESOLA, "SNI FAIL !AZ", snptr);

   return (SSL_TLSEXT_ERR_OK);
}

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

int SesolaDassgnChannel (SESOLA_STRUCT *sesolaptr)

{
   int  channel, status;

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

   if (WATCH_MODULE(WATCH_MOD_SESOLA))
      WatchThis (WATCHALL, WATCH_MOD_SESOLA, "SesolaDassgnChannel()");

   if (!sesolaptr->NetIoPtr) return (SS$_ABORT);

   if (status = sesolaptr->NetIoPtr->Channel)
   {
      /* break the connection */
      status = sys$dassgn (sesolaptr->NetIoPtr->Channel);
      sesolaptr->NetIoPtr->Channel = 0;
   }
   return (status);
}

/*****************************************************************************/
/*
Return a TLS Application Level Protocol Negotiation string or NULL.
*/

char* SesolaRequestALPN (REQUEST_STRUCT *rqptr)

{
   SESOLA_STRUCT  *sesolaptr;

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

   if (WATCH_MODULE(WATCH_MOD_SESOLA))
      WatchThis (WATCHALL, WATCH_MOD_SESOLA, "SesolaRequestALPN()");

   if (!(sesolaptr = SesolaRequestSesolaPtr (rqptr))) return (NULL);
   return (sesolaptr->AlpnProtocol);
}

/*****************************************************************************/
/*
Return a request's secure socket pointer or NULL.
*/

void* SesolaRequestSesolaPtr (REQUEST_STRUCT *rqptr)

{
   SESOLA_STRUCT  *sesolaptr;

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

   if (WATCH_MODULE(WATCH_MOD_SESOLA))
      WatchThis (WATCHALL, WATCH_MOD_SESOLA, "SesolaRequestSesolaPtr()");

   if (!rqptr) return (NULL);
   sesolaptr = NULL;
   if (HTTP2_REQUEST(rqptr))
      sesolaptr = (SESOLA_STRUCT*)rqptr->
                  Http2Ptr->NetIoPtr->SesolaPtr; 
   if (!sesolaptr) sesolaptr = (SESOLA_STRUCT*)rqptr->NetIoPtr->SesolaPtr;
   return (sesolaptr);
}

/*****************************************************************************/
/*
Return the value for ServiceFindVirtual() to use.
This stub function just used to dereference (void*).
 */

BOOL SesolaSNIserviceSet (SESOLA_STRUCT *sesolaptr)

{
   /*********/
   /* begin */
   /*********/

   if (WATCH_MODULE(WATCH_MOD_SESOLA))
      WatchThis (WATCHALL, WATCH_MOD_SESOLA,
                 "SesolaSNIserviceSet() !&B", sesolaptr->SNIserviceSet);

   return (sesolaptr->SNIserviceSet);
}

/*****************************************************************************/
/*
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;
   ulong  ErrorNumber;
   char  *FileNamePtr,
         *DataStringPtr;
   char  Buffer [256];

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

   if (WATCH_MODULE(WATCH_MOD_SESOLA))
      WatchThis (WATCHALL, WATCH_MOD_SESOLA,
                 "SesolaPrintOpenSslErrorList()");

   while (ErrorNumber =
          ERR_get_error_line_data (&FileNamePtr, &LineNumber,
                                   &DataStringPtr, &Flags))
   {
      FaoToStdout (
"-SSL-W-ERROR, \"!AZ\" (!AZ !SL)!&?\"\r\r!AZ!&?\"\r\r\n",
         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;
   SESOLA_STRUCT  *sesolaptr;

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

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

   sesolaptr = (SESOLA_STRUCT*)SSL_get_ex_data (SslPtr, 0);

   if (!sesolaptr->WatchItem) return;

   switch (WhereExactly & SSL_ST_MASK)
   {
      case SSL_CB_LOOP :
         WatchThis (WATCHITM(sesolaptr), WATCH_SESOLA,
                    "LOOP !AZ", SSL_state_string_long(SslPtr));
         break;
      case SSL_CB_EXIT :
         if (value == 0)
            WatchThis (WATCHITM(sesolaptr), WATCH_SESOLA,
                       "EXIT alert failed in !AZ",
                       SSL_state_string_long(SslPtr));
         else
         if (value < 0)
            WatchThis (WATCHITM(sesolaptr), WATCH_SESOLA,
                       "EXIT error/blocking in !AZ", 
                       SSL_state_string_long(SslPtr));
         break;
      case SSL_CB_READ :
         WatchThis (WATCHITM(sesolaptr), WATCH_SESOLA,
                    "READ read !AZ", SSL_state_string_long(SslPtr));
         break;
      case SSL_CB_WRITE :
         WatchThis (WATCHITM(sesolaptr), WATCH_SESOLA,
                    "WRITE !AZ", SSL_state_string_long(SslPtr));
         break;
      case SSL_CB_ALERT :
         WatchThis (WATCHITM(sesolaptr), WATCH_SESOLA,
                    "ALERT !AZ !AZ:!AZ",
                    (WhereExactly & SSL_CB_READ) ? "read" : "write",
                    SSL_alert_type_string_long(value),
                    SSL_alert_desc_string_long(value));
         break;
      case SSL_CB_READ_ALERT :
         WatchThis (WATCHITM(sesolaptr), WATCH_SESOLA,
                    "READ ALERT !AZ:!AZ",
                    SSL_alert_type_string_long(value),
                    SSL_alert_desc_string_long(value));
         break;
      case SSL_CB_WRITE_ALERT :
         WatchThis (WATCHITM(sesolaptr), WATCH_SESOLA,
                    "WRITE ALERT !AZ:!AZ",
                    SSL_alert_type_string_long(value),
                    SSL_alert_desc_string_long(value));
         break;
      case SSL_CB_ACCEPT_LOOP :
         WatchThis (WATCHITM(sesolaptr), WATCH_SESOLA,
                    "ACCEPT LOOP !AZ", SSL_state_string_long(SslPtr));
         break;
      case SSL_CB_ACCEPT_EXIT :
         WatchThis (WATCHITM(sesolaptr), WATCH_SESOLA,
                    "ACCEPT EXIT !AZ", SSL_state_string_long(SslPtr));
         break;
      case SSL_CB_CONNECT_LOOP :
         WatchThis (WATCHITM(sesolaptr), WATCH_SESOLA,
                    "CONNECT LOOP !AZ", SSL_state_string_long(SslPtr));
         break;
      case SSL_CB_CONNECT_EXIT :
         WatchThis (WATCHITM(sesolaptr), WATCH_SESOLA,
                    "CONNECT EXIT !AZ", SSL_state_string_long(SslPtr));
         break;
      case SSL_CB_HANDSHAKE_START :
         WatchThis (WATCHITM(sesolaptr), WATCH_SESOLA, "START handshake");
         break;
      case SSL_CB_HANDSHAKE_DONE :
         WatchThis (WATCHITM(sesolaptr), WATCH_SESOLA, "DONE handshake");
         break;
      default :
         WatchThis (WATCHITM(sesolaptr), 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

   SESOLA_STRUCT  *sesolaptr;

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

   sesolaptr = (SESOLA_STRUCT*)BIO_get_callback_arg (bioptr);

   if (!sesolaptr->WatchItem) return (retval);

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

      switch (cmd & 0xf)
      {
         case BIO_CB_READ : 
            WatchThis (WATCHITM(sesolaptr), WATCH_MOD_SESOLA,
                       "!&?after\rbefore\r READ !UL !SL",
                       cmd & BIO_CB_RETURN, argi, retval);
            break;
         case BIO_CB_WRITE : 
            WatchThis (WATCHITM(sesolaptr), WATCH_MOD_SESOLA,
                       "!&?after\rbefore\r WRITE !UL !SL",
                       cmd & BIO_CB_RETURN, argi, retval);
            break;
         case BIO_CB_FREE : 
            WatchThis (WATCHITM(sesolaptr), WATCH_MOD_SESOLA,
                       "!&?after\rbefore\r FREE !SL !SL",
                       cmd & BIO_CB_RETURN, argi, retval);
            break;
         case BIO_CB_CTRL : 
            WatchThis (WATCHITM(sesolaptr), WATCH_MOD_SESOLA,
                       "!&?after\rbefore\r CTRL !SL !SL",
                       cmd & BIO_CB_RETURN, argi, retval);
            break;
         default : 
            WatchThis (WATCHITM(sesolaptr), 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 (WATCHITM(sesolaptr), WATCH_SESOLA,
                    "READ !UL/!SL!&? (complete)\r (outstanding)\r",
                    argi, retval, retval > 0);
         break;
      case BIO_CB_WRITE : 
         WatchThis (WATCHITM(sesolaptr), WATCH_SESOLA,
                    "WRITE !UL/!SL!&? (complete)\r (outstanding)\r",
                    argi, retval, retval > 0);
         break;
      case BIO_CB_FREE : 
         WatchThis (WATCHITM(sesolaptr), WATCH_SESOLA,
                    "FREE !SL !SL", argi, retval);
         break;
      case BIO_CB_CTRL : 
         WatchThis (WATCHITM(sesolaptr), WATCH_SESOLA,
                    "CTRL !SL !SL", argi, retval);
         break;
      default : 
         WatchThis (WATCHITM(sesolaptr), 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  *cptr;
   SSL  *SslPtr;
   SSL_CIPHER  *CipherPtr;
   X509  *CertPtr;

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

   if (WATCHMOD (sesolaptr, WATCH_MOD_SESOLA))
      WatchThis (WATCHITM(sesolaptr), WATCH_MOD_SESOLA,
                 "SesolaWatchSession()");

   SslPtr = sesolaptr->SslPtr;

   if (CipherPtr = SSL_get_current_cipher (SslPtr))
   {
      cptr = (char*)SSL_CIPHER_get_name (CipherPtr);
      UseKeySize = SSL_CIPHER_get_bits(CipherPtr, &AlgKeySize);
   }
   else
   {
      AlgKeySize = UseKeySize = 0;
      cptr = "(none)";
   }

   WatchThis (WATCHITM(sesolaptr), WATCH_SESOLA,
"SESSION hit:!AZ protocol:!AZ cipher:!AZ keysize:!UL/!UL X509:!AZ",
              SSL_cache_hit(sesolaptr->SslPtr) ? "yes" : "no",
              (char*)SSL_get_version(SslPtr), cptr, UseKeySize, AlgKeySize,
              (CertPtr = SSL_get_peer_certificate (SslPtr)) ? "yes" : "no");
   if (CertPtr) X509_free (CertPtr);

#endif /* WATCH_CAT */
}

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

SesolaWatchErrors (REQUEST_STRUCT *rqptr)

{
#if WATCH_CAT

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

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

   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 (WATCHITM(rqptr), 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.  If 'rqptr' is NULL then
the information is written to SYS$OUTPUT.
*/

void SesolaWatchPeek
(
REQUEST_STRUCT *rqptr,
void *SeSoLaPtr
)
{
   static char  HexDigits [] = "0123456789abcdef";

   static char  SesolaFao [] = 
"|\n\
!33<SesolaPtr->!> !&X\n\
!33<SslPtr!> protocol:!AZ cipher:!AZ keysize:!UL/!UL\n\
!33<session_id!> !AZ\n\
!33<ConnectCertPtr!> !AZ\n\
!33<ClientCertPtr!> !AZ\n\
!33<CertVerifyCallbackCount!> !UL\n\
!33<CertVerifyCount!> !UL\n\
!33<CertVerifyCode!> !UL !UL !UL !UL ...\n\
!33<ClientAstFunction!> !&A\n\
!33<NetIoPtr->Channel!> !UL (!AZ)\n\
!33<ReadInProgress!> !&B\n\
!33<ReadIsComplete!> !&B\n\
!33<SslStateFunction!> !&A\n\
!33<ReadPtr!> !&X\n\
!33<ReadSize!> !UL\n\
!33<WriteInProgress!> !&B\n\
!33<WriteIsComplete!> !&B\n\
!33<WritePtr!> !&X\n\
!33<WriteLength!> !UL\n\
!33<HTTPduringHandshake!> !&B\n\
!33<SSHduringHandshake!> !&B\n\
!33<SslAcceptCount!> !UL\n\
!33<SslConnectCount!> !UL\n\
!33<SslShutdownCount!> !UL\n";

   int  cnt, status,
        AlgKeySize,
        SessionHint,
        UseKeySize;
   ushort  Length;
   ulong  *vecptr;
   ulong  FaoVector [48];
   char  *cptr, *sptr, *zptr,
         *CipherNamePtr;
   char  Buffer [4096],
         CertFingerprintMD5 [64],
         CertFingerprintSHA1 [64],
         CertFingerprintSHA256 [128],
         ClientCertBuffer [1024],
         ConnectCertBuffer [1024],
         NetDevName [64],
         IssuerString [256],
         NotAfterString [32],
         SessionId [64],
         SubjectString [256];
   SESOLA_STRUCT  *sesolaptr;
   SSL  *SslPtr;
   SSL_CIPHER  *CipherPtr;
   SSL_SESSION  *SessionPtr;

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

   if (WATCH_MODULE(WATCH_MOD_SESOLA))
      WatchThis (WATCHALL, WATCH_MOD_SESOLA, "SesolaWatchPeek()\n");

   if ((sesolaptr = (SESOLA_STRUCT*)SeSoLaPtr) == NULL) return;

   NetGetBgDevice (sesolaptr->NetIoPtr->Channel,
                   NetDevName, sizeof(NetDevName));

   SslPtr = sesolaptr->SslPtr;

   SessionPtr = SSL_get_session (SslPtr);

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

   /* dispose of this as soon as finished with it */
//   SSL_free (SslPtr);  /* hmmm? */

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

      FaoToBuffer (ConnectCertBuffer, sizeof(ConnectCertBuffer), NULL,
"issuer=!AZ\n\
!33<!> subject=!AZ\n\
!33<!> notAfter=!AZ\n\
!33<!> SHA256=!AZ\n\
!33<!> SHA1=!AZ\n\
!33<!> MD5=!AZ",
                   IssuerString, SubjectString, NotAfterString,
                   CertFingerprintSHA256, CertFingerprintSHA1,
                   CertFingerprintMD5);
   }
   else
      strzcpy (ConnectCertBuffer, "0x00000000", sizeof(ConnectCertBuffer));

   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_sha256,
                             CertFingerprintSHA256,
                             sizeof(CertFingerprintSHA256));
      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));

      FaoToBuffer (ClientCertBuffer, sizeof(ClientCertBuffer), NULL,
"issuer=!AZ\n\
!33<!> subject=!AZ\n\
!33<!> notAfter=!AZ\n\
!33<!> SHA256=!AZ\n\
!33<!> SHA1=!AZ\n\
!33<!> MD5=!AZ",
                   IssuerString, SubjectString, NotAfterString,
                   CertFingerprintSHA256, CertFingerprintSHA1,
                   CertFingerprintMD5);
   }
   else
      strzcpy (ClientCertBuffer, "0x00000000", sizeof(ClientCertBuffer));

   cptr = SSL_SESSION_get_id (SessionPtr, &cnt);
   zptr = (sptr = SessionId) + sizeof(SessionId)-1;
   if (cnt)
   {
      while (cnt--)
      {
         if (sptr >= zptr) break;
         *sptr++ = HexDigits[(*cptr >> 4) & 0x0f];
         if (sptr >= zptr) break;
         *sptr++ = HexDigits[*cptr++ & 0x0f];
      }
   }
   else
      for (cptr = "(none)"; *cptr; *sptr++ = *cptr++);
   *sptr = '\0';

   vecptr = FaoVector;
   *vecptr++ = sesolaptr;
   *vecptr++ = SSL_get_version(SslPtr);
   *vecptr++ = CipherNamePtr;
   *vecptr++ = UseKeySize;
   *vecptr++ = AlgKeySize;

   *vecptr++ = SessionId;

   *vecptr++ = ConnectCertBuffer;
   *vecptr++ = ClientCertBuffer;
   *vecptr++ = sesolaptr->CertVerifyCallbackCount;
   *vecptr++ = sesolaptr->CertVerifyCount;
   *vecptr++ = sesolaptr->CertVerifyCode[0];
   *vecptr++ = sesolaptr->CertVerifyCode[1];
   *vecptr++ = sesolaptr->CertVerifyCode[2];
   *vecptr++ = sesolaptr->CertVerifyCode[3];
   *vecptr++ = sesolaptr->ClientCertAstFunction;

   *vecptr++ = sesolaptr->NetIoPtr->Channel;
   *vecptr++ = NetDevName+1;
   *vecptr++ = sesolaptr->ReadInProgress;
   *vecptr++ = sesolaptr->ReadIsComplete;
   *vecptr++ = sesolaptr->SslStateFunction;
   *vecptr++ = sesolaptr->ReadPtr;
   *vecptr++ = sesolaptr->ReadSize;
   *vecptr++ = sesolaptr->WriteInProgress;
   *vecptr++ = sesolaptr->WriteIsComplete;
   *vecptr++ = sesolaptr->WritePtr;
   *vecptr++ = sesolaptr->WriteLength;
   *vecptr++ = sesolaptr->HTTPduringHandshake;
   *vecptr++ = sesolaptr->SSHduringHandshake;
   *vecptr++ = sesolaptr->SslAcceptCount;
   *vecptr++ = sesolaptr->SslConnectCount;
   *vecptr++ = sesolaptr->SslShutdownCount;

   status = FaolToBuffer (Buffer, sizeof(Buffer), &Length,
                          SesolaFao, &FaoVector);
   if (VMSnok (status) || status == SS$_BUFFEROVF)
      ErrorNoticed (rqptr, status, NULL, FI_LI);
   if (rqptr)
      WatchWrite (Buffer, Length);
   else
      fputs (Buffer, stdout);
}

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

SesolaReport
(
REQUEST_STRUCT *rqptr,
char *VirtualHostPort
)
{
   static char  VirtualFao [] =
"<script nonce=\"!AZ\">\n\
function reportVirtual()\n\
{\n\
   var virt = document.getElementById(\'virtual\');\n\
   var value = virt.options[virt.selectedIndex].value;\n\
   var href = window.location.href;\n\
   var rloc = href.indexOf(\'?virtual=\');\n\
   if (rloc > 0) href = href.substr(0,rloc);\n\
   href += \'?virtual=\' + value;\n\
   window.location.replace(href);\n\
   return false;\n\
}\n\
</script>\n\
<form action=\"!AZ\" onsubmit=\"return reportVirtual()\">\n\
<p><table class=\"ctgry\">\n\
<tr><th class=\"ctttl\" >Virtual SSL Server</th></tr>\n\
<tr><td><nobr>\n\
<select name=\"virtual\" id=\"virtual\">\n\
<option!&?\r SELECT\r value=\"\">!&?none configured\rfor this service\r\n";

   static char  ServerInfo1Fao [] =
"</select>\n\
<input type=\"submit\" value=\" select \">\n\
</nobr>\n\
</td></tr>\n\
</table>\n\
</form>\n\
<p><table class=\"ctgry\">\n\
<tr><th class=\"ctttl\">!AZ</th></tr>\n\
<tr><td>\n\
<table class=\"rghtlft\">\n\
<tr><th>Version:</th><td>!AZ</td></tr>\n\
<tr><th>Protocol:</th><td>!AZ</td></tr>\n\
<tr><th>Options:</th><td style=\"white-space:normal;\">\
0x!10@XQ<!!-- 0x!10@XQ -->; !AZ</td></tr>\n\
<tr><th>DH param:</th><td>!AZ</td></tr>\n\
<tr><th>Server Certificate:</th><td>!AZ</td></tr>\n\
<tr><th></th><td>\n\
<table cellpadding=\"0\" cellspacing=\"2\" border=\"0\">\n\
<tr><th>Issuer:</th>\
<td style=\"white-space:pre;\">!AZ</td></tr>\n\
<tr><th>Subject:</th>\
<td style=\"white-space:pre;\">!AZ</td></tr>\n\
<tr><th>Extensions:</th>\
<td><table style=\"display:inline-table;\
line-height:1em;margin-top:0;\">\n";

   static char  ExtensionFao [] =
"<tr><th style=\"align:right;vertical-align:top;\">!UL.</th>\
<td style=\"white-space:pre;\">!AZ</td></tr>\n";

   static char  ExtensionNone [] = "<tr><td><i>(none)</i></td></tr>\n";

   static char  ServerInfo2Fao [] =
"</table>\n\
</td></tr>\n\
<tr><th>Begins:</th><td>!AZ</td></tr>\n\
<tr><th>Expires:</th><td>!AZ</td></tr>\n\
<tr><th>SHA256:</th><td>!AZ</td></tr>\n\
<tr><th>SHA1:</th><td>!AZ</td></tr>\n\
<tr><th>MD5:</th><td>!AZ</td></tr>\n\
</table>\n\
</td></tr>\n\
<tr><th>Private Key:</th><td>!AZ</td></tr>\n\
<tr><th>CA Verify File:</th><td>!&@</td></tr>\n\
<tr><th>CA Verify Depth:</th><td>!&@</td></tr>\n\
<tr><th>Cipher List:</th>\
<td style=\"white-space:normal;\">!AZ</td></tr>\n\
<tr><th>Cipher Suites:</th>\
<td style=\"white-space:normal;\">!AZ</td></tr>\n\
<tr><th>Supported Ciphers:</th>\
<td>\n\
<table class=\"rghtlft\" style=\"margin-top:0;\">\n\
<tr><th></th><th class=\"talft\">Version</th>\
<th class=\"talft\">Name</th></tr>\n";

   static char  CiphersFao [] =
"<tr style=\"font-size:90%;\"><th>!UL.</th>\
<td>!AZ</td>\
<td>!AZ</td>\
</tr>\n";

   static char  CiphersNoneFao [] =
"<tr style=\"font-size:90%;\"><th></th>\
<td colspan=\"2\"><i>(n/a)</i></td></tr>\n";

   static char  SharedCiphersFao [] =
"<tr><th>Shared Ciphers:</th>\
<td style=\"white-space:normal;\">!AZ</td></tr>\n";

   static char  EndCiphersFao [] =
"</table>\n\
</td></tr>\n\
<tr><th>TLS/SSL:</th><td>\
<table class=\"rghtlft\" style=\"margin-top:0;\">\n\
<tr><th>Connect:</th><td>!UL</td><td>(!UL%)</td></tr>\n\
<tr><th>Request:</th><td>!UL</td><td>(!UL%)</td></tr>\n\
<tr><th>TLSv1.3:</th><td>!UL</td><td>(!UL%)</td></tr>\n"
"<tr><th>TLSv1.2:</th><td>!UL</td><td>(!UL%)</td></tr>\n\
<tr><th>TLSv1.1:</th><td>!UL</td><td>(!UL%)</td></tr>\n"
"<tr><th>TLSv1:</th><td>!UL</td><td>(!UL%)</td></tr>\n"
"</table>\n\
<tr><th>Session Ticket:</th><td>\n\
<table class=\"rghtlft\" style=\"margin-top:0;\">\n\
!&@\
</table>\n\
</td></tr>\n\
<tr><tr><th>Session ID Cache:</th><td>\n\
<table class=\"rghtlft\" style=\"margin-top:0;\">\n\
!&@\
</table>\n\
</td></tr>\n";

   static char  EndServerInfoFao [] =
"</table>\n\
</td></tr>\n\
</table>\n";

   static char  CurrentSessionFao [] =
"<p><table class=\"ctgry\">\n\
<tr><th class=\"ctttl\">Current Session</th></tr>\n\
<tr><td>\n\
<table class=\"rghtlft\">\n\
<tr><th>Session:</th>\
<td>!AZ (!2ZL:!2ZL:!2ZL ago), timeout (!2ZL:!2ZL:!2ZL) at !AZ</td></tr>\n";

   static char  ClientCert1Fao [] =
"<tr><th>Client&nbsp;Certificate:</th><td>\n\
<table cellpadding=\"0\" cellspacing=\"2\" border=\"0\">\n\
<tr><th>Issuer:</th>\
<td style=\"white-space:pre;\">!AZ</td></tr>\n\
<tr><th>Subject:</th>\
<td style=\"white-space:pre;\">!AZ</td></tr>\n\
<tr><th>Extensions:</th>\
<td><table style=\"display:inline-table;\
line-height:1em;margin-top:0;\">\n";

   static char  ClientCert2Fao [] =
"</table>\n\
<tr><th>Begins:</th><td>!AZ</td></tr>\n\
<tr><th>Expires:</th><td>!AZ</td></tr>\n\
<tr><th>SHA256:</th><td>!AZ</td></tr>\n\
<tr><th>SHA1:</th><td>!AZ</td></tr>\n\
<tr><th>MD5:</th><td>!AZ</td></tr>\n\
<tr><th>Fingerprint:</th><td>!AZ</td></tr>\n\
</table>\n\
</td></tr>\n";

   static char  ClientCertNoneFao [] =
"<tr><th>Client Certificate:</th>\
<td>!AZ \"!AZ\">Get Client Certificate!AZ</td></tr>\n";

   static char  CurrentSessionEndFao [] =
"</table>\n\
</td></tr>\n";

   static char  EndPageFao [] =
"</table>\n\
</div>\n\
</body>\n\
</html>\n";

   int  status,
        AlgKeySize,
        Count,
        SessionHits,
        SessionTimeout,
        SessionTimeCSec,
        SessionTimeoutCSec,
        ServiceCount,
        VersionTotal,
        Total,
        UseKeySize,
        UtcOffset;
   uint64  options;
   char  *cptr, *sptr, *zptr;
   ulong  /* WARNING: really does need to be fairly large!! */
          FaoVector [256];
   ulong  *vecptr;
   char  AuthFingerprint [64],
         CertString [256],
         CertCaString [256],
         CertDnString [256],
         CertFingerprintMD5 [64],
         CertFingerprintSHA1 [64],
         CertFingerprintSHA256 [128],
         CertNotAfterString [32],
         CertNotBeforeString [32],
         ScratchString [256],
         SharedCiphers [1024],
         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 (WATCHMOD (rqptr, WATCH_MOD_SESOLA))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_SESOLA, "SesolaReport()");

   sesolaptr = NULL;
   if (HTTP2_REQUEST(rqptr))
      sesolaptr = (SESOLA_STRUCT*)rqptr->Http2Ptr->NetIoPtr->SesolaPtr; 
   if (!sesolaptr) sesolaptr = (SESOLA_STRUCT*)rqptr->NetIoPtr->SesolaPtr;

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

   /* now (!!) we can report "parameters" */
   if (WATCHMOD (rqptr, WATCH_MOD_SESOLA))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_SESOLA, "!&Z", VirtualHostPort);

   if (VirtualHostPort[0])
   {
      LIST_ITERATE (svptr, &ServiceList)
      {
         if (svptr->SchemeType != SCHEME_HTTPS) continue;
         if (strsame (svptr->ServerHostPort, VirtualHostPort, -1)) break;
      }
      if (!svptr)
      {
         rqptr->rqResponse.HttpStatus = 500;
         ErrorGeneral (rqptr, "No such virtual service.", FI_LI);
         AdminEnd (rqptr);
         return;
      }
   }
   else
      svptr = rqptr->ServicePtr;

   /* if request's service not SSL then choose the first in the list */
   if (svptr->SchemeType != SCHEME_HTTPS)
   {
      LIST_ITERATE (svptr, &ServiceList)
         if (svptr->SchemeType != SCHEME_HTTPS) continue;
      if (svptr) VirtualHostPort = svptr->ServerHostPort;
   }

   if (!svptr || svptr->SchemeType != SCHEME_HTTPS)
   {
      rqptr->rqResponse.HttpStatus = 500;
      ErrorGeneral (rqptr, "No SSL service found!", FI_LI);
      AdminEnd (rqptr);
      return;
   }

   ServiceCount = SesolaServiceCount();

   if (sesolaptr)
   {
      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 ;^)
            VirtualHostPort is buffered and unbuffered around such a call here.
            (I could have made it a variable argument function, but this
            should work just as well!)
         */
         if (!sesolaptr->ReportClientCertState)
         {
            sesolaptr->ReportClientCertState = 1;
            zptr = (sptr = sesolaptr->ReportVirtualHostPort) +
                   sizeof(sesolaptr->ReportVirtualHostPort)-1;
            for (cptr = VirtualHostPort;
                 *cptr && sptr < zptr;
                 *sptr++ = *cptr++);
            *sptr = '\0';
            status = SesolaClientCert (rqptr, SESOLA_VERIFY_PEER_OPTIONAL,
                                       &SesolaReport);
            /* this faux status indicates renegotiation is underway */
            if (status == SS$_WAITUSRLBL) return;
            sesolaptr->ReportClientCertState = 0;
         }
      }
   }

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

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

   /* this is the service's context - not the current session's! */
   if (!(SslCtx = ((SESOLA_CONTEXT*)svptr->SSLserverPtr)->SslCtx))
   {
      rqptr->rqResponse.HttpStatus = 500;
      ErrorGeneral (rqptr, "No SSL context found!", FI_LI);
      AdminEnd (rqptr);
      return;
   }

   if (!(ServerCertPtr = SSL_CTX_get0_certificate (SslCtx)))
   {
      rqptr->rqResponse.HttpStatus = 500;
      ErrorGeneral (rqptr, "No SSL certificate found!", FI_LI);
      AdminEnd (rqptr);
      return;
   }

   options = SSL_CTX_get_options (SslCtx);

#ifndef SESOLA_REPORT_DN
   SesolaCertReportName (ServerCertPtr, "ISSUER",
                         CertCaString, sizeof(CertCaString));
   SesolaCertReportName (ServerCertPtr, "SUBJECT",
                         CertDnString, sizeof(CertDnString));
#else
   X509_NAME_oneline (X509_get_issuer_name(ServerCertPtr),
                      CertString, sizeof(CertString));
   SesolaCertReportDn (CertString, CertCaString, sizeof(CertCaString));

   X509_NAME_oneline (X509_get_subject_name(ServerCertPtr),
                      CertString, sizeof(CertString));
   SesolaCertReportDn (CertString, CertDnString, sizeof(CertDnString));
#endif

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

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

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

   FaoToBuffer (ScratchString, sizeof(ScratchString), NULL,
                "SSL Report &nbsp;&ndash;&nbsp; !AZ", svptr->ServerHostPort);
   AdminPageTitle (rqptr, ScratchString);

   vecptr = FaoVector;
   *vecptr++ = GenerateCspNonce (rqptr);
   *vecptr++ = ADMIN_REPORT_SSL;
   *vecptr++ = VirtualHostPort[0];
   *vecptr++ = (ServiceCount == 1);
   status = FaolToNet (rqptr, VirtualFao, &FaoVector);
   if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI);

   if (ServiceCount > 1)
   {
      LIST_ITERATE (tsvptr, &ServiceList)
      {
         if (tsvptr->SchemeType != SCHEME_HTTPS) continue;
         vecptr = FaoVector;
         *vecptr++ = tsvptr->ServerHostPort;
         *vecptr++ = strsame (tsvptr->ServerHostPort, VirtualHostPort, -1);
         *vecptr++ = tsvptr->ServerHostPort;

         status = FaolToNet (rqptr,
                     "<option value=\"!AZ\"!&? selected\r\r>!AZ\n",
                             &FaoVector);
         if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI);
      }
   }

   vecptr = FaoVector;

   *vecptr++ = svptr->ServerHostPort;
   *vecptr++ = SesolaVersion ();
   *vecptr++ = SesolaProtocolVersion (SslCtx);
   *vecptr++ = &options;
   *vecptr++ = &SesolaVersionOptions;
   *vecptr++ = SesolaOptionsAsString(&options);
   if (SesolaTmpDHbitsPtr)
      *vecptr++ = SesolaTmpDHbitsPtr;
   else
      *vecptr++ = "<i>(none)</i>";
   if (scptr->CertFilePtr[0])
      *vecptr++ = scptr->CertFilePtr;
   else
      *vecptr++ = "<i>(internal)</i>";
   *vecptr++ = CertCaString;
   *vecptr++ = CertDnString;

   status = FaolToNet (rqptr, ServerInfo1Fao, &FaoVector);
   if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI);

   Count = 0;
   SesolaCertExtension (ServerCertPtr, (char*)-1);
   while (cptr = SesolaCertExtension (NULL, NULL))
   {
      status = FaoToNet (rqptr, ExtensionFao, ++Count, cptr);
      if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI);
   }
   if (!Count) FaoToNet (rqptr, ExtensionNone);

   vecptr = FaoVector;

   *vecptr++ = CertNotBeforeString;
   *vecptr++ = CertNotAfterString;
   *vecptr++ = CertFingerprintSHA256;
   *vecptr++ = CertFingerprintSHA1;
   *vecptr++ = CertFingerprintMD5;
   if (scptr->KeyFilePtr[0])
      *vecptr++ = scptr->KeyFilePtr;
   else
      *vecptr++ = "<i>(internal)</i>";
   if (scptr->CaFilePtr[0])
   {
      *vecptr++ =
"<span style=\"padding-right:1em;\">!AZ</span>\
<a class=\"abttn bttn400\" href=\"!AZ?do=report&virtual=!AZ\">report</a>\
<a class=\"abttn bttn400\" href=\"!AZ?virtual=!AZ\">list</a>\
<a class=\"abttn bttn400\" 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>(default)</i><br>";

   if (scptr->CipherSuitesPtr[0])
      *vecptr++ = scptr->CipherSuitesPtr;
   else
      *vecptr++ = "<i>(default)</i><br>";

   status = FaolToNet (rqptr, ServerInfo2Fao, &FaoVector);
   if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI);

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

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

   if (CipherStackPtr = SSL_get1_supported_ciphers (SslPtr))
      Total = sk_SSL_CIPHER_num (CipherStackPtr);
   else
      Total = -1;
   if (Total == -1)
      status = FaoToNet (rqptr, CiphersNoneFao);
   else
   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 = FaolToNet (rqptr, CiphersFao, &FaoVector);
   }
   if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI);

   sk_SSL_CIPHER_free (CipherStackPtr);

   /* dispose of this as soon as finished with it */
   SSL_free (SslPtr);

   /***************/
   /* other stats */
   /***************/

   vecptr = FaoVector;

   *vecptr++ = AccountingPtr->ConnectSSLCount;
   *vecptr++ = PercentOf32(AccountingPtr->ConnectSSLCount,
                         AccountingPtr->ConnectCount[HTTP12]);
   *vecptr++ = AccountingPtr->RequestSSLCount;
   *vecptr++ = PercentOf32(AccountingPtr->RequestSSLCount,
                         AccountingPtr->ProcessingTotalCount[HTTP12]);

   VersionTotal = AccountingPtr->ConnectTLS13Count +
                  AccountingPtr->ConnectTLS12Count +
                  AccountingPtr->ConnectTLS11Count +
                  AccountingPtr->ConnectTLS1Count +
                  AccountingPtr->ConnectSSL3Count;

   *vecptr++ = AccountingPtr->ConnectTLS13Count;
   *vecptr++ = PercentOf32(AccountingPtr->ConnectTLS13Count, VersionTotal);
   *vecptr++ = AccountingPtr->ConnectTLS12Count;
   *vecptr++ = PercentOf32(AccountingPtr->ConnectTLS12Count, VersionTotal);
   *vecptr++ = AccountingPtr->ConnectTLS11Count;
   *vecptr++ = PercentOf32(AccountingPtr->ConnectTLS11Count, VersionTotal);
   *vecptr++ = AccountingPtr->ConnectTLS1Count;
   *vecptr++ = PercentOf32(AccountingPtr->ConnectTLS1Count, VersionTotal);

   if (!(options & SSL_OP_NO_TICKET))
   {
      *vecptr++ =
"<tr><th>Refresh:</th><td>!20&W</td></tr>\n\
<tr><th>Name:</th><td>!16&H</td></tr>\n\
<tr><th>New:</th><td>!UL</td></tr>\n\
<tr><th>Hit:</th><td>!UL</td></tr>\n\
<tr><th>Miss:</th><td>!UL</td></tr>\n";
      *vecptr++ = &SesolaTicketKeyTime64;
      *vecptr++ = SesolaTicketKey;
      *vecptr++ = SesolaSessionTicketNew;
      *vecptr++ = SesolaSessionTicketHit;
      *vecptr++ = SesolaSessionTicketMiss;
   }
   else
      *vecptr++ = "<tr><td><i>(disabled)</i></td></tr>\n";

   if (SesolaSessionCacheSize)
   {
      *vecptr++ =
"<tr><th>Size:</th><td>!UL</td></tr>\n\
<tr><th>Current:</th><td>!UL</td></tr>\n\
<tr><th>Full:</th><td>!UL</td></tr>\n\
<tr><th>Hit:</th><td>!UL</td></tr>\n\
<tr><th>Miss:</th><td>!UL</td></tr>\n\
<tr><th>Timeout:</th><td>!UL</td></tr>\n";
      *vecptr++ = SSL_CTX_sess_get_cache_size (SslCtx);
      *vecptr++ = SSL_CTX_sess_number (SslCtx);
      *vecptr++ = SSL_CTX_sess_cache_full (SslCtx);
      *vecptr++ = SesolaSessionCacheSize ? SSL_CTX_sess_hits (SslCtx) : 0;
      *vecptr++ = SesolaSessionCacheSize ? SSL_CTX_sess_misses (SslCtx) : 0;
      *vecptr++ = SSL_CTX_sess_timeouts (SslCtx);
   }
   else
      *vecptr++ = "<tr><td><i>(disabled)</i></td></tr>\n";

   SesolaCacheStats (rqptr);

   status = FaolToNet (rqptr, EndCiphersFao, &FaoVector);
   if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI);

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

   if (sesolaptr)
   {
      /*******************/
      /* current session */
      /*******************/

      SessionPtr = SSL_get_session (sesolaptr->SslPtr);
      SessionTimeCSec = SSL_get_time (SessionPtr);
      tmptr = localtime (&SessionTimeCSec);

      if (!strftime (TimeString, sizeof(TimeString), "%b %d %T %Y", tmptr))
         strzcpy (TimeString, "strftime() error", sizeof(TimeString));

      SessionTimeout = SSL_get_timeout (SessionPtr);
      SessionTimeoutCSec = SessionTimeCSec + SessionTimeout;
      tmptr = localtime (&SessionTimeoutCSec);

      if (!strftime (TimeoutString, sizeof(TimeoutString),
                     "%b %d %T %Y", tmptr))
         strzcpy (TimeoutString, "strftime() error", sizeof(TimeoutString));

      /* for some reason (no locale?) these are sometimes UTC - adjust back */
      time (&UtcOffset);
      (int)UtcOffset = (int)UtcOffset - (int)HttpdTickSecond;
      SessionTimeCSec -= UtcOffset;

      vecptr = FaoVector;

      *vecptr++ = TimeString;
      *vecptr++ = (HttpdTickSecond - SessionTimeCSec) / 3600;
      *vecptr++ = ((HttpdTickSecond - SessionTimeCSec) % 3600) / 60;
      *vecptr++ = (HttpdTickSecond - SessionTimeCSec) % 60;
      *vecptr++ = SessionTimeout / 3600;
      *vecptr++ = (SessionTimeout % 3600) / 60;
      *vecptr++ = SessionTimeout % 60;
      *vecptr++ = TimeoutString;

      status = FaolToNet (rqptr, CurrentSessionFao, &FaoVector);
      if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI);

      if (sesolaptr->ClientCertPtr == NULL)
      {
         if (sesolaptr->ReportVirtualHostPort[0])
            status = FaoToNet (rqptr, ClientCertNoneFao,
                        "<!--", "", "--><i>(none)</i>");
         else
         {
            if (HTTP2_REQUEST(rqptr))
               status = FaoToNet (rqptr, ClientCertNoneFao,
                                  "<a class=\"abttn dbttn\"",
                                  "",
                                  "</a> (due to HTTP/2)");
            else
               status = FaoToNet (rqptr, ClientCertNoneFao,
                                  "<a class=\"abttn\" href=",
                                  ADMIN_REPORT_SSL_CLIENT,
                                  "</a>");
         }
         if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI);
      }
      else
      {
#ifndef SESOLA_REPORT_DN
         SesolaCertReportName (sesolaptr->ClientCertPtr, "ISSUER",
                               CertCaString, sizeof(CertCaString));
         SesolaCertReportName (sesolaptr->ClientCertPtr, "SUBJECT",
                               CertDnString, sizeof(CertDnString));
#else
         X509_NAME_oneline (X509_get_issuer_name(sesolaptr->ClientCertPtr),
                            CertString, sizeof(CertString));
         SesolaCertReportDn (CertString, CertCaString, sizeof(CertCaString));

         X509_NAME_oneline (X509_get_subject_name(sesolaptr->ClientCertPtr),
                            CertString, sizeof(CertString));
         SesolaCertReportDn (CertString, CertDnString, sizeof(CertDnString));
#endif

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

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

         SesolaCertFingerprint (sesolaptr->ClientCertPtr, &EVP_sha256,
                                CertFingerprintSHA256,
                                sizeof(CertFingerprintSHA256));
         SesolaCertFingerprint (sesolaptr->ClientCertPtr, &EVP_sha1,
                                CertFingerprintSHA1,
                                sizeof(CertFingerprintSHA1));
         SesolaCertFingerprint (sesolaptr->ClientCertPtr, &EVP_md5,
                                CertFingerprintMD5, sizeof(CertFingerprintMD5));
         /* fingerprint for authentication purposes is sans colons */
         zptr = (sptr = AuthFingerprint) + sizeof(AuthFingerprint)-1;
         for (cptr = CertFingerprintMD5; *cptr; cptr++)
            if (*cptr != ':') *sptr++ = *cptr;
         *sptr = '\0';

         vecptr = FaoVector;
         *vecptr++ = CertCaString;
         *vecptr++ = CertDnString;

         status = FaolToNet (rqptr, ClientCert1Fao, &FaoVector);
         if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI);

         Count = 0;
         SesolaCertExtension (sesolaptr->ClientCertPtr, (char*)-1);
         while (cptr = SesolaCertExtension (NULL, NULL))
         {
            status = FaoToNet (rqptr, ExtensionFao, ++Count, cptr);
            if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI);
         }
         if (!Count) FaoToNet (rqptr, ExtensionNone);

         vecptr = FaoVector;
         *vecptr++ = CertNotBeforeString;
         *vecptr++ = CertNotAfterString;
         *vecptr++ = CertFingerprintSHA256;
         *vecptr++ = CertFingerprintSHA1;
         *vecptr++ = CertFingerprintMD5;
         *vecptr++ = AuthFingerprint;

         status = FaolToNet (rqptr, ClientCert2Fao, &FaoVector);
         if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, 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))
      {
         rqptr->rqResponse.HttpStatus = 500;
         ErrorGeneral (rqptr, "Woops.", FI_LI);
         AdminEnd (rqptr);
         return;
      }
      cptr = rqptr->rqCgi.BufferPtr;
      for (;;)
      {
         if (!*(USHORTPTR)cptr) break;        
         cptr += sizeof(short);
         if (!MATCH4 (cptr, "SSL_"))
         {
            while (*cptr) cptr++;
            cptr++;
            continue;
         }

         if (MATCH7 (cptr+4, "CIPHER="))
         {
            /* ciphers shared between server and client */
            sptr = SSL_get_shared_ciphers (sesolaptr->SslPtr,
                                           SharedCiphers,
                                           sizeof(SharedCiphers));
            vecptr = FaoVector;
            if (sptr)
               *vecptr++ = sptr;
            else
               *vecptr++ = "(BUGCHECK)";
            status = FaolToNet (rqptr, SharedCiphersFao, &FaoVector);
            if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI);
         }

         vecptr = FaoVector;
         zptr = sptr = (cptr += 4);
         if (*sptr != '=') *zptr++ = *sptr++;
         while (*sptr && *sptr != '=') *zptr++ = to_lower(*sptr++);
         *vecptr++ = sptr - cptr;
         *vecptr++ = cptr;
         if (*sptr) sptr++;
         cptr = sptr;
         while (*sptr) sptr++;
         *vecptr++ = sptr - cptr;
         *vecptr++ = cptr;
         FaolToNet (rqptr,
"<tr><th>!#AZ:</th><td>!#&;AZ</td></tr>\n",
                    &FaoVector);
         cptr = ++sptr;
      }

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

   if (cptr = SesolaMemTrackReport())
   {
      status = FaoToNet (rqptr, "<!!-- !AZ -->\n",  cptr);
      if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI);
   }

   status = FaoToNet (rqptr, "<!!-- !AZ -->\n", SesolaNetStats());
   if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI);

   /**************/
   /* end report */
   /**************/

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

   rqptr->rqResponse.PreExpired = PRE_EXPIRE_ADMIN;
   ResponseHeader200 (rqptr, "text/html", &rqptr->NetWriteBufferDsc);

   AdminEnd (rqptr);
}

/*****************************************************************************/
/*
Return a pointer to a strimng containg the TLS versions available to a context.
*/

char* SesolaProtocolVersion (SSL_CTX *SslCtx)

{
   static char  buf [48];
   static *version [] = { "", "  TLSv1", ", TLSv1.1",
                            ", TLSv1.2", ", TLSv1.3" };

   int  max, min, idx;
   char  *cptr, *sptr, *zptr;

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

   if (WATCH_MODULE(WATCH_MOD_SESOLA))
      WatchThis (WATCHALL, WATCH_MOD_SESOLA, "SesolaProtocolVersion()");

   min = SSL_CTX_get_min_proto_version (SslCtx) & 0xf;
   max = SSL_CTX_get_max_proto_version (SslCtx) & 0xf;
   if (min < 1 || max > 4) return ("(BUGCHECK)");
   zptr = (sptr = buf) + sizeof(buf)-1;
   *sptr = '\0';
   for (idx = min; idx <= max; idx++)
   {
      cptr = version[idx];
      if (!buf[0]) cptr += 2;
      while (*cptr && sptr < zptr) *sptr++ = *cptr++;
   }
   *sptr = '\0';
   return (buf);
}

/*****************************************************************************/
/*
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,
char *VirtualHostPort
)
{
   static char  BeginPageFao [] =
"<p><table class=\"ctgry\">\n\
<tr><th class=\"ctttl\">!AZ</th></tr>\n\
<tr><td>\n\
<table class=\"rghtlft\">\n\
<tr><th>CA Verify File:</th><td>!AZ</td></tr>\n\
<tr><th>CA Verify Depth:</th><td>!UL</td></tr>\n";

   static char  CertStoreNull [] =
"<tr><td><i>(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 class=\"rghtlft\">\n\
<tr><th>!UL.</th>\
<th>Issuer:</th>\
<td style=\"white-space:pre;\">!AZ</td></tr>\n\
<tr><th></th><th>Subject:</th>\
<td style=\"white-space:pre;\">!AZ</td></tr>\n\
<tr><th></th><th>Begins:</th><td>!AZ</td></tr>\n\
<tr><th></th><th>Expires:</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\">\n\
<a class=\"abttn bttn300\" href=\"!AZ?do=edit&virtual=!AZ\">Edit</a>\n\
<a class=\"abttn bttn300\" href=\"!AZ?virtual=!AZ\">List</a>\n\
<a class=\"abttn bttn300\" href=\"!AZ\">Reload</a>\n\
</td></tr>\n\
</table>\n\
</td></tr>\n\
</table>\n\
</div>\n\
</body>\n\
</html>\n";

   int  idx,
        status,
        Count,
        ObjectCount;
   ulong  FaoVector [16];
   ulong  *vecptr;
   char  *cptr, *sptr, *zptr;
   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;
   STACK_OF(X509_OBJECT) *ObjectsPtr;

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

   if (WATCHMOD (rqptr, WATCH_MOD_SESOLA))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_SESOLA, "SesolaReportCA() !AZ",
                 VirtualHostPort);

   if (VirtualHostPort[0])
   {
      LIST_ITERATE (svptr, &ServiceList)
         if (strsame (svptr->ServerHostPort, VirtualHostPort, -1)) break;
      if (!svptr)
      {
         rqptr->rqResponse.HttpStatus = 500;
         ErrorGeneral (rqptr, "No such virtual service.", FI_LI);
         AdminEnd (rqptr);
         return;
      }
   }
   else
      svptr = rqptr->ServicePtr;

   /* if the service is not SSL then choose the first in the list */
   if (svptr->SchemeType != SCHEME_HTTPS)
   {
      LIST_ITERATE (svptr, &ServiceList)
         if (svptr->SchemeType != SCHEME_HTTPS) continue;
      if (svptr) VirtualHostPort = svptr->ServerHostPort;
   }

   if (!svptr || svptr->SchemeType != SCHEME_HTTPS)
   {
      rqptr->rqResponse.HttpStatus = 500;
      ErrorGeneral (rqptr, "No SSL service found!", FI_LI);
      AdminEnd (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;

   AdminPageTitle (rqptr, "SSL Report, CA Verification");

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

   if (!(CertStorePtr = SSL_CTX_get_cert_store (SslCtx)))
   {
      status = FaolToNet (rqptr, CertStoreNull, NULL);
      if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI);
   }
   else
   {
      Count = 0;
      ObjectsPtr = X509_STORE_get0_objects(CertStorePtr);
      ObjectCount = sk_X509_OBJECT_num(ObjectsPtr);
      for (idx = 0; idx < ObjectCount; idx++)
      {
         CertObjectPtr = sk_X509_OBJECT_value (ObjectsPtr, idx);
         if (X509_OBJECT_get_type(CertObjectPtr) != X509_LU_X509) continue;
         CertPtr = X509_OBJECT_get0_X509 (CertObjectPtr);
         Count++;

#ifndef SESOLA_REPORT_DN
         SesolaCertReportName (CertPtr, "ISSUER",
                               CertCaString, sizeof(CertCaString));
         SesolaCertReportName (CertPtr, "SUBJECT",
                               CertDnString, sizeof(CertDnString));
#else
         X509_NAME_oneline (X509_get_issuer_name(CertPtr),
                            CertString, sizeof(CertString));
         SesolaCertReportDn (CertString, CertCaString, sizeof(CertCaString));

         X509_NAME_oneline (X509_get_subject_name(CertPtr),
                            CertString, sizeof(CertString));
         SesolaCertReportDn (CertString, CertDnString, sizeof(CertDnString));
#endif

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

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

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

         status = FaolToNet (rqptr, CertFao, &FaoVector);
         if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, 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 = FaolToNet (rqptr, EndPageFao, &FaoVector);
   if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI);

   rqptr->rqResponse.PreExpired = PRE_EXPIRE_ADMIN;
   ResponseHeader200 (rqptr, "text/html", &rqptr->NetWriteBufferDsc);

   AdminEnd (rqptr);
}

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

void 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 (WATCHALL, WATCH_MOD_SESOLA, "SesolaControlReloadCA()");

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

   ErrorCount = SSLcount = ReloadCount = 0;
   LIST_ITERATE (svptr, &ServiceList)
   {
      if (svptr->SchemeType != SCHEME_HTTPS) continue;
      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;
         SSL_CTX_set_cert_store (SslCtx, X509_STORE_new());

         /* load the file in afresh */
         if (SSLcount > 1)
            CertStorePtr = SesolaServiceSameCA (scptr->CaFilePtr);
         else
            CertStorePtr = NULL;

         if (CertStorePtr)
         {
            if (WATCH_MODULE(WATCH_MOD_SESOLA))
               WatchThis (WATCHALL, WATCH_MOD_SESOLA,
                          "SSL_CTX_set1_cert_store() !AZ", scptr->CaFilePtr);
            SSL_CTX_set1_cert_store (SslCtx, CertStorePtr);
            ReloadCount++;
         }
         else
         {
            if (WATCH_MODULE(WATCH_MOD_SESOLA))
               WatchThis (WATCHALL, WATCH_MOD_SESOLA,
                          "SSL_CTX_load_verify_locations() !AZ",
                          scptr->CaFilePtr);
            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++;
               FaoToStdout ("%HTTPD-W-SSL, !AZ\n \\!AZ\\\n",
                            svptr->ServerHostPort, scptr->CaFilePtr);
               SesolaPrintOpenSslErrorList ();
               FaoToStdout ("%HTTPD-W-SSL, client verification not enabled\n");
            }
         }
      }
   }

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

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

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

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

char* SesolaRequestCipher (SESOLA_STRUCT *sesolaptr)

{
   char  *cptr;
   SSL_CIPHER  *CipherPtr;

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

   if (WATCH_MODULE(WATCH_MOD_SESOLA))
      WatchThis (WATCHALL, WATCH_MOD_SESOLA, "SesolaRequestCipher()");

   if (CipherPtr = SSL_get_current_cipher (sesolaptr->SslPtr))
      cptr = (char*)SSL_CIPHER_get_name (CipherPtr);
   else
      cptr = "*BUGCHECK*";

   return (cptr);
}

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

BOOL SesolaRequestSessionReused (SESOLA_STRUCT *sesolaptr)

{
   /*********/
   /* begin */
   /*********/

   if (WATCH_MODULE(WATCH_MOD_SESOLA))
      WatchThis (WATCHALL, WATCH_MOD_SESOLA, "SesolaRequestSessionReused()");

   return ((BOOL)SSL_cache_hit(sesolaptr->SslPtr));
}

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

char* SesolaRequestVersion (SESOLA_STRUCT *sesolaptr)

{
   /*********/
   /* begin */
   /*********/

   if (WATCH_MODULE(WATCH_MOD_SESOLA))
      WatchThis (WATCHALL, WATCH_MOD_SESOLA, "SesolaRequestVersion()");

   return ((char*)SSL_get_version(sesolaptr->SslPtr));
}

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

REQUEST_STRUCT* SesolaGetRequestPtr (SESOLA_STRUCT *sesolaptr)

{
   /*********/
   /* begin */
   /*********/

   if (WATCH_MODULE(WATCH_MOD_SESOLA))
      WatchThis (WATCHALL, WATCH_MOD_SESOLA, "SesolaGetRequestPtr()");

   return (sesolaptr->RequestPtr);
}

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

SESOLA_STRUCT* SesolaRequestPtr (REQUEST_STRUCT *rqptr)

{
   /*********/
   /* begin */
   /*********/

   if (WATCH_MODULE(WATCH_MOD_SESOLA))
      WatchThis (WATCHALL, WATCH_MOD_SESOLA, "SesolaRequestPtr()");

   if (rqptr->Http2Ptr)
      return (rqptr->Http2Ptr->NetIoPtr->SesolaPtr);
   else
      return (rqptr->NetIoPtr->SesolaPtr);
}

/*****************************************************************************/
/*
Return a pointer to a static string containing the string equivalent of the bit
vector that is OpenSSL options.  Parameter is by reference.
*/

char* SesolaOptionsAsString (uint64 *optptr)

{
   static char  buf [512];

   uint64  bit, options;
   char  *cptr, *sptr, *zptr;

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

   if (WATCH_MODULE(WATCH_MOD_SESOLA))
      WatchThis (WATCHALL, WATCH_MOD_SESOLA, "SesolaOptionsAsString()");

   options = *optptr;
   zptr = (sptr = buf) + sizeof(buf)-4;
   for (bit = 0x0080000000; bit; bit = bit >> 1)
   {
       switch (bit & options)
       {
          case 0 : cptr = NULL; break;
          case 0x00000001 : cptr = "no_extended_master_secret"; break;
          case 0x00000002 : cptr = "cleanse_plaintext"; break;
          case 0x00000004 : cptr = "legacy_server_connect"; break;
          case 0x00000008 : cptr = "enable_ktls"; break;
          case 0x00000010 : cptr = "tlsext_padding"; break;
          case 0x00000020 : cptr = "0x00000020"; break;
          case 0x00000040 : cptr = "safari_ecdhe_ecdsa_bug"; break;
          case 0x00000080 : cptr = "ignore_unexpected_eof"; break;
          case 0x00000100 : cptr = "allow_client_renegotiation"; break;
          case 0x00000200 : cptr = "disable_tls_ca_names"; break;
          case 0x00000400 : cptr = "allow_no_dhe_ktx"; break;
          case 0x00000800 : cptr = "insert_empty_fragments"; break;
          case 0x00001000 : cptr = "no_query_mtu"; break;
          case 0x00002000 : cptr = "cookie_exchange"; break;
          case 0x00004000 : cptr = "no_ticket"; break;
          case 0x00008000 : cptr = "cisco_anyconnect"; break;
          case 0x00010000 : cptr = "no_session_resumption_on_renegotiation"; break;
          case 0x00020000 : cptr = "no_compression"; break;
          case 0x00040000 : cptr = "allow_unsafe_legacy_renegotiation"; break;
          case 0x00080000 : cptr = "no_encrypt_then_mac"; break;
          case 0x00100000 : cptr = "enable_middlebox_compat"; break;
          case 0x00200000 : cptr = "prioritize_chacha"; break;
          case 0x00400000 : cptr = "cipher_server_preference"; break;
          case 0x00800000 : cptr = "tls_rollback_bug"; break;
          case 0x01000000 : cptr = "no_anti_replay"; break;
          case 0x02000000 : cptr = "no_sslv3"; break;
          case 0x04000000 : cptr = "no_tlsv1"; break;
          case 0x08000000 : cptr = "no_tlsv1_2"; break;
          case 0x10000000 : cptr = "no_tlsv1_1"; break;
          case 0x20000000 : cptr = "no_tlsv1_3"; break;
          case 0x40000000 : cptr = "no_renegotiation"; break;
          case 0x80000000 : cptr = "cryptopro_tlsext_bug"; break;
          default : cptr = "BUGCHECK";
       }
       if (cptr)
       {
          if (sptr > buf)
          {
             if (sptr < zptr) *sptr++ = ',';
             if (sptr < zptr) *sptr++ = ' ';
          }
          while (*cptr && sptr < zptr) *sptr++ = *cptr++;
       }
   }
   if (sptr >= zptr) for (cptr = "..."; *cptr; *sptr++ = *cptr++);
   *sptr = '\0';
   return (buf);
}

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

char* SesolaVersion ()

{
   static char  String [128];

   char  *cptr, *sptr, *zptr;

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

   if (WATCH_MODULE(WATCH_MOD_SESOLA))
      WatchThis (WATCHALL, WATCH_MOD_SESOLA, "SesolaVersion()");

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

   SesolaOpenSslVersionNumber = OpenSSL_version_num();

   for (cptr = OpenSSL_version (OPENSSL_PLATFORM);
        *cptr && *cptr != ':';
        cptr++);
   for (cptr++; *cptr && *cptr == ' '; cptr++);

   for (sptr = OpenSSL_version (OPENSSL_DIR);
        *sptr && *sptr != ':';
        sptr++);
   for (sptr++; *sptr && *sptr == ' '; sptr++);

   /* transmogrify "SSL3$ROOT:[000000]" into SSL3$ROOT */
   if (*sptr == '\"') sptr++;
   for (zptr = sptr; *zptr && *zptr != ':'; zptr++);

   FaoToBuffer (String, sizeof(String), NULL, "!AZ (0x!8XL) !AZ !#AZ !AZ",
                OpenSSL_version (OPENSSL_VERSION),
                SesolaOpenSslVersionNumber, cptr, zptr-sptr, sptr,
                SESOLA_SHARE ? "/SHARE" : "/LIBRARY");

   return (String);
}

/*****************************************************************************/
/*
Wrapper for keeping track of OpenSSL malloc()s for debug/development.
*/ 

void* SesolaMalloc (int size)

{
   void  *mptr;

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

   if (SesolaMemVmTrack)
      mptr = VmOpenSslMalloc (size + sizeof(ulong)*2);
   else
      mptr = malloc (size + sizeof(ulong)*2);
   if (!mptr) return (mptr);
   *(ULONGPTR)mptr = size;
   (uchar*)mptr += sizeof(ulong);
   *(ULONGPTR)mptr = 0xfee1dead;
   (uchar*)mptr += sizeof(ulong);

   SesolaMallocCount++;
   SesolaMallocBytes += (ulong)size;
   SesolaInUseBytes = SesolaMallocBytes - SesolaFreeBytes;
   if (SesolaInUseBytes > SesolaMaxBytes) SesolaMaxBytes = SesolaInUseBytes;

   return (mptr);
}

/*****************************************************************************/
/*
Wrapper for keeping track of OpenSSL realloc()s for debug/development.
*/ 

void* SesolaRealloc (void *mptr, int size)

{
   ulong  msize;

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

   if (!mptr) return (SesolaMalloc (size));

   (uchar*)mptr -= sizeof(ulong);
   if (*(ULONGPTR)mptr != 0xfee1dead)
      ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI);
   (uchar*)mptr -= sizeof(ulong);
   msize = *(ULONGPTR)mptr;
   if (size <= msize)
   {
      (uchar*)mptr += sizeof(ulong)*2;
      return (mptr);
   }

   if (SesolaMemVmTrack)
      mptr = VmOpenSslRealloc (mptr, size + sizeof(ulong)*2);
   else
      mptr = realloc (mptr, size + sizeof(ulong)*2);
   if (!mptr) return (mptr);
   (uchar*)mptr += sizeof(ulong)*2;

   SesolaReallocCount++;
   /* with VmOpenSslRealloc() this is done as an alloc and free */
   SesolaMallocBytes += (ulong)size;
   SesolaFreeBytes += msize;
   SesolaInUseBytes = SesolaMallocBytes - SesolaFreeBytes;
   if (SesolaInUseBytes > SesolaMaxBytes) SesolaMaxBytes = SesolaInUseBytes;

   return (mptr);
}

/*****************************************************************************/
/*
Wrapper for keeping track of OpenSSL free()s for debug/development.
*/ 

void SesolaFree (void *mptr)

{
   ulong  msize;

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

   if (!mptr) return;
   (uchar*)mptr -= sizeof(ulong);
   if (*(ULONGPTR)mptr != 0xfee1dead)
      ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI);
   (uchar*)mptr -= sizeof(ulong);
   msize = *(ULONGPTR)mptr;
   if (SesolaMemVmTrack)
      VmOpenSslFree (mptr);
   else
      free (mptr);

   SesolaFreeCount++;
   SesolaFreeBytes += (ulong)msize;
   SesolaInUseBytes = SesolaMallocBytes - SesolaFreeBytes;
   if (SesolaInUseBytes > SesolaMaxBytes) SesolaMaxBytes = SesolaInUseBytes;
}

/*****************************************************************************/
/*
Return a pointer to a buffer containing the memory track report.
*/ 

char* SesolaMemTrackReport (void)

{
   static ulong   LastLookTime64;
   static char  buf [192];

   int  status;
   int64  avail64,
            davail64,
            dinuse64,
            dmax64,
            max64;
   char LastLookAgo [64];

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

   if (!SesolaMemTrack) return (NULL);

   ThisLongAgo (&LastLookTime64, LastLookAgo);

   dinuse64 = SesolaInUseBytes - SesolaPrevInUseBytes;
   dmax64 = SesolaMaxBytes - SesolaPrevMaxBytes;
   avail64 = SesolaMaxBytes - SesolaInUseBytes;
   davail64 = avail64 - SesolaPrevAvailBytes;

   status = FaoToBuffer (buf, sizeof(buf), NULL,
"!AZ malloc:!&,UL (!&,UL) realloc:!&,UL (!&,UL) free:!&,UL (!&,UL) \
inuse:!&,@SQ (!AZ!&,@SQ) max:!&,@SQ (!AZ!&,@SQ) avail:!&,@SQ (!AZ!&,@SQ) (!AZ)",
                      LastLookAgo,
                      SesolaMallocCount,
                      SesolaMallocCount - SesolaPrevMallocCount,
                      SesolaReallocCount,
                      SesolaReallocCount - SesolaPrevReallocCount,
                      SesolaFreeCount,
                      SesolaFreeCount - SesolaPrevFreeCount,
                      &SesolaInUseBytes, dinuse64 > 0 ? "+" : "", &dinuse64,
                      &SesolaMaxBytes, dmax64 > 0 ? "+" : "", &dmax64,
                      &avail64, davail64 > 0 ? "+" : "", &davail64,
                      SesolaMemVmTrack ? "VM" : "DECC");
   if (VMSnok (status)) ErrorNoticed (NULL, status, NULL, FI_LI);

   SesolaPrevFreeCount = SesolaFreeCount;
   SesolaPrevMallocCount = SesolaMallocCount;
   SesolaPrevReallocCount = SesolaReallocCount;
   SesolaPrevInUseBytes = SesolaInUseBytes;
   SesolaPrevMaxBytes = SesolaMaxBytes;
   SesolaPrevAvailBytes = avail64;

   sys$gettim (&LastLookTime64); 

   return (buf);
}

/*****************************************************************************/
/*
Reset the counters.
*/ 

void SesolaMemTrackReset (void)

{
   /*********/
   /* begin */
   /*********/

   SesolaFreeCount = SesolaMallocCount = SesolaReallocCount =
      SesolaPrevFreeCount = SesolaPrevMallocCount = SesolaPrevReallocCount = 0;

   SesolaFreeBytes = SesolaInUseBytes = SesolaMallocBytes = SesolaMaxBytes =
      SesolaPrevAvailBytes = SesolaPrevFreeBytes = SesolaPrevInUseBytes =
      SesolaPrevMallocBytes = SesolaPrevMaxBytes = 0;
}

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

/*********************************/
#endif /* SESOLA123 || SESOLA321 */
/*********************************/

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

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

/* required global storage */
BOOL  ProtocolHttpsAvailable = false,
      ProtocolHttpsConfigured = false,
      SesolaVerifyCAConfigured = false;

/* i.e. disabled */
int  SesolaGblSecStructSize = 0,
     SesolaTicketKeySuperDay,
     SesolaVersionBitmap = -1;

globalvalue int  SESOLA_SHARE;

char  HttpdSesola [] = "",
      SesolaAvailableVersions [] = "",
      SesolaParams [256] = "",
      SesolaTicketKey [] = "";

int  SesolaParamsSize = sizeof(SesolaParams);

/* the link deposits an integer in this */
globalvalue int  SESOLA_SHARE;

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

SesolaInit ()
{
   if (SesolaVersionBitmap <= 0) return;
   FaoToStdout ("%HTTPD-E-SSL, non-SSL version\n");
   exit (STS$K_ERROR | STS$M_INHIB_MSG);
}

SesolaInitService (SERVICE_STRUCT *svptr)
{
   return;
}

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

BOOL SesolaSNIserviceSet (void *sesolaptr)
{
   return (false);
}

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

SesolaReport
(
REQUEST_STRUCT *rqptr,
char *VirtualHostPort
)
{
   if (WATCHMOD (rqptr, WATCH_MOD_SESOLA))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_SESOLA, "SesolaReport()");

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

void SesolaAdminReloadCA (REQUEST_STRUCT *rqptr)

{
   if (WATCHMOD (rqptr, WATCH_MOD_SESOLA))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_SESOLA, "SesolaAdminReloadCA()");

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

void SesolaControlReloadCA ()
{
   if (WATCH_MODULE(WATCH_MOD_SESOLA))
      WatchThis (WATCHALL, WATCH_MOD_SESOLA, "SesolaControlReloadCA()");
}

void SesolaControlReloadCerts ()
{
   if (WATCH_MODULE(WATCH_MOD_SESOLA))
      WatchThis (WATCHALL, WATCH_MOD_SESOLA, "SesolaControlReloadCerts()");
}

void* SesolaGetRequestPtr (void* sesolaptr)
{
   return (NULL);
}

void* SesolaRequestPtr (REQUEST_STRUCT *rqptr)
{
   return (NULL);
}

SesolaReportCA
(
REQUEST_STRUCT *rqptr,
char *VirtualHostPort
)
{
   if (WATCHMOD (rqptr, WATCH_MOD_SESOLA))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_SESOLA, "SesolaReportCA()");

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

char* SesolaRequestCipher (void *sesolaptr)
{
   if (WATCH_MODULE(WATCH_MOD_SESOLA))
      WatchThis (WATCHALL, WATCH_MOD_SESOLA, "SesolaRequestCipher()");

   return ("*BUGCHECK*");
}

BOOL SesolaRequestSessionReused (void *sesolaptr)
{
   if (WATCH_MODULE(WATCH_MOD_SESOLA))
      WatchThis (WATCHALL, WATCH_MOD_SESOLA, "SesolaRequestSessionReused()");

   return (false);
}

char* SesolaRequestVersion (void *sesolaptr)
{
   if (WATCH_MODULE(WATCH_MOD_SESOLA))
      WatchThis (WATCHALL, WATCH_MOD_SESOLA, "SesolaRequestVersion()");

   return ("*BUGCHECK*");
}

char* SesolaSessionTicketNewKey ()
{
   if (WATCH_MODULE(WATCH_MOD_SESOLA))
      WatchThis (WATCHALL, WATCH_MOD_SESOLA, "SesolaSessionTicketNewKey()");

   return;
}

char* SesolaSessionTicketUseKey (uchar* keyptr)
{
   if (WATCH_MODULE(WATCH_MOD_SESOLA))
      WatchThis (WATCHALL, WATCH_MOD_SESOLA, "SesolaSessionTicketUseKey()");

   return;
}

char* SesolaRequestALPN (REQUEST_STRUCT *rqptr)

{
   if (WATCH_MODULE(WATCH_MOD_SESOLA))
      WatchThis (WATCHALL, WATCH_MOD_SESOLA, "SesolaRequestALPN()");

   return (NULL);
}

void* SesolaRequestSesolaPtr (REQUEST_STRUCT *rqptr)

{
   if (WATCH_MODULE(WATCH_MOD_SESOLA))
      WatchThis (WATCHALL, WATCH_MOD_SESOLA, "SesolaRequestSesolaPtr()");

   return (NULL);
}

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

   return ("");
}

SesolaWatchPeek
(
REQUEST_STRUCT *rqptr,
void *SeSoLaPtr
)
{
   if (WATCHMOD (rqptr, WATCH_MOD_SESOLA))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_SESOLA, "SesolaWatchPeek()");
   return;
}

int SesolaDassgnChannel (void *sesolaptr)
{
   if (WATCH_MODULE(WATCH_MOD_SESOLA))
      WatchThis (WATCHALL, WATCH_MOD_SESOLA, "SesolaDassgnChannel()");

   return;
}

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

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

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

