/*****************************************************************************/
/*
                               AuthConfig.c


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

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

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

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


This module handles authentication/authorization configuration.  It reads
configuration files, creates the configuration database (linked list), checking
paths against that database, and reports surrounding it.

See AUTH.C for overall detail on the WASD authorization environment.


VERSION HISTORY
---------------
18-MAR-2004  MGD  ACME authentication
01-SEP-2003  MGD  path 'final' keyword to conclude further rule mapping,
                  refine category WATCH rule reporting
26-AUG-2003  MGD  service directory located databases
18-JUN-2003  MGD  restore pre-v8.3 /SYSUAF behaviour
15-MAY-2003  MGD  break-in parameters (default to LGI sysgen parameters)
05-MAY-2003  MGD  regular expression support
03-MAY-2003  MGD  /SYSUAF=(VMS,ID) allows both VMS and ID authorization
                  (rules with =VMS and =ID can be concurrently deployed)
26-MAR-2003  MGD  refine rule failure handling and reporting
15-MAR-2003  MGD  script as SYSUAF username can be requested with rule
30-JAN-2003  MGD  authentication profile can be requested with rule
07-DEC-2002  MGD  skeleton key for admin menu
12-OCT-2002  MGD  refine metacon reporting
08-APR-2002  MGD  bugfix; AuthConfigProxyMap() wildcard string results
02-MAR-2002  MGD  bugfix; identify host group without "=host"
14-OCT-2001  MGD  meta-config
04-AUG-2001  MGD  support module WATCHing
28-APR-2001  MGD  proxy syntax changed from LOCAL=REMOTE to REMOTE=LOCAL
25-APR-2001  MGD  bugfix; proxy processing (bit brain-dead in spots!)
13-APR-2001  MGD  bugfix; conditions for NetThisVirtualService() call
11-MAR-2001  MGD  bugfix; add CONNECT method to access controls
28-FEB-2001  MGD  OdsLoadTextFile(), OdsParseTextFile(), [IncludeFile]
13-FEB-2001  MGD  authentication via "identification protocol" RFC1413,
                  proxy processing (as if authenticated via the SYSUAF)
10-DEC-2000  MGD  X509 (Client Certificate) realm
02-OCT-2000  MGD  flush the persona cache when (re)loading
01-SEP-2000  MGD  AuthConfigSearch() passed the path as a parameter
13-AUG-2000  MGD  bugfix; AuthConfigSearch() quick index
06-MAY-2000  MGD  proxy authorization requires changes to path searching
18-MAR-2000  MGD  bugfix; lexicographic cut-off point
16-MAR-2000  MGD  bugfix; AuthSearchConfig() removed '.' (?!) from match code
04-MAR-2000  MGD  use NetWriteFaol(), et.al.
02-JAN-2000  MGD  config file opened via ODS module
20-NOV-1999  MGD  add nil-access identifier to bypass hour restrictions
28-AUG-1999  MGD  unbundled from AUTH.C for v6.1
*/
/*****************************************************************************/

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

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

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

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

#define WASD_MODULE "AUTHCONFIG"

#if WATCH_MOD
#define FI_NOLI WASD_MODULE, __LINE__
#else
/* in production let's keep the exact line to ourselves! */
#define FI_NOLI WASD_MODULE, 0
#endif


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

BOOL  AuthorizationEnabled,
      AuthConfigACME,
      AuthFatalProblem,
      AuthPolicyAuthorizedOnly,
      AuthPolicySysUafRelaxed,
      AuthPolicySslOnly,
      AuthPolicySysUafIdentifiers,
      AuthPolicySysUafProxy,
      AuthPolicySysUafSslOnly,
      AuthPolicySysUafVms,
      AuthPolicySysUafWasdIdentifiers,
      AuthPromiscuous,
      AuthProtectRule,
      AuthSysUafEnabled,
      AuthSysUafPromiscuous,
      AuthVmsUserProfileEnabled,
      AuthVmsUserProfileNoRule;

int  AuthFailureLimit,
     AuthFailurePeriodSeconds,
     AuthFailureTimeoutSeconds;

unsigned long  AuthHttpsOnlyVmsIdentifier,
               AuthNilAccessVmsIdentifier,
               AuthPasswordChangeVmsIdentifier,
               AuthProxyAccessVmsIdentifier,
               AuthWasdPwdVmsIdentifier,
               AuthWasdHttpsVmsIdentifier,
               AuthWasdReadVmsIdentifier,
               AuthWasdWriteVmsIdentifier;

char  *AuthPromiscuousPwdPtr;

AUTH_CONFIG_META  AuthMeta;
AUTH_CONFIG_META  *AuthMetaPtr;

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

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

extern BOOL  AuthAcmeLinked;

extern int  EfnWait,
            ServerPort;

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

extern ACCOUNTING_STRUCT  *AccountingPtr;
extern CONFIG_STRUCT  Config;
extern HTTPD_GBLSEC  *HttpdGblSecPtr;
extern META_CONFIG  *MetaGlobalAuthPtr;
extern MSG_STRUCT  Msgs;
extern SYS_INFO  SysInfo;
extern WATCH_STRUCT  Watch;

/*****************************************************************************/
/*
Initialize the authentication/authorization environment.  Is used at server
startup and when the authorization configuration is reloaded.
*/ 

AuthConfigInit ()

{
   static unsigned long  LgiBrkTmo,
                         LgiBrkLim,
                         LgiHidTim;
   static VMS_ITEM_LIST3 SyiItem [] =
   {
     { sizeof(LgiBrkTmo), SYI$_LGI_BRK_TMO, &LgiBrkTmo, 0 },
     { sizeof(LgiBrkLim), SYI$_LGI_BRK_LIM, &LgiBrkLim, 0 },
     { sizeof(LgiHidTim), SYI$_LGI_HID_TIM, &LgiHidTim, 0 },
     { 0,0,0,0 }
   };

   int  status;
   IO_SB  IOsb;

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

   if (WATCH_MODULE(WATCH_MOD_AUTH))
      WatchThis (NULL, FI_LI, WATCH_MOD_AUTH, "AuthConfigInit()");

   /* if ACME available and linked into this image */
   if (SysInfo.VersionInteger >= 730 && AuthAcmeLinked) AuthConfigACME = true;

   MetaConUnload (&MetaGlobalAuthPtr, NULL);

   AuthConfigLoad (&MetaGlobalAuthPtr);

   status = sys$getsyiw (EfnWait, 0, 0, &SyiItem, &IOsb, 0, 0);
   if (VMSok (status)) status = IOsb.Status;
   if (VMSnok (status)) ErrorExitVmsStatus (status, "sys$getsyiw()", FI_LI);

   if (WATCH_MODULE(WATCH_MOD_AUTH))
      WatchThis (NULL, FI_LI, WATCH_MOD_AUTH, "LGI !UL !UL !UL",
                 LgiBrkTmo, LgiBrkLim, LgiHidTim);

   if (Config.cfAuth.FailureLimit)
      AuthFailureLimit = Config.cfAuth.FailureLimit;
   else
      AuthFailureLimit = LgiBrkLim;
   if (Config.cfAuth.FailurePeriodSeconds)
      AuthFailurePeriodSeconds = Config.cfAuth.FailurePeriodSeconds;
   else
      AuthFailurePeriodSeconds = LgiBrkTmo;
   if (Config.cfAuth.FailureTimeoutSeconds)
      AuthFailureTimeoutSeconds = Config.cfAuth.FailureTimeoutSeconds;
   else
      AuthFailureTimeoutSeconds = LgiHidTim;
}

/*****************************************************************************/
/*
Load authorization rules into meta-config structure.
*/ 
 
int AuthConfigLoad (META_CONFIG **MetaConPtrPtr)

{
   int  status;

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

   if (WATCH_MODULE(WATCH_MOD_AUTH))
      WatchThis (NULL, FI_LI, WATCH_MOD_AUTH,
                 "AuthConfigLoad() !AZ", CONFIG_AUTH_FILE_NAME);

   status = MetaConLoad (MetaConPtrPtr, CONFIG_AUTH_FILE_NAME,
                         &AuthConfigLoadCallBack, true, true);
   if (*MetaConPtrPtr == MetaGlobalAuthPtr)
   {
      /* server startup/reload */
      MetaConStartupReport (MetaGlobalAuthPtr, "AUTH");
      if (VMSnok (status)) exit (status);
   }
   return (status);
}

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

{
   int  status;

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

   if (WATCH_MODULE(WATCH_MOD_AUTH))
      WatchThis (NULL, FI_LI, WATCH_MOD_AUTH, "AuthConfigUnload()");

   if (mcptr->AuthMetaPtr)
   {
      if (mcptr->AuthMetaPtr == AuthMetaPtr)
      {
         memset (AuthMetaPtr, 0, sizeof(AUTH_CONFIG_META));
         AuthMetaPtr = NULL;
      }
      else
         VmFree (mcptr->AuthMetaPtr, FI_LI);
      mcptr->AuthMetaPtr = NULL;
   }
}

/*****************************************************************************/
/*
Called by MetaConUnload() callback for each line's associated data, basically
to check for a regular expression structure and free it if present, then just
dispose of the line data itself.
*/ 
 
AuthConfigUnloadLineData (void *LineDataPtr)

{
   int  status;
   AUTH_PATH  *apptr;

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

   if (WATCH_MODULE(WATCH_MOD_AUTH))
      WatchThis (NULL, FI_LI, WATCH_MOD_AUTH,
                 "AuthConfigUnloadLineData()");

   apptr = (AUTH_PATH*)LineDataPtr;
   if (apptr->RegexPregPath.buffer) regfree (&apptr->RegexPregPath);
   VmFree (apptr, FI_LI);
}

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

BOOL AuthConfigLoadCallBack (META_CONFIG *mcptr)

{
   static char  ProblemOverflow [] = "Storage overflow",
                ProblemProxyFile [] = "Error opening proxy file";

   int  status;
   char  *cptr, *sptr, *zptr;
   char  Scratch  [256],
         StringBuffer [1024];
   AUTH_CONFIG_META  *acptr;
   METACON_LINE  *mclptr;
   ODS_STRUCT  ProxyFileOds;

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

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

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

   /* if this is during server startup/reload set the global service pointer */
   if (mcptr == MetaGlobalAuthPtr)
      acptr = mcptr->AuthMetaPtr = AuthMetaPtr = &AuthMeta;
   else
   /* if a report then conjure one up by divine fiat */
   if (!mcptr->AuthMetaPtr)
      acptr = mcptr->AuthMetaPtr = VmGet (sizeof(AUTH_CONFIG_META));
   else
      /* not the first time through */
      acptr = mcptr->AuthMetaPtr;

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

      AuthConfigProxy (NULL, NULL);
      AuthConfigLine (NULL, NULL);

      /* if global service pointer, during server startup or reload */
      if (mcptr == MetaGlobalAuthPtr)
         memset (acptr, 0, sizeof(AUTH_CONFIG_META));

      return (true);
   }

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

      AuthConfigOther (mcptr);
      AuthConfigProxy (NULL, NULL);
      AuthConfigLine (NULL, NULL);

      return (true);
   }

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

   /* if it's not text/inline then authorization is not interested in it */
   if (mclptr->Token != METACON_TOKEN_TEXT && !mclptr->InlineTextPtr)
      return (true);

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

   if (strsame (cptr, "[AuthProxy]", 11))
   {
      /* SYSUAF proxy mapping(s) */
      AuthConfigProxy (mcptr, cptr+11);
      return (true);
   }

   if (strsame (cptr, "[AuthProxyFile]", 15))
   {
      /* file of SYSUAF proxy mappings, terminate the filename */
      cptr += 15;
      while (*cptr && ISLWS(*cptr)) cptr++;
      for (sptr = cptr; *sptr && !ISLWS(*sptr); sptr++);
      *sptr = '\0';

      /* a proxy file specification */
      status = OdsLoadTextFile (&ProxyFileOds, cptr);
      if (VMSnok (status))
      {
         MetaConReport (mcptr, METACON_REPORT_ERROR, ProblemProxyFile);
         WriteFao (Scratch, sizeof(Scratch), NULL,
                   "ERROR=%X!XL@!AZ", status, cptr);
         AuthConfigProxy (mcptr, Scratch);
         return (true);
      }

      /* pass each line feeding to the proxy configurator */
      for (;;)
      {
         cptr = OdsParseTextFile (&ProxyFileOds, '\\');
         if (!cptr) break;
         /* if a blank or comment line, then ignore */
         while (ISLWS(*cptr)) cptr++;
         if (!*cptr || *cptr == '!' || *cptr == '#') continue;
         AuthConfigProxy (mcptr, cptr);
      }

      return (true);
   }

   AuthConfigLine (mcptr, cptr);
   return (true);
}

/*****************************************************************************/
/*
Other things that need initing after the rules have been loaded.
*/ 

AuthConfigOther (META_CONFIG *mcptr)

