/***********************************************************************
 *
 *  Copyright (C) 2005-2006 Novell, Inc. All Rights Reserved.
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; version 2.1
 *  of the License.
 *
 *  This library 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
 *  Library Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, Novell, Inc.
 *
 *  To contact Novell about this file by physical or electronic mail,
 *  you may find current contact information at www.novell.com.
 *
 ***********************************************************************/


#include "FirefoxPasswordManager.h"
#include "Common.h"
#include "ProfileManager.h"


ProfileManager profileManager[MAX_PROFILE_COUNT];
int profileCount = 0;



/**
*   Check if firefox is there on the system
*
*   @return  1   if firefox libraries are present
*            0   otherwise
*
*   It loads the libraries from the firefox library path and if they are loaded
*   successfully then that indicates that firefox is present.
*
*/
extern "C" APIEXPORT int FPM_IsStoreAvailable()
{
	ProfileManager pm;
	return pm.IsStoreAvailable();
}



/*
*  Gets the list of profile names...
*
*  @param[in/out] profiles    pointer to array of profile names
*  @param[in/out] profileFlag Indicates if default profile or not.
*  @return        count     count of profiles
*               0         no profiles found 
*               < 0       on error
*
*    If one or more profiles found then profiles array is filled with 
*	 the profile names and count of profiles is returned. ProfileFlag[]
*    array is filled with 1 or 0 to indicate if the respective profile
*    is default profile or not.If no profiles found then value 0 is 
*    returned and negative values is returned if there is an error.
*
*/

extern "C" APIEXPORT int FPM_GetProfileList(char **profileList[], int *profileFlag[])
{

#ifdef WIN32

	char profileDir[MAX_PATH] = "";
	char partialPath[] = "Application Data\\Mozilla\\Firefox";
	char profilePath[MAX_PATH];
	char line[1024];

	DWORD pathSize = MAX_PATH;
	char *finalProfilePath = NULL;
	int  profileCount = 0;
	unsigned int i;
	HANDLE token;

		
	// Get current user's profile directory
	if( OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token) == FALSE )
	{
		PrintMessage(MESG_ERROR, "\n GetProfileList : Failed to get current process token ");
		return FPM_FALSE;
	}
	
	if( GetUserProfileDirectory(token, profileDir, &pathSize) == FALSE )
	{
		PrintMessage(MESG_ERROR, "\n GetProfileList : Failed to get user profile directory");
		return FPM_FALSE;
	}
	
	PrintMessage(MESG_DEBUG, "\n GetProfileList : User Profile directory = %s", profileDir);
	
	// Get firefox profile directory
	strcpy(profilePath, profileDir);
	strcat(profilePath,"\\");
	strcat(profilePath,partialPath);
	strcat(profilePath,"\\profiles.ini");
	
	PrintMessage(MESG_DEBUG, "\n GetProfileList : Firefox profile dir path = %s ", profilePath);


#else  // Linux platform....

	char profileDir[]  ="/.mozilla/firefox";
	char profileFile[] ="/.mozilla/firefox/profiles.ini";
	
	char line[1024];
	char *profilePath = NULL;
	char *homeDir = NULL;
	char *finalProfilePath = NULL;
	int  profileCount = 0;
	unsigned int i;
	
	// Get home directory
	homeDir = getenv("HOME");
	
	if(homeDir == NULL )
	{
	    PrintMessage(MESG_ERROR, "\n GetProfileList : Unable to get home directory ");
	    return FPM_FALSE;
	}	

	profilePath = (char*) malloc( strlen(homeDir) + strlen(profileFile) + 3 );
	if( profilePath == NULL )
	{
	    PrintMessage(MESG_ERROR, "\n GetProfileList : Insufficient memory ");
	    return FPM_FALSE;
	}
	strcpy(profilePath,homeDir);
	strcat(profilePath,profileFile);

	PrintMessage(MESG_DEBUG, "\n GetProfileList : Firefox profile dir path = %s ", profilePath);

