/*****************************************************************************
/*
                                   CLI.c

Module to process (interpret) the HTTPD command line.  Allows images activated
by a "foreign verb" to behave in a way that approximates the CLI$ (Command
Line Interpreter) utility calls.


VERSION HISTORY
---------------
12-JUL-2019  MGD  add /SYSPLUS, /OUTPUT=
19-OCT-2017  MGD  add /NOTE=<string> annotate server process log
21-JUN-2017  MGD  add /INSTANCE=CONFIG ensure config file values used 
07-JUL-2016  MGD  ParseCommandInteger() accept just an integer
24-JUL-2015  MGD  bugfix; ParseCommand() parenthesis parsing
05-OCT-2009  MGD  remove /FILBUF
11-JUL-2009  MGD  add /ENV=<integer>
25-MAY-2005  MGD  add /VALBLK[=16|64]
15-APR-2004  MGD  obsolete /LOCK
19-JUL-2003  MGD  add /[NO]NETWORK
03-MAY-2003  MGD  add /SYSUAF=VMS keyword
30-JAN-2003  MGD  add /PROFILE=NORULE keyword
23-SEP-2002  MGD  add /SCRIPT=AS=
16-SEP-2002  MGD  add /PERSONA[=RELAXED|AUTHORIZED|RELAXED=AUTHORIZED],
                      rework qualifier keyword parsing
05-MAY-2002  MGD  add /DEMO catch-all for freeware demo use, etc.,
                      /GBLSEC=NOPERM for no permanent global section
29-SEP-2001  MGD  add /[NO]INSTANCE[=integer] number of per-node instances,
                      /CLUSTER[=integer] grouping (changed from [=resname])
20-MAY-2001  MGD  add /GBLSEC=DELETE=<integer>
23-FEB-2001  MGD  add /SYSUAF=PROXY
20-DEC-2000  MGD  add /PROXY=
02-OCT-2000  MGD  add /DETACH, /IDENT=, /PERSONA=, /USER=
18-JUN-2000  MGD  add /ALL[=resname]
02-JAN-2000  MGD  add /[NO]ODS5, ability to use HTTPD$PARAM
30-OCT-1999  MGD  add /SYSPRV
29-SEP-1999  MGD  change /SYSUAF= ALL to RELAXED (backward-compatible)
30-JUL-1999  MGD  bugfix; /SYSUAF does not need a parameter!
26-MAY-1999  MGD  /SSL= now takes a string parameter
24-FEB-1998  MGD  /AUTH=ALL, /AUTH=(SSL,ALL), /SYSUAF=WASD_ID
07-NOV-1998  MGD  /[NO]WATCH=
07-AUG-1998  MGD  /PROMISCUOUS[=password]
06-JUL-1998  MGD  v5.1, /SYSUAF=ID
11-MAR-1998  MGD  /SYSUAF=(SSL,ALL)
04-FEB-1998  MGD  v5.0, /SSL, /SYSUAF=SSL
11-OCT-1997  MGD  v4.5, /CACHE, /VERSION
06-SEP-1997  MGD  v4.4, /FORMAT, /PROFILE, /SYSUAF, /SOFTWAREID
01-FEB-1997  MGD  HTTPd version 4
01-DEC-1995  MGD  HTTPd version 3
20-DEC-1994  MGD  multi-threaded daemon
20-JUN-1994  MGD  single-threaded daemon
*/
/*****************************************************************************/

#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 <string.h>

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

#include "wasd.h"

#define WASD_MODULE "CLI"

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

BOOL  CliCacheDisabled,
      CliCacheEnabled,
      CliDemo,
      CliDetach,
      CliGblSecDelete,
      CliGblSecNoPerm,
      CliInstanceConfig,
      CliInstanceNoCrePrc,
      CliInstancePassive,
      CliLoggingDisabled,
      CliLoggingEnabled,
      CliMonitorDisabled,
      CliMonitorEnabled,
      CliNetworkMode,
      CliNoNetworkMode,
      CliOdsExtendedDisabled,
      CliOdsExtendedEnabled,
      CliPersonaAuthorized,
      CliPersonaEnabled,
      CliPersonaRelaxed,
      CliPersonaRelaxedAuthorized,
      CliSoftwareID,
      CliSysPlus,
      CliTests,
      CliVersion;