{
   static $DESCRIPTOR (AuthHttpsOnlyVmsIdentifierDsc, AUTH_HTTPS_ONLY_VMS_ID);
   static $DESCRIPTOR (AuthNilAccessVmsIdentifierDsc, AUTH_NIL_ACCESS_VMS_ID);
   static $DESCRIPTOR (AuthPasswordChangeVmsIdentifierDsc,
                       AUTH_PASSWORD_CHANGE_VMS_ID);
   static $DESCRIPTOR (AuthProxyAccessVmsIdentifierDsc,
                       AUTH_PROXY_ACCESS_VMS_ID);
   static $DESCRIPTOR (AuthWasdHttpsVmsIdentifierDsc, AUTH_WASD_HTTPS_VMS_ID);
   static $DESCRIPTOR (AuthWasdPwdVmsIdentifierDsc, AUTH_WASD_PWD_VMS_ID);
   static $DESCRIPTOR (AuthWasdReadVmsIdentifierDsc, AUTH_WASD_READ_VMS_ID);
   static $DESCRIPTOR (AuthWasdWriteVmsIdentifierDsc, AUTH_WASD_WRITE_VMS_ID);

   int  status;

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

   if (WATCH_MODULE(WATCH_MOD_AUTH))
      WatchThis (NULL, FI_LI, WATCH_MOD_AUTH, "AuthConfigOther()");

   /* but only if we're dealing with the global configuration */
   if (mcptr != MetaGlobalAuthPtr) return;

   /* it's implied! */
   if (AuthPolicySysUafWasdIdentifiers) AuthPolicySysUafIdentifiers = true;

   if (AuthPromiscuous)
      MetaConReport (mcptr, METACON_REPORT_WARNING,
"!AZ authenticating any username with !&?specified\rany\r password!!",
                     AUTH_REALM_PROMISCUOUS, AuthPromiscuousPwdPtr);

   if (AuthPolicyAuthorizedOnly)
      MetaConReport (mcptr, METACON_REPORT_INFORM,
                     "All request paths must be authorized");

   if (AuthPolicySslOnly)
      MetaConReport (mcptr, METACON_REPORT_INFORM,
                     "Only SSL (https:) will be authorized");

   if (AuthSysUafPromiscuous)
      MetaConReport (mcptr, METACON_REPORT_WARNING,
                     "SYSUAF !AZ (testing only)\n",
                     AUTH_REALM_PROMISCUOUS);

   if (AuthSysUafEnabled)
   {
      if (AuthPolicySysUafIdentifiers)
      {
         if (AuthPolicySysUafVms)
            MetaConReport (mcptr, METACON_REPORT_INFORM,
               "SYSUAF authentication enabled (rights identifier available)");
         else
            MetaConReport (mcptr, METACON_REPORT_INFORM,
               "SYSUAF authentication enabled (only via rights identifier)");
      }
      else
      if (AuthPolicySysUafWasdIdentifiers)
         MetaConReport (mcptr, METACON_REPORT_INFORM,
            "SYSUAF authentication enabled (via WASD identifier - deprecated)");
      else
         MetaConReport (mcptr, METACON_REPORT_INFORM,
                        "SYSUAF authentication enabled");
   }

   if (AuthVmsUserProfileEnabled)
   {
      if (AuthVmsUserProfileNoRule)
         MetaConReport (mcptr, METACON_REPORT_INFORM,
            "VMS security profile enabled (pre-8.2 behaviour)");
      else
         MetaConReport (mcptr, METACON_REPORT_INFORM,
            "VMS security profile enabled (only by authorization rule)");
   }

   if (AuthPolicySysUafIdentifiers)
   {
      status = sys$asctoid (&AuthHttpsOnlyVmsIdentifierDsc,
                            &AuthHttpsOnlyVmsIdentifier, 0);
      if (VMSnok (status))
         MetaConReport (mcptr, METACON_REPORT_INFORM,
                        "Optional identifier !AZ\n%!&M",
                        AUTH_HTTPS_ONLY_VMS_ID, status);

      status = sys$asctoid (&AuthNilAccessVmsIdentifierDsc,
                            &AuthNilAccessVmsIdentifier, 0);
      if (VMSnok (status))
         MetaConReport (mcptr, METACON_REPORT_INFORM,
                        "Optional identifier !AZ\n%!&M",
                        AUTH_NIL_ACCESS_VMS_ID, status);

      status = sys$asctoid (&AuthPasswordChangeVmsIdentifierDsc,
                            &AuthPasswordChangeVmsIdentifier, 0);
      if (VMSnok (status))
         MetaConReport (mcptr, METACON_REPORT_INFORM,
                        "Optional identifier !AZ\n%!&M",
                        AUTH_PASSWORD_CHANGE_VMS_ID, status);

      if (AuthPolicySysUafProxy &&
          (AuthPolicySysUafIdentifiers || AuthPolicySysUafWasdIdentifiers))
      {
         status = sys$asctoid (&AuthProxyAccessVmsIdentifierDsc,
                               &AuthProxyAccessVmsIdentifier, 0);
         if (VMSnok (status))
            MetaConReport (mcptr, METACON_REPORT_ERROR,
                           "Required identifier !AZ\n%!&M",
                           AUTH_PROXY_ACCESS_VMS_ID, status);
      }
   }

   if (AuthPolicySysUafWasdIdentifiers)
   {
      status = sys$asctoid (&AuthWasdWriteVmsIdentifierDsc,
                            &AuthWasdWriteVmsIdentifier, 0);
      if (VMSnok (status))
         MetaConReport (mcptr, METACON_REPORT_ERROR,
                        "Required identifier !AZ\n%!&M",
                        AUTH_WASD_WRITE_VMS_ID, status);

      status = sys$asctoid (&AuthWasdReadVmsIdentifierDsc,
                            &AuthWasdReadVmsIdentifier, 0);
      if (VMSnok (status))
         MetaConReport (mcptr, METACON_REPORT_ERROR,
                        "Required identifier !AZ\n%!&M",
                        AUTH_WASD_READ_VMS_ID, status);

      status = sys$asctoid (&AuthWasdHttpsVmsIdentifierDsc,
                            &AuthWasdHttpsVmsIdentifier, 0);
      if (VMSnok (status))
         MetaConReport (mcptr, METACON_REPORT_ERROR,
                        "Required identifier !AZ\n%!&M",
                        AUTH_WASD_HTTPS_VMS_ID, status);

      status = sys$asctoid (&AuthWasdPwdVmsIdentifierDsc,
                            &AuthWasdPwdVmsIdentifier, 0);
      if (VMSnok (status))
         MetaConReport (mcptr, METACON_REPORT_ERROR,
                        "Required identifier !AZ\n%!&M",
                        AUTH_WASD_PWD_VMS_ID, status);
   }

   if (!(Config.cfAuth.BasicEnabled || Config.cfAuth.DigestEnabled))
      MetaConReport (mcptr, METACON_REPORT_WARNING,
                     "Neither BASIC or DIGEST authentication enabled");

   if (!AuthorizationEnabled)
      MetaConReport (mcptr, METACON_REPORT_WARNING, "AUTHORIZATION DISABLED");

   /* initialize the authentication cache */
   AuthCacheInit (mcptr);

   /* also flush the server persona cache */
   PersonaCache (NULL, 0);
}

/*****************************************************************************/
/*
Process a line of authorization configuration.
*/

#define ACCESS_RESTRICTION_LIST_SIZE 256
#define REALM_PARAMETER_SIZE AUTH_MAX_REALM_PARAM_LENGTH+1

