/*****************************************************************************/
/*
                               AuthCache.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.


INSTANCE SUPPORT
----------------
Note that within the global section no self-relative addresses can be used
(i.e. a linked list cannot be used).  All references into the section must be
made relative to the starting address.  This is due to the starting address not
being fixed in per-process virtual memory.

With the advent of multiple integrated "instances" executing on a single system
and sharing incoming service requests it became highly desirable to provide a
single global instance of the especially significant authentication cache
resource.

This has been done by making it work within the confines of globally shared
memory (i.e. a global section and collection of global pages).  This
necessarily puts some constraints on the design and operation.  Most notably
normal memory allocation routines are impossible and the quantity of memory
available for the required structures becomes constrained.  Hence a fixed (but
configurable) sized authentication cache, with records being reused as
required.

A typical message when there are too few cache entries for the concurrent
number of requests requiring authorization is SS$_ITEMNOTFOUND (or
"%SYSTEM-W-ITEMNOTFOUND, requested item cannot be returned").  This indicates
the authorization cache is "thrashing" for space (i.e. too many concurrent
requests attempting to use it).  Increase the value of [AuthCacheEntriesMax],
shut down all servers (to cause the global section to be deleted) and then
restart.

Typical message for insufficient storage space for an authorization cache
entry id SS$_RESULTOVF or "%SYSTEM-F-RESULTOVF, resultant string overflow". 
This might occur if /PROFILE was in use and an account generated a particularly
large one!  Increase the value of [AuthCacheEntrySize], shut down all servers
(to cause the global section to be deleted) and then restart.

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


VERSION HISTORY
---------------
04-JAN-2015  MGD  bugfix; AuthCacheNeedsReval() AlreadyLocked (per JPP)
07-NOV-2013  MGD  AuthCacheNeedsReval() so multiple cache entries for the
                    same credentials do not trigger multiple revalidations
25-MAY-2013  MGD  [AuthRevalidateLoginCookie] obsolete
09-SEP-2012  MGD  TOKEN authentication
18-JUL-2010  MGD  Uni Malaga pushes report items from !3ZL to !4ZL :-)
22-MAY-2007  JPP  bugfix; AuthCacheGblSecInit()
16-MAY-2007  MGD  AuthCacheReport() include authorization accounting
15-MAR-2007  MGD  bugfix; agent mappings using VMS-USER: not being cached
08-SEP-2003  MGD  allow for config-directory in cache
06-AUG-2003  MGD  sanity checking for locked cache
                  bugfix; AuthCacheAddRecord()
04-FEB-2003  MGD  AuthCacheReset() minor refinements
06-AUG-2002  MGD  enhance global section creation,
                  bugfix; set AuthCacheRecordSize from HTTPD$CONFIG value
02-MAR-2002  MGD  bugfix; read group cache offset assignment
20-OCT-2001  MGD  significant cache rework (for instance sharing)
04-AUG-2001  MGD  support module WATCHing
07-MAY-2000  MGD  login cookie
08-APR-2000  MGD  bugfix; AuthCacheFree()
04-MAR-2000  MGD  use FaolToNet(), et.al.
24-DEC-1999  MGD  break-in evasion period in report
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>

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

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

#define WASD_MODULE "AUTHCACHE"

#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 */
/******************/

int  AuthCacheRecordMax,
     AuthCacheRecordSize;

AUTH_GBLSEC  *AuthGblSecPtr;
int  AuthGblSecPages,
     AuthGblSecSize;

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

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

extern BOOL  AuthVmsUserProfileEnabled,
             InstanceNodeSupervisor;

extern BOOL  InstanceMutexHeld[];

extern int  AuthGblSecVersion,
            GblPageCount,
            GblSectionCount,
            HttpdTickSecond,
            InstanceNodeConfig,
            InstanceEnvNumber;

extern unsigned long  GblSecPrvMask [];

extern char  ErrorSanityCheck[];

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

/*****************************************************************************/
/*
Initialize the authentication cache.
*/ 

AuthCacheInit (META_CONFIG *mcptr)

