/*****************************************************************************/
/*
                                ProxyVerify.c

(Draw in a deep breath, and ...) this module implements a relatively simple,
pragmatic mechanism that allows a proxy server to authorize a request locally,
then convey that authorized username to a reverse-proxied server using a
standard HTTP "Authorization: basic ..." request field, while keeping the
original password private, and providing an HTTP-based mechanism for the
proxied-to server to verify that the request is indeed originating from the
proxy server.  It has been specifically developed to support a site (UMA)
with these requirements.  It may-or-may-not find more general usage!

As there is no way to predetermine which process will receive a verification
request when using multiple server instances the records must be capable of
being stored in a global section shared memory.  For a single instance the same
data structures are supported in local memory.


FUNCTIONAL DESCRIPTION
----------------------
1) The proxy server subjects the reverse-proxy path to authorization.  The
client is required to provide credentials with the request.  These credentials
are assessed by the proxy server and the request allowed to procede or rejected
and the credentials rerequested.

2) The username from the credentials is used to build a new, base-64 encoded,
'basic' authorization string (<username>:<password>) for use in the request
header sent to the proxied-to server.  The password component is derived from
an MD5 hash of the username, a binary counter and binary time components.  It
is UNIQUE FOR EVERY REQUEST and is OPAQUE TO THE PROXIED-TO SERVER.  The
original password is not transmitted.  This base-64 encoded authorization
string is stored by the proxy server as a record for later verification.   If
for some reason it cannot be generated or stored (e.g. records all in use) the
calling routine should abort the request and report an error (by checking
whether 'tkptr->VerifyRecordPtr' has remained NULL).

3) The proxied-to server receives the request and decodes the "Authorization:
basic ..." field sent with it, if required.  From that the original username
can be parsed.  If the proxied-to server wishes to verify that request is what
it purports to be - an already authorized, proxied request - it can request the
proxy server to do that by sending a standard HTTP GET request containing the
path prefix '/httpd/-/verify/' followed by the base-64 encoded string supplied
with the "Authorization: basic ..." field.

  NOTE: the verification string contains the username as well as a hash
        derived in part from using that username.  The username could not be
        substituted without changing the encoded string.  Using the entire
        authorization string verifies the username as well as the request.

4) The proxy server receives a request to verify the included authorization
string.  It searches through the verification records looking for one
containing that string.  If it finds it and that request has not been
previously verified returns a 200 (success) response.  If it cannot find it a
404 (not found) response.  If has been previously verified a 403 (forbidden)
response.

5) The proxied-to server assesses the response and continues or aborts the
request processing.

6) As the original request is rundown on the proxy server it clears the
corresponding verification record.


CONFIGURATION
-------------
The facility is enabled by setting WASD_CONFIG_GLOBAL [ProxyVerifyRecordMax]
to be non-zero.

Specific paths must then be SET in WASD_CONFIG_MAP to have reverse-proxy
verification performed.

  pass /httpd/-/verify/*
  redirect /proxied-to-server/* /http://proxied.to.server/*
  set http://proxied.to.server/* proxy=reverse=verify
  pass http://proxied.to.server/*

Of course the reverse-proxy path must also be appropriately authorized in
WASD_CONFIG_AUTH.

  ["realm description"=realm=type]
  http://proxied.to.server/* r+w


DEVELOPMENT
-----------
For development purposes the proxied-to server can make a request

  /httpd/-/verify/<3-digit-code>

and receive the response appropriate to that HTTP status code without there
being an actual request under verification.  That is, using
"/httpd/-/verify/200" returns a success response, "/httpd/-/verify/404" a not
found response, "/httpd/-/verify/501" a disabled response, etc.


VERSION HISTORY
---------------
22-MAY-2007  JPP  bugfix; ProxyVerifyGblSecInit()
20-NOV-2003  MGD  initial
*/
/*****************************************************************************/

