/*****************************************************************************/
/*
                                Service.c

This module provides a complementary function to CONFIG.C, which basically
configures per-HTTPD process runtime characterstics, whereas SERVICE.C allows
per-service (essentially virtual services) characteristics to be specified. 
While there is some overlap between the two they do perform different tasks.

Prior to WASD v7.1 service configuration was performed by CONFIG.C as part of
general configuration.  This had it's limitations and this module goes some way
in providing a more flexible and understandable configuration environment. 
This module configuration file, HTTP$SERVICE, does not have to be used and if
is not present the HTTP$CONFIG configuration file [Service] directive will
continue to provide backward-compatible service configuration.  However, new
features will only be incorporated via this module, and HTTP$SERVICE is
available it's service configuration overrides anything still present in
WASD_CONFIG.

All service directives must be delimitted with '[' and ']'.  The (virtual)
service they apply to are specified with the usual '[[' and ']]' directive.

  [[http://the.host.name:80]]
  [ServiceIPaddress] 130.185.250.1 
  [ServiceProxy]  disabled
  [ServiceNoLog]  disabled
  [ServiceBodyTag]  
  [ServiceErrorReportPath]  
    
Both IPv4 and IPv6 addresses may be used to specify a service name and or
service address.  This can be in normal or compressed form.  If part of a
service name hyphens should be substituted for colons so that the port can be
differentiated from the service address.

  [[http://130.185.250.1:80]]
  [[http://--FFFF-130.185.250.1:80]]
  [[http://--FFFF-1FA-B983:80]]

In the case of a service specification a 'generic' host name can be specified
using "*".  This generic host name is replaced with the IP host name of
whichever system starts the server.  This is a simple way for providing a basic
or common service on all members of a cluster for instance.

  [[http://*:80]]
  [ServiceProxy]  disabled
  [ServiceNoLog]  disabled

This configuration file only really needs to be used for more complex service
configurations.  Basic HTTP and HTTPS services need only be specified using the
the WASD_CONFIG_GLOBAL [Service] directive.

The [IncludeFile] directive takes a VMS file name as a parameter.  It then
attempts to read and insert any directives contained in that file into the
current point in the server configuration.  Files may be nested one deep (i.e.
a main configuration file allowed to include other files, but the other file
not allowed in include yet more).


PRECEDENCE OF SERVICE SPECIFICATIONS
------------------------------------
1. /SERVICE= command line qualifier
2. WASD_CONFIG_SERVER configuration file (if logical defined and file exists)
3. WASD_CONFIG_GLOBAL [Service] directive


WASD_CONFIG_SERVICE DIRECTIVES
------------------------
  o  IncludeFile                    <filename>
  o  ServiceAdmin                   DISABLED | ENABLED
  o  ServiceBind                    <string>
  o  ServiceBodyTag                 <string>
  o  ServiceConnect                 DISABLED | ENABLED
  o  ServiceClientSSL               DISABLED | ENABLED
  o  ServiceClientSSLCaFile         <string>
  o  ServiceClientSSLcert           <string>
  o  ServiceClientSSLcipherList     <string>
  o  ServiceClientSSLkey            <string>
  o  ServiceClientSSLverifyCA       DISABLED | ENABLED
  o  ServiceClientSSLversion        <string>
  o  ServiceErrorReportPath         <string>
  o  ServiceHttp2Protocol           DISABLED | ENABLED (default)
  o  ServiceLogFormat               <string>
  o  ServiceNoLog                   DISABLED | ENABLED
  o  ServiceNonSSLRedirect          <host-name>|<IP-address>[:<port>]
  o  ServiceProxy                   DISABLED | ENABLED
  o  ServiceProxyAffinity           DISABLED | ENABLED
  o  ServiceProxyAuth               NONE | PROXY | CHAIN | LOCAL
  o  ServiceProxyCache              obsolete as of v12.0.0
  o  ServiceProxyChain              <string>
  o  ServiceProxyChainCred          <username>:<password>
  o  ServiceProxyBind               <string>
  o  ServiceProxyTunnel             CONNECT | FIREWALL | RAW
  o  ServiceProxyReworkMax          <integer>
  o  ServiceRawSocket               DISABLED | ENABLED
  o  ServiceShareSSH                <integer> 
  o  ServiceSSLcert                 <string>
  o  ServiceSSLcipherList           <string>
  o  ServiceSSLcipherSuites         <string>
  o  ServiceSSLoptions              <string>
  o  ServiceSSLverifyPeer           DISABLED | ENABLED | OPTIONAL
  o  ServiceSSLverifyPeerCAfile     <string>
  o  ServiceSSLverifyDataMax        <integer>
  o  ServiceSSLverifyPeerDepth      <integer>
  o  ServiceSSLversion              <string>
  o  ServiceSSLkey                  <string>
  o  ServiceSSLstrictTransSec       <integer> [<string>]


COMMAND-LINE PARAMETERS
-----------------------
The following syntax and values are also available for both the WASD_CONFIG
[service] configuration parameter and /SERVICE= qualifier.  Also see SESOLA.C
for SSL parameter processing.

  http:                      system host name, HTTP service
  https:                     system host name, SSL service
  http:port                  system host name, supplied port, HTTP service
  https:port                 system host name, supplied SSL service
  http://host.domain         specified host name, pot 80, HTTP service
  https://host.domain        specified host name port 443, SSL service
  http://host.domain:port    specified host name and port, HTTP service
  https://host.domain:port   specified host name and port, SSL service
  ;admin                     service is for per-instance administration
  ;bind=                     supplied IP address for service
  ;ip=                       supplied IP address for service
  ;cafile=                   CA file for verifying X.509 certificates
  ;cert=                     SSL service's non-default server certificate
  ;chain=                    service chains to up-stream proxy service
  ;cipher=                   SSL service's specific, non-default cipher list
  ;suites=                   SSL service's specific, non-default cipher list
  ;connect                   service provides SSL connect proxy access
  ;key=                      SSL service's non-default server RSA private key
  ;lauth                     local authorization required
  ;backlog=                  listen queue length
  ;pauth                     proxy authorization required
  ;pbind                     supplied IP address for proxy socket
  ;pclientssl                client SSL supported
  ;proxy                     service provide proxy access
  ;verify                    always verify SSL connection using X.509 cert

For example:

  [service]
  http://host.name:8080;proxy;connect
  https://host.name:443;cert=WASD_LOCAL:SITE.PEM


VERSION HISTORY
---------------
22-APR-2024  MGD  bugfix; allow service name devoid of alphabetics (e.g. 8-10.)
08-JUN-2023  MGD  [ServiceSSLcipherSuites] for TLSv1.3
14-MAR-2021  MGD  proxy caching obsolete
26-FEB-2021  MGD  [ServiceConnect] respond at connection on a port
                  [ServiceProxyReworkMax] to allow proxy reworking
15-AUG-2020  MGD  if no service configured create http: and https: ex nihilo
30-NOV-2016  MGD  [ServiceRawSocket] service is raw [web]socket
                  ensure HTTP/2 is disabled on "tunnel" services
06-JUN-2016  MGD  [ServiceSSLsessionTimeout] lifetime of session ticket or ID
                  [ServiceSSLverifyPeerDataMax] <integer>
                  bugfix; ServiceReviseNow() next new service (other items)
21-FEB-2015  MGD  [ServiceSSLstrictTransSec] <string>
17-JAN-2015  MGD  ServiceConfigLoadCallback() StringBuffer[4096];
15-NOV-2014  MGD  [ServiceSSLoptions] <string>
04-OCT-2013  MGD  [ServiceNonSSLRedirect] <host-name>|<IP-address>[:<port>]
06-MAR_2011  MGD  ServiceEntityMatch() processes in-match and if-not-match
30-OCT-2010  MGD  ServiceFindVirtual() make "Host:" beginning "www." find
                    services with the same remaining domain (eliminates the
                    need to have parallel "www.name.dom" and "name.dom")
12-SEP-2010  MGD  ServiceReportNow() add synopsis to service report
10-JUN-2010  MGD  bugfix; allow METACON_TOKEN_INCLUDE for [IncludeFile]
27-FEB-2010  MGD  [ServiceProxyChainCred] down-stream proxy credentials
21-JAN-2010  MGD  ServiceChange()
15-NOV-2009  MGD  [ServiceShareSSH] share with (allow proxy to) SSH (timeout)
13-OCT-2009  MGD  allow for []-delimited IPv6 address (as service names)
                  ServiceFindVirtual() allow for IPv6 interface in "Host:"
22-AUG-2009  MGD  bugfix; ServiceConfigFromString() create and use
                    temporary service structure if generating report
31-JUL-2009  MGD  ServiceConfigAdd() use INADDR_ANY if host name lookup fails
26-MAY-2009  MGD  [ServiceLogFormat] user defined format
23-OCT-2007  MGD  [ServiceProxyAuth] CHAIN
22-APR-2006  MGD  [ServiceProxyAffinity]
11-JUN-2005  MGD  allow [ServiceErrorReportPath] to specify a negative
                  code to exclude that from error reporting (e.g. -401)
20-APR-2005  MGD  add 'SSL Shared With:' report line
20-JAN-2005  MGD  bugfix; ServiceConfigAdd(), NetHostNameLookup() status check
14-NOV-2004  MGD  bugfix; reporting configuration errors (confusing
                  mention of next service, rather than current service)
15-AUG-2004  MGD  [ServiceClientSSLcert], [ServiceClientSSLkey]
10-AUG-2004  MGD  'tunnelling' concept generalises CONNECT method
06-AUG-2004  MGD  bugfix; ServiceFindVirtual() port string comparison
10-APR-2004  MGD  significant modifications to support IPv6
28-JAN-2004  MGD  service access log report (via LoggingReport())
15-AUG-2003  MGD  where CDATA constraints make using &#10; entity impossible
                  use a field name of hidden$lf and &#94; substituted for it,
                  bugfix; ServiceConfigReviseNow() form element names must be
                  unique (technically correct and enforced by modern browsers)
08-JUN-2003  MGD  bugfix; ServiceConfigFromString() (jpp@esme.fr)
28-JAN-2003  MGD  relax ServiceParse() so that [[the.host.name]] is accepted
                  (i.e. without both scheme and port)
30-SEP-2002  MGD  bugfix; ServiceConfigFromString()
21-SEP-2002  MGD  bugfix; make WASD_CONFIG_SERVICE not found non-fatal
15-MAY-2002  MGD  change [ServiceProxyHttpSSL] to [ServiceClientSSL]
23-FEB-2002  MGD  change [ServiceSSLclientVerify..] to [ServiceSSLverifyPeer..]
                  to avoid confusion with unrelated [ServiceClientSSL..]
06-JAN-2002  MGD  [ServiceProxyHttpSSL..] HTTP to SSL (HTTPS) gateway
14-OCT-2001  MGD  [ServiceProxyBind], [ServiceIPaddress] to [ServiceBind]
29-SEP-2001  MGD  instance support (including admin service)
15-SEP-2001  MGD  meta-config
04-AUG-2001  MGD  support module WATCHing
04-JUL-2001  MGD  additional information in service report
27-JUN-2001  MGD  bugfix; parsing of [ServiceProxyChain]
28-FEB-2001  MGD  OdsLoadTextFile(), OdsParseTextFile(), [IncludeFile]
16-FEB-2001  MGD  add service-based error report status code handling
10-DEC-2000  MGD  SSL client certificate verification
21-NOV-2000  MGD  allow for generic service (no host name specified)
17-JUN-2000  MGD  initial
*/
/*****************************************************************************/

#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 <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

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

/* application related header */
#include "wasd.h"

#define WASD_MODULE "SERVICE"

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

BOOL  ServiceAdminSpecified,
      ServiceConfigFileDefined;
      ServiceLoadFromConfigFile,
      ServiceWwwImplied;
 
SERVICE_META  ServiceMeta;
SERVICE_META  *ServiceMetaPtr;

LIST_HEAD  ServiceList;

char  ErrorServiceList [] = "service list confusing";

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

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

extern BOOL  HttpdServerStartup,
             LoggingPerService,
             ProtocolHttpsAvailable,
             ProtocolHttpsConfigured;

extern int  OpcomMessages,
            ServerPort;

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

extern CONFIG_STRUCT  Config;
extern META_CONFIG  *MetaGlobalServicePtr;
extern MSG_STRUCT  Msgs;
extern WATCH_STRUCT  Watch;

/*****************************************************************************/
/*
*/ 
 
int ServiceConfigLoad (META_CONFIG **MetaConPtrPtr)

{
   int  status;

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

   if (WATCH_MODULE(WATCH_MOD_SERVICE))
      WatchThis (WATCHALL, WATCH_MOD_SERVICE, "ServiceConfigLoad()");

   ServiceWwwImplied = Config.cfMisc.WwwImplied;

   if (!ServiceConfigFileDefined)
      ServiceConfigFileDefined = ServiceLoadFromConfigFile = true;

   if (CliServices[0] || !ServiceLoadFromConfigFile)
   {
      /* set services using /SERVICE=, or no service configuration file */
      ServiceLoadFromConfigFile = false;
      status = ServiceConfigFromString (MetaConPtrPtr);
   }
   else
   {
      ServiceLoadFromConfigFile = true;
      status = MetaConLoad (MetaConPtrPtr,
                            CONFIG_SERVICE_FILE_NAME,
                            &ServiceConfigLoadCallback, true, false);
   }

   if (*MetaConPtrPtr == MetaGlobalServicePtr)
   {
      /* server startup */
      MetaConStartupReport (MetaGlobalServicePtr, "SERVICE");
      if (VMSnok (status))
      {
         /* the WASD_CONFIG_SERVICE file does not *have* to exist! */
         if (status != RMS$_FNF) exit (status);
         status = SS$_NORMAL;
      }

      if (!ServiceList.EntryCount)
      {
         ErrorNoticed (NULL, 0, "no service configured - ex nihilo...", FI_LI);
         ServiceLoadFromConfigFile = false;
         status = ServiceConfigFromString (MetaConPtrPtr);
      }
   }

   return (status);
}

/*****************************************************************************/
/*
Called by MetaConUnload() to free resources allocated during service
configuration.
*/ 
 
ServiceConfigUnload (META_CONFIG *mcptr)

{
   int  status;
   SERVICE_META  *smptr;
   SERVICE_STRUCT *svptr;

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

   if (WATCH_MODULE(WATCH_MOD_SERVICE))
      WatchThis (WATCHALL, WATCH_MOD_SERVICE, "ServiceConfigUnload()");

   smptr = mcptr->ServiceMetaPtr;
   if (smptr && smptr != ServiceMetaPtr)
   {
      LIST_ITERATE (svptr, smptr->ServiceListPtr)
      {
         if (svptr->SSLserverPtr) VmFree (svptr->SSLserverPtr, FI_LI);
         if (svptr->SSLclientPtr) VmFree (svptr->SSLclientPtr, FI_LI);
         VmFree (svptr, FI_LI);
      }
      VmFree (smptr, FI_LI);
      mcptr->ServiceMetaPtr = NULL;
   }
}

/*****************************************************************************/
/*
Called after each service has been fully configured.
*/ 
 
ServiceConfigPostProcess (META_CONFIG *mcptr)

