/*****************************************************************************/ /* AuthAccess.c Licensed under the Apache License, Version 2.0 (the License); you may not use this software except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an AS IS BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. Authorize access to a particular file or directory. Enable access (via SYSPRV) appropriately to ensure access. See AUTH.C for overall detail on the WASD authorization environment. VERSION HISTORY --------------- 04-JAN-2021 MGD AuthAccessEnable() NULL |rqptr| returns SYSPRV enabled? 16-OCT-2020 MGD AuthAccessEnable() add ->rqPathSet.WebDavAll and ->rqPathSet.WebDavAuth 29-SEP-2020 MGD AuthAccessEnable() file access use (rqptr->WebDavRequest || rqptr->WhiffOfWebDav) rather than ->WebDavTaskPtr 13-JUL-2014 MGD AuthAccessCheck() add explicit check against server account to improve reporting of underlying access 31-JUL-2007 MGD unbundled from AUTH.C */ /*****************************************************************************/ #ifdef WASD_VMS_V7 #undef _VMS__V6__SOURCE #define _VMS__V6__SOURCE #undef __VMS_VER #define __VMS_VER 70000000 #undef __CRTL_VER #define __CRTL_VER 70000000 #endif /* standard C header files */ #include #include #include #include /* VMS related header files */ #include #include #include #include #include #include #include #include /* application related header files */ #include "wasd.h" #define WASD_MODULE "AUTHACCESS" #if WATCH_MOD #define FI_NOLI WASD_MODULE, __LINE__ #else /* in production let's keep the exact line to ourselves! */ #define FI_NOLI WASD_MODULE, 0 #endif /******************/ /* global storage */ /******************/ /********************/ /* external storage */ /********************/ extern BOOL AuthPolicySysUafRelaxed, AuthPolicySysUafIdentifiers, AuthPolicySysUafWasdIdentifiers, AuthPolicySysUafVms, AuthSysUafEnabled, AuthSysUafPromiscuous, AuthVmsUserProfileEnabled, AuthVmsUserProfileNoRule; extern BOOL InstanceMutexHeld[]; extern int ServerPort; extern unsigned int HttpdUserProfileLength; extern unsigned char *HttpdUserProfilePtr; extern struct dsc$descriptor_s HttpdUserProfileDsc; extern unsigned long AuthHttpsOnlyVmsIdentifier, AuthNilAccessVmsIdentifier, AuthPasswordChangeVmsIdentifier, AuthWasdPwdVmsIdentifier, AuthWasdHttpsVmsIdentifier, AuthWasdReadVmsIdentifier, AuthWasdWriteVmsIdentifier; extern unsigned long SysPrvMask[]; extern char ErrorSanityCheck[], SoftwareID[]; extern ACCOUNTING_STRUCT *AccountingPtr; extern CONFIG_STRUCT Config; extern HTTPD_PROCESS HttpdProcess; extern MSG_STRUCT Msgs; extern WATCH_STRUCT Watch; /*****************************************************************************/ /* Using sys$check_access() check file read/write access permission for the supplied file name against the SYSUAF-authenticated user's security profile (created by AuthVmsCreateUserProfile()). File or directory specifications can be supplied. If the file or directory does not exist then check access to the parent directory (in this way permission to say, create a directory may be established). Returns SS$_NORMAL if access allowed, SS$_NOPRIV if denied, and other RMS status codes. */ int AuthAccessCheck ( REQUEST_STRUCT* rqptr, char *FileName, int FileNameLength, int AccessRequired ) { static unsigned long Flags = 0; static unsigned long ArmAccess; static unsigned long ArmControlAccess = ARM$M_CONTROL; static $DESCRIPTOR (ClassNameDsc, "FILE"); static $DESCRIPTOR (UserProfileDsc, ""); static VMS_ITEM_LIST3 ArmAccessItem [] = { { sizeof(ArmAccess), CHP$_ACCESS, &ArmAccess, 0 }, { 0, 0, 0, 0 } }; static VMS_ITEM_LIST3 ControlAccessItem [] = { { sizeof(ArmControlAccess), CHP$_ACCESS, &ArmControlAccess, 0 }, { 0, 0, 0, 0 } }; BOOL VmsUserProfile; int status, stts, DirFileNameLength, ParentFileNameLength = 0; char *cptr, *ProfilePtr, *WhoseProfilePtr; char DirFileName [ODS_MAX_FILE_NAME_LENGTH+1], ParentFileName [ODS_MAX_FILE_NAME_LENGTH+1]; $DESCRIPTOR (FileNameDsc, ""); $DESCRIPTOR (ParentNameDsc, ""); /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_AUTH)) WatchThis (WATCHITM(rqptr), WATCH_MOD_AUTH, "AuthAccessCheck() !&Z !&Z !UL !UL !&X !&B !&B", rqptr->RemoteUser, FileName, AccessRequired, rqptr->rqAuth.VmsUserProfileLength, rqptr->rqAuth.VmsUserProfilePtr, rqptr->rqPathSet.NoProfile, rqptr->rqPathSet.AccessProfile || rqptr->rqPathSet.AccessRead || rqptr->rqPathSet.AccessServer || rqptr->rqPathSet.AccessWrite); if (!HttpdUserProfileLength) AuthVmsCreateHttpdProfile (); /* wildcard specifications can be passed to this function */ if (FileNameLength > 0) cptr = FileName + FileNameLength; else for (cptr = FileName; *cptr; cptr++); if (cptr > FileName) cptr--; while (cptr > FileName && *cptr != ']') cptr--; if (*cptr == ']') cptr++; FileNameLength = cptr - FileName; while (*cptr && *cptr != '*' && *cptr != '%') cptr++; if (!*cptr) FileNameLength = cptr - FileName; if (!FileNameLength || !FileName[0]) { ErrorNoticed (rqptr, SS$_BUGCHECK, NULL, FI_LI); return (SS$_BUGCHECK); } if (FileNameLength && FileName[FileNameLength-1] == ']') { /* a directory has been specified, get the directory file name */ OdsNameOfDirectoryFile (FileName, FileNameLength, DirFileName, &DirFileNameLength); FileNameDsc.dsc$a_pointer = DirFileName; FileNameDsc.dsc$w_length = DirFileNameLength; } else { FileNameDsc.dsc$a_pointer = FileName; FileNameDsc.dsc$w_length = FileNameLength; } /****************/ /* check access */ /****************/ switch (AccessRequired) { case AUTH_ACCESS_READ : ArmAccess = ARM$M_READ; break; case AUTH_ACCESS_WRITE : ArmAccess = ARM$M_WRITE; break; default : ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI); } if (rqptr->rqAuth.VmsUserProfileLength) { VmsUserProfile = AuthVmsUserProfileNoRule; if (rqptr->rqPathSet.NoProfile) VmsUserProfile = false; if (rqptr->rqAuth.VmsUserProfile) VmsUserProfile = true; } else VmsUserProfile = false; if (AccessRequired == AUTH_ACCESS_WRITE) { /* Check if the HTTPd account has CONTROL access to the parent directory of the file or directory specification supplied in 'FileName'. If the server has CONTROL access then there is no general access to the directory, only an authenticated VMS user with a security profile can write into it. */ OdsNameOfDirectoryFile (FileNameDsc.dsc$a_pointer, FileNameDsc.dsc$w_length, ParentFileName, &ParentFileNameLength); ParentNameDsc.dsc$a_pointer = ParentFileName; ParentNameDsc.dsc$w_length = ParentFileNameLength; /* turn on SYSPRV to allow this to be done */ sys$setprv (1, &SysPrvMask, 0, 0); status = sys$check_access (0, &ParentNameDsc, 0, &ControlAccessItem, 0, &ClassNameDsc, 0, &HttpdUserProfileDsc); sys$setprv (0, &SysPrvMask, 0, 0); if (WATCHING (rqptr, WATCH_AUTH)) WatchThis (WATCHITM(rqptr), WATCH_AUTH, "ACCESS !AZ control !AZ !&S !&?YES\rNO\r", HttpdProcess.UserName, ParentNameDsc.dsc$a_pointer, status, VMSok(status)); if (VMSok (status)) { /* control ACE means must be using VMS profile */ if (!VmsUserProfile) return (SS$_NOPRIV); } else if (status != SS$_NOPRIV) { /* any other error status */ return (status); } /* drop through to check user access */ } /***************/ /* user access */ /***************/ stts = 0; if (VmsUserProfile) { /* against a SYSUAF-authenticated account */ UserProfileDsc.dsc$a_pointer = rqptr->rqAuth.VmsUserProfilePtr; UserProfileDsc.dsc$w_length = rqptr->rqAuth.VmsUserProfileLength; WhoseProfilePtr = rqptr->RemoteUser; ProfilePtr = "(using profile)"; } else { /* against the HTTPd server account */ UserProfileDsc.dsc$a_pointer = HttpdUserProfilePtr; UserProfileDsc.dsc$w_length = HttpdUserProfileLength; WhoseProfilePtr = HttpdProcess.UserName; if (rqptr->rqAuth.VmsUserProfileLength) ProfilePtr = "(NOT using profile)"; else ProfilePtr = "(no profile)"; } /* turn on SYSPRV to allow this to be done */ sys$setprv (1, &SysPrvMask, 0, 0); status = sys$check_access (0, &FileNameDsc, 0, &ArmAccessItem, 0, &ClassNameDsc, 0, &UserProfileDsc); if (WATCHMOD (rqptr, WATCH_MOD_AUTH)) WatchThis (WATCHITM(rqptr), WATCH_MOD_AUTH, "sys$check_access() !AZ !AZ !&S", WhoseProfilePtr, FileNameDsc.dsc$a_pointer, status); if (WATCHING (rqptr, WATCH_AUTH)) { /* only for informational purposes */ if (status == SS$_NOPRIV && VmsUserProfile) { /* also check against the HTTPd server account (e.g. via ACL) */ UserProfileDsc.dsc$a_pointer = HttpdUserProfilePtr; UserProfileDsc.dsc$w_length = HttpdUserProfileLength; stts = sys$check_access (0, &FileNameDsc, 0, &ArmAccessItem, 0, &ClassNameDsc, 0, &UserProfileDsc); if (WATCHMOD (rqptr, WATCH_MOD_AUTH)) WatchThis (WATCHITM(rqptr), WATCH_MOD_AUTH, "sys$check_access() !AZ !AZ !&S", HttpdProcess.UserName, FileNameDsc.dsc$a_pointer, stts); } } /* turn SYSPRV back off again */ sys$setprv (0, &SysPrvMask, 0, 0); if (status == RMS$_FNF || status == RMS$_DNF) { /* attempt to establish access to parent directory */ if (!ParentFileNameLength) { /* if not already initialised when checking for control above */ OdsNameOfDirectoryFile (FileNameDsc.dsc$a_pointer, FileNameDsc.dsc$w_length, ParentFileName, &ParentFileNameLength); ParentNameDsc.dsc$a_pointer = ParentFileName; ParentNameDsc.dsc$w_length = ParentFileNameLength; } if (VmsUserProfile) { /* against a SYSUAF-authenticated account */ UserProfileDsc.dsc$a_pointer = rqptr->rqAuth.VmsUserProfilePtr; UserProfileDsc.dsc$w_length = rqptr->rqAuth.VmsUserProfileLength; WhoseProfilePtr = rqptr->RemoteUser; ProfilePtr = "(using profile)"; } /* turn on SYSPRV to allow this to be done */ sys$setprv (1, &SysPrvMask, 0, 0); status = sys$check_access (0, &ParentNameDsc, 0, &ArmAccessItem, 0, &ClassNameDsc, 0, &UserProfileDsc); if (WATCHMOD (rqptr, WATCH_MOD_AUTH)) WatchThis (WATCHITM(rqptr), WATCH_MOD_AUTH, "sys$check_access() !AZ !AZ !&S", WhoseProfilePtr, ParentNameDsc.dsc$a_pointer, status); if (WATCHING (rqptr, WATCH_AUTH)) { /* only for informational purposes */ if (status == SS$_NOPRIV && VmsUserProfile) { /* also against the HTTPd server account (e.g. via ACL) */ UserProfileDsc.dsc$a_pointer = HttpdUserProfilePtr; UserProfileDsc.dsc$w_length = HttpdUserProfileLength; stts = sys$check_access (0, &ParentNameDsc, 0, &ArmAccessItem, 0, &ClassNameDsc, 0, &UserProfileDsc); if (WATCHMOD (rqptr, WATCH_MOD_AUTH)) WatchThis (WATCHITM(rqptr), WATCH_MOD_AUTH, "sys$check_access() !AZ !AZ !&S", HttpdProcess.UserName, ParentNameDsc.dsc$a_pointer, stts); } } /* turn SYSPRV back off again */ sys$setprv (0, &SysPrvMask, 0, 0); } if (WATCHING (rqptr, WATCH_AUTH)) { WatchThis (WATCHITM(rqptr), WATCH_AUTH, "ACCESS !AZ !AZ !AZ !AZ !&S !AZ", ProfilePtr, WhoseProfilePtr, AccessRequired == AUTH_ACCESS_READ ? "read" : "write", FileNameDsc.dsc$a_pointer, status, VMSok(status) ? "PERMITTED" : "DENIED"); if (VMSok(stts)) WatchThis (WATCHITM(rqptr), WATCH_AUTH, "NOTE: !AZ !AZ !AZ !&S !AZ", HttpdProcess.UserName, AccessRequired == AUTH_ACCESS_READ ? "read" : "write", FileNameDsc.dsc$a_pointer, stts, VMSok(stts) ? "PERMITTED" : "DENIED"); } if (VMSok (status)) return (status); /* DFS hosted volumes can return SS$_NOCALLPRIV. Trap SS$_ACCONFLICT into SS$_NOPRIV so directory listings don't halt. */ if (status == SS$_NOPRIV || status == SS$_NOCALLPRIV || status == SS$_ACCONFLICT) return (SS$_NOPRIV); /* if an RMS error return normal letting caller continue and report it */ if (((status & STS$M_FAC_NO) >> STS$V_FAC_NO) == RMS$_FACILITY) return (SS$_NORMAL); rqptr->rqResponse.ErrorTextPtr = "sys$check_access()"; rqptr->rqResponse.ErrorOtherTextPtr = FileName; ErrorVmsStatus (rqptr, status, FI_LI); return (status); } /*****************************************************************************/ /* Enables SYSPRV based on the ACCESS= or WEBDAV= path SETing. If =READ then enables it when requiring read access. If =WRITE then enables it for both read and write. If =PROFILE then the SYSUAF user security profile is used to check for access against the supplied file specification. If allowed then SYSPRV is enabled. If =SERVER then SYSPRV is never enabled, it becomes a best effort based on the access permissions of the server account. The 'AccessRequired' parameter AUTH_ACCESS_SYSPRV unconditionally enables SYSPRV for those instances where discretionary access is not necessary. No access required (zero value) disables SYSPRV. =WRITE takes precedence over =PROFILE, =READ and =SERVER =PROFILE takes precedence over =READ and =SERVER =READ takes precedence over =SERVER Returns boolean for whether SYSPRV is currently enabled. */ BOOL AuthAccessEnable ( REQUEST_STRUCT *rqptr, char *FileName, int AccessRequired ) { static BOOL SysPrvEnabled; BOOL IsWebDAV; int status; char *AccessPtr, *PathPtr; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_AUTH)) WatchThis (WATCHITM(rqptr), WATCH_MOD_AUTH, "AuthAccessEnable() !UL!AZ webdav:!&B read:!&B write:!&B profile:!&B !&Z", AccessRequired, AccessRequired == AUTH_ACCESS_SYSPRV ? " (SYSPRV)" : "", rqptr->WebDavRequest || rqptr->WhiffOfWebDav, (rqptr->rqPathSet.WebDavRead || rqptr->rqPathSet.AccessRead), (rqptr->rqPathSet.WebDavWrite || rqptr->rqPathSet.AccessWrite), (rqptr->rqPathSet.WebDavProfile || rqptr->rqPathSet.AccessProfile), FileName); /* if empty call then just return whether SYSPRV is enabled or not */ if (!rqptr) return (SysPrvEnabled); if (!AccessRequired) { /* disable */ if (!SysPrvEnabled) return (SysPrvEnabled); sys$setprv (0, &SysPrvMask, 0, 0); SysPrvEnabled = false; return (SysPrvEnabled); } IsWebDAV = rqptr->WebDavRequest || rqptr->WhiffOfWebDav || rqptr->rqPathSet.WebDavAll || rqptr->rqPathSet.WebDavAuth; /* enabling/disabling must be done in balance to prevent privs escaping */ if (SysPrvEnabled) ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI); switch (AccessRequired) { case AUTH_ACCESS_READ : AccessPtr = "read"; break; case AUTH_ACCESS_WRITE : AccessPtr = "write"; break; case AUTH_ACCESS_SYSPRV : /* unconditional enabling of SYSPRV */ sys$setprv (1, &SysPrvMask, 0, 0); SysPrvEnabled = true; return (SysPrvEnabled); default : ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI); } if ((IsWebDAV && rqptr->rqPathSet.WebDavWrite) || rqptr->rqPathSet.AccessWrite) { /* path set '=write' */ sys$setprv (1, &SysPrvMask, 0, 0); SysPrvEnabled = true; PathPtr = "write"; } else if ((IsWebDAV && rqptr->rqPathSet.WebDavProfile) || rqptr->rqPathSet.AccessProfile) { /* path set '=profile' */ status = AuthAccessCheck (rqptr, FileName, 0, AccessRequired); if (VMSok (status)) { sys$setprv (1, &SysPrvMask, 0, 0); SysPrvEnabled = true; } PathPtr = "profile"; } else if ((IsWebDAV && rqptr->rqPathSet.WebDavRead) || rqptr->rqPathSet.AccessRead) { /* path set '=read' */ if (AccessRequired == AUTH_ACCESS_READ) { sys$setprv (1, &SysPrvMask, 0, 0); SysPrvEnabled = true; } PathPtr = "read"; } else if ((IsWebDAV && rqptr->rqPathSet.WebDavServer) || rqptr->rqPathSet.AccessServer) { /* path set '=server' so relies on permissions for server account */ PathPtr = "server"; } else if (rqptr->rqAuth.VmsUserProfileLength) { /* no path SETing but authorized path with request SYSUAF profile */ status = AuthAccessCheck (rqptr, FileName, 0, AccessRequired); if (VMSok (status)) { sys$setprv (1, &SysPrvMask, 0, 0); SysPrvEnabled = true; } PathPtr = "(none)"; } else PathPtr = "(none)"; if (WATCHING (rqptr, WATCH_AUTH)) WatchThis (WATCHITM(rqptr), WATCH_AUTH, "ACCESS:!AZ WebDAV:!&B PATH:!AZ (SET read:!&B write:!&B profile:!&B) \ SYSPRV:!AZ !AZ", AccessPtr, IsWebDAV, PathPtr, (rqptr->rqPathSet.WebDavRead || rqptr->rqPathSet.AccessRead), (rqptr->rqPathSet.WebDavWrite || rqptr->rqPathSet.AccessWrite), (rqptr->rqPathSet.WebDavProfile || rqptr->rqPathSet.AccessProfile), SysPrvEnabled ? "enabled" : "disabled", FileName); return (SysPrvEnabled); } /*****************************************************************************/