{
   static char  ProblemRecordSize [] =
"Cache record size could be too small (adjusted upwards)";

   int  cnt, status;
   AUTH_CREC  *acrptr;

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

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

   if (AuthGblSecPtr)
   {
      /* cache has previously been initialized, just reset it */
      AuthCachePurge (true);
      return;
   }

   AuthCacheRecordMax = Config.cfAuth.CacheEntriesMax;
   AuthCacheRecordSize = Config.cfAuth.CacheEntrySize;

   if (AuthCacheRecordMax < AUTH_DEFAULT_CACHE_RECORD_MAX)
      AuthCacheRecordMax = AUTH_DEFAULT_CACHE_RECORD_MAX;

   if (!AuthCacheRecordSize)
      if (AuthVmsUserProfileEnabled)
         AuthCacheRecordSize = AUTH_DEFAULT_CACHE_RECORD_PROFILE_SIZE;
      else
         AuthCacheRecordSize = AUTH_DEFAULT_CACHE_RECORD_SIZE;
   /* let's round it to a 64 byte chunk */
   if (AuthCacheRecordSize % 64)
      AuthCacheRecordSize = ((AuthCacheRecordSize / 64) + 1) * 64;

   if (AuthVmsUserProfileEnabled &&
       AuthCacheRecordSize < AUTH_DEFAULT_CACHE_RECORD_PROFILE_SIZE)
   {
      MetaConReport (mcptr, METACON_REPORT_ERROR, ProblemRecordSize);
      AuthCacheRecordSize = AUTH_DEFAULT_CACHE_RECORD_PROFILE_SIZE;
   }
   else
   if (AuthCacheRecordSize < AUTH_DEFAULT_CACHE_RECORD_SIZE)
   {
      MetaConReport (mcptr, METACON_REPORT_ERROR, ProblemRecordSize);
      AuthCacheRecordSize = AUTH_DEFAULT_CACHE_RECORD_PROFILE_SIZE;
   }

   AuthCacheGblSecInit (mcptr);
}

/*****************************************************************************/
/*
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.
*/ 

int AuthCacheGblSecInit (META_CONFIG *mcptr)

{
   static char  ReportCacheRecords [] =
"Cache for !UL records of !UL bytes in !AZ of !UL page(let)s";

   /* 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,
        CacheRecordPoolSize,
        PageCount,
        SetPrvStatus;
   short  ShortLength;
   unsigned long  RetAddr [2];
   char  GblSecName [32];
   $DESCRIPTOR (GblSecNameDsc, GblSecName);
   AUTH_GBLSEC  *gsptr;

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

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

   CacheRecordPoolSize = AuthCacheRecordSize * AuthCacheRecordMax;
   AuthGblSecSize = sizeof(AUTH_GBLSEC) + CacheRecordPoolSize;
   AuthGblSecPages = AuthGblSecSize / 512;
   if (AuthGblSecSize & 0x1ff) AuthGblSecPages++;

   if (InstanceNodeConfig <= 1)
   {
      /* no need for a global section, just use process-local storage */
      AuthGblSecPtr = (AUTH_GBLSEC*)VmGet (AuthGblSecPages * 512);
      sys$gettim (&AuthGblSecPtr->SinceTime64);
      MetaConReport (mcptr, METACON_REPORT_INFORM, ReportCacheRecords,
                     AuthCacheRecordMax, AuthCacheRecordSize,
                     "local storage", AuthGblSecPages);
      return (SS$_CREATED);
   }

   FaoToBuffer (GblSecName, sizeof(GblSecName), &ShortLength,
                GBLSEC_NAME_FAO, HTTPD_NAME, AUTH_GBLSEC_VERSION_NUMBER,
                InstanceEnvNumber, "AUTH");
   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, AuthGblSecPages, 0,
                           ProtectionMask, AuthGblSecPages);
      sys$setprv (0, &GblSecPrvMask, 0, 0);

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

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

      /* section already exists, break if 'same size' and version! */
      if (gsptr->GblSecVersion &&
          gsptr->GblSecVersion == AuthGblSecVersion &&
          gsptr->GblSecLength == AuthGblSecSize)
         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", AuthGblSecPages);
      ErrorExitVmsStatus (status, String, FI_LI);
   }

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

   MetaConReport (mcptr, METACON_REPORT_INFORM, ReportCacheRecords,
                  AuthCacheRecordMax, AuthCacheRecordSize,
                  status == SS$_CREATED ? "a new global section" :
                                          "an existing global section",
                  AuthGblSecPages);

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

   GblSectionCount++;
   GblPageCount += PageCount;

   return (status);
}

/*****************************************************************************/
/*
Create a request-local record of authorization information.
*/ 