{
   static char  ProblemCannotConfig [] = "Cannot configure service";

   int  status;
   SERVICE_META  *smptr;
   SERVICE_STRUCT *svptr;

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

   if (WATCH_MODULE(WATCH_MOD_SERVICE))
      WatchThis (WATCHALL, WATCH_MOD_SERVICE, "ServiceConfigPostProcess()");

   smptr = mcptr->ServiceMetaPtr;

   if (smptr->ServiceLooksValid)
   {
      svptr = &smptr->ConfigService;

      /* disable irrelevant attributes */
      if (svptr->RawSocket)
      {
         svptr->AdminService = svptr->Http2Enabled =
            svptr->ProxyService = svptr->ConnectService = false;
         svptr->ShareSSH = 0;
      }
      else
      if (svptr->ConnectService)
      {
         svptr->AdminService = svptr->Http2Enabled =
            svptr->ProxyService = svptr->RawSocket = false;
         svptr->ShareSSH = 0;
      }
      else
      if (svptr->ProxyTunnel == PROXY_TUNNEL_CONNECT ||
          svptr->ProxyTunnel == PROXY_TUNNEL_FIREWALL ||
          svptr->ProxyTunnel == PROXY_TUNNEL_RAW)
         svptr->Http2Enabled = false;

      mcptr->ReportLinePtr = smptr->ServiceLine;
      mcptr->ReportLineNumber = smptr->ServiceLineNumber;
      status = ServiceConfigAdd (mcptr, &smptr->ConfigService);
      if (VMSnok (status))
         MetaConReport (mcptr, METACON_REPORT_ERROR, ProblemCannotConfig);
      mcptr->ReportLinePtr = NULL;
      mcptr->ReportLineNumber = 0;
   }
}

/*****************************************************************************/
/*
For each non-meta-config directive line read by MetaConLoad() this function is
called to parse the line text's contents and to configure the private data
structure associated with each rule.
*/

BOOL ServiceConfigLoadCallback (META_CONFIG *mcptr)

{
   static char  ProblemUnknownKeyword [] = "Unknown keyword",
                ProblemObsolete [] = "Directive is obsolete",
                ProblemDirective [] = "Unknown directive",
                ProblemInvalidService [] = "Invalid service specification",
                ProblemOverflow [] = "Storage overflow",
                ProblemNoSSL [] = "SSL not available",
                ProblemUsage [] = "Cannot use during service configuration";

   int  status,
        retval,
        LineLength;
   char  *cptr, *sptr, *zptr;
   char  StringBuffer [4096],
         StringScratch [4096];
   SERVICE_META  *smptr;
   METACON_LINE  *mclptr;
   SERVICE_STRUCT  *svptr;
   SESOLA_CONTEXT  *scptr, *ssptr;

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

   if (WATCH_MODULE(WATCH_MOD_SERVICE))
   {
      WatchThis (WATCHALL, WATCH_MOD_SERVICE,
                 "ServiceConfigLoadCallback() !&F !&X",
                 &ServiceConfigLoadCallback, mcptr);
      if (WATCH_MODULE(WATCH_MOD__DETAIL))
      {
         mclptr = mcptr->ParsePtr;
         WatchDataFormatted ("!&X !UL !UL !UL !UL !&X !&Z !&Z\n",
            mclptr, mclptr->Size, mclptr->Token, mclptr->Number,
            mclptr->Length, mclptr->LineDataPtr, mclptr->TextPtr,
            mclptr->InlineTextPtr);
      }
   }

   /* get a pointer to the current "line" */
   mclptr = mcptr->ParsePtr;

   if (mcptr == MetaGlobalServicePtr)
   {
      /* at server startup set the global service pointer */
      smptr = mcptr->ServiceMetaPtr = ServiceMetaPtr = &ServiceMeta;
      /* use the global service list */
      smptr->ServiceListPtr = &ServiceList;
   }
   else
   if (!mcptr->ServiceMetaPtr)
   {
      /* with a report conjure one up ex nihlo */
      smptr = mcptr->ServiceMetaPtr = VmGet (sizeof(SERVICE_META));
      /* use the internal service list */
      smptr->ServiceListPtr = &smptr->ServiceList;
   }
   else
      /* not the first time through */
      smptr = mcptr->ServiceMetaPtr;

   if (mclptr->Token == METACON_TOKEN_PRE)
   {
      /******************/
      /* pre-initialize */
      /******************/

      return (true);
   }

   if (mclptr->Token == METACON_TOKEN_POST)
   {
      /****************/
      /* post-process */
      /****************/

      /* if last loaded service add this to the list */
      ServiceConfigPostProcess (mcptr);

      return (true);
   }

   if (mclptr->Token == METACON_TOKEN_INCLUDE) return (true);

   if (mclptr->Token == METACON_TOKEN_VERSION) return (true);

   if (mclptr->Token == METACON_TOKEN_COMMENT)
      if (SAME4 (mclptr->TextPtr, '!#+#') ||
          SAME4 (mclptr->TextPtr, '!#-#') ||
          SAME4 (mclptr->TextPtr, '!#=#')) return (true);

   if (mclptr->Token != METACON_TOKEN_SERVICE &&
       mclptr->Token != METACON_TOKEN_TEXT)
   {
      /* only interested in services and directive text */
      MetaConReport (mcptr, METACON_REPORT_ERROR, ProblemUsage);
      return (true);
   }

   /***********/
   /* process */
   /***********/

   /* this service config structure will be used as working storage */
   svptr = &smptr->ConfigService;

   /* buffer the text associated with the current "line" */
   zptr = (sptr = StringBuffer) + sizeof(StringBuffer);
   cptr = mclptr->TextPtr;
   while (*cptr && sptr < zptr) *sptr++ = *cptr++;
   if (sptr >= zptr)
   {
      MetaConReport (mcptr, METACON_REPORT_ERROR, ProblemOverflow);
      return (false);
   }
   *sptr = '\0';
   cptr = StringBuffer;

   if (mclptr->Token == METACON_TOKEN_SERVICE)
   {
      /***************************/
      /* next (or first) service */
      /***************************/

      /* if previous valid service add this to the list */
      ServiceConfigPostProcess (mcptr);

      /* initialize service structure */
      memset (svptr, 0, sizeof(SERVICE_STRUCT));
      smptr->ServiceLooksValid = false;

      /* for MetaconReport() note line details for when ServiceConfigAdd() */
      strzcpy (smptr->ServiceLine,
               mcptr->CurrentOdsPtr->DataLinePtr,
               sizeof(smptr->ServiceLine));
      smptr->ServiceLineNumber = mcptr->CurrentOdsPtr->DataLineNumber;

      /* enable unless explicitly disabled */
      svptr->Http2Enabled = true;

      /* avoid loading the "next new service" entry ;^) */
      if (strchr (cptr, '?')) return (true);

      /*******************/
      /* process service */
      /*******************/

      smptr->ServiceLooksValid = true;

      retval = ServiceParse (cptr,
                             &svptr->RequestScheme,
                             &svptr->ServerHostName,
                             sizeof(svptr->ServerHostName),
                             &svptr->ServerPort,
                             &svptr->GenericService);

      if (retval == -1)
      {
         MetaConReport (mcptr, METACON_REPORT_ERROR, ProblemInvalidService);
         smptr->ServiceLooksValid = false;
      }

      if (svptr->RequestScheme == SCHEME_HTTPS)
      {
         if (ProtocolHttpsAvailable)
            svptr->SSLserverPtr = VmGet (sizeof(SESOLA_CONTEXT));
         else
         {
            MetaConReport (mcptr, METACON_REPORT_ERROR, ProblemNoSSL);
            smptr->ServiceLooksValid = false;
         }
      }

      return (true);
   }

   /**********************/
   /* service directives */
   /**********************/

   if (*cptr == '[')
   {
      cptr++;
      for (sptr = cptr; *sptr && *sptr != ']'; sptr++);
      if (*sptr != ']')
      {
         MetaConReport (mcptr, METACON_REPORT_ERROR, "missing \']\'?");
         return (true);
      }
      *sptr++ = '\0';
      while (*sptr && ISLWS(*sptr)) sptr++;
   }

   /****************************************************/
   /* ignore directives unless server specification ok */
   /****************************************************/

   if (!smptr->ServiceLooksValid) return (true);

   ssptr = svptr->SSLserverPtr;
   scptr = svptr->SSLclientPtr;

   if (strsame (cptr, "ServiceAdmin", -1))
      svptr->AdminService = MetaConSetBoolean (mcptr, sptr);
   else
   if (strsame (cptr, "ServiceBind", -1) ||
       /* and for backward compatibility */
       strsame (cptr, "ServiceIPaddress", -1))
   {
      MetaConSetString (mcptr, sptr, svptr->BindIpAddressString,
                        sizeof(svptr->BindIpAddressString));
      /* the convention for IPv6 literal hexadecimal is  upper-case */
      for (cptr = svptr->BindIpAddressString; *cptr; cptr++)
         *cptr = to_upper(*cptr);
   }
   else
   if (strsame (cptr, "ServiceBodyTag", -1))
      MetaConSetString (mcptr, sptr, svptr->BodyTag, sizeof(svptr->BodyTag));
   else
   if (strsame (cptr, "ServiceClientSSL", -1))
   {
      if (!scptr) svptr->SSLclientPtr = scptr = VmGet (sizeof(SESOLA_CONTEXT));
      svptr->SSLclientEnabled = MetaConSetBoolean (mcptr, sptr);
   }
   else
   if (strsame (cptr, "ServiceClientSSLCaFile", -1))
   {
      if (!scptr) svptr->SSLclientPtr = scptr = VmGet (sizeof(SESOLA_CONTEXT));
      MetaConSetString (mcptr, sptr, scptr->CaFile, sizeof(scptr->CaFile));
   }
   else
   if (strsame (cptr, "ServiceClientSSLcert", -1))
   {
      if (!scptr) svptr->SSLclientPtr = scptr = VmGet (sizeof(SESOLA_CONTEXT));
      MetaConSetString (mcptr, sptr, scptr->CertFile, sizeof(scptr->CertFile));
   }
   else
   if (strsame (cptr, "ServiceClientSSLcipherList", -1))
   {
      if (!scptr) svptr->SSLclientPtr = scptr = VmGet (sizeof(SESOLA_CONTEXT));
      MetaConSetString (mcptr, sptr, scptr->CipherList,
                        sizeof(scptr->CipherList));
   }
   else
   if (strsame (cptr, "ServiceClientSSLcipherSuites", -1))
   {
      if (!scptr) svptr->SSLclientPtr = scptr = VmGet (sizeof(SESOLA_CONTEXT));
      MetaConSetString (mcptr, sptr, scptr->CipherSuites,
                        sizeof(scptr->CipherSuites));
   }
   else
   if (strsame (cptr, "ServiceClientSSLkey", -1))
   {
      if (!scptr) svptr->SSLclientPtr = scptr = VmGet (sizeof(SESOLA_CONTEXT));
      MetaConSetString (mcptr, sptr, scptr->KeyFile, sizeof(scptr->KeyFile));
   }
   else
   if (strsame (cptr, "ServiceClientSSLverifyCA", -1))
   {
      if (!scptr) svptr->SSLclientPtr = scptr = VmGet (sizeof(SESOLA_CONTEXT));
      scptr->VerifyCA = MetaConSetBoolean (mcptr, sptr);
   }
   else
   if (strsame (cptr, "ServiceClientSSLversion", -1))
   {
      if (!scptr) svptr->SSLclientPtr = scptr = VmGet (sizeof(SESOLA_CONTEXT));
      MetaConSetString (mcptr, sptr, scptr->VersionString,
                        sizeof(scptr->VersionString));
   }
   else
   if (strsame (cptr, "ServiceErrorReportPath", -1))
      MetaConSetString (mcptr, sptr, svptr->ErrorReportPath,
                        sizeof(svptr->ErrorReportPath));
   else
   if (strsame (cptr, "ServiceHttp2Protocol", -1))
      svptr->Http2Enabled = MetaConSetBoolean (mcptr, sptr);
   else
   if (strsame (cptr, "ServiceLogFormat", -1))
      MetaConSetString (mcptr, sptr, svptr->LogFormat,
                        sizeof(svptr->LogFormat));
   else
   if (strsame (cptr, "ServiceNoLog", -1))
      svptr->NoLog = MetaConSetBoolean (mcptr, sptr);
   else
   if (strsame (cptr, "ServiceNoTrack", -1))
      MetaConReport (mcptr, METACON_REPORT_INFORM, ProblemObsolete);
   else
   if (strsame (cptr, "ServiceNonSslRedirect", -1))
      MetaConSetString (mcptr, sptr, svptr->NonSslRedirect,
                        sizeof(svptr->NonSslRedirect));
   else
   if (strsame (cptr, "ServiceProxy", -1))
      svptr->ProxyService = MetaConSetBoolean (mcptr, sptr);
   else
   if (strsame (cptr, "ServiceProxyAffinity", -1))
      svptr->ProxyAffinity = MetaConSetBoolean (mcptr, sptr);
   else
   if (strsame (cptr, "ServiceProxyAuth", -1))
   {
      MetaConSetString (mcptr, sptr, StringScratch, sizeof(StringScratch));
      if (!StringScratch[0] || strsame (StringScratch, "NONE", -1))
         svptr->ProxyAuthRequired = svptr->LocalAuthRequired =
            svptr->ProxyChainAuthRequired = false;
      else
      if (strsame (StringScratch, "PROXY", -1))
         svptr->ProxyAuthRequired = true;
      else
      if (strsame (StringScratch, "CHAIN", -1))
         svptr->ProxyChainAuthRequired = true;
      else
      if (strsame (StringScratch, "LOCAL", -1))
         svptr->LocalAuthRequired = true;
      else
      {
         MetaConReport (mcptr, METACON_REPORT_ERROR, ProblemUnknownKeyword);
         smptr->ServiceLooksValid = false;
         return (true);
      }
   }
   else
   if (strsame (cptr, "ServiceProxyBind", -1))
   {
      MetaConSetString (mcptr, sptr, svptr->ProxyBindIpAddressString,
                        sizeof(svptr->ProxyBindIpAddressString));
      /* the convention for IPv6 literal hexadecimal is  upper-case */
      for (cptr = svptr->ProxyBindIpAddressString; *cptr; cptr++)
         *cptr = to_upper(*cptr);
   }
   else
   if (strsame (cptr, "ServiceProxyCache", -1))
      MetaConReport (mcptr, METACON_REPORT_INFORM, ProblemObsolete);
   else
   if (strsame (cptr, "ServiceProxyChain", -1))
      MetaConSetString (mcptr, sptr, svptr->ProxyChainHostPort,
                        sizeof(svptr->ProxyChainHostPort));
   else
   if (strsame (cptr, "ServiceProxyChainCred", -1))
      MetaConSetString (mcptr, sptr, svptr->ProxyChainCred,
                        sizeof(svptr->ProxyChainCred));
   else
   if (strsame (cptr, "ServiceProxyReworkMax", -1))
      svptr->ProxyReworkMax = MetaConSetInteger (mcptr, sptr);
   else
   if (strsame (cptr, "ServiceProxySSL", -1))
   {
      /* backward compatibility (allows CONNECT) */
      if (MetaConSetBoolean (mcptr, sptr))
         svptr->ProxyTunnel = PROXY_TUNNEL_CONNECT;
      else
         svptr->ProxyTunnel = 0;
   }
   else
   if (strsame (cptr, "ServiceProxyTrack", -1))
      MetaConReport (mcptr, METACON_REPORT_INFORM, ProblemObsolete);
   else
   if (strsame (cptr, "ServiceProxyTunnel", -1))
   {
      MetaConSetString (mcptr, sptr, StringScratch, sizeof(StringScratch));
      if (!StringScratch[0] || strsame (StringScratch, "NONE", -1))
         svptr->ProxyTunnel = 0;
      else
      if (strsame (StringScratch, "CONNECT", -1))
         svptr->ProxyTunnel = PROXY_TUNNEL_CONNECT;
      else
      if (strsame (StringScratch, "FIREWALL", -1))
         svptr->ProxyTunnel = PROXY_TUNNEL_FIREWALL;
      else
      if (strsame (StringScratch, "RAW", -1))
         svptr->ProxyTunnel = PROXY_TUNNEL_RAW;
      else
      {
         MetaConReport (mcptr, METACON_REPORT_ERROR, ProblemUnknownKeyword);
         smptr->ServiceLooksValid = false;
         return (true);
      }
   }
   else
   if (strsame (cptr, "ServiceRawSocket", -1))
      svptr->RawSocket = MetaConSetBoolean (mcptr, sptr);
   else
   if (strsame (cptr, "ServiceConnect", -1))
      svptr->ConnectService = MetaConSetBoolean (mcptr, sptr);
   else
   if (strsame (cptr, "ServiceShareSSH", -1))
      svptr->ShareSSH = MetaConSetInteger (mcptr, sptr);
   else
   if (strsame (cptr, "ServiceSSLcert", -1))
   {
      if (!ssptr) svptr->SSLserverPtr = ssptr = VmGet (sizeof(SESOLA_CONTEXT));
      MetaConSetString (mcptr, sptr, ssptr->CertFile, sizeof(ssptr->CertFile));
   }
   else
   if (strsame (cptr, "ServiceSSLcipherList", -1))
   {
      if (!ssptr) svptr->SSLserverPtr = ssptr = VmGet (sizeof(SESOLA_CONTEXT));
      MetaConSetString (mcptr, sptr, ssptr->CipherList,
                        sizeof(ssptr->CipherList));
   }
   else
   if (strsame (cptr, "ServiceSSLcipherSuites", -1))
   {
      if (!ssptr) svptr->SSLserverPtr = ssptr = VmGet (sizeof(SESOLA_CONTEXT));
      MetaConSetString (mcptr, sptr, ssptr->CipherSuites,
                        sizeof(ssptr->CipherSuites));
   }
   else
   if (strsame (cptr, "ServiceSSLkey", -1))
   {
      if (!ssptr) svptr->SSLserverPtr = ssptr = VmGet (sizeof(SESOLA_CONTEXT));
      MetaConSetString (mcptr, sptr, ssptr->KeyFile, sizeof(ssptr->KeyFile));
   }
   else
   if (strsame (cptr, "ServiceSSLsessionLifetime", -1))
      svptr->ShareSSH = MetaConSetSeconds (mcptr, sptr, 1);
   else
   if (strsame (cptr, "ServiceSSLoptions", -1))
   {
      if (!ssptr) svptr->SSLserverPtr = ssptr = VmGet (sizeof(SESOLA_CONTEXT));
      MetaConSetString (mcptr, sptr, ssptr->OptionsString,
                        sizeof(ssptr->OptionsString));
   }
   else
   if (strsame (cptr, "ServiceSSLverifyPeer", -1) ||
       /* backward compatibility */
       strsame (cptr, "ServiceSSLclientVerify", -1) ||
       strsame (cptr, "ServiceSSLclientVerifyRequired", -1))
   {
      if (!ssptr) svptr->SSLserverPtr = ssptr = VmGet (sizeof(SESOLA_CONTEXT));
      MetaConSetString (mcptr, sptr, StringScratch, sizeof(StringScratch));
      if (!StringScratch[0] ||
          strsame (StringScratch, "DISABLED", -1) ||
          strsame (StringScratch, "NO", -1) ||
          strsame (StringScratch, "OFF", -1))
         ssptr->VerifyPeer = 0;
      else
      if (strsame (StringScratch, "ENABLED", -1) ||
          strsame (StringScratch, "YES", -1) ||
          strsame (StringScratch, "ON", -1))
         ssptr->VerifyPeer = SESOLA_VERIFY_PEER_REQUIRED;
      else
      if (strsame (StringScratch, "OPTIONAL", -1))
         ssptr->VerifyPeer = SESOLA_VERIFY_PEER_OPTIONAL;
      else
      {
         MetaConReport (mcptr, METACON_REPORT_ERROR, ProblemUnknownKeyword);
         smptr->ServiceLooksValid = false;
         return (true);
      }
   }
   else
   if (strsame (cptr, "ServiceSSLverifyPeerCAfile", -1) ||
       /* backward compatibility */
       strsame (cptr, "ServiceSSLclientVerifyCaFile", -1))
   {
      if (!ssptr) svptr->SSLserverPtr = ssptr = VmGet (sizeof(SESOLA_CONTEXT));
      MetaConSetString (mcptr, sptr, ssptr->CaFile, sizeof(ssptr->CaFile));
   }
   else
   if (strsame (cptr, "ServiceSSLverifyPeerDataMax", -1))
   {
      if (!ssptr) svptr->SSLserverPtr = ssptr = VmGet (sizeof(SESOLA_CONTEXT));
      ssptr->VerifyDataMax = MetaConSetInteger (mcptr, sptr);
   }
   else
   if (strsame (cptr, "ServiceSSLverifyPeerDepth", -1))
   {
      if (!ssptr) svptr->SSLserverPtr = ssptr = VmGet (sizeof(SESOLA_CONTEXT));
      ssptr->VerifyDepth = MetaConSetInteger (mcptr, sptr);
   }
   else
   if (strsame (cptr, "ServiceSSLversion", -1))
   {
      if (!ssptr) svptr->SSLserverPtr = ssptr = VmGet (sizeof(SESOLA_CONTEXT));
      MetaConSetString (mcptr, sptr, ssptr->VersionString,
                        sizeof(ssptr->VersionString));
   }
   else
   if (strsame (cptr, "ServiceSSLstrictTransSec", -1))
   {
      if (!ssptr) svptr->SSLserverPtr = ssptr = VmGet (sizeof(SESOLA_CONTEXT));
      MetaConSetString (mcptr, sptr, ssptr->StrictTransSec,
                        sizeof(ssptr->StrictTransSec));
   }
   else
      MetaConReport (mcptr, METACON_REPORT_ERROR, ProblemDirective);

   return (true);
}

