/* * * @APPLE_LICENSE_HEADER_START@ * * Copyright (c) 1999-2008 Apple Inc. All Rights Reserved. * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ * */ /* File: QTSSAccessModule.cpp Contains: Implementation of QTSSAccessModule. */ #include "QTSSAccessModule.h" #include "../defaultPaths.h" #include "OSArrayObjectDeleter.h" #include "QTSS_Private.h" #include "StrPtrLen.h" #include "OSMemory.h" #include "MyAssert.h" #include "StringFormatter.h" #include "StrPtrLen.h" #include "StringParser.h" #include "base64.h" #include "OS.h" #include "AccessChecker.h" #include "QTAccessFile.h" #include "QTSSModuleUtils.h" #ifndef __Win32__ #include #endif #include #include // ATTRIBUTES // STATIC DATA #define MODPREFIX_ "modAccess_" static StrPtrLen sSDPSuffix(".sdp"); static OSMutex* sUserMutex = NULL; static Bool16 sDefaultAuthenticationEnabled = true; static Bool16 sAuthenticationEnabled = true; static char* sDefaultUsersFilePath = DEFAULTPATHS_ETC_DIR "qtusers"; static char* sUsersFilePath = NULL; static char* sDefaultGroupsFilePath = DEFAULTPATHS_ETC_DIR "qtgroups"; static char* sGroupsFilePath = NULL; static char* sDefaultAccessFileName = "qtaccess"; static QTSS_AttributeID sBadNameMessageAttrID = qtssIllegalAttrID; static QTSS_AttributeID sUsersFileNotFoundMessageAttrID = qtssIllegalAttrID; static QTSS_AttributeID sGroupsFileNotFoundMessageAttrID = qtssIllegalAttrID; static QTSS_AttributeID sBadUsersFileMessageAttrID = qtssIllegalAttrID; static QTSS_AttributeID sBadGroupsFileMessageAttrID = qtssIllegalAttrID; static QTSS_StreamRef sErrorLogStream = NULL; static QTSS_TextMessagesObject sMessages = NULL; static QTSS_ModulePrefsObject sPrefs = NULL; static QTSS_PrefsObject sServerPrefs = NULL; static AccessChecker** sAccessCheckers; static UInt32 sNumAccessCheckers = 0; static UInt32 sAccessCheckerArraySize = 0; static Bool16 sAllowGuestDefaultEnabled = true; static Bool16 sDefaultGuestEnabled = true; // FUNCTION PROTOTYPES static QTSS_Error QTSSAccessModuleDispatch(QTSS_Role inRole, QTSS_RoleParamPtr inParams); static QTSS_Error Register(); static QTSS_Error Initialize(QTSS_Initialize_Params* inParams); static QTSS_Error Shutdown(); static QTSS_Error RereadPrefs(); static QTSS_Error AuthenticateRTSPRequest(QTSS_RTSPAuth_Params* inParams); static QTSS_Error AccessAuthorizeRTSPRequest(QTSS_StandardRTSP_Params* inParams); static char* GetCheckedFileName(); // FUNCTION IMPLEMENTATIONS QTSS_Error QTSSAccessModule_Main(void* inPrivateArgs) { return _stublibrary_main(inPrivateArgs, QTSSAccessModuleDispatch); } QTSS_Error QTSSAccessModuleDispatch(QTSS_Role inRole, QTSS_RoleParamPtr inParams) { switch (inRole) { case QTSS_Register_Role: return Register(); break; case QTSS_Initialize_Role: return Initialize(&inParams->initParams); break; case QTSS_RereadPrefs_Role: return RereadPrefs(); break; case QTSS_RTSPAuthenticate_Role: if (sAuthenticationEnabled) return AuthenticateRTSPRequest(&inParams->rtspAthnParams); break; case QTSS_RTSPAuthorize_Role: if (sAuthenticationEnabled) return AccessAuthorizeRTSPRequest(&inParams->rtspRequestParams); break; case QTSS_Shutdown_Role: return Shutdown(); break; } return QTSS_NoErr; } QTSS_Error Register() { // Do role & attribute setup (void)QTSS_AddRole(QTSS_Initialize_Role); (void)QTSS_AddRole(QTSS_RereadPrefs_Role); (void)QTSS_AddRole(QTSS_RTSPAuthenticate_Role); (void)QTSS_AddRole(QTSS_RTSPAuthorize_Role); // Add AuthenticateName and Password attributes static char* sBadAccessFileName = "QTSSAccessModuleBadAccessFileName"; static char* sUsersFileNotFound = "QTSSAccessModuleUsersFileNotFound"; static char* sGroupsFileNotFound = "QTSSAccessModuleGroupsFileNotFound"; static char* sBadUsersFile = "QTSSAccessModuleBadUsersFile"; static char* sBadGroupsFile = "QTSSAccessModuleBadGroupsFile"; (void)QTSS_AddStaticAttribute(qtssTextMessagesObjectType, sBadAccessFileName, NULL, qtssAttrDataTypeCharArray); (void)QTSS_IDForAttr(qtssTextMessagesObjectType, sBadAccessFileName, &sBadNameMessageAttrID); (void)QTSS_AddStaticAttribute(qtssTextMessagesObjectType, sUsersFileNotFound, NULL, qtssAttrDataTypeCharArray); (void)QTSS_IDForAttr(qtssTextMessagesObjectType, sUsersFileNotFound, &sUsersFileNotFoundMessageAttrID); (void)QTSS_AddStaticAttribute(qtssTextMessagesObjectType, sGroupsFileNotFound, NULL, qtssAttrDataTypeCharArray); (void)QTSS_IDForAttr(qtssTextMessagesObjectType, sGroupsFileNotFound, &sGroupsFileNotFoundMessageAttrID); (void)QTSS_AddStaticAttribute(qtssTextMessagesObjectType, sBadUsersFile, NULL, qtssAttrDataTypeCharArray); (void)QTSS_IDForAttr(qtssTextMessagesObjectType, sBadUsersFile, &sBadUsersFileMessageAttrID); (void)QTSS_AddStaticAttribute(qtssTextMessagesObjectType, sBadGroupsFile, NULL, qtssAttrDataTypeCharArray); (void)QTSS_IDForAttr(qtssTextMessagesObjectType, sBadGroupsFile, &sBadGroupsFileMessageAttrID); return QTSS_NoErr; } QTSS_Error Initialize(QTSS_Initialize_Params* inParams) { // Create an array of AccessCheckers sAccessCheckers = NEW AccessChecker*[2]; sAccessCheckers[0] = NEW AccessChecker(); sNumAccessCheckers = 1; sAccessCheckerArraySize = 2; // Setup module utils QTSSModuleUtils::Initialize(inParams->inMessages, inParams->inServer, inParams->inErrorLogStream); sErrorLogStream = inParams->inErrorLogStream; sMessages = inParams->inMessages; sPrefs = QTSSModuleUtils::GetModulePrefsObject(inParams->inModule); sServerPrefs = inParams->inPrefs; sUserMutex = NEW OSMutex(); RereadPrefs(); QTAccessFile::Initialize(); return QTSS_NoErr; } QTSS_Error Shutdown() { //cleanup // delete all the AccessCheckers UInt32 index; for(index = 0; index < sNumAccessCheckers; index++) delete sAccessCheckers[index]; delete[] sAccessCheckers; sNumAccessCheckers = 0; // delete the main users and groups path //if(sUsersFilePath != sDefaultUsersFilePath) // sUsersFilePath is assigned by a call to QTSSModuleUtils::GetStringAttribute which always // allocates memory even if it just returns the default value delete[] sUsersFilePath; sUsersFilePath = NULL; //if(sGroupsFilePath != sDefaultGroupsFilePath) // sGroupsFilePath is assigned by a call to QTSSModuleUtils::GetStringAttribute which always // allocates memory even if it just returns the default value delete[] sGroupsFilePath; sGroupsFilePath = NULL; return QTSS_NoErr; } char* GetCheckedFileName() { char *result = NULL; static char *badChars = "/'\""; char theBadCharMessage[] = "' '"; char *theBadChar = NULL; result = QTSSModuleUtils::GetStringAttribute(sPrefs, MODPREFIX_"qtaccessfilename", sDefaultAccessFileName); StrPtrLen searchStr(result); theBadChar = strpbrk(searchStr.Ptr, badChars); if ( theBadChar!= NULL) { theBadCharMessage[1] = theBadChar[0]; QTSSModuleUtils::LogError(qtssWarningVerbosity,sBadNameMessageAttrID, 0, theBadCharMessage, result); delete[] result; result = NEW char[::strlen(sDefaultAccessFileName) + 2]; ::strcpy(result, sDefaultAccessFileName); } return result; } QTSS_Error RereadPrefs() { OSMutexLocker locker(sUserMutex); // // Use the standard GetAttribute routine to retrieve the correct values for our preferences QTSSModuleUtils::GetAttribute(sPrefs, MODPREFIX_"enabled", qtssAttrDataTypeBool16, &sAuthenticationEnabled, &sDefaultAuthenticationEnabled, sizeof(sAuthenticationEnabled)); //if(sUsersFilePath != sDefaultUsersFilePath) // sUsersFilePath is assigned by a call to QTSSModuleUtils::GetStringAttribute which always // allocates memory even if it just returns the default value // delete this old memory before reassigning it to new memory delete[] sUsersFilePath; sUsersFilePath = NULL; //if(sGroupsFilePath != sDefaultGroupsFilePath) // sGroupsFilePath is assigned by a call to QTSSModuleUtils::GetStringAttribute which always // allocates memory even if it just returns the default value // delete this old memory before reassigning it to new memory delete[] sGroupsFilePath; sGroupsFilePath = NULL; sUsersFilePath = QTSSModuleUtils::GetStringAttribute(sPrefs, MODPREFIX_"usersfilepath", sDefaultUsersFilePath); sGroupsFilePath = QTSSModuleUtils::GetStringAttribute(sPrefs, MODPREFIX_"groupsfilepath", sDefaultGroupsFilePath); // GetCheckedFileName always allocates memory char* accessFile = GetCheckedFileName(); // QTAccessFile::SetAccessFileName makes its own copy, // so delete the previous allocated memory after this call QTAccessFile::SetAccessFileName(accessFile); delete [] accessFile; if(sAccessCheckers[0]->HaveFilePathsChanged(sUsersFilePath, sGroupsFilePath)) { sAccessCheckers[0]->UpdateFilePaths(sUsersFilePath, sGroupsFilePath); UInt32 err; err = sAccessCheckers[0]->UpdateUserProfiles(); if(err & AccessChecker::kUsersFileNotFoundErr) QTSSModuleUtils::LogError(qtssWarningVerbosity,sUsersFileNotFoundMessageAttrID, 0, sUsersFilePath, NULL); else if(err & AccessChecker::kBadUsersFileErr) QTSSModuleUtils::LogError(qtssWarningVerbosity,sBadUsersFileMessageAttrID, 0, sUsersFilePath, NULL); if(err & AccessChecker::kGroupsFileNotFoundErr) QTSSModuleUtils::LogError(qtssWarningVerbosity,sGroupsFileNotFoundMessageAttrID, 0, sGroupsFilePath, NULL); else if(err & AccessChecker::kBadGroupsFileErr) QTSSModuleUtils::LogError(qtssWarningVerbosity,sBadGroupsFileMessageAttrID, 0, sGroupsFilePath, NULL); } QTSSModuleUtils::GetAttribute(sServerPrefs,"enable_allow_guest_default", qtssAttrDataTypeBool16, &sAllowGuestDefaultEnabled,(void *)&sDefaultGuestEnabled, sizeof(sAllowGuestDefaultEnabled)); return QTSS_NoErr; } QTSS_Error AuthenticateRTSPRequest(QTSS_RTSPAuth_Params* inParams) { QTSS_RTSPRequestObject theRTSPRequest = inParams->inRTSPRequest; UInt32 fileErr; OSMutexLocker locker(sUserMutex); if ( (NULL == inParams) || (NULL == inParams->inRTSPRequest) ) return QTSS_RequestFailed; // Get the user profile object from the request object QTSS_UserProfileObject theUserProfile = NULL; UInt32 len = sizeof(QTSS_UserProfileObject); QTSS_Error theErr = QTSS_GetValue(theRTSPRequest, qtssRTSPReqUserProfile, 0, (void*)&theUserProfile, &len); Assert(len == sizeof(QTSS_UserProfileObject)); if (theErr != QTSS_NoErr) return theErr; Bool16 defaultPaths = true; // Check for a users and groups file in the access file // For this, first get local file path and root movie directory //get the local file path char* pathBuffStr = QTSSModuleUtils::GetLocalPath_Copy(theRTSPRequest); OSCharArrayDeleter pathBuffDeleter(pathBuffStr); if (NULL == pathBuffStr) return QTSS_RequestFailed; //get the root movie directory char* movieRootDirStr = QTSSModuleUtils::GetMoviesRootDir_Copy(theRTSPRequest); OSCharArrayDeleter movieRootDeleter(movieRootDirStr); if (NULL == movieRootDirStr) return QTSS_RequestFailed; // Now get the access file path char* accessFilePath = QTAccessFile::GetAccessFile_Copy(movieRootDirStr, pathBuffStr); OSCharArrayDeleter accessFilePathDeleter(accessFilePath); // Parse the access file for the AuthUserFile and AuthGroupFile keywords char* usersFilePath = NULL; char* groupsFilePath = NULL; // Get the request action from the request object QTSS_ActionFlags action = qtssActionFlagsNoFlags; len = sizeof(action); theErr = QTSS_GetValue(theRTSPRequest, qtssRTSPReqAction, 0, (void*)&action, &len); Assert(len == sizeof(action)); if (theErr != QTSS_NoErr) return theErr; // Allocates memory for usersFilePath and groupsFilePath QTSS_AuthScheme authScheme = QTAccessFile::FindUsersAndGroupsFilesAndAuthScheme(accessFilePath, action, &usersFilePath, &groupsFilePath); if((usersFilePath != NULL) || (groupsFilePath != NULL)) defaultPaths = false; if(usersFilePath == NULL) usersFilePath = strdup(sUsersFilePath); if(groupsFilePath == NULL) groupsFilePath = strdup(sGroupsFilePath); OSCharArrayDeleter userPathDeleter(usersFilePath); OSCharArrayDeleter groupPathDeleter(groupsFilePath); AccessChecker* currentChecker = NULL; UInt32 index; // If the default users and groups file are not the ones we need if(!defaultPaths) { // check if there is one AccessChecker that matches the needed paths // Don't have to check for the first one (or element zero) because it has the default paths for(index = 1; index < sNumAccessCheckers; index++) { // If an access checker that matches the users and groups file paths is found if(!sAccessCheckers[index]->HaveFilePathsChanged(usersFilePath, groupsFilePath)) { currentChecker = sAccessCheckers[index]; break; } } // If an existing AccessChecker for the needed paths isn't found if(currentChecker == NULL) { // Grow the AccessChecker array if needed if(sNumAccessCheckers == sAccessCheckerArraySize) { AccessChecker** oldAccessCheckers = sAccessCheckers; sAccessCheckers = NEW AccessChecker*[sAccessCheckerArraySize * 2]; for(index = 0; index < sNumAccessCheckers; index++) { sAccessCheckers[index] = oldAccessCheckers[index]; } sAccessCheckerArraySize *= 2; delete [] oldAccessCheckers; } // And create a new AccessChecker for the paths sAccessCheckers[sNumAccessCheckers] = NEW AccessChecker(); sAccessCheckers[sNumAccessCheckers]->UpdateFilePaths(usersFilePath, groupsFilePath); fileErr = sAccessCheckers[sNumAccessCheckers]->UpdateUserProfiles(); if(fileErr & AccessChecker::kUsersFileNotFoundErr) QTSSModuleUtils::LogError(qtssWarningVerbosity,sUsersFileNotFoundMessageAttrID, 0, usersFilePath, NULL); else if(fileErr & AccessChecker::kBadUsersFileErr) QTSSModuleUtils::LogError(qtssWarningVerbosity,sBadUsersFileMessageAttrID, 0, usersFilePath, NULL); if(fileErr & AccessChecker::kGroupsFileNotFoundErr) QTSSModuleUtils::LogError(qtssWarningVerbosity,sGroupsFileNotFoundMessageAttrID, 0, groupsFilePath, NULL); else if(fileErr & AccessChecker::kBadGroupsFileErr) QTSSModuleUtils::LogError(qtssWarningVerbosity,sBadGroupsFileMessageAttrID, 0, groupsFilePath, NULL); currentChecker = sAccessCheckers[sNumAccessCheckers]; sNumAccessCheckers++; } } else { currentChecker = sAccessCheckers[0]; } // Before retrieving the user profile information // check if the groups/users files have been modified and update them otherwise fileErr = currentChecker->UpdateUserProfiles(); /* // This is for logging the errors if users file and/or the groups file is not found or corrupted char* usersFile = currentChecker->GetUsersFilePathPtr(); char* groupsFile = currentChecker->GetGroupsFilePathPtr(); if(fileErr & AccessChecker::kUsersFileNotFoundErr) QTSSModuleUtils::LogError(qtssWarningVerbosity,sUsersFileNotFoundMessageAttrID, 0, usersFile, NULL); else if(fileErr & AccessChecker::kBadUsersFileErr) QTSSModuleUtils::LogError(qtssWarningVerbosity,sBadUsersFileMessageAttrID, 0, usersFile, NULL); if(fileErr & AccessChecker::kGroupsFileNotFoundErr) QTSSModuleUtils::LogError(qtssWarningVerbosity,sGroupsFileNotFoundMessageAttrID, 0, groupsFile, NULL); else if(fileErr & AccessChecker::kBadGroupsFileErr) QTSSModuleUtils::LogError(qtssWarningVerbosity,sBadGroupsFileMessageAttrID, 0, groupsFile, NULL); */ // Retrieve the password data and group information for the user and set them // in the qtssRTSPReqUserProfile attr // The password data is crypt of the real password for Basic authentication // and it is MD5(username:realm:password) for Digest authentication // It the access file didn't contain an auth scheme, then get the auth scheme out of the request object // else, set the qtssRTSPReqAuthScheme to that found in the access file if (authScheme == qtssAuthNone) { // Get the authentication scheme from the request object len = sizeof(authScheme); theErr = QTSS_GetValue(theRTSPRequest, qtssRTSPReqAuthScheme, 0, (void*)&authScheme, &len); Assert(len == sizeof(authScheme)); if (theErr != QTSS_NoErr) return theErr; } else { theErr = QTSS_SetValue(theRTSPRequest, qtssRTSPReqAuthScheme, 0, (void*)&authScheme, sizeof(authScheme)); if (theErr != QTSS_NoErr) return theErr; } // Set the qtssUserRealm to the realm value retrieved from the users file // This should be used for digest auth scheme, and if no realm is found in the qtaccess file, then // it should be used for basic auth scheme. // No memory is allocated; just a pointer is returned StrPtrLen* authRealm = currentChecker->GetAuthRealm(); (void)QTSS_SetValue(theUserProfile, qtssUserRealm, 0, (void*)(authRealm->Ptr), (authRealm->Len)); // Get the username from the user profile object char* usernameBuf = NULL; theErr = QTSS_GetValueAsString(theUserProfile, qtssUserName, 0, &usernameBuf); OSCharArrayDeleter usernameBufDeleter(usernameBuf); StrPtrLen username(usernameBuf); if (theErr != QTSS_NoErr) return theErr; // No memory is allocated; just a pointer to the profile is returned AccessChecker::UserProfile* profile = currentChecker->RetrieveUserProfile(&username); if(profile == NULL) return QTSS_NoErr; // Set the qtssUserPassword attribute to either the crypted password or the digest password // based on the authentication scheme if (authScheme == qtssAuthBasic) (void)QTSS_SetValue(theUserProfile, qtssUserPassword, 0, (void*)((profile->cryptPassword).Ptr), (profile->cryptPassword).Len); else if (authScheme == qtssAuthDigest) (void)QTSS_SetValue(theUserProfile, qtssUserPassword, 0, (void*)((profile->digestPassword).Ptr), (profile->digestPassword).Len); // Set the multivalued qtssUserGroups attr to the groups the user belongs to, if any UInt32 maxLen = profile->maxGroupNameLen; for(index = 0; index < profile->numGroups; index++) { UInt32 curLen = ::strlen(profile->groups[index]); if(curLen < maxLen) { char* groupWithPaddedZeros = NEW char[maxLen]; // memory allocated ::memcpy(groupWithPaddedZeros, profile->groups[index], curLen); ::memset(groupWithPaddedZeros+curLen, '\0', maxLen-curLen); (void)QTSS_SetValue(theUserProfile, qtssUserGroups, index, (void*)groupWithPaddedZeros, maxLen); delete [] groupWithPaddedZeros; // memory deleted } else { (void)QTSS_SetValue(theUserProfile, qtssUserGroups, index, (void*)(profile->groups[index]), maxLen); } } return QTSS_NoErr; } QTSS_Error AccessAuthorizeRTSPRequest(QTSS_StandardRTSP_Params* inParams) { Bool16 allowNoAccessFiles = sAllowGuestDefaultEnabled; //no access files allowed means allowing guest access (unknown users) QTSS_ActionFlags noAction = ~qtssActionFlagsRead; // allow any action QTSS_ActionFlags authorizeAction = QTSSModuleUtils::GetRequestActions(inParams->inRTSPRequest); Bool16 authorized =false; Bool16 allowAnyUser = false; QTAccessFile accessFile; return accessFile.AuthorizeRequest(inParams,allowNoAccessFiles, noAction, authorizeAction, &authorized, &allowAnyUser); }