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

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
---------------
07-SEP-2024  MGD  extend skeleton-key functionality
27-AUG-2013  MGD  bugfix; AuthVmsGetUai() interaction of logon= parameters
22-AUG-2012  MGD  bugfix; AuthVmsGetUai() logon= fall through
11-OCT-2009  MGD  AuthVmsGetUai() can now use [AuthSYSUAFlogonType] and/or
                  an optional authorization rule parameter 'param="logon=.."'
                  to specify the login mode (default is still NETWORK) 
                  a la ACME (and retrieves the access restriction days/hours
                  associated with that mode).
18-NOV-2008  MGD  bugfix; AuthVmsChangePassword() ensure that
                  rqAuth.SysUafDataPtr is populated
30-MAY-2007  MGD  AuthAccessCheck()
                  bugfix; &ArmAccessItem
03-JUN-2005  MGD  only check pwd2 pre-expiry if pwd2 is set
11-MAY-2005  MGD  provide PWDMIX mixed-case plus printable char passwords
                  in AuthVmsVerifyPassword() and AuthVmsChangePassword()
20-MAR-2005  MGD  AuthVmsVerifyUser() WATCH which flag causes failure
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 AuthAccessReadCheck() and
                  AuthAccessWriteCheck()
21-DEC-2002  MGD  AuthVmsLoadIdentifiers() more flexible
05-OCT-2002  MGD  allow wildcard specifications in AuthAccessReadCheck()
06-SEP-2002  MGD  AuthAccessReadCheck() 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; AuthAccessReadCheck() 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_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>
/* not defined VAX C 3.2 <uaidef.h> (probably unnecessary now 11-MAY-2005) */
#ifndef UAI$M_RESTRICTED
#define UAI$M_RESTRICTED 0x8
#endif 
#ifndef UAI$C_PURDY_S
#define UAI$C_PURDY_S 3
#endif 
/* probably not defined earlier than 7.3-2 */
#ifndef UAI$M_PWDMIX
#define UAI$M_PWDMIX 0x2000000
#endif 

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

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

extern BOOL  InstanceMutexHeld[];

extern int  ServerPort;

extern int64  HttpdTime64;

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;