AuthConfigLine
(
META_CONFIG *mcptr,
char *Line
)
{
   static BOOL  RealmSpecified;

   BOOL  FinalRule,
         NoCache,
         PathProblem,
         PathIsRegex,
         RealmProblem,
         VmsUserProfile,
         VmsUserScriptAs;
   int  status;
   int  SourceGroupRead,
        SourceGroupWrite,
        SourceRealm;
   unsigned long  *CanFlagsPtr;
   unsigned long  GroupCanFlags,
                  GroupReadVmsIdentifier,
                  GroupWriteVmsIdentifier,
                  RealmCanFlags,
                  RealmVmsIdentifier,
                  WorldCanFlags;
   char  *cptr, *lptr, *sptr, *zptr,
         *PathTemplatePtr,
         *ProxyStringPtr,
         *RestrictListPtr;
   char  GroupRead [AUTH_MAX_REALM_GROUP_LENGTH+1],
         GroupRestrictList [ACCESS_RESTRICTION_LIST_SIZE],
         GroupWrite [AUTH_MAX_REALM_GROUP_LENGTH+1],
         Realm [AUTH_MAX_REALM_GROUP_LENGTH+1],
         RealmDescription [AUTH_MAX_REALM_DESCR_LENGTH+1],
         RealmCanString [256],
         RealmParameter [REALM_PARAMETER_SIZE],
         WorldRestrictList [ACCESS_RESTRICTION_LIST_SIZE];
   METACON_LINE  *mclptr;
   regex_t  RegexPreg;

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

   if (WATCH_MODULE(WATCH_MOD_AUTH))
      WatchThis (NULL, FI_LI, WATCH_MOD_AUTH, "AuthConfigLine() !&Z", Line);

   if (!Line )
   {
      RealmSpecified = false;
      return;
   }

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

   FinalRule = NoCache = PathProblem = RealmProblem =
      VmsUserProfile = VmsUserScriptAs = false;
   GroupCanFlags = GroupReadVmsIdentifier = GroupWriteVmsIdentifier =
      RealmVmsIdentifier = RealmCanFlags =
      SourceGroupRead = SourceGroupWrite = WorldCanFlags = 0;
   PathTemplatePtr = NULL;
   ProxyStringPtr = "";
   GroupRead[0] = GroupWrite[0] = GroupRestrictList[0] =
      Realm[0] = RealmDescription[0] = RealmCanString[0] = RealmParameter[0] =
      WorldRestrictList[0] = '\0';
   CanFlagsPtr = &GroupCanFlags;
   RestrictListPtr = &GroupRestrictList;

   lptr = Line;

   if (*lptr == '[' || strsame (lptr, "REALM", 5))
   {
      /*********/
      /* realm */
      /*********/

      if (*lptr == '[')
      {
         lptr++;
         while (*lptr && ISLWS(*lptr)) lptr++;
      }
      else
      {
         /* skip over keyword and find start of realm name */
         while (*lptr && !ISLWS(*lptr)) lptr++;
         while (*lptr && ISLWS(*lptr)) lptr++;
      }

      /* new realm resets proxy mappings */
      AuthConfigProxy (NULL, NULL);

      /* by default (and historically) it's HTA */
      SourceRealm = AUTH_SOURCE_HTA;

      if (*lptr == '\"')
      {
         lptr++;
         zptr = (sptr = RealmDescription) + sizeof(RealmDescription);
         while (*lptr && *lptr != '\"')
         {
            if (sptr < zptr) *sptr++ = *lptr;
            lptr++;
         }
         if (sptr >= zptr)
         {
            MetaConReport (mcptr, METACON_REPORT_ERROR,
                           "Problem configuring realm description");
            RealmProblem = true;
            lptr = "]";
         }
         else
            *sptr = '\0';
         if (*lptr == '\"') lptr++;
         if (*lptr == '=') lptr++;
      }

      zptr = (sptr = Realm) + sizeof(Realm);
      while (*lptr &&
             *lptr != ';' &&
             *lptr != '=' &&
             *lptr != ']' &&
             !ISLWS(*lptr))
      {
         if (sptr < zptr) *sptr++ = toupper(*lptr);
         lptr++;
      }
      if (sptr >= zptr || !Realm[0])
      {
         MetaConReport (mcptr, METACON_REPORT_ERROR,
                        "Problem configuring realm");
         RealmProblem = true;
         lptr = "]";
      }
      *sptr = '\0';

      if (*lptr == '=')
      {
         if (strsame (lptr, "=ACME", 5))
         {
            lptr += 5;
            SourceRealm = AUTH_SOURCE_ACME;
         }
         else
         if (strsame (lptr, "=AGENT", 6))
         {
            lptr += 6;
            SourceRealm = AUTH_SOURCE_AGENT;
         }
         else
         if (strsame (lptr, "=HTA", 4))
         {
            lptr += 4;
            SourceRealm = AUTH_SOURCE_HTA;
         }
         else
         if (strsame (lptr, "=@HTA", 5))
         {
            lptr += 5;
            SourceRealm = AUTH_SOURCE_DIR_HTA;
         }
         else
         if (strsame (lptr, "=HOST", 5))
         {
            MetaConReport (mcptr, METACON_REPORT_ERROR,
                           "Host group cannot be an authentication realm");
            RealmProblem = true;
            lptr = "]";
         }
         else
         if (strsame (lptr, "=ID", 3))
         {
            lptr += 3;
            SourceRealm = AUTH_SOURCE_ID;
            if (VMSnok (status =
                AuthConfigIdentifier (Realm, &RealmVmsIdentifier)))
            {
               MetaConReport (mcptr, METACON_REPORT_ERROR,
                              "Realm !AZ identifier %!&M", Realm, status);
               RealmProblem = true;
               lptr = "]";
            }
         }
         else
         if (strsame (lptr, "=LIST", 5))
         {
            lptr += 5;
            SourceRealm = AUTH_SOURCE_LIST;
         }
         else
         if (strsame (lptr, "=@LIST", 6))
         {
            lptr += 6;
            SourceRealm = AUTH_SOURCE_DIR_LIST;
         }
         else
         if (strsame (lptr, "=VMS", 4))
         {
            lptr += 4;
            SourceRealm = AUTH_SOURCE_VMS;
         }
         else
         if (strsame (lptr, "=X509", 5))
         {
            lptr += 5;
            SourceRealm = AUTH_SOURCE_X509;
         }
         else
         if (strsame (lptr, "=RFC1413", 8))
         {
            lptr += 8;
            SourceRealm = AUTH_SOURCE_RFC1413;
         }
         else
         {
            MetaConReport (mcptr, METACON_REPORT_ERROR,
                           "Problem configuring realm source");
            RealmProblem = true;
            lptr = "]";
         }
      }
      else
      {
         /* does it look like a host group? */
         for (sptr = Realm; *sptr && isdigit(*sptr); sptr++);
         if (*sptr == '.')
         {
            MetaConReport (mcptr, METACON_REPORT_ERROR,
                           "Host group cannot be an authentication realm");
            RealmProblem = true;
            lptr = "]";
         }
      }

      /* 'VMS' is a required ACME agent */
      if (SourceRealm == AUTH_SOURCE_ACME)
         SourceRealm = AUTH_SOURCE_ACME;
      else
      /* if was "VMS" then it's SYSUAF no matter what was made equal to!! */
      if (strsame (Realm, AUTH_REALM_VMS, -1))
         SourceRealm = AUTH_SOURCE_VMS;
      else
      /* same for "WORLD" */
      if (strsame (Realm, AUTH_REALM_WORLD, -1))
         SourceRealm = AUTH_SOURCE_WORLD;
      else
      /* and "X509" */
      if (strsame (Realm, AUTH_REALM_X509, -1))
         SourceRealm = AUTH_SOURCE_X509;
      else
      /* and "RFC1413" */
      if (strsame (Realm, AUTH_REALM_RFC1413, -1))
         SourceRealm = AUTH_SOURCE_RFC1413;
      else
      /* and "SKELKEY" */
      if (strsame (Realm, AUTH_REALM_SKELKEY, -1))
         SourceRealm = AUTH_SOURCE_SKELKEY;
      else
      /* and for "EXTERNAL" */
      if (strsame (Realm, AUTH_REALM_EXTERNAL, -1))
         SourceRealm = AUTH_SOURCE_EXTERNAL;
      else
      /* and "NONE" */
      if (strsame (Realm, AUTH_REALM_NONE, -1))
         SourceRealm = AUTH_SOURCE_NONE;
      else
      /* and last but by no means least "PROMISCUOUS" */
      if (strsame (Realm, AUTH_REALM_PROMISCUOUS, -1))
         SourceRealm = AUTH_SOURCE_PROMISCUOUS;

      if (AuthPolicySysUafWasdIdentifiers)
      {
         /*
            If this is the VMS realm and identifiers are mandatory for
            SYSUAF authentication but the realm is not a specific identifier
            then it must be through the "hard-wired" WASD identifiers.
         */
         if (SourceRealm == AUTH_SOURCE_VMS && AuthPolicySysUafIdentifiers)
            SourceRealm = AUTH_SOURCE_WASD_ID;
      }

      if (SourceRealm == AUTH_SOURCE_ACME && !AuthConfigACME)
      {
         MetaConReport (mcptr, METACON_REPORT_ERROR,
                         "ACME authentication is not available");
         RealmProblem = true;
         lptr = "]";
      }

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

      /* semicolon separating realm from optional full-access (write) group */
      if (*lptr == ';')
      {
         /*****************************/
         /* optional read+write group */
         /*****************************/

         SourceGroupWrite = AUTH_SOURCE_HTA;

         lptr++;
         while (*lptr && ISLWS(*lptr)) lptr++;
         zptr = (sptr = GroupWrite) + sizeof(GroupWrite);
         while (*lptr &&
                *lptr != ';' &&
                *lptr != '=' &&
                *lptr != ']' &&
                !ISLWS(*lptr))
         {
            if (sptr < zptr) *sptr++ = toupper(*lptr);
            lptr++;
         }
         if (sptr >= zptr || !GroupWrite[0])
         {
            MetaConReport (mcptr, METACON_REPORT_ERROR,
                           "Problem configuring (first) group");
            RealmProblem = true;
            lptr = "]";
         }
         *sptr = '\0';

         if (*lptr == '=')
         {
            if (strsame (lptr, "=AGENT", 6))
            {
               lptr += 6;
               SourceGroupWrite = AUTH_SOURCE_AGENT;
            }
            else
            if (strsame (lptr, "=HTA", 4))
            {
               lptr += 4;
               SourceGroupWrite = AUTH_SOURCE_HTA;
            }
            else
            if (strsame (lptr, "=@HTA", 5))
            {
               lptr += 5;
               SourceGroupWrite = AUTH_SOURCE_DIR_HTA;
            }
            else
            if (strsame (lptr, "=HOST", 5))
            {
               lptr += 5;
               SourceGroupWrite = AUTH_SOURCE_HOST;
            }
            else
            if (strsame (lptr, "=ID", 3))
            {
               lptr += 3;
               SourceGroupWrite = AUTH_SOURCE_ID;
               if (VMSnok (status =
                   AuthConfigIdentifier (GroupWrite, &GroupWriteVmsIdentifier)))
               {
                  MetaConReport (mcptr, METACON_REPORT_ERROR,
                                 "Group !AZ identifier\n%!&M",
                                 GroupWrite, status);
                  RealmProblem = true;
                  lptr = "]";
               }
            }
            else
            if (strsame (lptr, "=LIST", 5))
            {
               lptr += 5;
               SourceGroupWrite = AUTH_SOURCE_LIST;
            }
            else
            if (strsame (lptr, "=@LIST", 6))
            {
               lptr += 6;
               SourceGroupWrite = AUTH_SOURCE_DIR_LIST;
            }
            else
            if (strsame (lptr, "=X509", 5))
            {
               lptr += 5;
               SourceGroupWrite = AUTH_SOURCE_X509;
            }
            else
            if (strsame (lptr, "=RFC1413", 8))
            {
               lptr += 8;
               SourceGroupWrite = AUTH_SOURCE_RFC1413;
            }
            else
            {
               MetaConReport (mcptr, METACON_REPORT_ERROR,
                              "Problem configuring (first) group source");
               RealmProblem = true;
               lptr = "]";
            }
         }
         else
         {
            /* does it look like a host group? */
            for (sptr = GroupWrite; *sptr && isdigit(*sptr); sptr++);
            if (*sptr == '.') SourceGroupWrite = AUTH_SOURCE_HOST;
         }
      }

      /* semicolon separating realm from optional read-only-access group */
      if (*lptr == ';')
      {
         /****************************/
         /* optional read-only group */
         /****************************/

         SourceGroupRead = AUTH_SOURCE_HTA;

         lptr++;
         while (*lptr && ISLWS(*lptr)) lptr++;
         zptr = (sptr = GroupRead) + sizeof(GroupRead);
         while (*lptr &&
                *lptr != '=' &&
                *lptr != ']' &&
                !ISLWS(*lptr))
         {
            if (sptr < zptr) *sptr++ = toupper(*lptr);
            lptr++;
         }
         if (sptr >= zptr || !GroupRead[0])
         {
            MetaConReport (mcptr, METACON_REPORT_ERROR,
                           "Problem configuring second group");
            RealmProblem = true;
            lptr = "]";
         }
         *sptr = '\0';

         if (*lptr == '=')
         {
            if (strsame (lptr, "=AGENT", 6))
            {
               lptr += 6;
               SourceGroupRead = AUTH_SOURCE_AGENT;
            }
            else
            if (strsame (lptr, "=HTA", 4))
            {
               lptr += 4;
               SourceGroupRead = AUTH_SOURCE_HTA;
            }
            else
            if (strsame (lptr, "=@HTA", 5))
            {
               lptr += 5;
               SourceGroupRead = AUTH_SOURCE_DIR_HTA;
            }
            else
            if (strsame (lptr, "=HOST", 5))
            {
               lptr += 5;
               SourceGroupRead = AUTH_SOURCE_HOST;
            }
            else
            if (strsame (lptr, "=ID", 3))
            {
               lptr += 3;
               SourceGroupRead = AUTH_SOURCE_ID;
               if (VMSnok (status =
                   AuthConfigIdentifier (GroupRead, &GroupReadVmsIdentifier)))
               {
                  MetaConReport (mcptr, METACON_REPORT_ERROR,
                                 "Group !AZ identifier\n%!&M",
                                 GroupWrite, status);
                  RealmProblem = true;
                  lptr = "]";
               }
            }
            else
            if (strsame (lptr, "=LIST", 5))
            {
               lptr += 5;
               SourceGroupRead = AUTH_SOURCE_LIST;
            }
            else
            if (strsame (lptr, "=@LIST", 6))
            {
               lptr += 6;
               SourceGroupRead = AUTH_SOURCE_DIR_LIST;
            }
            else
            if (strsame (lptr, "=X509", 5))
            {
               lptr += 5;
               SourceGroupRead = AUTH_SOURCE_X509;
            }
            else
            if (strsame (lptr, "=RFC1413", 8))
            {
               lptr += 8;
               SourceGroupRead = AUTH_SOURCE_RFC1413;
            }
            else
            {
               MetaConReport (mcptr, METACON_REPORT_ERROR,
                              "Problem configuring second group source");
               RealmProblem = true;
               lptr = "]";
            }
         }
         else
         {
            /* does it look like a host group? */
            for (sptr = GroupRead; *sptr && isdigit(*sptr); sptr++);
            if (*sptr == '.') SourceGroupWrite = AUTH_SOURCE_HOST;
         }
      }

      while (*lptr && ISLWS(*lptr)) lptr++;
      if (*lptr == ']')
         lptr++;
      else
      {
         MetaConReport (mcptr, METACON_REPORT_ERROR, "Generally confused\n");
         RealmProblem = true;
      }

      /* find start of any realm-context access flags */
      while (*lptr && ISLWS(*lptr)) lptr++;
      if (*lptr)
         strcpy (RealmCanString, lptr);
      else
         RealmCanString[0] = '\0';

      /***************************/
      /* bit more usage checking */
      /***************************/

      if ((SourceRealm == AUTH_SOURCE_VMS ||
           SourceRealm == AUTH_SOURCE_ID) &&
          !AuthSysUafEnabled)
      {
         MetaConReport (mcptr, METACON_REPORT_ERROR,
                        "/SYSUAF not enabled at command line");
         RealmProblem = true;
      }

      if (SourceRealm == AUTH_SOURCE_VMS &&
          !AuthPolicySysUafVms &&
          AuthPolicySysUafIdentifiers)
      {
         MetaConReport (mcptr, METACON_REPORT_ERROR,
            "Realm indicates VMS but /SYSUAF=VMS not enabled at command line");
         RealmProblem = true;
      }

      if (SourceRealm == AUTH_SOURCE_ID &&
          AuthPolicySysUafVms &&
          !AuthPolicySysUafIdentifiers)
      {
         MetaConReport (mcptr, METACON_REPORT_ERROR,
            "Realm indicates ID but /SYSUAF=ID not enabled at command line");
         RealmProblem = true;
      }

      if ((SourceGroupWrite == AUTH_SOURCE_ID ||
           SourceGroupRead == AUTH_SOURCE_ID) &&
          SourceRealm != AUTH_SOURCE_VMS &&
          SourceRealm != AUTH_SOURCE_ID)
      {
         MetaConReport (mcptr, METACON_REPORT_ERROR,
                        "ID used without SYSUAF authentication");
         RealmProblem = true;
      }

      if (SourceRealm == AUTH_SOURCE_WASD_ID &&
          SourceGroupWrite != AUTH_SOURCE_WASD_ID &&
          AuthPolicySysUafWasdIdentifiers &&
          (GroupWrite[0] || GroupRead[0]))
      {
         MetaConReport (mcptr, METACON_REPORT_ERROR,
                        "\"Hard-wired\" WASD identifier usage problem");
         RealmProblem = true;
      }

      /*************************/
      /* close enough for jazz */
      /*************************/

      AuthConfigAddRealm (mcptr,
                          RealmProblem, Realm, RealmDescription, SourceRealm,
                          RealmVmsIdentifier, GroupWrite, SourceGroupWrite,
                          GroupWriteVmsIdentifier, GroupRead, SourceGroupRead,
                          GroupReadVmsIdentifier, GroupCanFlags, WorldCanFlags);

      RealmSpecified = true;
      return;
   }

   if (*lptr != '[' && !strsame (lptr, "REALM", 5))
   {
      /********/
      /* path */
      /********/

      if (!RealmSpecified)
         MetaConReport (mcptr, METACON_REPORT_ERROR, "No realm to apply");

      /* if not already retrieved (try to) get (any) proxy mappings */
      if (!ProxyStringPtr[0]) ProxyStringPtr = AuthConfigProxy (mcptr, NULL);
      /* a single dollar effectively indicates 'cancel all proxies' */
      if (*(unsigned short*)ProxyStringPtr == '$\0') ProxyStringPtr = "";

      /* note start of path template */
      PathTemplatePtr = lptr;
      while (*lptr && !ISLWS(*lptr)) lptr++;
      /* terminate at the end of the path template */
      if (*lptr) *lptr++ = '\0';

      PathIsRegex = false;
      if (Config.cfMisc.RegexEnabled && *PathTemplatePtr == REGEX_CHAR)
      {
         cptr = StringRegexCompile (PathTemplatePtr+1, &RegexPreg);
         if (cptr)
         {
            MetaConReport (mcptr, METACON_REPORT_ERROR, "Regex: !AZ", cptr);
            PathProblem = true;
         }
         else
         {
            regfree (&RegexPreg);
            PathIsRegex = true;
         }
      }

      /* find start of access flags */
      while (*lptr && ISLWS(*lptr)) lptr++;
      if (!*lptr)
      {
         if (RealmCanString[0])
            lptr = RealmCanString;
         else
         {
            MetaConReport (mcptr, METACON_REPORT_ERROR,
                           "No access specified and no realm defaults");
            lptr = "";
            PathProblem = true;
         }
      }

      while (*lptr)
      {
         /********************************/
         /* set flags controlling access */
         /********************************/

         /* find the start of the next element */
         while (*lptr && (ISLWS(*lptr) || *lptr == ',' || *lptr == ';'))
         {
            if (*lptr++ != ';') continue;
            /* semicolon separates realm/group and optional world access */
            CanFlagsPtr = &WorldCanFlags;
            RestrictListPtr = &WorldRestrictList;
         }
         if (!*lptr) break;

         if (*lptr == '*' ||
             *lptr == '#' ||
             isdigit(*lptr) ||
             *lptr == '~' ||
             strsame (lptr, "http:", 5) ||
             strsame (lptr, "https:", 6) ||
             strsame (lptr, "localhost", 9))
         {
            /* access restriction list */
            for (sptr = RestrictListPtr; *sptr; sptr++);
            zptr = RestrictListPtr + ACCESS_RESTRICTION_LIST_SIZE;
            if (sptr > RestrictListPtr && sptr < zptr) *sptr++ = ',';
            while (*lptr && !ISLWS(*lptr) &&
                   *lptr != ',' && *lptr != ';' &&
                   sptr < zptr)
               *sptr++ = *lptr++;
            if (sptr >= zptr)
            {
               MetaConReport (mcptr, METACON_REPORT_ERROR,
                              "Restriction list too long");
               PathProblem = true;
               break;
            }
            else
               *sptr = '\0';
         }
         else
         if (strsame (lptr, "final", 5) && !isalpha(lptr[5]))
            FinalRule = true;
         else
         if (strsame (lptr, "param=", 6))
         {
            /* agent parameter */
            zptr = (sptr = RealmParameter) + REALM_PARAMETER_SIZE;
            lptr += 6;
            if (*lptr == '\"')
            {
               /* delimitted by double quotes */
               lptr++;
               while (*lptr && *lptr != '\"' && sptr < zptr) *sptr++ = *lptr++;
               if (*lptr) lptr++;
            }
            else
            if (*lptr == '\'')
            {
               /* delimitted by single quotes */
               lptr++;
               while (*lptr && *lptr != '\'' && sptr < zptr) *sptr++ = *lptr++;
               if (*lptr) lptr++;
            }
            else
            {
               /* delimited by restriction list syntax */
               while (*lptr && !ISLWS(*lptr) &&
                      *lptr != ',' && *lptr != ';' && 
                      sptr < zptr)
                  *sptr++ = *lptr++;
            }
            if (sptr >= zptr)
            {
               MetaConReport (mcptr, METACON_REPORT_ERROR,
                              "Agent parameter too long");
               PathProblem = true;
               break;
            }
            else
               *sptr = '\0';
         }
         else
         if (strsame (lptr, "nocache", 7) && !isalpha(lptr[7]))
            NoCache = true;
         else
         if (strsame (lptr, "profile", 7) && !isalpha(lptr[7]))
         {
            VmsUserProfile = true;
            if (!AuthVmsUserProfileEnabled)
               MetaConReport (mcptr, METACON_REPORT_ERROR,
                  "/PROFILE not enabled at command line");
         }
         else
         if (strsame (lptr, "scriptas", 8) && !isalpha(lptr[8]))
            VmsUserScriptAs = true;
         else
         if (strsame (lptr, "none", 4) && !isalpha(lptr[4]))
            *CanFlagsPtr = 0;
         else
         /* must come before "read" for obvious reasons! */
         if ((strsame (lptr, "READ+WRITE", 10) && !isalpha(lptr[10])) ||
             (strsame (lptr, "R+W", 3) && !isalpha(lptr[3])))
            *CanFlagsPtr |= (HTTP_METHOD_GET | HTTP_METHOD_HEAD |
                             HTTP_METHOD_DELETE | HTTP_METHOD_POST |
                             HTTP_METHOD_PUT | HTTP_METHOD_CONNECT);
         else
         if ((strsame (lptr, "READ", 4) && !isalpha(lptr[4])) ||
             (strsame (lptr, "R", 1) && !isalpha(lptr[1])))
            *CanFlagsPtr |= (HTTP_METHOD_GET | HTTP_METHOD_HEAD);
         else
         if ((strsame (lptr, "WRITE", 5) && !isalpha(lptr[5])) ||
             (strsame (lptr, "W", 1) && !isalpha(lptr[1])))
            *CanFlagsPtr |= (HTTP_METHOD_DELETE | HTTP_METHOD_POST |
                             HTTP_METHOD_PUT);
         else
         if (strsame (lptr, "CONNECT", 7) && !isalpha(lptr[7]))
            *CanFlagsPtr |= HTTP_METHOD_CONNECT;
         else
         if (strsame (lptr, "DELETE", 6) && !isalpha(lptr[6]))
            *CanFlagsPtr |= HTTP_METHOD_DELETE;
         else
         if (strsame (lptr, "GET", 3) && !isalpha(lptr[3]))
            *CanFlagsPtr |= HTTP_METHOD_GET | HTTP_METHOD_HEAD;
         else
         if (strsame (lptr, "HEAD", 4) && !isalpha(lptr[4]))
            *CanFlagsPtr |= HTTP_METHOD_HEAD;
         else
         if (strsame (lptr, "POST", 4) && !isalpha(lptr[4]))
            *CanFlagsPtr |= HTTP_METHOD_POST;
         else
         if (strsame (lptr, "PUT", 3) && !isalpha(lptr[3]))
            *CanFlagsPtr |= HTTP_METHOD_PUT;
         else
         {
            /* otherwise assume it's an alpha-numeric host name */
            for (sptr = RestrictListPtr; *sptr; sptr++);
            zptr = RestrictListPtr + ACCESS_RESTRICTION_LIST_SIZE;
            if (sptr > RestrictListPtr && sptr < zptr) *sptr++ = ',';
            while (*lptr && !ISLWS(*lptr) &&
                   *lptr != ',' && *lptr != ';' &&
                   sptr < zptr)
               *sptr++ = *lptr++;
            if (sptr >= zptr)
            {
               MetaConReport (mcptr, METACON_REPORT_ERROR,
                              "Restriction list too long");
               PathProblem = true;
               break;
            }
            else
               *sptr = '\0';
         }
         while (*lptr && !ISLWS(*lptr) && *lptr != ',' && *lptr != ';') lptr++;
      }

      AuthConfigAddPath (mcptr, PathProblem,
                         PathTemplatePtr, PathIsRegex, FinalRule,
                         GroupRestrictList, WorldRestrictList,
                         ProxyStringPtr, RealmParameter,
                         GroupCanFlags, WorldCanFlags, NoCache,
                         VmsUserProfile, VmsUserScriptAs);

      AuthorizationEnabled++;
      return;
   }

   MetaConReport (mcptr, METACON_REPORT_ERROR, "Generally confused\n");
}

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