int  CliInstanceMax,
     CliLockValueBlockSize,
     CliServerPort;

char  CommandLine [256],
      CliParameter [256],
      CliProcessIdentName [64],
      CliScriptAs [64],
      CliUserName [64];

char  CliServices [256];
int  CliServicesSize = sizeof(CliServices);

char  *CliAcceptHostsPtr = NULL,
      *CliOutputPtr = NULL,
      *CliRejectHostsPtr = NULL;

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

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

extern BOOL  AccountingZeroOnStartup,
             AuthPromiscuous,
             AuthPolicyAuthorizedOnly,
             AuthPolicySslOnly,
             AuthPolicySysUafProxy,
             AuthPolicySysUafSslOnly,
             AuthPolicySysUafRelaxed,
             AuthPolicySysUafIdentifiers,
             AuthPolicySysUafVms,
             AuthPolicySysUafWasdIdentifiers,
             AuthSysUafEnabled,
             AuthSysUafPromiscuous,
             AuthVmsUserProfileNoRule,
             AuthVmsUserProfileEnabled,
             ControlDoAllHttpd,
             NoSwapOut,
             OperateWithSysPrv;

extern int  ControlBufferSize,
            DclCgiPlusInSize,
            DclSysCommandSize,
            DclSysOutputSize,
            DclCgiVariablePrefixLength,
            HttpdServerNoteSize,
            InstanceEnvNumber,
            NetReadBufferSize,
            OutputBufferSize,
            PersonaIdentNameSize,
            ProcessPriority,
            ServerPort,
            SesolaParamsSize;

extern char  *AuthPromiscuousPwdPtr;

extern char  ControlBuffer[],
             DclCgiVariablePrefix[],
             HttpdServerNote[],
             LoggingFileName[],
             LoggingFormatUser[],
             LoggingPeriodName[],
             PersonaIdentName[],
             SesolaParams[],
             SoftwareID[];
 
extern HTTPD_PROCESS  HttpdProcess;

/****************************************************************************/
/*
This function allows images activated by a "foreign verb" to behave in a way 
that approximates the CLI$ (Command Line Interpreter) utility calls.  Get the 
entire command line following the verb that activated the image.  The command 
line is returned in uppercase, space compressed (i.e. maximum of one space 
between text elements, trimmed of leading and trailing spaces).  Returns a 
warning status if there were no parameters/qualifiers on the command line.
The variable CommandLine is global.
*/ 
 
int ParseCommandLine ()
 
{
   int  status;
   unsigned short  Length;
   unsigned long  Flags = 0;
   char  *cptr;
   $DESCRIPTOR (CommandLineDsc, CommandLine);
 
   if (!(cptr = getenv ("WASD_HTTPD_PARAM")) &&
       !(cptr = getenv ("HTTPD$PARAM")))
   {
      /* get the entire command line following the verb */
      if (VMSnok (status =
          lib$get_foreign (&CommandLineDsc, 0, &Length, &Flags)))
         return (status);
      CommandLine[Length] = '\0';
   }
   else
   {
      strncpy (CommandLine, cptr, sizeof(CommandLine));
      CommandLine[sizeof(CommandLine)-1] = '\0';
   }

   if (ParseCommand (CommandLine))
      return (SS$_NORMAL);
   else
      return (STS$K_ERROR | STS$M_INHIB_MSG);
}
 
/****************************************************************************/
/*
This function allows images activated by a "foreign verb" to behave in a way 
that approximates the CLI$ (Command Line Interpreter) utility calls.  Quoted 
strings are always indicated by being parsed to include a single leading 
quote.
*/ 
 
BOOL ParseCommand (char *CommandLine)
 
