/***********************************************************************
 *
 *  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 "SignonManager.h"

// Static data to keep track of buffers
// Look at function ReadChar
  static int last = 0;
  static int next = 0;





SignonManager::SignonManager()
{
	signonFile = NULL;
}

SignonManager::~SignonManager()
{

}


void SignonManager::SetupFunctions(void *funList[])
{
	cryptManager.SetupFunctions(funList);
}




int SignonManager::OpenSignonFile(char *firefoxProfileDir, char *fileName, char *accessType )
{
char *signonFilePath = NULL;
	
	// Clean up previous buffers
	last = 0;
	next = 0;
 

	signonFilePath = (char*) malloc( strlen(firefoxProfileDir) + strlen(fileName) + 3 );

	if( signonFilePath == NULL )
	{
		PrintMessage(MESG_ERROR, "\n Insufficient memory ....");
		return FPM_INSUFFICIENT_MEMORY;
	}
	
	strcpy(signonFilePath, firefoxProfileDir);
	strcat(signonFilePath, "/");
	strcat(signonFilePath, fileName);

	PrintMessage(MESG_DEBUG, "\n Final signon filename is  = %s ", signonFilePath);
		
	// Open the signon file 
	signonFile = fopen(signonFilePath, accessType);

	if( signonFile == NULL )
	{
		PrintMessage(MESG_ERROR, "\n SignonManager : Error opening signon file %s", signonFilePath);
		free(signonFilePath);
		return FPM_SIGNON_FILE_NOT_PRESENT;
	}

	// cleanup
	free(signonFilePath);

	return FPM_TRUE;

}


int SignonManager::CloseSignonFile()
{

	if( signonFile )
		fclose(signonFile);

	return FPM_TRUE;

}


int SignonManager::ReadLine(char *buffer, int size)
{
  Unichar c;
  int strLength = 0, i=0;
  
  buffer[0] = 0;

  while(1)
  {
	  c = ReadCharUTF8();  
	  
	  /* note that eof is not set until we read past the end of the file */
	  if ( c == FPM_FALSE ) // || feof(file) ) 
		  return FPM_FALSE;
	  
	  if (c == '\n') 
	  {
		  buffer[strLength++] = 0;
		  break;
	  }
	  
	  
	  if (c != '\r') 
	  {
		  for(i=0; i < 4 && ( (c & 0xff) != 0 ) ; i++)
		  {
			  if( strLength >= size )
			  {
				  // Increase the capacity dynamically
				  PrintMessage(MESG_ERROR, "SignonManager : Buffer is insufficient to store data");
				  return FPM_FALSE;
			  }
			  
			  buffer[strLength++] = (char)c;
			  c = c >> 8;
		  }
	  }
  }
  

 // PrintMessage(MESG_DEBUG,"SignonManager : ReadLine = %s ",buffer);
  return FPM_TRUE;

}




Unichar SignonManager::ReadCharUTF8()
{
	Unichar c = ReadChar();
	
	if ((c & 0x80) == 0x00) 
	{
		return c;
	} 
	else if ((c & 0xE0) == 0xC0) 
		{
			return (((c & 0x1F)<<6) + (ReadChar() & 0x3F));
		} 
		else if ((c & 0xF0) == 0xE0) 
			{
				return (((c & 0x0F)<<12) + ((ReadChar() & 0x3F)<<6) + (ReadChar() & 0x3F));
			} 


	return FPM_FALSE;  // 0 => not a utf8 character...
}






// This does buffered reading...
//
char SignonManager::ReadChar()
{
  const int buflen = 1000;
  static char buf[buflen+1];

  if (next >= last) 
  {
    next = 0;
    last = fread(buf, 1, buflen, signonFile);
    PrintMessage(MESG_DEBUG,"\n SignonManager : ReadChar = Read %d bytes ",last);

	if (last <= 0) // || feof(file)  
	{
      /* note that eof is not set until we read past the end of the file */
      PrintMessage(MESG_DEBUG,"\n SignonManager : ReadChar = End of file..! ");
	  return FPM_FALSE;
    }
  }

  return (buf[next++]);
}