AuthConfigAddRealm
(
META_CONFIG *mcptr,
BOOL RealmProblem,
char *Realm,
char *RealmDescription,
unsigned long SourceRealm,
unsigned long RealmVmsIdentifier,
char *GroupWrite,
unsigned long SourceGroupWrite,
unsigned long GroupWriteVmsIdentifier,
char *GroupRead,
unsigned long SourceGroupRead,
unsigned long GroupReadVmsIdentifier,
unsigned long  GroupCanFlags,
unsigned long  WorldCanFlags
)
{
   int  status,
        RealmDescriptionLength,
        GroupReadLength,
        GroupWriteLength,
        RealmLength,
        StringSpace,
        WorldRestrictListLength;
   char  *cptr, *pptr, *sptr, *tptr,
         *OffsetPtr;
   AUTH_REALM  *arptr;
   METACON_LINE  *mclptr;

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

   if (WATCH_MODULE(WATCH_MOD_AUTH))
   {
      WatchThis (NULL, FI_LI, WATCH_MOD_AUTH, "AuthConfigAddRealm()");
      WatchDataFormatted (
"!&B !&Z !&Z !UL !&X\n\
!&Z !UL !&X\n\
!&Z !UL !&X\n\
!&X !&X\n",
         RealmProblem,
         Realm, RealmDescription, SourceRealm, RealmVmsIdentifier,
         GroupWrite, SourceGroupWrite, GroupWriteVmsIdentifier,
         GroupRead, SourceGroupRead, GroupReadVmsIdentifier,
         GroupCanFlags, WorldCanFlags);
   }

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

   GroupReadLength = strlen(GroupRead);
   GroupWriteLength = strlen(GroupWrite);
   RealmLength = strlen(Realm);
   RealmDescriptionLength = strlen(RealmDescription);

   StringSpace = 4 + /* number of terminating nulls! */
                 GroupReadLength +
                 GroupWriteLength +
                 RealmLength +
                 RealmDescriptionLength;
   if (Debug) fprintf (stdout, "StringSpace: %d\n", StringSpace);

   arptr = VmGet (sizeof(AUTH_REALM) + StringSpace);

   /* set the meta-config data pointer to this structure, tag it! */
   mclptr->LineDataPtr = arptr;
   arptr->RecordType = AUTH_REALM_RECORD_TYPE;
   arptr->MetaConNumber = mclptr->Number;

   arptr->RealmProblem = RealmProblem;

   /* find the start of the string storage space */
   OffsetPtr = (char*)arptr + sizeof(AUTH_REALM);

   arptr->GroupReadPtr = OffsetPtr;
   memcpy (arptr->GroupReadPtr, GroupRead, GroupReadLength+1);
   OffsetPtr += (arptr->GroupReadLength = GroupReadLength) + 1;
   arptr->SourceGroupRead = SourceGroupRead;
   arptr->GroupReadVmsIdentifier = GroupReadVmsIdentifier;

   arptr->GroupWritePtr = OffsetPtr;
   memcpy (arptr->GroupWritePtr, GroupWrite, GroupWriteLength+1);
   OffsetPtr += (arptr->GroupWriteLength = GroupWriteLength) + 1;
   arptr->SourceGroupWrite = SourceGroupWrite;
   arptr->GroupWriteVmsIdentifier = GroupWriteVmsIdentifier;

   arptr->RealmPtr = OffsetPtr;
   memcpy (arptr->RealmPtr, Realm, RealmLength+1);
   OffsetPtr += (arptr->RealmLength = RealmLength) + 1;
   arptr->SourceRealm = SourceRealm;
   arptr->RealmVmsIdentifier = RealmVmsIdentifier;

   arptr->RealmDescrPtr = OffsetPtr;
   memcpy (arptr->RealmDescrPtr,
           RealmDescription,
           RealmDescriptionLength+1);
   OffsetPtr += (arptr->RealmDescriptionLength = RealmDescriptionLength) + 1;

   arptr->AuthGroupCan = GroupCanFlags;
   arptr->AuthWorldCan = WorldCanFlags;
}

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

AuthConfigAddPath
(
META_CONFIG *mcptr,
BOOL PathProblem,
char *Path,
BOOL PathIsRegex,
BOOL FinalRule,
char *GroupRestrictList,
char *WorldRestrictList,
char *ProxyStringPtr,
char *RealmParameter,
unsigned long  GroupCanFlags,
unsigned long  WorldCanFlags,
BOOL NoCache,
BOOL VmsUserProfile,
BOOL VmsUserScriptAs
)
{
   int  status,
        GroupRestrictListLength,
        PathLength,
        ProxyStringLength,
        PathParameterLength,
        StringSpace,
        WorldRestrictListLength;
   char  *cptr, *pptr, *sptr, *tptr,
         *OffsetPtr;
   AUTH_PATH  *apptr;
   METACON_LINE  *mclptr;

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

   if (WATCH_MODULE(WATCH_MOD_AUTH))
   {
      WatchThis (NULL, FI_LI, WATCH_MOD_AUTH, "AuthConfigAddPath()");
      WatchDataFormatted (
"!&B !&Z !&B !&B\n!&Z\n!&Z\n!&Z\n!&Z\n!&X !&X !&B !&B !&B\n",
         PathProblem, Path, FinalRule, PathIsRegex,
         GroupRestrictList, WorldRestrictList,
         ProxyStringPtr, RealmParameter,
         GroupCanFlags, WorldCanFlags, NoCache,
         VmsUserProfile, VmsUserScriptAs);
   }

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

   GroupRestrictListLength = strlen(GroupRestrictList);
   PathLength = strlen(Path);
   ProxyStringLength = strlen(ProxyStringPtr);
   PathParameterLength = strlen(RealmParameter);
   WorldRestrictListLength = strlen(WorldRestrictList);

   StringSpace = 5 + /* number of terminating nulls! */
                 GroupRestrictListLength +
                 PathLength +
                 ProxyStringLength +
                 PathParameterLength +
                 WorldRestrictListLength;
   if (Debug) fprintf (stdout, "StringSpace: %d\n", StringSpace);

   apptr = VmGet (sizeof(AUTH_PATH) + StringSpace);

   /* set the meta-config data pointer to this structure, tag it! */
   mclptr->LineDataPtr = apptr;
   apptr->RecordType = AUTH_PATH_RECORD_TYPE;
   apptr->MetaConNumber = mclptr->Number;

   apptr->PathProblem = PathProblem;

   /* set the meta-config data pointer to this structure */
   mclptr->LineDataPtr = apptr;

   /* find the start of the string storage space */
   OffsetPtr = (char*)apptr + sizeof(AUTH_PATH);

   apptr->GroupRestrictListPtr = sptr = OffsetPtr;
   cptr = GroupRestrictList;
   while (*cptr) *sptr++ = tolower(*cptr++);      
   *sptr = '\0';
   OffsetPtr += (apptr->GroupRestrictListLength =
                 GroupRestrictListLength) + 1;

   apptr->PathPtr = sptr = OffsetPtr;
   cptr = Path;
   while (*cptr) *sptr++ = tolower(*cptr++);      
   *sptr = '\0';
   OffsetPtr += (apptr->PathLength = PathLength) + 1;

   if (PathIsRegex)
   {
      /* the template was a regular expression, create a compiled version */
      StringRegexCompile (apptr->PathPtr, &apptr->RegexPregPath);
   }

   apptr->FinalRule = FinalRule;

   apptr->ProxyStringPtr = sptr = OffsetPtr;
   cptr = ProxyStringPtr;
   while (*cptr) *sptr++ = *cptr++;      
   *sptr = '\0';
   OffsetPtr += (apptr->ProxyStringLength = ProxyStringLength) + 1;

   cptr = WorldRestrictList;
   apptr->WorldRestrictListPtr = sptr = OffsetPtr;
   while (*cptr) *sptr++ = tolower(*cptr++);      
   *sptr = '\0';
   OffsetPtr += (apptr->WorldRestrictListLength =
                 WorldRestrictListLength) + 1;

   apptr->PathParameterPtr = OffsetPtr;
   memcpy (apptr->PathParameterPtr, RealmParameter, PathParameterLength+1);
   *sptr = '\0';
   apptr->PathParameterLength = PathParameterLength;

   apptr->AuthGroupCan = GroupCanFlags;
   apptr->AuthWorldCan = WorldCanFlags;

   apptr->NoCache = NoCache;
   apptr->VmsUserProfile = VmsUserProfile;
   apptr->VmsUserScriptAs = VmsUserScriptAs;
}