{
   BOOL  CommandLineOK = true;
   int  ParenCount = 0,
        QuoteCount = 0;
   char  *cptr, *eptr, *sptr, *zptr;
   char  Entity [1024] = "";
 
   /* set up any argument defaults */
   ParseCommandEntity (NULL);
 
#if WATCH_MOD
   if (strstr (CommandLine, "/WATCH") ||
       strstr (CommandLine, "/watch"))
   {
      /* convenience function for when /WATCHing */
      cptr = SysTrnLnm ("WASD_STARTUP_SERVER");
      if (cptr)
      {
         sptr = VmGet (strlen(CommandLine) + strlen(cptr));
         /* logical name first so that command-line can override */
         strcpy (sptr, cptr);
         strcat (sptr, CommandLine);
         CommandLine = sptr;
      }
   }
#endif /* WATCH_MOD */

   cptr = CommandLine;
   zptr = (eptr = Entity) + sizeof(Entity)-1;
 
   for (;;)
   {
      if (eptr >= zptr) exit (SS$_RESULTOVF);

      if (*cptr == '\"')
      {
         QuoteCount++;
         *eptr++ = *cptr++;
         continue;
      }
 
      if (QuoteCount & 1 && *cptr)
      {
         /* inside quoted text, copy all characters as literals */
         *eptr++ = *cptr++;
         continue;
      }
 
      if (*cptr == '(')
      {
         ParenCount++;
         *eptr++ = *cptr++;
         continue;
      }
 
      if (ParenCount && *cptr)
      {
         if (*cptr == ')') ParenCount--;
         *eptr++ = *cptr++;
         continue;
      }

      if ((Entity[0] && *cptr == '/') || ISLWS (*cptr) || !*cptr)
      {
         if (eptr >= zptr) exit (SS$_RESULTOVF);

         while (*cptr && ISLWS (*cptr)) cptr++;

         *eptr = '\0';
         if (Entity[0])
            if (!ParseCommandEntity (Entity))
                CommandLineOK = false;
 
         if (!*cptr) break;

         /* start of new entity */
         eptr = Entity;
         continue;
      }
 
      /* just copy any other character */
      *eptr++ = *cptr++;
   }
 
   if (QuoteCount & 1)
   {
      FaoToStdout ("%HTTPD-E-QUOTES, unbalanced quotes\n");
      return (false);
   }

   if (ParenCount)
   {
      FaoToStdout ("%HTTPD-E-PARENS, unbalanced parentheses\n");
      return (false);
   }

   return (CommandLineOK);
}
 
/*****************************************************************************/
/*
Get a string value from a qualifier, e.g. '/EXAMPLE=TEST'.
*/
 
BOOL ParseCommandString
(
char *Entity,
char *String,
BOOL Qualifier,
BOOL ReportErrors,
BOOL EnsureUpperCase
)
{
   int  QuoteCount = 0;
   char  *eptr, *sptr;
 
   eptr = Entity;
 
   if (Qualifier)
   {
      /* scan down to equate symbol */
      while (*eptr && *eptr != '=') eptr++;
      if (*eptr) eptr++;
      if (!*eptr)
      {
         if (ReportErrors)
            FaoToStdout ("%HTTPD-E-VALREQ, \
missing qualifier or keyword value\n \\!AZ\\\n",
               Entity+1);

         return (false);
      }
   }
 
   sptr = String;
   while (*eptr)
   {
      if (*eptr == '\"')
      {
         if (QuoteCount & 1)
         {
            /* are inside quotes, check for escaped quotes ("") */
            if (*++eptr != '\"')
            {
               /* now outside quotes */
               QuoteCount++;
            }
            /* drop thru to character copy */
         }
         else
         {
            /* now inside quotes */
            QuoteCount++;
            eptr++;
            continue;
         }
      }
 
      if (ISLWS(*eptr) && !(QuoteCount & 1))
         eptr++;
      else
      if (EnsureUpperCase)
         *sptr++ = to_upper(*eptr++);
      else
         *sptr++ = *eptr++;
   }
   *sptr = '\0';
 
   return (true);
}
 