#ifdef WASD_VMS_V7
#undef _VMS__V6__SOURCE
#define _VMS__V6__SOURCE
#undef __VMS_VER
#define __VMS_VER 70000000
#undef __CRTL_VER
#define __CRTL_VER 70000000
#endif

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

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

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

#define WASD_MODULE "PROXYVERIFY"

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

int  ProxyVerifyCurrentCount,
     ProxyVerifyGblSecPages,
     ProxyVerifyGblSecSize,
     ProxyVerifyRecordMax,
     ProxyVerifyRecordSize = sizeof(PROXYVERIFY_RECORD);

PROXYVERIFY_GBLSEC  *ProxyVerifyGblSecPtr;

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

extern BOOL  HttpdServerStartup;

extern int  GblPageCount,
            GblSectionCount,
            InstanceNodeConfig,
            InstanceEnvNumber,
            OpcomMessages,
            ProxyVerifyGblSecVersion;

extern unsigned long  GblSecPrvMask[];

extern CONFIG_STRUCT  Config;
extern HTTPD_PROCESS  HttpdProcess;
extern PROXY_ACCOUNTING_STRUCT  *ProxyAccountingPtr;
extern SYS_INFO  SysInfo;
extern WATCH_STRUCT  Watch;

/*****************************************************************************/
/*
If called during startup initialize (if necessary) the global section and then
ensure that any uncleared records for this instance remaining from any
non-clean previous shutdown are cleared.  If during a clean shutdown just clear
any remaining used records.
*/

ProxyVerifyInit ()

{
   int  idx,
        RecordCount;
   PROXYVERIFY_RECORD  *pvrptr,
                       *RecordPoolPtr;

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

   if (WATCH_MODULE(WATCH_MOD_PROXY))
      WatchThis (WATCHALL, WATCH_MOD_PROXY, "ProxyVerifyInit()");

   if (HttpdServerStartup)
   {
      ProxyVerifyRecordMax = Config.cfProxy.VerifyRecordMax;
      if (!ProxyVerifyRecordMax) return;
      if (ProxyVerifyRecordMax < PROXY_VERIFY_DEFAULT_RECORD_MAX)
         ProxyVerifyRecordMax = PROXY_VERIFY_DEFAULT_RECORD_MAX;
      ProxyVerifyGblSecInit ();
   }

   if (!ProxyVerifyGblSecPtr) return;

   InstanceMutexLock (INSTANCE_MUTEX_PROXY_VERIFY);

   RecordCount = ProxyVerifyGblSecPtr->RecordCount;
   RecordPoolPtr = ProxyVerifyGblSecPtr->RecordPool;

   for (idx = 0; idx < RecordCount; idx++)
   {
      pvrptr = &RecordPoolPtr[idx];
      if (!pvrptr->AuthorizationStringLength) continue;
      if (strcmp (pvrptr->HttpdPrcNam, HttpdProcess.PrcNam)) continue;
      memset (pvrptr, 0, sizeof(PROXYVERIFY_RECORD));
   }
   
   InstanceMutexUnLock (INSTANCE_MUTEX_PROXY_VERIFY);
} 

/*****************************************************************************/
/*
Create a base-64 encoded string containing the original, authenticated remote
username, along with an MD5 hash of a concatenation of that username, and a
binary counter and time compononents.  This base-64 encoded string is in a
format suitable for use as 'basic' HTTP authorization.  The PROXY.C module will
check if the path is set for proxy verify, and that 'tkptr->VerifyRecordPtr' in
non-NULL and rebuilds an "Authorization: basic ..." field using it if it does.
*/

ProxyVerifyRecordSet (PROXY_TASK *tkptr)

