/*****************************************************************************/
/*
                                MapUser.c

Gets the username's home file specification for use in /~ mappings.


VERSION HISTORY
---------------
07-OCT-2004  MGD  unbundled from MAPURL.C
*/
/*****************************************************************************/

#ifdef WASD_VMS_V7
#undef _VMS__V6__SOURCE
#define _VMS__V6__SOURCE
#undef __VMS_VER
#undef __VMS_VER
#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 <devdef.h>
#include <dvidef.h>
#include <prvdef.h>
#include <ssdef.h>
#include <stsdef.h>
#include <uaidef.h>

#include "wasd.h"

#define WASD_MODULE "MAPUSER"

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

#define MAPURL_DEFAULT_USER_CACHE_SIZE  32

LIST_HEAD  MapUrlUserNameCacheList;
int  MapUrlUserNameCacheCount,
     MapUrlUserNameCacheEntries;

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

extern BOOL  AuthPolicySysUafRelaxed,
             OdsExtended;

extern unsigned long  SysPrvMask[];

extern CONFIG_STRUCT  Config;
extern WATCH_STRUCT  Watch;

/*****************************************************************************/
/*
Get the path representing the username default device/directory.  This can be
retrieved from a cache, or retrieved from directly from the SYSUAF and the
cache subsequently updated.  The 'MAPURL_USER_RULE_FORBIDDEN_MSG' used to
return a user-access-denied to the calling routine, is designed to mask the
actual reason for denial.  This is done to help prevent the leakage of user
account information by being able to differentiate between accounts that exist
and those that don't, by looking at the message.  RequestExecute() checks for
this message and converts it into a "directory not found" message, the same as
that generated if a user does not have a [.WWW] subdirectory in the home area.
*/ 

char* MapUserName
(
REQUEST_STRUCT* rqptr,
char *UserNamePtr,
int *PathOdsPtr
)
{
   /* UAI flags that disallow SYSUAF-controlled account access */
   static unsigned long  DisallowVmsFlags =
          UAI$M_DISACNT | UAI$M_PWD_EXPIRED | UAI$M_PWD2_EXPIRED |
          UAI$M_CAPTIVE | UAI$M_RESTRICTED;

   static unsigned long  Context = -1;

   static unsigned long  UaiFlags;
   static unsigned long  UaiPriv [2];
   static char  UaiDefDev [MAPURL_USER_DEFDEV_SIZE+1],
                UaiDefDir [MAPURL_USER_DEFDIR_SIZE+1],
                UserName [MAPURL_USER_NAME_SIZE+1],
                UserPath [MAPURL_USER_PATH_SIZE+1];
   static $DESCRIPTOR (UserNameDsc, UserName);

   static struct {
      short BufferLength;
      short ItemCode;
      void  *BufferPtr;
      void  *LengthPtr;
   } UaiItems [] = 
   {
      { sizeof(UaiFlags), UAI$_FLAGS, &UaiFlags, 0 },
      { sizeof(UaiPriv), UAI$_PRIV, &UaiPriv, 0 },
      { sizeof(UaiDefDev), UAI$_DEFDEV, &UaiDefDev, 0 },
      { sizeof(UaiDefDir), UAI$_DEFDIR, &UaiDefDir, 0 },
      { 0,0,0,0 }
   };

   int  status,
        PathOds;
   char  *cptr, *sptr, *zptr;
   char  UserDefault [MAPURL_USER_DEFDEV_SIZE+MAPURL_USER_DEFDIR_SIZE+1];

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

   if (WATCHMOD (rqptr, WATCH_MOD_MAPURL))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_MAPURL,
                 "MapUserName() !&Z", UserNamePtr);

   zptr = (sptr = UserName) + sizeof(UserName);
   for (cptr = UserNamePtr;
        *cptr && *cptr != '/' && sptr < zptr;
        *sptr++ = to_upper(*cptr++));
   if (sptr >= zptr) return (MAPURL_USER_RULE_FORBIDDEN_MSG);
   *sptr = '\0';
   UserNameDsc.dsc$w_length = sptr - UserName;

   /* look for it, and if found, return it from the cache */
   if ((cptr = MapUserNameCache (rqptr, UserName, NULL, PathOdsPtr)))
      return (cptr);

   /***************************************/
   /* get the information from the SYSUAF */
   /***************************************/

   /* turn on SYSPRV to allow access to SYSUAF records */
   sys$setprv (1, &SysPrvMask, 0, 0);

   status = sys$getuai (0, &Context, &UserNameDsc, &UaiItems, 0, 0, 0);

   sys$setprv (0, &SysPrvMask, 0, 0);

   if (VMSnok (status)) 
   {
      if (status == RMS$_RNF)
      {
         if (WATCHPNT(rqptr) &&
             WATCH_CATEGORY(WATCH_MAPPING))
            WatchThis (WATCHITM(rqptr), WATCH_MAPPING,
                       "USER fail !AZ unknown", UserName);
         return (MAPURL_USER_RULE_FORBIDDEN_MSG);
      }
      if (WATCHPNT(rqptr) &&
          WATCH_CATEGORY(WATCH_MAPPING))
         WatchThis (WATCHITM(rqptr), WATCH_MAPPING,
                    "USER sys$getuai() !&S", status);
      return (MAPURL_USER_RULE_FORBIDDEN_MSG);
   }

   /* automatically disallow if any of these flags are set! */
   if (UaiFlags & DisallowVmsFlags)
   {
      if (WATCHPNT(rqptr) &&
          WATCH_CATEGORY(WATCH_MAPPING))
         WatchThis (WATCHITM(rqptr), WATCH_MAPPING,
                    "USER !AZ fail SYSUAF flags", UserName);
      return (MAPURL_USER_RULE_FORBIDDEN_MSG);
   }

   if (!AuthPolicySysUafRelaxed && (UaiPriv[0] & PRV$M_SYSPRV))
   {
      /* not allowing all accounts, exclude those with extended privileges */
      if (WATCHPNT(rqptr) &&
          WATCH_CATEGORY(WATCH_MAPPING))
         WatchThis (WATCHITM(rqptr), WATCH_MAPPING,
                    "USER !AZ fail SYSUAF privileges", UserName);
      return (MAPURL_USER_RULE_FORBIDDEN_MSG);
   }

   zptr = (sptr = UserDefault) + sizeof(UserDefault);
   for (cptr = UaiDefDev+1; UaiDefDev[0] && sptr < zptr; UaiDefDev[0]--)
      *sptr++ = *cptr++;
   for (cptr = UaiDefDir+1; UaiDefDir[0] && sptr < zptr; UaiDefDir[0]--)
      *sptr++ = *cptr++;
   if (sptr >= zptr)
      return (MAPURL_USER_RULE_FORBIDDEN_MSG);
   *sptr = '\0';

