/*****************************************************************************/
/*
                                ChkAcc.c

-----
WASD (HFRD) VMS Hypertext Services, Copyright (C) 1996-2002 Mark G.Daniel.

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

CGI utility that can check whether a specified user name is permitted access to
a specified file.  Relies on the WASD HTTPd authenticating a remote user via
the SYSUAF.  This is indicated by the CGI variable WWW_AUTH_REALM being "VMS",
and the variable WWW_REMOTE_USER containing the authenticated user name.  The
CGI variable WWW_PATH_TRANSLATED contains the unambiguous VMS file
specification being assessed (i.e. cannot contain wildcards).  The HTTP method
determines whether read or write access is being sought.  If "GET" or "HEAD"
then read access, any other write access.  The exit status always has message
reporting inhibited, so it is up to the using procedure to check and act on the
$STATUS symbol, etc.

Can be used as a stand-alone utility, or just the CheckAccess() function as a
template for inclusion in another CGI script.

Just to be able to make the access assessment on behalf of a third-party the
executable would have to be installed with SYSPRV privilege:

        $ INSTALL ADD HT_EXE:CHKACC /PRIVILEGE=(SYSPRV)

If this was a CGI script accessing a file on behalf of an authenticated
third-party then SYSPRV could be needed to open the file for either read or
write. This should enabled immediately before opening the file, then disabled
immediately after. If it is a temporary file (e.g. delete-on-close) then
SYSPRV would require re-enabling immediately before closing, then immediately
re-disabling.

Good advice: do not attempt writing privileged images unless you are confident
you are not creating security holes.


VERSION HISTORY
---------------
23-DEC-2003  MGD  v1.0.2, minor conditional mods to support IA64
08-JUL-2002  MGD  v1.0.1, minor changes for stand-along build procedure
25-AUG-1997  MGD  v1.0.0, initial development (and happy birthday :^)
*/
/*****************************************************************************/

#define SOFTWAREVN "1.0.2"
#define SOFTWARENM "CHKACC"
#ifdef __ALPHA
   char SoftwareID [] = SOFTWARENM " AXP-" SOFTWAREVN;
#endif
#ifdef __ia64
   char SoftwareID [] = SOFTWARENM " IA64-" SOFTWAREVN;
#endif
#ifdef __VAX
   char SoftwareID [] = SOFTWARENM " VAX-" SOFTWAREVN;
#endif

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

/* VMS related header files */
#include <acldef.h>
#include <armdef.h>
#include <chpdef.h>
#include <descrip.h>
#include <prvdef.h>
#include <ssdef.h>
#include <stsdef.h>

#define VMSok(x) ((x) & STS$M_SUCCESS)
#define VMSnok(x) !(((x) & STS$M_SUCCESS))

#define boolean int
#define true 1
#define false 0

#define READ_ACCESS 1
#define WRITE_ACCESS 2

/* macro provides NULL pointer if CGI variable does not exist */
#define GetCgiVarIfExists(CharPointer,CgiVariableName) \
   CharPointer = getenv(CgiVariableName)

/* macro provides pointer to empty string even if CGI variable does not exist */
#define GetCgiVar(CharPointer,CgiVariableName) \
   if ((CharPointer = getenv(CgiVariableName)) == NULL) \
       CharPointer = ""; \
   if (Debug) fprintf (stdout, "%s |%s|\n", CgiVariableName, CharPointer);

char  Utility [] = "CHKACC";

boolean  Debug;

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

main ()

