/*
 *
 * @APPLE_LICENSE_HEADER_START@
 *
 * Copyright (c) 1999-2001 Apple Computer, Inc.  All Rights Reserved. The
 * contents of this file constitute Original Code as defined in and are
 * subject to the Apple Public Source License Version 1.2 (the 'License').
 * You may not use this file except in compliance with the License.  Please
 * obtain a copy of the License at http://www.apple.com/publicsource and
 * read it before using this file.
 *
 * This 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:		QTSSFilePrivsModule.cpp

	Contains:	Implementation of QTSSFilePrivsModule. 
					
	
	
*/

#include "QTSSFilePrivsModule.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 "QTSSModuleUtils.h"
#include "base64.h"
#include "OS.h"

#ifndef __MW_
	#include <sys/errno.h>
	#include <pwd.h>
	#include <grp.h>
	#include <unistd.h>
	#include <fcntl.h>
	#include <sys/stat.h>
#endif



// STATIC DATA

static StrPtrLen		sDefaultRealm("WWW Streaming Server"); // testing only
static StrPtrLen		sSDPSuffix(".sdp");
static char*			sRootUserPtr					= "root";

const UInt32 kMaxPathLen = 512;	

static OSMutex*			sUserMutex 				= NULL;

// FUNCTION PROTOTYPES

QTSS_Error 	QTSSFilePrivsModuleDispatch(QTSS_Role inRole, QTSS_RoleParamPtr inParams);
QTSS_Error 	Register(QTSS_Register_Params* inParams);
QTSS_Error 	Initialize(QTSS_Initialize_Params* inParams);
QTSS_Error 	Shutdown();
QTSS_Error 	RereadPrefs();
QTSS_Error 	AuthenticateRTSPRequest(QTSS_StandardRTSP_Params* inParams);
Bool16 		QTSSAuthorize(QTSS_StandardRTSP_Params* inParams, const char* pathBuff);
Bool16 		CheckWorldAccess(const char* pathBuff);
Bool16 		CheckPassword(QTSS_StandardRTSP_Params* inParams);
Bool16	 	FileExists(char* pathBuff);
UInt32 		GetPathParentDestructive(const StrPtrLen *thePathPtr, StrPtrLen *resultPathPtr, UInt32 maxLen);


// FUNCTION IMPLEMENTATIONS

QTSS_Error QTSSFilePrivsModule_Main(void* inPrivateArgs)
{
	return _stublibrary_main(inPrivateArgs, QTSSFilePrivsModuleDispatch);
}


QTSS_Error 	QTSSFilePrivsModuleDispatch(QTSS_Role inRole, QTSS_RoleParamPtr inParams)
{
	switch (inRole)
	{
		case QTSS_Register_Role:
			return Register(&inParams->regParams);
		break;
		
		case QTSS_Initialize_Role:
			return Initialize(&inParams->initParams);
		break;
		
		case QTSS_RereadPrefs_Role:
			return RereadPrefs();
		break;
			
		case QTSS_RTSPAuthorize_Role:
 				return AuthenticateRTSPRequest(&inParams->rtspRequestParams);
		break;
			
		case QTSS_Shutdown_Role:
			return Shutdown();
		break;
	}
	
	return QTSS_NoErr;
}


QTSS_Error Register(QTSS_Register_Params* inParams)
{

	// Do role & attribute setup
	(void)QTSS_AddRole(QTSS_Initialize_Role);
	(void)QTSS_AddRole(QTSS_RereadPrefs_Role);
	(void)QTSS_AddRole(QTSS_RTSPAuthorize_Role);
		
	
	// Tell the server our name!
        static char* sModuleName = "QTSSFilePrivsModule";
	::strcpy(inParams->outModuleName, sModuleName);

	return QTSS_NoErr;
}


QTSS_Error Initialize(QTSS_Initialize_Params* inParams)
{
	// Setup module utils
	QTSSModuleUtils::Initialize(inParams->inMessages, inParams->inServer, inParams->inErrorLogStream);
	sUserMutex = NEW OSMutex();

	RereadPrefs();

	return QTSS_NoErr;
}