/*****************************************************************************/
/*
Add the service to the loaded list.
*/

int ServiceConfigAdd
(
META_CONFIG *mcptr,
SERVICE_STRUCT *svptr
)
{
   static BOOL  SetOfficialServer;

   int  status;
   char  *cptr, *sptr, *zptr;
   SERVICE_META  *smptr;
   SERVICE_STRUCT *nsvptr;

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

   if (WATCH_MODULE(WATCH_MOD_SERVICE))
      WatchThis (WATCHALL, WATCH_MOD_SERVICE,
                 "ServiceConfigAdd() !AZ:!UL",
                 svptr->ServerHostName, svptr->ServerPort);

   /* get a pointer to the meta-config data */
   smptr = mcptr->ServiceMetaPtr;

   status = NetHostNameLookup (svptr->ServerHostName,
                               svptr->ServerPort,
                               &svptr->ServerHostName,
                               &svptr->ServerHostPort,
                               &svptr->ServerIpAddressString,
                               &svptr->ServerIpAddress, NULL);
   if (VMSnok (status))
   {
      MetaConReport (mcptr, METACON_REPORT_ERROR,
                     "Problem resolving host name !AZ",
                     svptr->ServerHostName);
      /* set this to zero so that NetCreateService() uses primary address */
      IPADDRESS_ZERO (&svptr->ServerIpAddress);
   }

   if (svptr->BindIpAddressString[0])
   {
      status = TcpIpStringToAddress (svptr->BindIpAddressString,
                                     &svptr->BindIpAddress);
      if (VMSnok (status))
      {
         MetaConReport (mcptr, METACON_REPORT_ERROR,
                        "Problem with !AZ bind address !AZ",
                        svptr->ServerHostName, svptr->BindIpAddressString);
         return (status);
      }
   }

   /************************/
   /* check/set parameters */
   /************************/

   if (svptr->AdminService)
   {
      if (ServiceAdminSpecified)
      {
         MetaConReport (mcptr, METACON_REPORT_ERROR,
                        "Redundant administration service");
         return (STS$K_ERROR);
      }
      ServiceAdminSpecified = true;
   }

   svptr->ServerHostPortLength = strlen(svptr->ServerHostPort);
   svptr->ServerHostNameLength = strlen(svptr->ServerHostName);
   sprintf (svptr->ServerPortString, "%d", svptr->ServerPort); 

   if (svptr->RequestScheme == SCHEME_HTTPS)
      svptr->RequestSchemeNamePtr = "https:";
   else
      svptr->RequestSchemeNamePtr = "http:";

   if (!svptr->ListenBacklog)
      svptr->ListenBacklog = Config.cfServer.ListenBacklog;
   if (!svptr->ListenBacklog)
      svptr->ListenBacklog = DEFAULT_LISTEN_BACKLOG;

   if (svptr->ErrorReportPath[0])
      svptr->ServiceErrorReportPath = true;
   else
      strzcpy (svptr->ErrorReportPath,
               Config.cfReport.ErrorReportPath,
               sizeof(svptr->ErrorReportPath));

   /*****************************/
   /* proxy service information */
   /*****************************/

   if (svptr->ProxyService ||
       svptr->ProxyTunnel)
   {
      if (svptr->ProxyChainHostPort[0])
      {
         status = NetHostNameLookup (svptr->ProxyChainHostPort, 0,
                                     &svptr->ProxyChainHostName,
                                     &svptr->ProxyChainHostPort,
                                     &svptr->ProxyChainIpAddressString,
                                     &svptr->ProxyChainIpAddress, NULL);
         if (VMSnok (status))
         {
            MetaConReport (mcptr, METACON_REPORT_ERROR,
                           "Problem resolving host name !AZ",
                           svptr->ProxyChainHostPort);
            return (status);
         }

         svptr->ProxyChainHostNameLength = strlen(svptr->ProxyChainHostName);
         if (!svptr->ProxyChainPort)
            svptr->ProxyChainPort = DEFAULT_HTTP_PROXY_PORT;
         sprintf (svptr->ProxyChainPortString, "%d", svptr->ProxyChainPort);
      }
   }

   if (svptr->ProxyBindIpAddressString[0])
   {
      status = TcpIpStringToAddress (svptr->ProxyBindIpAddressString,
                                     &svptr->ProxyBindIpAddress);
      if (VMSnok (status))
      {
         MetaConReport (mcptr, METACON_REPORT_ERROR,
                        "Problem with !AZ proxy bind address !AZ",
                        svptr->ServerHostName, svptr->ProxyBindIpAddressString);
         return (status);
      }
   }

   /***************************************/
   /* allocate memory add and new service */
   /***************************************/

   /* copy the filled-out service structure into an in-list structure */
   nsvptr = VmGet (sizeof (SERVICE_STRUCT));
   memcpy (nsvptr, svptr, sizeof(SERVICE_STRUCT));

   if (svptr->SSLserverPtr)
   {
      /* SESOLA server structure */
      nsvptr->SSLserverPtr = VmGet (sizeof(SESOLA_CONTEXT));
      memcpy (nsvptr->SSLserverPtr,
              svptr->SSLserverPtr,
              sizeof(SESOLA_CONTEXT));
   }

   if (svptr->SSLclientPtr)
   {
      /* SESOLA client structure */
      nsvptr->SSLclientPtr = VmGet (sizeof(SESOLA_CONTEXT));
      memcpy (nsvptr->SSLclientPtr,
              svptr->SSLclientPtr,
              sizeof(SESOLA_CONTEXT));
   }

   ListAddTail (smptr->ServiceListPtr, nsvptr, LIST_ENTRY_TYPE_SERVICE);

   /*********/
   /* other */
   /*********/

   /* process the error report path and any associated status codes */
   for (cptr = nsvptr->ErrorReportPath; *cptr && !ISLWS(*cptr); cptr++);
   nsvptr->ErrorReportPathLength = cptr - nsvptr->ErrorReportPath;
   if (*cptr) *cptr++ = '\0';
   while (*cptr && ISLWS(*cptr)) cptr++;
   nsvptr->ErrorReportPathCodesPtr = cptr;
   nsvptr->ErrorReportPathCodesCount = 0;
   while (*cptr &&
          nsvptr->ErrorReportPathCodesCount <
          SERVICE_ERROR_REPORT_PATH_CODES_MAX)
   {
      while (*cptr && ISLWS(*cptr)) cptr++;
      if (isdigit(*cptr) || *cptr == '-') 
         nsvptr->ErrorReportPathCodes[nsvptr->ErrorReportPathCodesCount++] =
            atoi(cptr);
      while (*cptr && !ISLWS(*cptr)) cptr++;
   }
   if (nsvptr->ErrorReportPathCodesCount >=
       SERVICE_ERROR_REPORT_PATH_CODES_MAX)
      MetaConReport (mcptr, METACON_REPORT_ERROR,
                     "Error report path codes problem");

   /* process name, etc., generated from the primary port */
   if (!SetOfficialServer)
   {
      /* the host and port for the "official" server */
      SetOfficialServer = ServerPort = nsvptr->ServerPort;
      sprintf (ServerHostPort, "%s:%d", ServerHostName, ServerPort);
   }

   if (nsvptr->RequestScheme == SCHEME_HTTPS)
      if (ProtocolHttpsAvailable)
         ProtocolHttpsConfigured = true;

   return (SS$_NORMAL);
}

/*****************************************************************************/
/*
Parse a HTTP scheme, host name, and integer port number from a string. 
Defaults apply to missing components.  Some basic checking is performed on the
components.  Return the number of characters scanned from the string, or -1 to
indicate a fatal error.
*/ 

int ServiceParse
(
char *SourceString,
int *SchemeValuePtr,
char *HostNamePtr,
int SizeOfHostName,
int *PortNumberPtr,
BOOL *GenericServicePtr
)
{
   BOOL  IsHost;
   char  *cptr, *sptr, *tptr, *zptr;

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

   if (WATCH_MODULE(WATCH_MOD_SERVICE))
      WatchThis (WATCHALL, WATCH_MOD_SERVICE,
                 "ServiceParse() !&Z", SourceString);

   *SchemeValuePtr = *PortNumberPtr = 0;
   *HostNamePtr = '\0';
   *GenericServicePtr = false;

   cptr = SourceString;

   if (strsame (cptr, "http:", 5))
   {
      *SchemeValuePtr = SCHEME_HTTP;
      cptr += 5;
   }
   else
   if (strsame (cptr, "https:", 6))
   {
      *SchemeValuePtr = SCHEME_HTTPS;
      cptr += 6;
   }
   else
   {
      /* if it looks like an incorrect scheme specification */
      for (tptr = cptr;
           *tptr && *tptr != '.' && *tptr != ':' && *tptr != '[';
           tptr++);
      if (tptr[0] == ':' && !isdigit(tptr[1]) && tptr[1] != '*') return (-1);
      /* otherwise it defaults to */
      *SchemeValuePtr = SCHEME_HTTP;
   }

   if (*cptr == '/') cptr++;
   if (*cptr == '/') cptr++;

   if (*cptr == '[')
   {
      /* IPv6 address */
      zptr = (sptr = HostNamePtr) + SizeOfHostName;
      while (*cptr && *cptr != ']' && sptr < zptr) *sptr++ = *cptr++;
      if (*cptr == ']' && sptr < zptr) *sptr++ = *cptr++;
      if (sptr >= zptr) return (-1);
      *sptr = '\0'; 
   }
   else
   if (isdigit(*cptr))
   {
      /* check if it's a dotted-decimal address */
      for (tptr = cptr; *tptr && isdigit(*tptr); tptr++);
      if (*tptr == '.')
      {
         /* dotted decimal address */
         zptr = (sptr = HostNamePtr) + SizeOfHostName;
         while (*cptr && (*cptr == '.' || isdigit(*cptr)) && sptr < zptr)
            *sptr++ = *cptr++;
         if (sptr >= zptr) return (-1);
         *sptr = '\0';
      }
      else
      {
         /* alphanumeric host name */
         zptr = (sptr = HostNamePtr) + SizeOfHostName;
         while (*cptr && (*cptr == '*' || *cptr == '.' || isalnum(*cptr) ||
                          *cptr == '-' || *cptr == '_') && sptr < zptr)
            *sptr++ = *cptr++;
         if (sptr >= zptr) return (-1);
         *sptr = '\0';
      }
   }
   else
   if (*cptr == '*')
   {
      /* generic service */
      while (*cptr && *cptr == '*') cptr++;
   }
   else
   {
      /* alphanumeric host name, or numeric address */
      zptr = (sptr = HostNamePtr) + SizeOfHostName;
      while (*cptr && (*cptr == '*' || *cptr == '.' || isalnum(*cptr) ||
                       *cptr == '-' || *cptr == '_') && sptr < zptr)
         *sptr++ = *cptr++;
      if (sptr >= zptr) return (-1);
      *sptr = '\0';
   }

   if (!HostNamePtr[0])
   {
      /* none or generic specified, default to primary server name */
      *GenericServicePtr = true;
      zptr = (sptr = HostNamePtr) + SizeOfHostName;
      for (tptr = ServerHostName; *tptr && sptr < zptr; *sptr++ = *tptr++);
      if (sptr >= zptr) return (-1);
      *sptr = '\0';
   }

   if (isdigit(*cptr) || (cptr[0] == ':' && isdigit(cptr[1])))
   {
      /* IP port */
      if (*cptr == ':') cptr++;
      *PortNumberPtr = atol(cptr);
      while (*cptr && isdigit(*cptr)) cptr++;
      if (*PortNumberPtr <= 0 || *PortNumberPtr > 65535) return (-1);
   }
   else
   if (*SchemeValuePtr == SCHEME_HTTPS)
      *PortNumberPtr = DEFAULT_HTTPS_PORT;
   else
      *PortNumberPtr = DEFAULT_HTTP_PORT;

   return (cptr - SourceString);
}

