/*****************************************************************************/
/*
                                AuthAccess.c

Licensed under the Apache License, Version 2.0 (the License);
you may not use this software except in compliance with the License.
You may obtain a copy of the License at

   http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an AS IS BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

Authorize access to a particular file or directory.

Enable access (via SYSPRV) appropriately to ensure access.

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


VERSION HISTORY
---------------
04-JAN-2021  MGD  AuthAccessEnable() NULL |rqptr| returns SYSPRV enabled?
16-OCT-2020  MGD  AuthAccessEnable() add ->rqPathSet.WebDavAll and
                    ->rqPathSet.WebDavAuth
29-SEP-2020  MGD  AuthAccessEnable() file access use (rqptr->WebDavRequest ||
                    rqptr->WhiffOfWebDav) rather than ->WebDavTaskPtr
13-JUL-2014  MGD  AuthAccessCheck() add explicit check against server
                    account to improve reporting of underlying access
31-JUL-2007  MGD  unbundled from AUTH.C
*/
/*****************************************************************************/

#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 <armdef.h>
#include <chpdef.h>
#include <descrip.h>
#include <libdef.h>
#include <prvdef.h>
#include <ssdef.h>
#include <stsdef.h>

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

#define WASD_MODULE "AUTHACCESS"

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

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

extern BOOL  AuthPolicySysUafRelaxed,
             AuthPolicySysUafIdentifiers,
             AuthPolicySysUafWasdIdentifiers,
             AuthPolicySysUafVms,
             AuthSysUafEnabled,
             AuthSysUafPromiscuous,
             AuthVmsUserProfileEnabled,
             AuthVmsUserProfileNoRule;

extern BOOL  InstanceMutexHeld[];

extern int  ServerPort;

extern unsigned int  HttpdUserProfileLength;
extern unsigned char  *HttpdUserProfilePtr;
extern struct dsc$descriptor_s  HttpdUserProfileDsc;

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

extern unsigned long  SysPrvMask[];

extern char  ErrorSanityCheck[],
             SoftwareID[];

extern ACCOUNTING_STRUCT  *AccountingPtr;
extern CONFIG_STRUCT  Config;
extern HTTPD_PROCESS  HttpdProcess;
extern MSG_STRUCT  Msgs;
extern WATCH_STRUCT  Watch;

/*****************************************************************************/
/*
Using sys$check_access() check file read/write access permission for the
supplied file name against the SYSUAF-authenticated user's security profile
(created by AuthVmsCreateUserProfile()).  File or directory specifications can
be supplied. If the file or directory does not exist then check access to the
parent directory (in this way permission to say, create a directory may be
established). Returns SS$_NORMAL if access allowed, SS$_NOPRIV if denied, and
other RMS status codes.
*/