{
/* do NOT make this greater than 31! */
#define PROXY_VERIFY_PWD_LENGTH 31

   static unsigned long  VerifyTicker;

   int  idx,
        RecordCount,
        UserNamePlusLength,
        UserNameLength;
   unsigned long  Time64[2];
   char  *cptr, *sptr, *zptr;
   char  UserNamePlus [AUTH_MAX_USERNAME_LENGTH+12],
         UserNamePwd [AUTH_MAX_USERNAME_LENGTH+32+1],
         UserNamePwdBase64 [PROXY_VERIFY_MAX_AUTH_LENGTH+1];
   REQUEST_STRUCT  *rqptr;
   PROXYVERIFY_RECORD  *pvrptr,
                       *RecordPoolPtr;

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

   if (WATCHMOD (tkptr, WATCH_MOD_PROXY))
      WatchThis (WATCHITM(tkptr), WATCH_MOD_PROXY,
                 "ProxyVerifyRecordSet() !&Z",
                 tkptr->RequestPtr ? tkptr->RequestPtr->RemoteUser : "");

   if (!ProxyVerifyGblSecPtr) return;

   if (!(rqptr = tkptr->RequestPtr)) return;

   /* only generate such if the request has actually been authenticated */
   if (VMSnok(rqptr->rqAuth.FinalStatus) || !rqptr->RemoteUser[0]) return;

   /* 'HttpdTime64' has a granularity of only one second, use this one */
   sys$gettim (&Time64);

   /* first use is set to something less determinate */
   if (!VerifyTicker) VerifyTicker = Time64[0];
   VerifyTicker += 11;

   zptr = (sptr = UserNamePlus) + AUTH_MAX_USERNAME_LENGTH;
   for (cptr = rqptr->RemoteUser;
        *cptr && sptr < zptr;
        *sptr++ = *cptr++);
   SET4(sptr,VerifyTicker);
   sptr += 4;
   SET4(sptr,Time64[0]);
   sptr += 4;
   SET4(sptr,Time64[1]);
   sptr += 4;
   UserNamePlusLength = sptr - UserNamePlus;

   zptr = (sptr = UserNamePwd) + AUTH_MAX_USERNAME_LENGTH;
   for (cptr = rqptr->RemoteUser;
        *cptr && sptr < zptr;
        *sptr++ = *cptr++);
   UserNameLength = sptr - UserNamePwd;
   *sptr++ = ':';

   Md5HexString (UserNamePlus, UserNamePlusLength,
                 UserNamePwd+UserNameLength+1);
   /* trim the MD5 hex digest back to it's maximum length (31) */
   UserNamePwd[UserNameLength+1+PROXY_VERIFY_PWD_LENGTH] = '\0';

   BasicPrintableEncode (UserNamePwd,
                         UserNamePwdBase64,
                         sizeof(UserNamePwdBase64));

   if (WATCHMOD (rqptr, WATCH_MOD_PROXY))
      WatchDataFormatted ("!&Z !&Z\n", UserNamePwd, UserNamePwdBase64);

   /* search for a free record to store this in */
   InstanceMutexLock (INSTANCE_MUTEX_PROXY_VERIFY);

   RecordCount = ProxyVerifyGblSecPtr->RecordCount;
   RecordPoolPtr = ProxyVerifyGblSecPtr->RecordPool;

   for (idx = 0; idx < RecordCount; idx++)
   {
      pvrptr = &RecordPoolPtr[idx];
      if (pvrptr->AuthorizationStringLength) continue;
      break;
   }
   
   if (idx < ProxyVerifyRecordMax)
   {
      if (idx >= RecordCount)
         pvrptr = &RecordPoolPtr[ProxyVerifyGblSecPtr->RecordCount++];

      pvrptr->AuthorizationStringLength = strlen(UserNamePwdBase64);
      strcpy (pvrptr->AuthorizationString, UserNamePwdBase64);
      strcpy (pvrptr->HttpdPrcNam, HttpdProcess.PrcNam);
      strcpy (pvrptr->RemoteUser, rqptr->RemoteUser);
      strcpy (pvrptr->RealmName, rqptr->rqAuth.RealmPtr);
      pvrptr->SourceRealm = rqptr->rqAuth.SourceRealm;

      /* note the record in the task structure */
      tkptr->VerifyRecordPtr = pvrptr;
   }

   InstanceMutexUnLock (INSTANCE_MUTEX_PROXY_VERIFY);

   InstanceMutexLock (INSTANCE_MUTEX_HTTPD);
   ProxyAccountingPtr->VerifySetRecordCount++;
   if (idx < ProxyVerifyRecordMax)
      ProxyAccountingPtr->VerifyCurrentCount++;
   else
      ProxyAccountingPtr->VerifyFullCount++;
   InstanceMutexUnLock (INSTANCE_MUTEX_HTTPD);

   if (idx < ProxyVerifyRecordMax) return;

   ErrorNoticed (rqptr, 0, "record space exhausted", FI_LI);
} 