/*****************************************************************************/
/*
Parse the 'Service's parameter to determine which host(s)/port(s) services need
to be provided for.  This is a comma or newline separated list with no
white-space.  The service information may comprise of optional components:
"[scheme://][host][:port][;bind=address][;proxy][;chain=server][;cert=file]".
*/ 

int ServiceConfigFromString (META_CONFIG **MetaConPtrPtr)

{
   BOOL  ProxyService,
         SesolaServiceUsed;
   int  status,
        DummyRequestScheme,
        Length,
        ListenBacklog;
   char  *cptr, *sptr, *zptr;
   META_CONFIG  *mcptr;
   SERVICE_META  *smptr;
   SERVICE_STRUCT  *svptr;
   SESOLA_CONTEXT  SesolaServerService;
   SERVICE_STRUCT  ConfigService;
   char  StringScratch [256];

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

   if (WATCH_MODULE(WATCH_MOD_SERVICE))
      WatchThis (WATCHALL, WATCH_MOD_SERVICE,
                 "ServiceConfigFromString() !&Z !&Z !AZ:!UL",
                 CliServices, Config.cfServer.ServicePtr,
                 ServerHostName, ServerPort);

   status = MetaConLoad (MetaConPtrPtr, NULL, NULL, false, false);
   if (VMSnok (status)) return (status);

   mcptr = *MetaConPtrPtr;

   if (mcptr == MetaGlobalServicePtr)
   {
      /* during server startup set the global service pointer */
      smptr = mcptr->ServiceMetaPtr = ServiceMetaPtr = &ServiceMeta;
      /* use the global service list */
      smptr->ServiceListPtr = &ServiceList;
   }
   else
   {
      /* a report then conjure one up ex nihlo */
      smptr = mcptr->ServiceMetaPtr = VmGet (sizeof(SERVICE_META));
      /* use the internal service list */
      smptr->ServiceListPtr = &smptr->ServiceList;
   }

   if (CliServices[0])
      cptr = CliServices;
   else
   if (!Config.cfServer.ServicePtr)
   {
      if (ProtocolHttpsAvailable)
         sprintf (cptr = CliServices, "http://%s:80,https://%s:443",
                  ServerHostName, ServerHostName);
      else
         sprintf (cptr = CliServices, "http://%s:80", ServerHostName);
   }
   else
      cptr = Config.cfServer.ServicePtr;

   /******************************/
   /* parse comma-separated list */
   /******************************/

   while (*cptr)
   {
      while (*cptr && (isspace(*cptr) || *cptr == ',')) cptr++;
      if (!*cptr) break;

      memset (&ConfigService, 0, sizeof(ConfigService));
      memset (&SesolaServerService, 0, sizeof(SesolaServerService));
      SesolaServiceUsed = false;

      /*************************/
      /* service specification */
      /*************************/

      Length = ServiceParse (cptr,
                             &ConfigService.RequestScheme,
                             &ConfigService.ServerHostName,
                             sizeof(ConfigService.ServerHostName),
                             &ConfigService.ServerPort,
                             &ConfigService.GenericService);
      if (Length < 0) ErrorExitVmsStatus (0, ErrorServiceList, FI_LI);
      cptr += Length;

      if (!ProtocolHttpsAvailable &&
          ConfigService.RequestScheme == SCHEME_HTTPS)
      {
         FaoToStdout ("%HTTPD-W-SERVICE, \
SSL not available, service ignored\n \\!AZ//!AZ:!UL\\\n",
            ConfigService.RequestSchemeNamePtr,
            ConfigService.ServerHostPort);
         continue;
      }

      if (ConfigService.RequestScheme == SCHEME_HTTPS)
      {
         ConfigService.RequestSchemeNamePtr = "https:";
         SesolaServiceUsed = true;
      }
      else
         ConfigService.RequestSchemeNamePtr = "http:";

      /****************************/
      /* other service parameters */
      /****************************/

      while (*cptr == ';')
      {
         if (strsame (cptr, ";admin", 6))
            ConfigService.AdminService = true;
         else
         if (strsame (cptr, ";backlog=", 9))
         {
            cptr += 9;
            ConfigService.ListenBacklog = atoi(cptr);
         }
         else
         if (strsame (cptr, ";bind=", 6) ||
             /* for backward compatibility */
             strsame (cptr, ";ip=", 4))
         {
            while (*cptr && *cptr != '=') cptr++;
            if (*cptr) cptr++;
            zptr = (sptr = ConfigService.BindIpAddressString) +
                    sizeof(ConfigService.BindIpAddressString);
            while (*cptr && !isspace(*cptr) && *cptr != ',' &&
                   *cptr != ';' && sptr < zptr) *sptr++ = *cptr++;
            if (sptr >= zptr)
               ErrorExitVmsStatus (0, ErrorServiceList, FI_LI);
            *sptr = '\0';
         }
         else
         if (strsame (cptr, ";cafile=", 8))
         {
            cptr += 8;
            zptr = (sptr = SesolaServerService.CaFile) +
                   sizeof(SesolaServerService.CaFile);
            while (*cptr && !isspace(*cptr) && *cptr != ',' &&
                   *cptr != ';' && sptr < zptr) *sptr++ = to_upper(*cptr++);
            if (sptr >= zptr)
               ErrorExitVmsStatus (0, ErrorServiceList, FI_LI);
            *sptr = '\0';
            SesolaServiceUsed = true;
         }
         else
         if (strsame (cptr, ";cert=", 6))
         {
            cptr += 6;
            zptr = (sptr = SesolaServerService.CertFile) +
                   sizeof(SesolaServerService.CertFile);
            while (*cptr && !isspace(*cptr) && *cptr != ',' &&
                   *cptr != ';' && sptr < zptr) *sptr++ = to_upper(*cptr++);
            if (sptr >= zptr)
               ErrorExitVmsStatus (0, ErrorServiceList, FI_LI);
            *sptr = '\0';
            SesolaServiceUsed = true;
         }
         else
         if (strsame (cptr, ";chain=", 7))
         {
            cptr += 7;
            zptr = (sptr = ConfigService.ProxyChainHostPort) +
                   sizeof(ConfigService.ProxyChainHostPort);
            while (*cptr && !isspace(*cptr) && *cptr != ',' &&
                   *cptr != ';' && sptr < zptr) *sptr++ = *cptr++;
            if (sptr >= zptr)
               ErrorExitVmsStatus (0, ErrorServiceList, FI_LI);
            *sptr = '\0';
         }
         else
         if (strsame (cptr, ";connect", 8))
            ConfigService.ProxyTunnel == PROXY_TUNNEL_CONNECT;
         else
         if (strsame (cptr, ";cipher=", 8))
         {
            cptr += 8;
            zptr = (sptr = SesolaServerService.CipherList) +
                   sizeof(SesolaServerService.CipherList);
            while (*cptr && !isspace(*cptr) && *cptr != ',' &&
                   *cptr != ';' && sptr < zptr) *sptr++ = *cptr++;
            if (sptr >= zptr)
               ErrorExitVmsStatus (0, ErrorServiceList, FI_LI);
            *sptr = '\0';
            SesolaServiceUsed = true;
         }
         else
         if (strsame (cptr, ";suites=", 8))
         {
            cptr += 8;
            zptr = (sptr = SesolaServerService.CipherSuites) +
                   sizeof(SesolaServerService.CipherSuites);
            while (*cptr && !isspace(*cptr) && *cptr != ',' &&
                   *cptr != ';' && sptr < zptr) *sptr++ = *cptr++;
            if (sptr >= zptr)
               ErrorExitVmsStatus (0, ErrorServiceList, FI_LI);
            *sptr = '\0';
            SesolaServiceUsed = true;
         }
         else
         if (strsame (cptr, ";key=", 5))
         {
            cptr += 5;
            zptr = (sptr = SesolaServerService.KeyFile) +
                   sizeof(SesolaServerService.KeyFile);
            while (*cptr && !isspace(*cptr) && *cptr != ',' &&
                   *cptr != ';' && sptr < zptr) *sptr++ = to_upper(*cptr++);
            if (sptr >= zptr)
               ErrorExitVmsStatus (0, ErrorServiceList, FI_LI);
            *sptr = '\0';
            SesolaServiceUsed = true;
         }
         else
         if (strsame (cptr, ";lauth", 6))
            ConfigService.LocalAuthRequired = true;
         else
         if (strsame (cptr, ";paff", 6))
            ConfigService.ProxyAffinity = true;
         else
         if (strsame (cptr, ";pauth", 6))
            ConfigService.ProxyAuthRequired = true;
         else
         if (strsame (cptr, ";cauth", 6))
            ConfigService.ProxyChainAuthRequired = true;
         else
         if (strsame (cptr, ";proxy", 6))
            ConfigService.ProxyService = true;
         else
         if (strsame (cptr, ";pbind=", 7))
         {
            cptr += 7;
            zptr = (sptr = ConfigService.ProxyBindIpAddressString) +
                    sizeof(ConfigService.ProxyBindIpAddressString);
            while (*cptr && !isspace(*cptr) && *cptr != ',' &&
                   *cptr != ';' && sptr < zptr) *sptr++ = *cptr++;
            if (sptr >= zptr)
               ErrorExitVmsStatus (0, ErrorServiceList, FI_LI);
            *sptr = '\0';
         }
         else
         if (strsame (cptr, ";pclientssl", 11))
         {
            if (!ConfigService.SSLclientPtr)
               ConfigService.SSLclientPtr = VmGet (sizeof(SESOLA_CONTEXT));
            ConfigService.SSLclientEnabled = true;
         }
         else
         if (strsame (cptr, ";verify", 7))
            SesolaServiceUsed =  SesolaServerService.VerifyPeer = true;
         else
         {
            if (*cptr == ';') cptr++;
            sptr = cptr;
            while (*sptr && *sptr != ';' && *sptr != ',') sptr++;
            FaoToStdout ("%HTTPD-W-SERVICE, \
unknown parameter, ignored\n \\!#AZ\\\n",
               sptr-cptr, cptr);
            cptr = sptr;
         }

         if (*cptr == ';') cptr++;
         while (*cptr && !isspace(*cptr) && *cptr != ',' && *cptr != ';')
            cptr++;
      }

      /***************/
      /* add service */
      /***************/

      if (SesolaServiceUsed) ConfigService.SSLserverPtr = &SesolaServerService;

      status = ServiceConfigAdd (mcptr, &ConfigService);
      if (VMSnok (status))
      {
         FaoToStdout ("%HTTPD-W-SERVICE, \
cannot configure service\n \\!AZ//!AZ\\\n",
            ConfigService.RequestSchemeNamePtr,
            ConfigService.ServerHostPort);
         continue;
      }

      if (*cptr && *cptr != ',' && !isspace(*cptr))
         ErrorExitVmsStatus (0, ErrorServiceList, FI_LI);
   }

   return (SS$_NORMAL);
}

/*****************************************************************************/
/*
Search the list of virtual services for one matching.  If either host or port
component is omitted or specified as a wildcard then match any.
*/ 

BOOL ServiceIsConfigured (char *HostPort)

{
   char  *cptr, *sptr;
   SERVICE_STRUCT  *svptr;

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

   if (WATCH_MODULE(WATCH_MOD_SERVICE))
      WatchThis (WATCHALL, WATCH_MOD_SERVICE,
                 "ServiceIsConfigured() !&Z", HostPort);

   LIST_ITERATE (svptr, &ServiceList)
   {
      if (WATCH_MODULE(WATCH_MOD_SERVICE))
         WatchDataFormatted ("!&Z\n", svptr->ServerHostPort);

      cptr = HostPort;
      sptr = svptr->ServerHostPort;
      /* compare the host name portion */
      if (*cptr == ':')
      {
         if (*sptr == '[')
         {
            /* IPv6 address */
            while (*sptr && *sptr != ']') sptr++;
            if (*sptr == ']') sptr++;
         }
         else
            while (*sptr && *sptr != ':') sptr++;
      }
      while (*cptr && *cptr != ':' && *sptr && *sptr != ':')
      {
         if (*cptr == '*')
         {
            while (*cptr == '*') cptr++;
            while (*sptr && to_lower(*sptr) != to_lower(*cptr)) sptr++;
            if (!*cptr || *cptr == ':' || !*sptr || *sptr == ':') break;
         }
         if (to_lower(*cptr) != to_lower(*sptr)) break;
         cptr++;
         sptr++;
      }
      /* continue if the host name portion did not match */
      if ((*cptr && *cptr != ':') || *sptr != ':') continue;
      /* return true if the parameter did not contain a specific port */
      if (!*cptr)
      {
         if (WATCH_MODULE(WATCH_MOD_SERVICE))
            WatchThis (WATCHALL, WATCH_MOD_SERVICE, "YES");
         return (true);
      }
      cptr++;
      sptr++;
      /* compare the port portion */
      while (*cptr && *sptr)
      {
         if (*cptr == '*')
         {
            while (*cptr == '*') cptr++;
            while (*sptr && *sptr != *cptr) sptr++;
            if (!*cptr || !*sptr) break;
         }
         if (*cptr != *sptr) break;
         cptr++;
         sptr++;
      }
      /* return true if the ports matched */
      if (!*cptr && !*sptr)
      {
         if (WATCH_MODULE(WATCH_MOD_SERVICE))
            WatchThis (WATCHALL, WATCH_MOD_SERVICE, "YES");
         return (true);
      }
   }

   if (WATCH_MODULE(WATCH_MOD_SERVICE))
      WatchThis (WATCHALL, WATCH_MOD_SERVICE, "NO");
   return (false);
}

/*****************************************************************************/
/*
Find a service' host name and port that matches the request's "Host:" request
header line.  Point the request's service pointer at the virtual service's
structure is found.  Return true if a matching service was found, false if not.
*/ 

BOOL ServiceFindVirtual (REQUEST_STRUCT *rqptr)