#endif

	// Open the firefox profile setting file
	FILE *profile = fopen(profilePath, "r");
	
	if( profile == NULL )
	{
		PrintMessage(MESG_ERROR, "\n GetProfileList : Unable to find firefox profile file : %s ", profilePath);
		return FPM_FALSE;
	}

	// First find out the count of profiles....
	profileCount = 0;
	while(fgets(line, 1024, profile))
	{
		// Remove trailing end of line character
		line[strlen(line)-1]= 0;
		
		// Convert to smaller case until "=" found....
		for(i=0; i<strlen(line); i++)
		{
			if( line[i] == '=' )
				break;

			if( line[i] >=65 && line[i]<=90 )
				line[i]+=32;
		} 		

		if( strstr(line, "name=") != NULL )
			profileCount++;
		
	}

	PrintMessage(MESG_DEBUG, "\n GetProfileList : Total profiles found = %d ", profileCount);

	if( profileCount == 0 )
	{
		fclose(profile);
		return FPM_FALSE;
	}

	*profileList = ( char**) malloc(profileCount * sizeof (char *));
	*profileFlag = ( int * ) malloc(profileCount * sizeof(int));

	if( *profileList == NULL  || *profileFlag == NULL )
	{
	    PrintMessage(MESG_ERROR, "\n GetProfileList : Insufficient memory ");
	    fclose(profile);
		return FPM_FALSE;
	}

	char **profList = *profileList;
	int *profFlag   = *profileFlag;

	// Now read the profile names and store it..
	fseek(profile, 0, SEEK_SET);

	profileCount = 0;
	while(fgets(line, 1024, profile))
	{
		// Remove trailing end of line character
		line[strlen(line)-1]= 0;

		// Convert to smaller case until "=" found....
		for(i=0; i<strlen(line); i++)
		{
			if( line[i] == '=' )
				break;

			if( line[i] >=65 && line[i]<=90 )
				line[i]+=32;
		} 		

		if( strstr(line, "name=") != NULL )
		{
			char *temp = strchr(line,'=') + 1;
			profList[profileCount] = (char*) malloc(strlen(temp)+1);

			if( profList[profileCount] == NULL )
			{
				PrintMessage(MESG_ERROR, "\n GetProfileList : Insufficient memory ");
				fclose(profile);
				return 0;
			}

			strcpy(profList[profileCount],temp);
			profFlag[profileCount] = 0;

			PrintMessage(MESG_DEBUG, "\n GetProfileList : Found profile = [%s]", profList[profileCount]);
			profileCount++;
			continue;
		}
	
		// check if the current profile is default
		if( strstr(line, "default=1") != NULL )
		{
			profFlag[profileCount-1] = 1;
		}

	}

	fclose(profile);

	// if there is only one profile then set it default profile
	if( profileCount == 1 )
	{
		**profileFlag = 1;
	}

	return profileCount;
}



/**
*	 Initializes the firefox library with the specified profile
*
*	@param   profileName   name of the profile
*	@return  1     on success
*         <=0   on error 
*  
*    It initializes the firefox library with the specified profile. This must be called before 
*    invoking any operation on the specified profile.
*    It performs following tasks
*       * Determine firefox profile directory 
*       * Loads the firefox security libraries.
*       * Initializes the firefox security library for the profile.
*
*	If the mentioned profile is not found then FPM_PROFILE_NOT_PRESENT will be returned. If there is
*   an error in loading or initializing the firefox library then FPM_LIBRARY_LOAD_FAILED or FPM_LIBRARY_INIT_FAILED
*   is returned. If user has not enabled "remember passwords" then certain files (key3.db, cert8.db) required for 
*   initialization will not be present in the profile directory. This can cause FPM_LIBRARY_INIT_FAILED error.
*
*/

extern "C" APIEXPORT int FPM_FirefoxProfileInit(char *profileName)
{
int retValue;
int profileIndex = -1; 	

	// Check if the object for specified profile already present...
	for(int i=0; i< profileCount; i++)
	{
		if( profileManager[i].profileName != NULL )
		{
			if( STRCMPI(profileManager[i].profileName, profileName) == 0 )
			{
				PrintMessage(MESG_DEBUG, "\n FirefoxProfileInit : Object for specified profile %s exist ", profileName);
				profileIndex = i;

				break;
			}
		}
	}

	// This is new profile...
	if( profileIndex == -1) 
	{
		if( (profileCount + 1) >= MAX_PROFILE_COUNT)
		{
			PrintMessage(MESG_ERROR, "\n FirefoxProfileInit : Max profile count exceeded.");
			return FPM_PROFILE_LIMIT_EXCEEDED;
		}

		profileIndex = profileCount;
		profileCount++;
	}

	// check if the profile is already initialized...
	if( profileManager[profileIndex].isInitialized == FPM_TRUE )
	{
		PrintMessage(MESG_DEBUG, "\n FirefoxProfileInit :  Specified profile %s is already initialized", profileName);
		return FPM_TRUE;			
	}


	if( (retValue = profileManager[profileIndex].ProfileInit(profileName)) != FPM_TRUE )
	{
		PrintMessage(MESG_ERROR, "\n FirefoxProfileInit : Failed to initialize the profile %s ", profileName);
		return retValue;
	}

	PrintMessage(MESG_DEBUG, "\n FirefoxProfileInit : Firefox profile %s initialized successfully ", profileName);

	
	return FPM_TRUE;
}



