//
// $Id: Main.cpp,v 1.27 2005/08/05 20:19:15 bakerj Exp $
//
//****************************************************************************************//
// Copyright (c) 2005, The MITRE Corporation
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
//     * Redistributions of source code must retain the above copyright notice, this list
//       of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above copyright notice, this 
//       list of conditions and the following disclaimer in the documentation and/or other
//       materials provided with the distribution.
//     * Neither the name of The MITRE Corporation nor the names of its contributors may be
//       used to endorse or promote products derived from this software without specific 
//       prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY 
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
// OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT 
// SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
// OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
// TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
// EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
//****************************************************************************************//

#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 pruneStart = 0;
	int pruneEnd = 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])
		{
			
			// **********  class specification ********** //

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


			// **********  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;	
			
			// **********  use software criteria only in definitions  ********** //

			case 'f':

				Common::SetUseConfiguration(false);

				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;

			// **********  path to mapping file ********** //

			case 'p':
				
				if ((argc < 3 ) || (argv[2][0]) == '-')
				{
					Usage(programName);
					exit(0);
				}
				else
				{
				Common::SetMappingFile(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;

			// **********  status value ********** //

			case 's':
				
				if ((argc < 3 ) || (argv[2][0]) == '-')
				{
					Usage(programName);
					exit(0);
				}
				else
				{
				Common::SetStatusType(argv[2]);
				++argv;
				--argc;
				}
				
				break;
			
			// **********  verbose mode  ********** //

			case 'v':

				Log::verboseMode = true;

				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;
		XERCES_CPP_NAMESPACE_QUALIFIER DOMDocument *mappingDoc	= NULL;
		XmlProcessor *processor									= NULL;

		processor = new XmlProcessor();
		
		//	Write output.log message
		logMessage = " ** parsing " + Common::GetXMLfile() + " file.\n";
		logMessage.append("    - validating xml schema.\n");
		cout << logMessage;
		Log::WriteLog(logMessage);
		
		//	Parse the file
		ovalDoc = processor->ParseFile(Common::GetXMLfile());
		

		//////////////////////////////////////////////////////
		///////  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 
		DOMElement *schemaNode = XmlCommon::FindNodeNS(ovalDoc, "schema_version");
		string strSchemaVersion = XmlCommon::GetDataNodeValue(schemaNode);
	    
		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);
		}


		//////////////////////////////////////////////////////
		//////  Parse the mapping file					//////
		//////////////////////////////////////////////////////
			logMessage = " ** parsing " + Common::GetMappingfile() + "\n";
			logMessage.append("    - validating xml schema.\n");
			cout << logMessage;
			Log::WriteLog(logMessage);

			//	Parse the data file
			mappingDoc = processor->ParseFile(Common::GetMappingfile());

/*
		//////////////////////////////////////////////////////
		//////  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 Family Type from datafile			//
		//////////////////////////////////////////////////////

		if(Common::GetUseProvidedData()) {

 			XERCES_CPP_NAMESPACE_QUALIFIER DOMDocument *dataDocTemp = NULL;

			dataDocTemp = processor->ParseFile(Common::GetDatafile());
			DOMElement *oval_dataNode = XmlCommon::FindNode(dataDocTemp, "system_characteristics");
			string familyTemp = XmlCommon::GetAttributeByName(oval_dataNode, "family");
			Common::SetFamilyType(familyTemp);
		}

		//////////////////////////////////////////////////////
		//////			  Prune Data File			  	//////
		//////////////////////////////////////////////////////
		logMessage = " ** removing unneeded content from " + Common::GetXMLfile() + "\n";
		cout << logMessage;
		Log::WriteLog(logMessage);

		//Ensure statusTypes are set to default
		Common::SetStatusType(Common::GetStatusType());

		// DEBUG
		#ifdef _DEBUG
			pruneStart = GetTickCount();
		#endif
		//Prune ovaldoc to only include desired definitions & tests
		ovalDoc = processor->PruneDOMDocument(ovalDoc);
		// DEBUG
		#ifdef _DEBUG
			pruneEnd = GetTickCount();
		#endif	

		//////////////////////////////////////////////////////
		//  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


			dataCollector->Run();

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


			//	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";
			logMessage.append("    - validating xml schema.\n");
			cout << logMessage;
			Log::WriteLog(logMessage);

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

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

		//	Create the analyzer
		AnalysisEngine *analyzer = new AnalysisEngine(ovalDoc, dataDoc, mappingDoc, processor, Common::GetUseConfiguration(), Common::GetFamilyType());

		//	Output status
		logMessage = " ** running the data analysis.\n    - This may take a while.\n\n";
		cout << logMessage;
		Log::WriteLog(logMessage);

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

		#ifdef _DEBUG
			analysisEnd = GetTickCount();
		#endif
		
		//	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());
		}

		delete analyzer;

	} 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\nPrune time: " << pruneEnd - pruneStart << endl;
		cout << "CollectionRun 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

	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 << "   -c <string>  = analyze and collect data for definitions of the specified class. DEFAULT=\"vulnerability\"" << 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 Characteristics 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 DEFAULT=\"definitions.xml\"" << endl;
	cout << "   -p <string>  = path to the oval mapping XML file DEFAULT=\"mapping.xml\"" << endl;
	cout << "   -r <string>  = save results to the specified XML file DEFAULT=\"results.xml\"" << endl;
	cout << "   -f           = check for flawed software on disk only" << endl;
	cout << "   -s <string>  = analyze and collect data for the specified definitions status" << endl;
    cout << "      A bitmap indicating which status to include. Ordering as follows:" << endl;
	cout << "      INCOMPLETE, INITIAL SUBMISSION, DRAFT, INTERIM, ACCEPTED, DEPRECIATED " << endl; 
    cout << "      DEFAULT=\"000110\" or INTERIM, ACCEPTED" << endl;
	cout << "   -v           = print all information and error messages" << 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;
}