{
   BOOL  wwwName;
   int  HostNameLength,
        HostPortLength;
   char  *cptr, *sptr, *sptr1, *sptr2, *zptr;
   char  Ipv6HostSansIf [256];
   SERVICE_STRUCT  *svptr;

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

   if (WATCHMOD (rqptr, WATCH_MOD_SERVICE))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_SERVICE,
                 "ServiceFindVirtual() !&Z", rqptr->rqHeader.HostPtr);

   if (rqptr->NetIoPtr->SesolaPtr)
      if (SesolaSNIserviceSet (rqptr->NetIoPtr->SesolaPtr))
         return (true);
 
   if (!rqptr->rqHeader.HostPtr || !rqptr->rqHeader.HostPtr[0])
   {
      /* just use whatever service the request connected to */
      if (WATCHING (rqptr, WATCH_CONNECT))
         WatchThis (WATCHITM(rqptr), WATCH_CONNECT,
                    "ACTUAL !AZ", rqptr->ServicePtr->ServerHostPort); 
      return (true);
   }

   cptr = rqptr->rqHeader.HostPtr;
   if (*cptr == '[')
   {
      /* IPv6 (one form of anyway) */
      for (sptr1 = cptr; *sptr1 && *sptr1 != ']' && *sptr1 != '%'; sptr1++);
      if (*sptr1 == '%')
      {
         /* copy, excising the interface */
         zptr = (sptr = Ipv6HostSansIf) + sizeof(Ipv6HostSansIf)-1;
         while (*cptr && *cptr != '%' && sptr < zptr) *sptr++ = *cptr++;
         while (*cptr && *cptr != ']') cptr++;
         sptr1 = sptr;
         while (*cptr && sptr < zptr) *sptr++ = *cptr++;
         *sptr = '\0';
         cptr = Ipv6HostSansIf;
      }
      if (*sptr1 == ']') sptr1++;
   }
   else
   {
      /* IPv4 */
      for (sptr1 = cptr; *sptr1 && *sptr1 != ':'; sptr1++);
   }
   HostNameLength = sptr1 - cptr;
   while (*sptr1) sptr1++;
   HostPortLength = sptr1 - cptr;

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

   LIST_ITERATE (svptr, &ServiceList)
   {
      if (WATCHMOD (rqptr, WATCH_MOD_SERVICE))
         WatchThis (WATCHITM(rqptr), WATCH_MOD_SERVICE,
                    "!UL !UL !&Z !&B !UL !UL !&Z",
                    HostNameLength, HostPortLength, cptr, wwwName,
                    svptr->ServerHostNameLength, svptr->ServerHostPortLength,
                    svptr->ServerHostPort); 

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

      if (HostNameLength == HostPortLength)
      {
         /* no port specified, use default for request scheme */
         switch (rqptr->ServicePtr->RequestScheme)
         {
            case SCHEME_HTTP :
               if (svptr->ServerPort != DEFAULT_HTTP_PORT) continue;
               break;
            case SCHEME_HTTPS :
               if (svptr->ServerPort != DEFAULT_HTTPS_PORT) continue;
               break;
            default :
               continue;
         }
      }
      else
      if (HostPortLength != svptr->ServerHostPortLength)
         continue;

      sptr1 = cptr;
      /* only if a port was included in the "Host:" compare that */
      if (HostNameLength == HostPortLength)
         sptr2 = svptr->ServerHostName;
      else
         sptr2 = svptr->ServerHostPort;
      /* if "Host:" name begins "www." and this one does not */
      if (wwwName && !MATCH4 (sptr2, "www.")) sptr1 += 4;
      while (*sptr1 && *sptr2)
      {
         if (to_lower(*sptr1) == to_lower(*sptr2))
         {
            sptr1++;
            sptr2++;
         }
         else
            break;
      }
      if (*sptr1 || *sptr2) continue;

      if (WATCHING (rqptr, WATCH_CONNECT))
         WatchThis (WATCHITM(rqptr), WATCH_CONNECT,
                    "VIRTUAL !AZ", svptr->ServerHostPort); 

      /* change the request's service pointer to it's virtual equivalent */
      rqptr->ServicePtr = rqptr->NetIoPtr->ServicePtr = svptr;

      return (true);
   }

   if (WATCHING (rqptr, WATCH_CONNECT))
      WatchThis (WATCHITM(rqptr), WATCH_CONNECT, "UNKNOWN service"); 

   return (false);
}

/*****************************************************************************/
/*
Look for a matching service and change the request to point to that service. 
If none matching found then a 500 (internal - server configuration) error.  Of
course just changing the request pointer to another service can have profound
implications - consider changing an HTTP request to an SSL service or vice
versa!
*/ 

BOOL ServiceChange
(
REQUEST_STRUCT *rqptr,
char *ServiceSpec
)
{
   SERVICE_STRUCT  *svptr;

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

   if (WATCHMOD (rqptr, WATCH_MOD_SERVICE))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_SERVICE,
                 "ServiceChange() !&Z", ServiceSpec);

   if (strsame (ServiceSpec, "RAW", -1))
   {
      /* find the (first) service offering raw tunneling */
      LIST_ITERATE (svptr, &ServiceList)
         if (svptr->ProxyService && svptr->ProxyTunnel == PROXY_TUNNEL_RAW)
            break;
   }
   else
   if (strsame (ServiceSpec, "http://", 7))
   {
      /* find the matching HTTP service */
      LIST_ITERATE (svptr, &ServiceList)
      {
         if (svptr->RequestScheme != SCHEME_HTTP) continue;
         if (StringMatch (rqptr, svptr->ServerHostPort, ServiceSpec+7)) break;
      }
   }
   else
   if (strsame (ServiceSpec, "https://", 8))
   {
      /* find the matching SSL service */
      LIST_ITERATE (svptr, &ServiceList)
      {
         if (svptr->RequestScheme != SCHEME_HTTPS) continue;
         if (StringMatch (rqptr, svptr->ServerHostPort, ServiceSpec+8)) break;
      }
   }
   else
   {
      /* find the matching service */
      LIST_ITERATE (svptr, &ServiceList)
         if (StringMatch (rqptr, svptr->ServerHostPort, ServiceSpec)) break;
   }

   if (svptr)
   {
      if (WATCHING (rqptr, WATCH_REQUEST))
         WatchThis (WATCHITM(rqptr), WATCH_REQUEST,
                    "SERVICE change !AZ MATCHED !AZ!AZ -> !AZ!AZ",
                    ServiceSpec,
                    rqptr->ServicePtr->RequestSchemeNamePtr,
                    rqptr->ServicePtr->ServerHostPort,
                    svptr->RequestSchemeNamePtr,
                    svptr->ServerHostPort);

      /* move the request to that service */
      rqptr->ServiceBeforeChangePtr = rqptr->ServicePtr;
      rqptr->ServicePtr = rqptr->NetIoPtr->ServicePtr = svptr;

      return (true);
   }

   if (WATCHING (rqptr, WATCH_REQUEST))
      WatchThis (WATCHITM(rqptr), WATCH_REQUEST,
                 "SERVICE change !AZ NOMATCH", ServiceSpec);
   rqptr->rqResponse.HttpStatus = 500;

   return (false);
}

/*****************************************************************************/
/*
Output string configured services (called from ConfigReportNow()).
*/ 

int ServiceReportConfigFromString
(
REQUEST_STRUCT *rqptr,
CONFIG_STRUCT *cfptr
)
{
   static char  ServicesFao [] = 
"<p><table class=\"ctgry\">\n\
<tr><th class=\"ctttl\">Service</th></tr>\n\
<tr><td>\n\
<table class=\"rghtlft\">\n";

   static char  ServiceLoadFao [] =
"<tr><th class=\"tacnt\">(see &quot;Services&quot; report)</th></tr>\n";

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

   static char  OneServiceFao [] =
"<tr><th>!UL.</th>\
<td><tt>!AZ//!AZ!AZ!&@!AZ!&@!&@!AZ!AZ!AZ!AZ!AZ</tt></td>\
<td><tt>!AZ</tt></td></tr>\n";

   static char  ServiceListFao [] =
"<tr><th>!UL.</th><td><tt>!AZ<tt></td></tr>\n";

   static char  EndServicesFao [] =
"<tr><td colspan=\"2\"><b>Service Not Found URL:&nbsp;</b>!AZ</td></tr>\n\
</table>\n\
</td></tr>\n\
</table>\n";

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

   int  count, status;
   unsigned long  FaoVector [32];
   char  *cptr, *sptr;
   unsigned long  *vecptr;
   SERVICE_STRUCT  *svptr;

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

   if (WATCH_MODULE(WATCH_MOD_SERVICE))
      WatchThis (WATCHALL, WATCH_MOD_SERVICE,
                 "ServiceReportConfigFromString()");

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

   if (ServiceLoadFromConfigFile)
   {
      status = FaolToNet (rqptr, ServiceLoadFao, NULL);
      if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI);
   }
   else
   if (!cfptr->cfServer.ServicePtr)
   {
      status = FaolToNet (rqptr, ServiceNoneFao, NULL);
      if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI);
   }
   else
   if (cfptr != &Config)
   {
      count = 1;
      for (cptr = cfptr->cfServer.ServicePtr; *cptr; cptr++)
      {
         sptr = cptr;
         vecptr = FaoVector;
         *vecptr++ = count++;
         *vecptr++ = sptr;
         while (*cptr && *cptr != ',') cptr++;
         if (*cptr)
         {
            *cptr = '\0';
            status = FaolToNet (rqptr, ServiceListFao, &FaoVector);
            if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI);
            *cptr = ',';
         }
         else
         {
            status = FaolToNet (rqptr, ServiceListFao, &FaoVector);
            if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI);
         }
      }
   }
   else
   {
      count = 1;
      LIST_ITERATE (svptr, &ServiceList)
      {
         vecptr = FaoVector;

         *vecptr++ = count++;
         *vecptr++ = svptr->RequestSchemeNamePtr;
         *vecptr++ = svptr->ServerHostPort;

         if (svptr->ProxyChainHostPort[0])
         {
            *vecptr++ = ";chain=!AZ";
            *vecptr++ = svptr->ProxyChainHostPort;
         }
         else
            *vecptr++ = "";
         if (svptr->ProxyTunnel == PROXY_TUNNEL_CONNECT)
            *vecptr++ = ";connect";
         else
            *vecptr++ = "";
         if (svptr->BindIpAddressString[0])
         {
            *vecptr++ = ";ip=!AZ";
            *vecptr++ = svptr->BindIpAddressString;
         }
         else
            *vecptr++ = "";
         if (svptr->ListenBacklog != DEFAULT_LISTEN_BACKLOG)
         {
            *vecptr++ = ";backlog=!UL";
            *vecptr++ = svptr->ListenBacklog;
         }
         else
            *vecptr++ = "";
         if (svptr->LocalAuthRequired)
            *vecptr++ = ";lauth";
         else
            *vecptr++ = "";
         if (svptr->ProxyAffinity)
            *vecptr++ = ";paff";
         else
            *vecptr++ = "";
         if (svptr->ProxyAuthRequired)
            *vecptr++ = ";pauth";
         else
            *vecptr++ = "";
         if (svptr->ProxyChainAuthRequired)
            *vecptr++ = ";cauth";
         else
            *vecptr++ = "";
         if (svptr->ProxyService)
            *vecptr++ = ";proxy";
         else
            *vecptr++ = "";

         *vecptr++ = svptr->ServerIpAddressString;

         FaoCheck (sizeof(FaoVector), &FaoVector, vecptr, FI_LI);
         status = FaolToNet (rqptr, OneServiceFao, &FaoVector);
         if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI);
      }
   }

   vecptr = FaoVector;
   if (cfptr->cfServer.ServiceNotFoundUrl[0])
      *vecptr++ = cfptr->cfServer.ServiceNotFoundUrl;
   else
      *vecptr++ = NotNoneFao;

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

   return (SS$_NORMAL);
}

/*****************************************************************************/
/*
A server administration report on the server's configuration. This function
just wraps the reporting function, loading a temporary database if necessary
for reporting from the configuration file.
*/ 

ServiceReport
(
REQUEST_STRUCT *rqptr,
BOOL UseServerDatabase
)
{
   int  status;
   META_CONFIG  *mcptr;

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

   if (WATCHMOD (rqptr, WATCH_MOD_SERVICE))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_SERVICE,
                 "ServiceReport() !&F !UL",
                 &ServiceReport, UseServerDatabase);

   if (UseServerDatabase)
      ServiceReportNow (rqptr, MetaGlobalServicePtr);
   else
   {
      status = ServiceConfigLoad (&mcptr);
      if (VMSnok (status))
      {
         /* severe error reported */
         rqptr->rqResponse.HttpStatus = 403;
         ErrorGeneral (rqptr, mcptr->LoadReport.TextPtr, FI_LI);
      }
      else
         ServiceReportNow (rqptr, mcptr);
      MetaConUnload (&mcptr, NULL);
   }

   AdminEnd (rqptr);
}

/*****************************************************************************/
/*
Return a report on the HTTPd service configuration ... now!  This function
blocks while executing.
*/ 

