/*****************************************************************************/
/*
                               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
---------------
13-SEP-2013  MGD  bugfix; AuthConfigLoadCallBack() additional [AuthProxy]
                    with intervening rules should reset proxies
05-MAY-2013  MGD  read-only group can be specified as "*" for everyone else
09-SEP-2012  MGD  TOKEN authentication
18-JUL-2010  MGD  Uni Malaga pushes report items from !3ZL to !4ZL :-)
07-JUN-2009  MGD  AuthConfigInit() move WASD_NO_ACME to LNM$FILE_DEV
19-APR-2008  MGD  logical name WASD_NO_SYSUAF_ACME disables SYSUAF via ACME
                  logical name WASD_NO_ACME disables ACME altogether
24-NOV-2007  MGD  add AGENT+OPAQUE
                  add 'PathLocationPtr' datum
                  force ACME on VMS V7.3 and later
                  AuthConfigLoadCallBack() do not lower-case path
12-JUL-2006  MGD  increase AUTH_MAX_PATH_PARAM_LENGTH from 127 to 255
                  (initially prompted by development of AUTHAGENT_LDAP)
                  add '+=' to realm default syntax to cause realm default
                    to be concatenated to any path access directives
                  bugfix; propagate 'RealmCanString' by making it static
28-MAR-2006  MGD  note in boolean 'AuthRealmX509' if X509 realm in use
08-OCT-2005  MGD  bugfix; AuthConfigProxyMap() set cache record SYSUAF
                  authentication boolean in tandem with request boolean
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 FaolToNet(), 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_V7
#undef _VMS__V6__SOURCE
#define _VMS__V6__SOURCE
#undef __VMS_VER
#define __VMS_VER 70000000
#undef __CRTL_VER
#define __CRTL_VER 70000000
#endif

/* standard C header files */
#include <ctype.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
#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,
      AuthConfigSysUafUseACME,
      AuthFatalProblem,
      AuthNilAccessExists,
      AuthPolicyAuthorizedOnly,
      AuthPolicySysUafRelaxed,
      AuthPolicySslOnly,
      AuthPolicySysUafIdentifiers,
      AuthPolicySysUafProxy,
      AuthPolicySysUafSslOnly,
      AuthPolicySysUafVms,
      AuthPolicySysUafWasdIdentifiers,
      AuthPromiscuous,
      AuthProtectRule,
      AuthRealmToken,
      AuthRealmX509,
      AuthSysUafEnabled,
      AuthSysUafPromiscuous,
      AuthVmsUserProfileEnabled,
      AuthVmsUserProfileNoRule;

int  AuthFailureLimit,
     AuthFailurePeriodSeconds,
     AuthFailureTimeoutSeconds;

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

char  *AuthConfigHtaDirectory,
      *AuthPromiscuousPwdPtr;

AUTH_CONFIG_META  AuthMeta;
AUTH_CONFIG_META  *AuthMetaPtr;

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

extern BOOL  AuthAcmeLinked;

extern int  EfnWait,
            ServerPort;

extern int  ToLowerCase[],
            ToUpperCase[];