/**
*   Uninitializes the specified profile.
*
*	@param   profileName   name of the profile
*   @return  1     on success
*			<=0    on error 
*
*   Uninitializes the specified profile and unloads the firefox security library.
*   It also cleans up internal data structure.
*/

extern "C" APIEXPORT int FPM_FirefoxProfileExit(char *profileName)
{
	// Find the profile...
	for(int i=0; i< profileCount; i++)
	{
		if( profileManager[i].profileName != NULL )
		{
			if( STRCMPI(profileManager[i].profileName, profileName) == 0 )
			{
				// check if its initialized
				if( profileManager[i].isInitialized == FPM_TRUE )
				{
					PrintMessage(MESG_DEBUG, "\n FirefoxProfileExit :  Exiting the firefox profile %s ", profileName);
					profileManager[i].ProfileExit();
					return FPM_TRUE;
				}
				else
				{
					PrintMessage(MESG_ERROR, "\n FirefoxProfileExit :  Specified profile %s is not initialized , cannot exit the profile", profileName);
					return FPM_PROFILE_NOT_INITIALIZED;
				}
			}
		}
	}

	PrintMessage(MESG_ERROR, "\n FirefoxProfileExit :  Specified profile %s is not found", profileName);
	
	return FPM_PROFILE_NOT_PRESENT;


}


/**
*   Verifies if master passsword is set for the specified profile	
*
*	@param   profileName   name of the profile
*	@return 1    if master password is set
*			0    if master password not set
*
*	Checks if the master password is set or not for the specified profile. The application can 
*	use this function to determine if the user has set the master password. If so it can prompt 
*	the user to enter the master password.
*/

extern "C" APIEXPORT int FPM_IsMasterPasswordSet(char *profileName)
{

	// Find the profile...
	for(int i=0; i< profileCount; i++)
	{
		if( profileManager[i].profileName != NULL )
		{
			if( STRCMPI(profileManager[i].profileName, profileName) == 0 )
			{
				// check if its initialized
				if( profileManager[i].isInitialized == FPM_TRUE )
				{
					PrintMessage(MESG_DEBUG, "\n IsMasterPasswordSet :  invoking IsMasterPasswordSet for profile %s", profileName);
					return profileManager[i].IsMasterPasswordSet();
				}
				else
				{
					PrintMessage(MESG_ERROR, "\n IsMasterPasswordSet :  Specified profile %s is not initialized ", profileName);
					return FPM_PROFILE_NOT_INITIALIZED;
				}
			}
		}
	}

	PrintMessage(MESG_ERROR, "\n IsMasterPasswordSet :  Specified profile %s is not found", profileName);
	
	return FPM_PROFILE_NOT_PRESENT;

}



/**
*	Checks if the master password is correct for the specified profile.
*
*	@param    profileName      name of the profile
*	@param    masterPassword   Master password to be checked.
*	@return  1   if the specified master password is correct
*			 0   if the master password is wrong.
*
*
*	Check if the specified master password is correct or not. If it is 
*   correct then password is stored to the internal store for later use. 
*   If it is wrong then nothing is stored and 0 will be returned.
*/

extern "C" APIEXPORT int FPM_CheckMasterPassword(char *profileName, char *masterPassword)
{

	// Find the profile...
	for(int i=0; i< profileCount; i++)
	{
		if( profileManager[i].profileName != NULL )
		{
			if( STRCMPI(profileManager[i].profileName, profileName) == 0 )
			{
				// check if its initialized
				if( profileManager[i].isInitialized == FPM_TRUE )
				{
					PrintMessage(MESG_DEBUG, "\n CheckMasterPassword :  invoking CheckMasterPassword for profile %s", profileName);
					return profileManager[i].CheckMasterPassword(masterPassword, 1);
				}
				else
				{
					PrintMessage(MESG_ERROR, "\n CheckMasterPassword :  Specified profile %s is not initialized ", profileName);
					return FPM_PROFILE_NOT_INITIALIZED;
				}
			}
		}
	}

	
	PrintMessage(MESG_ERROR, "\n CheckMasterPassword :  Specified profile %s is not found", profileName);

	return FPM_PROFILE_NOT_PRESENT;

}