void AuthCacheRequestRecord
(
REQUEST_STRUCT *rqptr,
AUTH_CREC **RecordPtrPtr
)
{
   char  *cptr, *sptr;
   AUTH_CREC  *acrptr;

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

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

   acrptr = (AUTH_CREC*)VmGetHeap (rqptr, AuthCacheRecordSize);
   *RecordPtrPtr = acrptr;

   PUT_QUAD_QUAD (rqptr->rqTime.BeginTime64, acrptr->LastAccessTime64);

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

   /* "fixed" record fields */

   strcpy (acrptr->Realm, rqptr->rqAuth.RealmPtr);
   acrptr->RealmLength = rqptr->rqAuth.RealmLength;

   acrptr->UserNameLength = rqptr->RemoteUserLength;
   strcpy (acrptr->UserName, rqptr->RemoteUser);
   strcpy (acrptr->Password, rqptr->RemoteUserPassword);

   /* "dynamic" record fields */
   cptr = sptr = (char*)acrptr->Storage;

   memcpy (acrptr->ConfigDirectory = cptr,
           rqptr->ConfigDirectory,
           rqptr->ConfigDirectoryLength + 1);
   acrptr->ConfigDirectoryLength = rqptr->ConfigDirectoryLength;
   acrptr->ConfigDirectoryOffset = cptr - sptr;
   cptr += acrptr->ConfigDirectoryLength + 1;

   memcpy (acrptr->PathParameter = cptr,
           rqptr->rqAuth.PathParameterPtr,
           rqptr->rqAuth.PathParameterLength + 1);
   acrptr->PathParameterLength = rqptr->rqAuth.PathParameterLength;
   acrptr->PathParameterOffset = cptr - sptr;
   cptr += acrptr->PathParameterLength + 1;

   acrptr->SourceGroupWrite = rqptr->rqAuth.SourceGroupWrite;
   memcpy (acrptr->GroupWrite = cptr,
           rqptr->rqAuth.GroupWritePtr,
           rqptr->rqAuth.GroupWriteLength + 1);
   acrptr->GroupWriteLength = rqptr->rqAuth.GroupWriteLength;
   acrptr->GroupWriteOffset = cptr - sptr;
   cptr += acrptr->GroupWriteLength + 1;

   acrptr->SourceGroupRead = rqptr->rqAuth.SourceGroupRead;
   memcpy (acrptr->GroupRead = cptr,
           rqptr->rqAuth.GroupReadPtr,
           rqptr->rqAuth.GroupReadLength + 1);
   acrptr->GroupReadLength = rqptr->rqAuth.GroupReadLength;
   acrptr->GroupReadOffset = cptr - sptr;
   cptr += acrptr->GroupReadLength + 1;
                     
   /* note where we can start storing user details, VMS profile, etc. */
   acrptr->StorageNextOffset = cptr - sptr;

   /* small sanity check */
   if (AuthCacheRecordSize - (cptr - (char*)acrptr) <= 0)
      ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI);
}

/*****************************************************************************/
/*
Add the supplied authorization information to the (global section) cache.
*/ 

int AuthCacheAddRecord
(
AUTH_CREC *aptr,
AUTH_CREC **RecordPtrPtr
)
{
   int  cnt,
        OldestSecond,
        RecordCount;
   char  *cptr;
   unsigned char  *RecordPoolPtr;
   AUTH_CREC  *acrptr,
              *acr2ptr;
   
   /*********/
   /* begin */
   /*********/

   if (WATCH_MODULE(WATCH_MOD_AUTH))
      WatchThis (WATCHALL, WATCH_MOD_AUTH,
                 "AuthCacheAddRecord() !UL", AuthGblSecPtr->CacheRecordCount);

   if (InstanceNodeConfig > 1 && !InstanceMutexHeld[INSTANCE_MUTEX_AUTH_CACHE])
      ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI);

   RecordCount = AuthGblSecPtr->CacheRecordCount;
   RecordPoolPtr = AuthGblSecPtr->CacheRecordPool;

   if (RecordCount < AuthCacheRecordMax)
   {
      /* fresh record */
      acrptr = RecordPoolPtr + (AuthCacheRecordSize * RecordCount);
      AuthGblSecPtr->CacheRecordCount++;
   }
   else
   {
      /* all entries in list in use, reuse the least recently accessed */
      OldestSecond = HttpdTickSecond + 999999;
      acrptr = acr2ptr = RecordPoolPtr;
      for (cnt = 0; cnt < RecordCount; cnt++)
      {
         acrptr = RecordPoolPtr + (AuthCacheRecordSize * cnt);
         if (acrptr->LastAccessTickSecond < OldestSecond)
         {                              
            acr2ptr = acrptr;
            OldestSecond = acrptr->LastAccessTickSecond;
         }
         if (acrptr->SourceRealm) continue;
         break;
      }
      AuthGblSecPtr->CacheReuseCount++;
      acrptr = acr2ptr;
      memset (acrptr, 0, AuthCacheRecordSize);
   }

   /* copy contents of source record */
   memcpy (acrptr, aptr, AuthCacheRecordSize);

   /* adjust variable length field pointers using the field offsets */
   cptr = (char*)acrptr->Storage;
   acrptr->ConfigDirectory = cptr + acrptr->ConfigDirectoryOffset;
   acrptr->PathParameter = cptr + acrptr->PathParameterOffset;
   acrptr->GroupWrite = cptr + acrptr->GroupWriteOffset;
   acrptr->GroupRead = cptr + acrptr->GroupReadOffset;
 
   sys$gettim (&acrptr->LastAccessTime64);
   acrptr->LastAccessTickSecond = HttpdTickSecond;
   acrptr->LastAccessMinutesAgo = acrptr->LastAccessSecondsAgo = 0;

   *RecordPtrPtr = acrptr;

   return (SS$_NORMAL);
}

/*****************************************************************************/
/*
Add additional details to the cached record.
*/ 