/*****************************************************************************/
/*
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  cnt, status,
        AccessP,
        AccessS,
        LogonType;
   ulong  *qptr1,
          *qptr2;
   char  *cptr, *sptr, *zptr;
   AUTH_SYSUAF  *uafptr;
   VMS_ITEM_LIST3  *itmptr;

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

   if (WATCHMOD (rqptr, WATCH_MOD_AUTH))
      WatchThis (WATCHITM(rqptr), 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++ = to_upper(*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->UaiExpTime64);
   itmptr->item = UAI$_EXPIRATION;
   itmptr->buf_addr = &uafptr->UaiExpTime64;
   itmptr->ret_len = 0;
   itmptr++;

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

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

   itmptr->buf_len = sizeof(uafptr->UaiPwdLifeTime64);
   itmptr->item = UAI$_PWD_LIFETIME;
   itmptr->buf_addr = &uafptr->UaiPwdLifeTime64;
   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++;

   if (!(cptr = rqptr->rqAuth.PathParameterPtr)) cptr = "";
   while (*cptr && (to_lower(*cptr) != 'l' || !strsame(cptr,"logon=",6))) cptr++;
   if (*cptr)
   {
      /* same set of parameters as used by AuthAcmeVerifyUser() */
      if (strsame (cptr, "logon=NETWORK", 13))
      {
         LogonType = AUTH_LOGON_TYPE_NETWORK;
         AccessP = UAI$_NETWORK_ACCESS_P;
         AccessS = UAI$_NETWORK_ACCESS_S;
      }
      else
      if (strsame (cptr, "logon=BATCH", 11))
      {
         LogonType = AUTH_LOGON_TYPE_BATCH;
         AccessP = UAI$_BATCH_ACCESS_P;
         AccessS = UAI$_BATCH_ACCESS_S;
      }
      else
      if (strsame (cptr, "logon=LOCAL", 11))
      {
         LogonType = AUTH_LOGON_TYPE_LOCAL;
         AccessP = UAI$_LOCAL_ACCESS_P;
         AccessS = UAI$_LOCAL_ACCESS_S;
      }
      else
      if (strsame (cptr, "logon=DIALUP", 12))
      {
         LogonType = AUTH_LOGON_TYPE_DIALUP;
         AccessP = UAI$_DIALUP_ACCESS_P;
         AccessS = UAI$_DIALUP_ACCESS_S;
      }
      else
      if (strsame (cptr, "logon=REMOTE", 12))
      {
         LogonType = AUTH_LOGON_TYPE_REMOTE;
         AccessP = UAI$_REMOTE_ACCESS_P;
         AccessS = UAI$_REMOTE_ACCESS_S;
      }
      else
      if (strsame (cptr, "logon=", 6))
      {
         AccessP = AccessS = LogonType = 0;
         if (WATCHING (rqptr, WATCH_AUTH))
            WatchThis (WATCHITM(rqptr), WATCH_AUTH,
                       "unknown LOGON TYPE \"!AZ\"", cptr);
      }
   }
   else
   if (Config.cfAuth.SysUafLogonType)
   {
      switch (LogonType = Config.cfAuth.SysUafLogonType)
      {
         case AUTH_LOGON_TYPE_NETWORK :
              AccessP = UAI$_NETWORK_ACCESS_P;
              AccessS = UAI$_NETWORK_ACCESS_S;
              break;
         case AUTH_LOGON_TYPE_BATCH :
              AccessP = UAI$_BATCH_ACCESS_P;
              AccessS = UAI$_BATCH_ACCESS_S;
              break;
         case AUTH_LOGON_TYPE_LOCAL :
              AccessP = UAI$_LOCAL_ACCESS_P;
              AccessS = UAI$_LOCAL_ACCESS_S;
              break;
         case AUTH_LOGON_TYPE_DIALUP :
              AccessP = UAI$_DIALUP_ACCESS_P;
              AccessS = UAI$_DIALUP_ACCESS_S;
              break;
         case AUTH_LOGON_TYPE_REMOTE :
              AccessP = UAI$_REMOTE_ACCESS_P;
              AccessS = UAI$_REMOTE_ACCESS_S;
              break;
         default :
              ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI);
      }
   }
   else
   {
      LogonType = AUTH_LOGON_TYPE_NETWORK;
      AccessP = UAI$_NETWORK_ACCESS_P;
      AccessS = UAI$_NETWORK_ACCESS_S;
   }

   rqptr->rqAuth.SysUafLogonType = LogonType;

   itmptr->buf_len = 3;
   itmptr->item = AccessP;
   itmptr->buf_addr = &uafptr->UaiAccessP;
   itmptr->ret_len = 0;
   itmptr++;

   itmptr->buf_len = 3;
   itmptr->item = AccessS;
   itmptr->buf_addr = &uafptr->UaiAccessS;
   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_AUTH))
            WatchThis (WATCHITM(rqptr), 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 */
   cptr = (sptr = uafptr->UaiOwner) + 1;
   for (cnt = uafptr->UaiOwner[0]; cnt >= 0; cnt--)
      *sptr++ = *cptr++;
   *sptr = '\0';
   while (sptr > uafptr->UaiOwner && isspace(*(sptr-1))) sptr--;
   *sptr = '\0';

   if (WATCHING (rqptr, WATCH_AUTH))
   {
      qptr1 = &uafptr->UaiPwd;
      qptr2 = &uafptr->UaiPwd2;
      WatchThis (WATCHITM(rqptr), WATCH_AUTH,
                 "GETUAI \"!AZ\" !&S", 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\
logon:!AZ prime:!8XL primary:!6XL secondary:!6XL\n",
            &uafptr->UaiExpTime64,
            &uafptr->UaiPwdDateTime64,
            qptr1[1], qptr1[0],
            &uafptr->UaiPwd2DateTime64,
            qptr2[1], qptr2[0], 
            &uafptr->UaiPwdLifeTime64,
            &uafptr->UaiFlags,
            uafptr->UaiPriv[1], uafptr->UaiPriv[0],
            AuthSysUafLogonType(LogonType),
            uafptr->UaiPrimeDays,
            uafptr->UaiAccessP, uafptr->UaiAccessS);
   }

   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 (WATCHMOD (rqptr, WATCH_MOD_AUTH))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_AUTH,
                 "AuthVmsVerifyPassword() !&Z pwdmix:!&B",
                 rqptr->rqAuth.SysUafDataPtr->UserName,
                 rqptr->rqAuth.SysUafDataPtr->UaiFlags & UAI$M_PWDMIX);

   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;

   if (rqptr->rqAuth.SkelKeyAuthenticated)
   {
      if (WATCHING (rqptr, WATCH_AUTH))
         WatchThis (WATCHITM(rqptr), WATCH_AUTH, "SKELKEY proxy authenticated");
      return (SS$_NORMAL);
   }

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

   zptr = (sptr = Password) + sizeof(Password)-1;
   if (uafptr->UaiFlags & UAI$M_PWDMIX)
   {
      /* 7.3-2 mixed-case plus printable char passwords */
      for (cptr = rqptr->RemoteUserPassword;
           *cptr && sptr < zptr;
           *sptr++ = *cptr++);
   }
   else
   {
      /* to uppercase! */
      for (cptr = rqptr->RemoteUserPassword;
           *cptr && sptr < zptr;
           *sptr++ = to_upper(*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 != uafptr->UaiPwd)
   {
      if (!AuthSysUafPromiscuous)
      {
         if (WATCHING (rqptr, WATCH_AUTH))
            WatchThis (WATCHITM(rqptr), 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)
   {
      /* password has expired */
      if (!strsame (rqptr->rqHeader.RequestUriPtr,
                    INTERNAL_PASSWORD_CHANGE,
                    sizeof(INTERNAL_PASSWORD_CHANGE)-1))
      {
         /* and not in the process of changing it */
         if (rqptr->rqPathSet.AuthSysUafPwdExpUrlPtr)
            cptr = rqptr->rqPathSet.AuthSysUafPwdExpUrlPtr;
         else
         if (Config.cfAuth.SysUafPwdExpUrl[0])
            cptr = Config.cfAuth.SysUafPwdExpUrl;
         else
            cptr = NULL;

         if (cptr)
         {
            /* expired password URL is configured */
            if (!strsame (rqptr->rqHeader.RequestUriPtr, cptr, -1))
            {
               /* request URI doesn't match it though, so redirect */
               ResponseLocation (rqptr, cptr, -1);
               status = AUTH_DENIED_BY_REDIRECT;
            }
         }
         else
         {
            /* is not configured */
            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;
   int64  PwdExpTime64;
   char  *cptr, *sptr, *zptr;
   AUTH_CREC  *acrptr;
   AUTH_SYSUAF  *uafptr;
   VMS_ITEM_LIST3  *itmptr;

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

   if (WATCHMOD (rqptr, WATCH_MOD_AUTH))
      WatchThis (WATCHITM(rqptr), 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->UaiAccessP = uafptr->UaiAccessP;
   acrptr->UaiAccessS = uafptr->UaiAccessS;

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

   /* automatically disallow if any of these flags are set! */
   if ((AuthPolicySysUafIdentifiers || 
        AuthPolicySysUafWasdIdentifiers ||
        AuthPolicySysUafRelaxed) &&
       (uafptr->UaiFlags & DisallowFlags))
   {
      if (WATCHING (rqptr, WATCH_AUTH))
      {
         if (uafptr->UaiFlags & UAI$M_DISACNT)
            cptr = "DISACNT";
         else
         if (uafptr->UaiFlags & UAI$M_PWD_EXPIRED)
            cptr = "PWD_EXPIRED";
         else
         if (uafptr->UaiFlags & UAI$M_PWD2_EXPIRED)
            cptr = "PWD2_EXPIRED";
         else
            cptr = "?";
         WatchThis (WATCHITM(rqptr), WATCH_AUTH, "FAIL SYSUAF flag:!AZ", cptr);
      }
      return (AUTH_DENIED_BY_LOGIN);
   }

   /* automatically disallow if any of these flags are set! */
   if (((AuthPolicySysUafVms && !AuthPolicySysUafRelaxed) ||
        (!AuthPolicySysUafIdentifiers && !AuthPolicySysUafRelaxed)) &&
       (uafptr->UaiFlags & DisallowVmsFlags))
   {
      if (WATCHING (rqptr, WATCH_AUTH))
      {
         if (uafptr->UaiFlags & UAI$M_DISACNT)
            cptr = "DISACNT";
         else
         if (uafptr->UaiFlags & UAI$M_PWD_EXPIRED)
            cptr = "PWD_EXPIRED";
         else
         if (uafptr->UaiFlags & UAI$M_PWD2_EXPIRED)
            cptr = "PWD2_EXPIRED";
         else
         if (uafptr->UaiFlags & UAI$M_CAPTIVE)
            cptr = "CAPTIVE";
         else
         if (uafptr->UaiFlags & UAI$M_RESTRICTED)
            cptr = "RESTRICTED";
         else
            cptr = "?";
         WatchThis (WATCHITM(rqptr), WATCH_AUTH, "FAIL SYSUAF flag:!AZ", cptr);
      }
      return (AUTH_DENIED_BY_LOGIN);
   }

   if (uafptr->UaiExpTime64)
   {
      /* check account expiration */
      if (uafptr->UaiExpTime64 < HttpdTime64)
      {
         if (WATCHING (rqptr, WATCH_AUTH))
            WatchThis (WATCHITM(rqptr), WATCH_AUTH,
                       "FAIL SYSUAF account expired");
         return (AUTH_DENIED_BY_LOGIN);
      }
   }

   rqptr->rqAuth.SysUafPwdExpired = false;
   if (!Config.cfAuth.SysUafAcceptExpPwd &&
       uafptr->UaiPwdDateTime64 == DELTA64_ZERO)
   {
      if (WATCHING (rqptr, WATCH_AUTH))
         WatchThis (WATCHITM(rqptr), WATCH_AUTH, "FAIL SYSUAF pwd pre-expired");
      rqptr->rqAuth.SysUafPwdExpired = true;
   }
   else
   if (!Config.cfAuth.SysUafAcceptExpPwd &&
       uafptr->UaiPwdDateTime64 &&
       uafptr->UaiPwdLifeTime64)
   {
      /* check password expiration */
      PwdExpTime64 = uafptr->UaiPwdDateTime64 + uafptr->UaiPwdLifeTime64; 
      if (PwdExpTime64 > HttpdTime64)
      {
         rqptr->rqAuth.NoCache = true;
         if (WATCHING (rqptr, WATCH_AUTH))
            WatchThis (WATCHITM(rqptr), WATCH_AUTH, "FAIL SYSUAF pwd expired");
         /* only after the password is confirmed OK *then* do any redirect */
         rqptr->rqAuth.SysUafPwdExpired = true;
      }
   }

   if (uafptr->UaiPwd2)
   {
      /* the secondary password has been set */
      if (!Config.cfAuth.SysUafAcceptExpPwd &&
          uafptr->UaiPwd2DateTime64 == DELTA64_ZERO)
      {
         if (WATCHING (rqptr, WATCH_AUTH))
            WatchThis (WATCHITM(rqptr), WATCH_AUTH,
                       "FAIL SYSUAF pwd2 pre-expired");
         return (AUTH_DENIED_BY_LOGIN);
      }
      else
      if (!Config.cfAuth.SysUafAcceptExpPwd &&
          uafptr->UaiPwd2DateTime64 &&
          uafptr->UaiPwdLifeTime64)
      {
         /* check password 2 expiration */
         PwdExpTime64 = uafptr->UaiPwd2DateTime64 + uafptr->UaiPwdLifeTime64; 
         if (PwdExpTime64 > HttpdTime64)
         {
            if (WATCHING (rqptr, WATCH_AUTH))
               WatchThis (WATCHITM(rqptr), 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_AUTH))
         WatchThis (WATCHITM(rqptr), 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);
   strzcpy (cptr, uafptr->UaiOwner, rqptr->rqAuth.UserDetailsLength+1);

   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 (WATCHMOD (rqptr, WATCH_MOD_AUTH))
      WatchThis (WATCHITM(rqptr), 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 (WATCHMOD (rqptr, WATCH_MOD_AUTH))
         WatchThis (WATCHITM(rqptr), WATCH_MOD_AUTH,
                    "ID %X!8XL !%I", ThisId, ThisId);

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

         if (WATCHING (rqptr, WATCH_AUTH))
            WatchThis (WATCHITM(rqptr), 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_AUTH))
               WatchThis (WATCHITM(rqptr), WATCH_AUTH,
                          "HOLDS identifier !AZ", AUTH_WASD_READ_VMS_ID);

            continue;
         }

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

            if (WATCHING (rqptr, WATCH_AUTH))
               WatchThis (WATCHITM(rqptr), 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_AUTH))
            WatchThis (WATCHITM(rqptr), 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_AUTH))
            WatchThis (WATCHITM(rqptr), 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_AUTH))
            WatchThis (WATCHITM(rqptr), 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_AUTH))
            WatchThis (WATCHITM(rqptr), 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_AUTH))
            WatchThis (WATCHITM(rqptr), 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_AUTH))
         WatchThis (WATCHITM(rqptr), 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_AUTH))
         WatchThis (WATCHITM(rqptr), 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 (WATCHMOD (rqptr, WATCH_MOD_AUTH))
      WatchThis (WATCHITM(rqptr), 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 =
         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 (WATCHMOD (rqptr, WATCH_MOD_AUTH))
      WatchThis (WATCHITM(rqptr), 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_AUTH))
         WatchThis (WATCHITM(rqptr), 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 (WATCHMOD (rqptr, WATCH_MOD_AUTH))
         WatchThis (WATCHITM(rqptr), WATCH_MOD_AUTH,
                    "ID !&S !%I", *idptr, *idptr);
   }

   if (*idptr)
   {
      if (WATCHING (rqptr, WATCH_AUTH))
         WatchThis (WATCHITM(rqptr), 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 (WATCHMOD (rqptr, WATCH_MOD_AUTH))
      WatchThis (WATCHITM(rqptr), 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 AuthAccessReadCheck())
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 (WATCHMOD (rqptr, WATCH_MOD_AUTH))
      WatchThis (WATCHITM(rqptr), 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 (WATCHMOD (rqptr, WATCH_MOD_AUTH))
   {
      WatchThis (WATCHITM(rqptr), 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 (WATCHALL, 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 (WATCHALL, 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;
}

/*****************************************************************************/
/*
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  PasswordBuffer [AUTH_MAX_PASSWORD_LENGTH+1],
                UserNameBuffer [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, UserNameBuffer);
   static $DESCRIPTOR (PasswordDsc, PasswordBuffer);

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

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

   if (WATCHMOD (rqptr, WATCH_MOD_AUTH))
      WatchThis (WATCHALL, WATCH_MOD_AUTH, "AuthVmsChangePassword()");

   if (WATCHING (rqptr, WATCH_RESPONSE))
      WatchThis (WATCHITM(rqptr), WATCH_RESPONSE, "CHANGE SYSUAF password");

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

   if (!(uafptr = rqptr->rqAuth.SysUafDataPtr))
   {
      if (VMSnok (status = AuthVmsGetUai (rqptr, rqptr->RemoteUser)))
         ErrorExitVmsStatus (status, ErrorSanityCheck, FI_LI);
      if (!(uafptr = rqptr->rqAuth.SysUafDataPtr))
         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 = PasswordBuffer) + sizeof(PasswordBuffer)-1;
   cptr = PasswordNew; 
   if (uafptr->UaiFlags & UAI$M_PWDMIX)
   {
      /* 7.3-2 mixed-case plus printable char passwords */
      while (*cptr && sptr < zptr)
      {
         if (!isprint(*cptr) || *cptr == '\"') break;
         *sptr++ = *cptr++;
      }
   }
   else
   {
      /* all upper-case alphanumeric plus dollar and underscore only */
      while (*cptr && sptr < zptr)
      {
         if (!(isalnum(*cptr) || *cptr == '_' || *cptr == '$')) break;
         *sptr++ = to_upper(*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 - PasswordBuffer;

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

   /* 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);

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

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

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