/*****************************************************************************/
/*
Clear the pointed-to record as unused.
Called by ProxyEnd() as part of a proxied request rundown.
*/

ProxyVerifyRecordReset (PROXY_TASK *tkptr)

{
   /*********/
   /* begin */
   /*********/

   if (WATCHMOD (tkptr, WATCH_MOD_PROXY))
      WatchThis (WATCHITM(tkptr), WATCH_MOD_PROXY,
                 "ProxyVerifyRecordReset() !&Z",
                 tkptr->RequestPtr ? tkptr->RequestPtr->RemoteUser : "");

   InstanceMutexLock (INSTANCE_MUTEX_PROXY_VERIFY);
   memset (tkptr->VerifyRecordPtr, 0, sizeof(PROXYVERIFY_RECORD));
   InstanceMutexUnLock (INSTANCE_MUTEX_PROXY_VERIFY);

   InstanceMutexLock (INSTANCE_MUTEX_HTTPD);
   if (ProxyAccountingPtr->VerifyCurrentCount)
      ProxyAccountingPtr->VerifyCurrentCount--;
   InstanceMutexUnLock (INSTANCE_MUTEX_HTTPD);

   tkptr->VerifyRecordPtr = NULL;
} 

/*****************************************************************************/
/*
This function performs the '/httpd/-/verify/' functionality.  The client
appends the "Authorization: basic ..." base-64 encoded string to the above path
and makes a GET request using it.  The base-64 encoded string is extracted from
the path then the array of records is searched for one containing this string. 
An appropriate HTTP response is generated and returned to the client.
*/

