/*****************************************************************************/ /* AuthHTA.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 the WASD HTA databases (maintained via HTADMIN.C). See AUTH.C for overall detail on the WASD authorization environment. VERSION HISTORY --------------- 21-MAY-2021 MGD keep HTA record structure as-was (using unsigned longs) and munge for access to 64 bit time 30-MAY-2007 MGD allow for CONNECT method (basically as a 'write' flag) 28-SEP-2003 MGD HTA database now "read [record] regardless of lock" 26-AUG-2003 MGD service directory located authorization databases 27-APR-2002 MGD use sys$setprv() 04-AUG-2001 MGD support module WATCHing 12-DEC-2000 MGD username size now HTA-specific, different to '->RemoteUser' 02-JAN-2000 MGD no significant modifications for ODS-5 (no NAM block) 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 #include #include #include /* VMS related header files */ #include #include #include #include #include #include /* not defined VAX C 3.2 (probably unnecessary now 11-MAY-2005) */ #ifndef UAI$C_PURDY_S #define UAI$C_PURDY_S 3 #endif /* application related header files */ #include "wasd.h" #define WASD_MODULE "AUTHHTA" #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 /********************/ /* external storage */ /********************/ extern int ServerPort; extern int ToLowerCase[], ToUpperCase[]; extern unsigned long SysPrvMask[]; extern char *AuthConfigHtaDirectory; extern char ErrorSanityCheck[], SoftwareID[]; extern ACCOUNTING_STRUCT *AccountingPtr; extern CONFIG_STRUCT Config; extern MSG_STRUCT Msgs; extern WATCH_STRUCT Watch; /****************************************************************************/ /* Verify the request username/password and/or read capabilities from the on-disk HTA (HTTPd) authorization database. 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). The digest is stored in both upper and lower case versions because clients will have case-lock either on or off producing a digest of either upper or lower case username and password. Keep digests of both so that provided one or other case is used exclusively the digest thereof can still be matched with one or other stored here :^) */ int AuthReadHtDatabase ( REQUEST_STRUCT* rqptr, char *DatabaseName, BOOL AuthenticatePassword ) { static char Password [AUTH_MAX_PASSWORD_LENGTH+1], UserName [AUTH_MAX_HTA_USERNAME_LENGTH+1]; static $DESCRIPTOR (PasswordDsc, Password); static $DESCRIPTOR (UserNameDsc, UserName); BOOL PasswordAuthenticated, UserNameEnabled; int status; unsigned long HashedPwd [2]; unsigned long *BeginTimePtr; char *cptr, *sptr, *zptr; char A1HexDigest [33], HexDigest [33]; AUTH_HTAREC AuthHtRecord; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_AUTH)) WatchThis (WATCHITM(rqptr), WATCH_MOD_AUTH, "AuthReadHtDatabase() !&Z !&Z !UL", DatabaseName, rqptr->RemoteUser, AuthenticatePassword); /* flag that case-less username and password checks were performed */ rqptr->rqAuth.CaseLess = true; /* set the capabilities to zero before we do anything else! */ rqptr->rqAuth.UserCan = 0; /* to uppercase! */ zptr = (sptr = UserName) + sizeof(UserName)-1; for (cptr = rqptr->RemoteUser; *cptr && sptr < zptr; *sptr++ = TOUP(*cptr++)); *sptr = '\0'; UserNameDsc.dsc$w_length = sptr - UserName; /* look for the record, leave the database file open if found */ status = AuthHtDatabaseAccess (rqptr, true, DatabaseName, UserName, &AuthHtRecord, NULL, NULL); if (status == RMS$_EOF) { if (WATCHING (rqptr, WATCH_AUTH)) WatchThis (WATCHITM(rqptr), WATCH_AUTH, "FAIL !AZ!AZ username", DatabaseName, AuthSourceString (DatabaseName, AUTH_SOURCE_HTA)); /* close the database */ AuthHtDatabaseAccess (NULL, false, NULL, NULL, NULL, NULL, NULL); if (AuthenticatePassword) return (AUTH_DENIED_BY_LOGIN); else return (AUTH_DENIED_BY_GROUP); } if (VMSnok (status)) { rqptr->rqResponse.ErrorTextPtr = MsgFor(rqptr,MSG_AUTH_DATABASE); rqptr->rqResponse.ErrorOtherTextPtr = DatabaseName; ErrorVmsStatus (rqptr, status, FI_LI); return (status); } /****************/ /* record found */ /****************/ PasswordAuthenticated = false; if (AuthHtRecord.Flags & AUTH_FLAG_ENABLED) { UserNameEnabled = true; if (AuthenticatePassword && AuthHtRecord.HashedPwd[0] && AuthHtRecord.HashedPwd[1]) { if (rqptr->rqAuth.Scheme == AUTH_SCHEME_BASIC) { /* to uppercase! */ zptr = (sptr = Password) + sizeof(Password)-1; for (cptr = rqptr->RemoteUserPassword; *cptr && sptr < zptr; *sptr++ = TOUP(*cptr++)); *sptr = '\0'; PasswordDsc.dsc$w_length = sptr - Password; status = sys$hash_password (&PasswordDsc, UAI$C_PURDY_S, 0, &UserNameDsc, &HashedPwd); if (VMSnok (status)) { /* ensure the currently open database is closed */ AuthHtDatabaseAccess (NULL, false, NULL, NULL, NULL, NULL, NULL); rqptr->rqResponse.ErrorTextPtr = MsgFor(rqptr,MSG_AUTH_USER); ErrorVmsStatus (rqptr, status, FI_LI); return (status); } if (WATCHPNT(rqptr) && WATCH_MODULE(WATCH_MOD_AUTH)) WatchThis (WATCHITM(rqptr), WATCH_MOD_AUTH, "!8XL!8XL !8XL!8XL", HashedPwd[1], HashedPwd[0], AuthHtRecord.HashedPwd[1], AuthHtRecord.HashedPwd[0]); if (HashedPwd[0] == AuthHtRecord.HashedPwd[0] && HashedPwd[1] == AuthHtRecord.HashedPwd[1]) PasswordAuthenticated = true; } else if (rqptr->rqAuth.Scheme == AUTH_SCHEME_DIGEST) { DigestHexString (AuthHtRecord.A1DigestLoCase, 16, A1HexDigest); DigestResponse (A1HexDigest, rqptr->rqAuth.DigestNoncePtr, rqptr->rqHeader.MethodName, rqptr->rqAuth.DigestUriPtr, HexDigest); if (WATCHPNT(rqptr) && WATCH_MODULE(WATCH_MOD_AUTH)) { WatchDataDump (AuthHtRecord.A1DigestLoCase, 16); WatchThis (WATCHITM(rqptr), WATCH_MOD_AUTH, "!&Z !&Z", rqptr->rqAuth.DigestResponsePtr, HexDigest); } if (!strcmp (rqptr->rqAuth.DigestResponsePtr, HexDigest)) PasswordAuthenticated = true; else { DigestHexString (AuthHtRecord.A1DigestUpCase, 16, A1HexDigest); DigestResponse (A1HexDigest, rqptr->rqAuth.DigestNoncePtr, rqptr->rqHeader.MethodName, rqptr->rqAuth.DigestUriPtr, HexDigest); if (WATCHPNT(rqptr) && WATCH_MODULE(WATCH_MOD_AUTH)) { WatchDataDump (AuthHtRecord.A1DigestUpCase, 16); WatchThis (WATCHITM(rqptr), WATCH_MOD_AUTH, "!&Z !&Z", rqptr->rqAuth.DigestResponsePtr, HexDigest); } if (!strcmp (rqptr->rqAuth.DigestResponsePtr, HexDigest)) PasswordAuthenticated = true; } } if (!PasswordAuthenticated) if (WATCHING (rqptr, WATCH_AUTH)) WatchThis (WATCHITM(rqptr), WATCH_AUTH, "FAIL !AZ!AZ password", DatabaseName, AuthSourceString (DatabaseName, AUTH_SOURCE_HTA)); if (WATCHMOD (rqptr, WATCH_MOD_AUTH)) WatchThis (WATCHITM(rqptr), WATCH_MOD_AUTH, "AUTHENTICATED"); } if (!AuthenticatePassword || PasswordAuthenticated) { /* set the appropriate authorization flags */ if (AuthHtRecord.Flags & AUTH_FLAG_CONNECT) rqptr->rqAuth.UserCan |= HTTP_METHOD_CONNECT; if (AuthHtRecord.Flags & AUTH_FLAG_DELETE) rqptr->rqAuth.UserCan |= HTTP_METHOD_DELETE; if (AuthHtRecord.Flags & AUTH_FLAG_GET) rqptr->rqAuth.UserCan |= HTTP_METHOD_GET | HTTP_METHOD_HEAD; if (AuthHtRecord.Flags & AUTH_FLAG_HEAD) rqptr->rqAuth.UserCan |= HTTP_METHOD_HEAD; if (AuthHtRecord.Flags & AUTH_FLAG_POST) rqptr->rqAuth.UserCan |= HTTP_METHOD_POST; if (AuthHtRecord.Flags & AUTH_FLAG_PUT) rqptr->rqAuth.UserCan |= HTTP_METHOD_PUT; if (AuthHtRecord.Flags & AUTH_FLAG_HTTPS_ONLY) rqptr->rqAuth.HttpsOnly = true; else rqptr->rqAuth.HttpsOnly = false; } } BeginTimePtr = &rqptr->rqTime.BeginTime64; if (UserNameEnabled && (!AuthenticatePassword || PasswordAuthenticated)) { AuthHtRecord.AccessCount++; AuthHtRecord.LastAccessTime64[0] = BeginTimePtr[0]; AuthHtRecord.LastAccessTime64[1] = BeginTimePtr[1]; } else { AuthHtRecord.FailureCount++; AuthHtRecord.LastFailureTime64[0] = BeginTimePtr[0]; AuthHtRecord.LastFailureTime64[1] = BeginTimePtr[1]; } /* update the record, close the database file */ status = AuthHtDatabaseAccess (NULL, false, NULL, NULL, NULL, NULL, &AuthHtRecord); if (VMSnok (status)) { rqptr->rqResponse.ErrorTextPtr = MsgFor(rqptr,MSG_AUTH_DATABASE); rqptr->rqResponse.ErrorOtherTextPtr = DatabaseName; ErrorVmsStatus (rqptr, status, FI_LI); return (status); } if (!AuthenticatePassword || PasswordAuthenticated) { if (AuthenticatePassword) { int Length; char *cptr, *sptr; Length = 0; if (AuthHtRecord.FullName[0]) Length += strlen(AuthHtRecord.FullName); if (AuthHtRecord.Email[0]) { if (Length) Length++; Length += strlen(AuthHtRecord.Email); } if (AuthHtRecord.Contact[0]) { if (Length) Length++; Length += strlen(AuthHtRecord.Contact); } if (Length) { rqptr->rqAuth.UserDetailsLength = Length; rqptr->rqAuth.UserDetailsPtr = sptr = VmGetHeap (rqptr, rqptr->rqAuth.UserDetailsLength+1); if (AuthHtRecord.FullName[0]) for (cptr = AuthHtRecord.FullName; *cptr; *sptr++ = *cptr++); if (AuthHtRecord.Email[0]) { if (sptr > rqptr->rqAuth.UserDetailsPtr) *sptr++ = '\n'; for (cptr = AuthHtRecord.Email; *cptr; *sptr++ = *cptr++); } if (AuthHtRecord.Contact[0]) { if (sptr > rqptr->rqAuth.UserDetailsPtr) *sptr++ = '\n'; for (cptr = AuthHtRecord.Contact; *cptr; *sptr++ = *cptr++); } *sptr = '\0'; } } return (SS$_NORMAL); } else if (AuthenticatePassword) return (AUTH_DENIED_BY_LOGIN); else return (AUTH_DENIED_BY_GROUP); } /****************************************************************************/ /* Locate a specified user record in a specified on-disk database. Either return that record if found or if an update record supplied, update it! Can return any RMS or VMS error status code. Returns SS$_NORMAL is operation (read, add or update) completed successfully. Returns AUTH_DENIED_BY_LOGIN if the record could not be found. Returns SS$_INCOMPAT if the database file version is incorrect. THE DATABASE FILE IS ALWAYS CLOSED ON AN ERROR CONDITION. 'LeaveFileOpen' will leave the database file open with the current record context ready for update, or for locating another user record. This function is not reentrant and therefore this context is valid ONLY within the one AST. Call with 'DatabaseName' and 'UserName' non-NULL and all other pointers set to NULL to check whether the user name has a record in the database. */ int AuthHtDatabaseAccess ( REQUEST_STRUCT* rqptr, BOOL LeaveFileOpen, char *DatabaseName, char *UserName, AUTH_HTAREC *AuthHtRecordReadPtr, AUTH_HTAREC *AuthHtRecordAddPtr, AUTH_HTAREC *AuthHtRecordUpdatePtr ) { static struct FAB AuthFileFab; static struct RAB AuthFileRab; int status, AuthFileNameLength, UserNameLength; char *cptr, *sptr, *zptr; char AuthFileName [256]; AUTH_HTAREC *AuthHtRecordPtr; AUTH_HTAREC AuthHtRecord; /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD_AUTH)) WatchThis (WATCHALL, WATCH_MOD_AUTH, "AuthHtDatabaseAccess() !&X !&B !&Z !&Z !&X !&X !&X", rqptr, LeaveFileOpen, DatabaseName, UserName, AuthHtRecordReadPtr, AuthHtRecordAddPtr, AuthHtRecordUpdatePtr); if (!AuthHtRecordReadPtr && !AuthHtRecordAddPtr && !AuthHtRecordUpdatePtr) { /************************/ /* force database close */ /************************/ sys$close (&AuthFileFab, 0, 0); return (SS$_NORMAL); } if (!UserName && AuthHtRecordUpdatePtr) { /*********************************/ /* update previously read record */ /*********************************/ AuthFileRab.rab$l_rbf = AuthHtRecordUpdatePtr; AuthFileRab.rab$w_rsz = sizeof(AUTH_HTAREC); status = sys$update (&AuthFileRab, 0, 0); if (VMSok (status)) { /* update completed successfully */ if (!LeaveFileOpen) sys$close (&AuthFileFab, 0, 0); return (SS$_NORMAL); } /* update error */ sys$close (&AuthFileFab, 0, 0); return (status); } if (!DatabaseName) { /*****************************************/ /* locate within currently open database */ /*****************************************/ if (VMSnok (status = sys$rewind (&AuthFileRab, 0, 0))) { sys$close (&AuthFileFab, 0, 0); return (status); } } else { /**********************************/ /* open currently closed database */ /**********************************/ if (rqptr->rqAuth.PathParameterPtr && rqptr->rqAuth.PathParameterPtr[0]) { if ((cptr = strstr (rqptr->rqAuth.PathParameterPtr, "/directory=")) || (cptr = strstr (rqptr->rqAuth.PathParameterPtr, "/DIRECTORY="))) cptr += 11; else cptr = NULL; } else if (rqptr->rqAuth.DirectoryPtr) cptr = rqptr->rqAuth.DirectoryPtr; else if (rqptr->ConfigDirectory[0]) cptr = rqptr->ConfigDirectory; else cptr = AuthConfigHtaDirectory; /* just a safeguard against the service directory not being configured */ if (!*cptr) cptr = AUTH_DIR_NOT_CONFIGURED; zptr = (sptr = AuthFileName) + sizeof(AuthFileName)-1; while (*cptr && sptr < zptr) *sptr++ = *cptr++; for (cptr = DatabaseName; *cptr && sptr < zptr; *sptr++ = *cptr++); for (cptr = HTA_FILE_TYPE; *cptr && sptr < zptr; *sptr++ = *cptr++); *sptr = '\0'; AuthFileNameLength = sptr - AuthFileName; if (WATCHING (rqptr, WATCH_AUTH)) WatchThis (WATCHITM(rqptr), WATCH_AUTH, "FILE !AZ", AuthFileName); AuthFileFab = cc$rms_fab; /* if a read-only operation without file left open then read-only! */ if (!LeaveFileOpen && !AuthHtRecordAddPtr && !AuthHtRecordUpdatePtr) AuthFileFab.fab$b_fac = FAB$M_GET; else AuthFileFab.fab$b_fac = FAB$M_GET | FAB$M_PUT | FAB$M_UPD; AuthFileFab.fab$l_fna = AuthFileName; AuthFileFab.fab$b_fns = AuthFileNameLength; AuthFileFab.fab$b_shr = FAB$M_SHRGET | FAB$M_SHRPUT | FAB$M_SHRUPD; /* turn on SYSPRV to allow access to authentication database file */ sys$setprv (1, &SysPrvMask, 0, 0); status = sys$open (&AuthFileFab, 0, 0); sys$setprv (0, &SysPrvMask, 0, 0); /* status from sys$open() */ if (VMSnok (status)) return (status); AuthFileRab = cc$rms_rab; AuthFileRab.rab$l_fab = &AuthFileFab; /* 2 buffers of sixty-four blocks (records) each */ AuthFileRab.rab$b_mbc = 64; AuthFileRab.rab$b_mbf = 2; /* read-ahead and read regardless of lock options */ AuthFileRab.rab$l_rop = RAB$M_RAH | RAB$M_RRL; if (VMSnok (status = sys$connect (&AuthFileRab, 0, 0))) { sys$close (&AuthFileFab, 0, 0); return (status); } } /*****************/ /* locate record */ /*****************/ /* for add and update read into the scratch record */ if (!AuthHtRecordReadPtr) AuthFileRab.rab$l_ubf = AuthHtRecordPtr = &AuthHtRecord; else AuthFileRab.rab$l_ubf = AuthHtRecordPtr = AuthHtRecordReadPtr; AuthFileRab.rab$w_usz = sizeof(AUTH_HTAREC); if (UserName) UserNameLength = strlen(UserName); while (VMSok (status = sys$get (&AuthFileRab, 0, 0))) { /* check the version of the authorization database */ if (AuthHtRecordPtr->DatabaseVersion && AuthHtRecordPtr->DatabaseVersion != AUTH_HTA_VERSION) { status = SS$_INCOMPAT & 0xfffffffe; break; } /* if deleted record (all set to zeroes) continue */ if (!AuthHtRecordAddPtr && !AuthHtRecordPtr->UserNameLength) continue; /* if adding a record then use the first deleted one found */ if (AuthHtRecordAddPtr && !AuthHtRecordPtr->UserNameLength) break; if (UserNameLength != AuthHtRecordPtr->UserNameLength) continue; cptr = AuthHtRecordPtr->UserName; sptr = UserName; while (*cptr && *sptr && TOLO(*cptr) == TOLO(*sptr)) { cptr++; sptr++; } if (*cptr || *sptr) continue; break; } if (!AuthHtRecordUpdatePtr && !AuthHtRecordAddPtr) { /***************/ /* read record */ /***************/ if (VMSok (status)) { /* record found */ if (!LeaveFileOpen) sys$close (&AuthFileFab, 0, 0); return (SS$_NORMAL); } if (status == RMS$_EOF) { /* user record not found */ if (!LeaveFileOpen) sys$close (&AuthFileFab, 0, 0); return (status); } /* RMS error */ sys$close (&AuthFileFab); return (status); } if (AuthHtRecordUpdatePtr) { /*****************/ /* update record */ /*****************/ if (VMSnok (status)) { if (status == RMS$_EOF) { /* user record not found */ if (!LeaveFileOpen) sys$close (&AuthFileFab, 0, 0); return (status); } /* RMS error */ sys$close (&AuthFileFab, 0, 0); return (status); } AuthFileRab.rab$l_rbf = AuthHtRecordUpdatePtr; AuthFileRab.rab$w_rsz = sizeof(AUTH_HTAREC); status = sys$update (&AuthFileRab, 0, 0); if (VMSok (status)) { /* update completed successfully */ if (!LeaveFileOpen) sys$close (&AuthFileFab, 0, 0); return (SS$_NORMAL); } /* RMS error */ sys$close (&AuthFileFab, 0, 0); return (status); } if (AuthHtRecordAddPtr) { /****************/ /* add a record */ /****************/ if (VMSnok (status) && status != RMS$_EOF) { /* RMS error */ sys$close (&AuthFileFab, 0, 0); return (status); } AuthFileRab.rab$l_rbf = AuthHtRecordAddPtr; AuthFileRab.rab$w_rsz = sizeof(AUTH_HTAREC); /* if reached end-of-file then add a record, else update zeroed one */ if (status == RMS$_EOF) status = sys$put (&AuthFileRab, 0, 0); else status = sys$update (&AuthFileRab, 0, 0); if (VMSok (status)) { /* put or update completed successfully */ if (!LeaveFileOpen) sys$close (&AuthFileFab, 0, 0); return (SS$_NORMAL); } /* RMS error */ sys$close (&AuthFileFab, 0, 0); return (status); } /* sanity check error, should never get here */ return (SS$_BUGCHECK); } /****************************************************************************/