int AuthCacheAddRecordDetails
(
REQUEST_STRUCT *rqptr,
AUTH_CREC *acrptr
)
{
   int  StorageRemaining;
   char  *cptr, *sptr;

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

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

   if (InstanceNodeConfig > 1 && !InstanceMutexHeld[INSTANCE_MUTEX_AUTH_CACHE])
      ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI);

   acrptr->DetailsUpdated = true;

   sptr = (char*)acrptr->Storage;
   cptr = sptr + acrptr->StorageNextOffset;
   StorageRemaining = AuthCacheRecordSize - (cptr - (char*)acrptr);

   StorageRemaining -= rqptr->rqAuth.UserDetailsLength + 1;
   if (StorageRemaining <= 0)
   {
      ErrorNoticed (rqptr, SS$_RESULTOVF, NULL, FI_LI);
      return (SS$_RESULTOVF);
   }

   memcpy (acrptr->UserDetails = cptr,
           rqptr->rqAuth.UserDetailsPtr ? rqptr->rqAuth.UserDetailsPtr : "",
           rqptr->rqAuth.UserDetailsLength + 1);
   acrptr->UserDetailsLength = rqptr->rqAuth.UserDetailsLength;
   acrptr->UserDetailsOffset = acrptr->UserDetails - sptr;
   cptr += acrptr->UserDetailsLength + 1;
   
   StorageRemaining -= rqptr->rqAuth.VmsUserProfileLength;
   if (StorageRemaining <= 0)
   {
      ErrorNoticed (rqptr, SS$_RESULTOVF, NULL, FI_LI);
      return (SS$_RESULTOVF);
   }

   if (acrptr->VmsUserProfileLength = rqptr->rqAuth.VmsUserProfileLength)
   {
      memcpy (acrptr->VmsUserProfilePtr = cptr,
              rqptr->rqAuth.VmsUserProfilePtr,
              rqptr->rqAuth.VmsUserProfileLength);
      acrptr->VmsUserProfileLength = rqptr->rqAuth.VmsUserProfileLength;
      acrptr->VmsUserProfileOffset = acrptr->VmsUserProfilePtr - sptr;
      cptr += acrptr->VmsUserProfileLength;
   }

   /* note where we could if we needed to start storing more detail */
   acrptr->StorageNextOffset = cptr - sptr;

   return (SS$_NORMAL);
}

/*****************************************************************************/
/*
Search all records in the cache for those with the same realm and username.
Check any entries found for revalidation timeout.  If one is found that does
*not* require revalidation (i.e. has recently enough been revalidated) then
return false.  If none are found or all require revalidation return true.
*/ 

BOOL AuthCacheNeedsReval
(
REQUEST_STRUCT *rqptr,
AUTH_CREC *acr1ptr
)
{
   BOOL AlreadyLocked;
   int  cnt,
        HitCount,
        RecordCount;
   unsigned long  LastAccessMinutesAgo;
   unsigned char  *RecordPoolPtr;
   AUTH_CREC  *acr2ptr;
   
   /*********/
   /* begin */
   /*********/

   if (WATCHMOD (rqptr, WATCH_MOD_AUTH))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_AUTH,
                 "AuthCacheNeedsReval() !AZ !AZ !UL",
                 acr1ptr->Realm, acr1ptr->UserName,
                 rqptr->rqAuth.RevalidateTimeout);

   if (rqptr->AuthRevalidateCount) return (false);

   if (!(AlreadyLocked = InstanceMutexHeld[INSTANCE_MUTEX_AUTH_CACHE]))
      InstanceMutexLock (INSTANCE_MUTEX_AUTH_CACHE);

   RecordCount = AuthGblSecPtr->CacheRecordCount;
   RecordPoolPtr = AuthGblSecPtr->CacheRecordPool;

   for (cnt = HitCount = 0; cnt < RecordCount; cnt++)
   {
      acr2ptr = RecordPoolPtr + (AuthCacheRecordSize * cnt);
      if (!acr2ptr->SourceRealm) continue;

      if (WATCH_MODULE(WATCH_MOD_AUTH) && WATCH_MODULE(WATCH_MOD__DETAIL))
         WatchDataFormatted ("!UL !UL\n!&Z !&Z\n!&Z !&Z\n!&Z !&Z\n",
            acr1ptr->SourceRealm, acr2ptr->SourceRealm,
            acr1ptr->Realm, acr2ptr->Realm,
            acr1ptr->ConfigDirectory, acr2ptr->ConfigDirectory,
            acr1ptr->UserName, acr2ptr->UserName);

      /* compare the records */
      if (acr1ptr->SourceRealm != acr2ptr->SourceRealm) continue;
      if (acr1ptr->UserNameLength != acr2ptr->UserNameLength) continue;
      if (acr1ptr->RealmLength != acr2ptr->RealmLength) continue;
      if (acr1ptr->ConfigDirectoryLength != acr2ptr->ConfigDirectoryLength)
         continue;
      if (strcmp (acr1ptr->Realm, acr2ptr->Realm)) continue;
      if (strcmp (acr1ptr->UserName, acr2ptr->UserName)) continue;
      if (acr1ptr->ConfigDirectoryLength &&
          strcmp (acr1ptr->ConfigDirectory, acr2ptr->ConfigDirectory))
         continue;

      /* got this far, must have matched */
      LastAccessMinutesAgo = (HttpdTickSecond -
                              acr2ptr->LastAccessTickSecond) / 60;

      if (WATCHMOD (rqptr, WATCH_MOD_AUTH))
         WatchDataFormatted ("!UL < !UL !UL\n",
           LastAccessMinutesAgo, rqptr->rqAuth.RevalidateTimeout,
           LastAccessMinutesAgo < rqptr->rqAuth.RevalidateTimeout ?
              HitCount+1 : HitCount);

      /* there will always be one (itself) so we need a second */
      if (LastAccessMinutesAgo < rqptr->rqAuth.RevalidateTimeout)
         if (HitCount++) break;
   }

   if (!AlreadyLocked) InstanceMutexUnLock (INSTANCE_MUTEX_AUTH_CACHE);

   if (WATCHMOD (rqptr, WATCH_MOD_AUTH))
      WatchThis (WATCHALL, WATCH_MOD_AUTH,
                 "!&?TRUE\rFALSE\r", HitCount <= 1);

   return (HitCount <= 1);
}

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

