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


This module provides functions related to the authentication of usernames via
access to the SYSUAF and the hashed password and other entry flags, etc.  It
also processes VMS identifiers used for controlling access.  Functions create
and validate access against WASD's VMS user profile capability.  Other
functionality includes checking access to files, etc. via these for the server
as well as users holding profiles.

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


VERSION HISTORY
---------------
18-MAR-2004  MGD  rework $GETUAI() during ACME development,
                  add check for pre-expired passwords
20-NOV-2003  MGD  only check secondary password expiry date/time
                  if the secondary password hash is not empty
24-JUL-2003  MGD  remote username already massaged by AuthorizeRealm()
03-MAY-2003  MGD  /SYSUAF=(VMS,ID) accomodated in AuthVmsVerifyUser()
30-JAN-2003  MGD  authentication profile can be requested via rule,
                  refine checks in AuthVmsCheckUserAccess() and
                  AuthVmsCheckWriteAccess()
21-DEC-2002  MGD  AuthVmsLoadIdentifiers() more flexible
05-OCT-2002  MGD  allow wildcard specifications in AuthVmsCheckUserAccess()
06-SEP-2002  MGD  AuthVmsCheckUserAccess() traps SS$_NOCALLPRIV returning
                  SS$_NOPRIV to allow directory listings of DFS volumes
13-MAY-2002  MGD  SYSUAF expired password redirection ([AuthSysUafPwdExpUrl])
07-MAY-2002  MGD  bugfix; account/password expiry
27-APR-2002  MGD  use sys$setprv()
20-NOV-2001  MGD  bugfix; /RELAXED should allow all but DISUSERed accounts
                  to authenticate regardless of RESTRICTED or CAPTIVE flags
04-AUG-2001  MGD  support module WATCHing
05-APR-2001  MGD  bugfix; AuthVmsCheckUserAccess() return SS$_NOPRIV
26-MAR-2001  MGD  bugfix; sys$create_user_profile() length size from word
                  (System Services Manual) to unsigned int (startlet.h)!
23-FEB-2001  MGD  bugfix; identifier check OK should continue to load details
27-MAR-2000  MGD  bugfix; AuthVmsCheckIdentifier() error return changed from
                  AUTH_DENIED_BY_OTHER to AUTH_DENIED_BY_LOGIN
20-NOV-1999  MGD  add nil-access identifier to bypass hour restrictions
02-OCT-1999  MGD  VMS user profile WATCH enhancements
                  bugfix; AuthCreateVmsUserProfile()
28-AUG-1999  MGD  unbundled from AUTH.C for v6.1
*/
/*****************************************************************************/

#ifdef WASD_VMS_V6
#undef _VMS_V6_SOURCE
#define _VMS_V6_SOURCE
#undef __VMS_VER
#define __VMS_VER 60000000
#undef __CRTL_VER
#define __CRTL_VER 60000000
#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>
/* not defined in VAX C 3.2 <uaidef.h> */
#define UAI$M_RESTRICTED 0x8
#define UAI$C_PURDY_S 3

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

#define WASD_MODULE "AUTHVMS"

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

unsigned int  HttpdUserProfileLength;
unsigned char  *HttpdUserProfilePtr;
$DESCRIPTOR (HttpdUserProfileDsc, "");

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

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

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

extern BOOL  InstanceMutexHeld[];

extern int  ServerPort;

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

extern unsigned long  HttpdBinTime[],
                      SysPrvMask[];

extern char  ErrorSanityCheck[],
             ServerHostName[],
             ServerHostPort[],
             SoftwareID[],
             Utility[];

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

/*****************************************************************************/
/*
Get the specified username's flags, authorized privileges, quadword password,
hash salt and encryption algorithm from SYSUAF into an allocated SYSUAF
authentication data buffer.
*/ 