ServiceReportNow
(
REQUEST_STRUCT *rqptr,
META_CONFIG *mcptr
)
{
#define REPORT_ENDIS(b) \
{ if (b) *vecptr++ = "[enabled]"; else *vecptr++ = "[disabled]"; }

#define REPORT_STRING_DEFAULT(b) \
{ if (b && b[0]) *vecptr++ = b; else *vecptr++ = ReportDefault; }

#define REPORT_STRING_NONE(b) \
{ if (b && b[0]) *vecptr++ = b; else *vecptr++ = ReportNotNone; }

   static char  ReportSynopsisFao [] =
"<p><table class=\"ctgry\">\n\
<tr><th class=\"ctttl\">Synopsis</th></tr>\n\
<tr><td>\n\
<table class=\"lftlft\">\n\
<tr><td></td>\
<th class=\"sbttl pdrght1\">Service</th>\
<th class=\"sbttl pdrght1\">Address</th>\
<th class=\"sbttl pdrght1\">IP</th>\
<th class=\"sbttl pdrght1\">HTTP</th>\
<th class=\"sbttl pdrght1\">SSL</th>\
<th class=\"sbttl pdrght1\">Proxy</th>\
<th class=\"sbttl pdrght1\">Tunnel</th>\
<th class=\"sbttl\">Chain</th></tr>\n";

   static char  ReportOneSynopsisFao [] =
"<tr><th class=\"targht\">!UL.</th>\
<td class=\"pdrght1\">!AZ//!AZ</td>\
<td class=\"pdrght1\">!AZ</td>\
<td class=\"pdrght1 targht\">!&?v4\rv6\r</td>\
<td class=\"pdrght1 targht\">!AZ</td>\
<td class=\"pdrght1\">!AZ</td>\
<td class=\"pdrght1\">!AZ</td>\
<td class=\"pdrght1\">!AZ!AZ!&@</td>\
<td>!AZ</td>\
</tr>\n";

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

   static char  ReportServiceFao [] =
"<p><table class=\"ctgry\">\n\
<tr><th class=\"ctttl\">&nbsp;&nbsp;!UL&nbsp;&nbsp;-&nbsp;&nbsp;\
<a href=\"!AZ//!AZ\">!AZ//!AZ</a></th></tr>\n\
<tr><td>\n\
<table class=\"rghtlft\">\n\
<tr><td>\n\
<table cellpadding=\"0\" cellspacing=\"5\" border=\"0\">\n\
<tr><th>Service IP Address:</th><td>!AZ</td></tr>\n\
<tr><th>Bind IP Address:</th><td>!AZ</td></tr>\n\
<tr><th>MultiHome IP Address:</th><td>!&I</td></tr>\n\
<tr><th>Socket IP Address:</th><td>!&@</td></tr>\n\
!&@\
<tr><th>Device:</th><td>!&@</td></tr>\n";

   static char  ReportHttp2Fao [] =
"<tr><td style=\"height:0.5em;\"></td></tr>\n\
<tr><th>HTTP/2:</th><td>!&@</td></tr>\n";

   static char  ReportSslServiceFao [] =
"<tr><td style=\"height:0.5em;\"></td></tr>\n\
<tr><th>SSL Version:</th><td>!AZ</td></tr>\n\
<tr><th>SSL Options:</th>\
<td style=\"white-space:normal;\">!AZ</td></tr>\n\
<tr><th>SSL Cipher List:</th>\
<td style=\"white-space:normal;\">!AZ</td></tr>\n\
<tr><th>SSL Cipher Suites:</th>\
<td style=\"white-space:normal;\">!AZ</td></tr>\n\
<tr><th>SSL Certificate:</th><td>!AZ</td></tr>\n\
<tr><th>SSL Private Key:</th><td>!AZ</td></tr>\n\
<tr><th>SSL Session Lifetime:</th><td>!AZ!AZ</td></tr>\n\
<tr><th>SSL Verify Peer:</th><td>!AZ</td></tr>\n\
<tr><th>SSL Verify Peer Data Max:</th><td>!UL!AZ</td></tr>\n\
<tr><th>SSL Verify Peer CA File:</th><td>!AZ</td></tr>\n\
<tr><th>Strict Transport Security:</th>\
<td>!AZ</td></tr>\n\
<tr><th>SSL Shared With:</th><td>";

   static char  ReportSslClientFao [] =
"<tr><td style=\"height:0.5em;\"></td></tr>\n\
<tr><th>SSL/Client</th><td>!AZ</td></tr>\n\
<tr><th>SSL/Client Version:</th><td>!AZ</td></tr>\n\
<tr><th>SSL/Client Certificate:</th><td>!AZ</td></tr>\n\
<tr><th>SSL/Client Private Key:</th><td>!AZ</td></tr>\n\
<tr><th>SSL/Client Verify CA:</th><td>!AZ</td></tr>\n\
<tr><th>SSL/Client Verify CA File:</th><td>!AZ</td></tr>\n\
<tr><th>SSL/Client Cipher List:</th>\
<td style=\"white-space:normal;\">!AZ</td></tr>\n\
<tr><th>SSL/Client Cipher Suites:</th>\
<td style=\"white-space:normal;\">!AZ</td></tr>\n";

   static char  ReportProxyFao [] =
"<tr><td style=\"height:0.5em;\"></td></tr>\n\
<tr><th>Proxy:</th><td>!AZ</td></tr>\n";

   static char  ReportProxyDetailsFao [] =
"<tr><th>Proxy Tunnel:</th><td>!AZ</td></tr>\n\
<tr><th>Proxy Bind IP Address:</th><td>!AZ</td></tr>\n\
<tr><th>Proxy Affinity:</th><td>!AZ</td></tr>\n\
<tr><th>Proxy Authorization:</th><td>!AZ</td></tr>\n\
<tr><th>Proxy Chain:</th><td>!AZ</td></tr>\n\
<tr><th>Proxy Chain Cred:</th><td>!AZ</td></tr>\n\
<tr><th>Proxy Rework Max:</th><td>!UL</td></tr>\n";

   static char  ReportOtherFao [] =
"<tr><td style=\"height:0.5em;\"></td></tr>\n\
<tr><th>RawSocket:</th><td>!AZ</td></tr>\n\
<tr><th>(On) Connect:</th><td>!AZ</td></tr>\n\
<tr><th>Share SSH:</th><td>!UL\
&nbsp;<i>(!&?enabled\rdisabled\r)</i></td></tr>\n\
<tr><th>Admin:</th><td>!&@</td></tr>\n\
<tr><th>NoLog:</th><td>!AZ</td></tr>\n\
<tr><th>Log Format:</th><td><tt>!AZ</tt>!AZ</td></tr>\n\
<tr><th>Body Tag:</th><td colspan=\"2\">!AZ</td></tr>\n\
<tr><th>Report Path:</th><td colspan=\"2\">!&@</td></tr>\n\
<tr><td style=\"height:0.5em;\"></td></tr>\n\
<tr><th>Access Log:</th><td>!&@</td></tr>\n\
</table>\n\
</td></tr>\n\
</table>\n\
</td></tr>\n\
</table>\n";

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

   static char  ReportDefault [] = "<i>(default)</i>",
                ReportNotAppl [] = "<i>(n/a)</i>",
                ReportNotNone [] = "<i>(none)</i>",
                ReportServiceNoneFao [] = "<i>(no services defined)</i>";

   int  count, idx, status,
        SslShareCount;
   unsigned long  *vecptr;
   unsigned long  FaoVector [64];
   char  *cptr, *sptr, *zptr;
   LIST_HEAD  *slptr;
   SERVICE_META  *smptr;
   SERVICE_STRUCT  *svptr, *tsvptr;
   SESOLA_CONTEXT  *scptr, *ssptr;

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

   if (WATCHMOD (rqptr, WATCH_MOD_SERVICE))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_SERVICE, "ServiceReportNow()");

   /* get a pointer to the meta-config data */
   smptr = mcptr->ServiceMetaPtr;

   AdminPageTitle (rqptr, "Service Configuration");
   AdminMetaConReport (rqptr, mcptr, MetaGlobalServicePtr);
   if (ServiceConfigFileDefined)
      AdminMetaConSource (rqptr, mcptr, MetaGlobalServicePtr);

   /************/
   /* synopsis */
   /************/

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

   count = 0;
   LIST_ITERATE (svptr, smptr->ServiceListPtr)
   {
      count++;

      vecptr = FaoVector;

      *vecptr++ = count;
      *vecptr++ = svptr->RequestSchemeNamePtr;
      *vecptr++ = svptr->ServerHostPort;
      if (svptr->BindIpAddressString[0])
         *vecptr++ = svptr->BindIpAddressString;
      else
         *vecptr++ = svptr->ServerIpAddressString;
      *vecptr++ = IPADDRESS_IS_V4(&svptr->ServerIpAddress);
      *vecptr++ = svptr->Http2Enabled ? "2+1" : "1";
      if (svptr->SSLserverPtr)
         *vecptr++ = ((SESOLA_CONTEXT*)svptr->SSLserverPtr)->VersionStringPtr;
      else
         *vecptr++ = "";
      if (svptr->ProxyService)
         *vecptr++ = "enabled";
      else
         *vecptr++ = "";
      if (svptr->ShareSSH)
         *vecptr++ = "ShareSSH ";
      else
         *vecptr++ = "";
      if (!svptr->ProxyService)
         *vecptr++ = "";
      else
      if (svptr->ProxyTunnel == PROXY_TUNNEL_CONNECT)
         *vecptr++ = "CONNECT";
      else
      if (svptr->ProxyTunnel == PROXY_TUNNEL_FIREWALL)
         *vecptr++ = "FIREWALL";
      else
      if (svptr->ProxyTunnel == PROXY_TUNNEL_RAW)
         *vecptr++ = "RAW";
      else
         *vecptr++ = "";
      if (!svptr->ProxyService)
         *vecptr++ = "";
      else
      if (svptr->SSLclientPtr &&
          ((SESOLA_CONTEXT*)svptr->SSLclientPtr)->VersionStringPtr)
      {
         *vecptr++ = "+!AZ";
         *vecptr++ = ((SESOLA_CONTEXT*)svptr->SSLclientPtr)->VersionStringPtr;
      }
      else
         *vecptr++ = "";
      if (svptr->ProxyChainHostPort[0])
         *vecptr++ = svptr->ProxyChainHostPort;
      else
         *vecptr++ = "";

      FaoCheck (sizeof(FaoVector), &FaoVector, vecptr, FI_LI);
      status = FaolToNet (rqptr, ReportOneSynopsisFao, &FaoVector);
      if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI);
   }

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

   /************/
   /* services */
   /************/

   count = 0;
   LIST_ITERATE (svptr, smptr->ServiceListPtr)
   {
      count++;

      vecptr = FaoVector;

      *vecptr++ = count;
      *vecptr++ = svptr->RequestSchemeNamePtr;
      *vecptr++ = svptr->ServerHostPort;
      *vecptr++ = svptr->RequestSchemeNamePtr;
      *vecptr++ = svptr->ServerHostPort;

      *vecptr++ = svptr->ServerIpAddressString;
      REPORT_STRING_NONE (svptr->BindIpAddressString)
      *vecptr++ = &svptr->MultiHomeIpAddress;

      if (svptr->ServerChannel)
      {
         *vecptr++ = "!&I!&? (INADDR_ANY)\r\r";
         *vecptr++ = &svptr->ServerIpAddress;
         *vecptr++ = IPADDRESS_IS_ANY (&svptr->ServerIpAddress);
      }
      else
      if (mcptr == MetaGlobalServicePtr)
      {
         if (svptr->BgDevName[0])
         {
            *vecptr++ = "<i>(!AZ)</i>";
            *vecptr++ = svptr->BgDevName;
         }
         else
            *vecptr++ = ReportNotNone;
      }
      else
         *vecptr++ = ReportNotAppl;

      if (mcptr == MetaGlobalServicePtr)
      {
         if (VMSnok (svptr->ServerBindStatus))
         {
            if (svptr->ServerBindStatus)
            {
               *vecptr++ = 
"<tr><th>Bind:</th><td>\
<font color=\"#ff0000\">!&m<font></td></tr>\n";
               *vecptr++ = svptr->ServerBindStatus;
            }
            else
               *vecptr++ = 
"<tr><th>Bind:</th><td>reused existing socket</td></tr>\n";
         }
         else
            *vecptr++ = "";
      }
      else
         *vecptr++ = "";

      if (svptr->ServerChannel)
      {
         *vecptr++ = "!AZ (!UL channel!%s)";
         *vecptr++ = svptr->BgDevName;
         *vecptr++ = NetGetRefCnt (svptr->ServerChannel);
      }
      else
      if (svptr->BgDevName[0])
      {
         *vecptr++ = "<i>(!AZ)</i>";
         *vecptr++ = svptr->BgDevName;
      }
      else
         *vecptr++ = ReportNotNone;

      FaoCheck (sizeof(FaoVector), &FaoVector, vecptr, FI_LI);
      status = FaolToNet (rqptr, ReportServiceFao, &FaoVector);
      if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI);

      vecptr = FaoVector;
      REPORT_ENDIS (svptr->Http2Enabled)
      status = FaolToNet (rqptr, ReportHttp2Fao, &FaoVector);
      if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI);

      if (ssptr = svptr->SSLserverPtr)
      {
         vecptr = FaoVector;

         REPORT_STRING_DEFAULT (ssptr->VersionString)
         REPORT_STRING_DEFAULT (ssptr->OptionsString)
         REPORT_STRING_DEFAULT (ssptr->CipherList)
         REPORT_STRING_DEFAULT (ssptr->CipherSuites)
         REPORT_STRING_DEFAULT (ssptr->CertFile)
         REPORT_STRING_DEFAULT (ssptr->KeyFile)
         *vecptr++ = MetaConShowSeconds (rqptr, ssptr->SessionLifetime);
         *vecptr++ = ssptr->SessionLifetime ? "" : " <i>(default)</i>";
         switch (ssptr->VerifyPeer)
         {
             case SESOLA_VERIFY_PEER_OPTIONAL : *vecptr++ = "[optional]"; break;
             case SESOLA_VERIFY_PEER_REQUIRED : *vecptr++ = "[enabled]"; break;
             default : *vecptr++ = "[disabled]";
         }
         *vecptr++ = ssptr->VerifyDataMax;
         *vecptr++ = ssptr->VerifyDataMax ? "" : " <i>(default)</i>";
         REPORT_STRING_DEFAULT (ssptr->CaFile)
         REPORT_STRING_DEFAULT (ssptr->StrictTransSec)

         FaoCheck (sizeof(FaoVector), &FaoVector, vecptr, FI_LI);
         status = FaolToNet (rqptr, ReportSslServiceFao, &FaoVector);
         if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI);

         SslShareCount = 0;
         LIST_ITERATE (tsvptr, smptr->ServiceListPtr)
         {
            if (tsvptr == svptr) continue;
            if (tsvptr->RequestScheme != SCHEME_HTTPS) continue;
            if (!IPADDRESS_IS_SAME (&tsvptr->MultiHomeIpAddress,
                                    &svptr->MultiHomeIpAddress)) continue;
            if (tsvptr->ServerPort != svptr->ServerPort) continue;
            SslShareCount++;
            FaoToNet (rqptr, "!AZ<br>", tsvptr->ServerHostPort);
         }
         if (SslShareCount)
            FaoToNet (rqptr, "</td></tr>\n");
         else
            FaoToNet (rqptr, "!AZ</td></tr>\n", ReportNotNone);
      }

      if (ssptr && svptr->NonSslRedirect[0])
         FaoToNet (rqptr,
"<tr><th>Non-SSL Redirect:</th>\
<td>!&;AZ</td></tr><td>\n",
                   svptr->NonSslRedirect);

      if (scptr = svptr->SSLclientPtr)
      {
         vecptr = FaoVector;

         REPORT_ENDIS (svptr->SSLclientEnabled)
         REPORT_STRING_DEFAULT (scptr->VersionStringPtr)
         REPORT_STRING_DEFAULT (scptr->CertFilePtr)
         REPORT_STRING_DEFAULT (scptr->KeyFilePtr)
         REPORT_ENDIS (scptr->VerifyCA)
         REPORT_STRING_DEFAULT (scptr->CaFilePtr)
         REPORT_STRING_DEFAULT (scptr->CipherListPtr)
         REPORT_STRING_DEFAULT (scptr->CipherSuitesPtr)

         FaoCheck (sizeof(FaoVector), &FaoVector, vecptr, FI_LI);
         status = FaolToNet (rqptr, ReportSslClientFao, &FaoVector);
         if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI);
      }

      vecptr = FaoVector;

      REPORT_ENDIS (svptr->ProxyService)

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

      if (svptr->ProxyService)
      {
         vecptr = FaoVector;

         if (svptr->ProxyTunnel == PROXY_TUNNEL_CONNECT)
            *vecptr++ = "CONNECT";
         else
         if (svptr->ProxyTunnel == PROXY_TUNNEL_FIREWALL)
            *vecptr++ = "FIREWALL";
         else
         if (svptr->ProxyTunnel == PROXY_TUNNEL_RAW)
            *vecptr++ = "RAW";
         else
            *vecptr++ = ReportNotNone;
         REPORT_STRING_NONE (svptr->ProxyBindIpAddressString)
         REPORT_ENDIS (svptr->ProxyAffinity)
         if (svptr->ProxyAuthRequired)
            *vecptr++ = "PROXY";
         else
         if (svptr->ProxyChainAuthRequired)
            *vecptr++ = "CHAIN";
         else
         if (svptr->LocalAuthRequired)
            *vecptr++ = "LOCAL";
         else
            *vecptr++ = ReportNotNone;
         REPORT_STRING_NONE (svptr->ProxyChainHostPort)
         REPORT_STRING_NONE (svptr->ProxyChainCred)
         *vecptr++ = svptr->ProxyReworkMax;

         FaoCheck (sizeof(FaoVector), &FaoVector, vecptr, FI_LI);
         status = FaolToNet (rqptr, ReportProxyDetailsFao, &FaoVector);
         if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI);
      }

      vecptr = FaoVector;

      REPORT_ENDIS (svptr->RawSocket)
      REPORT_ENDIS (svptr->ConnectService)
      *vecptr++ = svptr->ShareSSH;
      *vecptr++ = svptr->ShareSSH;
      if (svptr->AdminService)
         *vecptr++ = "<font color=\"#ff0000\"><b>!AZ</b></font>";
      REPORT_ENDIS (svptr->AdminService)
      REPORT_ENDIS (svptr->NoLog)
      REPORT_STRING_DEFAULT (svptr->LogFormat)
      if (svptr->LogFormatProblem)
         *vecptr++ = "&nbsp;&nbsp;<font color=\"#ff0000\"><b>(PROBLEM)</b></font>";
      else
         *vecptr++ = "";
      REPORT_STRING_DEFAULT (svptr->BodyTag)
      if (svptr->ServiceErrorReportPath)
      {
         *vecptr++ = "!AZ !AZ";
         *vecptr++ = svptr->ErrorReportPath;
         *vecptr++ = svptr->ErrorReportPathCodesPtr;
      }
      else
         *vecptr++ = ReportDefault;

      if (svptr->LogFileNameLength)
      {
         *vecptr++ =
"<a href=\"!AZ?service=!AZ\">!AZ</a> <i>(!&?open\rclosed\r)</i>";
         *vecptr++ = ADMIN_REPORT_SERVICE_LOG;
         *vecptr++ = svptr->ServerHostPort;
         if (LoggingPerService)
         {
            *vecptr++ = svptr->LogFileName;
            *vecptr++ = svptr->LogFileOpen;
         }
         else
         {
            slptr = smptr->ServiceListPtr;
            *vecptr++ = ((SERVICE_STRUCT*)LIST_GET_HEAD(slptr))->LogFileName;
            *vecptr++ = ((SERVICE_STRUCT*)LIST_GET_HEAD(slptr))->LogFileOpen;
         }
      }
      else
         *vecptr++ = "<i>(none)</i>";

      FaoCheck (sizeof(FaoVector), &FaoVector, vecptr, FI_LI);
      status = FaolToNet (rqptr, ReportOtherFao, &FaoVector);
      if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI);
   }

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

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

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