/**
*	Loads the signon data from the firefox signon file for specified profile
*
*	@param   profileName    name of the profile
*	@param   struct Host**  pointer to list of signon host structure
*	@param   doRefresh      signon data to be refreshed or not 
*	@return  1              success
*         <= 0           If an error has occurred.
*
*	Returns the pointer to the internal signon data store which contains list of hosts
*	and associated name/value pairs. If doRefresh value is positive then fresh signon 
*	data is loaded from the signon file. Otherwise current signon data is returned.
*
*	If the master password is set and its not specified or wrong password is specified
*   then error code FPM_MASTERPASSWORD_WRONG will be returned. In this case use 
*	CheckMasterPassword function to set the correct master password and then call this 
*	function again.
*
*	In case of error in reading signon information FPM_SIGNON_FILE_READ_ERROR or 
*	FPM_SIGNON_FILE_NOT_PRESENT will be returned.
*
*/


extern "C" APIEXPORT int FPM_GetSignonData(char *profileName,struct Host **host, int doRefresh)
{

	// Find the profile...
	for(int i=0; i< profileCount; i++)
	{
		if( profileManager[i].profileName != NULL )
		{
			if( STRCMPI(profileManager[i].profileName, profileName) == 0 )
			{
				// check if its initialized
				if( profileManager[i].isInitialized == FPM_TRUE )
				{
					PrintMessage(MESG_DEBUG, "\n GetSignonData :  invoking GetSignonData for profile %s", profileName);
					return profileManager[i].GetSignonData(host, doRefresh);
				}
				else
				{
					PrintMessage(MESG_ERROR, "\n GetSignonData :  Specified profile %s is not initialized", profileName);
					return FPM_PROFILE_NOT_INITIALIZED;
				}
			}
		}
	}

	
	PrintMessage(MESG_ERROR, "\n GetSignonData :  Specified profile %s is not found", profileName);

	return FPM_PROFILE_NOT_PRESENT;
}

/**
*   Updates the firefox signon file with new signon data.
*
*	@param   profileName    name of the profile
*	@return   1      If signon data written to the disk successfully 
*           <=0    If an error has occurred.
*
*	Writes the signon data from the internal signon data store to the disk. If an 
*	error occurs then proper error code will be returned. If the master password is set
*   and its not specified or wrong password is specified then error code FPM_MASTERPASSWORD_WRONG
*   will be returned. In this case use CheckMasterPassword function to set the correct 
*   master password and then call this function again.
*
*	In case of write error, error code FPM_SIGNON_FILE_WRITE_ERROR will be returned.
*
*	If the signon file is locked then error code FPM_SIGNON_FILE_LOCKED will be 
*   returned. In this case application should ask the user to close the firefox 
*   application and then it should call this function again.
*
*/

extern "C" APIEXPORT int FPM_WriteSignonData(char *profileName)
{

	// Find the profile...
	for(int i=0; i< profileCount; i++)
	{
		if( profileManager[i].profileName != NULL )
		{
			if( STRCMPI(profileManager[i].profileName, profileName) == 0 )
			{
				// check if its initialized
				if( profileManager[i].isInitialized == FPM_TRUE )
				{
					PrintMessage(MESG_DEBUG, "\n WriteSignonData :  invoking WriteSignonData for profile %s", profileName);
					return profileManager[i].WriteSignonData();
				}
				else
				{
					PrintMessage(MESG_ERROR, "\n WriteSignonData :  Specified profile %s is not initialized", profileName);
					return FPM_PROFILE_NOT_INITIALIZED;
				}
			}
		}
	}

	
	PrintMessage(MESG_ERROR, "\n WriteSignonData :  Specified profile %s is not found", profileName);

	return FPM_PROFILE_NOT_PRESENT;
}


/**
*   Adds signon data for new host...
*
*	@param   profileName    name of the profile
*	@param   struct Host*   pointer to host structure to be added
*	@param   doUpdate       signon data to be written to the file or not
*	@return  1      success
*         <=0    error
*
*	Adds the specified host information to the internal signon data store. If the 
*	value of doUpdate is positive then the entire signon data is written to the file. 
*	Otherwise changes are done only in the internal data store.
*
*	If doUpdate is positive then error code may be from FPM_WriteSignonData function.
*
*/


