//
// $Id: Main.cpp,v 1.7 2004/07/08 19:50:44 bakerj Exp $
//
//************************** Property of the MITRE Corporation ***************************//
//
// Copyright (c) 2003 - The MITRE Corporation
//
// This file is part of the OVAL Definition Interpreter.
//
// The OVAL Definition Interpreter is free software; you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by the Free Software
// Foundation; either version 2 of the License.
//
// The OVAL Definition Interpreter 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License along with the OVAL
// Definition Interpreter; if not, write to the Free Software Foundation, Inc., 59 Temple
// Place, Suite 330, Boston, MA 02111-1307 USA
//
//****************************************************************************************//

#include "Main.h"

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~  Main  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//

int main(int argc, char* argv[])
{
	//------------------------------------------------------------------------------------//
	//
	//  ABSTRACT
	//
	//  This is the starting point for this app.
	//
	//------------------------------------------------------------------------------------//

///////////////////////		DEBUG	///////////////////////////////////////
#ifdef _DEBUG
	int startTime = 0;
	int endTime = 0;
	int collectionStart = 0;
	int collectionEnd = 0;
	int analysisStart = 0;
	int analysisEnd = 0;
	startTime = GetTickCount();
#endif
///////////////////////		DEBUG	///////////////////////////////////////

	//////////////////////////////////////////////////////
	////////////  Parse Command-line Options  ////////////
	//////////////////////////////////////////////////////

	string programName = argv[0];

	// There must be at least two arguments.  The program name and the xmlfile hash. (or
	// the -m flag signifing no hash is required)
	//
	// Loop through each argument passed into this application.  With each one we need to
	// check to see if it is a valid option.  After checking the argument, depricate the
	// argc variable.  Therefore, with each loop, argc should get smaller and smaller until
	// it is eventually less than or equal to 1.  (NOTE: We have already checked argv[0]
	// which is why we stop when argc is less than or equal to 1)  This signifies that we
	// have run out of arguments to check.

	while (argc > 1)
	{
		// Make sure that the switch control starts with a dash.

		if (argv[1][0] != '-')
		{
			if ((argc == 2) && (Common::GetVerifyXMLfile() == true))
			{
				Common::SetXMLfileMD5(argv[1]);
				++argv;
				--argc;
				continue;
			}
			else
			{
				Usage(programName);
				exit(0);
			}
		}

		// Determine which option has been signalled.  Perform necessary steps.

		switch (argv[1][1])
		{
			
			// **********  save the data  ********** //

			case 'd':

				if ((argc < 3) || (argv[2][0] == '-'))
				{
					Usage(programName);
					exit(0);
				}
				else
				{
					Common::SetSaveData(true);
					Common::SetDataFile(argv[2]);
					++argv;
					--argc;
				}

				break;	

			// **********  available options  ********** //

			case 'h':

				Usage(programName);
				exit(0);

				break;

			// **********  use input data file  ********** //

			case 'i':

				if ((argc < 3) || (argv[2][0] == '-'))
				{
					Usage(programName);
					exit(0);
				}
				else
				{
					Common::SetDataFile(argv[2]);
					Common::SetUseProvidedData(true);
					++argv;
					--argc;
				}

				break;

			// ********  don't compare xmlfile to MD5 hash  ******** //

			case 'm':

				Common::SetVerifyXMLfile(false);

				break;

			// **********  path to definitions.xml file  ********** //

			case 'o':

				if ((argc < 3) || (argv[2][0] == '-'))
				{
					Usage(programName);
					exit(0);
				}
				else
				{
					Common::SetXMLfile(argv[2]);
					++argv;
					--argc;
				}

				break;

			// **********  save results in XML file  ********** //

			case 'r':

				if ((argc < 3) || (argv[2][0] == '-'))
				{
					Usage(programName);
					exit(0);
				}
				else
				{
					Common::SetOutputToFile(true);
					Common::SetOutputFilename(argv[2]);
					++argv;
					--argc;
				}

				break;
		
			// **********  use software criteria only in definitions  ********** //

			case 's':

				Common::SetUseConfiguration(false);

				break;

			// **********  verbose mode  ********** //

			case 'v':

				Log::verboseMode = true;

				break;
		
			// **********  do not validate input xml file's schema	********** //
			case 'x':

				Common::SetValidateXMLSchemas(false);

				break;

			// **********  Read in variable values  ********** //
/*
			case 'y':

				if ((argc < 3) || (argv[2][0] == '-'))
				{
					Usage(programName);
					exit(0);
				}
				else
				{
					Common::SetUseVariableFile(true);
					Common::SetVariableFilename(argv[2]);
					++argv;
					--argc;
				}

				break;
*/
			// **********  MD5 Utility  ********** //

			case 'z':

				Common::SetGenerateMD5(true);

				break;

			// **********  Default  ********** //
			default:

				Usage(programName);
				exit(0);
		}

		++argv;
		--argc;
	}

	//////////////////////////////////////////////////////
	//////////////////  Check MD5 Flag  //////////////////
	//////////////////////////////////////////////////////

	if(Common::GetGenerateMD5())
	{
		// Open the xmlfile so we can pass it to the MD5 hash routine.  Make
		// sure we open it in binary mode.  If not, ctrl+m characters will be
		// stripped before computing the hash, resulting in the wrong hash
		// being produced.

		FILE* fpVerify = fopen(Common::GetXMLfile().c_str(), "rb");
		if (fpVerify == NULL)
		{
			cerr << endl << "** ERROR: Could not open file.";
			exit(0);
		}

		// Create the md5 hash.  This constructor creates a new md5 object,
		// updates the hash, finalizes the hash, and closes the FILE object.

		MD5 context(fpVerify);

		// Get the hash and print it to the screen.

		string hashBuf = context.hex_digest();
		cout << endl << hashBuf << endl;

		exit(0);
	}


	//////////////////////////////////////////////////////
	////////////////////  Clear Log  /////////////////////
	//////////////////////////////////////////////////////

	// Clear the existing log file.  This way, a malicious user can not place a log file
	// in the directory that they have access to, enabling them to view the output.

	if (Log::ClearLogFile() == false)
	{
		cerr << endl << "ERROR: Unable to clear the existing log file." << endl;
		exit(0);
	}

	//////////////////////////////////////////////////////
	///////////////////  Print Header  ///////////////////
	//////////////////////////////////////////////////////

	// Get the current time measured in the number of seconds elapsed since 1/1/70.  Then
	// format this time so it is readable.

	time_t currentTime;
	time(&currentTime);
	char* timeBuffer = ctime(&currentTime);
	Common::SetStartTime(timeBuffer);

	// Create header.

	string headerMessage = "";

	headerMessage.append("\n");
	headerMessage.append("----------------------------------------------------\n");
	headerMessage.append("OVAL XML Definition Interpreter\n");
       	headerMessage.append("Version: " + Version::GetVersion() + " Build: " + Version::GetBuild() +"\n");
       	headerMessage.append("Build date: " + Version::GetBuildDate() + "\n");
	headerMessage.append("Copyright (c) 2004 - The MITRE Corporation\n");
	headerMessage.append("----------------------------------------------------\n");
	headerMessage.append("\n");
	headerMessage.append(timeBuffer);
	headerMessage.append("\n");

	// Send header to console and log file.

	cout << headerMessage;
	Log::WriteLog(headerMessage);

	//////////////////////////////////////////////////////
	///////////////	Check MD5 Included	//////////////////
	//////////////////////////////////////////////////////

	// Check to make sure the MD5 hash was included if required.  
	// If not, we need to notify the user and exit.

	if ((Common::GetVerifyXMLfile() == true) &&
		(Common::GetXMLfileMD5().empty() == true))
	{
		cerr << endl << "You must supply the MD5 hash for the xmlfile or use the -m ";
		cerr << "command to skip the MD5 check." << endl;
		Usage(programName);
		exit(0);
	}

	//////////////////////////////////////////////////////
	//////////////  Disable All Privileges  //////////////
	//////////////////////////////////////////////////////
	#ifdef WIN32
		if (Common::DisableAllPrivileges() == false)
		{
			string errorMessage = "";

			errorMessage.append("\nERROR: Unable to disable all privileges.  The program ");
			errorMessage.append("will terminate.\n");

			cerr << errorMessage;
			Log::WriteLog(errorMessage);

			exit(0);
		}
	#endif

	//////////////////////////////////////////////////////
	///////////////  Verify oval.xml file - MD5  ///////////////
	//////////////////////////////////////////////////////
	string logMessage = "";

	if (Common::GetVerifyXMLfile() == true)
	{
		logMessage = " ** verifying the MD5 hash of'";
		logMessage.append(Common::GetXMLfile());
		logMessage.append("' file\n");

		cout << logMessage;
		Log::WriteLog(logMessage);

		// Open the xmlfile so we can pass it to the MD5 hash routine.  Make sure we open
		// it in binary mode.  If not, ctrl+m characters will be stripped before computing
		// the hash, resulting in the wrong hash being produced.

		FILE* fpVerify = fopen(Common::GetXMLfile().c_str(), "rb");
		if (fpVerify == NULL)
		{
			string errorMessage = "";

			errorMessage.append("\nERROR: Unable to open the '");
			errorMessage.append(Common::GetXMLfile());
			errorMessage.append("' file to verify it.  The program will terminate.\n");

			cerr << errorMessage;
			Log::WriteLog(errorMessage);

			exit(0);
		}

		// Create the md5 hash.  This constructor creates a new md5 object, updates the
		// hash, finalizes the hash, and closes the FILE object.
		
		MD5 context(fpVerify);

		string hashBuf = context.hex_digest();

		// Compare (without regard to case) the MD5 hash we just created with the one
		// given by the user.  If the two do not match, then exit the application.  Make
		// sure we compare in both directions.  _strnicmp only checks that the first X
		// characters of string2 are the same as the first X characters of string1.
		// This means that without the second check, if the supplied datafile hash is only
		// the first character of the real hash, then the test will succeed.

		if ((STRNICMP(hashBuf.c_str(),
					   Common::GetXMLfileMD5().c_str(),
					   Common::GetXMLfileMD5().length()) != 0) ||
			(STRNICMP(Common::GetXMLfileMD5().c_str(),
			           hashBuf.c_str(),
					   hashBuf.length()) != 0))
		{
			string errorMessage = "";

			errorMessage.append("\nERROR: The '");
			errorMessage.append(Common::GetXMLfile());
			errorMessage.append("' file is not valid.  The program will terminate.\n");

			cerr << errorMessage;
			Log::WriteLog(errorMessage);

			exit(0);
		}
	}

	//////////////////////////////////////////////////////
	////////////  parse oval.xml file	//////////////////
	//////////////////////////////////////////////////////
	try
	{
		//	Initialize DomDocument pointers
		XERCES_CPP_NAMESPACE_QUALIFIER DOMDocument *ovalDoc		= NULL;
		XERCES_CPP_NAMESPACE_QUALIFIER DOMDocument *dataDoc		= NULL;
		XERCES_CPP_NAMESPACE_QUALIFIER DOMDocument *resultDoc	= NULL;
		DOMProcessor *processor									= NULL;

		processor = new DOMProcessor();
		
		//	Write output.log message
		logMessage = " ** parsing " + Common::GetXMLfile() + " file.\n";
		if(Common::GetValidateXMLSchemas())
			logMessage.append("    - validating xml schema.\n");
		else
			logMessage.append("    - xml schema will not be validated.\n");

		cout << logMessage;
		Log::WriteLog(logMessage);
		
		//	Parse the file
		ovalDoc = processor->ParseFile(Common::GetXMLfile(), Common::GetValidateXMLSchemas());

		//////////////////////////////////////////////////////
		///////  Check the version of the xml schema	//////
		//////////////////////////////////////////////////////
		
		logMessage = " ** checking schema version\n";
	    logMessage.append("     - Definition Interpreter version - " + Version::GetVersion() + "\n");
				
		//	The major version of the schem must be less or equal to the 
		//	major version of this interpreter

		//	Get the schema version from the provided definitions.xml 
		DOMNode *ovalNode = DOMCommon::FindNode(ovalDoc, "oval");
		string strSchemaVersion = DOMCommon::GetAttributeByName(ovalNode, "oval:schemaVersion");
	
		logMessage.append("     - Schema version - " + strSchemaVersion + "\n");		
		
		cout << logMessage;
		Log::WriteLog(logMessage);
		
		//	Compare the versions as integers
		if(atoi(&(Version::GetVersion().at(0))) < atoi(&strSchemaVersion.at(0)))
		{
			string errorMessage = "";

			errorMessage.append("ERROR: The schema version of the definitions file is greater than that of the ");
			errorMessage.append("Definition Interpreter.  You must upgrade to the latest version.  The ");
			errorMessage.append("program will terminate.\n");

			cerr << errorMessage;
			Log::WriteLog(errorMessage);

			exit(0);
		}
/*
		//////////////////////////////////////////////////////
		//////  Gather variable information if needed	//////
		//////////////////////////////////////////////////////

		if(Common::GetUseVariableFile())
		{
			//	Create a new VariableCollector
			VariableCollector *varCollector = new VariableCollector(processor, Common::GetVariableFilename(), ovalDoc); 

			// Run the collector
			varCollector->Run();
		}
*/

		//////////////////////////////////////////////////////
		//  Get a data file									//
		//		- either run collector or parse input file	//
		//////////////////////////////////////////////////////
		//	Run the collector if desired
		if(!Common::GetUseProvidedData())
		{
			//	Create a new data document
			logMessage = " ** creating a new data document.\n";
			cout << logMessage;
			Log::WriteLog(logMessage);

			dataDoc = processor->CreateDOMDocument("system_characteristics");

			//	Create a new data collector
			DATACOLLECTOR *dataCollector = new DATACOLLECTOR(ovalDoc, dataDoc);

			logMessage = " ** gathering data for the OVAL definitions.\n";
			cout << logMessage;
			Log::WriteLog(logMessage);


///////////////////////		DEBUG	///////////////////////////////////////
#ifdef _DEBUG
			collectionStart = GetTickCount();
#endif
///////////////////////		DEBUG	///////////////////////////////////////

			dataCollector->Run();

///////////////////////		DEBUG	///////////////////////////////////////
#ifdef _DEBUG
			collectionEnd = GetTickCount();
#endif
///////////////////////		DEBUG	///////////////////////////////////////

			//	Finished with the data collector
			delete(dataCollector);

			//////////////////////////////////////////////////////
			//	Optionally save the data model					//
			//////////////////////////////////////////////////////
			if(Common::GetSaveData())
			{
				logMessage = " ** saving data model to " + Common::GetDatafile() +".\n";
				cout << logMessage;
				Log::WriteLog(logMessage);

				processor->WriteDOMDocument(dataDoc, Common::GetDatafile());
			}

		//	Read in the data file
		}else
		{
			logMessage = " ** parsing " + Common::GetDatafile() + " for analysis.\n";
			if(Common::GetValidateXMLSchemas())
				logMessage.append("    - validating xml schema.\n");
			else
				logMessage.append("    - xml schema will not be validated.\n");

			cout << logMessage;
			Log::WriteLog(logMessage);

			//	Parse the data file
			dataDoc = processor->ParseFile(Common::GetDatafile(), Common::GetValidateXMLSchemas());
		}

		//////////////////////////////////////////////////////
		///////////////		Run Analysis		//////////////
		//////////////////////////////////////////////////////

		//	Create the analyzer
		DOMOvalXMLAnalyzer *test = new DOMOvalXMLAnalyzer(ovalDoc, dataDoc, processor, Common::GetUseConfiguration());
		//	Output status
		logMessage = " ** running the data analysis.\n    - This may take a while.\n\n";
		cout << logMessage;
		Log::WriteLog(logMessage);

///////////////////////		DEBUG	///////////////////////////////////////
#ifdef _DEBUG
		analysisStart = GetTickCount();
#endif
///////////////////////		DEBUG	///////////////////////////////////////
		//	Evaluate the tests
		resultDoc = test->Run();
///////////////////////		DEBUG	///////////////////////////////////////
#ifdef _DEBUG
		analysisEnd = GetTickCount();
#endif
///////////////////////		DEBUG	///////////////////////////////////////

		//	write the result document
		if(Common::GetOutputToFile())
		{
			logMessage = " ** saving detailed results to " + Common::GetOutputFilename() + ".\n";
			cout << logMessage;
			Log::WriteLog(logMessage);
			processor->WriteDOMDocument(resultDoc, Common::GetOutputFilename());
		}

	}
	catch(Exception ex)
	{
		cout << ex.GetErrorMessage() << endl;
		Log::WriteLog(ex.GetErrorMessage());
	}

	//////////////////////////////////////////////////////
	///////////////////  Print Footer  ///////////////////
	//////////////////////////////////////////////////////

	// Create footer.

	string footerMessage = "";

	footerMessage.append("\n");
	footerMessage.append("----------------------------------------------------\n");

	// Send footer to console and log file.

	cout << footerMessage;
	Log::WriteLog(footerMessage);

///////////////////////		DEBUG	///////////////////////////////////////
#ifdef _DEBUG
	endTime = GetTickCount();
	cout << "\n\n\nCollectionRun time: " << collectionEnd - collectionStart << endl;
	cout << "Analysis time: " << analysisEnd - analysisStart << endl;
	cout << "Other time: " << (endTime - startTime) - ((analysisEnd - analysisStart) + (collectionEnd - collectionStart)) << endl;
	cout << "Run time: " << endTime - startTime << endl;
#endif
///////////////////////		DEBUG	///////////////////////////////////////


	return 0;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~  Functions  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//

void Usage(string programNameIn)
{
	//------------------------------------------------------------------------------------//
	//
	//  ABSTRACT
	//
	//  Prints out a list of option flags that can be used with this exe.
	//
	//------------------------------------------------------------------------------------//

	cout << endl;
	cout << "Command Line: >ovaldi [option] MD5Hash" << endl;
	cout << endl;
	cout << "Options:" << endl;
	cout << "   -d <string> = save data to the specified XML file DEFAULT=\"system-characteristics.xml\"" << endl;
	cout << "   -h          = show options available from command line" << endl;
	cout << "   -i <string> = use data from input System Charactoristics file" << endl;
	cout << "   -m          = do not verify the oval.xml file with an MD5 hash" << endl;
	cout << "   -o <string> = path to the definitions.xml file" << endl;
	cout << "   -r <string> = save results to the specified XML file DEFAULT=\"results.xml\"" << endl;
	cout << "   -s          = check for flawed software on disk only" << endl;
	cout << "   -v          = print all information and error messages" << endl;
	cout << "   -x          = do not validate input xml schemas. Applies to all input files." << endl;
//	cout << "   -y <string> = get variable values from the specified XML file." << endl; //DEFAULT=\"variables.xml\"" << endl;
	cout << "   -z          = return md5 of current definitions.xml" << endl;
	cout << endl;
}