int SignonManager::WriteCharUTF8(Unichar c) 
{
  if (c <= 0x7F) 
  {
	if( fputc((char)c, signonFile) == EOF )
		return FPM_SIGNON_FILE_WRITE_ERROR;
  } 
  else if (c <= 0x7FF) 
  {
	if( fputc( ((Unichar)0xC0) | ((c>>6) & 0x1F), signonFile) == EOF )
		return FPM_SIGNON_FILE_WRITE_ERROR;
    
	if( fputc( ((Unichar)0x80) | (c & 0x3F), signonFile ) == EOF )
		return FPM_SIGNON_FILE_WRITE_ERROR;

  } 
  else 
  {
    if( fputc( ((Unichar)0xE0) | ((c>>12) & 0xF), signonFile) == EOF )
		return FPM_SIGNON_FILE_WRITE_ERROR;
    if( fputc( ((Unichar)0x80) | ((c>>6) & 0x3F), signonFile) == EOF)
		return FPM_SIGNON_FILE_WRITE_ERROR;
    if( fputc( ((Unichar)0x80) | (c & 0x3F), signonFile) == EOF)
		return FPM_SIGNON_FILE_WRITE_ERROR;
  }

  return FPM_TRUE;
}



int SignonManager::WriteLine(char *line) 
{

  for(int i=0; i < strlen(line); i++)
  {
	if( WriteCharUTF8(line[i]) != FPM_TRUE )
		return FPM_SIGNON_FILE_WRITE_ERROR;

  }

  if( WriteCharUTF8('\n') != FPM_TRUE )
	  return FPM_SIGNON_FILE_WRITE_ERROR;

  return FPM_TRUE;
  
}




/*
*  Load the signon data from firefox signon file.....
*
*
*/
int SignonManager::LoadSignonData(char *firefoxProfileDir )
{
char header[256];
char buffer[4096];
char hostName[4096];
char name[1024];
int bufferLength = 4095;
int retValue;
char *clearData = NULL;
int count = 0;


	// open the signon file 
	if( (retValue = OpenSignonFile(firefoxProfileDir, SIGNON_FILE_NAME, "r")) != FPM_TRUE )
	{
		return retValue;   
	}

	// Clean up any previously loaded data...
	dataManager.RemoveAllData();

	// read the signon header information 
	if( ReadLine(header, 256) != FPM_TRUE )
	{
		PrintMessage(MESG_ERROR, "\n SignonManager : Error in reading signon format header %s", header);
		CloseSignonFile();
		return FPM_SIGNON_FILE_READ_ERROR;
	}

	// check if the format is right...
	if( strcmp(header, HEADER_VERSION) != 0)
	{
		PrintMessage(MESG_ERROR, "\n SignonManager : Header version information is not proper");
		CloseSignonFile();
		return FPM_SIGNON_FILE_INVALID_DATA;
	}


	PrintMessage(MESG_DEBUG, "\n\n ****** Reject Host List *******");

    // read the reject list 
	while ( ReadLine(buffer, bufferLength) == FPM_TRUE)
	{
    	// Check for end of reject list i.e full stop
		if (strlen(buffer) != 0 && buffer[0] == '.') 
		{
			break; // end of reject list
		}

		if( (retValue = dataManager.AddRejectHost(buffer) ) != FPM_TRUE )
		{
			CloseSignonFile();
			dataManager.RemoveAllData();  // clean up any partial loaded data
			
			return retValue;
		}

		PrintMessage(MESG_DEBUG, "\n Reject Host : %s ", buffer);

	}


	PrintMessage(MESG_DEBUG, "\n\n ****** Host list with username / password ****** ");


	while (ReadLine(hostName, bufferLength) == FPM_TRUE) 
	{
		// a blank line is perfectly valid here -- corresponds to a local file 
		if (strlen(hostName) < 1) 
		{
			PrintMessage(MESG_ERROR, "\n SignonManager : Host URL is not proper");
			CloseSignonFile();
			dataManager.RemoveAllData();  // clean up partial loaded data

			return FPM_ILLEGAL_HOSTNAME;
		}
		
		if( ( retValue = dataManager.AddHost(hostName) ) != FPM_TRUE )
		{
			CloseSignonFile();
			dataManager.RemoveAllData();  // clean up partial loaded data

			return retValue;
		}


		PrintMessage(MESG_DEBUG, "\n\n Host : %s ", hostName);
	
		// prepare to read the name/value pairs 
		while( ReadLine(buffer, bufferLength) == FPM_TRUE ) 
		{
			// line starting with . terminates the pairs for this URL entry 
			if (buffer[0] == '.') 
			{
				break; // end of URL entry 
			}

			// save the name part and determine if it is a password 
			unsigned char isPassword = 0;
			if (buffer[0] == '*') 
			{
				isPassword = 1;
				strcpy(name,&buffer[1]);
			    retValue = ReadLine(buffer, bufferLength);
			}
			else 
			{
				isPassword = 0;
				strcpy(name, buffer);
				retValue = ReadLine(buffer, bufferLength);
		    }

			PrintMessage(MESG_DEBUG, "\n\n name = %s and value = %s  ", name, buffer);
	
			// read in and save the value part
			if ( retValue != FPM_TRUE ) 
			{
				// error in input file so give up 
				PrintMessage(MESG_ERROR, "\n SignonManager : Error occured while reading VALUE for : %s ", name);
				CloseSignonFile();
				dataManager.RemoveAllData();  // clean up partial loaded data

				return FPM_SIGNON_FILE_READ_ERROR;				
			}

			// Decrypt the encrypted value....
			retValue = FPM_FALSE;
			if( ((retValue = cryptManager.DecryptString(buffer, &clearData)) == FPM_TRUE) && (clearData != NULL) )
			{
				// Add the name/value pair to the existing store....
				retValue = dataManager.AddHostElement(hostName, name, clearData, isPassword);
				
				if( retValue != FPM_TRUE )
				{
					CloseSignonFile();
					dataManager.RemoveAllData();  // clean up partial loaded data
					return retValue;
				}
			}
			else 
			{

				CloseSignonFile();
				dataManager.RemoveAllData();  // clean up partial loaded data
				
				return retValue;
			}
			
		}

	  }


	// Now close the signon file
	CloseSignonFile();

	// Print data for cross checking
#ifdef DEBUG	
	dataManager.PrintAllRejectHosts();
	dataManager.PrintAllHosts();
#endif
	return FPM_TRUE;

}