QTSS_Error Shutdown()
{
	return QTSS_NoErr;
}

QTSS_Error RereadPrefs()
{
	return QTSS_NoErr;
}


UInt32 GetPathParentDestructive(const StrPtrLen *thePathPtr, StrPtrLen *resultPathPtr, UInt32 maxLen)
{
	
	if (resultPathPtr && thePathPtr)
	{
		StrPtrLen thePath = *thePathPtr;

		while ( (thePath.Len > 0) && (thePath.Ptr[thePath.Len -1] == '/') )
		{	
			thePath.Len--;
		}
		
		while ( (thePath.Len > 0) && (thePath.Ptr[thePath.Len -1] != '/') )
		{	
			thePath.Len--;
		}	
		
		if (thePath.Len < maxLen)
		{
			memcpy (resultPathPtr->Ptr,thePath.Ptr, thePath.Len);
			resultPathPtr->Len = thePath.Len;
			resultPathPtr->Ptr[thePath.Len] = 0;
			//printf("new dir =%s \n",resultPathPtr->Ptr);
		}
			
	}
	
	return resultPathPtr->Len;
}

Bool16 IsUserMember(uid_t userID, gid_t groupID)
{
	struct passwd* user = getpwuid(userID);
	struct group* group = getgrgid(groupID);
	
	if ((user == NULL) || (group == NULL))
		return false;
		
	if (user->pw_gid == groupID) return true;
	
	int i = 0;
	while (group->gr_mem[i] != NULL)
	{
		if (!strcmp(user->pw_name, group->gr_mem[i]))
			return true;
		i++;
	}
	
	return false;
}

Bool16 CheckFileAccess(struct passwd	*passwdStructPtr, StrPtrLen */*nameStrPtr*/, char* pathBuff)
{
	Bool16 result = false;

	/*
	
		 if the user is the owner then
			if owner access then succeed
			else fail
		if user is in group then
			if group access then succeed
			else fail
			
		if guest is on then
			succed 
			else fail
		
	*/
	
	do // once only check for the current entity
	{
		struct stat statData;
		
		int statResult =  stat(pathBuff,&statData);
			
		if (0 != statResult)
		{	
			//printf("no access to file =%s\n",pathBuff);
			result = true; // let access error be handled by server
			break;
		}	
	

		if ( statData.st_uid == (UInt16) passwdStructPtr->pw_uid) // check if owner
		{
			if	(  (statData.st_mode & 0400 ) != 0 ) // has owner read access
			{	//printf("owner access to file =%s\n",pathBuff); 
				result = true;
				break;
			}
			else
			{	//printf("no owner access to file =%s\n",pathBuff); 
				result = false;
				break;
			
			}
		}
		
		
		if (statData.st_gid == (UInt16) passwdStructPtr->pw_gid)  // check if user's default group owns
		{
			if  ( (statData.st_mode & 0040) != 0 ) // has group read access
			{	//printf("user default group has access to file =%s\n",pathBuff); 
				result = true;
				break;
			}
			else
			{	//printf("user default group has no access to file =%s\n",pathBuff); 
				result = false;
				break;
			}
		
		}

		if (IsUserMember(passwdStructPtr->pw_uid, statData.st_gid)) // check if user in group
		{
			if  ( (statData.st_mode & 0040) != 0 ) // has group read access
			{	//printf("user member group has access to file =%s\n",pathBuff); 
				result = true;
				break;
			}
			else
			{	//printf("user member group has no access to file =%s\n",pathBuff); 
				result = false;
				break;
			}
		
		}
		
		if  ( (statData.st_mode & 0004) != 0 ) // has world read access
		{		
			//printf("world has access to file =%s\n",pathBuff); 
			result = true;
			break;
			
		}
		
		//printf("world has no read access to file =%s\n",pathBuff); 
		result = false;
				   		
	} while (false);
	
	
	if (!result) 
	{	
		//printf("CheckFileAccess failed for %s on file %s\n",nameStrPtr->Ptr, pathBuff);
	}
	else
	{
		//printf("success on file %s for %s\n",pathBuff, nameStrPtr->Ptr);
	}
	
	return result;
}   