#undef REPORT_ENDIS
#undef REPORT_STRING_DEFAULT
#undef REPORT_STRING_NONE
}

/*****************************************************************************/
/*
A server administration menu for service configuration. This function just
wraps the revision function, loading a temporary database if necessary for
reporting from the configuration file.
*/ 

ServiceConfigRevise
(
REQUEST_STRUCT *rqptr,
BOOL UseServerDatabase
)
{
   int  status;
   META_CONFIG  *mcptr;

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

   if (WATCHMOD (rqptr, WATCH_MOD_SERVICE))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_SERVICE,
                 "ServiceConfigRevise() !&F !UL",
                 &ServiceConfigRevise, UseServerDatabase);

   if (UseServerDatabase)
      ServiceConfigReviseNow (rqptr, MetaGlobalServicePtr);
   else
   {
      status = ServiceConfigLoad (&mcptr);
      if (VMSnok (status))
      {
         /* severe error reported */
         rqptr->rqResponse.HttpStatus = 403;
         ErrorGeneral (rqptr, mcptr->LoadReport.TextPtr, FI_LI);
      }
      else
         ServiceConfigReviseNow (rqptr, mcptr);
      MetaConUnload (&mcptr, NULL);
   }

   AdminEnd (rqptr);
}

/*****************************************************************************/
/*
Return a report on the HTTPd service configuration ... now!
*/ 

ServiceConfigReviseNow
(
REQUEST_STRUCT *rqptr,
META_CONFIG *mcptr
)
{
   static char  ReviseServiceNoneFao [] = "<i>(no services defined)</i>";

   static char  ReviseServiceFao [] =
"!AZ\
<p><table class=\"ctgry\">\n\
<tr><th class=\"ctttl\">!&@!AZ!AZ!AZ</th></tr>\n\
<tr><td>\n\
<table class=\"rghtlft\">\n\
\
<tr><th>Scheme:</th><td>\n\
<input type=\"hidden\" name=\"hidden$lf\" value=\"&#94;[[\">\n\
<input type=\"radio\" name=\"ServiceScheme!UL\" value=\"http:\"!&? CHECKED\r\r>http\n\
<input type=\"radio\" name=\"ServiceScheme!UL\" value=\"https:\"!&? CHECKED\r\r>https\n\
</td></tr>\n\
<input type=\"hidden\" name=\"hidden$lf\" value=\"//\">\n\
<tr><th>Name:</th><td>\
<input type=\"text\" size=\"40\" name=\"ServiceName!UL\" value=\"!AZ\">\
</td></tr>\n\
<input type=\"hidden\" name=\"hidden$lf\" value=\":\">\n\
<tr><th>Port:</th><td>\
<input type=\"text\" size=\"5\" name=\"ServicePort!UL\" value=\"!AZ\">\n\
<input type=\"hidden\" name=\"hidden$lf\" value=\"]]\">\n\
</td></tr>\n\
\
<tr><th>Bind IP Address:</th><td>\
<input type=\"hidden\" name=\"hidden$lf\" value=\"&#94;[ServiceBind]&#32;&#32;\">\n\
<input type=\"text\" size=\"20\" name=\"ServiceBind!UL\" value=\"!AZ\">\n\
&nbsp;<i>(only if NI multi-homed)</i></td></tr>\n";

   static char  ReviseHttp2Fao [] =
"<tr><td style=\"height:0.5em;\"></td></tr>\n\
\
<tr><th>HTTP2:</th><td>\
<input type=\"hidden\" name=\"hidden$lf\" value=\"&#94;[ServiceHttp2Protocol]&#32;&#32;\">\n\
<input type=\"radio\" name=\"ServiceHttp2Protocol!UL\" value=\"enabled\"!&? CHECKED\r\r>enabled\n\
<input type=\"radio\" name=\"ServiceHttp2Protocol!UL\" value=\"disabled\"!&? CHECKED\r\r>disabled\n\
</td></tr>\n";

   static char  ReviseSslServiceFao [] =
"<tr><td style=\"height:0.5em;\"></td></tr>\n\
\
<tr><th>SSL Version:</th><td>\
<input type=\"hidden\" name=\"hidden$lf\" value=\"&#94;[ServiceSSLversion]&#32;&#32;\">\n\
<input type=\"text\" size=\"40\" name=\"ServiceSSLversion!UL\" value=\"!AZ\">\n\
</td></tr>\n\
<tr><th>SSL Options:</th><td>\
<input type=\"hidden\" name=\"hidden$lf\" value=\"&#94;[ServiceSSLoptions]&#32;&#32;\">\n\
<input type=\"text\" size=\"40\" name=\"ServiceSSLoptions!UL\" value=\"!AZ\">\n\
</td></tr>\n\
<tr><th>SSL Cipher List:</th><td>\
<input type=\"hidden\" name=\"hidden$lf\" value=\"&#94;[ServiceSSLcipherList]&#32;&#32;\">\n\
<input type=\"text\" size=\"40\" name=\"ServiceSSLcipherList!UL\" value=\"!AZ\">\n\
</td></tr>\n\
<tr><th>SSL Cipher Suites:</th><td>\
<input type=\"hidden\" name=\"hidden$lf\" value=\"&#94;[ServiceSSLcipherSuites]&#32;&#32;\">\n\
<input type=\"text\" size=\"40\" name=\"ServiceSSLcipherSuites!UL\" value=\"!AZ\">\n\
</td></tr>\n\
<tr><th>SSL Certificate:</th><td>\
<input type=\"hidden\" name=\"hidden$lf\" value=\"&#94;[ServiceSSLcert]&#32;&#32;\">\n\
<input type=\"text\" size=\"40\" name=\"ServiceSSLcert!UL\" value=\"!AZ\">\n\
</td></tr>\n\
<tr><th>SSL Private Key:</th><td>\
<input type=\"hidden\" name=\"hidden$lf\" value=\"&#94;[ServiceSSLkey]&#32;&#32;\">\n\
<input type=\"text\" size=\"40\" name=\"ServiceSSLkey!UL\" value=\"!AZ\">\n\
</td></tr>\n\
<tr><th>SSL Session Lifetime:</th><td>\
<input type=\"hidden\" name=\"hidden$lf\" value=\"&#94;[ServiceSSLsessionLifetime]&#32;&#32;\">\n\
<input type=\"text\" size=\"7\" name=\"ServiceSSLsessionLifetime!UL\" value=\"!AZ\">\n\
 <i>(hh:mm:ss)</i></td></tr>\n\
<tr><th>SSL Verify Peer:</th><td>\
<input type=\"hidden\" name=\"hidden$lf\" value=\"&#94;[ServiceSSLVerifyPeer]&#32;&#32;\">\n\
<input type=\"radio\" name=\"ServiceSSLVerifyPeer!UL\" value=\"enabled\"!&? CHECKED\r\r>enabled\n\
<input type=\"radio\" name=\"ServiceSSLVerifyPeer!UL\" value=\"optional\"!&? CHECKED\r\r>optional\n\
<input type=\"radio\" name=\"ServiceSSLVerifyPeer!UL\" value=\"disabled\"!&? CHECKED\r\r>disabled\n\
</td></tr>\n\
<tr><th>SSL Verify Peer Data Max:</th><td>\
<input type=\"hidden\" name=\"hidden$lf\" value=\"&#94;[ServiceSSLVerifyPeerDataMax]&#32;&#32;\">\n\
<input type=\"text\" size=\"5\" name=\"ServiceSSLVerifyPeerDataMax!UL\" value=\"!UL\">\n\
 <i>(kBytes)</i></td></tr>\n\
<tr><th>SSL Verify Peer CA File:</th><td>\
<input type=\"hidden\" name=\"hidden$lf\" value=\"&#94;[ServiceSSLVerifyPeerCaFile]&#32;&#32;\">\n\
<input type=\"text\" size=\"40\" name=\"ServiceSSLVerifyPeerCaFile!UL\" value=\"!AZ\">\n\
</td></tr>\n\
<tr><th>SSL Verify Peer Depth:</th><td>\
<input type=\"hidden\" name=\"hidden$lf\" value=\"&#94;[ServiceVerifyPeerDepth]&#32;&#32;\">\n\
<input type=\"text\" size=\"5\" name=\"ServiceVerifyPeerDepth!UL\" value=\"!UL\">\n\
</td></tr>\n\
<tr><th>Strict Transport Security:</th><td>\
<input type=\"hidden\" name=\"hidden$lf\" value=\"&#94;[ServiceSSLstrictTransSec]&#32;&#32;\">\n\
<input type=\"text\" size=\"40\" name=\"ServiceSSLstrictTransSec!UL\" value=\"!UL\">\n\
</td></tr>\n\
<tr><th>Non-SSL Redirect:</th><td>\
<input type=\"hidden\" name=\"hidden$lf\" value=\"&#94;[ServiceNonSSLRedirect]&#32;&#32;\">\n\
<input type=\"text\" size=\"40\" name=\"ServiceNonSSLRedirect!UL\" value=\"!AZ\">\n\
</td></tr>\n";

   static char  ReviseProxyFao [] =
"<tr><td style=\"height:0.5em;\"></td></tr>\n\
\
<tr><th>Proxy:</th><td>\
<input type=\"hidden\" name=\"hidden$lf\" value=\"&#94;[ServiceProxy]&#32;&#32;\">\n\
<input type=\"radio\" name=\"ServiceProxy!UL\" value=\"enabled\"!&? CHECKED\r\r>enabled\n\
<input type=\"radio\" name=\"ServiceProxy!UL\" value=\"disabled\"!&? CHECKED\r\r>disabled\n\
</td></tr>\n";

   static char  ReviseProxyDetailsFao [] =
"<tr><th>Proxy Tunnel:</th><td>\
<input type=\"hidden\" name=\"hidden$lf\" value=\"&#94;[ServiceProxyTunnel]&#32;&#32;\">\n\
<input type=\"radio\" name=\"ServiceProxyTunnel!UL\" value=\"none\"!&? CHECKED\r\r>none\n\
<input type=\"radio\" name=\"ServiceProxyTunnel!UL\" value=\"CONNECT\"!&? CHECKED\r\r>CONNECT\n\
<input type=\"radio\" name=\"ServiceProxyTunnel!UL\" value=\"FIREWALL\"!&? CHECKED\r\r>FIREWALL\n\
<input type=\"radio\" name=\"ServiceProxyTunnel!UL\" value=\"RAW\"!&? CHECKED\r\r>RAW\n\
</td></tr>\n\
\
<tr><th>Proxy Bind IP Address:</th><td>\
<input type=\"hidden\" name=\"hidden$lf\" value=\"&#94;[ServiceProxyBind]&#32;&#32;\">\n\
<input type=\"text\" size=\"20\" name=\"ServiceProxyBind!UL\" value=\"!AZ\">\n\
&nbsp;<i>(only if NI multi-homed)</i></td></tr>\n\
\
<tr><th>Proxy Affinity:</th><td>\
<input type=\"hidden\" name=\"hidden$lf\" value=\"&#94;[ServiceProxyAffinity]&#32;&#32;\">\n\
<input type=\"radio\" name=\"ServiceProxyAffinity!UL\" value=\"enabled\"!&? CHECKED\r\r>enabled\n\
<input type=\"radio\" name=\"ServiceProxyAffinity!UL\" value=\"disabled\"!&? CHECKED\r\r>disabled\n\
</td></tr>\n\
\
<tr><th>Proxy Authorization:</th><td>\
<input type=\"hidden\" name=\"hidden$lf\" value=\"&#94;[ServiceProxyAuth]&#32;&#32;\">\n\
<input type=\"radio\" name=\"ServiceProxyAuth!UL\" value=\"none\"!&? CHECKED\r\r>none\n\
<input type=\"radio\" name=\"ServiceProxyAuth!UL\" value=\"PROXY\"!&? CHECKED\r\r>PROXY\n\
<input type=\"radio\" name=\"ServiceProxyAuth!UL\" value=\"CHAIN\"!&? CHECKED\r\r>CHAIN\n\
<input type=\"radio\" name=\"ServiceProxyAuth!UL\" value=\"LOCAL\"!&? CHECKED\r\r>LOCAL\n\
</td></tr>\n\
\
<tr><th>Proxy Chain:</th><td>\
<input type=\"hidden\" name=\"hidden$lf\" value=\"&#94;[ServiceProxyChain]&#32;&#32;\">\n\
<input type=\"text\" size=\"40\" name=\"ServiceProxyChain!UL\" value=\"!AZ\">\n\
</td></tr>\n\
\
<tr><th>Proxy Chain Cred:</th><td>\
<input type=\"hidden\" name=\"hidden$lf\" value=\"&#94;[ServiceProxyChainCred]&#32;&#32;\">\n\
<input type=\"text\" size=\"40\" name=\"ServiceProxyChainCred!UL\" value=\"!AZ\">\
</td></tr>\n\
\
<tr><th>Proxy Rework Max:</th><td>\
<input type=\"hidden\" name=\"hidden$lf\" value=\"&#94;[ServiceProxyReworkMax]&#32;&#32;\">\n\
<input type=\"text\" size=\"5\" name=\"ServiceProxyReworMax!UL\" value=\"!UL\">\n\
 <i>(kBytes)</i></td></tr>\n";

   static char  ReviseSslClientFao [] =
"<tr><td style=\"height:0.5em;\"></td></tr>\n\
\
<tr><th>SSL/Client:</th><td>\
<input type=\"hidden\" name=\"hidden$lf\" value=\"&#94;[ServiceClientSSL]&#32;&#32;\">\n\
<input type=\"radio\" name=\"ServiceClientSSL!UL\" value=\"enabled\"!&? CHECKED\r\r>enabled\n\
<input type=\"radio\" name=\"ServiceClientSSL!UL\" value=\"disabled\"!&? CHECKED\r\r>disabled\n\
</td></tr>\n";

   static char  ReviseSslClientDetailsFao [] =
"<tr><th>SSL/Client Version:</th><td>\
<input type=\"hidden\" name=\"hidden$lf\" value=\"&#94;[ServiceClientSSLversion]&#32;&#32;\">\n\
<input type=\"text\" size=\"40\" name=\"ServiceSSLversion!UL\" value=\"!AZ\">\n\
</td></tr>\n\
<tr><th>SSL Certificate:</th><td>\
<input type=\"hidden\" name=\"hidden$lf\" value=\"&#94;[ServiceSSLcert]&#32;&#32;\">\n\
<input type=\"text\" size=\"40\" name=\"ServiceSSLcert!UL\" value=\"!AZ\">\n\
</td></tr>\n\
<tr><th>SSL Private Key:</th><td>\
<input type=\"hidden\" name=\"hidden$lf\" value=\"&#94;[ServiceSSLkey]&#32;&#32;\">\n\
<input type=\"text\" size=\"40\" name=\"ServiceSSLkey!UL\" value=\"!AZ\">\n\
</td></tr>\n\
<tr><th>SSL/Client Verify CA:</th><td>\
<input type=\"hidden\" name=\"hidden$lf\" value=\"&#94;[ServiceClientSSLverifyCA]&#32;&#32;\">\n\
<input type=\"radio\" name=\"ServiceClientSSLverifyCA!UL\" value=\"enabled\"!&? CHECKED\r\r>enabled\n\
<input type=\"radio\" name=\"ServiceClientSSLverifyCA!UL\" value=\"disabled\"!&? CHECKED\r\r>disabled\n\
</td></tr>\n\
<tr><th>SSL/Client Verify CA File:</th><td>\
<input type=\"hidden\" name=\"hidden$lf\" value=\"&#94;[ServiceClientSSLCaFile]&#32;&#32;\">\n\
<input type=\"text\" size=\"40\" name=\"ServiceClientSSLCaFile!UL\" value=\"!AZ\">\n\
</td></tr>\n\
<tr><th>SSL/Client Cipher List:</th><td>\
<input type=\"hidden\" name=\"hidden$lf\" value=\"&#94;[ServiceClientSSLcipherList]&#32;&#32;\">\n\
<input type=\"text\" size=\"40\" name=\"ServiceClientSSLcipherList!UL\" value=\"!AZ\">\n\
</td></tr>\n\
<tr><th>SSL/Client Cipher Suites:</th><td>\
<input type=\"hidden\" name=\"hidden$lf\" value=\"&#94;[ServiceClientSSLcipherSuites]&#32;&#32;\">\n\
<input type=\"text\" size=\"40\" name=\"ServiceClientSSLcipherSuites!UL\" value=\"!AZ\">\n\
</td></tr>\n";

   static char  ReviseOtherFao [] =
"<tr><td style=\"height:0.5em;\"></td></tr>\n\
\
<tr><th>RawSocket:</th><td>\
<input type=\"hidden\" name=\"hidden$lf\" value=\"&#94;[ServiceRawSocket]&#32;&#32;\">\n\
<input type=\"radio\" name=\"ServiceRawSocket!UL\" value=\"enabled\"!&? CHECKED\r\r>enabled\n\
<input type=\"radio\" name=\"ServiceRawSocket!UL\" value=\"disabled\"!&? CHECKED\r\r>disabled\n\
</td></tr>\n\
\
<tr><th>(On) Connect:</th><td>\
<input type=\"hidden\" name=\"hidden$lf\" value=\"&#94;[ServiceConnect]&#32;&#32;\">\n\
<input type=\"radio\" name=\"ServiceConnect!UL\" value=\"enabled\"!&? CHECKED\r\r>enabled\n\
<input type=\"radio\" name=\"ServiceConnect!UL\" value=\"disabled\"!&? CHECKED\r\r>disabled\n\
</td></tr>\n\
\
<tr><th>Share SSH:</th><td>\
<input type=\"hidden\" name=\"hidden$lf\" value=\"&#94;[ServiceShareSSH]&#32;&#32;\">\n\
<input type=\"text\" size=\"5\" name=\"ServiceShareSSH!UL\" value=\"!UL\">\n\
</td></tr>\n\
\
<tr><th>Admin:</th><td>\
<input type=\"hidden\" name=\"hidden$lf\" value=\"&#94;[ServiceAdmin]&#32;&#32;\">\n\
<input type=\"radio\" name=\"ServiceAdmin!UL\" value=\"enabled\"!&? CHECKED\r\r>enabled\n\
<input type=\"radio\" name=\"ServiceAdmin!UL\" value=\"disabled\"!&? CHECKED\r\r>disabled\n\
</td></tr>\n\
\
<tr><th>NoLog:</th><td>\
<input type=\"hidden\" name=\"hidden$lf\" value=\"&#94;[ServiceNoLog]&#32;&#32;\">\n\
<input type=\"radio\" name=\"ServiceNoLog!UL\" value=\"enabled\"!&? CHECKED\r\r>enabled\n\
<input type=\"radio\" name=\"ServiceNoLog!UL\" value=\"disabled\"!&? CHECKED\r\r>disabled\n\
</td></tr>\n\
\
<tr><th>Log Format:</th><td colspan=\"2\">\n\
<input type=\"hidden\" name=\"hidden$lf\" value=\"&#94;[ServiceLogFormat]&#32;&#32;\">\n\
<input type=\"text\" size=\"40\" name=\"ServiceLogFormat!UL\" value=\"!AZ\">\n\
</td></tr>\n\
\
<tr><th>Body Tag:</th><td colspan=\"2\">\n\
<input type=\"hidden\" name=\"hidden$lf\" value=\"&#94;[ServiceBodyTag]&#32;&#32;\">\n\
<input type=\"text\" size=\"40\" name=\"ServiceBodyTag!UL\" value=\"!AZ\">\n\
</td></tr>\n\
\
<tr><th>Error Report Path:</th><td colspan=\"2\">\n\
<input type=\"hidden\" name=\"hidden$lf\" value=\"&#94;[ServiceErrorReportPath]&#32;&#32;\">\n\
<input type=\"text\" size=\"40\" name=\"ServiceErrorReportPath!UL\" value=\"!&@\">\n\
</td></tr>\n\
\
</table>\n\
</td></tr>\n\
</table>\n\
<input type=\"hidden\" name=\"hidden$lf\" value=\"&#94;\">\n";

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

   int  count, idx, status;
   unsigned long  *vecptr;
   unsigned long  FaoVector [128];
   char  *cptr, *sptr, *zptr;
   SERVICE_META  *smptr;
   SERVICE_STRUCT  *svptr;
   SESOLA_CONTEXT  *scptr, *ssptr;

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

   if (WATCHMOD (rqptr, WATCH_MOD_SERVICE))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_SERVICE, "ServiceConfigReviseNow()");

   /* get a pointer to the meta-config data */
   smptr = mcptr->ServiceMetaPtr;

   AdminPageTitle (rqptr, "Service Configuration");
   AdminMetaConReport (rqptr, mcptr, MetaGlobalServicePtr);

   if (ServiceConfigFileDefined)
   {
      AdminMetaConSource (rqptr, mcptr, MetaGlobalServicePtr);
      AdminMetaConBeginUpdateForm (rqptr);
   }
   else
   {
      status = FaoToNet (rqptr, "<form method=\"POST\" action=\"\">");
      if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI);
   }

   /************/
   /* services */
   /************/

   if (!LIST_GET_HEAD(smptr->ServiceListPtr))
   {
      status = FaolToNet (rqptr, ReviseServiceNoneFao, NULL);
      if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI);
   }
   else
   {
      count = 0;
      LIST_ITERATE (svptr, smptr->ServiceListPtr)
      {
         count++;

         vecptr = FaoVector;

         /* place holder for the "next new service" comment (see below) */
         *vecptr++ = "";

         *vecptr++ = "!UL&nbsp;&nbsp;-&nbsp;&nbsp;";
         *vecptr++ = count;

         *vecptr++ = svptr->RequestSchemeNamePtr;
         *vecptr++ = "//";
         *vecptr++ = svptr->ServerHostPort;

         *vecptr++ = count;
         *vecptr++ = svptr->RequestScheme == SCHEME_HTTP;
         *vecptr++ = count;
         *vecptr++ = svptr->RequestScheme == SCHEME_HTTPS;
         *vecptr++ = count;
         if (svptr->GenericService)
            *vecptr++ = "*";
         else
            *vecptr++ = svptr->ServerHostName;
         *vecptr++ = count;
         *vecptr++ = svptr->ServerPortString;
         *vecptr++ = count;
         *vecptr++ = svptr->BindIpAddressString;

         FaoCheck (sizeof(FaoVector), &FaoVector, vecptr, FI_LI);
         status = FaolToNet (rqptr, ReviseServiceFao, &FaoVector);
         if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI);

         vecptr = FaoVector;
         *vecptr++ = count;
         *vecptr++ = svptr->Http2Enabled;
         *vecptr++ = count;
         *vecptr++ = !svptr->Http2Enabled;
         status = FaolToNet (rqptr, ReviseHttp2Fao, &FaoVector);
         if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI);

         if (ssptr = svptr->SSLserverPtr)
         {
            vecptr = FaoVector;

            *vecptr++ = count;
            *vecptr++ = ssptr->VersionString;
            *vecptr++ = count;
            *vecptr++ = ssptr->OptionsString;
            *vecptr++ = count;
            *vecptr++ = ssptr->CipherList;
            *vecptr++ = count;
            *vecptr++ = ssptr->CipherSuites;
            *vecptr++ = count;
            *vecptr++ = ssptr->CertFile;
            *vecptr++ = count;
            *vecptr++ = ssptr->KeyFile;
            *vecptr++ = count;
            *vecptr++ = MetaConShowSeconds (rqptr, ssptr->SessionLifetime);
            *vecptr++ = count;
            *vecptr++ = ssptr->VerifyPeer == SESOLA_VERIFY_PEER_REQUIRED;
            *vecptr++ = count;
            *vecptr++ = ssptr->VerifyPeer == SESOLA_VERIFY_PEER_OPTIONAL;
            *vecptr++ = count;
            *vecptr++ = !ssptr->VerifyPeer;
            *vecptr++ = count;
            *vecptr++ = ssptr->VerifyDataMax;
            *vecptr++ = count;
            *vecptr++ = ssptr->CaFile;
            *vecptr++ = count;
            *vecptr++ = ssptr->VerifyDepth;
            *vecptr++ = count;
            *vecptr++ = ssptr->StrictTransSec;
            *vecptr++ = count;
            *vecptr++ = svptr->NonSslRedirect;

            FaoCheck (sizeof(FaoVector), &FaoVector, vecptr, FI_LI);
            status = FaolToNet (rqptr, ReviseSslServiceFao, &FaoVector);
            if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI);
         }

         vecptr = FaoVector;
         *vecptr++ = count;
         *vecptr++ = svptr->ProxyService;
         *vecptr++ = count;
         *vecptr++ = !svptr->ProxyService;
         status = FaolToNet (rqptr, ReviseProxyFao, &FaoVector);
         if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI);

         if (svptr->ProxyService)
         {
            vecptr = FaoVector;
            *vecptr++ = count;
            *vecptr++ = !svptr->ProxyTunnel;
            *vecptr++ = count;
            *vecptr++ = (svptr->ProxyTunnel == PROXY_TUNNEL_CONNECT);
            *vecptr++ = count;
            *vecptr++ = (svptr->ProxyTunnel == PROXY_TUNNEL_FIREWALL);
            *vecptr++ = count;
            *vecptr++ = (svptr->ProxyTunnel == PROXY_TUNNEL_RAW);
            *vecptr++ = count;
            *vecptr++ = svptr->ProxyBindIpAddressString;
            *vecptr++ = count;
            *vecptr++ = svptr->ProxyAffinity;
            *vecptr++ = count;
            *vecptr++ = !svptr->ProxyAffinity;
            *vecptr++ = count;
            *vecptr++ = !(svptr->ProxyAuthRequired ||
                          svptr->ProxyChainAuthRequired ||
                          svptr->LocalAuthRequired);
            *vecptr++ = count;
            *vecptr++ = svptr->ProxyAuthRequired;
            *vecptr++ = count;
            *vecptr++ = svptr->ProxyChainAuthRequired;
            *vecptr++ = count;
            *vecptr++ = svptr->LocalAuthRequired;
            *vecptr++ = count;
            *vecptr++ = svptr->ProxyChainHostPort;
            *vecptr++ = count;
            *vecptr++ = svptr->ProxyChainCred;
            *vecptr++ = svptr->ProxyReworkMax;

            FaoCheck (sizeof(FaoVector), &FaoVector, vecptr, FI_LI);
            status = FaolToNet (rqptr, ReviseProxyDetailsFao, &FaoVector);
            if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI);

            vecptr = FaoVector;
            *vecptr++ = count;
            *vecptr++ = svptr->SSLclientEnabled;
            *vecptr++ = count;
            *vecptr++ = !svptr->SSLclientEnabled;
            status = FaolToNet (rqptr, ReviseSslClientFao, &FaoVector);
            if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI);

            if (scptr = svptr->SSLclientPtr)
            {
               vecptr = FaoVector;

               *vecptr++ = count;
               *vecptr++ = scptr->VersionString;
               *vecptr++ = count;
               *vecptr++ = scptr->CertFile;
               *vecptr++ = count;
               *vecptr++ = scptr->KeyFile;
               *vecptr++ = count;
               *vecptr++ = scptr->VerifyCA;
               *vecptr++ = count;
               *vecptr++ = !scptr->VerifyCA;
               *vecptr++ = count;
               *vecptr++ = scptr->CaFile;
               *vecptr++ = count;
               *vecptr++ = scptr->CipherList;
               *vecptr++ = count;
               *vecptr++ = scptr->CipherSuites;

               FaoCheck (sizeof(FaoVector), &FaoVector, vecptr, FI_LI);
               status = FaolToNet (rqptr, ReviseSslClientDetailsFao,
                                   &FaoVector);
               if (VMSnok (status))
                  ErrorNoticed (rqptr, status, NULL, FI_LI);
            }
         }

         vecptr = FaoVector;

         *vecptr++ = count;
         *vecptr++ = svptr->RawSocket;
         *vecptr++ = count;
         *vecptr++ = !svptr->RawSocket;
         *vecptr++ = count;
         *vecptr++ = svptr->ConnectService;
         *vecptr++ = count;
         *vecptr++ = !svptr->ConnectService;
         *vecptr++ = count;
         *vecptr++ = svptr->ShareSSH;
         *vecptr++ = count;
         *vecptr++ = svptr->AdminService;
         *vecptr++ = count;
         *vecptr++ = !svptr->AdminService;
         *vecptr++ = count;
         *vecptr++ = svptr->NoLog;
         *vecptr++ = count;
         *vecptr++ = !svptr->NoLog;
         *vecptr++ = count;
         *vecptr++ = svptr->LogFormat;
         *vecptr++ = count;
         *vecptr++ = svptr->BodyTag;
         *vecptr++ = count;
         if (svptr->ServiceErrorReportPath)
         {
            *vecptr++ = "!AZ !AZ";
            *vecptr++ = svptr->ErrorReportPath;
            *vecptr++ = svptr->ErrorReportPathCodesPtr;
         }
         else
            *vecptr++ = "";

         FaoCheck (sizeof(FaoVector), &FaoVector, vecptr, FI_LI);
         status = FaolToNet (rqptr, ReviseOtherFao, &FaoVector);
         if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI);
      }
   }

   /***********************/
   /* "blank" new service */
   /***********************/

   count++;

   vecptr = FaoVector;
   *vecptr++ =