int SignonManager::WriteSignonData(char *firefoxProfileDir)
{
int retValue;
char *cryptData = NULL;
char *signonFilePath = NULL;
char *tempFilePath = NULL;
char fileName[256];

Host *t;
HostElement *h;
RejectHost *r;
	
	// TODO  : If signon data has not changed since last write then return...
/*  // There may be requirement to write empty data...
	if( dataManager.hostList == NULL )
	{
		PrintMessage(MESG_ERROR, "\n WriteSignonData : Signon data is empty...");
		return FPM_SIGNON_DATASTORE_EMPTY;
	}
*/

	// Generate random file name
	srand( (unsigned)time( NULL ) );
	sprintf(fileName,"signon_fpm_%d.txt", rand());

	// Setup the signon and temp filename..
	signonFilePath = (char*) malloc( strlen(firefoxProfileDir) + strlen(SIGNON_FILE_NAME) + 3);
	tempFilePath =(char*) malloc( strlen(firefoxProfileDir) + strlen(fileName) + 3);

	if( signonFilePath == NULL || tempFilePath == NULL)
	{
		PrintMessage(MESG_ERROR, "\n WriteSignonData : Insufficient memory ....");
		return FPM_INSUFFICIENT_MEMORY;
	}
	
	strcpy(signonFilePath, firefoxProfileDir);
	strcat(signonFilePath, "/");
	strcat(signonFilePath, SIGNON_FILE_NAME);

	strcpy(tempFilePath, firefoxProfileDir);
	strcat(tempFilePath, "/");
	strcat(tempFilePath, fileName);

	// Open signon file
	if( (retValue = OpenSignonFile(firefoxProfileDir, fileName, "w")) != FPM_TRUE )
	{
		PrintMessage(MESG_ERROR, "\nWriteSignonData :  Failed to create temp signon file : %s.", fileName);
		return retValue; 
	}
	

	// write out the format revision number 
	if( (retValue = WriteLine(HEADER_VERSION)) != FPM_TRUE)
		goto write_signon_error;

	// write out reject host list 
	for(r=dataManager.rejectHostList; r ; r=r->next)
	{
		if( (retValue = WriteLine(r->hostName)) != FPM_TRUE )
			goto write_signon_error;

	}
	
	// End of reject host list
	if( (retValue = WriteLine(".")) != FPM_TRUE )
		goto write_signon_error;

	  
	/* format for cached logins shall be:
	* url LINEBREAK {name LINEBREAK value LINEBREAK}*  . LINEBREAK
	* if type is password, name is preceded by an asterisk (*)
	*/


	// write out each URL node 
	for(t=dataManager.hostList; t ; t=t->next)
	{
		PrintMessage(MESG_DEBUG, "\n\nWriteSignonData :  Adding name/value pairs for host %s", t->hostName);
		
		if( (retValue = WriteLine(t->hostName)) != FPM_TRUE )
			goto write_signon_error;


		for(h=t->child; h ; h= h->next)
		{
			PrintMessage(MESG_DEBUG, "\n nWriteSignonData : %s  : %s ", h->name, h->value);
			
			if( h->isPassword)
			{
				if( (retValue = WriteCharUTF8('*')) != FPM_TRUE )
					goto write_signon_error;
			}
			
			if( (retValue = WriteLine(h->name)) != FPM_TRUE )
				goto write_signon_error;

			
			// encrypt the value
			retValue = FPM_FALSE;
			retValue = cryptManager.EncryptString(h->value, &cryptData);

			if( (retValue == FPM_TRUE) && (cryptData != NULL ))
			{
				if( (retValue = WriteLine(cryptData)) != FPM_TRUE )
					goto write_signon_error;
			}
			else
			{
				PrintMessage(MESG_ERROR, "\n nWriteSignonData : Encryption of value %s failed ", h->value);
				goto write_signon_error;
			}
		}

		if( (retValue = WriteLine(".")) != FPM_TRUE )
			goto write_signon_error;
	}

	// close this temporary file...
	CloseSignonFile();


	PrintMessage(MESG_DEBUG, "\n WriteSignonData : Removing previous signon file %s ", signonFilePath);

	// Delete previous signon file
	if( remove(signonFilePath) != 0 )
	{
		// Either file not present or file is locked...
		PrintMessage(MESG_ERROR, "\n WriteSignonData : Failed to delete signon file : %s ", signonFilePath);
		FILE *sfile= NULL;
		if( (sfile = fopen(signonFilePath,"r") ) != NULL )
		{
			// we are able to open signon file in read only mode => file is present
			fclose(sfile);
			remove(tempFilePath);  // delete temporary signon file 

			PrintMessage(MESG_ERROR, "\nWriteSignonData : Signon file is locked ");
			return FPM_SIGNON_FILE_LOCKED;
		}

		// File is not present ..that's good news , so continue...
	}

	PrintMessage(MESG_DEBUG, "success \n WriteSignonData : Renaming temp file %s back to signon file %s ", tempFilePath, signonFilePath);

	// Rename temp file back to signon file
	if( ( retValue = rename(tempFilePath, signonFilePath) ) != 0 )
	{
		PrintMessage(MESG_ERROR, "\n WriteSignonData : Failed to rename the temp file %s back to signon file %s ", tempFilePath, signonFilePath);
		PrintMessage(MESG_ERROR, "\n WriteSignonData : Following error has occured : %d ", retValue);
		
		return FPM_FALSE;
	}

	PrintMessage(MESG_DEBUG, "\n WriteSignonData : Successfully written new signon data ...");

	return FPM_TRUE;

write_signon_error:
	
	CloseSignonFile();
	remove(tempFilePath);  // remove the temporary signon file....

	return retValue;
}



int SignonManager::RemoveSignonData()
{

  return dataManager.RemoveAllData();

}


int SignonManager::AddHost(struct Host *host)
{

 return dataManager.AddHost(host);

}
	

int SignonManager::ModifyHost(struct Host *host)
{

 return dataManager.ModifyHost(host);

}
	


int SignonManager::RemoveHost(char *hostName)
{
	return dataManager.RemoveHost(hostName);
}
	



struct Host * SignonManager::GetHostInfo()
{

	return dataManager.hostList;
}