extern "C" APIEXPORT int FPM_AddHost(char *profileName, struct Host *host, int doUpdate)
{

	// Find the profile...
	for(int i=0; i< profileCount; i++)
	{
		if( profileManager[i].profileName != NULL )
		{
			if( STRCMPI(profileManager[i].profileName, profileName) == 0 )
			{
				// check if its initialized
				if( profileManager[i].isInitialized == FPM_TRUE )
				{
					PrintMessage(MESG_DEBUG, "\n AddHost :  invoking AddHost for profile %s", profileName);
					return profileManager[i].AddHost(host, doUpdate);
				}
				else
				{
					PrintMessage(MESG_ERROR, "\n AddHost :  Specified profile %s is not initialized", profileName);
					return FPM_PROFILE_NOT_INITIALIZED;
				}
			}
		}
	}

	
	PrintMessage(MESG_ERROR, "\n AddHost :  Specified profile %s is not found", profileName);

	return FPM_PROFILE_NOT_PRESENT;

}

/**
*   Modifies the credentials for the specified host url for specified profile.
*
*	@param   profileName    name of the profile
*	@param   struct Host*   pointer to host structure to be modified.
*	@param   doUpdate       signon data to be written to the file or not
*	@return   1      success
*         <=0     error
*
*	Modifes the values of the specified host with new values. If the value 
*	of doUpdate is positive then the entire signon data is written to the file. 
*	Otherwise changes are done only in the internal data store. If any of
*	the names ( name/value pairs ) is not matched with the existing name in the 
*	Host's username/password list then error FPM_NAME_NOT_PRESENT is returned.
*
*	If doUpdate is positive then error code may be from FPM_WriteSignonData function.
*
*/
  
extern "C" APIEXPORT int FPM_ModifyHost(char *profileName, struct Host *host, int doUpdate)
{
	// Find the profile...
	for(int i=0; i< profileCount; i++)
	{
		if( profileManager[i].profileName != NULL )
		{
			if( STRCMPI(profileManager[i].profileName, profileName) == 0 )
			{
				// check if its initialized
				if( profileManager[i].isInitialized == FPM_TRUE )
				{
					PrintMessage(MESG_DEBUG, "\n ModifyHost :  invoking ModifyHost for profile %s", profileName);
					return profileManager[i].ModifyHost(host, doUpdate);
				}
				else
				{
					PrintMessage(MESG_ERROR, "\n ModifyHost :  Specified profile %s is not initialized", profileName);
					return FPM_PROFILE_NOT_INITIALIZED;
				}
			}
		}
	}

	
	PrintMessage(MESG_ERROR, "\n ModifyHost :  Specified profile %s is not found", profileName);

	return FPM_PROFILE_NOT_PRESENT;


}

/**
*    Removes the signon credentials for specified host
*
*	@param   profileName  name of the profile
*	@param   hostName     complete URL of the host name 
*	@param   doUpdate     signon data to be written to the file or not
*
*	@return  1      on success
*         <=0    on error
*
*	Removes the specified host from the internal signon data store. All 
*	name-value pairs associated with specified host will also be removed. 
*	If the value of doUpdate is positive then the entire signon data is
*	written to the file. Otherwise changes are done only in the internal data store. 
*
*	If doUpdate is positive then error code may be from FPM_WriteSignonData function.
*
*/
extern "C" APIEXPORT int FPM_RemoveHost(char *profileName, char *hostName, int doUpdate)
{
	// Find the profile...
	for(int i=0; i< profileCount; i++)
	{
		if( profileManager[i].profileName != NULL )
		{
			if( STRCMPI(profileManager[i].profileName, profileName) == 0 )
			{
				// check if its initialized
				if( profileManager[i].isInitialized == FPM_TRUE )
				{
					PrintMessage(MESG_DEBUG, "\n RemoveHost :  invoking RemoveHost for profile %s", profileName);
					return profileManager[i].RemoveHost(hostName, doUpdate);
				}
				else
				{
					PrintMessage(MESG_ERROR, "\n RemoveHost :  Specified profile %s is not initialized", profileName);
					return FPM_PROFILE_NOT_INITIALIZED;
				}
			}
		}
	}

	PrintMessage(MESG_ERROR, "\n RemoveHost :  Specified profile %s is not found", profileName);

	return FPM_PROFILE_NOT_PRESENT;
}