/*****************************************************************************/
/*
                                Logging.c

Logging functions for the WASD VMS HTTPd. By default, produces a Web "common"
format log suitable for analysis by tools capable of processing that format.
Secretes four pseudo-requests logging the BEGINing, OPENing, CLOSEing and
ENDing of logging activities. These shouldn't introduce problems for analysis
tools as they look like legitimate POST entries for user "HTTPd"!

The [LogFormat] directive generates logs in the "common+server", "combined" and
a user-defined format.  If a user-defined format contains an error the server
will exit the first time a request is logged.  Caveat emptor!

Log file name must be supplied via [LogFile] or the logical name HTTPD$LOG
(process, job, or system level).  An error is reporting if logging is enabled
without either of these being supplied.  If the log file name specified is
"SYS$OUTPUT" log entries are written to SYS$OUTPUT.  This is useful for
checking user-defined log entries before committing files to that format.

The [LogNaming] format can be any of "NAME" (default) which names the log file
using the first period-delimited component of the IP host name, "HOST" which
uses as much of the IP host name as can be accomodated within the maximum 39
character filename limitation (of ODS-2), or "ADDRESS" which uses the full IP
host address in the name.  Both HOST and ADDRESS have hyphens substituted for
periods in the string.  If these are specified then by default the service port
follows the host name component.  This may be suppressed using the
[LogPerServiceHostOnly] directive, allowing a minimum extra 3 characters in the
name, and combining entries for all ports associated with the host name.

The log file can have a [LogPeriod] associated with it.  This period determines
when a new log file is created.  The periods are daily, weekly and monthly.
A daily period is indicated by DAILY, a weekly period by MONDAY, TUESDAY,
WEDNESDAY, THURSDAY, FRIDAY, SATURDAY and SUNDAY (the day of the week on
which, at midnight or the first request thereafter, the log is recreated), and
finally a monthly recreation by MONTHLY.  The HTTPD$LOG logical is then used
merely for the directory component and the file name is fixed at
HOST_YYYYMMDD_ACCESS.LOG, where host is the first dot-separated component of
the primary IP host name and YYYY, MM, DD is the year, month and day the log
was created.

When multiple instances, either on the one node or across a cluster, are
writing to the same log file (multi-stream), then RMS insists on automatically
flushing each log record write (considering the issues with maintaining 'dirty'
buffers, perhaps cluster-wide, this is not unreasonable).  This does increase
the physical disk I/O significantly though, and of course reduces server
performance equally (or more so) as a result.  The simple (and effective)
solution to this is to have only one stream writing to a log file at the one
time.  This is enabled using the [LogPerInstance] configuration directive. 
With this active each log file has the node name and then the instance process
name appended to the .LOG file type resulting in names similar to (for a two
instance single node)

  10-168-0-3_80_20020527_ACCESS.LOG_KLAATU_HTTPD-80
  10-168-0-3_80_20020527_ACCESS.LOG_KLAATU_HTTPE-80
  10-168-0-3_443_20020527_ACCESS.LOG_KLAATU_HTTPD-80
  10-168-0-3_443_20020527_ACCESS.LOG_KLAATU_HTTPE-80

Of course these need to be integrated at some stage, either using one of the
many log file analysis tools, or a specific utility such as WASD's CALOG.C
(Consolidate Access LOGs, pronounced as with the breakfast cereals).


COMMON FORMAT
-------------
(NCSA, CERN, etc.)

'client rfc1413 auth-user date/time request status bytes'


COMBINED FORMAT
---------------
(NCSA)

'client rfc1413 auth-user date/time request status bytes referer user-agent'


COMMON FORMAT plus server name
-------------
(NCSA, CERN, etc.)

'client rfc1413 auth-user date/time request status bytes server'


USER-DEFINED FORMAT
-------------------

Must begin with a character that is used as a substitute when a particular
field is empty (use "\0" for no substitute, as in the "windows log format"
example below).  Two different "escape" characters introduce the following
parameters of characters.

A '!' followed by ...

  'AR' :  authentication realm
  'AU' :  authenticated user name
  'BB' :  bytes in body (excluding response header)
  'BY' :  bytes transmitted to the client
  'CA' :  client numeric address
  'CC' :  X509 client certificate authorization distinguishing name
  'CN' :  client host name (or address if DNS lookup disabled)
  'CI' :  cache involvement (0 == none, 1 == could have but didn't, 2 == did!)
  'EM' :  request elapsed time in milliseconds
  'ES' :  request elapsed time in seconds
  'EU' :  request elapsed time in microseconds
  'ID' :  session track ID
  'ME' :  request method
  'PA' :  request path (NOTE ... this is not the full request path!  See 'R')
  'PR' :  HTTP protocol (e.g. "HTTP/1.0")
  'QS' :  request query string
  'RF' :  referer
  'RQ' :  FULL request path (including script and any query string)
  'RS' :  response status code
  'SC' :  script name
  'SM' :  request scheme name (i.e. "http:" or "https:") 
  'SN' :  server host name
  'SP' :  server port
  'TC' :  request time (common log format)
  'TG' :  request time (GMT)
  'TV' :  request time (VMS format)
  'UA' :  user agent

A '\' followed by ...

  '0' :  a null character (used to define the empty field character)
  '!' :  insert an '!'
  '\' :  insert a '\'
  'n' :  insert a newline
  'q' :  insert a quote (so that in the DCL the quotes won't need escaping!)
  't' :  insert a TAB

Any other character is directly inserted into the log entry.


Examples
--------

The equivalent of the common log format is:

  "-!CN !ID !AU [!TC] \q!RQ\q !RS !BY"

The combined log format:

  "-!CN !ID !AU [!TC] \q!RQ\q !RS !BY \q!RF\q \q!UA\q"

The WebSite "windows log format" would be created by:

  "\0!TC\t!CA\t!SN\t!AR\t!AU\t!ME\t!PA\t!RQ\t!EM\t!UA\t!RS\t!BB\t"

To get the common log format plus append request duration in seconds:

  "-!CN !ID !AU [!TC] \q!RQ\q !RS !BY !ES"


VERSION HISTORY
---------------
04-MAR-2004  MGD  timestamp entry, server account username
28-JAN-2004  MGD  LoggingReport(),
                  add HTTP protocol to timestamp and pseudo entries
28-JUN-2003  MGD  bugfix; add HTTP protocol to combined/common format URL
                  'PR' same datum with user format (jf.pieronne@laposte.fr)
08-APR-2003  MGD  timestamp and pseudo entry formats, make bytes numeric and
                  leading slash before server POST paths (e.g. "/KLAATU::..")
                  (Analog could complain about both of these),
                  add the server software ID as the user-agent field
26-MAY-2002  MGD  logging per-instance
21-DEC-2001  MGD  bugfix; 07-NOV-2001 for ALL services :^}
07-NOV-2001  MGD  bugfix; close current log file if period changes
25-OCT-2001  MGD  timestamp log file(s) every hour,
                  FAB$M_TEF to deallocate unused file space
04-AUG-2001  MGD  support module WATCHing
04-MAY-2001  MGD  increased LoggingDo() 'String' size from 2048 TO 4096
20-MAR-2001  MGD  added FAB$M_DFW, w_deq=nnn, b_mbc=127,
                  improves log performance by 500%!! (jfp@altavista.net)
15-DEC-2000  MGD  client certificate authorization (as rfc1413 & 'CC')
15-OCT-2000  MGD  correct time string TZ (GMT format)
07-MAY-2000  MGD  session track ID
16-FEB-2000  MGD  "[LogNaming] HOST", [LogPerServiceHostOnly] processing
12-JUN-1999  MGD  modify WATCH log entry format
22-MAR-1999  MGD  increased LoggingDo() 'String' size from 1024 to 2048
07-NOV-1998  MGD  WATCH facility
27-AUG-1998  MGD  exclude specified hosts from being logged
17-MAY-1998  MGD  add per-service logging
23-NOV-1997  MGD  signal failed log file create/put (e.g. disk quota :^)
25-OCT-1997  MGD  log file period new for v4.5,
                  log file now opened for sharing GET, PUT and UPD
                  added 'CI' (cache-involvement) user-format directive
                  bugfix; user format from configuration file
25-SEP-1997  MGD  bugfix; ACCVIO if request structure exists but empty
                  (assumed if it existed all the pointers were valid - WRONG!)
06-SEP-1997  MGD  added "common+server", "combined", "user-defined" log formats
10-JUL-1997  MGD  minor modification for server-admin logging on/off/flush
10-JUL-1996  MGD  RequestUriPtr instead of ScriptPtr/PathInfoPtr/QueryStringPtr
01-DEC-1995  MGD  HTTPd version 3, "common" log format
27-SEP-1995  MGD  use time values in request thread structure
16-JUN-1995  MGD  added node name, changed logging fields to be space-separated
07-APR-1995  MGD  initial development for addition to multi-threaded daemon
*/
/*****************************************************************************/