int AuthVmsGetUai
(
REQUEST_STRUCT *rqptr,
char *UserName
)
{
   static $DESCRIPTOR (UserNameDsc, "");

   static unsigned long  Context = -1;

   int  status;
   char  *cptr, *sptr, *zptr;
   AUTH_SYSUAF  *uafptr;
   VMS_ITEM_LIST3  *itmptr;

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

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_AUTH))
      WatchThis (rqptr, FI_LI, WATCH_MOD_AUTH,
                 "AuthVmsGetUai() !&Z", UserName);

   /* double-check! */
   if (!AuthSysUafEnabled) return (AUTH_DENIED_BY_LOGIN);

   uafptr = rqptr->rqAuth.SysUafDataPtr =
      (AUTH_SYSUAF*)VmGetHeap (rqptr, sizeof(AUTH_SYSUAF));

   zptr = (sptr = uafptr->UserName) + sizeof(uafptr->UserName)-1;
   for (cptr = UserName; *cptr && sptr < zptr; *sptr++ = toupper(*cptr++));
   *sptr = '\0';
   uafptr->UserNameLength = sptr - uafptr->UserName;

   itmptr = &uafptr->UaiItemList;

   itmptr->buf_len = sizeof(uafptr->UaiUic);
   itmptr->item = UAI$_UIC;
   itmptr->buf_addr = &uafptr->UaiUic;
   itmptr->ret_len = 0;
   itmptr++;

   itmptr->buf_len = sizeof(uafptr->UaiOwner)-1;
   itmptr->item = UAI$_OWNER;
   itmptr->buf_addr = &uafptr->UaiOwner;
   itmptr->ret_len = 0;
   itmptr++;

   itmptr->buf_len = sizeof(uafptr->UaiExpBinTime);
   itmptr->item = UAI$_EXPIRATION;
   itmptr->buf_addr = &uafptr->UaiExpBinTime;
   itmptr->ret_len = 0;
   itmptr++;

   itmptr->buf_len = sizeof(uafptr->UaiPwdDateBinTime);
   itmptr->item = UAI$_PWD_DATE;
   itmptr->buf_addr = &uafptr->UaiPwdDateBinTime;
   itmptr->ret_len = 0;
   itmptr++;

   itmptr->buf_len = sizeof(uafptr->UaiPwd2DateBinTime);
   itmptr->item = UAI$_PWD2_DATE;
   itmptr->buf_addr = &uafptr->UaiPwd2DateBinTime;
   itmptr->ret_len = 0;
   itmptr++;

   itmptr->buf_len = sizeof(uafptr->UaiPwdLifeBinTime);
   itmptr->item = UAI$_PWD_LIFETIME;
   itmptr->buf_addr = &uafptr->UaiPwdLifeBinTime;
   itmptr->ret_len = 0;
   itmptr++;

   itmptr->buf_len = sizeof(uafptr->UaiFlags);
   itmptr->item = UAI$_FLAGS;
   itmptr->buf_addr = &uafptr->UaiFlags;
   itmptr->ret_len = 0;
   itmptr++;

   itmptr->buf_len = sizeof(uafptr->UaiPriv);
   itmptr->item = UAI$_PRIV;
   itmptr->buf_addr = &uafptr->UaiPriv;
   itmptr->ret_len = 0;
   itmptr++;

   itmptr->buf_len = sizeof(uafptr->UaiPwd);
   itmptr->item = UAI$_PWD;
   itmptr->buf_addr = &uafptr->UaiPwd;
   itmptr->ret_len = 0;
   itmptr++;

   itmptr->buf_len = sizeof(uafptr->UaiPwd2);
   itmptr->item = UAI$_PWD2;
   itmptr->buf_addr = &uafptr->UaiPwd2;
   itmptr->ret_len = 0;
   itmptr++;

   itmptr->buf_len = sizeof(uafptr->UaiEncrypt);
   itmptr->item = UAI$_ENCRYPT;
   itmptr->buf_addr = &uafptr->UaiEncrypt;
   itmptr->ret_len = 0;
   itmptr++;

   itmptr->buf_len = sizeof(uafptr->UaiSalt);
   itmptr->item = UAI$_SALT;
   itmptr->buf_addr = &uafptr->UaiSalt;
   itmptr->ret_len = 0;
   itmptr++;

   itmptr->buf_len = sizeof(unsigned long);
   itmptr->item = UAI$_PRIMEDAYS;
   itmptr->buf_addr = &uafptr->UaiPrimeDays;
   itmptr->ret_len = 0;
   itmptr++;

   itmptr->buf_len = 3;
   itmptr->item = UAI$_NETWORK_ACCESS_P;
   itmptr->buf_addr = &uafptr->UaiNetworkAccessP;
   itmptr->ret_len = 0;
   itmptr++;

   itmptr->buf_len = 3;
   itmptr->item = UAI$_NETWORK_ACCESS_S;
   itmptr->buf_addr = &uafptr->UaiNetworkAccessS;
   itmptr->ret_len = 0;
   itmptr++;

   itmptr->buf_len = 3;
   itmptr->item = UAI$_REMOTE_ACCESS_P;
   itmptr->buf_addr = &uafptr->UaiRemoteAccessP;
   itmptr->ret_len = 0;
   itmptr++;

   itmptr->buf_len = 3;
   itmptr->item = UAI$_REMOTE_ACCESS_S;
   itmptr->buf_addr = &uafptr->UaiRemoteAccessS;
   itmptr->ret_len = 0;
   itmptr++;

   memset (itmptr, 0, sizeof(VMS_ITEM_LIST3));

   UserNameDsc.dsc$a_pointer = uafptr->UserName;
   UserNameDsc.dsc$w_length = uafptr->UserNameLength;

   /* turn on SYSPRV to allow access to SYSUAF records */
   sys$setprv (1, &SysPrvMask, 0, 0);
   status = sys$getuai (0, &Context, &UserNameDsc, &uafptr->UaiItemList,
                        0, 0, 0);
   sys$setprv (0, &SysPrvMask, 0, 0);

   if (VMSnok (status)) 
   {
      if (status == RMS$_RNF)
      {
         if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_AUTH))
            WatchThis (rqptr, FI_LI, WATCH_AUTH, "FAIL SYSUAF username");
         return (AUTH_DENIED_BY_LOGIN);
      }
      rqptr->rqResponse.ErrorTextPtr = MsgFor(rqptr,MSG_AUTH_DATABASE_VMS);
      ErrorVmsStatus (rqptr, status, FI_LI);
      return (status);
   }

   /* eliminate the counted string, strip trailing spaces */
   uafptr->UaiOwner[32] = '\0';
   for (cptr = (sptr = uafptr->UaiOwner) + 1; *cptr; *sptr++ = *cptr++);
   *sptr = '\0';
   if (sptr > uafptr->UaiOwner) sptr--;
   while (sptr > uafptr->UaiOwner && isspace(*sptr)) sptr--;
   if (!isspace(*sptr)) sptr++;
   *sptr = '\0';

   if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_AUTH))
   {
      WatchThis (rqptr, FI_LI, WATCH_AUTH,
                 "GETUAI \"!AZ\" !&S %!-!&M", uafptr->UserName, status);
      if (VMSok (status))
         WatchDataFormatted (
"expire:!&D pwd:!&D(!8XL!8XL) pwd2:!&D(!8XL!8XL) life:!&D\n\
flags:!8XL priv:<63-32>!8XL<31-00>!8XL\n\
prime:!8XL netp:!6XL nets:!6XL remp:!6XL rems:!6XL\n",
            &uafptr->UaiExpBinTime,
            &uafptr->UaiPwdDateBinTime,
            uafptr->UaiPwd[1], uafptr->UaiPwd[0],
            &uafptr->UaiPwd2DateBinTime,
            uafptr->UaiPwd2[1], uafptr->UaiPwd2[0], 
            &uafptr->UaiPwdLifeBinTime,
            &uafptr->UaiFlags,
            uafptr->UaiPriv[1], uafptr->UaiPriv[0],
            uafptr->UaiPrimeDays,
            uafptr->UaiNetworkAccessP, uafptr->UaiNetworkAccessS,
            uafptr->UaiRemoteAccessP, uafptr->UaiRemoteAccessS);
   }

   return (status);
}

/*****************************************************************************/
/*
Verify the request username/password hash against the SYSUAF password.  Check
if the primary password has expired and if it has the redirect to a configured
'change password' URL or deny access.
*/ 

int AuthVmsVerifyPassword (REQUEST_STRUCT* rqptr)