#ifdef ODS_EXTENDED

   if (OdsExtended)
   {
      /* on-disk-structure of user area */
      PathOds = OdsVolumeStructure (UserDefault);
      if (PathOds && (PathOds != MAPURL_PATH_ODS_2 &&
                      PathOds != MAPURL_PATH_ODS_5))
      {
         if (WATCHING (rqptr, WATCH_MAPPING))
            WatchThis (WATCHITM(rqptr), WATCH_MAPPING,
                       "USER !AZ !AZ ODS-? %X8XL %!&M",
                       UserName, UserDefault, PathOds, PathOds);
         return (MAPURL_USER_RULE_FORBIDDEN_MSG);
      }
   }
   else

#endif /* ODS_EXTENDED */

      PathOds = 0;

   /* generate a URL-style version of the VMS specification */
   MapOdsVmsToUrl (UserPath, UserDefault, sizeof(UserPath), true, PathOds);

   if (WATCHPNT(rqptr) &&
       WATCH_CATEGORY(WATCH_MAPPING))
      WatchThis (WATCHITM(rqptr), WATCH_MAPPING,
                 "USER !AZ !AZ !AZ ODS-!UL",
                 UserName, UserDefault, UserPath, PathOds);

   /* trim trailing slash */
   for (cptr = UserPath; *cptr && !SAME2(cptr,'/\0'); cptr++);
   *cptr = '\0';

   /* update it in the cache */
   MapUserNameCache (rqptr, UserName, UserPath, &PathOds);

   /* if required return the path's on-disk-structure */
   if (PathOdsPtr) *PathOdsPtr = PathOds;

   return (UserPath);
}

/*****************************************************************************/
/*
Keep a linked-list of cache entries.  If 'UserName' and 'UserPath' are NULL
then the list is reset (this happen on mapping rule reload).  If 'UserName' is
non-NULL and 'UserPath' is NULL the list is searched for a matching username
and the associated path string returned.  If 'UserName' and 'UserPath' are
non-NULL add/update a cache entry.  If the list has reached maximum size reuse
the last entry, otherwise create a new entry.  Move/add the entry to the head
of the list.  It's therefore a first-in/first-out queue.  Cache contents remain
current until demands on space (due to new entries) cycles through the maximum
available entries.  To explicitly flush the contents reload the rules.
*/ 