int AuthCacheFindRecord
(
AUTH_CREC *acr1ptr,
AUTH_CREC **RecordPtrPtr
)
{
   int  cnt,
        MinutesAgo,
        RecordCount;
   unsigned char  *RecordPoolPtr;
   AUTH_CREC  *acr2ptr;
   
   /*********/
   /* begin */
   /*********/

   if (WATCH_MODULE(WATCH_MOD_AUTH))
      WatchThis (WATCHALL, WATCH_MOD_AUTH,
                 "AuthCacheFindRecord() !UL !UL",
                 AuthGblSecPtr->CacheRecordCount, acr1ptr->FindRecordCount);

   if (InstanceNodeConfig > 1 && !InstanceMutexHeld[INSTANCE_MUTEX_AUTH_CACHE])
      ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI);

   RecordCount = AuthGblSecPtr->CacheRecordCount;
   RecordPoolPtr = AuthGblSecPtr->CacheRecordPool;

   for (cnt = 0; cnt < RecordCount; cnt++)
   {
      acr2ptr = RecordPoolPtr + (AuthCacheRecordSize * cnt);
      if (!acr2ptr->SourceRealm) continue;

      if (WATCH_MODULE(WATCH_MOD_AUTH) && WATCH_MODULE(WATCH_MOD__DETAIL))
         WatchDataFormatted (
"!UL !UL\n!UL !UL\n!UL !UL\n!&Z !&Z\n!&Z !&Z\n\
!&Z !&Z\n!&Z !&Z\n!&Z !&Z\n!&Z !&Z\n!UL !UL !&B\n",
            acr1ptr->SourceRealm, acr2ptr->SourceRealm,
            acr1ptr->SourceGroupWrite, acr2ptr->SourceGroupWrite,
            acr1ptr->SourceGroupRead, acr2ptr->SourceGroupRead,
            acr1ptr->Realm, acr2ptr->Realm,
            acr1ptr->ConfigDirectory, acr2ptr->ConfigDirectory,
            acr1ptr->PathParameter, acr2ptr->PathParameter,
            acr1ptr->GroupWrite, acr2ptr->GroupWrite,
            acr1ptr->GroupRead, acr2ptr->GroupRead,
            acr1ptr->UserName, acr2ptr->UserName,
            strlen(acr1ptr->Password), strlen(acr2ptr->Password),
            !strcmp(acr1ptr->Password, acr2ptr->Password));

      /* compare the records */
      if (acr1ptr->SourceRealm != acr2ptr->SourceRealm) continue;
      if (acr1ptr->UserNameLength != acr2ptr->UserNameLength) continue;
      if (acr1ptr->RealmLength != acr2ptr->RealmLength) continue;
      if (acr1ptr->ConfigDirectoryLength != acr2ptr->ConfigDirectoryLength)
         continue;
      if (acr1ptr->PathParameterLength != acr2ptr->PathParameterLength)
         continue;
      if (acr1ptr->SourceGroupWrite != acr2ptr->SourceGroupWrite) continue;
      if (acr1ptr->GroupWriteLength != acr2ptr->GroupWriteLength) continue;
      if (acr1ptr->SourceGroupRead != acr2ptr->SourceGroupRead) continue;
      if (acr1ptr->GroupReadLength != acr2ptr->GroupReadLength) continue;
      if (strcmp (acr1ptr->UserName, acr2ptr->UserName)) continue;
      if (strcmp (acr1ptr->Realm, acr2ptr->Realm)) continue;
      if (acr1ptr->ConfigDirectoryLength &&
          strcmp (acr1ptr->ConfigDirectory, acr2ptr->ConfigDirectory)) continue;
      if (acr1ptr->PathParameterLength &&
          strcmp (acr1ptr->PathParameter, acr2ptr->PathParameter)) continue;
      if (acr1ptr->GroupWriteLength &&
          strcmp (acr1ptr->GroupWrite, acr2ptr->GroupWrite)) continue;
      if (acr1ptr->GroupReadLength &&
          strcmp (acr1ptr->GroupRead, acr2ptr->GroupRead)) continue;

      /* got this far, must have matched */
      *RecordPtrPtr = acr2ptr;
      if (!acr1ptr->FindRecordCount++)
      {
         /* first time this request that the record has been searched for */
         acr2ptr->LastAccessSecondsAgo = HttpdTickSecond -
                                         acr2ptr->LastAccessTickSecond;
         acr2ptr->LastAccessMinutesAgo = acr2ptr->LastAccessSecondsAgo / 60;
         if (acr2ptr->LastAccessMinutesAgo < Config.cfAuth.CacheMinutes)
            AuthGblSecPtr->CacheHitCount++;
         else
            AuthGblSecPtr->CacheTimeoutCount++;
         sys$gettim (&acr2ptr->LastAccessTime64);
         acr2ptr->LastAccessTickSecond = HttpdTickSecond;
      }

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

      return (SS$_NORMAL);
   }

   if (WATCH_MODULE(WATCH_MOD_AUTH))
      WatchThis (WATCHALL, WATCH_MOD_AUTH, "NOT-FOUND");

   AuthGblSecPtr->CacheMissCount++;
   return (SS$_ITEMNOTFOUND);
}

