/* * * @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: QTAccessFile.cpp Contains: This file contains the implementation for finding and parsing qtaccess files. */ #include #include #include #include "SafeStdLib.h" #include "QTSS.h" #include "OSHeaders.h" #include "StrPtrLen.h" #include "StringParser.h" #include "QTSSModuleUtils.h" #include "OSFileSource.h" #include "OSMemory.h" #include "OSHeaders.h" #include "QTAccessFile.h" #include "OSArrayObjectDeleter.h" #include #if __MacOSX__ #include #endif #include #include #include #define DEBUG_QTACCESS 0 #define debug_printf if (DEBUG_QTACCESS) qtss_printf char* QTAccessFile::sAccessValidUser = "require valid-user\n"; char* QTAccessFile::sAccessAnyUser = "require any-user\n"; UInt8 QTAccessFile::sWhitespaceAndGreaterThanMask[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, //0-9 // \t is a stop 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, //10-19 //'\r' & '\n' are stop conditions 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //20-29 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, //30-39 ' ' is a stop 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //40-49 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //50-59 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, //60-69 // '>' is a stop 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //70-79 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //80-89 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //90-99 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //100-109 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //110-119 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //120-129 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //130-139 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //140-149 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //150-159 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //160-169 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //170-179 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //180-189 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //190-199 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //200-209 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //210-219 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //220-229 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //230-239 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //240-249 0, 0, 0, 0, 0, 0 //250-255 }; char* QTAccessFile::sQTAccessFileName = "qtaccess"; Bool16 QTAccessFile::sAllocatedName = false; OSMutex* QTAccessFile::sAccessFileMutex = NULL;//QTAccessFile isn't reentrant const int kBuffLen = 512; void QTAccessFile::Initialize() // called by server at initialize never call again { if (NULL == sAccessFileMutex) { sAccessFileMutex = NEW OSMutex(); } } void QTAccessFile::SetAccessFileName(const char *inQTAccessFileName) { OSMutexLocker locker(sAccessFileMutex); if (NULL == inQTAccessFileName) { Assert(NULL != inQTAccessFileName); return; } if (sAllocatedName) { delete [] sQTAccessFileName; } sAllocatedName = true; sQTAccessFileName = NEW char[strlen(inQTAccessFileName)+1]; ::strcpy(sQTAccessFileName, inQTAccessFileName); } Bool16 QTAccessFile::HaveUser(char *userName, void* extraDataPtr) { Bool16 result = false; if (NULL != userName && 0 != userName[0]) result = true; return result; } Bool16 QTAccessFile::HaveGroups( char** groupArray, UInt32 numGroups, void* extraDataPtr) { Bool16 result = false; if (numGroups > 0 && groupArray != NULL) result = true; return result; } Bool16 QTAccessFile::HaveRealm( char *userName, StrPtrLen* ioRealmNameStr, void *extraData ) { Bool16 result = false; if (ioRealmNameStr != NULL && ioRealmNameStr->Ptr != NULL && ioRealmNameStr->Len > 0) result = true; return result; } void QTAccessFile::GetRealm(StrPtrLen* accessRealm, StrPtrLen* ioRealmNameStr, char *userName,void *extraDataPtr ) { if (ioRealmNameStr->Len <= accessRealm->Len) accessRealm->Len = ioRealmNameStr->Len -1; // just copy what we can ::memcpy(ioRealmNameStr->Ptr, accessRealm->Ptr,accessRealm->Len); ioRealmNameStr->Ptr[accessRealm->Len] = 0; } Bool16 QTAccessFile::TestUser(StrPtrLen* accessUser, char *userName,void *extraDataPtr ) { Bool16 result = false; if ( accessUser->Equal(userName) ) result = true; return result; } Bool16 QTAccessFile::TestGroup( StrPtrLen* accessGroup, char *userName, char**groupArray, UInt32 numGroups, void *extraDataPtr) { for (UInt32 index = 0; index < numGroups; index ++) { if(accessGroup->Equal(groupArray[index])) return true; } return false; } Bool16 QTAccessFile::TestExtraData( StrPtrLen* wordPtr, StringParser* lineParserPtr, void* extraDataPtr) { return false; } Bool16 QTAccessFile::AccessAllowed ( char *userName, char**groupArray, UInt32 numGroups, StrPtrLen *accessFileBufPtr, QTSS_ActionFlags inFlags,StrPtrLen* ioRealmNameStr, Bool16 *outAllowAnyUserPtr, void *extraDataPtr ) { if (NULL == accessFileBufPtr || NULL == accessFileBufPtr->Ptr || 0 == accessFileBufPtr->Len) return true; // nothing to check if (ioRealmNameStr != NULL && ioRealmNameStr->Ptr != NULL && ioRealmNameStr->Len > 0) ioRealmNameStr->Ptr[0] = 0; StringParser accessFileParser(accessFileBufPtr); QTSS_ActionFlags currentFlags = qtssActionFlagsRead; StrPtrLen line; StrPtrLen word; Bool16 haveUserName = false; Bool16 haveRealmResultBuffer = false; Bool16 haveGroups = false; *outAllowAnyUserPtr = false; haveUserName = HaveUser(userName, extraDataPtr); haveGroups = HaveGroups(groupArray, numGroups, extraDataPtr); haveRealmResultBuffer = HaveRealm(userName, ioRealmNameStr, extraDataPtr ); while( accessFileParser.GetDataRemaining() != 0 ) { accessFileParser.GetThruEOL(&line); // Read each line StringParser lineParser(&line); lineParser.ConsumeWhitespace();//skip over leading whitespace if (lineParser.GetDataRemaining() == 0) // must be an empty line continue; char firstChar = lineParser.PeekFast(); if ( (firstChar == '#') || (firstChar == '\0') ) continue; //skip over comments and blank lines... lineParser.ConsumeUntilWhitespace(&word); if ( word.Equal(" or while (word.Len != 0) // compare each word in the line { if (word.Equal("WRITE") ) { currentFlags |= inFlags & qtssActionFlagsWrite; // accept following lines if inFlags has write } if (word.Equal("READ") ) { currentFlags |= inFlags & qtssActionFlagsRead; // accept following lines if inFlags has read } lineParser.ConsumeWhitespace(); lineParser.ConsumeUntil(&word, sWhitespaceAndGreaterThanMask); } continue; //done with limit line } if ( word.Equal("") ) currentFlags = qtssActionFlagsRead; // set the current access state to the default of read access if (0 == (currentFlags & inFlags)) continue; // ignore lines because inFlags doesn't match the current access state if (haveRealmResultBuffer && word.Equal("AuthName") ) //realm name { lineParser.ConsumeWhitespace(); lineParser.GetThruEOL(&word); StringParser::UnQuote(&word);// if the parsed string is surrounded by quotes then remove them. GetRealm(&word, ioRealmNameStr, userName, extraDataPtr ); // we don't change the buffer len ioRealmNameStr->Len because we might have another AuthName tag to copy continue; // done with AuthName (realm) } if (word.Equal("require") ) { lineParser.ConsumeWhitespace(); lineParser.ConsumeUntilWhitespace(&word); if (haveUserName && word.Equal("valid-user") ) { return true; } if ( word.Equal("any-user") ) { *outAllowAnyUserPtr = true; return true; } if (!haveUserName) continue; if (word.Equal("user") ) { lineParser.ConsumeWhitespace(); lineParser.ConsumeUntilWhitespace(&word); while (word.Len != 0) // compare each word in the line { if (TestUser(&word, userName, extraDataPtr )) return true; lineParser.ConsumeWhitespace(); lineParser.ConsumeUntilWhitespace(&word); } continue; // done with "require user" line } if (haveGroups && word.Equal("group")) // check if we have groups for the user { lineParser.ConsumeWhitespace(); lineParser.ConsumeUntilWhitespace(&word); while (word.Len != 0) // compare each word in the line { if (TestGroup(&word, userName, groupArray, numGroups, extraDataPtr) ) return true; lineParser.ConsumeWhitespace(); lineParser.ConsumeUntilWhitespace(&word); } continue; // done with "require group" line } if (TestExtraData(&word, &lineParser, extraDataPtr)) return true; continue; // done with unparsed "require" line } } return false; // user or group not found } char* QTAccessFile::GetAccessFile_Copy( const char* movieRootDir, const char* dirPath) { OSMutexLocker locker(sAccessFileMutex); char* currentDir= NULL; char* lastSlash = NULL; int movieRootDirLen = ::strlen(movieRootDir); int maxLen = strlen(dirPath)+strlen(sQTAccessFileName) + strlen(kPathDelimiterString) + 1; currentDir = NEW char[maxLen]; ::strcpy(currentDir, dirPath); //strip off filename lastSlash = ::strrchr(currentDir, kPathDelimiterChar); if (lastSlash != NULL) lastSlash[0] = '\0'; //check qtaccess files while ( true ) //walk backward up the dir tree. { int curLen = strlen(currentDir) + strlen(sQTAccessFileName) + strlen(kPathDelimiterString); if ( curLen >= maxLen ) break; ::strcat(currentDir, kPathDelimiterString); ::strcat(currentDir, sQTAccessFileName); QTSS_Object fileObject = NULL; if( QTSS_OpenFileObject(currentDir, qtssOpenFileNoFlags, &fileObject) == QTSS_NoErr) { (void)QTSS_CloseFileObject(fileObject); return currentDir; } //strip off the "/qtaccess" lastSlash = ::strrchr(currentDir, kPathDelimiterChar); lastSlash[0] = '\0'; //strip of the tailing directory lastSlash = ::strrchr(currentDir, kPathDelimiterChar); if (lastSlash == NULL) break; else lastSlash[0] = '\0'; if ( (lastSlash-currentDir) < movieRootDirLen ) //bail if we start eating our way out of fMovieRootDir break; } delete[] currentDir; return NULL; } // allocates memory for outUsersFilePath and outGroupsFilePath - remember to delete // returns the auth scheme QTSS_AuthScheme QTAccessFile::FindUsersAndGroupsFilesAndAuthScheme(char* inAccessFilePath, QTSS_ActionFlags inAction, char** outUsersFilePath, char** outGroupsFilePath) { QTSS_AuthScheme authScheme = qtssAuthNone; QTSS_ActionFlags currentFlags = qtssActionFlagsRead; if (inAccessFilePath == NULL) return authScheme; *outUsersFilePath = NULL; *outGroupsFilePath = NULL; //Assert(outUsersFilePath == NULL); //Assert(outGroupsFilePath == NULL); StrPtrLen accessFileBuf; (void)QTSSModuleUtils::ReadEntireFile(inAccessFilePath, &accessFileBuf); OSCharArrayDeleter accessFileBufDeleter(accessFileBuf.Ptr); StringParser accessFileParser(&accessFileBuf); StrPtrLen line; StrPtrLen word; while( accessFileParser.GetDataRemaining() != 0 ) { accessFileParser.GetThruEOL(&line); // Read each line StringParser lineParser(&line); lineParser.ConsumeWhitespace(); //skip over leading whitespace if (lineParser.GetDataRemaining() == 0) // must be an empty line continue; char firstChar = lineParser.PeekFast(); if ( (firstChar == '#') || (firstChar == '\0') ) continue; //skip over comments and blank lines... lineParser.ConsumeUntilWhitespace(&word); if ( word.Equal(" or while (word.Len != 0) // compare each word in the line { if (word.Equal("WRITE") ) { currentFlags |= inAction & qtssActionFlagsWrite; // accept following lines if inFlags has write } if (word.Equal("READ") ) { currentFlags |= inAction & qtssActionFlagsRead; // accept following lines if inFlags has read } lineParser.ConsumeWhitespace(); lineParser.ConsumeUntil(&word, sWhitespaceAndGreaterThanMask); } continue; //done with limit line } if ( word.Equal("") ) currentFlags = qtssActionFlagsRead; // set the current access state to the default of read access if (0 == (currentFlags & inAction)) continue; // ignore lines because inFlags doesn't match the current access state if (word.Equal("AuthUserFile") ) { lineParser.ConsumeWhitespace(); lineParser.GetThruEOL(&word); StringParser::UnQuote(&word);// if the parsed string is surrounded by quotes then remove them. if(*outUsersFilePath != NULL) // we are encountering the AuthUserFile keyword twice! delete[] *outUsersFilePath; // The last one found takes precedence...delete the previous path *outUsersFilePath = word.GetAsCString(); continue; } if (word.Equal("AuthGroupFile") ) { lineParser.ConsumeWhitespace(); lineParser.GetThruEOL(&word); StringParser::UnQuote(&word);// if the parsed string is surrounded by quotes then remove them. if(*outGroupsFilePath != NULL) // we are encountering the AuthGroupFile keyword twice! delete[] *outGroupsFilePath; // The last one found takes precedence...delete the previous path *outGroupsFilePath = word.GetAsCString(); continue; } if (word.Equal("AuthScheme") ) { lineParser.ConsumeWhitespace(); lineParser.GetThruEOL(&word); StringParser::UnQuote(&word);// if the parsed string is surrounded by quotes then remove them. if (word.Equal("basic")) authScheme = qtssAuthBasic; else if (word.Equal("digest")) authScheme = qtssAuthDigest; continue; } } return authScheme; } QTSS_Error QTAccessFile::AuthorizeRequest(QTSS_StandardRTSP_Params* inParams, Bool16 allowNoAccessFiles, QTSS_ActionFlags noAction, QTSS_ActionFlags authorizeAction, Bool16 *outAuthorizedPtr, Bool16 *outAllowAnyUserPtr) { if ( (NULL == inParams) || (NULL == inParams->inRTSPRequest) || (NULL == outAllowAnyUserPtr) || (NULL == outAuthorizedPtr) ) return QTSS_RequestFailed; *outAllowAnyUserPtr = false; *outAuthorizedPtr = false; QTSS_RTSPRequestObject theRTSPRequest = inParams->inRTSPRequest; // get the type of request // Don't touch write requests QTSS_ActionFlags action = QTSSModuleUtils::GetRequestActions(theRTSPRequest); if(action == qtssActionFlagsNoFlags) return QTSS_RequestFailed; if( (action & noAction) != 0) return QTSS_NoErr; // we don't handle //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; QTSS_UserProfileObject theUserProfile = QTSSModuleUtils::GetUserProfileObject(theRTSPRequest); if (NULL == theUserProfile) return QTSS_RequestFailed; char* accessFilePath = QTAccessFile::GetAccessFile_Copy(movieRootDirStr, pathBuffStr); OSCharArrayDeleter accessFilePathDeleter(accessFilePath); char* username = QTSSModuleUtils::GetUserName_Copy(theUserProfile); OSCharArrayDeleter usernameDeleter(username); UInt32 numGroups = 0; char** groupCharPtrArray = QTSSModuleUtils::GetGroupsArray_Copy(theUserProfile, &numGroups); OSCharPointerArrayDeleter groupCharPtrArrayDeleter(groupCharPtrArray); StrPtrLen accessFileBuf; (void)QTSSModuleUtils::ReadEntireFile(accessFilePath, &accessFileBuf); OSCharArrayDeleter accessFileBufDeleter(accessFileBuf.Ptr); if (accessFileBuf.Len == 0 && !allowNoAccessFiles) { accessFileBuf.Set(sAccessValidUser); if (DEBUG_QTACCESS) qtss_printf("QTAccessFile::AuthorizeRequest SET Accessfile valid user for no accessfile %s\n", sAccessValidUser); } if (accessFileBuf.Len == 0 && allowNoAccessFiles) { accessFileBuf.Set(sAccessAnyUser); if (DEBUG_QTACCESS) qtss_printf("QTAccessFile::AuthorizeRequest SET Accessfile any user for no access file %s\n", sAccessAnyUser); } char realmName[kBuffLen] = { 0 }; StrPtrLen realmNameStr(realmName,kBuffLen -1); //check if this user is allowed to see this movie Bool16 allowRequest = this->AccessAllowed(username, groupCharPtrArray, numGroups, &accessFileBuf, authorizeAction,&realmNameStr, outAllowAnyUserPtr ); debug_printf("accessFile.AccessAllowed for user=%s returned %d\n", username, allowRequest); // Get the auth scheme QTSS_AuthScheme theAuthScheme = qtssAuthNone; UInt32 len = sizeof(theAuthScheme); QTSS_Error theErr = QTSS_GetValue(theRTSPRequest, qtssRTSPReqAuthScheme, 0, (void*)&theAuthScheme, &len); Assert(len == sizeof(theAuthScheme)); if(theErr != QTSS_NoErr) return theErr; // If auth scheme is basic and the realm is present in the access file, use it if((theAuthScheme == qtssAuthBasic) && (realmNameStr.Ptr[0] != '\0')) //set the realm if we have one (void) QTSS_SetValue(theRTSPRequest,qtssRTSPReqURLRealm, 0, realmNameStr.Ptr, ::strlen(realmNameStr.Ptr)); else // if auth scheme is basic and no realm is present, or if the auth scheme is digest, use the realm from the users file { char* userRealm = NULL; (void) QTSS_GetValueAsString(theUserProfile, qtssUserRealm, 0, &userRealm); if(userRealm != NULL) { OSCharArrayDeleter userRealmDeleter(userRealm); (void) QTSS_SetValue(theRTSPRequest,qtssRTSPReqURLRealm, 0, userRealm, ::strlen(userRealm)); } } *outAuthorizedPtr = allowRequest; Bool16 founduser = this->HaveUser(username, NULL); Bool16 authContinue = true; char nameBuff[256]; StrPtrLen reqNameStr(nameBuff, kBuffLen); StrPtrLen profileNameStr(username); theErr = QTSS_GetValue (theRTSPRequest,qtssRTSPReqUserName,0, (void *) reqNameStr.Ptr, &reqNameStr.Len); if (DEBUG_QTACCESS) { qtss_printf("QTAccessFile::AuthorizeRequest qtaccess profile user =%s ", username); reqNameStr.PrintStr("request user=","\n"); qtss_printf("QTAccessFile::AuthorizeRequest allowRequest=%d founduser=%d authContinue=%d\n", allowRequest, founduser, authContinue); } if (allowRequest && founduser) theErr = QTSSModuleUtils::AuthorizeRequest(theRTSPRequest, &allowRequest, &founduser,&authContinue); if (!allowRequest && !founduser) theErr = QTSSModuleUtils::AuthorizeRequest(theRTSPRequest, &allowRequest, &founduser,&authContinue); if (DEBUG_QTACCESS) { qtss_printf("QTAccessFile::AuthorizeRequest QTSSModuleUtils::AuthorizeRequest qtaccess profile user =%s ", username); reqNameStr.PrintStr("request user=","\n"); qtss_printf("QTAccessFile::AuthorizeRequest allowRequest=%d founduser=%d authContinue=%d\n", allowRequest, founduser, authContinue); } return theErr; } bool DSAccessFile::CheckGroupMembership(const char* inUsername, const char* inGroupName) { struct passwd *user = NULL; struct group *group = NULL; #if __MacOSX__ uuid_t userID; uuid_t groupID; int isMember = 0; #else int numGroups = 50; gid_t *groupList; groupList = (gid_t *) malloc(numGroups * sizeof(gid_t)); #endif // Look up the user using the POSIX APIs: only care about the UID. user = getpwnam(inUsername); if ( user == NULL ) return false; #if __MacOSX__ uuid_clear(userID); if ( mbr_uid_to_uuid(user->pw_uid, userID) ) return false; #endif // Look up the group using the POSIX APIs: only care about the GID. group = getgrnam(inGroupName); endgrent(); if ( group == NULL ) return false; #if __MacOSX__ uuid_clear(groupID); if ( mbr_gid_to_uuid(group->gr_gid, groupID) ) return false; // In Tiger, group membership is painfully simple: we ask memberd for it! // mbr_check_membership() returns 0 on success and error code on failure. if ( mbr_check_membership(userID, groupID, &isMember) ) return false; return (bool)isMember; #else if(getgrouplist(inUsername, user->pw_gid, groupList, &numGroups) == -1) { qtss_printf("QTAccessFile::CheckGroupMembership getgrouplist for user %s returned -1 and numGroups = %d", inUsername, numGroups); return false; } for(int i = 0; i < numGroups; i++) { if(groupList[i] == group->gr_gid) { return true; } } return false; #endif } Bool16 DSAccessFile::ValidUser( char*userName, void* extraDataPtr) { struct passwd *user = getpwnam(userName); Bool16 result =true; if ( user == NULL ) { return result; } return true; }