/*
 *
 * @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:		AccessChecker.cpp

	Contains:

*/

#include <signal.h>
	
#ifndef __Win32__
    #ifndef __USE_XOPEN
        #define __USE_XOPEN 1
	#endif
	#include <unistd.h>
#endif

#include <string.h>
#include <stdio.h>
#include <stdlib.h>

#include "StringParser.h"
#include "OSFileSource.h"
#include "base64.h"
#include "OSMemory.h"
#include "OSHeaders.h"
#include "AccessChecker.h"
#include "QTSSModuleUtils.h"
#include "OSArrayObjectDeleter.h"

static StrPtrLen sAuthWord("realm", 5);

// Constructor
// Allocates no memory
AccessChecker::AccessChecker() :
	fGroupsFilePath(NULL),
	fUsersFilePath(NULL),
	fUsersFileModDate(-1),
	fGroupsFileModDate(-1),
	fProfiles(NULL),
	fNumUsers(0),
	fCurrentSize(0)
{
}

// Destructor
// Deletes the fUsersFilePath, fGroupsFilePath, 
// and calls the function to delete the authRealm and all the profiles
AccessChecker::~AccessChecker()
{
	delete[] fGroupsFilePath;
	delete[] fUsersFilePath;
	
	DeleteProfilesAndRealm();
}


// Allocates memory for the fUsersFilePath and fGroupsFilePath
// Before this call is made, make sure that the previous memory allocated is deleted
// or that memory will be orphaned!
void AccessChecker::UpdateFilePaths(const char* inUsersFilePath, const char* inGroupsFilePath) {
	// Assert input arguments are not null
	Assert(inUsersFilePath != NULL);
	Assert(inGroupsFilePath != NULL);

	// Before reassigning, delete old paths
	delete[] fGroupsFilePath;
	delete[] fUsersFilePath;
	
	fUsersFilePath = NEW char[strlen(inUsersFilePath)+1];
	::strcpy(fUsersFilePath, inUsersFilePath);
	
	fGroupsFilePath = NEW char[strlen(inGroupsFilePath)+1];
	::strcpy(fGroupsFilePath, inGroupsFilePath);
}

// Function to delete memory allocated for all the profiles, and the authRealm
// For each profile, memory is allocated for
// 									username
//									cryptPassword
//									digestPassword
//									each group that the user belongs to (array of group names)
// All of the above are deleted
void AccessChecker::DeleteProfilesAndRealm()
{
	UInt32 i, j;
	
	// delete the profiles
	if(fProfiles != NULL)
	{
		// For each profile
		for(i = 0; i < fNumUsers; i++)
		{
			UserProfile* profile = fProfiles[i];
			
			// delete the username
			if((profile->username).Len != 0) 
			{
				delete (profile->username).Ptr;
				(profile->username).Len = 0;
			}
			
			// delete cryptPassword
			if((profile->cryptPassword).Len != 0) 
			{
				delete (profile->cryptPassword).Ptr;
				(profile->cryptPassword).Len = 0;
			}
			
			// delete digestPassword
			if((profile->digestPassword).Len != 0) 
			{
				delete (profile->digestPassword).Ptr;
				(profile->digestPassword).Len = 0;
			}
			
			// delete each group name
			for(j = 0; j < profile->numGroups; j++) 
			{
				delete [] profile->groups[j];
			}			
			// delete the array of pointers to the group names
			delete [] profile->groups;
			
			profile->groups = NULL;
			profile->maxGroupNameLen = 0;
			profile->numGroups = 0;
			profile->groupsSize = 0;
			delete profile;
		}
		
		// delete the array of profile pointers
		delete [] fProfiles;
		fProfiles = NULL;
	}
	
	// delete the fAuthRealm field
	if(fAuthRealm.Len != 0) {
		delete fAuthRealm.Ptr;
		fAuthRealm.Len = 0;
	}
	
	fNumUsers = 0;
	fCurrentSize = 0;
}