{
   static char  Password [AUTH_MAX_PASSWORD_LENGTH+1];
   static $DESCRIPTOR (PasswordDsc, Password);
   static $DESCRIPTOR (UserNameDsc, "");

   int  status;
   char  *cptr, *sptr, *zptr;
   AUTH_SYSUAF  *uafptr;

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

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_AUTH))
      WatchThis (rqptr, FI_LI, WATCH_MOD_AUTH,
                 "AuthVmsVerifyPassword() !&Z",
                 rqptr->rqAuth.SysUafDataPtr->UserName);

   if (!(uafptr = rqptr->rqAuth.SysUafDataPtr))
      ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI);

   /* flag that case-less username and password checks were performed */
   rqptr->rqAuth.CaseLess = true;

   UserNameDsc.dsc$a_pointer = uafptr->UserName;
   UserNameDsc.dsc$w_length = uafptr->UserNameLength;

   /* to uppercase! */
   zptr = (sptr = Password) + sizeof(Password)-1;
   for (cptr = rqptr->RemoteUserPassword;
        *cptr && sptr < zptr;
        *sptr++ = toupper(*cptr++));
   *sptr = '\0';
   PasswordDsc.dsc$w_length = sptr - Password;

   status = sys$hash_password (&PasswordDsc, uafptr->UaiEncrypt,
                               uafptr->UaiSalt, &UserNameDsc,
                               &uafptr->HashedPwd);
   if (VMSnok (status))
   {
      rqptr->rqResponse.ErrorTextPtr = MsgFor(rqptr,MSG_AUTH_USER);
      ErrorVmsStatus (rqptr, status, FI_LI);
      return (status);
   }

   if (uafptr->HashedPwd[0] != uafptr->UaiPwd[0] ||
       uafptr->HashedPwd[1] != uafptr->UaiPwd[1])
   {
      if (!AuthSysUafPromiscuous)
      {
         if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_AUTH))
            WatchThis (rqptr, FI_LI, WATCH_AUTH, "FAIL SYSUAF password");
         return (AUTH_DENIED_BY_LOGIN);
      }
   }

   /* assumes flag has been set by a prior call to AuthVmsVerifyUser() */
   if (rqptr->rqAuth.SysUafPwdExpired &&
       !strsame (rqptr->rqHeader.RequestUriPtr,
                 INTERNAL_PASSWORD_CHANGE,
                 sizeof(INTERNAL_PASSWORD_CHANGE)-1))
   {
      /* password has expired (and this is not the 'change' path) */
      if (rqptr->rqPathSet.AuthSysUafPwdExpUrlPtr)
      {
         rqptr->rqResponse.LocationPtr =
            rqptr->rqPathSet.AuthSysUafPwdExpUrlPtr;
         status = AUTH_DENIED_BY_REDIRECT;
      }
      else
      if (Config.cfAuth.SysUafPwdExpUrl[0])
      {
         rqptr->rqResponse.LocationPtr = Config.cfAuth.SysUafPwdExpUrl;
         status = AUTH_DENIED_BY_REDIRECT;
      }
      else
         status = AUTH_DENIED_BY_LOGIN;
   }

   return (status);
}

/*****************************************************************************/
/*
Verify the SYSUAF-authenticated request username conforms to other SYSUAF
requirements, such as password not expired, rights identifier authentication
control, etc.  Assumes the username/password has already, or will be, validated
against the SYSUAF in some manner, either directly as occurs here in AUTHVMS.C
or via the ACME as occurs in AUTHACME.C.  Some things checked during ACME
authentication (such as password expiry, prime and secondary days) are redone
here.  This function additionally checks for privleged accounts and rights
identifier requirements.  I do not intend to further granulate this
functionality and so any redundancy will be lived with.

Returns a success status if user password authenticated,
AUTH_DENIED_BY_LOGIN if not authenticated, or other error status if a
genuine VMS error occurs (which should be reported where and when it occurs).
*/ 

int AuthVmsVerifyUser (REQUEST_STRUCT* rqptr)