/*****************************************************************************/
/*
Get an integer value from a qualifier, e.g. '/EXAMPLE=99'.
*/
 
BOOL ParseCommandInteger
(
char *Entity,
int *IntegerPtr,
int Base,
BOOL ReportErrors
)
{
   char  *eptr, *sptr;
 
   /* if not in the form /<string>=<integer> assume just an integer */
   if (*(eptr = Entity) == '/')
   {
      while (*eptr && *eptr != '=') eptr++;
      if (*eptr) eptr++;
   }
   if (*eptr)
   {
      *IntegerPtr = strtol (eptr, &sptr, Base);
      if (sptr > eptr && !*sptr)
         return (true);
      else
      {
         if (ReportErrors)
            FaoToStdout ("%HTTPD-E-BADVALUE, \
'!AZ' is an invalid keyword value\n",
               eptr);

         return (false);
      }
   }
   else
   {
      if (ReportErrors)
      {
         FaoToStdout ("%HTTPD-E-VALREQ, \
missing qualifier or keyword value\n \\!AZ\\\n",
            Entity+1);
      }
      return (false);
   }
}
 
/*****************************************************************************/
/*
A single command line "entity" has been parsed, check if its recognised.  This 
function is the one modified for the individual requirements of each program.
*/
 
BOOL ParseCommandEntity (char *Entity)
 