extern char  ErrorSanityCheck[],
             SoftwareID[];

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 }
   };
   static $DESCRIPTOR (LnmFileDevDsc, "LNM$FILE_DEV");
   static $DESCRIPTOR (NoAcmeDsc, "WASD_NO_ACME");
   static $DESCRIPTOR (NoSysUafAcmeDsc, "WASD_NO_SYSUAF_ACME");

   int  status;
   IO_SB  IOsb;

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

   if (WATCH_MODULE(WATCH_MOD_AUTH))
      WatchThis (WATCHALL, WATCH_MOD_AUTH, "AuthConfigInit()");

   /* if ACME available and linked into this image */
   if (SysInfo.VersionInteger >= 730 && AuthAcmeLinked)
   {
      /* provide ACME on capable systems unless development logical defined */
      status = sys$trnlnm (0, &LnmFileDevDsc, &NoAcmeDsc, 0, 0);
      if (status == SS$_NOLOGNAM) AuthConfigACME = true;
   }
   if (AuthConfigACME)
   {
      /* allow mandatory SYSUAF authentication using ACME to be disabled */
      status = sys$trnlnm (0, &LnmFileDevDsc, &NoSysUafAcmeDsc, 0, 0);
      if (status == SS$_NOLOGNAM) AuthConfigSysUafUseACME = true;
   }

   MetaConUnload (&MetaGlobalAuthPtr, NULL);

   AuthConfigLoad (&MetaGlobalAuthPtr);

   AuthConfigHtaDirectory = v10orPrev10 (HTA_DIRECTORY,-1);

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

   if (WATCH_MODULE(WATCH_MOD_AUTH))
      WatchThis (WATCHALL, 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 (WATCHALL, WATCH_MOD_AUTH, "AuthConfigLoad()");

   status = MetaConLoad (MetaConPtrPtr,
                         v10orPrev10(CONFIG_AUTH_FILE_NAME,
                                     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 (WATCHALL, 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 (WATCHALL, 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 (WATCHALL, 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) */
      if (acptr->ProxyRuleReset)
      {
         AuthConfigProxy (NULL, NULL);
         acptr->ProxyRuleReset = false;
      }
      AuthConfigProxy (mcptr, cptr+11);
      return (true);
   }

   if (strsame (cptr, "[AuthProxyFile]", 15))
   {
      /* file of SYSUAF proxy mappings */
      if (acptr->ProxyRuleReset)
      {
         AuthConfigProxy (NULL, NULL);
         acptr->ProxyRuleReset = false;
      }

      /* 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);
         FaoToBuffer (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);
   }

   acptr->ProxyRuleReset = 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 (WATCHALL, WATCH_MOD_AUTH, "AuthConfigOther()");

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

   AuthNilAccessExists = false;

   /* 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);
      else
         AuthNilAccessExists = true;

      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);

   AuthTokenInit ();

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

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

#define PATH_PARAMETER_SIZE AUTH_MAX_PATH_PARAM_LENGTH
#define ACCESS_RESTRICTION_LIST_SIZE PATH_PARAMETER_SIZE+255

AuthConfigLine
(
META_CONFIG *mcptr,
char *Line
)
{
   static int  CurrentSourceRealm;
   static char  RealmCanString [ACCESS_RESTRICTION_LIST_SIZE+1];

   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  CanScratch [ACCESS_RESTRICTION_LIST_SIZE*2+1],
         GroupRead [AUTH_MAX_REALM_GROUP_LENGTH+1],
         GroupRestrictList [ACCESS_RESTRICTION_LIST_SIZE+1],
         GroupWrite [AUTH_MAX_REALM_GROUP_LENGTH+1],
         PathParameter [PATH_PARAMETER_SIZE+1],
         Realm [AUTH_MAX_REALM_GROUP_LENGTH+1],
         RealmDescription [AUTH_MAX_REALM_DESCR_LENGTH+1],
         RealmParameter [AUTH_MAX_REALM_PARAM_LENGTH+1],
         WorldRestrictList [ACCESS_RESTRICTION_LIST_SIZE+1];
   METACON_LINE  *mclptr;
   regex_t  RegexPreg;

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

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

   if (!Line)
   {
      /* initialize */
      CurrentSourceRealm = 0;
      RealmCanString[0] = '\0';
      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] = RealmParameter[0] =
      PathParameter[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 != '+' &&
             *lptr != ']' &&
             !ISLWS(*lptr))
      {
         if (sptr < zptr) *sptr++ = TOUP(*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+OPAQUE", 13))
         {
            lptr += 13;
            SourceRealm = AUTH_SOURCE_AGENT_OPAQUE;
         }
         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, "=TOKEN", 6))
         {
            lptr += 6;
            SourceRealm = AUTH_SOURCE_TOKEN;
         }
         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
      /* same for "OPAQUE" */
      if (strsame (Realm, AUTH_REALM_OPAQUE, -1))
         SourceRealm = AUTH_SOURCE_OPAQUE;
      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 "TOKEN" */
      if (strsame (Realm, AUTH_REALM_TOKEN, -1))
         SourceRealm = AUTH_SOURCE_TOKEN;
      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++;

      if (*lptr == '+')
      {
         /****************************/
         /* optional realm parameter */
         /****************************/

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

      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)-1;
         while (*lptr &&
                *lptr != ';' &&
                *lptr != '=' &&
                *lptr != ']' &&
                !ISLWS(*lptr) &&
                sptr < zptr) *sptr++ = TOUP(*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+OPAQUE", 13))
            {
               lptr += 13;
               SourceGroupWrite = AUTH_SOURCE_AGENT_OPAQUE;
            }
            else
            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
            if (strsame (lptr, "=TOKEN", 6))
            {
               lptr += 6;
               SourceGroupWrite = AUTH_SOURCE_TOKEN;
            }
            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)-1;
         while (*lptr &&
                *lptr != '=' &&
                *lptr != ']' &&
                !ISLWS(*lptr) &&
                sptr < zptr) *sptr++ = TOUP(*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+OPAQUE", 13))
            {
               lptr += 6;
               SourceGroupRead = AUTH_SOURCE_AGENT_OPAQUE;
            }
            else
            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
            if (strsame (lptr, "=TOKEN", 6))
            {
               lptr += 6;
               SourceGroupRead = AUTH_SOURCE_TOKEN;
            }
            else
            {
               MetaConReport (mcptr, METACON_REPORT_ERROR,
                              "Problem configuring second group source");
               RealmProblem = true;
               lptr = "]";
            }
         }
         else
         if (GroupRead[0] == '*')
            SourceGroupRead = AUTH_SOURCE_THEREST;
         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
      {
         lptr = "";
         MetaConReport (mcptr, METACON_REPORT_ERROR, "Generally confused\n");
         RealmProblem = true;
      }

      /* find start of any realm-context access directives */
      while (*lptr && ISLWS(*lptr)) lptr++;
      if (*lptr)
      {
         zptr = (sptr = RealmCanString) + sizeof(RealmCanString)-1;
         while (*lptr && sptr < zptr) *sptr++ = *lptr++;
         *sptr = '\0';
      }
      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;
      }

      if (SourceRealm == AUTH_SOURCE_TOKEN)
      {
         int  IPport = atoi(Realm);
         if (Realm[0] && IPport < 0 || IPport > 65535)
         {
            MetaConReport (mcptr, METACON_REPORT_ERROR,
                           "TOKEN port misconfigured");
            RealmProblem = true;
         }
      }

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

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

      CurrentSourceRealm = SourceRealm;
      return;
   }

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

      if (!CurrentSourceRealm)
         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 (SAME2(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.RegexSyntax && *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 (CurrentSourceRealm == AUTH_SOURCE_OPAQUE)
         {
            /* should be nothing more on the line but there is */
            MetaConReport (mcptr, METACON_REPORT_WARNING,
               "Paths in opaque realms require no parameters - ignored");
            lptr = "";
         }
         else
         if (SAME2(RealmCanString,'+='))
         {
            /* concatenate realm defaults to path access directives */
            zptr = (sptr = CanScratch) + sizeof(CanScratch)-1;
            while (*lptr && sptr < zptr) *sptr++ = *lptr++;
            for (lptr = RealmCanString+2;
                 *lptr && sptr < zptr;
                 *sptr++ = *lptr++);
            *sptr = '\0';
            lptr = CanScratch;
         }
      }
      else
      {
         if (CurrentSourceRealm != AUTH_SOURCE_OPAQUE)
         {
            /* nothing more on the line! */
            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 = PathParameter) + PATH_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_READ_METHODS |
                            HTTP_WRITE_METHODS |
                            HTTP_WEBDAV_READ_METHODS |
                            HTTP_WEBDAV_WRITE_METHODS;
         else
         if ((strsame (lptr, "READ", 4) && !isalpha(lptr[4])) ||
             (strsame (lptr, "R", 1) && !isalpha(lptr[1])))
            *CanFlagsPtr |= HTTP_READ_METHODS |
                            HTTP_WEBDAV_READ_METHODS;
         else
         if ((strsame (lptr, "WRITE", 5) && !isalpha(lptr[5])) ||
             (strsame (lptr, "W", 1) && !isalpha(lptr[1])))
            *CanFlagsPtr |= HTTP_WRITE_METHODS;
         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, "OPTIONS", 7) && !isalpha(lptr[7]))
            *CanFlagsPtr |= HTTP_METHOD_OPTIONS;
         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
         if (strsame (lptr, "TRACE", 5) && !isalpha(lptr[5]))
            *CanFlagsPtr |= HTTP_METHOD_TRACE;
         else
         if (strsame (lptr, "COPY", 4) && !isalpha(lptr[4]))
            *CanFlagsPtr |= HTTP_METHOD_WEBDAV_COPY;
         else
         if (strsame (lptr, "LOCK", 4) && !isalpha(lptr[4]))
            *CanFlagsPtr |= HTTP_METHOD_WEBDAV_LOCK;
         else
         if (strsame (lptr, "MOVE", 4) && !isalpha(lptr[4]))
            *CanFlagsPtr |= HTTP_METHOD_WEBDAV_MOVE;
         else
         if (strsame (lptr, "MKCOL", 5) && !isalpha(lptr[5]))
            *CanFlagsPtr |= HTTP_METHOD_WEBDAV_MKCOL;
         else
         if (strsame (lptr, "PROPFIND", 8) && !isalpha(lptr[8]))
            *CanFlagsPtr |= HTTP_METHOD_WEBDAV_PROPFIND;
         else
         if (strsame (lptr, "PROPPATCH", 9) && !isalpha(lptr[9]))
            *CanFlagsPtr |= HTTP_METHOD_WEBDAV_PROPPATCH;
         else
         if (strsame (lptr, "UNLOCK", 6) && !isalpha(lptr[6]))
            *CanFlagsPtr |= HTTP_METHOD_WEBDAV_UNLOCK;
         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, PathParameter, CurrentSourceRealm,
                         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,
char *RealmParameter,
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,
        RealmParamLength,
        StringSpace,
        WorldRestrictListLength;
   char  *cptr, *pptr, *sptr, *tptr,
         *OffsetPtr;
   AUTH_REALM  *arptr;
   METACON_LINE  *mclptr;

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

   if (WATCH_MODULE(WATCH_MOD_AUTH))
   {
      WatchThis (WATCHALL, WATCH_MOD_AUTH, "AuthConfigAddRealm()");
      WatchDataFormatted (
"!&B !&Z !&Z !&Z !UL !&X\n\
!&Z !UL !&X\n\
!&Z !UL !&X\n\
!&X !&X\n",
         RealmProblem,
         Realm, RealmDescription, RealmParameter,
         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);
   RealmParamLength = strlen(RealmParameter);

   StringSpace = 5 + /* number of terminating nulls! */
                 GroupReadLength +
                 GroupWriteLength +
                 RealmLength +
                 RealmDescriptionLength +
                 RealmParamLength;

   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->RealmParamPtr = OffsetPtr;
   memcpy (arptr->RealmParamPtr, RealmParameter, RealmParamLength+1);
   OffsetPtr += (arptr->RealmParamLength = RealmParamLength) + 1;

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

   if (SourceRealm == AUTH_SOURCE_TOKEN)
   {
      /* note that TOKEN authorization is in use */
      AuthRealmToken = true;
   }

   if (SourceRealm == AUTH_SOURCE_X509)
   {
      /* note that X509 client certification is in use */
      AuthRealmX509 = true;
   }
}

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

AuthConfigAddPath
(
META_CONFIG *mcptr,
BOOL PathProblem,
char *Path,
BOOL PathIsRegex,
BOOL FinalRule,
char *GroupRestrictList,
char *WorldRestrictList,
char *ProxyStringPtr,
char *PathParameter,
unsigned long SourceRealm,
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 (WATCHALL, WATCH_MOD_AUTH, "AuthConfigAddPath()");
      WatchDataFormatted (
"!&B !&Z !&B !&B\n!&Z\n!&Z\n!&Z\n!&Z\n!UL !&X !&X !&B !&B !&B\n",
         PathProblem, Path, FinalRule, PathIsRegex,
         GroupRestrictList, WorldRestrictList,
         ProxyStringPtr, PathParameter, SourceRealm,
         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(PathParameter);
   WorldRestrictListLength = strlen(WorldRestrictList);

   StringSpace = 5 + /* number of terminating nulls! */
                 GroupRestrictListLength +
                 PathLength +
                 ProxyStringLength +
                 PathParameterLength +
                 WorldRestrictListLength;

   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;
   apptr->SourceRealm = SourceRealm;

   /* 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++ = *cptr++;      
   *sptr = '\0';
   OffsetPtr += (apptr->GroupRestrictListLength =
                 GroupRestrictListLength) + 1;

   apptr->PathPtr = sptr = OffsetPtr;
   cptr = Path;
   while (*cptr) *sptr++ = *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++ = *cptr++;      
   *sptr = '\0';
   OffsetPtr += (apptr->WorldRestrictListLength =
                 WorldRestrictListLength) + 1;

   apptr->PathParameterPtr = OffsetPtr;
   memcpy (apptr->PathParameterPtr, PathParameter, 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 (WATCHALL, 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
'ProxyStringPtr' field of the authorisation record.  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 (WATCHMOD (rqptr, WATCH_MOD_AUTH))
      WatchThis (WATCHITM(rqptr), 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++ = TOUP(*cptr++);
   if (sptr >= zptr)
   {
      ErrorGeneralOverflow (rqptr, FI_LI);
      return (AUTH_DENIED_BY_OTHER);
   }
   *sptr = '\0';
   RemoteUserLength = sptr - RemoteUser;

   if (WATCHING (rqptr, WATCH_AUTH))
      WatchThis (WATCHITM(rqptr), WATCH_AUTH, "PROXY map !AZ@!AZ",
                 RemoteUser, rqptr->ClientPtr->Lookup.HostName);

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

      dptr = cptr;
      IsNetMask = false;
      SoFarSoGood = true;
      ErrorMsgPtr = NULL;
      ProxyUserName[0] = '\0';

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

         SET2(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 (*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 (IsNetMask)
         {
            char *tptr = RuleHost;  /* preserve string address using pointer */
            status = TcpIpNetMask (rqptr, WATCH_AUTH, &tptr,
                                   &rqptr->ClientPtr->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->ClientPtr->IpAddressString, RuleHost))
               SoFarSoGood = false;
         }
         else
         {
            /* if wildcard string compare fails the no match */
            if (!StringMatch (rqptr, rqptr->ClientPtr->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++ = TOUP(*cptr++);
            if (sptr >= zptr)
            {
               ErrorGeneralOverflow (rqptr, FI_LI);
               return (AUTH_DENIED_BY_OTHER);
            }
            *sptr = '\0';
            ProxyUserNameLength = sptr - ProxyUserName;
         }
      }

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

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

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

      if (SoFarSoGood && !SAME2(RuleUserName,'*\0'))
      {
         /* it's not a username wildcard rule */
         if (strsame (RuleUserName, RemoteUser, -1))
         {
            /* the remote (client) and rule usernames are the same */
            if (SAME2(ProxyUserName,'-\0'))
            {
               /* it's a forbidden match */
               if (WATCHING (rqptr, WATCH_AUTH))
                  WatchThis (WATCHITM(rqptr), 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_AUTH))
         WatchThis (WATCHITM(rqptr), WATCH_AUTH, "PROXY none");
      return (AUTH_DENIED_BY_LOGIN);
   }

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

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

   /* buffer the original remote user (the browser-supplied user id) */
   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;
      rqptr->rqAuth.SysUafAuthenticated = acrptr->SysUafAuthenticated = true;

      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_AUTH))
               WatchThis (WATCHITM(rqptr), 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 =
            acrptr->SysUafAuthenticated = true;
         strcpy (acrptr->ProxyUserName, ProxyUserName);
         acrptr->ProxyUserNameLength = ProxyUserNameLength;
      }
      else
      {
         /* the verification has failed for some reason */
         rqptr->rqAuth.SysUafAuthenticated =
            acrptr->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 (WATCHALL, 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);
   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 (WATCHMOD (rqptr, WATCH_MOD_AUTH))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_AUTH,
                 "AuthConfigSearch() !&Z", PathBeingAuthorized);

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

   RealmDataPtr = NULL;

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

      if (WATCHPNT(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_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 (SAME4 (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 (WATCHMOD (rqptr, 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 (WATCHMOD (rqptr, 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_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.RegexSyntax && *tptr == REGEX_CHAR) tptr++;
      /* while matching vanilla characters */
      while ((isalnum(*tptr) || *tptr == '/' ||
              *tptr == '-' || *tptr == '_') &&
             TOLO(*pptr) == TOLO(*tptr))
      {
         pptr++;
         tptr++;
      }
      /* if non-matching vanilla character */
      if (isalnum(*tptr) || *tptr == '/' || *tptr == '-' || *tptr == '_')
      {
         if (!WatchThisMatch) continue;
         WatchThis (WATCHITM(rqptr), WATCH_MATCH, "NO !&Z", tptr);
         continue;
      }
      /* if a trailing wildcard then it's a match already! */
      if (SAME2(tptr,'*\0'))
      {
         status = SS$_NORMAL;
         if (!WatchThisMatch) break;
         WatchThis (WATCHITM(rqptr), 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 = 10 + /* number of terminating nulls */
                 /* these five are from the realm record */
                 arptr->GroupWriteLength +
                 arptr->GroupReadLength +
                 arptr->RealmLength +
                 arptr->RealmDescriptionLength +
                 arptr->RealmParamLength +
                 /* these five are from the path record */
                 apptr->PathLength +
                 apptr->GroupRestrictListLength +
                 apptr->ProxyStringLength +
                 apptr->PathParameterLength +
                 apptr->WorldRestrictListLength;
   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;

   if (arptr->RealmParamPtr[0])
   {
      memcpy (rqptr->rqAuth.RealmParamPtr = cptr,
              arptr->RealmParamPtr, arptr->RealmParamLength+1);
      cptr += arptr->RealmParamLength+1;
   }

   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.PathLocationPtr = cptr,
           apptr->PathPtr, apptr->PathLength+1);
   cptr += apptr->PathLength+1;
   rqptr->rqAuth.PathLocationLength = apptr->PathLength;

   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 (WATCHALL, 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 (TOLO(RealmSourcePtr[0]))
   {
      case '@' :
         switch (TOLO(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' :
         if (strsame (RealmSourcePtr, "agent+opaque", 12))
            SourceRealm = AUTH_SOURCE_AGENT_OPAQUE;
         else
            SourceRealm = AUTH_SOURCE_AGENT;
         break;
      case 'e' : SourceRealm = AUTH_SOURCE_EXTERNAL; break;
      case 'h' :
         switch (TOLO(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 'o' : SourceRealm = AUTH_SOURCE_OPAQUE;   break;
      case 'r' : SourceRealm = AUTH_SOURCE_RFC1413;  break;
      case 'v' : SourceRealm = AUTH_SOURCE_VMS;      break;
      case 'w' :
         switch (TOLO(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");
   }

   /* opaque authorization doesn't need an access string */
   if (SourceRealm == AUTH_SOURCE_OPAQUE) return (NULL);

   /* 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_AUTH))
               WatchThis (WATCHITM(rqptr), 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,
BOOL UseServerDatabase,
char *VirtualService
)
{
   int  status;
   META_CONFIG  *mcptr;

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

   if (WATCHMOD (rqptr, WATCH_MOD_AUTH))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_AUTH,
                 "AuthConfigReport() !UL !&Z",
                 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);
   }

   AdminEnd (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 class=\"lftlft\">\n\
<tr><td><pre>";

   static char  EndPageFao [] =
"<b>----</b>\
</pre>\n\
</td></tr>\n\
</table>\n\
</div>\n\
</body>\n\
</html>\n";

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

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

   if (WATCHMOD (rqptr, WATCH_MOD_AUTH))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_AUTH,
                 "AuthConfigReportNow() !&B !&Z",
                 UseServerDatabase, VirtualService);

   AdminPageTitle (rqptr, "Path Authorization");
   AdminMetaConReport (rqptr, mcptr, MetaGlobalAuthPtr);
   AdminMetaConSource (rqptr, mcptr, MetaGlobalAuthPtr);

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

   AdminVirtualServiceForm (rqptr, ADMIN_REPORT_AUTH_PATHS,
                            VirtualService, UseServerDatabase);

   status = FaolToNet (rqptr, BeginTable, NULL);
   if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, 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] &&
             !StringMatch (rqptr, VirtualService, mclptr->TextPtr))
            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 = FaolToNet (rqptr, EndPageFao, NULL);
   if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI);

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

/*****************************************************************************/
/*
Display all 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>!4ZL</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 [] =
"!4ZL !#* !&?\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  PathOpaqueFao [] = "<b>!4ZL</b> !#* !AZ\n";

   static char  WatchPathOpaqueFao [] = "!4ZL !#* !AZ\n";

   static char  RealmFao [] =
"<b>!4ZL</b> !#* !&?<strike>\r\r<b>\
[!&@!&;AZ!AZ!&@;!&;AZ!AZ;!&;AZ!AZ]</b>!&?</strike>\r\r\n";

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

   static char  CommentFao [] = "<b>!4ZL</b>  !#* !AZ\n";

   static char  NonRuleFao [] =
"<b>!4ZL</b> !#* !&?<strike>\r\r<b>!#AZ</b>  !&;AZ!&?</strike>\r\r\n";

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

   static char  RuleVirtualFao [] =
"<b>!4ZL</b> !#* !&?<strike>\r\r<b>[[!&;AZ]]</b>!&?</strike>\r\r\n";

   static char  RuleImplicitVirtualFao [] =
"<b>0000</b> <b>[[*:*]]</b>\n";

   static char  VersionFao [] = "<b>!4ZL  [[!AZ]]</b>\n";

   static char  WatchRuleVirtualFao [] =
"!4ZL !#* !&?\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 (WATCHMOD (rqptr, WATCH_MOD_AUTH))
      WatchThis (WATCHITM(rqptr), 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 = FaolToBuffer (Buffer, SizeOfBuffer, &Length,
                             WatchRule ? WatchRuleVirtualFao : RuleVirtualFao,
                             &FaoVector);
      if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI);
      return ((int)Length);
   }
   else
   if (mclptr->Number == 1)
   {
      status = FaolToNet (rqptr, RuleImplicitVirtualFao, NULL);
      if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI);
   }

   if (mclptr->Token != METACON_TOKEN_TEXT)
   {
      vecptr = FaoVector;
      *vecptr++ = mclptr->Number;

      if (mclptr->Token == METACON_TOKEN_COMMENT)
      {
         *vecptr++ = (mclptr->MetaFileLevel+
                      mclptr->FlowControlLevel+1) * 3;
         *vecptr++ = mclptr->TextPtr;
         status = FaolToBuffer (Buffer, SizeOfBuffer, &Length,
                                CommentFao, &FaoVector);
         if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI);
         return ((int)Length);
      }

      if (mclptr->Token == METACON_TOKEN_VERSION)
      {
         *vecptr++ = mclptr->TextPtr;
         status = FaolToBuffer (Buffer, SizeOfBuffer, &Length,
                                VersionFao, &FaoVector);
         if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI);
         return ((int)Length);
      }

      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 = FaolToBuffer (Buffer, SizeOfBuffer, &Length,
                             WatchRule ? WatchNonRuleFao : NonRuleFao,
                             &FaoVector);
      if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, 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 */
      /**********/

      if (apptr->SourceRealm == AUTH_SOURCE_OPAQUE)
      {
         vecptr = FaoVector;
         *vecptr++ = mclptr->Number;
         *vecptr++ = (mclptr->MetaFileLevel+mclptr->FlowControlLevel) * 3 + 3;
         *vecptr++ = apptr->PathPtr;

         status = FaolToBuffer (Buffer, SizeOfBuffer, &Length,
                                WatchRule ? WatchPathOpaqueFao : PathOpaqueFao,
                                &FaoVector);
         if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI);
         return ((int)Length);
      }

      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++ = WatchRule ? "!#* PARAM=\'!AZ\'\n" :
                                 "!#* PARAM=\'!&;AZ\'\n";
         *vecptr++ = (mclptr->MetaFileLevel+
                      mclptr->FlowControlLevel) * 3 + 8;
         *vecptr++ = apptr->PathParameterPtr;
      }
      else
         *vecptr++ = "";

      *vecptr++ = apptr->PathProblem;

      status = FaolToBuffer (Buffer, SizeOfBuffer, &Length,
                             WatchRule ? WatchPathFao : PathFao, &FaoVector);
      if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, 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);
      if (arptr->RealmParamPtr && arptr->RealmParamPtr[0])
      {
         *vecptr++ = "+&quot;!&;AZ&quot;";
         *vecptr++ = arptr->RealmParamPtr;
      }
      else
         *vecptr++ = "";
      *vecptr++ = arptr->GroupWritePtr;
      *vecptr++ = AuthSourceString (arptr->GroupWritePtr,
                                    arptr->SourceGroupWrite);
      *vecptr++ = arptr->GroupReadPtr;
      *vecptr++ = AuthSourceString (arptr->GroupReadPtr,
                                    arptr->SourceGroupRead);
      *vecptr++ = arptr->RealmProblem;

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

   return (0);
}

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