Bool16 CheckDirAccess(struct passwd	*passwdStructPtr, StrPtrLen */*nameStrPtr*/, char* pathBuff)
{
	Bool16 result = true;

	char searchBuffer[kMaxPathLen] = {};
	StrPtrLen searchPath(searchBuffer, kMaxPathLen -1);
	StrPtrLen thePath(pathBuff);
	
/*
	for each directory in path until fail
		 if the user is the owner then
			if owner access then succeed-continue
			else fail - stop
			
		if user is in group then
			if group access then succeed-continue
			else fail - stop
			
		if guest access then
			succeed - continue
			else fail - stop
*/

	if (thePath.Len <= searchPath.Len)
	{	
		memcpy(searchBuffer,thePath.Ptr, thePath.Len);
	
		while ( (true == result) && (0 != GetPathParentDestructive(&searchPath, &searchPath, kMaxPathLen)) ) // loop until fail or have checked full directory path and file
		{
			result = false;
						
			do // once only check for the current entity
			{
				struct stat statData;
				
				int statResult =  stat(searchBuffer,&statData);
					
				if (0 != statResult)
				{	
					//printf("no access to path =%s\n",searchBuffer);
					result = true; // let the error be handled in the server
					break; //  allow
				}	
			
		

				if ( statData.st_uid == (UInt16) passwdStructPtr->pw_uid) // check if owner
				{
					if	(  (statData.st_mode & 0100 ) != 0 ) // has owner search access
					{	//printf("owner search access to directory =%s\n",pathBuff); 
						result = true;
						break;
					}
					else
					{	//printf("no owner search access to directory =%s\n",pathBuff); 
						result = false;
						break;
					
					}
				}
				
				
				if (statData.st_gid == (UInt16) passwdStructPtr->pw_gid)  // check if user's default group owns
				{
					if  ( (statData.st_mode & 0010) != 0 ) // has group search access
					{	//printf("user default group has search access to directory =%s\n",pathBuff); 
						result = true;
						break;
					}
					else
					{	//printf("user default group has no search access to directory =%s\n",pathBuff); 
						result = false;
						break;
					}
				
				}

				if (IsUserMember(passwdStructPtr->pw_uid, statData.st_gid)) // check if user in group
				{
					if  ( (statData.st_mode & 0010) != 0 ) // has group search access
					{	//printf("user member group has search access to directory =%s\n",pathBuff); 
						result = true;
						break;
					}
					else
					{	//printf("user member group has no search access to directory =%s\n",pathBuff); 
						result = false;
						break;
					}
				
				}
				
				if  ( (statData.st_mode & 0001) != 0 ) // has world search access
				{		
					//printf("world has search access to directory =%s\n",pathBuff); 
					result = true;
					break;
					
				}
				
				//printf("world has no search access to directory =%s\n",pathBuff); 
				result = false;

						   		
			} while (false);
			
		} 

	}
	
	
	if (!result) 
	{	
		//printf("CheckDirAccess failed for %s on file %s\n",nameStrPtr->Ptr, searchBuffer);
	}
	else
	{
		//printf("success on file %s for %s\n",searchBuffer, nameStrPtr->Ptr);
	}
	
	return result;
}   


Bool16 FileExists(char* pathBuff)
{
	struct stat statData;
	Bool16 result = true;
	
	if (0 != stat(pathBuff,&statData) ) // something wrong
	{
		if ( OSThread::GetErrno() == ENOENT )	// doesn't exist
			result = false;
	}
	
	return result;

}



Bool16 CheckWorldFileAccess(char* pathBuff)
{

	Bool16 result = false;
	struct stat statData;
							
	do // once only check for the current entity
	{
		//printf("stat on %s \n",pathBuff);
		if (0 != stat(pathBuff,&statData))
		{	
			//printf("no access to path =%s\n",pathBuff);
			result = true; // let the server deal with this one
			break;
		}			
			
		//printf("statData.st_mode = %x \n",statData.st_mode);	
		if (0 == (statData.st_mode & 0004) ) // world read access
		{	
			//printf("no world access to path =%s\n",pathBuff);
			break;
		}

   		result = true;
	   		
    
	} while (false);

	
	return result;
}  