#ifdef WASD_VMS_V6
#undef _VMS_V6_SOURCE
#define _VMS_V6_SOURCE
#undef __VMS_VER
#define __VMS_VER 60000000
#undef __CRTL_VER
#define __CRTL_VER 60000000
#endif

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

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

/* application header files */
#include "wasd.h"
#include "httpd.h"
#include "error.h"
#include "logging.h"

#define WASD_MODULE "LOGGING"

#define LOGGING_NAMING_NAME 1
#define LOGGING_NAMING_HOST 2
#define LOGGING_NAMING_ADDRESS 3

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

char  ErrorLoggingFormatLength [] = "log format too long";

int  LoggingDayWeekBegins;

BOOL  LoggingEnabled,
      LoggingFormatCommon,
      LoggingFormatCommonServer,
      LoggingFormatCombined,
      LoggingNaming,
      LoggingPerPeriod,
      LoggingPerService,
      LoggingPeriodDaily,
      LoggingPeriodWeekly,
      LoggingPeriodMonthly,
      LoggingSysOutput;

     
int  LoggingFileNameLength,
     LoggingNaming,
     LoggingTimeStampCount;

char  LoggingFileName [256],
      LoggingFormatUser [128],
      LoggingPeriodName [128];

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

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

extern BOOL  CliLoggingDisabled,
             CliLoggingEnabled;

extern int  ExitStatus,
            HttpdDayOfWeek,
            HttpdTickSecond,
            ServerPort;

extern unsigned long  SysPrvMask[];

extern char  CliLoggingPeriod[],
             ErrorSanityCheck[],
             ServerHostName[],
             SoftwareID[],
             TimeGmtString[],
             Utility[];

extern CONFIG_STRUCT  Config;
extern HTTPD_PROCESS  HttpdProcess;
extern SERVICE_STRUCT  *ServiceListHead;
extern SYS_INFO  SysInfo;
extern WATCH_STRUCT  Watch;

/*****************************************************************************/
/*
Parameter 'Function' can be:

LOGGING_BEGIN           server startup
LOGGING_OPEN            open server logging, other than server startup
LOGGING_CLOSE           close server logging, other than server shutdown
LOGGING_END             server shutdown
LOGGING_ENTRY           log a request
LOGGING_TIMESTAMP       put a timestamp pseudo-entry into the log file
LOGGING_FLUSH           close the log file, flushing the contents

With per-service logging most of these functions must be performed on each
service's log, logging a request entry occurs for the service involved only.
*/ 

int Logging
(
REQUEST_STRUCT *rqptr,
int Function
)
{
   BOOL  PrevLoggingEnabled,
         WatchThisOne;
   int  status;
   char  *cptr, *sptr;
   SERVICE_STRUCT  *svptr;

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

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD__OTHER))
      WatchThis (rqptr, FI_LI, WATCH_MOD__OTHER, "Logging() !UL", Function);

   if (!rqptr && WATCH_CATEGORY(WATCH_LOG))
      WatchThisOne = true;
   else
   if (rqptr)
   {
      if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_LOG))
         WatchThisOne = true;
      else
         WatchThisOne = false;
   }
   else
      WatchThisOne = false;

   switch (Function)
   {
      case LOGGING_ENTRY :

         if (!LoggingEnabled) return (STS$K_ERROR);

         /* if the request path has been mapping-rule set NOlog then don't! */
         if (rqptr && rqptr->rqPathSet.NoLog)
         {
            if (WATCH_CAT && WatchThisOne)
               WatchThis (rqptr, FI_LI, WATCH_LOG,
                          "NOLOG !AZ", rqptr->rqHeader.PathInfoPtr);
            return (SS$_NORMAL);
         }

         if (Config.cfLog.ExcludeHostsPtr &&
             rqptr)
         {
            sptr = Config.cfLog.ExcludeHostsPtr;
            while (*sptr)
            {
               /** if (Debug) fprintf (stdout, "sptr |%s|\n", sptr); **/
               if (isdigit(*sptr))
                  cptr = rqptr->rqClient.IpAddressString;
               else
                  cptr = rqptr->rqClient.Lookup.HostName;
               while (*cptr)
               {
                  if (*sptr == STRING_LIST_CHAR) break;
                  if (*sptr == '*')
                  {
                     while (*sptr && *sptr == '*' &&
                            *sptr != STRING_LIST_CHAR) sptr++;
                     while (*cptr && *cptr != *sptr) cptr++;
                  }
                  if (tolower(*cptr) != tolower(*sptr)) break;
                  if (*cptr) cptr++;
                  if (*sptr) sptr++;
               }
               if (!*cptr && (!*sptr || *sptr == STRING_LIST_CHAR)) break;
               while (*sptr && *sptr != STRING_LIST_CHAR) sptr++;
               if (*sptr) sptr++;
            }
            /* if it was found then return without logging the request */
            if (!*cptr && (!*sptr || *sptr == STRING_LIST_CHAR))
            {
               if (WATCH_CAT && WatchThisOne)
                  WatchThis (rqptr, FI_LI, WATCH_LOG,
                             "EXCLUDE !AZ", rqptr->rqClient.Lookup.HostName);
               return (SS$_NORMAL);
            }
         }

         if (rqptr->ServicePtr->NoLog)
         {
            if (WATCH_CAT && WatchThisOne)
               WatchThis (rqptr, FI_LI, WATCH_LOG,
                          "NOLOG for !AZ//!AZ",
                          rqptr->ServicePtr->RequestSchemeNamePtr,
                          rqptr->ServicePtr->ServerHostPort);
            return (STS$K_ERROR);
         }

         if (LoggingPerService)
            status = LoggingDo (rqptr, rqptr->ServicePtr,
                                Function, WatchThisOne);
         else
            status = LoggingDo (rqptr, ServiceListHead,
                                Function, WatchThisOne);

         return (status);

      case LOGGING_BEGIN :
      case LOGGING_CLOSE :
      case LOGGING_END :
      case LOGGING_FLUSH :
      case LOGGING_OPEN :
      case LOGGING_TIMESTAMP :

         switch (Function)
         {
            case LOGGING_BEGIN :

               if (Config.cfLog.Enabled || CliLoggingEnabled)
                  LoggingEnabled = true;
               if (CliLoggingDisabled) LoggingEnabled = false;

               LoggingPerService = Config.cfLog.PerService ||
                                   Config.cfLog.PerServiceHostOnly;

               /* in log using host part of file name, name or address? */
               LoggingNaming = LOGGING_NAMING_NAME;
               if (Config.cfLog.Naming[0])
               {
                  if (strsame (Config.cfLog.Naming, "NAME", -1))
                     LoggingNaming = LOGGING_NAMING_NAME;
                  else
                  if (strsame (Config.cfLog.Naming, "HOST", -1))
                     LoggingNaming = LOGGING_NAMING_HOST;
                  else
                  if (strsame (Config.cfLog.Naming, "ADDRESS", -1))
                     LoggingNaming = LOGGING_NAMING_ADDRESS;
                  else
                     fprintf (stdout, "%%%s-W-LOG, naming unknown\n \\%s\\\n",
                              Utility, Config.cfLog.Naming);
               }
               break;

            case LOGGING_OPEN :

               if (LoggingEnabled) return (STS$K_ERROR);
               break;

            default :

               if (!LoggingEnabled) return (STS$K_ERROR);
               break;
         }

         PrevLoggingEnabled = LoggingEnabled;

         if (Function == LOGGING_TIMESTAMP) LoggingTimeStampCount++;

         if (LoggingPerService)
         {
            for (svptr = ServiceListHead; svptr; svptr = svptr->NextPtr)
            {
               status = LoggingDo (rqptr, svptr, Function, WatchThisOne);
               if (VMSnok (status)) break;
            }
         }
         else
            status = LoggingDo (rqptr, ServiceListHead, Function, WatchThisOne);

         if (Debug) fprintf (stdout, "LoggingDo() %%X%08.08X\n", status);

         switch (Function)
         {
            case LOGGING_BEGIN :

               if (VMSnok (status)) LoggingEnabled = false;
               break;

            case LOGGING_OPEN :

               if (VMSok (status))
                  LoggingEnabled = true;
               else
                  LoggingEnabled = PrevLoggingEnabled;
               break;

            case LOGGING_CLOSE :
            case LOGGING_END :

               if (VMSok (status))
                  LoggingEnabled = false;
               else
                  LoggingEnabled = PrevLoggingEnabled;
         }

         return (status);

      default :

         ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI);
   }
}

/*****************************************************************************/
/*
What an incomprehensibly long function this has grown to be! :^(

The 'svptr' parameter points to the service structure associated with the log. 
If per-service logging is enabled this will be the appropriate one of however
many services are created.  If per-service logging is not enabled this will
always be the first service created (i.e. the "primary" service).  Where
service information is required for user-format logging it is accessed from the
request structure's service pointer.
*/ 