/****************************************************************************/
/*
When 'StringPtr' is non-NULL the string is parsed looking for something like
'vms-username=remote-username[@host.name]' where the square brackets indicate
an optional host host name.  Essentially this is any string delimited by
white-space.  Multiple proxy entries can be placed in a single string (line)
separated by white-space.  The newly parsed position in the string is returned. 
This can be done one or many times with the string being dynamically allocated
and built up into a series of white-space delimited proxy mappings.

If 'StringPtr' is passed as NULL a pointer to the stored string is returned and
the internal pointers reset to empty.  It is up to the calling routine to then
use/dispose of the string as appropriate.
*/ 

char* AuthConfigProxy
(
META_CONFIG *mcptr,
char *StringPtr
)
{
#define AUTH_CONFIG_META_PROXY_STRING_ALLOC 256

   static int  ProxyCurrentLength,
               ProxyLineLength,
               ProxyStringLength;
   static char  *ProxyStringPtr;

   char  *cptr, *sptr;
   int  Length;

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

   if (WATCH_MODULE(WATCH_MOD_AUTH))
      WatchThis (NULL, FI_LI, WATCH_MOD_AUTH,
                 "AuthConfigProxy() !&X !&Z", mcptr, StringPtr);

   if (!StringPtr)
   {
      if (!ProxyCurrentLength) return ("");
      if (mcptr) return (ProxyStringPtr);
      VmFree (ProxyStringPtr, FI_LI);
      ProxyStringPtr = NULL;
      ProxyCurrentLength = ProxyLineLength = ProxyStringLength = 0;
      return ("");
   }

   if (!AuthPolicySysUafProxy)
   {
      /* prevent disabled proxies from inadvertantly allowing access */
      if (ProxyCurrentLength) return (ProxyStringPtr);
      MetaConReport (mcptr, METACON_REPORT_ERROR,
                     "/SYSUAF=PROXY not enabled at command line");
      /* override the supplied proxy with this bogus one */
      StringPtr = "proxy@command.line=NOT-ENABLED";
   }

   for (;;)
   {
      for (sptr = StringPtr; *sptr && ISLWS(*sptr); sptr++);
      if (!*sptr) break;
      cptr = sptr;
      while (*sptr && !ISLWS(*sptr)) sptr++;
      StringPtr = sptr;

      if ((Length = (sptr - cptr)) > ProxyStringLength - ProxyCurrentLength)
      {
         if (Length < AUTH_CONFIG_META_PROXY_STRING_ALLOC+1)
            Length = AUTH_CONFIG_META_PROXY_STRING_ALLOC+1;
         ProxyStringLength += Length;
         ProxyStringPtr = VmRealloc (ProxyStringPtr, ProxyStringLength+1, FI_LI);
      }

      sptr = ProxyStringPtr + ProxyCurrentLength;
      if (ProxyLineLength > 80)
      {
         *sptr++ = '\n';
         ProxyLineLength = 0;
      }
      else
      if (ProxyLineLength)
         *sptr++ = ' ';
      while (*cptr && !ISLWS(*cptr))
      {
         *sptr++ = *cptr++;
         ProxyLineLength++;
      }
      *sptr = '\0';
      ProxyCurrentLength = sptr - ProxyStringPtr;

      if (WATCH_MODULE(WATCH_MOD_AUTH) && WATCH_MODULE(WATCH_MOD__DETAIL))
         WatchDataFormatted ("!UL !UL !&Z\n", ProxyStringLength,
                             ProxyCurrentLength, ProxyStringPtr);
   }

   return (NULL);
}

/****************************************************************************/
/*
Searches for a matching proxy entry in the string passed as the 'ProxyString'
parameter.  Searches against the username in the request's remote user and
client host name (which with WASD usually contains the client's IP address).
See description in the prologue to AUTH.C for information on what this function
provides in proxy mappings.

  [*|REMOTE][@host-name|@IP-address|VLSM-address]=[*|LOCAL]

  *@host.name=*                     (many-to-many, host match)
  @host-name=                       (the same, different syntax)
  remote-name@host.name=*           (many-to-the-same, host match)
  remote-name@host.name=local-name  (one-to-one, host match)
  *@host.name=local-name            (many-to-one, host match)
  *=local-name                      (many-to-one, all hosts)
  remote-name=local-name            (one-to-one, all hosts)

Note that remote an local usernames may be wilcarded only using a single
wildcard (i.e. complete match) but host names and addresses may be matched
on a partial string using a wildcard.  The use of isspace() rather than ISLWS()
allows embedded newlines to break very long lines of proxy mappings when
outputing them as reports.
*/ 

int AuthConfigProxyMap
(
REQUEST_STRUCT *rqptr,
AUTH_CREC *acrptr
)
{
   BOOL  IsDottedDecimal,
         IsNetMask,
         SoFarSoGood;
   int  status,
        ProxyUserNameLength,
        RemoteUserLength;
   char  *cptr, *dptr, *sptr, *zptr,
         *ErrorMsgPtr;
   char  RuleHost [128],
         RemoteUser [AUTH_MAX_USERNAME_LENGTH+1],
         RuleUserName [AUTH_MAX_USERNAME_LENGTH+1],
         ProxyUserName [AUTH_MAX_USERNAME_LENGTH+1];

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

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_AUTH))
      WatchThis (rqptr, FI_LI, WATCH_MOD_AUTH, "AuthConfigProxyMap()");

   /* get name-only portion of any name@host remote username (RFC1413) */
   cptr = rqptr->RemoteUser;
   zptr = (sptr = RemoteUser) + sizeof(RemoteUser);
   while (*cptr && *cptr != '@' && sptr < zptr) *sptr++ = toupper(*cptr++);
   if (sptr >= zptr)
   {
      ErrorGeneralOverflow (rqptr, FI_LI);
      return (AUTH_DENIED_BY_OTHER);
   }
   *sptr = '\0';
   RemoteUserLength = sptr - RemoteUser;
   if (Debug) fprintf (stdout, "remote |%s|\n", RemoteUser);

   if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_AUTH))
      WatchThis (rqptr, FI_LI, WATCH_AUTH, "PROXY map !AZ@!AZ",
                 RemoteUser, rqptr->rqClient.Lookup.HostName);

   ProxyUserName[0] = '\0';
   cptr = rqptr->rqAuth.ProxyStringPtr;
   while (*cptr)
   {
      while (*cptr && isspace (*cptr)) cptr++;
      if (*cptr == '#')
      {
         /* commented-out rule */
         if (*(unsigned short*)cptr == '#!')
         {
            /* comment spans all characters until next '!' */
            while (*cptr && *cptr != '!') cptr++;
            if (*cptr) cptr++;
         }
         else
            while (*cptr && !isspace (*cptr)) cptr++;
         continue;
      }

      if (Debug) fprintf (stdout, "cptr |%s|\n", cptr);
      dptr = cptr;
      IsNetMask = false;
      SoFarSoGood = true;
      ErrorMsgPtr = NULL;
      ProxyUserName[0] = '\0';

      if (*cptr == '*' || *cptr == '@')
      {
         /********************/
         /* wildcard mapping */
         /********************/

         *(unsigned short*)RuleUserName = '*\0';
         if (*cptr == '*') cptr++;
      }
      else
      {
         /*************************/
         /* get the rule username */
         /*************************/

         zptr = (sptr = RuleUserName) + sizeof(RuleUserName);
         while (*cptr && *cptr != '@' && !isspace(*cptr) && sptr < zptr)
            *sptr++ = *cptr++;
         if (sptr >= zptr)
         {
            ErrorGeneralOverflow (rqptr, FI_LI);
            return (AUTH_DENIED_BY_OTHER);
         }
         *sptr = '\0';
         if (Debug) fprintf (stdout, "rule |%s|\n", RuleUserName);
      }

      if (*cptr == '@')
      {
         /********************/
         /* a host component */
         /********************/

         IsDottedDecimal = true;
         cptr++;
         zptr = (sptr = RuleHost) + sizeof(RuleHost);
         while (*cptr && *cptr != '=' && !isspace(*cptr) && sptr < zptr)
         {
            if (*cptr == '/') IsNetMask = true;
            if (isalpha(*cptr)) IsDottedDecimal = false;
            *sptr++ = *cptr++;
         }
         if (sptr >= zptr)
         {
            ErrorGeneralOverflow (rqptr, FI_LI);
            return (AUTH_DENIED_BY_OTHER);
         }
         *sptr = '\0';
         if (Debug) fprintf (stdout, "host |%s|\n", RuleHost);

         if (IsNetMask)
         {
            char *tptr = RuleHost;  /* preserve string address using pointer */
            status = TcpIpNetMask (rqptr, WATCH_AUTH, &tptr,
                                   &rqptr->rqClient.IpAddress);
            if (status == SS$_UNREACHABLE)
            {
               /* if the masked IP addresses do not match */
               SoFarSoGood = false;
            }
            else
            if (VMSnok(status))
            {
               /* net mask problem */
               ErrorMsgPtr = "network mask";
               SoFarSoGood = false;
            }
         }
         else
         if (IsDottedDecimal)
         {
            /* if wildcard string compare fails the no match */
            if (!StringMatch (rqptr, rqptr->rqClient.IpAddressString, RuleHost))
               SoFarSoGood = false;
         }
         else
         {
            /* if wildcard string compare fails the no match */
            if (!StringMatch (rqptr, rqptr->rqClient.Lookup.HostName, RuleHost))
               SoFarSoGood = false;
         }
      }

      if (*cptr != '=')
      {
         ErrorMsgPtr = "rule syntax";
         SoFarSoGood = false;
      }
      else
      if (SoFarSoGood)
      {
         /***************************/
         /* get the SYSUAF username */
         /***************************/

         cptr++;
         if (*cptr == '*' || isspace(*cptr))
         {
            /* wildcard mapping */
            if (*cptr == '*') cptr++;
            strcpy (ProxyUserName, RemoteUser);
            ProxyUserNameLength = RemoteUserLength;
         }
         else
         {
            zptr = (sptr = ProxyUserName) + sizeof(ProxyUserName);
            while (*cptr && !isspace(*cptr) && sptr < zptr)
               *sptr++ = toupper(*cptr++);
            if (sptr >= zptr)
            {
               ErrorGeneralOverflow (rqptr, FI_LI);
               return (AUTH_DENIED_BY_OTHER);
            }
            *sptr = '\0';
            ProxyUserNameLength = sptr - ProxyUserName;
            if (Debug) fprintf (stdout, "proxy |%s|\n", ProxyUserName);
         }
      }

      while (*cptr && !isspace (*cptr)) cptr++;

      /****************/
      /* end of parse */
      /****************/

      if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_AUTH))
      {
         WatchDataFormatted ("!#AZ", cptr-dptr, dptr);
         if (!ErrorMsgPtr)
            WatchDataFormatted ("\n");
         else
            WatchDataFormatted (" <ERROR: !AZ\n", ErrorMsgPtr);
      }

      if (SoFarSoGood && *(unsigned short*)RuleUserName != '*\0')
      {
         /* it's not a username wildcard rule */
         if (strsame (RuleUserName, RemoteUser, -1))
         {
            /* the remote (client) and rule usernames are the same */
            if (*(unsigned short*)ProxyUserName == '-\0')
            {
               /* it's a forbidden match */
               if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_AUTH))
                  WatchThis (rqptr, FI_LI, WATCH_AUTH, "PROXY forbidden");
               return (AUTH_DENIED_BY_LOGIN);
            }
         }
         else
            SoFarSoGood = false;
      }

      /* if there's been a successful mapping */
      if (SoFarSoGood) break;

      /* reset any proxy username parsed during matching */
      ProxyUserName[0] = '\0';
   }

   if (!ProxyUserName[0])
   {
      /**************/
      /* no mapping */
      /**************/

      if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_AUTH))
         WatchThis (rqptr, FI_LI, WATCH_AUTH, "PROXY none");
      return (AUTH_DENIED_BY_LOGIN);
   }

   /*****************/
   /* found mapping */
   /*****************/

   if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_AUTH))
      WatchThis (rqptr, FI_LI, WATCH_AUTH, "PROXY result \"!AZ\"",
                 ProxyUserName);

   /* buffer the original remote user in the authorization structure */
   strcpy (rqptr->rqAuth.RemoteUser, rqptr->RemoteUser);
   rqptr->rqAuth.RemoteUserLength = rqptr->RemoteUserLength;

   /* now replace the original remote user with the potential SYSUAF one */
   strcpy (rqptr->RemoteUser, ProxyUserName);
   rqptr->RemoteUserLength = ProxyUserNameLength;

   if (strsame (acrptr->ProxyUserName, ProxyUserName, -1))
   {
      /************************/
      /* use previous details */
      /************************/

      if (rqptr->rqAuth.UserDetailsLength = acrptr->UserDetailsLength)
      {
         rqptr->rqAuth.UserDetailsPtr =
            VmGetHeap (rqptr, rqptr->rqAuth.UserDetailsLength+1);
         strcpy (rqptr->rqAuth.UserDetailsPtr, acrptr->UserDetails);
      }
      else
         rqptr->rqAuth.UserDetailsPtr = NULL;
   
      if (rqptr->rqAuth.VmsUserProfileLength =
          acrptr->VmsUserProfileLength)
      {
         rqptr->rqAuth.VmsUserProfilePtr =
            VmGetHeap (rqptr, acrptr->VmsUserProfileLength);
         memcpy (rqptr->rqAuth.VmsUserProfilePtr,
                 acrptr->VmsUserProfilePtr,
                 acrptr->VmsUserProfileLength);
      }
      else
         rqptr->rqAuth.VmsUserProfilePtr = NULL;

      rqptr->rqAuth.VmsUserScriptAs = acrptr->VmsUserScriptAs;

      return (SS$_NORMAL);
   }
   else
   {
      /*************************/
      /* proxy username verify */
      /*************************/

      if (VMSok (status = AuthVmsGetUai (rqptr, rqptr->RemoteUser)))
         status = AuthVmsVerifyUser (rqptr);

      if (VMSok (status) &&
          (AuthPolicySysUafIdentifiers || AuthPolicySysUafWasdIdentifiers))
      {
         status = AuthVmsHoldsIdentifier (rqptr, AUTH_PROXY_ACCESS_VMS_ID,
                                          AuthProxyAccessVmsIdentifier);
         /* if requires proxy identifier ... and doesn't have it */
         if (VMSnok (status))
         {
            if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_AUTH))
               WatchThis (rqptr, FI_LI, WATCH_AUTH,
                          "FAIL SYSUAF proxy identifier");
            status = AUTH_DENIED_BY_OTHER;
         }
      }

      if (VMSok (status))
         if (AuthVmsUserProfileEnabled)
            status = AuthVmsCreateUserProfile (rqptr);

      if (VMSok (status))
      {
         rqptr->rqAuth.SysUafAuthenticated = true;
         strcpy (acrptr->ProxyUserName, ProxyUserName);
         acrptr->ProxyUserNameLength = ProxyUserNameLength;
      }
      else
      {
         /* the verification has failed for some reason */
         rqptr->rqAuth.SysUafAuthenticated = false;

         /* restore the original details */
         strcpy (rqptr->RemoteUser, rqptr->rqAuth.RemoteUser);
         rqptr->RemoteUserLength = rqptr->rqAuth.RemoteUserLength;
         rqptr->rqAuth.RemoteUser[0] = acrptr->ProxyUserName[0] = '\0';
         rqptr->rqAuth.RemoteUserLength = acrptr->ProxyUserNameLength = 0;
      }

      return (status);
   }
}