Bool16 CheckWorldDirAccess(char* pathBuff)
{

	Bool16 result = true;
	struct stat statData;
	char searchBuffer[kMaxPathLen] = {};
	StrPtrLen searchPath(searchBuffer, kMaxPathLen -1);
	StrPtrLen thePath(pathBuff);
	
	if (thePath.Len <= searchPath.Len)
	{	
		memcpy(searchBuffer,thePath.Ptr, thePath.Len);
	
		while ( (true == result) && (0 != GetPathParentDestructive(&searchPath, &searchPath, kMaxPathLen)) ) // loop until fail or have checked full directory path and file
		{
			result = false;
						
			do // once only check for the current entity
			{
				//printf("stat on %s \n",searchBuffer);
				if (0 != stat(searchBuffer,&statData))
				{	
					//printf("no world access to path =%s\n",searchBuffer);
					result = true; // let the server deal with this one
					break;
				}			
					
				//printf("statData.st_mode = %x \n",statData.st_mode);	
				if (0 == (statData.st_mode & 0001) )
				{	
					//printf("no world access to path =%s\n",searchBuffer);
					result = false;
					break;
				}
		
		   		result = true;
			   		
		    
			} while (false);
		} ;
	}

	return result;
}  

   
Bool16 QTSSAuthorize(QTSS_StandardRTSP_Params* inParams, char* pathBuff)
{
	QTSS_Error theErr = QTSS_NoErr;
	Bool16 result = false;
	
	const int kBuffLen = 256;
	char passwordBuff[kBuffLen] = {};
	char nameBuff[kBuffLen] = {};
	StrPtrLen nameStr(nameBuff, kBuffLen -1); 
	StrPtrLen passwordStr(passwordBuff, kBuffLen -1); 
	Bool16	noUserName = false;
	Bool16  noPassword = false;
	Bool16  isSpecialGuest = false;
	
	do
	{

		theErr = QTSS_GetValue (inParams->inRTSPRequest,qtssRTSPReqUserName,0, (void *) nameStr.Ptr, &nameStr.Len);
		//printf("GetValue qtssRTSPReqUserName err =%ld \n",theErr);
		
		if ( (theErr != QTSS_NoErr) || (nameStr.Len == 0) || (nameStr.Ptr == NULL) || (*nameStr.Ptr == NULL))
		{
			//printf ("no user name\n");
			noUserName = true;
		}
			
		//printf("RTSPRequest dictionary name =%s  len = %ld\n",nameStr.Ptr, nameStr.Len);

		theErr = QTSS_GetValue (inParams->inRTSPRequest,qtssRTSPReqUserPassword,0, (void *) passwordStr.Ptr, &passwordStr.Len);
		//printf("GetValue qtssRTSPReqUserName err =%ld \n",theErr);
		if ( (theErr != QTSS_NoErr) || (passwordStr.Len == 0) || (passwordStr.Ptr == NULL) || (*passwordStr.Ptr == NULL))
		{
			//printf ("no Password\n");
			noPassword = true;
		}
		//printf("RTSPRequest dictionary password =%s len = %ld \n",passwordStr.Ptr, passwordStr.Len);

		if (noUserName && noPassword) isSpecialGuest = true;
		
		if (isSpecialGuest) // no name and no password means guest
		{
			//printf ("User is guest check for access\n");
			
			result = CheckWorldDirAccess(pathBuff);
			if (true == result)
				result = CheckWorldFileAccess(pathBuff);
			
			break; // no further processing on guest
		}
		
		if (0 == strcasecmp(nameStr.Ptr, sRootUserPtr) )
		{	//printf("user is root no root access to file =%s\n",pathBuff); // must log
			result = false; // don't allow
			break;
		}

		struct passwd	*passwdStructPtr = getpwnam(nameStr.Ptr);
		if (NULL == passwdStructPtr) 
		{	
			//printf("failed to find name =%s\n",passwordStr.Ptr);
			break;
		}
		
		char *theCryptedPassword = crypt(passwordStr.Ptr, passwdStructPtr->pw_passwd);
		if ( 0 != strcmp(passwdStructPtr->pw_passwd, theCryptedPassword ) )
		{	
			//printf("failed to match name to password =%s\n",passwordStr.Ptr);
			break;
		}
		
		result = CheckDirAccess(passwdStructPtr, &nameStr, pathBuff);
		if (!result) break;
		
		result = CheckFileAccess(passwdStructPtr, &nameStr, pathBuff);
		if (!result) break;

		//printf("QTSSAuthorize: user %s is authorized for %s\n",nameStr.Ptr,pathBuff);
		
		
	} while (false);
	
	if (!result) 
	{	//printf("QTSSAuthorize: user %s is un authorized for %s\n",nameStr.Ptr,pathBuff);
	}
	
	return result;
}