{
   /* UAI flags that disallow an identifier-controlled account access */
   static unsigned long  DisallowFlags =
          UAI$M_DISACNT | UAI$M_PWD_EXPIRED | UAI$M_PWD2_EXPIRED;

   /* 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 $DESCRIPTOR (UserNameDsc, "");

   static unsigned long  Context = -1;

   BOOL  AlreadyLocked;
   int  status,
        LockStatus;
   unsigned long  PwdExpBinTime [2],
                  ScratchBinTime [2];
   char  *cptr, *sptr, *zptr;
   AUTH_CREC  *acrptr;
   AUTH_SYSUAF  *uafptr;
   VMS_ITEM_LIST3  *itmptr;

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

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_AUTH))
   {
      WatchThis (rqptr, FI_LI, WATCH_MOD_AUTH,
                 "AuthVmsVerifyUser() !&Z",
                 rqptr->rqAuth.SysUafDataPtr->UserName);
   }

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

   status = AuthCacheFindRecord (rqptr->rqAuth.CacheSearchRecordPtr, &acrptr);
   if (VMSnok (status))
   {
      /* something has happened to the cache record in the meantime! */
      if (AlreadyLocked) return (AUTH_DENIED_BY_OTHER);
      InstanceMutexUnLock (INSTANCE_MUTEX_AUTH_CACHE);
      return (AUTH_DENIED_BY_OTHER);
   }

   if (!AlreadyLocked) InstanceMutexUnLock (INSTANCE_MUTEX_AUTH_CACHE);

   if (!(uafptr = rqptr->rqAuth.SysUafDataPtr))
      ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI);

   /* copy these data to the authorization cache record */
   acrptr->UaiPrimeDays = uafptr->UaiPrimeDays;
   acrptr->UaiNetworkAccessP = uafptr->UaiNetworkAccessP;
   acrptr->UaiNetworkAccessS = uafptr->UaiNetworkAccessS;
   acrptr->UaiRemoteAccessP = uafptr->UaiRemoteAccessP;
   acrptr->UaiRemoteAccessS = uafptr->UaiRemoteAccessS;

   /*******************************/
   /* check flags and expirations */
   /*******************************/

   /* automatically disallow if any of these flags are set! */
   if ((AuthPolicySysUafIdentifiers && (uafptr->UaiFlags & DisallowFlags)) ||
       (AuthPolicySysUafWasdIdentifiers && (uafptr->UaiFlags & DisallowFlags)) ||
       (AuthPolicySysUafRelaxed && (uafptr->UaiFlags & DisallowFlags) ||
       (AuthPolicySysUafVms && !AuthPolicySysUafRelaxed &&
        (uafptr->UaiFlags & DisallowVmsFlags)) ||
       (!AuthPolicySysUafIdentifiers && !AuthPolicySysUafRelaxed &&
        (uafptr->UaiFlags & DisallowVmsFlags))))
   {
      if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_AUTH))
         WatchThis (rqptr, FI_LI, WATCH_AUTH, "FAIL SYSUAF flags");
      return (AUTH_DENIED_BY_LOGIN);
   }

   if (uafptr->UaiExpBinTime[0] || uafptr->UaiExpBinTime[1])
   {
      /* check account expiration */
      status = lib$sub_times (&HttpdBinTime, &uafptr->UaiExpBinTime,
                              &ScratchBinTime);
      if (status != LIB$_NEGTIM)
      {
         if (VMSnok (status))
         {
            ErrorNoticed (status, "lib$sub_times()", FI_LI);
            return (status);
         }
         /* current time > account expiry time */
         if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_AUTH))
            WatchThis (rqptr, FI_LI, WATCH_AUTH, "FAIL SYSUAF account expired");
         return (AUTH_DENIED_BY_LOGIN);
      }
   }

   rqptr->rqAuth.SysUafPwdExpired = false;
   if (!Config.cfAuth.SysUafAcceptExpPwd &&
       uafptr->UaiPwdDateBinTime[0] == 0xffffffff &&
       uafptr->UaiPwdDateBinTime[1] == 0xffffffff)
   {
      if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_AUTH))
         WatchThis (rqptr, FI_LI, WATCH_AUTH, "FAIL SYSUAF pwd pre-expired");
      rqptr->rqAuth.SysUafPwdExpired = true;
   }
   else
   if (!Config.cfAuth.SysUafAcceptExpPwd &&
       (uafptr->UaiPwdDateBinTime[0] || uafptr->UaiPwdDateBinTime[1]) &&
       (uafptr->UaiPwdLifeBinTime[0] || uafptr->UaiPwdLifeBinTime[1]))
   {
      /* check password expiration */
      status = lib$add_times (&uafptr->UaiPwdLifeBinTime,
                              &uafptr->UaiPwdDateBinTime,
                              &PwdExpBinTime);
      if (VMSnok (status))
      {
         ErrorNoticed (status, "lib$add_times()", FI_LI);
         return (status);
      }
      status = lib$sub_times (&HttpdBinTime, &PwdExpBinTime, &ScratchBinTime);
      if (status != LIB$_NEGTIM)
      {
         if (VMSnok (status))
         {
            ErrorNoticed (status, "lib$sub_times()", FI_LI);
            return (status);
         }
         /* current time > password expiry time */
         rqptr->rqAuth.NoCache = true;
         if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_AUTH))
            WatchThis (rqptr, FI_LI, WATCH_AUTH, "FAIL SYSUAF pwd expired");
         /* only after the password is confirmed OK *then* do any redirect */
         rqptr->rqAuth.SysUafPwdExpired = true;
      }
   }

   if (!Config.cfAuth.SysUafAcceptExpPwd &&
       uafptr->UaiPwd2DateBinTime[0] == 0xffffffff &&
       uafptr->UaiPwd2DateBinTime[1] == 0xffffffff)
   {
      if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_AUTH))
         WatchThis (rqptr, FI_LI, WATCH_AUTH, "FAIL SYSUAF pwd2 pre-expired");
      return (AUTH_DENIED_BY_LOGIN);
   }
   else
   if (!Config.cfAuth.SysUafAcceptExpPwd &&
       (uafptr->UaiPwd2[0] || uafptr->UaiPwd2[1]) &&
       (uafptr->UaiPwd2DateBinTime[0] || uafptr->UaiPwd2DateBinTime[1]) &&
       (uafptr->UaiPwdLifeBinTime[0] || uafptr->UaiPwdLifeBinTime[1]))
   {
      /* check password 2 expiration */
      status = lib$add_times (&uafptr->UaiPwdLifeBinTime,
                              &uafptr->UaiPwd2DateBinTime,
                              &PwdExpBinTime);
      if (VMSnok (status))
      {
         ErrorNoticed (status, "lib$add_times()", FI_LI);
         return (status);
      }
      status = lib$sub_times (&HttpdBinTime, &PwdExpBinTime,
                              &ScratchBinTime);
      if (status != LIB$_NEGTIM)
      {
         if (VMSnok (status))
         {
            ErrorNoticed (status, "lib$sub_times()", FI_LI);
            return (status);
         }
         /* current time > password expiry time */
         if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_AUTH))
            WatchThis (rqptr, FI_LI, WATCH_AUTH, "FAIL SYSUAF pwd2 expired");
         return (AUTH_DENIED_BY_LOGIN);
      }
   }

   /*******************************/
   /* identifier/privilege checks */
   /*******************************/

   if (AuthPolicySysUafIdentifiers)
   {
      /* check SYSUAF-authenticated account holds the correct identifier */
      if (VMSnok (status = AuthVmsLoadIdentifiers (rqptr, uafptr->UaiUic)))
         return (status);
      if (VMSnok (status = AuthVmsCheckIdentifier (rqptr)))
         return (status);
   }

   if (!AuthPolicySysUafRelaxed &&
       rqptr->rqAuth.SourceRealm == AUTH_SOURCE_VMS &&
       (uafptr->UaiPriv[0] & PRV$M_SYSPRV))
   {
      /* not allowing all accounts, exclude those with extended privileges */
      if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_AUTH))
         WatchThis (rqptr, FI_LI, WATCH_AUTH, "FAIL SYSUAF privileges");
      return (AUTH_DENIED_BY_LOGIN);
   }

   rqptr->rqAuth.UserDetailsLength = strlen(uafptr->UaiOwner);
   rqptr->rqAuth.UserDetailsPtr = cptr =
      VmGetHeap (rqptr, rqptr->rqAuth.UserDetailsLength+1);
   strcpy (cptr, uafptr->UaiOwner);

   return (SS$_NORMAL);
}

/****************************************************************************/
/*
SYSUAF authentication is being controlled by possession of an identifier ...
got the identifier you can be SYSUAF-authenticated, haven't then you cant! See
description at beginning of module.  Returns normal status if account possesses
suitable identifier, AUTH_DENIED_BY_LOGIN if it doesn't, or error status.
*/ 

AuthVmsCheckIdentifier (REQUEST_STRUCT *rqptr)