// Memory is allocated for each username record found in the users file
// Memory is also allocated for each group name found in the groups file per user
// All this memory must be deleted if the profiles are deleted, before parsing
// the file again
UInt32 AccessChecker::UpdateUserProfiles() {
	
	UInt32 index = 0;
	UInt32 i = 0, j = 0;
	UInt32 resultErr = kNoErr;
	Bool16 groupFileErrors = true;
        Bool16 userFileErrors = true;
        
	StrPtrLen line;
	
        QTSS_TimeVal oldUsersFileModDate = fUsersFileModDate;
        QTSS_TimeVal oldGroupsFileModDate = fGroupsFileModDate;
        
	// Read the users file into a buffer
	StrPtrLen userData;
	QTSS_TimeVal newModDate = -1;
	QTSS_Error err = QTSS_NoErr;
	// QTSSModuleUtils::ReadEntireFile allocates memory for userData
	err = QTSSModuleUtils::ReadEntireFile(fUsersFilePath, &userData, fUsersFileModDate, &newModDate);
	if(err == QTSS_FileNotFound)
		resultErr |= kUsersFileNotFoundErr;
	else if(err != QTSS_NoErr)
		resultErr |= kUsersFileUnknownErr;
	else
                userFileErrors = false;
        
        if(userFileErrors)
                fUsersFileModDate = -1;
        else if(userData.Len != 0) 
                fUsersFileModDate = newModDate;
	
	newModDate = -1;
	
	// Read the groups file into a buffer
	StrPtrLen groupData;
	// QTSSModuleUtils::ReadEntireFile allocates memory for groupData
	err = QTSSModuleUtils::ReadEntireFile(fGroupsFilePath, &groupData, fGroupsFileModDate, &newModDate);
	if(err == QTSS_FileNotFound)
		resultErr |= kGroupsFileNotFoundErr;
	else if(err != QTSS_NoErr)
		resultErr |= kGroupsFileUnknownErr;
        else
                groupFileErrors = false;
        
        if(groupFileErrors)
                fGroupsFileModDate = -1;
	else if(groupData.Len != 0)
		fGroupsFileModDate = newModDate;

        if(userFileErrors)
        {
            // delete user profiles and exit
            DeleteProfilesAndRealm();
            return resultErr;
        }
        
        if((fUsersFileModDate == oldUsersFileModDate) && (fGroupsFileModDate == oldGroupsFileModDate))
		return resultErr;
	
	// If either the users or groups file has changed
	// the old profiles and the old realm should be deleted
	// before a new array of profiles is created and a new realm from the users file is read	
	DeleteProfilesAndRealm();
	
	// Since one or both of the files has changed, reread the files and create user profiles	
	if(userData.Len == 0)
		(void)QTSSModuleUtils::ReadEntireFile(fUsersFilePath, &userData, -1, NULL);
	if(groupData.Len == 0 && !groupFileErrors)
		(void)QTSSModuleUtils::ReadEntireFile(fGroupsFilePath, &groupData, -1, NULL);
		
	
	// This will delete the memory allocated for userData when we return from this function
	OSCharArrayDeleter userDataPtrDeleter(userData.Ptr);
	// This will delete the memory allocated for groupData when we return from this function
	OSCharArrayDeleter groupDataPtrDeleter(groupData.Ptr);
	
	// Create the fProfiles array of size kDefaultNumProfiles
	fProfiles = NEW UserProfile*[kDefaultNumProfiles];
	fCurrentSize = kDefaultNumProfiles;
	
	StringParser userDataParser(&userData);
	// check if the first line is "realm"
	while(true) {
		StrPtrLen word;
		userDataParser.GetThruEOL(&line);
		StringParser authLineParser(&line);
		// Skip over leading whitespace
		authLineParser.ConsumeUntil(NULL, StringParser::sWhitespaceMask);
		// Skip over comments and blank lines
		if ((authLineParser.GetDataRemaining() == 0) || (authLineParser[0] == '#') || (authLineParser[0] == '\0') )
	   		continue;
		authLineParser.ConsumeWord(&word);
		if(sAuthWord.Equal(word)) {
			authLineParser.ConsumeWhitespace();
			authLineParser.ConsumeUntil(&word, StringParser::sEOLMask);
			fAuthRealm.Set(word.GetAsCString(), word.Len);		
		}
		else {
			// This shouldn't happen because it means that the realm line
			// is not the first non-commented out line in the file
			// Implies the users file is corrupted!
			resultErr |= kBadUsersFileErr;
			
			// Create a new user profile for the first username
			UserProfile* profile = NEW UserProfile;
			profile->groups = NEW char*[kDefaultNumGroups];
			profile->maxGroupNameLen = 0;
			profile->numGroups = 0;
			profile->groupsSize = kDefaultNumGroups;
			(profile->username).Set(word.GetAsCString(), word.Len);
			// Get the crypted password
			if ( authLineParser.Expect(':') )
			{
				authLineParser.ConsumeUntil(&word, ':');
				(profile->cryptPassword).Set(word.GetAsCString(), word.Len);
				// Get the digest password
				authLineParser.GetThruEOL(&word);
				(profile->digestPassword).Set(word.GetAsCString(), word.Len);
			}		
	
			fProfiles[index] = profile;
			index ++;
		}
		break;
	}
	
	while(userDataParser.GetDataRemaining() != 0) {
		// Read each line
		userDataParser.GetThruEOL(&line);
		StringParser userLineParser(&line);
		//parse the line
		//skip over leading whitespace
		userLineParser.ConsumeUntil(NULL, StringParser::sWhitespaceMask);
	
		//skip over comments and blank lines
		if ((userLineParser.GetDataRemaining() == 0) || (userLineParser[0] == '#') || (userLineParser[0] == '\0') )
	    	continue;
	
		// Create a new user profile for each username found
		UserProfile* profile = NEW UserProfile;
		profile->groups = NEW char*[kDefaultNumGroups];
		profile->maxGroupNameLen = 0;
		profile->numGroups = 0;
		profile->groupsSize = kDefaultNumGroups;
		StrPtrLen word;
		userLineParser.ConsumeUntil(&word, ':');
		(profile->username).Set(word.GetAsCString(), word.Len);
		// Get the crypted password
		if ( userLineParser.Expect(':') )
		{
			userLineParser.ConsumeUntil(&word, ':');
			(profile->cryptPassword).Set(word.GetAsCString(), word.Len);
			if(userLineParser.Expect(':')) {
				// Get the digest password
				userLineParser.GetThruEOL(&word);
				(profile->digestPassword).Set(word.GetAsCString(), word.Len);
			}
		}			
	
		if(index >= fCurrentSize) {
			UserProfile** oldProfiles = fProfiles;
			fProfiles = NEW UserProfile*[fCurrentSize * 2];
			for(i = 0; i < fCurrentSize; i++) 
			{
				fProfiles[i] = oldProfiles[i];
			}
			fCurrentSize *= 2;
			delete [] oldProfiles;
		}
		
		fProfiles[index] = profile;
		index ++;
	}
	fNumUsers = index;
	
        if(!groupFileErrors)	
	{
            StringParser groupDataParser(&groupData);
            while(groupDataParser.GetDataRemaining() != 0) {
                    // Read each line
                    groupDataParser.GetThruEOL(&line);
                    StringParser groupLineParser(&line);	
                    //parse the line
                    //skip over leading whitespace
                    groupLineParser.ConsumeUntil(NULL, StringParser::sWhitespaceMask);
            
                    //skip over comments and blank lines
                    if ((groupLineParser.GetDataRemaining() == 0) || (groupLineParser[0] == '#') || (groupLineParser[0] == '\0') )
                    continue;
                            
            //parse the groupname
                    StrPtrLen groupName;
                    groupLineParser.ConsumeUntil(&groupName, ':');
                    
                    if (groupLineParser.Expect(':')) {
                            StrPtrLen groupUser;
                            UInt32 nameLen = groupName.Len + 1;
                            
                            while(groupLineParser.GetDataRemaining() != 0)
                            {
                                    groupLineParser.ConsumeWhitespace();
                                    groupLineParser.ConsumeUntilWhitespace(&groupUser);
                                    for(i = 0; i < fNumUsers; i++) {
                                            if(fProfiles[i]->username.Equal(groupUser))
                                            {
                                                    UInt32 grpSize = fProfiles[i]->groupsSize;
                                                    if(fProfiles[i]->numGroups >= grpSize) {
                                                            char** oldGroups = fProfiles[i]->groups;
                                                            fProfiles[i]->groups = NEW char*[grpSize * 2];
                                                            for(j = 0; j < grpSize; j++) {
                                                                    fProfiles[i]->groups[j] = oldGroups[j];
                                                            }
                                                            fProfiles[i]->groupsSize *= 2;
                                                            delete [] oldGroups;
                                                    }
                                                    
                                                    fProfiles[i]->groups[fProfiles[i]->numGroups] = groupName.GetAsCString();
                                                    if(nameLen > fProfiles[i]->maxGroupNameLen) 
                                                            fProfiles[i]->maxGroupNameLen = nameLen;
                                                    fProfiles[i]->numGroups++;
                                                    break;
                                            }
                                    }
                            }
                    }
            }
        }
	
	return resultErr;
}

// No memory is allocated
Bool16	AccessChecker::HaveFilePathsChanged(const char* inUsersFilePath, const char* inGroupsFilePath)
{
	Bool16 changed = true;
	if((inUsersFilePath != NULL) && (inGroupsFilePath != NULL) && (fUsersFilePath != NULL) && (fGroupsFilePath != NULL)) {
		if((strcmp(inUsersFilePath, fUsersFilePath) == 0) && (strcmp(inGroupsFilePath, fGroupsFilePath) == 0))
			changed = false;
	}
	return changed;
}

// No memory is allocated
AccessChecker::UserProfile* AccessChecker::RetrieveUserProfile(const StrPtrLen* inUserName)
{
	UInt32 index = 0;
	for(index = 0; index < fNumUsers; index++) {
		if(fProfiles[index]->username.Equal(*inUserName)) 
			return fProfiles[index];
	}
	return NULL;
}