int AuthAccessCheck
(
REQUEST_STRUCT* rqptr,
char *FileName,
int FileNameLength,
int AccessRequired
)
{
   static unsigned long  Flags = 0;
   static unsigned long  ArmAccess;
   static unsigned long  ArmControlAccess = ARM$M_CONTROL;
   static $DESCRIPTOR (ClassNameDsc, "FILE");
   static $DESCRIPTOR (UserProfileDsc, "");
   static VMS_ITEM_LIST3  ArmAccessItem [] = 
   {
      { sizeof(ArmAccess), CHP$_ACCESS, &ArmAccess, 0 },
      { 0, 0, 0, 0 }
   };
   static VMS_ITEM_LIST3 ControlAccessItem [] =
   {
      { sizeof(ArmControlAccess), CHP$_ACCESS, &ArmControlAccess, 0 },
      { 0, 0, 0, 0 }
   };

   BOOL  VmsUserProfile;
   int  status, stts,
        DirFileNameLength,
        ParentFileNameLength = 0;
   char  *cptr,
         *ProfilePtr,
         *WhoseProfilePtr;
   char  DirFileName [ODS_MAX_FILE_NAME_LENGTH+1],
         ParentFileName [ODS_MAX_FILE_NAME_LENGTH+1];
   $DESCRIPTOR (FileNameDsc, "");
   $DESCRIPTOR (ParentNameDsc, "");

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

   if (WATCHMOD (rqptr, WATCH_MOD_AUTH))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_AUTH,
                 "AuthAccessCheck() !&Z !&Z !UL !UL !&X !&B !&B",
                  rqptr->RemoteUser, FileName, AccessRequired,
                  rqptr->rqAuth.VmsUserProfileLength,
                  rqptr->rqAuth.VmsUserProfilePtr,
                  rqptr->rqPathSet.NoProfile,
                  rqptr->rqPathSet.AccessProfile ||
                     rqptr->rqPathSet.AccessRead ||
                     rqptr->rqPathSet.AccessServer ||
                     rqptr->rqPathSet.AccessWrite);

   if (!HttpdUserProfileLength) AuthVmsCreateHttpdProfile ();

   /* wildcard specifications can be passed to this function */
   if (FileNameLength > 0)
      cptr = FileName + FileNameLength;
   else
      for (cptr = FileName; *cptr; cptr++);
   if (cptr > FileName) cptr--;
   while (cptr > FileName && *cptr != ']') cptr--;
   if (*cptr == ']') cptr++;
   FileNameLength = cptr - FileName;
   while (*cptr && *cptr != '*' && *cptr != '%') cptr++;
   if (!*cptr) FileNameLength = cptr - FileName;

   if (!FileNameLength || !FileName[0])
   {
      ErrorNoticed (rqptr, SS$_BUGCHECK, NULL, FI_LI);
      return (SS$_BUGCHECK);
   }

   if (FileNameLength && FileName[FileNameLength-1] == ']')
   {
      /* a directory has been specified, get the directory file name */
      OdsNameOfDirectoryFile (FileName, FileNameLength,
                              DirFileName, &DirFileNameLength);
      FileNameDsc.dsc$a_pointer = DirFileName;
      FileNameDsc.dsc$w_length = DirFileNameLength;
   }
   else
   {
      FileNameDsc.dsc$a_pointer = FileName;
      FileNameDsc.dsc$w_length = FileNameLength;
   }

   /****************/
   /* check access */
   /****************/

   switch (AccessRequired)
   {
      case AUTH_ACCESS_READ :
         ArmAccess = ARM$M_READ;
         break;
      case AUTH_ACCESS_WRITE :
         ArmAccess = ARM$M_WRITE;
         break;
      default :
         ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI);
   }

   if (rqptr->rqAuth.VmsUserProfileLength)
   {
      VmsUserProfile = AuthVmsUserProfileNoRule;
      if (rqptr->rqPathSet.NoProfile) VmsUserProfile = false;
      if (rqptr->rqAuth.VmsUserProfile) VmsUserProfile = true;
   }
   else
      VmsUserProfile = false;

   if (AccessRequired == AUTH_ACCESS_WRITE)
   {
      /*
         Check if the HTTPd account has CONTROL access to the parent
         directory of the file or directory specification supplied
         in 'FileName'. If the server has CONTROL access then there is no
         general access to the directory, only an authenticated VMS user
         with a security profile can write into it.
      */

      OdsNameOfDirectoryFile (FileNameDsc.dsc$a_pointer,
                              FileNameDsc.dsc$w_length,
                              ParentFileName, &ParentFileNameLength);
      ParentNameDsc.dsc$a_pointer = ParentFileName;
      ParentNameDsc.dsc$w_length = ParentFileNameLength;

      /* turn on SYSPRV to allow this to be done */
      sys$setprv (1, &SysPrvMask, 0, 0);

      status = sys$check_access (0, &ParentNameDsc, 0, &ControlAccessItem,
                                 0, &ClassNameDsc, 0, &HttpdUserProfileDsc);

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

      if (WATCHING (rqptr, WATCH_AUTH))
         WatchThis (WATCHITM(rqptr), WATCH_AUTH,
                    "ACCESS !AZ control !AZ !&S !&?YES\rNO\r",
                    HttpdProcess.UserName, ParentNameDsc.dsc$a_pointer,
                    status, VMSok(status));

      if (VMSok (status))
      {
         /* control ACE means must be using VMS profile */
         if (!VmsUserProfile) return (SS$_NOPRIV);
      }
      else
      if (status != SS$_NOPRIV)
      {
         /* any other error status */
         return (status);
      }
      /* drop through to check user access */
   }

   /***************/
   /* user access */
   /***************/

   stts = 0;

   if (VmsUserProfile)
   {
      /* against a SYSUAF-authenticated account */
      UserProfileDsc.dsc$a_pointer = rqptr->rqAuth.VmsUserProfilePtr;
      UserProfileDsc.dsc$w_length = rqptr->rqAuth.VmsUserProfileLength;
      WhoseProfilePtr = rqptr->RemoteUser;
      ProfilePtr = "(using profile)";
   }
   else
   {
      /* against the HTTPd server account */
      UserProfileDsc.dsc$a_pointer = HttpdUserProfilePtr;
      UserProfileDsc.dsc$w_length = HttpdUserProfileLength;
      WhoseProfilePtr = HttpdProcess.UserName;
      if (rqptr->rqAuth.VmsUserProfileLength)
         ProfilePtr = "(NOT using profile)";
      else
         ProfilePtr = "(no profile)";
   }

   /* turn on SYSPRV to allow this to be done */
   sys$setprv (1, &SysPrvMask, 0, 0);

   status = sys$check_access (0, &FileNameDsc, 0, &ArmAccessItem,
                              0, &ClassNameDsc, 0, &UserProfileDsc);

   if (WATCHMOD (rqptr, WATCH_MOD_AUTH))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_AUTH,
                 "sys$check_access() !AZ !AZ !&S",
                 WhoseProfilePtr, FileNameDsc.dsc$a_pointer, status);

   if (WATCHING (rqptr, WATCH_AUTH))
   {
      /* only for informational purposes */
      if (status == SS$_NOPRIV && VmsUserProfile)
      {
         /* also check against the HTTPd server account (e.g. via ACL) */
         UserProfileDsc.dsc$a_pointer = HttpdUserProfilePtr;
         UserProfileDsc.dsc$w_length = HttpdUserProfileLength;

         stts = sys$check_access (0, &FileNameDsc, 0, &ArmAccessItem,
                                  0, &ClassNameDsc, 0, &UserProfileDsc);

         if (WATCHMOD (rqptr, WATCH_MOD_AUTH))
            WatchThis (WATCHITM(rqptr), WATCH_MOD_AUTH,
                       "sys$check_access() !AZ !AZ !&S",
                       HttpdProcess.UserName, FileNameDsc.dsc$a_pointer, stts);
      }
   }

   /* turn SYSPRV back off again */
   sys$setprv (0, &SysPrvMask, 0, 0);

   if (status == RMS$_FNF || status == RMS$_DNF)
   {
      /* attempt to establish access to parent directory */
      if (!ParentFileNameLength)
      {
         /* if not already initialised when checking for control above */
         OdsNameOfDirectoryFile (FileNameDsc.dsc$a_pointer,
                                 FileNameDsc.dsc$w_length,
                                 ParentFileName, &ParentFileNameLength);
         ParentNameDsc.dsc$a_pointer = ParentFileName;
         ParentNameDsc.dsc$w_length = ParentFileNameLength;
      }

      if (VmsUserProfile)
      {
         /* against a SYSUAF-authenticated account */
         UserProfileDsc.dsc$a_pointer = rqptr->rqAuth.VmsUserProfilePtr;
         UserProfileDsc.dsc$w_length = rqptr->rqAuth.VmsUserProfileLength;
         WhoseProfilePtr = rqptr->RemoteUser;
         ProfilePtr = "(using profile)";
      }

      /* turn on SYSPRV to allow this to be done */
      sys$setprv (1, &SysPrvMask, 0, 0);

      status = sys$check_access (0, &ParentNameDsc, 0, &ArmAccessItem,
                                 0, &ClassNameDsc, 0, &UserProfileDsc);

      if (WATCHMOD (rqptr, WATCH_MOD_AUTH))
         WatchThis (WATCHITM(rqptr), WATCH_MOD_AUTH,
                    "sys$check_access() !AZ !AZ !&S",
                    WhoseProfilePtr, ParentNameDsc.dsc$a_pointer, status);

      if (WATCHING (rqptr, WATCH_AUTH))
      {
         /* only for informational purposes */
         if (status == SS$_NOPRIV && VmsUserProfile)
         {
            /* also against the HTTPd server account (e.g. via ACL) */
            UserProfileDsc.dsc$a_pointer = HttpdUserProfilePtr;
            UserProfileDsc.dsc$w_length = HttpdUserProfileLength;

            stts = sys$check_access (0, &ParentNameDsc, 0, &ArmAccessItem,
                                     0, &ClassNameDsc, 0, &UserProfileDsc);

            if (WATCHMOD (rqptr, WATCH_MOD_AUTH))
               WatchThis (WATCHITM(rqptr), WATCH_MOD_AUTH,
                          "sys$check_access() !AZ !AZ !&S",
                          HttpdProcess.UserName,
                          ParentNameDsc.dsc$a_pointer, stts);
         }
      }

      /* turn SYSPRV back off again */
      sys$setprv (0, &SysPrvMask, 0, 0);
   }

   if (WATCHING (rqptr, WATCH_AUTH))
   {
      WatchThis (WATCHITM(rqptr), WATCH_AUTH, "ACCESS !AZ !AZ !AZ !AZ !&S !AZ",
                 ProfilePtr, WhoseProfilePtr,
                 AccessRequired == AUTH_ACCESS_READ ? "read" : "write",
                 FileNameDsc.dsc$a_pointer,
                 status, VMSok(status) ? "PERMITTED" : "DENIED");

      if (VMSok(stts))
         WatchThis (WATCHITM(rqptr), WATCH_AUTH, "NOTE: !AZ !AZ !AZ !&S !AZ",
                    HttpdProcess.UserName,
                    AccessRequired == AUTH_ACCESS_READ ? "read" : "write",
                    FileNameDsc.dsc$a_pointer,
                    stts, VMSok(stts) ? "PERMITTED" : "DENIED");
   }

   if (VMSok (status)) return (status);

   /*
       DFS hosted volumes can return SS$_NOCALLPRIV.
       Trap SS$_ACCONFLICT into SS$_NOPRIV so directory listings don't halt.
   */
   if (status == SS$_NOPRIV ||
       status == SS$_NOCALLPRIV ||
       status == SS$_ACCONFLICT) return (SS$_NOPRIV);

   /* if an RMS error return normal letting caller continue and report it */
   if (((status & STS$M_FAC_NO) >> STS$V_FAC_NO) == RMS$_FACILITY)
      return (SS$_NORMAL);

   rqptr->rqResponse.ErrorTextPtr = "sys$check_access()";
   rqptr->rqResponse.ErrorOtherTextPtr = FileName;
   ErrorVmsStatus (rqptr, status, FI_LI);
   return (status);
}