/****************************************************************************/
/*
Get the identifier value from the supplied name.
*/ 

AuthConfigIdentifier
(
char *NamePtr,
unsigned long  *ValuePtr
)
{
   static $DESCRIPTOR (NameDsc, "");

   int  status;

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

   if (WATCH_MODULE(WATCH_MOD_AUTH))
      WatchThis (NULL, FI_LI, WATCH_MOD_AUTH,
                 "AuthConfigIdentifier() !&Z", NamePtr);

   *ValuePtr = 0;
   NameDsc.dsc$a_pointer = NamePtr;
   NameDsc.dsc$w_length = strlen(NamePtr);
   status = sys$asctoid (&NameDsc, ValuePtr, 0);
   if (Debug)
      fprintf (stdout, "sys$asctoid() %%X%08.08X %08.08X\n", status, *ValuePtr);
   return (status);
}

/*****************************************************************************/
/*
Look for a path template matching the request path in the path authorization
meta-config.
*/ 

int AuthConfigSearch
(
REQUEST_STRUCT *rqptr,
char *PathBeingAuthorized
)
{
   BOOL  WatchThisMatch;
   int  status,
        TotalLength;
   char  *cptr, *pptr, *sptr, *tptr, *zptr;
   AUTH_PATH  *apptr;
   AUTH_REALM  *arptr,
               *RealmDataPtr;
   METACON_LINE  *mclptr;

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

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_AUTH))
      WatchThis (rqptr, FI_LI, WATCH_MOD_AUTH,
                 "AuthConfigSearch() !&Z", PathBeingAuthorized);

   if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_MATCH))
      WatchThisMatch = true;
   else
      WatchThisMatch = false;

   RealmDataPtr = NULL;

   MetaConParseReset (MetaGlobalAuthPtr, true);
   for (;;)
   {
      mclptr = MetaGlobalAuthPtr->ParsePtr = MetaGlobalAuthPtr->ParseNextPtr;

      if (WATCHING(rqptr) &&
          WATCH_MODULE(WATCH_MOD_METACON) && WATCH_MODULE(WATCH_MOD__DETAIL))
         WatchDataFormatted ("!&X !UL !UL !UL !UL !&X !&Z\n",
            mclptr, mclptr->Size, mclptr->Token, mclptr->Number,
            mclptr->Length, mclptr->LineDataPtr, mclptr->TextPtr);

      /* if terminating empty "line" */
      if (!mclptr->Size)
      {
         status = STS$K_ERROR;
         break;
      }

      if (mclptr->Token == METACON_TOKEN_TEXT)
      {
         /* get required data, adjust the parse context to the next "line" */
         cptr = (char*)mclptr->LineDataPtr;
         MetaGlobalAuthPtr->ParseNextPtr =
            (METACON_LINE*)((char*)mclptr + mclptr->Size);
      }
      else
      if (mclptr->Token != METACON_TOKEN_DIRECTORY)
      {
         /* not a simple text line, have meta-config parse this one for us */
         if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_AUTH))
            cptr = MetaConParse (rqptr, MetaGlobalAuthPtr, &mclptr, WATCH_AUTH);
         else
            cptr = MetaConParse (rqptr, MetaGlobalAuthPtr, &mclptr, 0);

         /* if end of rules */
         if (!cptr)
         {
            status = STS$K_ERROR;
            break;
         }
         /* if error string */
         if (!*cptr && *(cptr+1))
         {
            status = SS$_ABORT;
            break;
         }
         /* if inline rule and expression was false */
         if (*(unsigned long*)cptr == '\0\0\0\1') continue;
         cptr = (char*)mclptr->LineDataPtr;
      }

      if (mclptr->Token == METACON_TOKEN_DIRECTORY)
      {
         /* get required data, adjust the parse context to the next "line" */
         cptr = mclptr->TextPtr + sizeof("[ConfigDirectory]");
         while (*cptr && ISLWS(*cptr)) cptr++;
         zptr = (sptr = rqptr->ConfigDirectory) +
                sizeof(rqptr->ConfigDirectory)-1;
         while (*cptr && sptr < zptr) *sptr++ = *cptr++;
         *sptr = '\0';
         rqptr->ConfigDirectoryLength = sptr - rqptr->ConfigDirectory;
         MetaGlobalAuthPtr->ParseNextPtr =
            (METACON_LINE*)((char*)mclptr + mclptr->Size);
         continue;
      }

      /* if there is no associated authorization data then ignore line */
      if (!cptr) continue;

      /* check the record type integer of this record */
      if (*(int*)cptr == AUTH_REALM_RECORD_TYPE)
      {
         /* just note the last realm record encountered */
         RealmDataPtr = (AUTH_REALM*)cptr;
         continue;
      }

      if (*(int*)cptr == AUTH_PATH_RECORD_TYPE)
      {
         apptr = (AUTH_PATH*)cptr;
         /* the 'arptr' here points to the last realm record encountered */
         if (!(arptr = RealmDataPtr))
         {
            if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_AUTH))
            {
               WatchDataFormatted (
"!&Z final:!&B regex:!&B\n\
!&Z !&Z\n\
!&X !&X !&B !&B !&B\n\
NO REALM TO APPLY!!\n",
                  apptr->PathPtr, apptr->FinalRule, 
                  apptr->RegexPregPath.buffer,
                  apptr->GroupRestrictListPtr,
                  apptr->WorldRestrictListPtr,
                  apptr->AuthGroupCan, apptr->AuthWorldCan,
                  apptr->NoCache,
                  apptr->VmsUserProfile, apptr->VmsUserScriptAs);
            }
            status = STS$K_ERROR;
            break;
         }
      }
      else
         ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI);

      if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_AUTH))
      {
         WatchDataFormatted (
"!&Z\n\
!&B !&Z !&Z !UL !&X\n\
!&Z !UL !&X\n\
!&Z !UL !&X\n\
!&Z !&Z\n\
!&Z\n\
!&X !&X\n\
!&X !&X !&B !&B !&B\n",
            apptr->PathPtr,
            apptr->FinalRule,
            arptr->RealmPtr, arptr->RealmDescrPtr,
            arptr->SourceRealm, arptr->RealmVmsIdentifier,
            arptr->GroupWritePtr, arptr->SourceGroupWrite,
            arptr->GroupWriteVmsIdentifier,
            arptr->GroupReadPtr, arptr->SourceGroupRead,
            arptr->GroupReadVmsIdentifier,
            apptr->GroupRestrictListPtr,
            apptr->WorldRestrictListPtr,
            apptr->PathParameterPtr,
            arptr->AuthGroupCan, arptr->AuthWorldCan,
            apptr->AuthGroupCan, apptr->AuthWorldCan,
            apptr->NoCache,
            apptr->VmsUserProfile, apptr->VmsUserScriptAs);
      }

      if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_AUTH))
      {
         int  BufferCount;
         char  Buffer [2048];
         BufferCount = AuthConfigReportRule (rqptr, mclptr, true,
                                             Buffer, sizeof(Buffer));
         WatchData (Buffer, BufferCount);
      }

      /* quick check obvious non-/match before using expensive functions */
      tptr = apptr->PathPtr;
      pptr = PathBeingAuthorized;
      if (Config.cfMisc.RegexEnabled && *tptr == REGEX_CHAR) tptr++;
      /* while matching vanilla characters */
      while ((isalnum(*tptr) || *tptr == '/' ||
              *tptr == '-' || *tptr == '_') &&
             tolower(*pptr) == tolower(*tptr))
      {
         pptr++;
         tptr++;
      }
      /* if non-matching vanilla character */
      if (isalnum(*tptr) || *tptr == '/' || *tptr == '-' || *tptr == '_')
      {
         if (!WatchThisMatch) continue;
         WatchThis (rqptr, FI_LI, WATCH_MATCH, "NO !&Z", tptr);
         continue;
      }
      /* if a trailing wildcard then it's a match already! */
      if (*(unsigned short*)tptr == '*\0')
      {
         status = SS$_NORMAL;
         if (!WatchThisMatch) break;
         WatchThis (rqptr, FI_LI, WATCH_MATCH, "YES");
         break;
      }

      /* expensive comparison of path and template, break if matched */
      if (StringMatchAndRegex (rqptr, PathBeingAuthorized, apptr->PathPtr,
                               SMATCH_STRING_REGEX, &apptr->RegexPregPath,
                               NULL))
      {
         status = SS$_NORMAL;
         break;
      }
   }

   /*************************************/
   /* if path not found, or other error */
   /*************************************/

   if (VMSnok (status)) return (status);

   /* a final rule is effectively 'path not found' */
   if (apptr->FinalRule) return (STS$K_ERROR);

   /**************/
   /* path found */
   /**************/

   /*
      As the path authorization information is potentially volatile
      (i.e. can be reloaded) we need request-local copies of these fields.
   */ 

   TotalLength = 8 + /* number of terminating nulls */
                 /* these five are from the realm record */
                 arptr->GroupWriteLength +
                 arptr->GroupReadLength +
                 arptr->RealmLength +
                 arptr->RealmDescriptionLength +
                 /* these three are from the path record */
                 apptr->GroupRestrictListLength +
                 apptr->ProxyStringLength +
                 apptr->PathParameterLength +
                 apptr->WorldRestrictListLength;
   if (Debug) fprintf (stdout, "TotalLength: %d\n", TotalLength);
   cptr = VmGetHeap (rqptr, TotalLength);

   /******************************/
   /* data from the realm record */
   /******************************/

   rqptr->rqAuth.RealmProblem = arptr->RealmProblem;
   memcpy (rqptr->rqAuth.RealmPtr = cptr,
           arptr->RealmPtr, arptr->RealmLength+1);
   cptr += arptr->RealmLength+1;
   rqptr->rqAuth.RealmLength = arptr->RealmLength;
   rqptr->rqAuth.SourceRealm = arptr->SourceRealm;
   rqptr->rqAuth.RealmVmsIdentifier = arptr->RealmVmsIdentifier;

   if (arptr->RealmDescrPtr[0])
   {
      memcpy (rqptr->rqAuth.RealmDescrPtr = cptr,
              arptr->RealmDescrPtr, arptr->RealmDescriptionLength+1);
      cptr += arptr->RealmDescriptionLength+1;
   }
   else
      rqptr->rqAuth.RealmDescrPtr = rqptr->rqAuth.RealmPtr;

   memcpy (rqptr->rqAuth.ProxyStringPtr = cptr,
           apptr->ProxyStringPtr, apptr->ProxyStringLength+1);
   cptr += apptr->ProxyStringLength+1;
   rqptr->rqAuth.ProxyStringLength = apptr->ProxyStringLength;

   memcpy (rqptr->rqAuth.GroupWritePtr = cptr,
           arptr->GroupWritePtr, arptr->GroupWriteLength+1);
   cptr += arptr->GroupWriteLength+1;
   rqptr->rqAuth.GroupWriteLength = arptr->GroupWriteLength;
   rqptr->rqAuth.SourceGroupWrite = arptr->SourceGroupWrite;
   rqptr->rqAuth.GroupWriteVmsIdentifier = arptr->GroupWriteVmsIdentifier;

   memcpy (rqptr->rqAuth.GroupReadPtr = cptr,
           arptr->GroupReadPtr, arptr->GroupReadLength+1);
   cptr += arptr->GroupReadLength+1;
   rqptr->rqAuth.GroupReadLength = arptr->GroupReadLength;
   rqptr->rqAuth.SourceGroupRead = arptr->SourceGroupRead;
   rqptr->rqAuth.GroupReadVmsIdentifier = arptr->GroupReadVmsIdentifier;

   /*****************************/
   /* data from the path record */
   /*****************************/

   rqptr->rqAuth.PathProblem = apptr->PathProblem;

   memcpy (rqptr->rqAuth.GroupRestrictListPtr = cptr,
           apptr->GroupRestrictListPtr, apptr->GroupRestrictListLength+1);
   cptr += apptr->GroupRestrictListLength+1;

   memcpy (rqptr->rqAuth.WorldRestrictListPtr = cptr,
           apptr->WorldRestrictListPtr, apptr->WorldRestrictListLength+1);
   rqptr->rqAuth.WorldRestrictListPtr = apptr->WorldRestrictListPtr;

   memcpy (rqptr->rqAuth.PathParameterPtr = cptr,
           apptr->PathParameterPtr, apptr->PathParameterLength+1);
   cptr += apptr->PathParameterLength + 1;
   rqptr->rqAuth.PathParameterLength = apptr->PathParameterLength;

   rqptr->rqAuth.GroupCan = apptr->AuthGroupCan;
   rqptr->rqAuth.WorldCan = apptr->AuthWorldCan;

   rqptr->rqAuth.NoCache = apptr->NoCache;
   rqptr->rqAuth.VmsUserProfile = apptr->VmsUserProfile;
   rqptr->rqAuth.VmsUserScriptAs = apptr->VmsUserScriptAs;

   return (SS$_NORMAL);
}