int LoggingDo
(
REQUEST_STRUCT *rqptr,
SERVICE_STRUCT *svptr,
int Function,
BOOL WatchThisOne
)
{
#  define MAX_FAO_VECTOR 64

   static char  *MonthName [] =
      { "", "Jan", "Feb", "Mar", "Apr", "May", "Jun",
            "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };

   static $DESCRIPTOR (PeriodFileNameFaoDsc,
              "!#AZ!AZ!AZ_!UL!2ZL!2ZL_ACCESS.LOG!AZ!AZ!AZ!AZ");
   static $DESCRIPTOR (ServiceFileNameFaoDsc,
              "!#AZ!AZ!AZ_ACCESS.LOG!AZ!AZ!AZ!AZ");

   static $DESCRIPTOR (BytesFaoDsc, "!UL\0");
   static $DESCRIPTOR (ElapsedResponseDurationDsc, "!UL\0");
   static $DESCRIPTOR (ElapsedSecondsDsc, "!UL.!3ZL\0");
   static $DESCRIPTOR (StatusFaoDsc, "!UL\0");
   static $DESCRIPTOR (VmsTimeFaoDsc, "!%D\0");

   static $DESCRIPTOR (LogFileTimeFaoDsc,
                       "!2ZL/!3AZ/!4ZL:!2ZL:!2ZL:!2ZL !3AZ!2AZ");

   /* server-host - - [date/time] \"what-status\" 200 0 */
   static $DESCRIPTOR (LogFilePseudoFaoDsc,
"!AZ !AZ !AZ [!AZ] \"POST /!AZ::!AZ-!AZ-!8XL HTTP/1.0\" 200 0!AZ!AZ!AZ");

   /* server-host - - [date/time] \"TIMESTAMP-count\" 200 0 */
   static $DESCRIPTOR (LogFileTimeStampFaoDsc,
"!AZ !AZ !AZ [!AZ] \"POST /!AZ::!AZ-!AZ-!8ZL HTTP/1.0\" 200 0!AZ!AZ!AZ");

   /* host r-ident user [date/time] \"request\" status bytes */
   static $DESCRIPTOR (LogFileCommonFaoDsc,
"!AZ !AZ !AZ [!AZ] \"!AZ !AZ!AZ\" !UL !AZ");

   /* as for "common" above, plus ' server'*/
   static $DESCRIPTOR (LogFileCommonServerFaoDsc,
"!AZ !AZ !AZ [!AZ] \"!AZ !AZ!AZ\" !UL !AZ !AZ");

   /* as for "common" above, plus ' referer user-agent'*/
   static $DESCRIPTOR (LogFileCombinedFaoDsc,
"!AZ !AZ !AZ [!AZ] \"!AZ !AZ!AZ\" !UL !AZ \"!AZ\" \"!AZ\"");

   /* user-defined log-file format */
   static $DESCRIPTOR (LogFileUserFaoDsc, "!#(AZ)");

   static $DESCRIPTOR (LnmFileDevDsc, "LNM$FILE_DEV");
   static $DESCRIPTOR (LoggingFileNameLogicalDsc, "HTTPD$LOG");

   static BOOL  UserFormatProblem = false;

   static int  ExtendBlocks = LOGGING_DEFAULT_EXTEND_BLOCKS,
               PreviousDay = 0,
               PreviousMonth = 0,
               PreviousYear = 0;
   static unsigned short  Length,
                          NumericTime [7],
                          WeeklyNumericTime [7];
   static unsigned long  BinaryTime [2];

   static char  BytesString [32],
                Elapsed [32],
                FormatUser [256],
                ProcessName [16],
                StatusString [16],
                String [4096],
                TimeString [32],
                VmsTimeString [32];
   static $DESCRIPTOR (BytesStringDsc, BytesString);
   static $DESCRIPTOR (ElapsedDsc, Elapsed);
   static $DESCRIPTOR (StatusStringDsc, StatusString);
   static $DESCRIPTOR (StringDsc, String);
   static $DESCRIPTOR (TimeStringDsc, TimeString);
   static $DESCRIPTOR (VmsTimeStringDsc, VmsTimeString);

   static struct FAB  LogFileFab;
   static struct RAB  LogFileRab;

   static struct {
      short int  buf_len;
      short int  item;
      unsigned char   *buf_addr;
      unsigned short  *short_ret_len;
   } LnmItems [] =
   {
      { sizeof(LoggingFileName)-1, LNM$_STRING, LoggingFileName, &Length },
      { 0,0,0,0 }
   };

   BOOL  PeriodChanged;
   int  status,
        SetimrStatus;
   unsigned long  *vecptr,
                  *BinaryTimePtr;
   unsigned long  FaoVector [MAX_FAO_VECTOR];
   char  *cptr, *sptr, *zptr,
         *FunctionNamePtr;
   char  MiscChars [MAX_FAO_VECTOR * 2],
         ClientCertSubject [512];

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

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD__OTHER))
      WatchThis (rqptr, FI_LI, WATCH_MOD__OTHER,
                 "LoggingDo() !UL !&X !&X", Function, rqptr, svptr);

   /* logging to SYS$OUTPUT does not do many of the things that set 'status' */
   status = SS$_NORMAL;

   if (Function == LOGGING_BEGIN)
   {
      /*****************/
      /* begin logging */
      /*****************/

      /* if no logging file name from command line then use any config one */
      if (!LoggingFileName[0] && Config.cfLog.FileName[0])
         strcpy (LoggingFileName, Config.cfLog.FileName);

      if (strsame (LoggingFileName, "SYS$OUTPUT", -1))
         LoggingSysOutput = true;
      else
         LoggingSysOutput = false;

      if (Config.cfLog.ExtendBlocks) ExtendBlocks = Config.cfLog.ExtendBlocks;
      if (ExtendBlocks < 0)
         ExtendBlocks = 0; 
      else
      if (ExtendBlocks > 65535)
         ExtendBlocks = 65535; 

      /************************/
      /* determine log format */
      /************************/

      UserFormatProblem = false;
      if (LoggingFormatUser[0])
         strcpy (FormatUser, LoggingFormatUser);
      else
      if (Config.cfLog.Format[0])
         strcpy (FormatUser, Config.cfLog.Format);
      else
         FormatUser[0] = '\0';

      if (FormatUser[0])
      {
         if (strsame (FormatUser, "COMMON", -1))
         {
            LoggingFormatCommonServer = LoggingFormatCombined = false;
            LoggingFormatCommon = true;
            FormatUser[0] = '\0';
         }
         else
         if (strsame (FormatUser, "COMMON_SERVER", -1))
         {
            LoggingFormatCommon = LoggingFormatCombined = false;
            LoggingFormatCommonServer = true;
            FormatUser[0] = '\0';
         }
         else
         if (strsame (FormatUser, "COMBINED", -1))
         {
            LoggingFormatCommon = LoggingFormatCommonServer = false;
            LoggingFormatCombined = true;
            FormatUser[0] = '\0';
         }
         else
         if (FormatUser[0])
         {
            LoggingFormatCommon = LoggingFormatCommonServer =
               LoggingFormatCombined = false;
         }
         else
            LoggingFormatCommon = true;
      }
      else
         LoggingFormatCommon = true;

      /****************************/
      /* determine logging period */
      /****************************/

      LoggingPerPeriod = LoggingPeriodDaily =
         LoggingPeriodWeekly = LoggingPeriodMonthly = false;

      if (!LoggingPeriodName[0] && Config.cfLog.Period[0])
         strcpy (LoggingPeriodName, Config.cfLog.Period);

      if (LoggingPeriodName[0])
      {
         PreviousDay = PreviousMonth = PreviousYear = 0;

         if (strsame (LoggingPeriodName, "DAILY", 3))
            LoggingPerPeriod = LoggingPeriodDaily = true;
         else
         if (strsame (LoggingPeriodName, "SUNDAY", 3))
         {
            LoggingPerPeriod = LoggingPeriodWeekly = true;
            LoggingDayWeekBegins = 7;
         }
         else
         if (strsame (LoggingPeriodName, "MONDAY", 4))
         {
            LoggingPerPeriod = LoggingPeriodWeekly = true;
            LoggingDayWeekBegins = 1;
         }
         else
         if (strsame (LoggingPeriodName, "TUESDAY", 3))
         {
            LoggingPerPeriod = LoggingPeriodWeekly = true;
            LoggingDayWeekBegins = 2;
         }
         else
         if (strsame (LoggingPeriodName, "WEDNESDAY", 3))
         {
            LoggingPerPeriod = LoggingPeriodWeekly = true;
            LoggingDayWeekBegins = 3;
         }
         else
         if (strsame (LoggingPeriodName, "THURSDAY", 3))
         {
            LoggingPerPeriod = LoggingPeriodWeekly = true;
            LoggingDayWeekBegins = 4;
         }
         else
         if (strsame (LoggingPeriodName, "FRIDAY", 3))
         {
            LoggingPerPeriod = LoggingPeriodWeekly = true;
            LoggingDayWeekBegins = 5;
         }
         else
         if (strsame (LoggingPeriodName, "SATURDAY", 3))
         {
            LoggingPerPeriod = LoggingPeriodWeekly = true;
            LoggingDayWeekBegins = 6;
         }
         else
         if (strsame (LoggingPeriodName, "MONTH", 4))
            LoggingPerPeriod = LoggingPeriodMonthly = true;
         else
         {
            fprintf (stdout, "%%%s-W-LOG, period unknown\n \\%s\\\n",
                     Utility, LoggingPeriodName);
            LoggingPerPeriod = false;
         }
      }

      /*************************/
      /* get logging host name */
      /*************************/

      zptr = (sptr = svptr->LogHostName) + sizeof(svptr->LogHostName)-1;
      if (LoggingNaming == LOGGING_NAMING_ADDRESS)
      {
         /* internet address with periods replaced by hyphens */
         for (cptr = svptr->ServerIpAddressString;
              *cptr && sptr < zptr;
              cptr++)
            if (*cptr == '.') *sptr++ = '-'; else *sptr++ = *cptr;
      }
      else
      if (LoggingNaming == LOGGING_NAMING_HOST)
      {
         /* as much of the IP host name as possible (periods->hyphens) */
         for (cptr = svptr->ServerHostName;
              *cptr && sptr < zptr;
              cptr++)
            if (*cptr == '.') *sptr++ = '-'; else *sptr++ = toupper(*cptr);
      }
      else
      {
         /* default, LOGGING_NAMING_NAME */
         for (cptr = svptr->ServerHostName;
              *cptr && (*cptr == '.' || isdigit(*cptr));
              cptr++);
         if (*cptr)
         {
            /* the first period-separated part of the host name */
            for (cptr = svptr->ServerHostName;
                 *cptr && *cptr != '.' && sptr < zptr;
                 *sptr++ = toupper(*cptr++));
         }
         else
         {
            /* all numeric service name */
            for (cptr = svptr->ServerHostName;
                 *cptr && sptr < zptr;
                 cptr++)
               if (*cptr == '.') *sptr++ = '-'; else *sptr++ = *cptr;
         }
      }
      *sptr = '\0';

      zptr = (sptr = ProcessName) + sizeof(ProcessName)-1;
      for (cptr = HttpdProcess.PrcNam; *cptr && sptr < zptr; cptr++)
         if (!isalnum(*cptr)) *sptr++ = '-'; else *sptr++ = toupper(*cptr);
      *sptr = '\0';

      if (Debug)
         fprintf (stdout, "LogHostName/Port |%s|%s|%s| \n",
                  svptr->LogHostName, svptr->ServerPortString, ProcessName);

      if (!LoggingEnabled) return (SS$_NORMAL);
   }

   if (Function == LOGGING_FLUSH)
   {
      /******************/
      /* flush log file */
      /******************/

      if (LoggingSysOutput) return (SS$_NORMAL);
      if (!svptr->LogFileOpen) return (SS$_NORMAL);

      if (WATCH_CAT && WatchThisOne)
         WatchThis (rqptr, FI_LI, WATCH_LOG,
                    "FLUSH !AZ", svptr->LogFileName);

      sys$flush (&svptr->LogFileRab, 0, 0);

      return (SS$_NORMAL);
   }

   /*************************/
   /* log file name change? */
   /*************************/

   /* get numeric vector time for this log transaction */
   if (!rqptr)
   {
      sys$gettim (BinaryTimePtr = &BinaryTime);
      sys$numtim (&NumericTime, &BinaryTime);
   }
   else
      sys$numtim (&NumericTime, BinaryTimePtr = &rqptr->rqTime.Vms64bit);

   if (LoggingPerPeriod && NumericTime[2] != PreviousDay)
   {
      PeriodChanged = false;
      if (LoggingPeriodDaily)
      {
         PeriodChanged = true;
         PreviousYear = NumericTime[0];
         PreviousMonth = NumericTime[1];
         PreviousDay = NumericTime[2];
      }
      else
      if (LoggingPeriodWeekly)
      {
         LoggingWeek (BinaryTimePtr, &NumericTime, &WeeklyNumericTime);
         if (WeeklyNumericTime[0] != PreviousYear ||
             WeeklyNumericTime[1] != PreviousMonth ||
             WeeklyNumericTime[2] != PreviousDay)
         {
            PeriodChanged = true;
            PreviousYear = WeeklyNumericTime[0];
            PreviousMonth = WeeklyNumericTime[1];
            PreviousDay = WeeklyNumericTime[2];
         }
      }
      else
      if (LoggingPeriodMonthly)
      {
         if (NumericTime[1] != PreviousMonth)
         {
            PeriodChanged = true;
            PreviousYear = NumericTime[0];
            PreviousMonth = NumericTime[1];
            PreviousDay = NumericTime[2];
         }
      }
      else
         ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI);

      if (PeriodChanged)
      {
         /* close files for ALL service logs (as applicable) */
         SERVICE_STRUCT  *svptr;
         for (svptr = ServiceListHead; svptr; svptr = svptr->NextPtr)
         {
            if (!svptr->LogFileOpen) continue;
            sys$close (&svptr->LogFileFab, 0, 0);
            svptr->LogFileOpen = false;
         }
      }
   }

   if (!svptr->LogFileOpen && !LoggingSysOutput)
   {
      /*********************/
      /* open the log file */
      /*********************/

      if (Debug)
         fprintf (stdout, "LoggingFileName |%s|\nsvptr->LogFileName |%s|\n",
                  LoggingFileName, svptr->LogFileName);

      if (!LoggingFileName[0])
      {
         status = sys$trnlnm (0, &LnmFileDevDsc, &LoggingFileNameLogicalDsc,
                              0, &LnmItems);
         if (VMSnok (status)) return (status);
         LoggingFileName[Length] = '\0';
         if (Debug) fprintf (stdout, "LoggingFileName |%s|\n", LoggingFileName);
      }

      if (!LoggingFileNameLength)
         LoggingFileNameLength = strlen(LoggingFileName);

      /* initialize the FAB before setting up the file name! */
      svptr->LogFileFab = cc$rms_fab;

      if (LoggingPerPeriod)
      {
         /*************************************************/
         /* per-period logging (with/without per-service) */
         /*************************************************/

         if (PeriodChanged)
         {
            vecptr = FaoVector;
            /* host name component can be from 17 to 20, or 23 chars max */
            Length = strlen(svptr->LogHostName);
            if (Config.cfLog.PerServiceHostOnly) {
               if (Length > 23) Length = 23;
            } 
            else {
               if (Length + strlen(svptr->ServerPortString) > 22)
                  Length = 22 - strlen(svptr->ServerPortString);
            } 
            *vecptr++ = Length;
            *vecptr++ = svptr->LogHostName;
            if (Config.cfLog.PerServiceHostOnly)
            {
               *vecptr++ = "";
               *vecptr++ = "";
            }
            else
            {
               *vecptr++ = "_";
               *vecptr++ = svptr->ServerPortString;
            }
            if (LoggingPeriodWeekly)
            {
               *vecptr++ = WeeklyNumericTime[0];
               *vecptr++ = WeeklyNumericTime[1];
               *vecptr++ = WeeklyNumericTime[2];
            }
            else
            {
               *vecptr++ = NumericTime[0];
               *vecptr++ = NumericTime[1];
               if (LoggingPeriodMonthly)
                  *vecptr++ = 1;
               else
                  *vecptr++ = NumericTime[2];
            }

            if (Config.cfLog.PerInstance)
            {
               *vecptr++ = "_";
               *vecptr++ = SysInfo.NodeName;
               *vecptr++ = "_";
               *vecptr++ = ProcessName;
            }
            else
            {
               *vecptr++ = "";
               *vecptr++ = "";
               *vecptr++ = "";
               *vecptr++ = "";
            }

            svptr->LogFileNameDsc.dsc$a_pointer = svptr->LogFileName;
            svptr->LogFileNameDsc.dsc$w_length = sizeof(svptr->LogFileName)-1;

            if (VMSnok (status =
                sys$faol (&PeriodFileNameFaoDsc, &svptr->LogFileNameLength,
                          &svptr->LogFileNameDsc, &FaoVector)))
               ErrorExitVmsStatus (status, "sys$faol()", FI_LI);
            svptr->LogFileName[svptr->LogFileNameLength] = '\0';
            if (Debug)
               fprintf (stdout, "svptr->LogFileName |%s|\n",
                        svptr->LogFileName);
         }

         /* logging file name is used for the directory component only */
         svptr->LogFileFab.fab$l_dna = LoggingFileName;  
         svptr->LogFileFab.fab$b_dns = LoggingFileNameLength;
         svptr->LogFileFab.fab$l_fna = svptr->LogFileName;  
         svptr->LogFileFab.fab$b_fns = svptr->LogFileNameLength;
      }
      else
      if (LoggingNaming)
      {
         /***************************************/
         /* per-service logging (no per-period) */
         /***************************************/

         if (!svptr->LogFileNameLength)
         {
            vecptr = FaoVector;
            /* host name component can be from 26 to 29, or 32 chars max */
            Length = strlen(svptr->LogHostName);
            if (Config.cfLog.PerServiceHostOnly) {
               if (Length > 32) Length = 32;
            } 
            else {
               if (Length + strlen(svptr->ServerPortString) > 31)
                  Length = 31 - strlen(svptr->ServerPortString);
            } 
            *vecptr++ = Length;
            *vecptr++ = svptr->LogHostName;
            if (Config.cfLog.PerServiceHostOnly)
            {
               *vecptr++ = "";
               *vecptr++ = "";
            }
            else
            {
               *vecptr++ = "_";
               *vecptr++ = svptr->ServerPortString;
            }

            if (Config.cfLog.PerInstance)
            {
               *vecptr++ = "_";
               *vecptr++ = SysInfo.NodeName;
               *vecptr++ = "_";
               *vecptr++ = ProcessName;
            }
            else
            {
               *vecptr++ = "";
               *vecptr++ = "";
               *vecptr++ = "";
               *vecptr++ = "";
            }

            svptr->LogFileNameDsc.dsc$a_pointer = svptr->LogFileName;
            svptr->LogFileNameDsc.dsc$w_length = sizeof(svptr->LogFileName)-1;

            if (VMSnok (status =
                sys$faol (&ServiceFileNameFaoDsc, &svptr->LogFileNameLength,
                          &svptr->LogFileNameDsc, &FaoVector)))
               ErrorExitVmsStatus (status, "sys$faol()", FI_LI);
            svptr->LogFileName[svptr->LogFileNameLength] = '\0';
            if (Debug)
               fprintf (stdout, "svptr->LogFileName |%s|\n",
                        svptr->LogFileName);
         }

         /* logging file name is used for the directory component only */
         svptr->LogFileFab.fab$l_dna = LoggingFileName;  
         svptr->LogFileFab.fab$b_dns = LoggingFileNameLength;
         svptr->LogFileFab.fab$l_fna = svptr->LogFileName;  
         svptr->LogFileFab.fab$b_fns = svptr->LogFileNameLength;
      }
      else
      {
         /******************/
         /* fixed log file */
         /******************/

         /* the logging file name must be complete */
         svptr->LogFileFab.fab$l_fna = LoggingFileName;  
         svptr->LogFileFab.fab$b_fns = LoggingFileNameLength;
      }

      /************************/
      /* create/open log file */
      /************************/

      if (WATCH_CAT && WatchThisOne)
         WatchThis (rqptr, FI_LI, WATCH_LOG,
                    "OPEN !AZ", svptr->LogFileName);

      svptr->LogFileFab.fab$l_fop = FAB$M_CIF | FAB$M_DFW |
                                    FAB$M_SQO | FAB$M_TEF;
      svptr->LogFileFab.fab$w_deq = ExtendBlocks;
      svptr->LogFileFab.fab$b_fac = FAB$M_PUT;
      svptr->LogFileFab.fab$b_rfm = FAB$C_STMLF;
      svptr->LogFileFab.fab$b_shr = FAB$M_SHRGET | FAB$M_SHRPUT | FAB$M_SHRUPD;
      svptr->LogFileFab.fab$b_org = FAB$C_SEQ;
      svptr->LogFileFab.fab$b_rat = FAB$M_CR;
   
      /* turn on SYSPRV to allow access to the logging file */
      sys$setprv (1, &SysPrvMask, 0, 0);

      status = sys$create (&svptr->LogFileFab, 0, 0);

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

      if (VMSnok (status))
      {
         if (Debug) fprintf (stdout, "sys$create() %%X%08.08X\n", status);

         if (WATCH_CAT && WatchThisOne)
            WatchThis (rqptr, FI_LI, WATCH_LOG,
                       "CREATE !AZ !&S %!&M",
                       svptr->LogFileName, status, status);

         if (!svptr->LogFileError)
         {
            svptr->LogFileError = true;
            WriteFaoStdout ("%!AZ-W-LOG, file create error\n-!&M\n \\!AZ\\\n",
                            Utility, status, svptr->LogFileName);
         }
         return (status);
      }
      svptr->LogFileError = false;

      svptr->LogFileRab = cc$rms_rab;
      svptr->LogFileRab.rab$l_fab = &svptr->LogFileFab;
      svptr->LogFileRab.rab$b_mbc = 127;
      svptr->LogFileRab.rab$b_mbf = 4;
      svptr->LogFileRab.rab$l_rop = RAB$M_EOF | RAB$M_WBH;

      if (VMSnok (status = sys$connect (&svptr->LogFileRab, 0, 0)))
      {
         if (Debug) fprintf (stdout, "sys$connect() %%X%08.08X\n", status);

         if (WATCH_CAT && WatchThisOne)
            WatchThis (rqptr, FI_LI, WATCH_LOG,
                       "CREATE !AZ !&S %!&M",
                       svptr->LogFileName, status, status);

         sys$close (&svptr->LogFileFab, 0, 0);
         return (status);
      }

      svptr->LogFileOpen = true;
   }

   /***********************/
   /* act on the log file */
   /***********************/

   if (Debug) fprintf (stdout, "log file |%s|\n", svptr->LogFileName);

   status = sys$fao (&LogFileTimeFaoDsc, &Length, &TimeStringDsc,
                     NumericTime[2],
                     MonthName[NumericTime[1]],
                     NumericTime[0],
                     NumericTime[3],
                     NumericTime[4],
                     NumericTime[5],
                     TimeGmtString, TimeGmtString+4);
   /** if (Debug) fprintf (stdout, "sys$fao() %%X%08.08X\n", status); **/
   TimeString[Length] = '\0';

   if (Function == LOGGING_BEGIN ||
       Function == LOGGING_CLOSE ||
       Function == LOGGING_END ||
       Function == LOGGING_OPEN ||
       Function == LOGGING_TIMESTAMP)
   {
      switch (Function)
      {
         case LOGGING_BEGIN :
            FunctionNamePtr = "BEGIN"; break;
         case LOGGING_CLOSE :
            FunctionNamePtr = "CLOSE"; break;
         case LOGGING_END :
            FunctionNamePtr = "END"; break;
         case LOGGING_OPEN :
            FunctionNamePtr = "OPEN"; break;
         case LOGGING_TIMESTAMP :
            FunctionNamePtr = "TIMESTAMP"; break;
         default :
            FunctionNamePtr = "*ERROR*";
      }

      if (LoggingFormatCommon ||
          LoggingFormatCommonServer ||
          LoggingFormatCombined)
      {
         vecptr = FaoVector;
         *vecptr++ = ServerHostName;
         *vecptr++ = "-";
         *vecptr++ = HttpdProcess.UserName;
         *vecptr++ = TimeString;
         *vecptr++ = SysInfo.NodeName;
         *vecptr++ = HttpdProcess.PrcNam;
         *vecptr++ = FunctionNamePtr;
         if (Function == LOGGING_TIMESTAMP)
            *vecptr++ = LoggingTimeStampCount;
         else
            *vecptr++ = ExitStatus;
         if (LoggingFormatCommonServer)
         {
            *vecptr++ = " \"-\" \"-\"";
            *vecptr++ = "";
            *vecptr++ = "";
         }
         else
         if (LoggingFormatCombined)
         {
            *vecptr++ = " \"-\" \"";
            *vecptr++ = SoftwareID;
            *vecptr++ = "\"";
         }
         else
         {
            *vecptr++ = "";
            *vecptr++ = "";
            *vecptr++ = "";
         }

         if (Function == LOGGING_TIMESTAMP)
            status = sys$faol (&LogFileTimeStampFaoDsc, &Length, &StringDsc,
                               &FaoVector);
         else
            status = sys$faol (&LogFilePseudoFaoDsc, &Length, &StringDsc,
                               &FaoVector);
         /* if (Debug) fprintf (stdout, "sys$faol() %%X%08.08X\n", status); */
         if (VMSnok (status) || status == SS$_BUFFEROVF)
            ErrorExitVmsStatus (status, "sys$faol()", FI_LI);
         String[Length] = '\0';

         if (WATCH_CAT && WatchThisOne)
         {
            WatchThis (rqptr, FI_LI, WATCH_LOG,
                       "ENTRY !UL bytes", Length);
            WatchData (String, Length);
         }

         if (LoggingSysOutput)
            fprintf (stdout, "%s\n", String);
         else
         {
            svptr->LogFileRab.rab$l_rbf = String;
            svptr->LogFileRab.rab$w_rsz = Length;
            /** if (Debug) fprintf (stdout, "String |%s|\n", String); **/
            if (VMSnok (status = sys$put (&svptr->LogFileRab, 0, 0)))
            {
               if (Debug) fprintf (stdout, "sys$put() %%X%08.08X\n", status);
               if (!svptr->LogFileError)
               {
                  svptr->LogFileError = true;
                  WriteFaoStdout (
                     "%!AZ-W-LOG, file write error\n-!&M\n \\!AZ\\\n",
                     Utility, status, svptr->LogFileName);
               }
            }
            else
               svptr->LogFileError = false;
         }
      }

      if (Function == LOGGING_END ||
          Function == LOGGING_CLOSE)
      {
         /******************/
         /* close log file */
         /******************/

         if (WATCH_CAT && WatchThisOne)
            WatchThis (rqptr, FI_LI, WATCH_LOG,
                       "CLOSE !AZ", svptr->LogFileName);

         if (LoggingSysOutput) return (SS$_NORMAL);
         sys$close (&svptr->LogFileFab, 0, 0);
         svptr->LogFileOpen = false;
      }

      return (SS$_NORMAL);
   }

   if (!rqptr) return (SS$_NORMAL);

   /*************************/
   /* format request record */
   /*************************/

   if (LoggingFormatCommon ||
       LoggingFormatCommonServer ||
       LoggingFormatCombined)
   {
      /*******************/
      /* common/combined */
      /*******************/

      vecptr = FaoVector;

      *vecptr++ = rqptr->rqClient.Lookup.HostName;

      /* X509 client cert auth (replace all spaces with underscores) */
      if (rqptr->rqAuth.ClientCertSubjectPtr &&
          rqptr->rqAuth.ClientCertSubjectPtr[0])
      {
         zptr = (sptr = ClientCertSubject) + sizeof(ClientCertSubject)-1;
         for (cptr = rqptr->rqAuth.ClientCertSubjectPtr;
              *cptr && sptr < zptr;
              cptr++)
         {
            if (isspace(*cptr))
               *sptr++ = '_';
            else
               *sptr++ = *cptr;
         }
         *sptr = '\0';
         *vecptr++ = ClientCertSubject;
      }
      else
      /* if tracking is not in use or none applies "place-mark" it */
      if (rqptr->TrackId[0])
         *vecptr++ = rqptr->TrackId;
      else
         *vecptr++ = "-";

      /* if authentication has not been supplied "place-mark" remote user */
      if (rqptr->RemoteUser[0])
         *vecptr++ = rqptr->RemoteUser;
      else
         *vecptr++ = "-";

      *vecptr++ = TimeString;

      if (rqptr->rqHeader.MethodName[0])
         *vecptr++ = rqptr->rqHeader.MethodName;
      else
         *vecptr++ = "-";

      if (!rqptr->rqHeader.RequestUriPtr ||
          !rqptr->rqHeader.RequestUriPtr[0])
         *vecptr++ = "-";
      else
         *vecptr++ = rqptr->rqHeader.RequestUriPtr;

      switch (rqptr->rqHeader.HttpVersion)
      {
         case HTTP_VERSION_1_0 : *vecptr++ = " HTTP/1.0"; break;
         case HTTP_VERSION_1_1 : *vecptr++ = " HTTP/1.1"; break;
         default : *vecptr++ = "";
      }

      *vecptr++ = rqptr->rqResponse.HttpStatus;

      if (rqptr->BytesRawTx)
      {
         sys$fao (&BytesFaoDsc, &Length, &BytesStringDsc, rqptr->BytesRawTx);
         *vecptr++ = BytesString;
      }
      else
         *vecptr++ = "0";

      if (LoggingFormatCommonServer)
         *vecptr++ = rqptr->ServicePtr->ServerHostName;
      else
      if (LoggingFormatCombined)
      {
         if (!rqptr->rqHeader.RefererPtr ||
             !rqptr->rqHeader.RefererPtr[0])
            *vecptr++ = "-";
         else
            *vecptr++ = rqptr->rqHeader.RefererPtr;

         if (!rqptr->rqHeader.UserAgentPtr ||
             !rqptr->rqHeader.UserAgentPtr[0])
            *vecptr++ = "-";
         else
            *vecptr++ = rqptr->rqHeader.UserAgentPtr;
      }
   }
   else
   if (FormatUser[0] || Config.cfLog.Format[0])
   {
      /***********************/
      /* user-defined format */
      /***********************/

      int  cnt;
      char  *cptr, *sptr, *AbsentPtr;

      if (UserFormatProblem) return (SS$_NORMAL);

      cnt = 0;
      sptr = MiscChars;
      AbsentPtr = "";
      if (FormatUser[0])
         cptr = FormatUser;
      else
         cptr = Config.cfLog.Format;

      vecptr = FaoVector;

      while (*cptr)
      {
         if (Debug) fprintf (stdout, "|%s|\n", cptr);

         if (cnt > MAX_FAO_VECTOR)
            ErrorExitVmsStatus (0, ErrorLoggingFormatLength, FI_LI);

         if (cnt == 1) AbsentPtr = FaoVector[0];

         switch (*cptr)
         {
            case '!' :
            {
               cptr++;
               switch (*(unsigned short*)cptr)
               {
                  case 'AR' :
                     if (!rqptr->rqAuth.RealmPtr)
                        *vecptr++ = AbsentPtr;
                     else
                     if (!rqptr->rqAuth.RealmPtr[0])
                        *vecptr++ = AbsentPtr;
                     else
                        *vecptr++ = rqptr->rqAuth.RealmPtr;
                     cnt++;
                     cptr += 2;
                     continue;
                  case 'AU' :
                     if (!rqptr->RemoteUser[0])
                        *vecptr++ = AbsentPtr;
                     else
                        *vecptr++ = rqptr->RemoteUser;
                     cnt++;
                     cptr += 2;
                     continue;
                  case 'BB' :
                     if (rqptr->BytesRawTx)
                     {
                        sys$fao (&BytesFaoDsc, 0, &BytesStringDsc,
                           rqptr->BytesRawTx - rqptr->rqResponse.HeaderLength);
                        *vecptr++ = BytesString;
                     }
                     else
                        *vecptr++ = "0";
                     cnt++;
                     cptr += 2;
                     continue;
                  case 'BY' :
                     if (rqptr->BytesRawTx)
                     {
                        sys$fao (&BytesFaoDsc, 0, &BytesStringDsc,
                                 rqptr->BytesRawTx);
                        *vecptr++ = BytesString;
                     }
                     else
                        *vecptr++ = "0";
                     cnt++;
                     cptr += 2;
                     continue;
                  case 'CA' :
                     *vecptr++ = rqptr->rqClient.IpAddressString;
                     cnt++;
                     cptr += 2;
                     continue;
                  case 'CC' :
                     /* replace all spaces with underscores */
                     if (rqptr->rqAuth.ClientCertSubjectPtr &&
                         rqptr->rqAuth.ClientCertSubjectPtr[0])
                     {
                        zptr = (sptr = ClientCertSubject) +
                                sizeof(ClientCertSubject)-1;
                        for (cptr = rqptr->rqAuth.ClientCertSubjectPtr;
                             *cptr && sptr < zptr;
                             cptr++)
                        {
                           if (isspace(*cptr))
                              *sptr++ = '_';
                           else
                              *sptr++ = *cptr;
                        }
                        *sptr = '\0';
                        *vecptr++ = ClientCertSubject;
                     }
                     else
                        *vecptr++ = AbsentPtr;
                     cnt++;
                     cptr += 2;
                     continue;
                  case 'CN' :
                     *vecptr++ = rqptr->rqClient.Lookup.HostName;
                     cnt++;
                     cptr += 2;
                     continue;
                  case 'CI' :
                     /* cache involvement removed 03-APR-2002 */
                     cptr += 2;
                     continue;
                  case 'EM' :
                     /* durations are measured in microseconds */
                     sys$fao (&ElapsedResponseDurationDsc, 0, &ElapsedDsc,
                              rqptr->rqResponse.Duration / 1000);
                     *vecptr++ = Elapsed;
                     cnt++;
                     cptr += 2;
                     continue;
                  case 'ES' :
                     /* durations are measured in microseconds */
                     sys$fao (&ElapsedSecondsDsc, 0, &ElapsedDsc,
                              rqptr->rqResponse.Duration / 1000000,
                              (rqptr->rqResponse.Duration / 1000) % 1000);
                     *vecptr++ = Elapsed;
                     cnt++;
                     cptr += 2;
                     continue;
                  case 'EU' :
                     /* durations are measured in microseconds */
                     sys$fao (&ElapsedResponseDurationDsc, 0, &ElapsedDsc,
                              rqptr->rqResponse.Duration);
                     *vecptr++ = Elapsed;
                     cnt++;
                     cptr += 2;
                     continue;
                  case 'ID' :
                     if (!rqptr->TrackId[0])
                        *vecptr++ = AbsentPtr;
                     else
                        *vecptr++ = rqptr->TrackId;
                     cnt++;
                     cptr += 2;
                     continue;
                  case 'ME' :
                     *vecptr++ = rqptr->rqHeader.MethodName;
                     cnt++;
                     cptr += 2;
                     continue;
                  case 'PA' :
                     if (!rqptr->rqHeader.PathInfoPtr)
                        *vecptr++ = AbsentPtr;
                     else
                     if (!rqptr->rqHeader.PathInfoPtr[0])
                        *vecptr++ = AbsentPtr;
                     else
                        *vecptr++ = rqptr->rqHeader.PathInfoPtr;
                     cnt++;
                     cptr += 2;
                     continue;
                  case 'PR' :
                     switch (rqptr->rqHeader.HttpVersion)
                     {
                        case HTTP_VERSION_1_0 : *vecptr++ = " HTTP/1.0"; break;
                        case HTTP_VERSION_1_1 : *vecptr++ = " HTTP/1.1"; break;
                        default : *vecptr++ = "";
                     }
                     cnt++;
                     cptr += 2;
                     continue;
                  case 'QS' :
                     if (!rqptr->rqHeader.QueryStringPtr)
                        *vecptr++ = AbsentPtr;
                     else
                     if (!rqptr->rqHeader.QueryStringPtr[0])
                        *vecptr++ = AbsentPtr;
                     else
                        *vecptr++ = rqptr->rqHeader.QueryStringPtr;
                     cnt++;
                     cptr += 2;
                     continue;
                  case 'RF' :
                     if (!rqptr->rqHeader.RefererPtr)
                        *vecptr++ = AbsentPtr;
                     else
                     if (!rqptr->rqHeader.RefererPtr[0])
                        *vecptr++ = AbsentPtr;
                     else
                        *vecptr++ = rqptr->rqHeader.RefererPtr;
                     cnt++;
                     cptr += 2;
                     continue;
                  case 'RQ' :
                     if (!rqptr->rqHeader.RequestUriPtr)
                        *vecptr++ = AbsentPtr;
                     else
                     if (!rqptr->rqHeader.RequestUriPtr[0])
                        *vecptr++ = AbsentPtr;
                     else
                        *vecptr++ = rqptr->rqHeader.RequestUriPtr;
                     cnt++;
                     cptr += 2;
                     continue;
                  case 'RS' :
                     sys$fao (&StatusFaoDsc, 0, &StatusStringDsc,
                              rqptr->rqResponse.HttpStatus);
                     *vecptr++ = StatusString;
                     cnt++;
                     cptr += 2;
                     continue;
                  case 'SC' :
                     if (!rqptr->ScriptName[0])
                        *vecptr++ = AbsentPtr;
                     else
                        *vecptr++ = rqptr->ScriptName;
                     cnt++;
                     cptr += 2;
                     continue;
                  case 'SM' :
                     /* must come from request's service pointer */
                     *vecptr++ = rqptr->ServicePtr->RequestSchemeNamePtr;
                     cnt++;
                     cptr += 2;
                     continue;
                  case 'SN' :
                     /* must come from request's service pointer */
                     *vecptr++ = rqptr->ServicePtr->ServerHostName;
                     cnt++;
                     cptr += 2;
                     continue;
                  case 'SP' :
                     /* must come from request's service pointer */
                     *vecptr++ = rqptr->ServicePtr->ServerPortString;
                     cnt++;
                     cptr += 2;
                     continue;
                  case 'TC' :
                     *vecptr++ = TimeString;
                     cnt++;
                     cptr += 2;
                     continue;
                  case 'TG' :
                     *vecptr++ = rqptr->rqTime.GmDateTime;
                     cnt++;
                     cptr += 2;
                     continue;
                  case 'TV' :
                     sys$fao (&VmsTimeFaoDsc, 0, &VmsTimeStringDsc,
                              &rqptr->rqTime.Vms64bit);
                     *vecptr++ = VmsTimeString;
                     cnt++;
                     cptr += 2;
                     continue;
                  case 'UA' :
                     if (!rqptr->rqHeader.UserAgentPtr)
                        *vecptr++ = AbsentPtr;
                     else
                     if (!rqptr->rqHeader.UserAgentPtr[0])
                        *vecptr++ = AbsentPtr;
                     else
                        *vecptr++ = rqptr->rqHeader.UserAgentPtr;
                     cnt++;
                     cptr += 2;
                     continue;
                  default :
                     UserFormatProblem = true;
                     continue;
               }
            }

            case '\\' :
            {
               cptr++;
               switch (*cptr)
               {
                  case '0' :
                     *vecptr++ = "";
                     cnt++;
                     cptr++;
                     continue;
                  case 'n' :
                     *vecptr++ = "\n";
                     cnt++;
                     cptr++;
                     continue;
                  case 'q' :
                     *vecptr++ = "\"";
                     cnt++;
                     cptr++;
                     continue;
                  case 't' :
                     *vecptr++ = "\t";
                     cnt++;
                     cptr++;
                     continue;
                  case '!' :
                     *vecptr++ = "!";
                     cnt++;
                     cptr++;
                     continue;
                  case '\\' :
                     *vecptr++ = "\\";
                     cnt++;
                     cptr++;
                     continue;
                  default :
                     UserFormatProblem = true;
                     continue;
               }
            }

            default :
            {
               *sptr = *cptr++;
               *vecptr++ = sptr++;
               *sptr++ = '\0';
               cnt++;
               continue;
            }
         }
      }

      if (cnt <= 1) UserFormatProblem = true;

      if (UserFormatProblem)
      {
         fprintf (stdout, "%%%s-W-LOG, user format problem\n \\%s\\\n",
                  Utility, FormatUser);
         return (SS$_NORMAL);
      }

      /** if (Debug) fprintf (stdout, "cnt-1: %d\n", cnt-1); **/
      /* now set [0] to the repeat count */
      FaoVector[0] = cnt - 1;
   }
   else
      ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI);

   /*************/
   /* sys$fao() */
   /*************/

   if (LoggingFormatCommon)
      status = sys$faol (&LogFileCommonFaoDsc, &Length, &StringDsc,
                         &FaoVector);
   else
   if (LoggingFormatCombined)
      status = sys$faol (&LogFileCombinedFaoDsc, &Length, &StringDsc,
                         &FaoVector);
   else
   if (LoggingFormatCommonServer)
      status = sys$faol (&LogFileCommonServerFaoDsc, &Length, &StringDsc,
                         &FaoVector);
   else
   if (FormatUser[0])
      status = sys$faol (&LogFileUserFaoDsc, &Length, &StringDsc,
                         &FaoVector);
   else
      ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI);

   /** if (Debug) fprintf (stdout, "sys$faol() %%X%08.08X\n", status); **/

   if (VMSnok (status) || status == SS$_BUFFEROVF)
      ErrorNoticed (status, "sys$faol()", FI_LI);
   String[Length] = '\0';

   /********************/
   /* write the record */
   /********************/

   if (WATCH_CAT && WatchThisOne)
   {
      if (LoggingPerService)
      {
         WatchThis (rqptr, FI_LI, WATCH_LOG, "ENTRY !AZ !UL bytes",
                    svptr->LogFileName, Length);
         WatchData (String, Length);
      }
      else
      {
         WatchThis (rqptr, FI_LI, WATCH_LOG, "ENTRY !UL bytes", Length);
         WatchData (String, Length);
      }
   }

   if (LoggingSysOutput)
   {
      fprintf (stdout, "%s\n", String);
      status = SS$_NORMAL;
   }
   else
   {
      svptr->LogFileRab.rab$l_rbf = String;
      svptr->LogFileRab.rab$w_rsz = Length;
      /** if (Debug) fprintf (stdout, "String |%s|\n", String); **/
      if (VMSnok (status = sys$put (&svptr->LogFileRab, 0, 0)))
      {
         if (Debug) fprintf (stdout, "sys$put() %%X%08.08X\n", status);

         if (WATCH_CAT && WatchThisOne)
            WatchThis (rqptr, FI_LI, WATCH_LOG,
                       "PUT !AZ !&S %!&M",
                       svptr->LogFileName, status, status);

         if (!svptr->LogFileError)
         {
            svptr->LogFileError = true;
            WriteFaoStdout ("%!AZ-W-LOG, file write error\n-!&M\n \\!AZ\\\n",
                            Utility, status, svptr->LogFileName);
         }
      }
      else
         svptr->LogFileError = false;
   }

   return (status);
}