/*****************************************************************************/
/*
Clear all of the authentication records.  Will result in all subsequent
requests being re-authenticated from their respective on-disk databases. 
Called from the Admin.c and Control.c modules.  'ControlFlush' indicates its
was initiated through a /DO= command and has therefore been seen by all of
multiple instances.
*/

int AuthCachePurge (BOOL ControlFlush)

{
   int  cnt, status,
        RecordCount;
   unsigned char  *RecordPoolPtr;
   AUTH_CREC  *acrptr;

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

   if (WATCH_MODULE(WATCH_MOD_AUTH))
      WatchThis (WATCHALL, WATCH_MOD_AUTH, "AuthCachePurge() !&B !UL !&B",
                 ControlFlush, InstanceNodeConfig, InstanceNodeSupervisor);

   /* in a multi-instance config, shared data only by the supervisor */
   if (ControlFlush && InstanceNodeConfig > 1 && !InstanceNodeSupervisor)
      return (SS$_NORMAL);

   InstanceMutexLock (INSTANCE_MUTEX_AUTH_CACHE);

   RecordCount = AuthGblSecPtr->CacheRecordCount;
   RecordPoolPtr = AuthGblSecPtr->CacheRecordPool;

   for (cnt = 0; cnt < RecordCount; cnt++)
   {
      acrptr = RecordPoolPtr + (AuthCacheRecordSize * cnt);
      memset (acrptr, 0, AuthCacheRecordSize);
   }

   AuthGblSecPtr->CacheRecordCount = 0;
   sys$gettim (&AuthGblSecPtr->SinceTime64);

   InstanceMutexUnLock (INSTANCE_MUTEX_AUTH_CACHE);

   return (SS$_NORMAL);
}

/*****************************************************************************/
/*
Reset the entry for all records with a matching realm and user name.  This
effectively causes the username to be revalidated next authorized path access.
*/

int AuthCacheReset
(
REQUEST_STRUCT *rqptr,
char *Realm,
char *UserName
)
{
   int  cnt, status,
        RecordCount;
   unsigned char  *RecordPoolPtr;
   AUTH_CREC  *acrptr;

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

   if (WATCH_MODULE(WATCH_MOD_AUTH))
      WatchThis (WATCHALL, WATCH_MOD_AUTH,
                 "AuthCacheReset() !&Z !&Z", Realm, UserName);

   InstanceMutexLock (INSTANCE_MUTEX_AUTH_CACHE);

   RecordCount = AuthGblSecPtr->CacheRecordCount;
   RecordPoolPtr = AuthGblSecPtr->CacheRecordPool;

   for (cnt = 0; cnt < RecordCount; cnt++)
   {
      acrptr = RecordPoolPtr + (AuthCacheRecordSize * cnt);
      if (!acrptr->SourceRealm) continue;
      if (Realm && Realm[0] && strcmp (Realm, acrptr->Realm)) continue;
      if (UserName && UserName[0] && strcmp (UserName, acrptr->UserName)) continue;
      if (WATCH_MODULE(WATCH_MOD_AUTH))
         WatchThis (WATCHALL, WATCH_MOD_AUTH,
                    "!&Z !&Z", acrptr->Realm, acrptr->UserName);
      memset (acrptr, 0, AuthCacheRecordSize);
   }

   InstanceMutexUnLock (INSTANCE_MUTEX_AUTH_CACHE);

   return (SS$_NORMAL);
}