/****************************************************************************/
/*
"Realm Description"=<realm>=<type>:<access>:<restriction>
Returns a NULL is successfully parses, a pointer to an explanatory string if
not successful.
*/ 

char* AuthConfigParseProtectRule
(
REQUEST_STRUCT *rqptr,
char *RulePtr,
int RuleLength
)
{
   static unsigned long  RealmVmsIdentifier;
   static char  PrevRealm [AUTH_MAX_REALM_GROUP_LENGTH+1];

   int  status,
        AccessLength,
        GroupCan,
        RealmLength,
        RealmDescrLength,
        RealmSourceLength,
        RestrictLength,
        SourceRealm,
        StringLength,
        WorldCan;
   char  *bptr, *cptr, *sptr, *zptr,
         *AccessPtr,
         *BufferPtr,
         *RealmPtr,
         *RealmDescrPtr,
         *RealmSourcePtr,
         *RestrictPtr;
   char  Buffer [1024];

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

   if (WATCH_MODULE(WATCH_MOD_AUTH))
      WatchThis (NULL, FI_LI, WATCH_MOD_AUTH,
                 "AuthConfigParseProtectRule() !&Z", RulePtr);

   if (RuleLength <= 0) RuleLength = strlen(RulePtr);

   if (rqptr)
   {
      BufferPtr = VmGetHeap (rqptr, RuleLength+1);
      memcpy (BufferPtr, RulePtr, RuleLength+1);
      /* preemptively set this true, then reset once it's parsed ok */
      rqptr->rqAuth.RealmProblem = true;
   }
   else
   {
      if (RuleLength+1 > sizeof(Buffer)) return ("Buffer overflow");
      memcpy (BufferPtr = Buffer, RulePtr, RuleLength+1);
   }

   zptr = (sptr = bptr = BufferPtr) + RuleLength+1;
   cptr = RulePtr;

   RealmDescrPtr = NULL;
   if (*cptr == '\"')
   {
      /* optional realm description */
      cptr++;
      RealmDescrPtr = bptr;
      while (*cptr && *cptr != '\"')
      {
         if (cptr[0] == '\\' && cptr[1]) cptr++;
         if (sptr < zptr) *sptr++ = *cptr++;
      }
      *sptr = '\0';
      RealmDescrLength = sptr - RealmDescrPtr;
      sptr++;
      if (!RealmDescrLength) return ("Realm description not specified");
      if (!*cptr) return ("Mandatory parameter(s) missing");
      cptr++;
      if (*cptr != '=') return ("Mandatory parameter(s) missing");
      cptr++;
   }
   else
   {
      RealmDescrPtr = bptr;
      *sptr++ = '\0';
      RealmDescrLength = 0;
   }
   bptr += RealmDescrLength + 1;

   /* mandatory authentication realm */
   RealmPtr = sptr = bptr;
   while (*cptr && *cptr != '=' && sptr < zptr) *sptr++ = *cptr++;
   *sptr = '\0';
   RealmLength = sptr - bptr;
   sptr++;
   if (!RealmLength) return ("Realm not specified");
   if (!*cptr) return ("Mandatory parameter(s) missing");
   cptr++;
   bptr += RealmLength + 1;

   if (!RealmDescrLength)
   {
      /* must have a realm description of some sort - use the realm name */
      RealmDescrPtr = RealmPtr;
      RealmDescrLength = RealmLength;
   }

   /* mandatory authentication source */
   RealmSourcePtr = sptr = bptr;
   while (*cptr && *cptr != ':' && sptr < zptr) *sptr++ = *cptr++;
   *sptr = '\0';
   RealmSourceLength = sptr - bptr;
   sptr++;
   if (!RealmSourceLength) return ("Realm type not specified");
   if (!*cptr) return ("Mandatory parameter(s) missing");
   cptr++;
   bptr += RealmSourceLength + 1;

   switch (tolower(RealmSourcePtr[0]))
   {
      case '@' :
         switch (tolower(RealmSourcePtr[1]))
         {
            case 'l' : SourceRealm = AUTH_SOURCE_DIR_LIST; break;
            case 'h' : SourceRealm = AUTH_SOURCE_DIR_HTA;  break;
            default : return ("Realm source incorrect");
         }
         break;
      case 'a' : SourceRealm = AUTH_SOURCE_AGENT;    break;
      case 'e' : SourceRealm = AUTH_SOURCE_EXTERNAL; break;
      case 'h' :
         switch (tolower(RealmSourcePtr[1]))
         {
            case 't' : SourceRealm = AUTH_SOURCE_HTA;  break;
            case 'o' : SourceRealm = AUTH_SOURCE_HOST; break;
            default : return ("Realm source incorrect");
         }
         break;
      case 'i' : SourceRealm = AUTH_SOURCE_ID;       break;
      case 'l' : SourceRealm = AUTH_SOURCE_LIST;     break;
      case 'r' : SourceRealm = AUTH_SOURCE_RFC1413;  break;
      case 'v' : SourceRealm = AUTH_SOURCE_VMS;      break;
      case 'w' :
         switch (tolower(RealmSourcePtr[1]))
         {
            /** case 'a' : SourceRealm = AUTH_SOURCE_WASD_ID; break; **/
            case 'o' : SourceRealm = AUTH_SOURCE_WORLD;   break;
            default : return ("Realm source incorrect");
         }
         break;
      case 'x' : SourceRealm = AUTH_SOURCE_X509;    break;
      default : return ("Realm source incorrect");
   }

   /* mandatory access string */
   AccessPtr = sptr = bptr;
   while (*cptr && *cptr != ':' && sptr < zptr) *sptr++ = *cptr++;
   *sptr = '\0';
   AccessLength = sptr - bptr;
   sptr++;
   if (!AccessLength) return ("Access not specified");
   if (*cptr) cptr++;
   bptr += AccessLength + 1;

   /* possibilities: "r", "r+w", "r,r", "r+w,r", "r+w,r+w", */
   cptr = AccessPtr;
   if (cptr[0] == 'r' && cptr[1] == '+' && cptr[2] == 'w')
   {
      GroupCan = AUTH_READWRITE_ACCESS;
      cptr += 3;
   }
   else
   if (cptr[0] == 'r' && (!cptr[1] || cptr[1] == ','))
   {
      GroupCan = AUTH_READONLY_ACCESS;
      cptr++;
   }
   else
      return ("Group access incorrect");
   if (cptr[0] == ',')
   {
      cptr++;
      if (cptr[0] == 'r' && cptr[1] == '+' && cptr[2] == 'w')
      {
         WorldCan = AUTH_READWRITE_ACCESS;
         cptr += 3;
      }
      else
      if (cptr[0] == 'r' && !cptr[1])
      {
         WorldCan = AUTH_READONLY_ACCESS;
         cptr++;
      }
      else
         return ("World access incorrect");
   }
   else
   if (cptr[0])
      return ("Access incorrect");
   else
      WorldCan = 0;

   /* access restriction string */
   RestrictPtr = sptr = bptr;
   while (*cptr && sptr < zptr) *sptr++ = *cptr++;
   *sptr = '\0';
   RestrictLength = sptr - bptr;
   sptr++;
   bptr += RestrictLength + 1;

   if (SourceRealm == AUTH_SOURCE_ID)
   {
      /* previous identifier value is buffered for a minor efficiency */
      if (!strsame (RealmPtr, PrevRealm, -1))
      {
         status = AuthConfigIdentifier (RealmPtr, &RealmVmsIdentifier);
         if (VMSnok (status))
         {
            if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_AUTH))
               WatchThis (rqptr, FI_LI, WATCH_AUTH,
                          "Realm !AZ identifier %!&M", RealmPtr, status);
            if (status == SS$_NOSUCHID) return ("Unknown rights identifier");
            return ("Rights identifier problem");
         }
         zptr = (sptr = PrevRealm) + sizeof(PrevRealm)-1;
         for (cptr = RealmPtr; *cptr && sptr < zptr; *sptr++ = *cptr++);
         *sptr = '\0';
      }
   }

   /* if just validating rather than applying the rule */
   if (!rqptr) return (NULL);

   /********************/
   /* apply to request */
   /********************/

   rqptr->rqAuth.RealmProblem = false;

   if (RealmDescrPtr)
      rqptr->rqAuth.RealmDescrPtr = RealmDescrPtr;
   else
      rqptr->rqAuth.RealmDescrPtr = RealmPtr;
   rqptr->rqAuth.RealmPtr = RealmPtr;
   rqptr->rqAuth.RealmLength = RealmLength;
   rqptr->rqAuth.SourceRealm = SourceRealm;
   if (SourceRealm == AUTH_SOURCE_ID)
      rqptr->rqAuth.RealmVmsIdentifier = RealmVmsIdentifier;
   else
      rqptr->rqAuth.RealmVmsIdentifier = 0;

   rqptr->rqAuth.GroupCan = GroupCan;
   rqptr->rqAuth.WorldCan = WorldCan;

   rqptr->rqAuth.GroupWritePtr = "";
   rqptr->rqAuth.GroupWriteLength =
      rqptr->rqAuth.SourceGroupWrite =
      rqptr->rqAuth.GroupWriteVmsIdentifier = 0;

   rqptr->rqAuth.GroupReadPtr = "";
   rqptr->rqAuth.GroupReadLength =
      rqptr->rqAuth.SourceGroupRead =
      rqptr->rqAuth.GroupReadVmsIdentifier = 0;

   rqptr->rqAuth.GroupRestrictListPtr = RestrictPtr;
   rqptr->rqAuth.WorldRestrictListPtr = "";

   rqptr->rqAuth.ProxyStringPtr = "";
   rqptr->rqAuth.ProxyStringLength = 0;

   rqptr->rqAuth.PathParameterPtr = "";
   rqptr->rqAuth.PathParameterLength = 0;

   rqptr->rqAuth.VmsUserProfile = 0;
   rqptr->rqAuth.VmsUserScriptAs = NULL;

   rqptr->rqAuth.NoCache = false;

   return (NULL);
}

/*****************************************************************************/
/*
This function just wraps the reporting function, loading a temporary database
if necessary for reporting from the configuration file.
*/ 

AuthConfigReport
(
REQUEST_STRUCT *rqptr,
REQUEST_AST NextTaskFunction,
BOOL UseServerDatabase,
char *VirtualService
)
{
   int  status;
   META_CONFIG  *mcptr;

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

   if (WATCHING(rqptr) && (WATCH_MODULE(WATCH_MOD_AUTH)))
      WatchThis (rqptr, FI_LI, WATCH_MOD_AUTH,
                 "AuthConfigReport() !&F !UL !&Z",
                 NextTaskFunction, UseServerDatabase, VirtualService);

   if (UseServerDatabase)
   {
      /* use mapping rules already loaded into the server */
      AuthConfigReportNow (rqptr, MetaGlobalAuthPtr, true, VirtualService);
   }
   else
   {
      /* load temporary set of rules from mapping file */
      status = AuthConfigLoad (&mcptr);
      if (VMSnok (status))
      {
         /* severe error reported */
         rqptr->rqResponse.HttpStatus = 403;
         ErrorGeneral (rqptr, mcptr->LoadReport.TextPtr, FI_LI);
      }
      else
         AuthConfigReportNow (rqptr, mcptr, false, VirtualService);
      MetaConUnload (&mcptr, NULL);
   }

   SysDclAst (NextTaskFunction, rqptr);
}