/*****************************************************************************/
/*
Fills 'WeeklyNumericTime' with the date of the most recent
'LoggingDayWeekBegins' (i.e. the last Monday, Tuesday ... Sunday)
*/

LoggingWeek
(
unsigned long *BinaryTimePtr,
unsigned short *NumericTimePtr,
unsigned short *WeeklyNumericTimePtr
)
{
   static unsigned long  DeltaDaysBinaryTime [2],
                         ResultBinaryTime [2];
   static $DESCRIPTOR (DeltaDaysFaoDsc, "!UL 00:00:00.00");

   int  status,
        DeltaDays;
   unsigned short  Length;
   char  DeltaString [32];
   $DESCRIPTOR (DeltaStringDsc, DeltaString);

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

   if (WATCH_MODULE(WATCH_MOD__OTHER))
      WatchThis (NULL, FI_LI, WATCH_MOD__OTHER, "LoggingWeek()");

   if (Debug)
   {
      char  Time [48];
      $DESCRIPTOR (TimeFaoDsc, "!%D\0");
      $DESCRIPTOR (TimeDsc, Time);

      sys$fao (&TimeFaoDsc, 0, &TimeDsc, BinaryTimePtr);
      fprintf (stdout, "time |%s|\n", Time); 
   }

   /* monday == 1, tuesday == 2 ... sunday == 7 */
   if ((DeltaDays = HttpdDayOfWeek - LoggingDayWeekBegins) < 0)
      DeltaDays += 7;

   if (Debug)
      fprintf (stdout, "%d = %d - %d\n",
               DeltaDays, HttpdDayOfWeek, LoggingDayWeekBegins);

   if (!DeltaDays)
   {
      /* same bat-time, same bat-channel */
      memcpy (WeeklyNumericTimePtr, NumericTimePtr, 14);
      return;
   }

   if (VMSnok (status =
       sys$fao (&DeltaDaysFaoDsc, &Length, &DeltaStringDsc, DeltaDays)))
      ErrorExitVmsStatus (status, "sys$fao()", FI_LI);
   DeltaStringDsc.dsc$w_length = Length;
   if (Debug)
   {
      DeltaString[Length] = '\0';
      fprintf (stdout, "DeltaString |%s|\n", DeltaString);
   }

   if (VMSnok (status = sys$bintim (&DeltaStringDsc, DeltaDaysBinaryTime)))
      ErrorExitVmsStatus (status, "sys$bintim()", FI_LI);

   if (VMSnok (status =
       lib$sub_times (BinaryTimePtr, &DeltaDaysBinaryTime, &ResultBinaryTime)))
      ErrorExitVmsStatus (status, "lib$sub_times()", FI_LI);

   if (Debug)
   {
      char  Week [48];
      $DESCRIPTOR (WeekFaoDsc, "!%D\0");
      $DESCRIPTOR (WeekDsc, Week);

      sys$fao (&WeekFaoDsc, 0, &WeekDsc, &ResultBinaryTime);
      fprintf (stdout, "start of week |%s|\n", Week); 
   }

   /* overwrite the supplied numeric time with the start of week's */
   sys$numtim (WeeklyNumericTimePtr, &ResultBinaryTime);
}