{
   BOOL  ok;
   int  Length;
   char  Scratch [256];
   char  *cptr, *sptr, *tptr, *zptr;

   if (!Entity)
   {
      /* set up any argument defaults */
      return (true);
   }

   ok = false;

   if (Entity[0] == '/')
   {
      if (strsame (Entity, "/ACCEPT=", 4))
      {
         ok = ParseCommandString (Entity, Scratch, true, true, true);
         for (cptr = Scratch; *cptr && ISLWS(*cptr); cptr++);
         if (!*cptr) return (ok);
         CliAcceptHostsPtr = VmGet (Length = strlen(Scratch)+1);
         memcpy (CliAcceptHostsPtr, Scratch, Length);
         return (ok);
      }

      if (strsame (Entity, "/AUTHORIZE=", 4))
      {
         for (cptr = Entity; *cptr && *cptr != '='; cptr++);
         if (*cptr) cptr++;
         while (*cptr)
         {
            while (*cptr == '(' || *cptr == ',' || *cptr == ')') cptr++;
            zptr = (sptr = Scratch) + sizeof(Scratch)-1;
            while (*cptr && *cptr != ',' && *cptr != ')' && sptr < zptr)
               *sptr++ = *cptr++;
            *sptr = '\0';
            if (!Scratch[0]) continue;

            if (strsame (Scratch, "ALL", -1))
               AuthPolicyAuthorizedOnly = ok = true;
            else
            if (strsame (Scratch, "SSL", -1))
               AuthPolicySslOnly = ok = true;
            else
            {
               CliIVKEYW (Scratch);
               return (false);
            }
         }
         if (!ok)
            CliVALREQ (Entity);

         return (true);
      }

      if (strsame (Entity, "/CACHE", 4))
      {
         CliCacheEnabled = true;
         CliCacheDisabled = false;
         return (true);
      }
      if (strsame (Entity, "/NOCACHE", 6))
      {
         CliCacheEnabled = false;
         CliCacheDisabled = true;
         return (true);
      }

      if (strsame (Entity, "/CGI_PREFIX=", 4))
      {
         ok = ParseCommandString (Entity, DclCgiVariablePrefix,
                                  true, true, true);
         DclCgiVariablePrefixLength = strlen(DclCgiVariablePrefix);
         return (ok);
      }

      if (strsame (Entity, "/CLUSTER=", 4) ||
          strsame (Entity, "/ALL=", 4))
      {
         ControlDoAllHttpd = true;
         for (cptr = Entity; *cptr && *cptr != '='; cptr++);
         if (!*cptr) return (true);
         /* optional grouping number */
         return (ParseCommandInteger (Entity, &InstanceEnvNumber, 10, true));
      }

      if (strsame (Entity, "/DCLCGIPLUSIN=", 6))
         return (ParseCommandInteger (Entity, &DclCgiPlusInSize, 10, true));

      if (strsame (Entity, "/DETACH", 4)) return (CliDetach = true);

      if (strsame (Entity, "/DO=", -1))
      {
         strzcpy (ControlBuffer, "HELP", ControlBufferSize);
         return (true);
      }

      if (strsame (Entity, "/DO=", 4))
         return (ParseCommandString (Entity, ControlBuffer, true, true, false));

#ifdef DBUG
      /* turns on all "if (Debug)" statements */
      if (strsame (Entity, "/DBUG", -1))
         return (Debug = true);
#endif

      if (strsame (Entity, "/DEMO", -1))
         return (CliDemo = true);

      if (strsame (Entity, "/ENV=", 4))
         return (ParseCommandInteger (Entity, &InstanceEnvNumber, 10, true));

      if (strsame (Entity, "/FORMAT=", 4))
         return (ParseCommandString (Entity, LoggingFormatUser,
                                     true, true, false));

      if (strsame (Entity, "/GBLSEC=DELETE", 15))
      {
         CliGblSecDelete = true;
         return (true);
      }
      if (strsame (Entity, "/GBLSEC=NOPERM", 15))
      {
         CliGblSecNoPerm = true;
         return (true);
      }

      if (strsame (Entity, "/IDENT=", 4))
         return (ParseCommandString (Entity, CliProcessIdentName,
                                     true, true, true));

      if (strsame (Entity, "/NOINSTANCE", 6))
         return (CliInstanceNoCrePrc = true);
      if (strsame (Entity, "/INSTANCE=", 4))
      {
         for (cptr = Entity; *cptr && *cptr != '='; cptr++);
         if (*cptr) cptr++;
         while (*cptr)
         {
            while (*cptr == '(' || *cptr == ',' || *cptr == ')') cptr++;
            zptr = (sptr = Scratch) + sizeof(Scratch)-1;

            while (*cptr && *cptr != ',' && *cptr != ')' && sptr < zptr)
               *sptr++ = *cptr++;
            *sptr = '\0';
            if (!Scratch[0]) continue;

            if (ok = strsame (Scratch, "CONFIG", 3))
               CliInstanceConfig = true;
            else
            if (ok = strsame (Scratch, "CPU", 3))
               CliInstanceMax = INSTANCE_PER_CPU;
            else
            if (ok = strsame (Scratch, "PASSIVE", 3))
               CliInstancePassive = true;
            else
            if (isdigit(Scratch[0]) || Scratch[0] == '-')
            {
               ok =  ParseCommandInteger (Scratch, &CliInstanceMax, 10, true);
               if (ok && CliInstanceMax == 0) CliInstanceMax = -999;
            }
            else
            {
               CliIVKEYW (Scratch);
               return (false);
            }
         }
         if (!ok)
         {
            CliVALREQ (Entity);
            return (false);
         }
         return (true);
      }

      if (strsame (Entity, "/LOG=", 4))
      {
         CliLoggingEnabled = true;
         CliLoggingDisabled = false;
         if (Entity[4] == '=')
            return (ParseCommandString (Entity, LoggingFileName,
                                        true, true, true));
         else
            return (true);
      }
      if (strsame (Entity, "/NOLOG", 6))
      {
         CliLoggingEnabled = false;
         CliLoggingDisabled = true;
         return (true);
      }

      if (strsame (Entity, "/MONITOR", 4))
      {
         CliMonitorEnabled = true;
         CliMonitorDisabled = false;
         return (true);
      }
      if (strsame (Entity, "/NOMONITOR", 6))
      {
         CliMonitorEnabled = false;
         CliMonitorDisabled = true;
         return (true);
      }

      if (strsame (Entity, "/NETWORK", 5))
      {
         CliNetworkMode = true;
         CliNoNetworkMode = false;
         return (true);
      }
      if (strsame (Entity, "/NONETWORK", 5))
      {
         CliNetworkMode = false;
         CliNoNetworkMode = true;
         return (true);
      }

      if (strsame (Entity, "/NETBUF=", 5))
         return (ParseCommandInteger (Entity, &NetReadBufferSize, 10, true));

      if (strsame (Entity, "/NOTE=", 6))
      {
         strzcpy (HttpdServerNote, "/NOTE=", HttpdServerNoteSize);
         return (ParseCommandString (Entity, HttpdServerNote+6,
                                     true, true, false));
      }

      if (strsame (Entity, "/ODS5", -1))
      {
         CliOdsExtendedEnabled = true;
         CliOdsExtendedDisabled = false;
         return (true);
      }
      if (strsame (Entity, "/NOODS5", -1))
      {
         CliOdsExtendedEnabled = false;
         CliOdsExtendedDisabled = true;
         return (true);
      }

      if (strsame (Entity, "/OUTBUF=", 5))
         return (ParseCommandInteger (Entity, &OutputBufferSize, 10, true));

      if (strsame (Entity, "/OUTPUT=", 4))
      {
         ok = ParseCommandString (Entity, Scratch, true, true, true);
         for (cptr = Scratch; *cptr && ISLWS(*cptr); cptr++);
         if (!*cptr) return (ok);
         CliOutputPtr = VmGet (Length = strlen(Scratch)+1);
         memcpy (CliOutputPtr, Scratch, Length);
         return (ok);
      }

      if (strsame (Entity, "/PERIOD=", 5))
         return (ParseCommandString (Entity, LoggingPeriodName,
                                     true, true, false));

      if (strsame (Entity, "/PERSONA=", 5))
      {
         for (cptr = Entity; *cptr && *cptr != '='; cptr++);
         if (!*cptr) return (CliPersonaEnabled = true);
         cptr++;
         while (*cptr)
         {
            while (*cptr == '(' || *cptr == ',' || *cptr == ')') cptr++;
            zptr = (sptr = Scratch) + sizeof(Scratch)-1;
            while (*cptr && *cptr != ',' && *cptr != ')' && sptr < zptr)
               *sptr++ = *cptr++;
            *sptr = '\0';
            if (!Scratch[0]) continue;

            if (strsame (Scratch, "AUTHORIZED", 4))
               CliPersonaAuthorized = true;
            else
            if (strsame (Scratch, "RELAXED", 5))
            {
               CliPersonaRelaxed = true;
               for (sptr = Scratch; *sptr && *sptr != '='; sptr++);
               if (strsame (sptr, "=AUTHORIZED", 5))
                  CliPersonaRelaxedAuthorized = true;
            }
            else
            if (strsame (Scratch, "IDENT=", 6))
            {
               strzcpy (PersonaIdentName, Scratch+6, PersonaIdentNameSize);
               for (sptr = PersonaIdentName; *sptr; sptr++)
                  *sptr = to_upper(*sptr);
            }
            else
            {
               strzcpy (PersonaIdentName, Scratch, PersonaIdentNameSize);
               for (sptr = PersonaIdentName; *sptr; sptr++)
                  *sptr = to_upper(*sptr);
            }
         }
         return (CliPersonaEnabled = true);
      }
      if (strsame (Entity, "/NOPERSONA", 5))
      {
         CliPersonaAuthorized = CliPersonaEnabled =
            CliPersonaRelaxed = CliPersonaRelaxedAuthorized = false;
         return (true);
      }

      if (strsame (Entity, "/PORT=", 4))
         return (ParseCommandInteger (Entity, &CliServerPort, 10, true));

      if (strsame (Entity, "/PRIORITY=", 4))
         return (ParseCommandInteger (Entity, &ProcessPriority, 10, true));

      if (strsame (Entity, "/PROFILE=", 5))
      {
         for (cptr = Entity; *cptr && *cptr != '='; cptr++);
         if (*cptr)
         {
            cptr++;
            if (strsame (cptr, "NORULE", -1))
               AuthVmsUserProfileNoRule = true;
            else
            {
               CliIVKEYW (cptr);
               return (AuthVmsUserProfileEnabled = false);
            }
         }
         return (AuthVmsUserProfileEnabled = true);
      }
      if (strsame (Entity, "/NOPROFILE", 6))
      {
         AuthVmsUserProfileEnabled = false;
         return (true);
      }

      /* for testing only, also needs /SYSUAF=PROMSICUOUS if using SYSUAF! */
      if (strsame (Entity, "/PROMISCUOUS=", 5))
      {
         for (cptr = Entity; *cptr && *cptr != '='; cptr++);
         if (*cptr)
         {
            cptr++;
            AuthPromiscuousPwdPtr = VmGet (Length = strlen(cptr)+1);
            memcpy (AuthPromiscuousPwdPtr, cptr, Length);
         }
         return (AuthPromiscuous = true);
      }
      if (strsame (Entity, "/NOPROMISCUOUS", 7))
      {
         AuthPromiscuous = false;
         return (true);
      }

      if (strsame (Entity, "/REJECT=", 4))
      {
         ok = ParseCommandString (Entity, Scratch, true, true, true);
         for (cptr = Scratch; *cptr && ISLWS(*cptr); cptr++);
         if (!*cptr) return (ok);
         CliRejectHostsPtr = VmGet (Length = strlen(Scratch)+1);
         memcpy (CliRejectHostsPtr, Scratch, Length);
         return (ok);
      }

      if (strsame (Entity, "/SCRIPT=AS=", 4))
      {
         for (cptr = Entity; *cptr && *cptr != '='; cptr++);
         if (*cptr) cptr++;
         while (*cptr && *cptr != '=') cptr++;
         if (*cptr)
         {
            cptr++;
            zptr = (sptr = CliScriptAs) + sizeof(CliScriptAs)-1;
            while (*cptr && sptr < zptr) *sptr++ = to_upper(*cptr++);
            *sptr = '\0';
            if (CliScriptAs[0]) return (true);
         }
         CliVALREQ (Entity);
         return (false);
      }

      if (strsame (Entity, "/SERVICES=", 4))
         return (ParseCommandString (Entity, CliServices, true, true, false));

      /* 
         This qualifier may come in handy if having to use VMS or WASD
         causes embarressment :^)  Seriously, if you consider providing
         the server identification a security issue use this qualifier
         to set it to something like "Mine", "Unknown", "?", etc.
      */
      if (strsame (Entity, "/SOFTWAREID=", 4))
      {
         CliSoftwareID = true;
         return (ParseCommandString (Entity, SoftwareID, true, true, false));
      }

      if (strsame (Entity, "/SSL=", 4))
         return (ParseCommandString (Entity, SesolaParams, true, true, false));

      if (strsame (Entity, "/NOSSL", 6))
      {
         strzcpy (SesolaParams, "/NOSSL", SesolaParamsSize);
         return (true);
      }

      if (strsame (Entity, "/SUBBUF=", 5))
         return (ParseCommandInteger (Entity, &DclSysOutputSize, 10, true));

      if (strsame (Entity, "/SWAP", -1))
      {
         NoSwapOut = false;
         return (true);
      }
      if (strsame (Entity, "/NOSWAP", -1))
         return (NoSwapOut = true);

      if (strsame (Entity, "/SYSPLUS", -1))
         return (CliSysPlus = true);

      if (strsame (Entity, "/SYSPRV", -1))
         return (OperateWithSysPrv = true);
      if (strsame (Entity, "/NOSYSPRV", -1))
      {
         OperateWithSysPrv = false;
         return (true);
      }

      if (strsame (Entity, "/SYSUAF=", 4))
      {
         AuthSysUafEnabled = true;
         for (cptr = Entity; *cptr && *cptr != '='; cptr++);
         if (!*cptr) return (true);
         cptr++;
         while (*cptr)
         {
            while (*cptr == '(' || *cptr == ',' || *cptr == ')') cptr++;
            zptr = (sptr = Scratch) + sizeof(Scratch)-1;
            while (*cptr && *cptr != ',' && *cptr != ')' && sptr < zptr)
               *sptr++ = *cptr++;
            *sptr = '\0';
            if (!Scratch[0]) continue;

            if (strsame (Scratch, "ALL", -1) ||
                strsame (Scratch, "RELAXED", -1))
               AuthPolicySysUafRelaxed = true;
            else
            if (strsame (Scratch, "ID", -1))
               AuthPolicySysUafIdentifiers = true;
            else
            if (strsame (Scratch, "PROXY", -1))
               AuthPolicySysUafProxy = true;
            else
            if (strsame (Scratch, "SSL", -1))
               AuthPolicySysUafSslOnly = true;
            else
            if (strsame (Scratch, "VMS", -1))
               AuthPolicySysUafVms = true;
            else
            if (strsame (Scratch, "WASD", -1) ||
                strsame (Scratch, "WASD_ID", -1))
               AuthPolicySysUafWasdIdentifiers = true;
            else
            {
               CliIVKEYW (Scratch);
               return (AuthSysUafEnabled = false);
            }
         }

         return (true);
      }
      if (strsame (Entity, "/NOSYSUAF", 6))
      {
         AuthSysUafEnabled = false;
         return (true);
      }

      /* for testing SYSUAF-authenticated security profiles purposes only */
      if (strsame (Entity, "/SYSUAF=PROMISCUOUS", -1))
         return (AuthSysUafEnabled = AuthSysUafPromiscuous = true);

      if (strsame (Entity, "/TESTS", 4))
         return (CliTests = true);

      if (strsame (Entity, "/USER=", 4))
         return (ParseCommandString (Entity, CliUserName, true, true, true));

      if (strsame (Entity, "/VALBLK=", 5))
      {
         /* used without a value set to the default value block size */
         CliLockValueBlockSize = -1;
         ParseCommandInteger (Entity, &CliLockValueBlockSize, 10, false);
         if (CliLockValueBlockSize == -1 ||
             CliLockValueBlockSize == 16 ||
             CliLockValueBlockSize == 64) return (true);
         CliIVKEYW (Entity);
         CliLockValueBlockSize = 0;
         return (false);
      }

      if (strsame (Entity, "/VERSION", 4))
         return (CliVersion = true);

      if (strsame (Entity, "/WATCH=", 4) ||
          strsame (Entity, "/NOWATCH", 6))
         return (WatchCliParse (Entity));

      if (strsame (Entity, "/ZERO", -1))
         return (AccountingZeroOnStartup = true);
      if (strsame (Entity, "/NOZERO", -1))
      {
         AccountingZeroOnStartup = false;
         return (true);
      }

      for (sptr = Entity; *sptr && *sptr != '='; sptr++);
      *sptr = '\0';
      CliIVQUAL (Entity+1);
      return (false);
   }
 
   if (!CliParameter[0])
      return (ParseCommandString (Entity, CliParameter, false, true, false));

   FaoToStdout ("%HTTPD-E-MAXPARM, too many parameters\n \\!AZ\\\n", Entity);
   return (false);
}
   
/*****************************************************************************/
/*
*/
 
BOOL CliIVQUAL (char *qual)
 
{
   FaoToStdout ("%HTTPD-E-IVQUAL, unrecognised qualifier\n \\!AZ\\\n", qual);
   return (false);
}

/*****************************************************************************/
/*
*/
 
BOOL CliIVKEYW (char *keyw)
 
{
   FaoToStdout ("%HTTPD-E-IVKEYW, unrecognized keyword\n \\!AZ\\\n", keyw);
   return (false);
}

/*****************************************************************************/
/*
*/
 
BOOL CliVALREQ (char *qual)
 
{
   FaoToStdout ("%HTTPD-E-VALREQ, missing qualifier or keyword value\n \
\\!AZ\\\n", qual);
   return (false);
}

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