ProxyVerifyRequest
(
REQUEST_STRUCT *rqptr,
REQUEST_AST NextTaskFunction
)
{
   int  idx,
        RecordCount,
        StatusCode,
        StringLength,
        VerifyAttemptCount;
   unsigned long  First4;
   char  *cptr, *sptr;
   char  UserNamePwd [AUTH_MAX_USERNAME_LENGTH+1+32+1];
   PROXYVERIFY_RECORD  *pvrptr,
                       *RecordPoolPtr;

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

   if (WATCHMOD (rqptr, WATCH_MOD_PROXY))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_PROXY, "ProxyVerifyRequest()");

   if (!ProxyVerifyGblSecPtr)
   {
      /* global section not initialized, facility not available! */
      ResponseHeader (rqptr, 501, "text/plain", 14, NULL, NULL);
      NetWrite (rqptr, NextTaskFunction, "501 disabled!\n", 14);
      return;
   }

   /* do not cache any of these responses */
   rqptr->rqResponse.PreExpired = true;

   cptr = rqptr->rqHeader.PathInfoPtr + sizeof(HTTPD_VERIFY)-1;
   StringLength = strlen(cptr);

   if (StringLength == 3)
   {
      /**************************/
      /* for developer purposes */
      /**************************/

      StatusCode = atoi(cptr);
      switch (StatusCode)
      {
         case 200 :
            ResponseHeader (rqptr, 200, "text/plain", 7, NULL, NULL);
            NetWrite (rqptr, NextTaskFunction, "200 ok\n", 7);
            return;
         case 403 :
            ResponseHeader (rqptr, 403, "text/plain", 14, NULL, NULL);
            NetWrite (rqptr, NextTaskFunction, "403 forbidden\n", 14);
            return;
         case 404 :
            ResponseHeader (rqptr, 404, "text/plain", 12, NULL, NULL);
            NetWrite (rqptr, NextTaskFunction, "404 unknown\n", 12);
            return;
         case 501 :
            ResponseHeader (rqptr, 501, "text/plain", 14, NULL, NULL);
            NetWrite (rqptr, NextTaskFunction, "501 disabled!\n", 14);
            return;
         default :
            rqptr->rqResponse.HttpStatus = StatusCode;
            ErrorGeneral (rqptr, rqptr->rqHeader.PathInfoPtr, FI_LI);
            SysDclAst (NextTaskFunction, rqptr);
            return;
      }
   }

   First4 = *(ULONGPTR)cptr;

   InstanceMutexLock (INSTANCE_MUTEX_PROXY_VERIFY);

   RecordCount = ProxyVerifyGblSecPtr->RecordCount;
   RecordPoolPtr = ProxyVerifyGblSecPtr->RecordPool;

   for (idx = 0; idx < RecordCount; idx++)
   {
      pvrptr = &RecordPoolPtr[idx];

      if (!pvrptr->AuthorizationStringLength) continue;
      if (pvrptr->AuthorizationStringLength != StringLength) continue;
      if (*(ULONGPTR)pvrptr->AuthorizationString != First4) continue;
      if (strcmp (pvrptr->AuthorizationString, cptr)) continue;

      /*********/
      /* found */
      /*********/

      if (WATCHPNT(rqptr) && (WATCH_CATEGORY(WATCH_AUTH) ||
                              WATCH_CATEGORY(WATCH_PROXY)))
         WatchThis (WATCHITM(rqptr),
            WATCH_CATEGORY(WATCH_AUTH) ? WATCH_AUTH : WATCH_PROXY,
            "VERIFY instance:!AZ user:!AZ realm:!AZ!AZ",
            pvrptr->HttpdPrcNam, pvrptr->RemoteUser, pvrptr->RealmName,
            AuthSourceString (pvrptr->RealmName, pvrptr->SourceRealm));

      if (!pvrptr->VerifyAttemptCount++)
      {
         /***************************/
         /* successful verification */
         /***************************/

         InstanceMutexUnLock (INSTANCE_MUTEX_PROXY_VERIFY);

         InstanceMutexLock (INSTANCE_MUTEX_HTTPD);
         ProxyAccountingPtr->VerifyFindRecordCount++;
         ProxyAccountingPtr->Verify200Count++;
         InstanceMutexUnLock (INSTANCE_MUTEX_HTTPD);

         ResponseHeader (rqptr, 200, "text/plain", 7, NULL, NULL);
         NetWrite (rqptr, NextTaskFunction, "200 ok\n", 7);
         return;
      }

      /********************************/
      /* uh-oh ... multiple attempts! */
      /********************************/

      VerifyAttemptCount = pvrptr->VerifyAttemptCount;

      InstanceMutexUnLock (INSTANCE_MUTEX_PROXY_VERIFY);

      InstanceMutexLock (INSTANCE_MUTEX_HTTPD);
      ProxyAccountingPtr->VerifyFindRecordCount++;
      ProxyAccountingPtr->Verify403Count++;
      InstanceMutexUnLock (INSTANCE_MUTEX_HTTPD);

      BasicPrintableDecode (cptr, UserNamePwd, sizeof(UserNamePwd));
      for (sptr = UserNamePwd; *sptr && *sptr != ':'; sptr++);
      *sptr = '\0';

      FaoToStdout ("%HTTPD-W-PROXYVERIFY, !20%D, !AZ (!AZ) !AZ !UL attempts\n",
                   0, cptr, UserNamePwd,
                   rqptr->ClientPtr->Lookup.HostName, VerifyAttemptCount);

      if (OpcomMessages & OPCOM_AUTHORIZATION)
         FaoToOpcom ("%HTTPD-W-PROXYVERIFY, !AZ (!AZ) !AZ !UL attempts",
                     cptr, UserNamePwd,
                     rqptr->ClientPtr->Lookup.HostName, VerifyAttemptCount);

      ResponseHeader (rqptr, 403, "text/plain", 14, NULL, NULL);
      NetWrite (rqptr, NextTaskFunction, "403 forbidden\n", 14);
      return;
   }

   /*************/
   /* not found */
   /*************/

   InstanceMutexUnLock (INSTANCE_MUTEX_PROXY_VERIFY);

   InstanceMutexLock (INSTANCE_MUTEX_HTTPD);
   ProxyAccountingPtr->VerifyFindRecordCount++;
   ProxyAccountingPtr->Verify404Count++;
   InstanceMutexUnLock (INSTANCE_MUTEX_HTTPD);

   BasicPrintableDecode (cptr, UserNamePwd, sizeof(UserNamePwd));
   for (sptr = UserNamePwd; *sptr && *sptr != ':'; sptr++);
   *sptr = '\0';

   FaoToStdout ("%HTTPD-W-PROXYVERIFY, !20%D, !AZ (!AZ) !AZ unknown\n",
                0, cptr, UserNamePwd, rqptr->ClientPtr->Lookup.HostName);

   if (OpcomMessages & OPCOM_AUTHORIZATION)
      FaoToOpcom ("%HTTPD-W-PROXYVERIFY, !AZ (!AZ) !AZ unknown",
                  cptr, UserNamePwd, rqptr->ClientPtr->Lookup.HostName);

   ResponseHeader (rqptr, 404, "text/plain", 12, NULL, NULL);
   NetWrite (rqptr, NextTaskFunction, "404 unknown\n", 12);
   return;
} 