Bool16 FileOrSDPFileExists(char *pathBuff, UInt32 *pathLen, const UInt32 maxLen, QTSS_Error *theErrPtr)
{
	Bool16 result = false;
	
	do
	{
		if (FileExists(pathBuff)) 
		{	
			//printf("file exists =%s\n",pathBuff);
			result = true;
			break; // file is there
		}
		
		if ( (*pathLen + sSDPSuffix.Len + 1) >= maxLen)
		{	
			//printf("buffer too small for path\n");
			if (theErrPtr) *theErrPtr = ENAMETOOLONG; // don't allow request to succed we can't authorize
			break;
		}
		
		strcat(pathBuff, sSDPSuffix.Ptr);
		//printf("sdp path =%s\n",pathBuff);
		
		if (FileExists(pathBuff)) 
		{
			//printf("sdp file exists =%s\n",pathBuff);
			result = true; 
			break; // sdp file is there		
		}
		
	} while (false);
	
	return result;
	
}



QTSS_Error AuthenticateRTSPRequest(QTSS_StandardRTSP_Params* inParams)
{
	QTSS_Error theErr = QTSS_NoErr;
	OSMutexLocker locker(sUserMutex);
	
/*

	Notes:
			pathBuff is cleared so Getting the path should leave a zero terminated string.
			
			Order is important.
			1) Get the path 
			2) See if the path exists or an sdp path based on the path exists
			3) if world access on the path is available, allow the request
			4) if non-world access on the path or file check the name and password on the path and file

*/

	do 
	{			
		if  ( (NULL == inParams) || (NULL == inParams->inRTSPRequest) )
		{
			theErr = QTSS_RequestFailed;
			break;
		}
		
		UInt32 pathLen = kMaxPathLen -1;
		char pathBuff[kMaxPathLen] = {};	
		QTSS_RTSPRequestObject	theRTSPRequest = inParams->inRTSPRequest;
		
		theErr = QTSS_GetValue (theRTSPRequest,qtssRTSPReqLocalPath,0, (void *) pathBuff, &pathLen);
		if ( (theErr != QTSS_NoErr) || (0 == pathLen) )
		{	
			//printf("path not found in request\n");
			break; // Bail on the request. The Server will handle the error
		}

		Bool16 fileOk = FileOrSDPFileExists(pathBuff, &pathLen, kMaxPathLen, &theErr);
		if (!fileOk) 
		{	
			//printf("file not found\n");
			break; // Do nothing. The Server will handle the error
		}


		Bool16 allowRequest = QTSSAuthorize(inParams, pathBuff);
		if (allowRequest) 
		{	
			//printf("user is authorized\n");
			break; // Do nothing. Allow the request (the default behavior)
		}
			
		// We are not allowing the request so pass false back to the server.
		theErr = QTSS_SetValue(theRTSPRequest,qtssRTSPReqUserAllowed, 0, &allowRequest, sizeof(allowRequest));
		if (theErr != QTSS_NoErr) break;
		
		#if 0 // test setting a specific realm this is not used in this module
			theErr = QTSS_SetValue(theRTSPRequest,qtssRTSPReqURLRealm, 0, sDefaultRealm.Ptr, sDefaultRealm.Len);
			if (theErr != QTSS_NoErr) break;
		#endif
	
	} while (false); 
	
	if (theErr)
	{
		Assert(0);
		theErr = QTSS_RequestFailed;
	}
	

	return theErr;
}