{
   BOOL  HoldsRealmId,
         HoldsWasdReadId,
         HoldsWasdWriteId;
   unsigned long  ThisId;
   unsigned long  *idptr;

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

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_AUTH))
      WatchThis (rqptr, FI_LI, WATCH_MOD_AUTH, "AuthVmsCheckIdentifier()");

   if (!rqptr->rqAuth.VmsIdentifiersPtr)
      ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI);

   HoldsRealmId = HoldsWasdReadId = HoldsWasdWriteId = false;

   for (idptr = rqptr->rqAuth.VmsIdentifiersPtr; ThisId = *idptr; idptr++)
   {
      if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_AUTH))
         WatchThis (rqptr, FI_LI, WATCH_MOD_AUTH,
                    "ID !&S !%I", ThisId, ThisId);

      if (rqptr->rqAuth.SourceRealm == AUTH_SOURCE_ID &&
          ThisId == rqptr->rqAuth.RealmVmsIdentifier)
      {                     
         HoldsRealmId = true;

         if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_AUTH))
            WatchThis (rqptr, FI_LI, WATCH_AUTH,
                       "HOLDS identifier !AZ", rqptr->rqAuth.RealmPtr);

         continue;
      }

      if (rqptr->rqAuth.SourceRealm == AUTH_SOURCE_WASD_ID)
      {
         if (ThisId == AuthWasdReadVmsIdentifier)
         {
            HoldsWasdReadId = true;

            if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_AUTH))
               WatchThis (rqptr, FI_LI, WATCH_AUTH,
                          "HOLDS identifier !AZ", AUTH_WASD_READ_VMS_ID);

            continue;
         }

         if (ThisId == AuthWasdWriteVmsIdentifier)
         {                     
            HoldsWasdWriteId = true;

            if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_AUTH))
               WatchThis (rqptr, FI_LI, WATCH_AUTH,
                          "HOLDS identifier !AZ", AUTH_WASD_WRITE_VMS_ID);

            continue;
         }
      }

      if (ThisId == AuthHttpsOnlyVmsIdentifier)
      {
         /* account is only allowed to authenticate via SSL */
         rqptr->rqAuth.HttpsOnly = true;

         if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_AUTH))
            WatchThis (rqptr, FI_LI, WATCH_AUTH,
                       "HOLDS identifier !AZ", AUTH_HTTPS_ONLY_VMS_ID);

         continue;
      }

      if (ThisId == AuthNilAccessVmsIdentifier)
      {
         /* account may always authenticate even if hours seem to prohibit */
         rqptr->rqAuth.SysUafNilAccess = true;

         if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_AUTH))
            WatchThis (rqptr, FI_LI, WATCH_AUTH,
                       "HOLDS identifier !AZ", AUTH_NIL_ACCESS_VMS_ID);

         continue;
      }

      if (ThisId == AuthPasswordChangeVmsIdentifier)
      {
         /* account is allowed to change it's password via the server */
         rqptr->rqAuth.SysUafCanChangePwd = true;

         if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_AUTH))
            WatchThis (rqptr, FI_LI, WATCH_AUTH,
                       "HOLDS identifier !AZ", AUTH_PASSWORD_CHANGE_VMS_ID);

         continue;
      }

      if (ThisId == AuthWasdHttpsVmsIdentifier)
      {
         /* account is only allowed to authenticate via SSL */
         rqptr->rqAuth.HttpsOnly = true;

         if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_AUTH))
            WatchThis (rqptr, FI_LI, WATCH_AUTH,
                       "HOLDS identifier !AZ", AUTH_WASD_HTTPS_VMS_ID);

         continue;
      }

      if (ThisId == AuthWasdPwdVmsIdentifier)
      {
         /* account is allowed to change it's password via the server */
         rqptr->rqAuth.SysUafCanChangePwd = true;

         if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_AUTH))
            WatchThis (rqptr, FI_LI, WATCH_AUTH,
                       "HOLDS identifier !AZ", AUTH_WASD_PWD_VMS_ID);

         continue;
      }
   }

   /************************/
   /* check authentication */
   /************************/

   if (rqptr->rqAuth.SourceRealm == AUTH_SOURCE_ID &&
       !HoldsRealmId)
   {
      /* authentication is by local identifier ... and doesn't have it */
      if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_AUTH))
         WatchThis (rqptr, FI_LI, WATCH_AUTH, "FAIL SYSUAF access identifier");
      return (AUTH_DENIED_BY_LOGIN);
   }

   if (rqptr->rqAuth.SourceRealm == AUTH_SOURCE_WASD_ID &&
       !HoldsWasdWriteId &&
       !HoldsWasdReadId)
   {
      /* authentication only with WASD identifier .. and doesn't have it */
      if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_AUTH))
         WatchThis (rqptr, FI_LI, WATCH_AUTH, "FAIL SYSUAF access identifier");
      return (AUTH_DENIED_BY_LOGIN);
   }

   return (SS$_NORMAL);
}

/****************************************************************************/
/*
Allocate dynamic memory to contain a zero-terminated vector of rights
identifiers held by the specified UIC.  Read those identifiers.  Restart if
insufficient space, increasing buffer space by a factor of two, until the
number of identifiers becomes rediculous.
*/ 

AuthVmsLoadIdentifiers
(
REQUEST_STRUCT *rqptr,
unsigned long  UaiUic
)
{
   static struct {
      unsigned long  Uic;
      unsigned long  Unused;
   } Holder = { 0, 0 };

   int  status,
        idcnt,
        idalloc;
   unsigned long  Ctx,
                  ThisId;
   unsigned long  *idptr;

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

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_AUTH))
      WatchThis (rqptr, FI_LI, WATCH_MOD_AUTH, "AuthVmsLoadIdentifiers()");

   /* if already loaded then just continue */
   if (rqptr->rqAuth.VmsIdentifiersPtr) return (SS$_NORMAL);

   idalloc = AUTH_MIN_VMS_IDENTIFIERS;
  
   while (idalloc < AUTH_MAX_VMS_IDENTIFIERS)
   {
      /* plus one longword as a sentinal (will contain zero) */
      rqptr->rqAuth.VmsIdentifiersPtr = idptr =
         (unsigned long*)VmGetHeap (rqptr, (idalloc+1)*sizeof(unsigned long));

      Holder.Uic = UaiUic;

      rqptr->rqAuth.VmsIdentifiersCount = 0;
      idcnt = idalloc;

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

      Ctx = 0;
      while (VMSok (status = sys$find_held (&Holder, &ThisId, 0, &Ctx)))
      {
         if (--idcnt)
         {
            *idptr++ = ThisId;
            rqptr->rqAuth.VmsIdentifiersCount++;
            continue;
         }
         status = SS$_BUFFEROVF;
         VmFreeFromHeap (rqptr, rqptr->rqAuth.VmsIdentifiersPtr, FI_LI); 
         idptr = NULL;
         idalloc *= 2;
         sys$finish_rdb (&Ctx);
         break;
      }
      /* sentinal (not really necessary, but let's be over-cautious) */
      if (idptr) *idptr = 0;

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

      /* if we got to the end of the identifiers without mishap */
      if (status == SS$_NOSUCHID) return (SS$_NORMAL);
   }

   /*********/
   /* error */
   /*********/

   sys$finish_rdb (&Ctx);
   rqptr->rqAuth.VmsIdentifiersPtr = NULL;
   rqptr->rqResponse.ErrorTextPtr = MsgFor(rqptr,MSG_AUTH_DATABASE_VMS);
   ErrorVmsStatus (rqptr, status, FI_LI);
   return (status);
}