/*****************************************************************************/
/*
Display all records in the authentication cache.
*/

AuthCacheReport (REQUEST_STRUCT *rqptr)

{
   static char  BeginPageFao [] =
"<p><table class=\"ctgry\">\n\
<tr><td>\n\
<table class=\"rghtlft\">\n\
\
<tr><th class=\"sbttl\">Source</th>\n\
<tr><th>ACME:</th><td>!UL</td>\n\
<tr><th>Agent:</th><td>!UL</td>\n\
<tr><th>HTA:</th><td>!UL</td>\n\
<tr><th>List:</th><td>!UL</td>\n\
<tr><th>RFC1413:</th><td>!UL</td></tr>\n\
<tr><th>Skeleton-Key:</th><td>!UL</td><td>!AZ</td></tr>\n\
<tr><th>SYSUAF:</th><td>!UL</td></tr>\n\
<tr><th>Token:</th><td>!UL</td></tr>\n\
<tr><th>X509:</th><td>!UL</td></tr>\n\
</table>\n\
</td><td>\n\
\
<table class=\"rghtlft\" style=\"margin-left:-2em;\">\n\
<tr><th class=\"sbttl\">Scheme</th></tr>\n\
<tr><th>Basic:</th><td>!UL</td></tr>\n\
<tr><th>Digest:</th><td>!UL</td></tr>\n\
<tr><th>Other:</th><td>!UL</td></tr>\n\
</table>\n\
</td>\
<td>\n\
\
<table class=\"rghtlft\" style=\"margin-left:2em;\">\n\
<tr><th class=\"sbttl\">Cache</th></tr>\n\
<tr><th>Since:</th><td colspan=\"2\"><nobr>!20&W</nobr></td></tr>\n\
<tr><th>Maximum:</th><td colspan=\"2\">!UL x !UL bytes</td></tr>\n\
<tr><th>Current:</th><td>!UL</td>\
<td style=\"width:100%\"></td></tr>\n\
<tr><th>Reused:</th><td>!UL</td></tr>\n\
<tr><th>Hits:</th><td>!UL</td></tr>\n\
<tr><th>Misses:</th><td>!UL</td></tr>\n\
<tr><th>Timeouts:</th><td>!UL</td></tr>\n\
</table>\n\
\
</td></tr>\n\
</table>\n\
\
<p><table class=\"lftlft\">\n\\
<tr>\
<th></th>\
<th colspan=\"2\" class=\"sbttl\">Realm</th>\
<th colspan=\"2\">Group-R+W</th>\
<th>Group-R</u></th>\
<th colspan=\"4\">Config-Dir&nbsp;/&nbsp;Path-Param</th>\
</tr>\n<tr>\
<th></th>\
<th>User</th>\
<th>Access</th>\
<th class=\"targht\">Basic</th>\
<th>Digest</th>\
<th><nobr>Most Recent</th>\
<th class=\"targht\">Cache</th>\
<th class=\"targht\">Source</th>\
<th class=\"targht\">Fail</th>\
<th class=\"targht\">Break-in</th>\
</tr>\n";

   static char  RecordFao [] =
"<tr>\
<th>!4ZL</th>\
<td colspan=\"2\">!AZ!AZ</td>\
<td colspan=\"2\">!&@</td>\
<td>!&@</td>\
<td colspan=\"4\">!AZ !AZ</td>\
</tr>\n\
<tr class=\"hlght\">\
<th></th>\
<td>!AZ</td>\
<td>!AZ!AZ!AZ</td>\
<td class=\"targht\">!UL</td>\
<td class=\"targht\">!UL</td>\
<td>!20%D</td>\
<td class=\"targht\">!UL</td>\
<td class=\"targht\">!UL</td>\
<td class=\"targht\">!UL</td>\
<td class=\"targht\">!AZ!UL!AZ</td>\
<td class=\"pshlft\"></td>\
</tr>\n";

   static char EmptyCacheFao [] =
"<tr clas=\"hlght\"><th>0000</th>\
<td colspan=\"9\"><i>empty</i></td><tr>\n";

   static char  EndPageFao [] =
"</table>\n\
</div>\n\
</body>\n\
</html>\n";

   int  cnt, status,
        RecordCount;
   unsigned long  FaoVector [48];
   unsigned long  *vecptr;
   unsigned char  *RecordPoolPtr;
   char  *cptr;
   AUTH_CREC  *acrptr;

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

   if (WATCHMOD (rqptr, WATCH_MOD_AUTH))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_AUTH, "AuthCacheReport()");

   AdminPageTitle (rqptr, "User Authentication");

   vecptr = FaoVector;

   InstanceMutexLock (INSTANCE_MUTEX_HTTPD);

   *vecptr++ = AccountingPtr->AuthAcmeCount;
   *vecptr++ = AccountingPtr->AuthAgentCount;
   *vecptr++ = AccountingPtr->AuthHtDatabaseCount;
   *vecptr++ = AccountingPtr->AuthSimpleListCount;
   *vecptr++ = AccountingPtr->AuthRFC1413Count;
   *vecptr++ = AccountingPtr->AuthSkelKeyCount;
   if (HttpdGblSecPtr->AuthSkelKeyHttpdTickSecond)
      *vecptr++ = "(<b>Active</b>)";
   else
      *vecptr++ = "(Inactive)";
   *vecptr++ = AccountingPtr->AuthVmsCount;
   *vecptr++ = AccountingPtr->AuthTokenCount;
   *vecptr++ = AccountingPtr->AuthX509Count;
   *vecptr++ = AccountingPtr->AuthBasicCount;
   *vecptr++ = AccountingPtr->AuthDigestCount;
   *vecptr++ = AccountingPtr->AuthOtherCount;

   InstanceMutexUnLock (INSTANCE_MUTEX_HTTPD);

   InstanceMutexLock (INSTANCE_MUTEX_AUTH_CACHE);

   *vecptr++ = &AuthGblSecPtr->SinceTime64;
   *vecptr++ = AuthCacheRecordMax;
   *vecptr++ = AuthCacheRecordSize;
   *vecptr++ = AuthGblSecPtr->CacheRecordCount;
   *vecptr++ = AuthGblSecPtr->CacheReuseCount;
   *vecptr++ = AuthGblSecPtr->CacheHitCount;
   *vecptr++ = AuthGblSecPtr->CacheMissCount;
   *vecptr++ = AuthGblSecPtr->CacheTimeoutCount;

   status = FaolToNet (rqptr, BeginPageFao, &FaoVector);
   if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI);

   RecordCount = AuthGblSecPtr->CacheRecordCount;
   RecordPoolPtr = AuthGblSecPtr->CacheRecordPool;

   for (cnt = 0; cnt < RecordCount; cnt++)
   {
      acrptr = RecordPoolPtr + (AuthCacheRecordSize * cnt);
      if (!acrptr->SourceRealm) continue;

      cptr = AuthCanString (acrptr->AuthUserCan, AUTH_CAN_FORMAT_HTML);
      if (!cptr) cptr = "*ERROR*";

      vecptr = FaoVector;

      *vecptr++ = cnt+1;

      *vecptr++ = acrptr->Realm;
      *vecptr++ = AuthSourceString (acrptr->Realm, acrptr->SourceRealm);
      if (acrptr->GroupWrite[0])
      {
         *vecptr++ = "!AZ!AZ";
         *vecptr++ = acrptr->GroupWrite;
         *vecptr++ = AuthSourceString (acrptr->GroupWrite,
                                       acrptr->SourceGroupWrite);
      }
      else
         *vecptr++ = "<i>none</i>";
      if (acrptr->GroupRead[0])
      {
         *vecptr++ = "!AZ!AZ";
         *vecptr++ = acrptr->GroupRead;
         *vecptr++ = AuthSourceString (acrptr->GroupRead,
                                       acrptr->SourceGroupRead);
      }
      else
         *vecptr++ = "<i>none</i>";
      if (acrptr->ConfigDirectory[0])
         *vecptr++ = acrptr->ConfigDirectory;
      else
      if (!acrptr->PathParameter[0])
         *vecptr++ = "<i>neither</i>";
      else
         *vecptr++ = "";
      if (acrptr->PathParameter[0])
         *vecptr++ = acrptr->PathParameter;
      else
         *vecptr++ = "";

      *vecptr++ = acrptr->UserName;
      *vecptr++ = cptr;
      if (acrptr->VmsUserProfileLength)
         *vecptr++ = " (profile)";
      else
         *vecptr++ = "";
      if (acrptr->HttpsOnly)
         *vecptr++ = " (&quot;https:&quot;&nbsp;only)";
      else
         *vecptr++ = "";

      *vecptr++ = acrptr->BasicCount;
      *vecptr++ = acrptr->DigestCount;
      *vecptr++ = &acrptr->LastAccessTime64;
      *vecptr++ = acrptr->AccessCount;
      *vecptr++ = acrptr->DataBaseCount;
      if (acrptr->FailureCount < Config.cfAuth.FailureLimit)
      {
         *vecptr++ = acrptr->FailureCount;
         *vecptr++ = "";
         *vecptr++ = 0;
         *vecptr++ = "";
      }
      else
      {
         *vecptr++ = 0;
         *vecptr++ = "<font color=\"#ff0000\">";
         *vecptr++ = acrptr->FailureCount;
         *vecptr++ = "</font>";
      }

      status = FaolToNet (rqptr, RecordFao, &FaoVector);
      if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI);
   }
   if (!RecordCount)
   {
      status = FaolToNet (rqptr, EmptyCacheFao, NULL);
      if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI);
   }

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

   InstanceMutexUnLock (INSTANCE_MUTEX_AUTH_CACHE);

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

   AdminEnd (rqptr);
}

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

