/*****************************************************************************/ /* AuthAccess.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. 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 --------------- 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 int ToLowerCase[], ToUpperCase[]; 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 HttpdTime64[], 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; 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->WebDavTaskPtr, (rqptr->rqPathSet.WebDavRead || rqptr->rqPathSet.AccessRead), (rqptr->rqPathSet.WebDavWrite || rqptr->rqPathSet.AccessWrite), (rqptr->rqPathSet.WebDavProfile || rqptr->rqPathSet.AccessProfile), FileName); if (!AccessRequired) { /* disable */ if (!SysPrvEnabled) return (SysPrvEnabled); sys$setprv (0, &SysPrvMask, 0, 0); SysPrvEnabled = false; return (SysPrvEnabled); } /* 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 ((rqptr->WebDavTaskPtr && rqptr->rqPathSet.WebDavWrite) || rqptr->rqPathSet.AccessWrite) { /* path set '=write' */ sys$setprv (1, &SysPrvMask, 0, 0); SysPrvEnabled = true; PathPtr = "write"; } else if ((rqptr->WebDavTaskPtr && 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 ((rqptr->WebDavTaskPtr && 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 ((rqptr->WebDavTaskPtr && 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, rqptr->WebDavTaskPtr, 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); } /*****************************************************************************/