/*****************************************************************************/
/*
Return a plain text report comprising access records in the last 65kBytes
(maximum) of the log associated with the specified service.  Not asynchronous
but fast (and convenient) enough not to introduce too much granularity.
*/

LoggingReport
(
REQUEST_STRUCT *rqptr,
REQUEST_AST NextTaskFunction,
char *ServerHostPort
)
{
   int  cnt, status,
        BufferBlockCount;
   char  *cptr, *zptr,
         *BufferPtr;
   struct FAB  LogFileFab;
   struct RAB  LogFileRab;
   struct XABFHC  LogFileXabFhc;
   SERVICE_STRUCT  *svptr;

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

   if (WATCH_MODULE(WATCH_MOD__OTHER))
      WatchThis (NULL, FI_LI, WATCH_MOD__OTHER,
                 "LoggingReport() !&A !&Z", NextTaskFunction, ServerHostPort);

   for (svptr = ServiceListHead; svptr; svptr = svptr->NextPtr)
      if (strsame (svptr->ServerHostPort, ServerHostPort, -1)) break;
   if (!svptr)
   {
      rqptr->rqResponse.HttpStatus = 404;
      ErrorGeneral (rqptr, "Service not found.", FI_LI);
      SysDclAst (NextTaskFunction, rqptr);
      return;
   }
   if (!LoggingPerService) svptr = ServiceListHead;
   if (!svptr->LogFileNameLength)
   {
      rqptr->rqResponse.HttpStatus = 404;
      ErrorGeneral (rqptr, "Log file not defined.", FI_LI);
      SysDclAst (NextTaskFunction, rqptr);
      return;
   }

   /* make sure we get all the latest access records */
   if (svptr->LogFileOpen) sys$flush (&svptr->LogFileRab, 0, 0);

   LogFileFab = cc$rms_fab;
   LogFileFab.fab$l_dna = LoggingFileName;  
   LogFileFab.fab$b_dns = LoggingFileNameLength;
   LogFileFab.fab$l_fna = svptr->LogFileName;  
   LogFileFab.fab$b_fns = svptr->LogFileNameLength;
   LogFileFab.fab$b_fac = FAB$M_GET | FAB$M_BIO;
   LogFileFab.fab$b_shr = FAB$M_SHRGET | FAB$M_SHRPUT | FAB$M_UPI;
   LogFileFab.fab$l_xab = &LogFileXabFhc;

   LogFileXabFhc = cc$rms_xabfhc;

   sys$setprv (1, &SysPrvMask, 0, 0);
   status = sys$open (&LogFileFab, 0, 0);
   sys$setprv (0, &SysPrvMask, 0, 0);
   if (VMSok (status)) status = LogFileFab.fab$l_sts;
   if (VMSnok (status))
   {
      rqptr->rqResponse.ErrorTextPtr = svptr->LogFileName;
      ErrorVmsStatus (rqptr, status, FI_LI);
      SysDclAst (NextTaskFunction, rqptr);
      return;
   }

   if (LogFileXabFhc.xab$l_ebk > 127)
      BufferBlockCount = 127;
   else
      BufferBlockCount = LogFileXabFhc.xab$l_ebk;

   BufferPtr = VmGetHeap (rqptr, BufferBlockCount * 512);

   LogFileRab = cc$rms_rab;
   if (BufferBlockCount >= LogFileXabFhc.xab$l_ebk)
      LogFileRab.rab$l_bkt = 1;
   else
      LogFileRab.rab$l_bkt = LogFileXabFhc.xab$l_ebk - BufferBlockCount + 1;
   LogFileRab.rab$l_fab = &LogFileFab;
   LogFileRab.rab$l_rop = RAB$M_BIO;
   LogFileRab.rab$l_ubf = BufferPtr;
   LogFileRab.rab$w_usz = BufferBlockCount * 512;

   if (WATCH_MODULE(WATCH_MOD__OTHER))
      WatchThis (NULL, FI_LI, WATCH_MOD__OTHER,
                 "XABFHC ebk:!UL ffb:!UL bkt:!UL usz:!UL",
                 LogFileXabFhc.xab$l_ebk, LogFileXabFhc.xab$w_ffb,
                 LogFileRab.rab$l_bkt, LogFileRab.rab$w_usz);

   status = sys$connect (&LogFileRab, 0, 0);
   if (VMSok (status)) status = LogFileRab.rab$l_sts;
   if (VMSnok (status))
   {
      rqptr->rqResponse.ErrorTextPtr = svptr->LogFileName;
      ErrorVmsStatus (rqptr, status, FI_LI);
      sys$close (&LogFileFab, 0, 0);
      SysDclAst (NextTaskFunction, rqptr);
      return;
   }

   status = sys$read (&LogFileRab, 0, 0);
   if (VMSok (status)) status = LogFileRab.rab$l_sts;
   if (status == RMS$_EOF) status = SS$_NORMAL;
   if (VMSnok (status))
   {
      rqptr->rqResponse.ErrorTextPtr = svptr->LogFileName;
      ErrorVmsStatus (rqptr, status, FI_LI);
      sys$close (&LogFileFab, 0, 0);
      SysDclAst (NextTaskFunction, rqptr);
      return;
   }

   status = sys$close (&LogFileFab, 0, 0);
   if (VMSok (status)) status = LogFileFab.fab$l_sts;
   if (VMSnok (status))
   {
      rqptr->rqResponse.ErrorTextPtr = svptr->LogFileName;
      ErrorVmsStatus (rqptr, status, FI_LI);
      SysDclAst (NextTaskFunction, rqptr);
      return;
   }

   /* whole records only (quite likely to straddle a block boundary) */
   zptr = (cptr = BufferPtr) + LogFileRab.rab$w_rsz;
   while (cptr < zptr && *cptr != '\n') cptr++;
   cptr++;
   /* if empty or only the one record */
   if (cptr >= zptr) cptr = BufferPtr;
   cnt = LogFileRab.rab$w_rsz - (cptr - BufferPtr);

   rqptr->rqResponse.PreExpired = PRE_EXPIRE_ADMIN;
   RESPONSE_HEADER_200_PLAIN (rqptr);
   if (cnt)
      NetWrite (rqptr, NextTaskFunction, cptr, cnt);
   else
      SysDclAst (NextTaskFunction, rqptr);
}

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