/****************************************************************************/
/*
Look through the request's array of a VMS authenticated user's identifiers for
the specified one.  Return zero to indicate the identifier was not held, normal
to indicate it was, or any other error status.
*/ 

int AuthVmsHoldsIdentifier
(
REQUEST_STRUCT *rqptr,
char *IdentifierName,
unsigned long ThisId
)
{
   unsigned long  *idptr;

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

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_AUTH))
      WatchThis (rqptr, FI_LI, WATCH_MOD_AUTH,
                 "AuthVmsHoldsIdentifier() !&Z !&X", IdentifierName, ThisId);

   /* if not already loaded via SYSUAF authentication then really an error */
   if (!rqptr->rqAuth.VmsIdentifiersPtr)
   {
      /* has NOT been authenticated via the SYSUAF (shouldn't happen!) */

      if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_AUTH))
         WatchThis (rqptr, FI_LI, WATCH_AUTH,
                    "IDENTIFIER request not SYSUAF authenticated");

      rqptr->rqResponse.HttpStatus = 403;
      ErrorGeneral (rqptr, MsgFor(rqptr,MSG_AUTH_ACCESS_DENIED), FI_NOLI);

      return (SS$_ABORT);
   }

   if (!ThisId) return (false);

   for (idptr = rqptr->rqAuth.VmsIdentifiersPtr;
        *idptr && *idptr != ThisId;
        idptr++)
   {
      if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_AUTH))
         WatchThis (rqptr, FI_LI, WATCH_MOD_AUTH,
                    "ID !&S !%I", *idptr, *idptr);
   }

   if (*idptr)
   {
      if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_AUTH))
         WatchThis (rqptr, FI_LI, WATCH_AUTH,
                    "HOLDS identifier !AZ", IdentifierName);

      return (SS$_NORMAL);
   }

   return (0);
}

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

AuthVmsHoldsWasdGroupIdent
(
REQUEST_STRUCT *rqptr,
char *GroupName
)
{
   static char GroupIdName [AUTH_MAX_REALM_GROUP_LENGTH+1] =
                           AUTH_WASD__GROUP_VMS_ID;
   static $DESCRIPTOR (GroupIdNameDsc, GroupIdName);

   int  status;
   unsigned long  ThisId;
   char  *cptr, *sptr, *zptr;

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

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_AUTH))
      WatchThis (rqptr, FI_LI, WATCH_MOD_AUTH,
                 "AuthVmsHoldsWasdGroupIdent() !&Z", GroupName);

   zptr = (sptr = GroupIdName) + sizeof(GroupIdName);
   for (cptr = GroupName; *cptr && sptr < zptr; *sptr++ = *cptr++);
   if (sptr >= zptr)
   {
      ErrorGeneral (rqptr, MsgFor(rqptr,MSG_GENERAL_OVERFLOW), FI_LI);
      return (SS$_ABORT);
   }
   *sptr = '\0';
   GroupIdNameDsc.dsc$w_length = sptr - GroupIdName;
   if (VMSnok (status = sys$asctoid (&GroupIdNameDsc, &ThisId, 0)))
   {
      rqptr->rqResponse.ErrorTextPtr = GroupIdName;
      ErrorVmsStatus (rqptr, status, FI_LI);
      return (status);
   }

   return (AuthVmsHoldsIdentifier (rqptr, GroupIdName, ThisId));
}

/*****************************************************************************/
/*
Using sys$create_user_profile() create a permanent security profile of the
SYSUAF-authenticated user name.  This will be stored in the cache and attached
to the request structure each time the user is re-authenticated from it.  This
profile will be used to check file/directory (see AuthVmsCheckUserAccess())
access permission against the authenticated user not the server account.
*/

int AuthVmsCreateUserProfile (REQUEST_STRUCT* rqptr)

{
   static unsigned long  Context = -1;
   static unsigned char  *UserProfilePtr;
   static $DESCRIPTOR (UserNameDsc, "");

   int  status;
   unsigned int  UserProfileLength;

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

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_AUTH))
      WatchThis (rqptr, FI_LI, WATCH_MOD_AUTH,
                 "AuthVmsCreateUserProfile() !&Z", rqptr->RemoteUser);

   if (!UserProfilePtr) UserProfilePtr = VmGet (AUTH_CREATE_USER_PROFILE_SIZE);
   UserProfileLength = AUTH_CREATE_USER_PROFILE_SIZE;

   rqptr->rqAuth.VmsUserProfilePtr = NULL;
   rqptr->rqAuth.VmsUserProfileLength = 0;

   UserNameDsc.dsc$a_pointer = rqptr->RemoteUser;
   UserNameDsc.dsc$w_length = rqptr->RemoteUserLength;

   /* turn on SYSPRV to allow this to be done */
   sys$setprv (1, &SysPrvMask, 0, 0);
   status = sys$create_user_profile (&UserNameDsc, 0, 0, UserProfilePtr,
                                     &UserProfileLength, &Context);
   sys$setprv (0, &SysPrvMask, 0, 0);

   if (VMSnok (status)) 
   {
      rqptr->rqResponse.ErrorTextPtr = "sys$create_user_profile()";
      ErrorVmsStatus (rqptr, status, FI_LI);
      return (status);
   }

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_AUTH))
   {
      WatchThis (rqptr, FI_LI, WATCH_MOD_AUTH, "PROFILE !&Z !UL",
                 rqptr->RemoteUser, UserProfileLength);
      WatchDataDump (UserProfilePtr, UserProfileLength);
   }

   rqptr->rqAuth.VmsUserProfilePtr =
      VmGetHeap (rqptr, rqptr->rqAuth.VmsUserProfileLength = UserProfileLength);
   memcpy (rqptr->rqAuth.VmsUserProfilePtr, UserProfilePtr, UserProfileLength);
      
   return (SS$_NORMAL);
}

/*****************************************************************************/
/*
Using sys$create_user_profile() create a permanent security profile of the
HTTPd server account.  Returns no status, exits server if error encountered.
*/ 
 
AuthVmsCreateHttpdProfile ()