"<input type=\"hidden\" name=\"hidden$lf\" value=\"\
&#32;&#94;# place-holder for the next new service\">\n";
   *vecptr++ = "";
   *vecptr++ = "- Next New Service -";
   *vecptr++ = "";
   *vecptr++ = "";
   *vecptr++ = count;
   *vecptr++ = true;
   *vecptr++ = count;
   *vecptr++ = false;
   *vecptr++ = count;
   *vecptr++ = "?.?.?.?";
   *vecptr++ = count;
   *vecptr++ = "80";
   *vecptr++ = count;
   *vecptr++ = "";

   FaoCheck (sizeof(FaoVector), &FaoVector, vecptr, FI_LI);
   status = FaolToNet (rqptr, ReviseServiceFao, &FaoVector);
   if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI);

   vecptr = FaoVector;
   *vecptr++ = count;
   *vecptr++ = false;
   *vecptr++ = count;
   *vecptr++ = true;
   status = FaolToNet (rqptr, ReviseProxyFao, &FaoVector);
   if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI);

   vecptr = FaoVector;
   *vecptr++ = count;
   *vecptr++ = false;
   *vecptr++ = count;
   *vecptr++ = true;
   *vecptr++ = count;
   *vecptr++ = 0;
   *vecptr++ = count;
   *vecptr++ = false;
   *vecptr++ = count;
   *vecptr++ = true;
   *vecptr++ = count;
   *vecptr++ = false;
   *vecptr++ = count;
   *vecptr++ = true;
   *vecptr++ = count;
   *vecptr++ = false;
   *vecptr++ = count;
   *vecptr++ = "";
   *vecptr++ = count;
   *vecptr++ = "";

   FaoCheck (sizeof(FaoVector), &FaoVector, vecptr, FI_LI);
   status = FaolToNet (rqptr, ReviseOtherFao, &FaoVector);
   if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI);

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

   if (ServiceConfigFileDefined)
      AdminMetaConEndUpdateForm (rqptr);
   else
      status = FaolToNet (rqptr, "</form>\n", NULL);
   status = FaolToNet (rqptr, "</body>\n</html>\n", NULL);
   if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI);

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

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