/*****************************************************************************/
/*
If only one instance can execute (from configuration) then allocate a block of
process-local dynamic memory and point to that as the cache.  If multiple
instances create and map a global section and point to that.
*/ 

ProxyVerifyGblSecInit ()

{
   static char  GblSecReport [] =
"%HTTPD-I-PROXYVERIFY, for !UL records in !AZ of !UL page(let)s\n";

   /* global, allocate space, system, in page file, writable */
   static int CreFlags = SEC$M_GBL | SEC$M_EXPREG | SEC$M_SYSGBL |
                         SEC$M_PAGFIL | SEC$M_WRT;
   static int DelFlags = SEC$M_SYSGBL;
   /* system & owner full access, group and world no access */
   static unsigned long  ProtectionMask = 0xff00;
   /* it is recommended to map into any virtual address in the region (P0) */
   static unsigned long  InAddr [2] = { 0x200, 0x200 };

   int  attempt, status,
        BaseGblSecPages,
        VerifyRecordPoolSize,
        PageCount,
        SetPrvStatus;
   short  ShortLength;
   unsigned long  RetAddr [2];
   char  GblSecName [32];
   $DESCRIPTOR (GblSecNameDsc, GblSecName);
   PROXYVERIFY_GBLSEC  *gsptr;

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

   if (WATCH_MODULE(WATCH_MOD_PROXY))
      WatchThis (WATCHALL, WATCH_MOD_PROXY, "ProxyVerifyGblSecInit()");

   VerifyRecordPoolSize = ProxyVerifyRecordSize * ProxyVerifyRecordMax;
   ProxyVerifyGblSecSize = sizeof(PROXYVERIFY_GBLSEC) + VerifyRecordPoolSize;
   ProxyVerifyGblSecPages = ProxyVerifyGblSecSize / 512;
   if (ProxyVerifyGblSecSize & 0x1ff) ProxyVerifyGblSecPages++;

   if (InstanceNodeConfig <= 1)
   {
      /* no need for a global section, just use process-local storage */
      ProxyVerifyGblSecPtr =
         (PROXYVERIFY_GBLSEC*)VmGet (ProxyVerifyGblSecPages * 512);
      sys$gettim (&ProxyVerifyGblSecPtr->SinceTime64);
      FaoToStdout (GblSecReport, ProxyVerifyRecordMax,
                   "local storage", ProxyVerifyGblSecPages);
      return (SS$_CREATED);
   }

   FaoToBuffer (GblSecName, sizeof(GblSecName), &ShortLength,
                GBLSEC_NAME_FAO, HTTPD_NAME, PROXYVERIFY_GBLSEC_VERSION_NUMBER,
                InstanceEnvNumber, "PROXYVERIFY");
   GblSecNameDsc.dsc$w_length = ShortLength;

   if VMSnok ((SetPrvStatus = sys$setprv (1, &GblSecPrvMask, 0, 0)))
      ErrorExitVmsStatus (SetPrvStatus, "sys$setprv()", FI_LI);

   for (attempt = 1; attempt <= 2; attempt++)
   {
      /* create and/or map the specified global section */
      sys$setprv (1, &GblSecPrvMask, 0, 0);
      status = sys$crmpsc (&InAddr, &RetAddr, 0, CreFlags,
                           &GblSecNameDsc, 0, 0, 0, ProxyVerifyGblSecPages, 0,
                           ProtectionMask, ProxyVerifyGblSecPages);
      sys$setprv (0, &GblSecPrvMask, 0, 0);

      if (WATCH_MODULE(WATCH_MOD_PROXY))
         WatchThis (WATCHALL, WATCH_MOD_PROXY,
                    "sys$crmpsc() !&S begin:!UL end:!UL",
                    status, RetAddr[0], RetAddr[1]);

      PageCount = (RetAddr[1]+1) - RetAddr[0] >> 9;
      ProxyVerifyGblSecPtr = gsptr = (PROXYVERIFY_GBLSEC*)RetAddr[0];
      ProxyVerifyGblSecPages = PageCount;
      if (VMSnok (status) || status == SS$_CREATED) break;

      /* section already exists, break if 'same size' and version! */
      if (gsptr->GblSecVersion &&
          gsptr->GblSecVersion == ProxyVerifyGblSecVersion &&
          gsptr->GblSecLength == ProxyVerifyGblSecSize)
         break;

      /* delete the current global section, have one more attempt */
      sys$setprv (1, &GblSecPrvMask, 0, 0);
      status = sys$dgblsc (DelFlags, &GblSecNameDsc, 0);
      sys$setprv (0, &GblSecPrvMask, 0, 0);
      status = SS$_IDMISMATCH;
   }

   if (VMSnok (status))
   {
      /* must have this global section! */
      char  String [256];
      FaoToBuffer (String, sizeof(String), NULL,
                   "1 global section, !UL global pages",
                   ProxyVerifyGblSecPages);
      ErrorExitVmsStatus (status, String, FI_LI);
   }

   if (WATCH_MODULE(WATCH_MOD_PROXY))
      WatchThis (WATCHALL, WATCH_MOD_PROXY,
         "GBLSEC \"!AZ\" page(let)s:!UL !&S",
         GblSecName, PageCount, status);

   FaoToStdout (GblSecReport, ProxyVerifyRecordMax,
                status == SS$_CREATED ? "a new global section" :
                                        "an existing global section",
                ProxyVerifyGblSecPages);

   if (status == SS$_CREATED)
   {
      /* first time it's been mapped */
      memset (gsptr, 0, PageCount * 512);
      gsptr->GblSecVersion = ProxyVerifyGblSecVersion;
      gsptr->GblSecLength =  ProxyVerifyGblSecSize;
      sys$gettim (&ProxyVerifyGblSecPtr->SinceTime64);
   }

   GblSectionCount++;
   GblPageCount += PageCount;

   return (status);
}

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