{
   static unsigned long  Flags = 0;

   int  status;
   char  HttpdUserProfile [512];
   $DESCRIPTOR (HttpdUserNameDsc, "");

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

   if (WATCH_MODULE(WATCH_MOD_AUTH))
      WatchThis (NULL, FI_LI, WATCH_MOD_AUTH, "AuthVmsCreateHttpdProfile()");

   if (HttpdUserProfileLength) return;

   HttpdUserNameDsc.dsc$a_pointer = HttpdProcess.UserName;
   HttpdUserNameDsc.dsc$w_length = strlen(HttpdProcess.UserName);

   status = sys$create_user_profile (&HttpdUserNameDsc, 0, Flags,
                                     HttpdUserProfile,
                                     &HttpdUserProfileLength, 0);
   if (VMSnok (status))
      ErrorExitVmsStatus (status, "sys$create_user_profile()", FI_LI);

   if (WATCH_MODULE(WATCH_MOD_AUTH))
   {
      WatchThis (NULL, FI_LI, WATCH_MOD_AUTH, "PROFILE !&Z !UL",
                 HttpdProcess.UserName, HttpdUserProfileLength);
      WatchDataDump (HttpdUserProfile, HttpdUserProfileLength);
   }

   HttpdUserProfilePtr = VmGet (HttpdUserProfileLength);
   memcpy (HttpdUserProfilePtr, HttpdUserProfile, HttpdUserProfileLength);
   HttpdUserProfileDsc.dsc$a_pointer = HttpdUserProfilePtr;
   HttpdUserProfileDsc.dsc$w_length = HttpdUserProfileLength;
}

/*****************************************************************************/
/*
Using sys$check_access() check file read 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.
Returns SS$_NORMAL if access allowed, SS$_NOPRIV if denied, and other RMS
status codes.
*/

int AuthVmsCheckUserAccess
(
REQUEST_STRUCT* rqptr,
char *FileName,
int FileNameLength
)
{
   static unsigned long  Flags = 0,
                         ArmReadAccess = ARM$M_READ;
   static $DESCRIPTOR (ClassNameDsc, "FILE");
   static $DESCRIPTOR (UserProfileDsc, "");
   static VMS_ITEM_LIST3  ReadItem [] = 
   {
      { sizeof(ArmReadAccess), CHP$_ACCESS, &ArmReadAccess, 0 },
      { 0, 0, 0, 0 }
   };

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

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

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_AUTH))
      WatchThis (rqptr, FI_LI, WATCH_MOD_AUTH,
                 "AuthVmsCheckUserAccess() !&Z !&Z !UL !&X !&B",
                  rqptr->RemoteUser, FileName,
                  rqptr->rqAuth.VmsUserProfileLength,
                  rqptr->rqAuth.VmsUserProfilePtr,
                  rqptr->rqPathSet.NoProfile);

   if (FileNameLength <= 0) FileNameLength = strlen(FileName);

   for (cptr = FileName + FileNameLength;
        cptr > FileName && *cptr != ']'  && *cptr != '*' && *cptr != '%';
        cptr--);
   if (cptr > FileName && *cptr != ']')
   {
      /* 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 */
   /****************/

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

   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 */
      if (!HttpdUserProfileLength) AuthVmsCreateHttpdProfile ();
      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, &ArmReadAccess,
                              0, &ClassNameDsc, 0, &UserProfileDsc);
   sys$setprv (0, &SysPrvMask, 0, 0);

   if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_AUTH))
      WatchThis (rqptr, FI_LI, WATCH_AUTH,
                 "ACCESS !AZ !AZ read !AZ !&S !&?YES\rNO\r",
                 ProfilePtr, WhoseProfilePtr, FileNameDsc.dsc$a_pointer,
                 status, VMSok(status));

   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);
}

/*****************************************************************************/
/*
Check that the HTTPd account has CONTROL or WRITE 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 can write into it. If there is WRITE
access then any authenticated user can write into it. Returns SS$_NORMAL if
access allowed, SS$_NOPRIV if denied, and other status codes.  Error report
should be generated by the calling routine.
*/ 
 
int AuthVmsCheckWriteAccess
(
REQUEST_STRUCT *rqptr,
char *FileName,
int FileNameLength
)
{
   static unsigned long  Flags = 0;
   static unsigned long  ArmWriteAccess = ARM$M_WRITE,
                         ArmControlAccess = ARM$M_CONTROL;

   static VMS_ITEM_LIST3 WriteAccessItem [] =
   { 
      { sizeof(ArmWriteAccess), CHP$_ACCESS, &ArmWriteAccess, 0 },
      { 0, 0, 0, 0 }
   };
   static VMS_ITEM_LIST3 ControlAccessItem [] =
   {
      { sizeof(ArmControlAccess), CHP$_ACCESS, &ArmControlAccess, 0 },
      { 0, 0, 0, 0 }
   };

   static $DESCRIPTOR (ClassNameDsc, "FILE");
   static $DESCRIPTOR (ObjectNameDsc, "");
   static $DESCRIPTOR (UserProfileDsc, "");

   BOOL  VmsUserProfile;
   int  status,
        DirectoryFileLength;
   char  DirectoryFile [ODS_MAX_FILE_NAME_LENGTH+1];

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

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_AUTH))
      WatchThis (rqptr, FI_LI, WATCH_MOD_AUTH,
                 "AuthVmsCheckWriteAccess() !&Z", FileName);

   if (!HttpdUserProfileLength) AuthVmsCreateHttpdProfile ();

   if (FileNameLength <= 0) FileNameLength = strlen(FileName);

   OdsNameOfDirectoryFile (FileName, FileNameLength,
                           DirectoryFile, &DirectoryFileLength);

   ObjectNameDsc.dsc$a_pointer = DirectoryFile;
   ObjectNameDsc.dsc$w_length = DirectoryFileLength;

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

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

   if (VmsUserProfile)
   {
      /****************************/
      /* check for control access */
      /****************************/

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

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

      if (VMSok (status))
      {
         /* now check if the authenticated VMS user has write access */
         UserProfileDsc.dsc$a_pointer = rqptr->rqAuth.VmsUserProfilePtr;
         UserProfileDsc.dsc$w_length = rqptr->rqAuth.VmsUserProfileLength;

         status = sys$check_access (0, &ObjectNameDsc, 0, &WriteAccessItem,
                                    0, &ClassNameDsc, 0, &UserProfileDsc);

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

         if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_AUTH))
            WatchThis (rqptr, FI_LI, WATCH_AUTH,
                       "ACCESS (using profile) !AZ write !AZ !&S !&?YES\rNO\r",
                       rqptr->RemoteUser, ObjectNameDsc.dsc$a_pointer,
                       status, VMSok(status));

         /* no privilege to write */
         if (status == SS$_NOPRIV) return (status);

         /* write access OK */
         if (VMSok (status)) return (status);

         /* if the directory file not exist then directory does not exist */
         if (status == RMS$_FNF) status = RMS$_DNF;

         return (status);
      }
      /* no control access, drop through to check for write access */
   }

   /**************************/
   /* check for write access */
   /**************************/

   status = sys$check_access (0, &ObjectNameDsc, 0, &WriteAccessItem,
                              0, &ClassNameDsc, 0, &HttpdUserProfileDsc);

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

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

   /* no privilege to write */
   if (status == SS$_NOPRIV) return (status);

   /* write access OK */
   if (VMSok (status)) return (status);

   /* if the directory file did not exist then directory does not exist */
   if (status == RMS$_FNF) status = RMS$_DNF;

   return (status);
}