/*****************************************************************************/
/*
Display all records in the authorization path meta-config.
*/

AuthConfigReportNow
(
REQUEST_STRUCT *rqptr,
META_CONFIG *mcptr,
BOOL UseServerDatabase,
char *VirtualService
)
{
   static char  BeginRules [] =
"<P><H3><U>Authorization Rules</U></H3>\n";

   static char  BeginTable [] =
"<P><TABLE CELLPADDING=2 CELLSPACING=0 BORDER=0>\n\
<TR><TD><PRE>";

   static char  EndPageFao [] =
"</PRE>\n\
</TD></TR>\n\
</TABLE>\n\
<HR SIZE=1 NOSHADE WIDTH=60% ALIGN=left>\n\
</BODY>\n\
</HTML>\n";

   BOOL  ThisVirtualService;
   int  status,
        BufferCount;
   char  *cptr;
   char  Buffer [2048];
   AUTH_PATH  *apptr;
   METACON_LINE  *mclptr;

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

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_AUTH))
      WatchThis (rqptr, FI_LI, WATCH_MOD_AUTH,
                 "AuthConfigReportNow() !&B !&Z",
                 UseServerDatabase, VirtualService);

   rqptr->rqResponse.PreExpired = PRE_EXPIRE_ADMIN;
   RESPONSE_HEADER_200_HTML (rqptr);
   AdminPageTitle (rqptr, "Path Authorization");
   AdminMetaConReport (rqptr, mcptr, MetaGlobalAuthPtr);
   AdminMetaConSource (rqptr, mcptr, MetaGlobalAuthPtr, mcptr->IncludeFile);

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

   AdminVirtualServiceForm (rqptr, ADMIN_REPORT_AUTH_PATHS,
                            VirtualService, UseServerDatabase);

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

   ThisVirtualService = true;

   MetaConParseReset (mcptr, true);
   while (mclptr = MetaConParseRaw (mcptr))
   {
      if (mclptr->Token == METACON_TOKEN_SERVICE)
      {
         /* if filtering on a virtual service */
         if (VirtualService[0] &&
             *(unsigned long*)mclptr->TextPtr != '*:*\0' &&
             !StringMatch (rqptr, mclptr->TextPtr, VirtualService))
            ThisVirtualService = false;
         else
            ThisVirtualService = true;
      }
      else
      if (!ThisVirtualService)
         continue;

      BufferCount = AuthConfigReportRule (rqptr, mclptr, false,
                                          Buffer, sizeof(Buffer));
      NetWriteBuffered (rqptr, NULL, Buffer, BufferCount);

      /* break if it's a final record of a specific virtual service */
      if (!VirtualService[0]) continue;
      if (!(cptr = mclptr->LineDataPtr)) continue;
      if (*(int*)cptr == AUTH_PATH_RECORD_TYPE)
      {
         apptr = (AUTH_PATH*)cptr;
         if (apptr->FinalRule) break;
      }
   }

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

/*****************************************************************************/
/*
Display all a single records from the authorization path meta-config.
*/

int AuthConfigReportRule
(
REQUEST_STRUCT *rqptr,
METACON_LINE *mclptr,
BOOL WatchRule,
char *Buffer,
int SizeOfBuffer
)
{
   static char  PathFao [] =
"<B>!3ZL</B>  !#* !&?<STRIKE>\r\r!AZ  G:!AZ  W:!AZ\
!&?  FINAL\r\r!&?  PROFILE\r\r!&?  SCRIPTAS\r\r!&?  NOCACHE\r\r\n\
!&@!&@!&@!&@!&?</STRIKE>\r\r";

   static char  WatchPathFao [] =
"!3ZL  !#* !&?\r\r!AZ  G:!AZ  W:!AZ\
!&?  FINAL\r\r!&?  PROFILE\r\r!&?  SCRIPTAS\r\r!&?  NOCACHE\r\r\n\
!&@!&@!&@!&@!&? <--ERROR\r\r";

   static char  RealmFao [] =
"<B>!3ZL</B>  !#* !&?<STRIKE>\r\r<B>\
[!&@!&;AZ!AZ;!&;AZ!AZ;!&;AZ!AZ]</B>!&?</STRIKE>\r\r\n";

   static char  WatchRealmFao [] =
"!3ZL  !#* !&?\r\r<B>\
[!&@!&;AZ!AZ;!&;AZ!AZ;!&;AZ!AZ]!&? <--ERROR\r\r\n";

   static char  NonRuleFao [] =
"<B>!3ZL</B>  !#* !&?<STRIKE>\r\r<B>!#AZ</B>  !&;AZ!&?</STRIKE>\r\r\n";

   static char  WatchNonRuleFao [] =
"!3ZL  !#* !&?\r\r!#AZ  !&;AZ!&? <--ERROR\r\r\n";

   static char  RuleVirtualFao [] =
"<B>!3ZL</B>  !#* !&?<STRIKE>\r\r<B>[[!&;AZ]]</B>!&?</STRIKE>\r\r\n";

   static char  RuleImplicitVirtualFao [] =
"<B>000</B>  !#* <B>[[*:*]]</B>\n";

   static char  WatchRuleVirtualFao [] =
"!3ZL  !#* !&?\r\r<B>[[!&;AZ]]!&? <--ERROR\r\r\n";

   int  status;
   unsigned short  Length;
   unsigned long  FaoVector [64];
   unsigned long  *vecptr;
   char  *cptr;
   char  GroupCanString [128],
         WorldCanString [128];
   AUTH_PATH  *apptr;
   AUTH_REALM  *arptr;

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

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_AUTH))
      WatchThis (rqptr, FI_LI, WATCH_MOD_AUTH, "AuthConfigReportRule()");

   if (mclptr->Token == METACON_TOKEN_SERVICE)
   {
      vecptr = FaoVector;
      *vecptr++ = mclptr->Number;
      *vecptr++ = (mclptr->MetaFileLevel+mclptr->FlowControlLevel) * 3;
      *vecptr++ = mclptr->ConfigProblem;
      *vecptr++ = mclptr->TextPtr;
      *vecptr++ = mclptr->ConfigProblem;
      status = WriteFaol (Buffer, SizeOfBuffer, &Length,
                          WatchRule ? WatchRuleVirtualFao : RuleVirtualFao,
                          &FaoVector);
      if (VMSnok (status)) ErrorNoticed (status, "WriteFaol()", FI_LI);
      return ((int)Length);
   }
   else
   if (mclptr->Number == 1)
   {
      vecptr = FaoVector;
      *vecptr++ = (mclptr->MetaFileLevel+mclptr->FlowControlLevel) * 3;
      status = NetWriteFaol (rqptr, RuleImplicitVirtualFao, &FaoVector);
      if (VMSnok (status)) ErrorNoticed (status, "NetWriteFaol()", FI_LI);
   }

   if (mclptr->Token != METACON_TOKEN_TEXT)
   {
      vecptr = FaoVector;
      *vecptr++ = mclptr->Number;
      switch (mclptr->Token)
      {
         case METACON_TOKEN_ELIF :
         case METACON_TOKEN_ELSE :
         case METACON_TOKEN_UNIF :
         case METACON_TOKEN_IFIF :
            if (mclptr->FlowControlLevel)
               *vecptr++ = (mclptr->MetaFileLevel+
                            mclptr->FlowControlLevel) * 3;
            else
               *vecptr++ = (mclptr->MetaFileLevel+
                            mclptr->FlowControlLevel+1) * 3;
            break;
         default:
            *vecptr++ = (mclptr->MetaFileLevel+
                         mclptr->FlowControlLevel+1) * 3;
      }
      for (cptr = mclptr->TextPtr; *cptr && !ISLWS(*cptr); cptr++);
      *vecptr++ = mclptr->ConfigProblem;
      *vecptr++ = cptr - mclptr->TextPtr;
      *vecptr++ = mclptr->TextPtr;
      while (*cptr && ISLWS(*cptr)) cptr++;
      *vecptr++ = cptr;
      *vecptr++ = mclptr->ConfigProblem;
      status = WriteFaol (Buffer, SizeOfBuffer, &Length,
                          WatchRule ? WatchNonRuleFao : NonRuleFao,
                          &FaoVector);
      if (VMSnok (status)) ErrorNoticed (status, "WriteFaol()", FI_LI);
      return ((int)Length);
   }

   /* for example, proxy configuration */
   if (!(cptr = mclptr->LineDataPtr)) return (0);

   /* check the record type integer of this record */
   apptr = arptr = NULL;
   if (*(int*)cptr == AUTH_REALM_RECORD_TYPE)
      arptr = (AUTH_REALM*)cptr;
   else
   if (*(int*)cptr == AUTH_PATH_RECORD_TYPE)
      apptr = (AUTH_PATH*)cptr;
   else
      ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI);

   if (apptr)
   {
      /**********/
      /* a path */
      /**********/

      cptr = AuthCanString (apptr->AuthGroupCan, AUTH_CAN_FORMAT_SHORT);
      if (!cptr) cptr = "*ERROR*";
      strcpy (GroupCanString, cptr);

      cptr = AuthCanString (apptr->AuthWorldCan, AUTH_CAN_FORMAT_SHORT);
      if (!cptr) cptr = "*ERROR*";
      strcpy (WorldCanString, cptr);

      vecptr = FaoVector;
      *vecptr++ = mclptr->Number;
      *vecptr++ = (mclptr->MetaFileLevel+
                   mclptr->FlowControlLevel) * 3 + 3;
      *vecptr++ = apptr->PathProblem;
      *vecptr++ = apptr->PathPtr;
      *vecptr++ = GroupCanString;
      *vecptr++ = WorldCanString;
      *vecptr++ = apptr->FinalRule;
      *vecptr++ = apptr->VmsUserProfile;
      *vecptr++ = apptr->VmsUserScriptAs;
      *vecptr++ = apptr->NoCache;
     
      if (apptr->GroupRestrictListPtr[0])
      {
         *vecptr++ = "!#* G:!&;AZ\n";
         *vecptr++ = (mclptr->MetaFileLevel+
                      mclptr->FlowControlLevel) * 3 + 8;
         *vecptr++ = apptr->GroupRestrictListPtr;
      }
      else
         *vecptr++ = "";

      if (apptr->WorldRestrictListPtr[0])
      {
         *vecptr++ = "!#* W:!&;AZ\n";
         *vecptr++ = (mclptr->MetaFileLevel+
                      mclptr->FlowControlLevel) * 3 + 8;
         *vecptr++ = apptr->WorldRestrictListPtr;
      }
      else
         *vecptr++ = "";

      if (apptr->ProxyStringPtr[0])
      {
         *vecptr++ = "!#* !&;AZ\n";
         *vecptr++ = (mclptr->MetaFileLevel+
                      mclptr->FlowControlLevel) * 3 + 8;
         *vecptr++ = apptr->ProxyStringPtr;
      }
      else
         *vecptr++ = "";

      if (apptr->PathParameterPtr[0])
      {
         *vecptr++ = "!#* PARAM=\"!&;AZ\"\n";
         *vecptr++ = (mclptr->MetaFileLevel+
                      mclptr->FlowControlLevel) * 3 + 8;
         *vecptr++ = apptr->PathParameterPtr;
      }
      else
         *vecptr++ = "";

      *vecptr++ = apptr->PathProblem;

      status = WriteFaol (Buffer, SizeOfBuffer, &Length,
                          WatchRule ? WatchPathFao : PathFao, &FaoVector);
      if (VMSnok (status)) ErrorNoticed (status, "WriteFaol()", FI_LI);
      return ((int)Length);
   }

   if (arptr)
   {
      /***********/
      /* a realm */
      /***********/

      vecptr = FaoVector;
      *vecptr++ = mclptr->Number;
      *vecptr++ = (mclptr->MetaFileLevel+mclptr->FlowControlLevel) * 3;
      *vecptr++ = arptr->RealmProblem;
      if (arptr->RealmDescrPtr && arptr->RealmDescrPtr[0])
      {
         *vecptr++ = "&quot;!&;AZ&quot;=";
         *vecptr++ = arptr->RealmDescrPtr;
      }
      else
         *vecptr++ = "";
      *vecptr++ = arptr->RealmPtr;
      *vecptr++ = AuthSourceString (arptr->RealmPtr, arptr->SourceRealm);
      *vecptr++ = arptr->GroupWritePtr;
      *vecptr++ = AuthSourceString (arptr->GroupWritePtr,
                                    arptr->SourceGroupWrite);
      *vecptr++ = arptr->GroupReadPtr;
      *vecptr++ = AuthSourceString (arptr->GroupReadPtr,
                                    arptr->SourceGroupRead);
      *vecptr++ = arptr->RealmProblem;

      status = WriteFaol (Buffer, SizeOfBuffer, &Length,
                          WatchRule ? WatchRealmFao : RealmFao, &FaoVector);
      if (VMSnok (status)) ErrorNoticed (status, "WriteFaol()", FI_LI);
      return ((int)Length);
   }

   return (0);
}

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