{
   int  status;
   char  *CgiAuthRealmPtr,
         *CgiPathTranslatedPtr,
         *CgiRemoteUserPtr,
         *CgiRequestMethodPtr;

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

   if (getenv ("CHKACC$DBUG") != NULL) Debug = true;

   GetCgiVar (CgiAuthRealmPtr, "WWW_AUTH_REALM");
   GetCgiVar (CgiPathTranslatedPtr, "WWW_PATH_TRANSLATED");
   GetCgiVar (CgiRemoteUserPtr, "WWW_REMOTE_USER");
   GetCgiVar (CgiRequestMethodPtr, "WWW_REQUEST_METHOD");

   if (!CgiAuthRealmPtr[0] || strcmp (CgiAuthRealmPtr, "VMS"))
   {
      fprintf (stdout, "%%%s-E-AUTHREALM, not VMS\n", Utility);
      exit (STS$K_ERROR | STS$M_INHIB_MSG);
   }

   if (!CgiRemoteUserPtr[0])
   {
      fprintf (stdout, "%%%s-E-REMOTEUSER, no authenticated user\n", Utility);
      exit (STS$K_ERROR | STS$M_INHIB_MSG);
   }

   if (!CgiPathTranslatedPtr[0])
   {
      fprintf (stdout, "%%%s-E-PATHTRANS, no file specification\n", Utility);
      exit (STS$K_ERROR | STS$M_INHIB_MSG);
   }

   if (!CgiRequestMethodPtr[0])
   {
      fprintf (stdout, "%%%s-E-REQUESTMETHOD, method?\n", Utility);
      exit (STS$K_ERROR | STS$M_INHIB_MSG);
   }

   if (!strcmp (CgiRequestMethodPtr, "GET") ||
       !strcmp (CgiRequestMethodPtr, "HEAD"))
      status = CheckAccess (CgiRemoteUserPtr, CgiPathTranslatedPtr,
                            READ_ACCESS);
   else
      status = CheckAccess (CgiRemoteUserPtr, CgiPathTranslatedPtr,
                            WRITE_ACCESS);

   exit (status | STS$M_INHIB_MSG);
}

/*****************************************************************************/
/*
Can be called one or more times using different user names. Using VMS security
system services checks that the specified user name can access the specified
file name either for read or write. Returns SS$_NOPRIV if access is denied,
SS$_NORMAL if access permitted, or other status code.
*/

int CheckAccess
(
char *UserName,
char *FileName,
int Access
)
{
   static unsigned long  SysPrvMask [2] = { PRV$M_SYSPRV, 0 };
   static unsigned long  Flags = 0,
                         ArmReadAccess = ARM$M_READ,
                         ArmWriteAccess = ARM$M_WRITE;
   static $DESCRIPTOR (ClassNameDsc, "FILE");

   static struct
   {
      short BufferLength;
      short ItemCode;
      void  *BufferPtr;
      void  *LengthPtr;
   }
   ReadItem [] = 
   {
      { sizeof(ArmReadAccess), CHP$_ACCESS, &ArmReadAccess, 0 },
      { 0, 0, 0, 0 }
   },
   WriteItem [] = 
   {
      { sizeof(ArmWriteAccess), CHP$_ACCESS, &ArmWriteAccess, 0 },
      { 0, 0, 0, 0 }
   };

   int  status,
        SetPrvStatus;
   unsigned short  UserProfileLength;
   unsigned char  UserProfile [2048];
   $DESCRIPTOR (FileNameDsc, FileName);
   $DESCRIPTOR (UserNameDsc, UserName);
   $DESCRIPTOR (UserProfileDsc, "");


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

   if (Debug) fprintf (stdout, "CheckAccess() |%s|%s|\n", UserName, FileName);

   UserNameDsc.dsc$w_length = strlen(UserName);
   UserProfileLength = sizeof(UserProfile);

   if (VMSnok (status = sys$setprv (1, &SysPrvMask, 0, 0))) return (status);

   status = sys$create_user_profile (&UserNameDsc, 0, Flags,
                                     UserProfile, &UserProfileLength, 0);
   if (Debug)
      fprintf (stdout, "sys$create_user_profile() %%X%08.08X\n", status);

   if (VMSnok (status))
   {
      if (VMSnok (SetPrvStatus = sys$setprv (0, &SysPrvMask, 0, 0)))
         exit (SetPrvStatus);
      return (status);
   }

   FileNameDsc.dsc$a_pointer = FileName;
   FileNameDsc.dsc$w_length = strlen(FileName);

   UserProfileDsc.dsc$a_pointer = (char*)UserProfile;
   UserProfileDsc.dsc$w_length = UserProfileLength;

   if (Access == READ_ACCESS)
      status = sys$check_access (0, &FileNameDsc, 0, &ArmReadAccess,
                                 0, &ClassNameDsc, 0, &UserProfileDsc);
   else
   if (Access == WRITE_ACCESS)
      status = sys$check_access (0, &FileNameDsc, 0, &ArmWriteAccess,
                                 0, &ClassNameDsc, 0, &UserProfileDsc);
   else
   {
      if (VMSnok (SetPrvStatus = sys$setprv (0, &SysPrvMask, 0, 0)))
         exit (SetPrvStatus);
      return (SS$_BADPARAM);
   }
   if (Debug) fprintf (stdout, "sys$check_access() %%X%08.08X\n", status);

   if (VMSnok (SetPrvStatus = sys$setprv (0, &SysPrvMask, 0, 0)))
      exit (SetPrvStatus);

   return (status);
}

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