/*****************************************************************************/
/*
Change the specified username's ('rqptr->RemoteUser') password in the SYSUAF. 
It assumes the calling routine HTAdminChangePassword() has performed required
preliminary sanity checks.  This function is just to perform the change.
*/ 
 
AuthVmsChangePassword
(
REQUEST_STRUCT *rqptr,
char *PasswordNew
)
{
   static unsigned long  Context = -1;
   static unsigned long  UaiPriv [2],
                         HashedPwd [2],
                         UaiPwd [2];
   static unsigned short  UaiSalt;
   static unsigned char  UaiEncrypt;
   static char  PasswordUpperCase [AUTH_MAX_PASSWORD_LENGTH+1],
                UserNameUpperCase [AUTH_MAX_USERNAME_LENGTH+1];
   static VMS_ITEM_LIST3 GetItems [] = 
   {
      { sizeof(UaiPwd), UAI$_PWD, &UaiPwd, 0 },
      { sizeof(UaiEncrypt), UAI$_ENCRYPT, &UaiEncrypt, 0 },
      { sizeof(UaiSalt), UAI$_SALT, &UaiSalt, 0 },
      { 0, 0, 0, 0 }
   };
   static VMS_ITEM_LIST3 SetItems [] = 
   {
      { sizeof(HashedPwd), UAI$_PWD, &HashedPwd, 0 },
      { 0, 0, 0, 0 } 
   };
   static $DESCRIPTOR (UserNameDsc, UserNameUpperCase);
   static $DESCRIPTOR (PasswordDsc, PasswordUpperCase);

   BOOL  AccessAllowed;
   int  status;
   char  *cptr, *sptr, *zptr;

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

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_AUTH))
      WatchThis (NULL, FI_LI, WATCH_MOD_AUTH, "AuthVmsChangePassword()");

   if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_RESPONSE))
      WatchThis (rqptr, FI_LI, WATCH_RESPONSE, "CHANGE SYSUAF password");

   if (!AuthSysUafEnabled)
      ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI);

   if (AuthPolicySysUafRelaxed)
      AccessAllowed = true;
   else
   {
      if ((rqptr->rqAuth.SourceRealm == AUTH_SOURCE_VMS &&
           !rqptr->rqAuth.SourceGroupWrite) ||
          (rqptr->rqAuth.SourceRealm == AUTH_SOURCE_VMS &&
           rqptr->rqAuth.SourceGroupWrite == AUTH_SOURCE_ID) ||
          (rqptr->rqAuth.SourceRealm == AUTH_SOURCE_ID &&
           rqptr->rqAuth.SourceGroupWrite == AUTH_SOURCE_ID)) 
         AccessAllowed = true;
      else
      if (rqptr->rqAuth.SourceRealm == AUTH_SOURCE_WASD_ID &&
          rqptr->rqAuth.SysUafCanChangePwd)
         AccessAllowed = true;
      else
         AccessAllowed = false;
   }

   if (!AccessAllowed)
   {
      rqptr->rqResponse.HttpStatus = 403;
      ErrorGeneral (rqptr, MsgFor(rqptr,MSG_AUTH_ACCESS_DENIED), FI_LI);
      HTAdminChangePasswordEnd (rqptr);
      return;
   }

   /* yet more checking! */
   zptr = (sptr = PasswordUpperCase) + sizeof(PasswordUpperCase)-1;
   cptr = PasswordNew; 
   while (*cptr && sptr < zptr)
   {
      if (!(isalnum(*cptr) || *cptr == '_' || *cptr == '$')) break;
      *sptr++ = toupper(*cptr++);
   }
   if (*cptr)
   {
      /* unacceptable character present */
      rqptr->rqResponse.HttpStatus = 403;
      ErrorGeneral (rqptr, MsgFor(rqptr,MSG_HTADMIN_PWD_ERROR), FI_LI);
      HTAdminChangePasswordEnd (rqptr);
      return;
   }
   *sptr = '\0';
   PasswordDsc.dsc$w_length = sptr - PasswordUpperCase;

   zptr = (sptr = UserNameUpperCase) + sizeof(UserNameUpperCase)-1;
   for (cptr = rqptr->RemoteUser;
        *cptr && sptr < zptr;
        *sptr++ = toupper(*cptr++));
   *sptr = '\0';
   UserNameDsc.dsc$w_length = sptr - UserNameUpperCase;

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

   UserNameDsc.dsc$w_length = rqptr->RemoteUserLength;
   status = sys$getuai (0, &Context, &UserNameDsc, &GetItems, 0, 0, 0);
   if (Debug) fprintf (stdout, "sys$getuai() %%X%08.08X\n", status);

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

   if (VMSnok (status)) 
   {
      rqptr->rqResponse.ErrorTextPtr = MsgFor(rqptr,MSG_AUTH_USER);
      ErrorVmsStatus (rqptr, status, FI_LI);                      
      HTAdminChangePasswordEnd (rqptr);
      return;
   }

   status = sys$hash_password (&PasswordDsc, UaiEncrypt,
                               UaiSalt, &UserNameDsc, &HashedPwd);
   if (Debug) fprintf (stdout, "sys$hash_password() %%X%08.08X\n", status);
   if (VMSnok (status))
   {
      rqptr->rqResponse.ErrorTextPtr = MsgFor(rqptr,MSG_AUTH_USER);
      ErrorVmsStatus (rqptr, status, FI_LI);
      HTAdminChangePasswordEnd (rqptr);
      return;
   }

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

   status = sys$setuai (0, 0, &UserNameDsc, &SetItems, 0, 0, 0);
   if (Debug) fprintf (stdout, "sys$setuai() %%X%08.08X\n", status);

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

   if (VMSnok (status)) 
   {
      rqptr->rqResponse.ErrorTextPtr = MsgFor(rqptr,MSG_AUTH_USER);
      ErrorVmsStatus (rqptr, status, FI_LI);
      HTAdminChangePasswordEnd (rqptr);
      return;
   }

   HTAdminChangePasswordEnd (rqptr);
}

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