/*****************************************************************************/
/*
Enables SYSPRV based on the ACCESS= or WEBDAV= path SETing.

If =READ then enables it when requiring read access.  If =WRITE then enables it
for both read and write.  If =PROFILE then the SYSUAF user security profile is
used to check for access against the supplied file specification.  If allowed
then SYSPRV is enabled.  If =SERVER then SYSPRV is never enabled, it becomes a
best effort based on the access permissions of the server account.  The
'AccessRequired' parameter AUTH_ACCESS_SYSPRV unconditionally enables SYSPRV
for those instances where discretionary access is not necessary.  No access
required (zero value) disables SYSPRV.

=WRITE takes precedence over =PROFILE, =READ and =SERVER
=PROFILE takes precedence over =READ and =SERVER
=READ takes precedence over =SERVER

Returns boolean for whether SYSPRV is currently enabled.
*/

BOOL AuthAccessEnable
(
REQUEST_STRUCT *rqptr,
char *FileName,
int AccessRequired
)
{
   static BOOL  SysPrvEnabled;

   BOOL IsWebDAV;
   int  status;
   char  *AccessPtr,
         *PathPtr;

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

   if (WATCHMOD (rqptr, WATCH_MOD_AUTH))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_AUTH,
"AuthAccessEnable() !UL!AZ webdav:!&B read:!&B write:!&B profile:!&B !&Z",
                 AccessRequired,
                 AccessRequired == AUTH_ACCESS_SYSPRV ? " (SYSPRV)" : "",
                 rqptr->WebDavRequest || rqptr->WhiffOfWebDav,
                 (rqptr->rqPathSet.WebDavRead ||
                  rqptr->rqPathSet.AccessRead),
                 (rqptr->rqPathSet.WebDavWrite ||
                  rqptr->rqPathSet.AccessWrite),
                  (rqptr->rqPathSet.WebDavProfile ||
                   rqptr->rqPathSet.AccessProfile),
                  FileName);

   /* if empty call then just return whether SYSPRV is enabled or not */
   if (!rqptr) return (SysPrvEnabled);

   if (!AccessRequired)
   {
      /* disable */
      if (!SysPrvEnabled) return (SysPrvEnabled);
      sys$setprv (0, &SysPrvMask, 0, 0);
      SysPrvEnabled = false;
      return (SysPrvEnabled);
   }

   IsWebDAV = rqptr->WebDavRequest ||
              rqptr->WhiffOfWebDav ||
              rqptr->rqPathSet.WebDavAll ||
              rqptr->rqPathSet.WebDavAuth;

   /* enabling/disabling must be done in balance to prevent privs escaping */
   if (SysPrvEnabled)
      ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI);

   switch (AccessRequired)
   {
      case AUTH_ACCESS_READ : AccessPtr = "read"; break;
      case AUTH_ACCESS_WRITE : AccessPtr = "write"; break;

      case AUTH_ACCESS_SYSPRV :

         /* unconditional enabling of SYSPRV */
         sys$setprv (1, &SysPrvMask, 0, 0);
         SysPrvEnabled = true;
         return (SysPrvEnabled);

      default :
         ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI);
   }

   if ((IsWebDAV &&
        rqptr->rqPathSet.WebDavWrite) ||
       rqptr->rqPathSet.AccessWrite)
   {
      /* path set '=write' */
      sys$setprv (1, &SysPrvMask, 0, 0);
      SysPrvEnabled = true;
      PathPtr = "write";
   }    
   else
   if ((IsWebDAV &&
        rqptr->rqPathSet.WebDavProfile) ||
       rqptr->rqPathSet.AccessProfile)
   {
      /* path set '=profile' */
      status = AuthAccessCheck (rqptr, FileName, 0, AccessRequired);
      if (VMSok (status))
      {
         sys$setprv (1, &SysPrvMask, 0, 0);
         SysPrvEnabled = true;
      }
      PathPtr = "profile";
   }
   else
   if ((IsWebDAV &&
        rqptr->rqPathSet.WebDavRead) ||
       rqptr->rqPathSet.AccessRead)
   {
      /* path set '=read' */
      if (AccessRequired == AUTH_ACCESS_READ)
      {
         sys$setprv (1, &SysPrvMask, 0, 0);
         SysPrvEnabled = true;
      }
      PathPtr = "read";
   }
   else
   if ((IsWebDAV &&
        rqptr->rqPathSet.WebDavServer) ||
       rqptr->rqPathSet.AccessServer)
   {
      /* path set '=server' so relies on permissions for server account */
      PathPtr = "server";
   }
   else
   if (rqptr->rqAuth.VmsUserProfileLength)
   {
      /* no path SETing but authorized path with request SYSUAF profile */
      status = AuthAccessCheck (rqptr, FileName, 0, AccessRequired);
      if (VMSok (status))
      {
         sys$setprv (1, &SysPrvMask, 0, 0);
         SysPrvEnabled = true;
      }
      PathPtr = "(none)";
   }
   else
      PathPtr = "(none)";

   if (WATCHING (rqptr, WATCH_AUTH))
      WatchThis (WATCHITM(rqptr), WATCH_AUTH,
"ACCESS:!AZ WebDAV:!&B PATH:!AZ (SET read:!&B write:!&B profile:!&B) \
SYSPRV:!AZ !AZ",
                 AccessPtr, IsWebDAV, PathPtr,
                 (rqptr->rqPathSet.WebDavRead ||
                  rqptr->rqPathSet.AccessRead),
                 (rqptr->rqPathSet.WebDavWrite ||
                  rqptr->rqPathSet.AccessWrite),
                 (rqptr->rqPathSet.WebDavProfile ||
                  rqptr->rqPathSet.AccessProfile),
                 SysPrvEnabled ? "enabled" : "disabled",
                 FileName);

   return (SysPrvEnabled);
}

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