char* MapUserNameCache
(
REQUEST_STRUCT* rqptr,
char *UserName,
char *UserPath,
int *PathOdsPtr
)
{
   LIST_ENTRY  *leptr;
   MAP_URL_USER_ENTRY  *ucptr;

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

   if (WATCHMOD (rqptr, WATCH_MOD_MAPURL))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_MAPURL,
                 "MapUserNameCache() !&Z !&Z !&X",
                 UserName, UserPath, PathOdsPtr);

   if (!UserName && !UserPath)
   {
      /***************/
      /* reset cache */
      /***************/

      MapUrlUserNameCacheEntries = Config.cfMisc.MapUserNameCacheEntries;
      if (!MapUrlUserNameCacheEntries)
         MapUrlUserNameCacheEntries = MAPURL_DEFAULT_USER_CACHE_SIZE;
      MapUrlUserNameCacheCount = 0;

      /* empty the list */
      leptr = MapUrlUserNameCacheList.HeadPtr;
      MapUrlUserNameCacheList.HeadPtr = MapUrlUserNameCacheList.TailPtr = NULL;
      MapUrlUserNameCacheList.EntryCount = 0;
      while (leptr) 
      {
         ucptr = (MAP_URL_USER_ENTRY*)leptr;
         leptr = leptr->NextPtr;
         VmFree (ucptr, FI_LI);
      }
      return (NULL);
   }

   if (!UserPath)
   {
      /****************/
      /* search cache */
      /****************/

      /* process the cache entry list from most to least recent */
      for (leptr = MapUrlUserNameCacheList.HeadPtr;
           leptr;
           leptr = leptr->NextPtr)
      {
         ucptr = (MAP_URL_USER_ENTRY*)leptr;

         /* if this one has been reset there won't be any more down the list */
         if (!ucptr->UserName[0]) break;

         /* compare the first two characters (at least one and a null) */
         if (!MATCH4(ucptr->UserName, UserName)) continue;
         /* full string comparison */
         if (strcmp (ucptr->UserName, UserName)) continue;

         /*************/
         /* cache hit */
         /*************/

         if ((void*)MapUrlUserNameCacheList.HeadPtr != (void*)ucptr)
         {
            /* move it to the head of the list */
            ListRemove (&MapUrlUserNameCacheList, ucptr);
            ListAddHead (&MapUrlUserNameCacheList, ucptr, LIST_ENTRY_TYPE_USER);
         }

         ucptr->HitCount++;
         ucptr->LastTime64 = rqptr->rqTime.BeginTime64;

         if (WATCHING (rqptr, WATCH_MAPPING))
            WatchThis (WATCHITM(rqptr), WATCH_MAPPING,
                       "USER !AZ cache !AZ/ ODS-!UL !UL hits",
                       ucptr->UserName, ucptr->UserPath,
                       ucptr->PathOds, ucptr->HitCount);

         if (PathOdsPtr) *PathOdsPtr = ucptr->PathOds;
         return (ucptr->UserPath);
      }

      /* not found */
      return (NULL);
   }

   /****************/
   /* update cache */
   /****************/

   if (MapUrlUserNameCacheCount < MapUrlUserNameCacheEntries)
   {
      /* allocate memory for a new entry */
      ucptr = VmGet (sizeof (MAP_URL_USER_ENTRY));
      MapUrlUserNameCacheCount++;
   }
   else
   {
      /* reuse the tail entry (least recent) */
      ucptr = MapUrlUserNameCacheList.TailPtr;
      ucptr->ReuseCount++;
      ListRemove (&MapUrlUserNameCacheList, ucptr);
   }

   /* add entry to the head of the user cache list (most recent) */
   ListAddHead (&MapUrlUserNameCacheList, ucptr, LIST_ENTRY_TYPE_USER);

   strncpy (ucptr->UserName, UserName, sizeof(ucptr->UserName));
   strncpy (ucptr->UserPath, UserPath, sizeof(ucptr->UserPath));
   ucptr->UserName[sizeof(ucptr->UserName)-1] =
      ucptr->UserPath[sizeof(ucptr->UserPath)-1] = '\0';
   ucptr->HitCount = 1;
   ucptr->LastTime64 = rqptr->rqTime.BeginTime64;
#ifdef ODS_EXTENDED
   if (PathOdsPtr) ucptr->PathOds = *PathOdsPtr;
#else /* ODS_EXTENDED */
   if (PathOdsPtr) ucptr->PathOds = 0;
#endif /* ODS_EXTENDED */

   return (UserPath);
}

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

