//
// $Id: Main.cpp,v 1.28 2006/10/27 15:51:50 bakerj Exp $
//
//****************************************************************************************//
// Copyright (c) 2006, 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 analysisStart = 0;
	int analysisEnd = 0;
	startTime = GetTickCount();
#endif
///////////////////////		DEBUG	///////////////////////////////////////


// init the log system - set the log level here
#ifdef _DEBUG
	Log::Init(Log::DEBUG, "ovaldi.log", false);
#else
	Log::Init(Log::INFO, "ovaldi.log", false);
#endif

	//////////////////////////////////////////////////////
	/////////  Parse Command-line Options  ///////////////
	//////////////////////////////////////////////////////
	ProcessCommandLine(argc, argv);

	//////////////////////////////////////////////////////
	//////////////////  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);
	}

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

	// Create header.
	string headerMessage = "";
	headerMessage.append("\n");
	headerMessage.append("----------------------------------------------------\n");
	headerMessage.append("OVAL Definition Interpreter\n");
    headerMessage.append("Version: " + Version::GetVersion() + " Build: " + Version::GetBuild() +"\n");
    headerMessage.append("Build date: " + Version::GetBuildDate() + "\n");
	headerMessage.append("Copyright (c) 2006 - 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::UnalteredMessage(headerMessage);

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

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

			cerr << errorMessage;
			Log::Fatal(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::UnalteredMessage(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::Fatal(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::Fatal(errorMessage);

			exit(0);
		}
	}

	//////////////////////////////////////////////////////
	////////////  parse oval.xml file	//////////////////
	//////////////////////////////////////////////////////
	try {

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

		//////////////////////////////////////////////////////
		///////  Check the version of the xml schema	//////
		//////////////////////////////////////////////////////
		// The interpreter is implemented to support a set version of the OVAL schema.
		// Make sure that the schema version of the xml file provided matches taht schema
		// version that the interpreters is built for.

		logMessage = " ** checking schema version\n";
				
		//	Get the schema version from the provided definitions.xml 
		DOMElement *schemaNode = XmlCommon::FindElementNS(DocumentManager::GetDefinitionDocument(), "schema_version");
		string strSchemaVersion = XmlCommon::GetDataNodeValue(schemaNode);
	    
		logMessage.append("     - Schema version - " + strSchemaVersion + "\n");		
		
		cout << logMessage;
		Log::UnalteredMessage(logMessage);
		
		if(strSchemaVersion.compare(SUPPORTED_VERSION) != 0) {
			string errorMessage = "ERROR: Unsupported schema version found.\n";
			errorMessage.append("The schema version of the definitions file is: " + strSchemaVersion + " ");
			errorMessage.append("The OVAL Definition Interpreter is compatible with version ");
			errorMessage.append(SUPPORTED_VERSION);
			errorMessage.append(".\n");

			cerr << errorMessage;
			Log::Fatal(errorMessage);
			exit(0);
		}

		//////////////////////////////////////////////////////
		//  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 OVAL System Charateristics file.\n";
			cout << logMessage;
			Log::UnalteredMessage(logMessage);

			DocumentManager::SetSystemCharacterisitcsDocument(processor->CreateDOMDocumentNS("http://oval.mitre.org/XMLSchema/oval-system-characteristics-5", "oval_system_characteristics"));	       

			//	Initialize the data collector and then get and instance.
			DataCollector::Init();
			AbsDataCollector* dataCollector = AbsDataCollector::Instance();

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

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

			dataCollector->Run();

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

			delete(dataCollector);

			// save the data model
			logMessage = " ** saving data model to " + Common::GetDatafile() +".\n";
			cout << logMessage;
			Log::UnalteredMessage(logMessage);
			processor->WriteDOMDocument(DocumentManager::GetSystemCharacterisitcsDocument(), Common::GetDatafile());

		//	Read in the data file
		} else {

			logMessage = " ** parsing " + Common::GetDatafile() + " for analysis.\n";
			logMessage.append("    - validating xml schema.\n");
			cout << logMessage;
			Log::UnalteredMessage(logMessage);

			//	Parse the data file
			DocumentManager::SetSystemCharacterisitcsDocument(processor->ParseFile(Common::GetDatafile()));
		}
		
		//////////////////////////////////////////////////////
		///////////////		Run Analysis		//////////////
		//////////////////////////////////////////////////////

		// create a results docuemnt
		DocumentManager::SetResultDocument(processor->CreateDOMDocumentNS("http://oval.mitre.org/XMLSchema/oval-results-5", "oval_results"));

		//	Create the analyzer
		Analyzer* analyzer = new Analyzer();

		//	Output status
		logMessage = " ** running the OVAL Definition analysis.\n";
		cout << logMessage;
		Log::UnalteredMessage(logMessage);

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

		#ifdef _DEBUG
			analysisEnd = GetTickCount();
		#endif

		// print the results 
		analyzer->PrintResults();

		//	write the result document
		logMessage = " ** saving OVAL results to " + Common::GetOutputFilename() + ".\n";
		cout << logMessage;
		Log::UnalteredMessage(logMessage);
		processor->WriteDOMDocument(DocumentManager::GetResultDocument(), Common::GetOutputFilename());

		delete analyzer;
		delete processor;

		// run the xsl
		if(!Common::GetNoXsl()) {
			logMessage = " ** running OVAL Results xsl: " + Common::GetXSLFilename() + ".\n";
			cout << logMessage;
			Log::UnalteredMessage(logMessage);
			XslCommon::ApplyXSL(Common::GetOutputFilename(), Common::GetXSLFilename(), Common::GetXSLOutputFilename());
		} else {
			logMessage = " ** skippinging OVAL Results xsl\n";
			cout << logMessage;
			Log::UnalteredMessage(logMessage);
		}

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

	//////////////////////////////////////////////////////
	///////////////////  Print Footer  ///////////////////
	//////////////////////////////////////////////////////
	string footerMessage = "";
	footerMessage.append("\n");
	footerMessage.append("----------------------------------------------------\n");
	cout << footerMessage;
	Log::UnalteredMessage(footerMessage);

	// DEBUG
	#ifdef _DEBUG
		endTime = GetTickCount();
		cout << "Collection 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

	Log::Shutdown();

	return 0;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~  Functions  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//
void ProcessCommandLine(int argc, char* argv[]) {
	//------------------------------------------------------------------------------------//
	//
	//  ABSTRACT
	//
	//  Prints out a list of option flags that can be used with this exe.
	//
	//------------------------------------------------------------------------------------//

	// 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();
				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();
					exit(0);
				} else {
					Common::SetDataFile(argv[2]);
					++argv;
					--argc;
				}

				break;	
				
			// **********  available options  ********** //
			case 'h':

				Usage();
				exit(0);

				break;

			// **********  use input data file  ********** //
			case 'i':

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

				break;

			// **********  save results in XML file  ********** //
			case 'r':

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

				break;

			// **********  no xsl  ********** //
			case 's':

				Common::SetNoXsl(true);

				break;

			// **********  apply the specified xsl to the results XML file  ********** //
			case 't':

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

				break;

			// **********  write specified xsl output to the specified file  ********** //
			case 'x':

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

				break;
			
			// **********  verbose mode  ********** //
			case 'p':

				Log::SetLevel(Log::DEBUG);
				Log::SetToScreen(true);

				break;

			// **********  location of external variable file  ********** //
			case 'v':

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

				break;

			// **********  MD5 Utility  ********** //
			case 'z':

				Common::SetGenerateMD5(true);

				break;

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

				Usage();
				exit(0);
		}

		++argv;
		--argc;
	}

	////////////////////////////////////////////////////////////
	// 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();
		exit(0);
	}
}

void Usage() {
	//------------------------------------------------------------------------------------//
	//
	//  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 << "   -h           = show options available from command line" << endl;
	cout << "\n";
	cout << "   -o <string>  = path to the definitions xml file DEFAULT=\"definitions.xml\"" << endl;
	cout << "   -d <string>  = save data to the specified XML file DEFAULT=\"system-characteristics.xml\"" << endl;
	cout << "   -r <string>  = save results to the specified XML file DEFAULT=\"oval-results.xml\"" << endl;
	cout << "   -v <string>  = get external variable values from the specified XML file. DEFAULT=\"external-variables.xml\"" << endl;
	cout << "\n";
	cout << "   -i <string>  = use data from input System Characteristics file" << endl;
	cout << "\n";
	cout << "   -m           = do not verify the oval.xml file with an MD5 hash" << endl;	
	cout << "   -p           = print all information and error messages" << endl;
	cout << "\n";
	cout << "   -s           = do not apply a stylesheet to the results xml." << endl;
	cout << "   -t <string>  = apply the sepcified xsl to the results xml. DEFAULT=\"results_to_html.xsl\"" << endl;
	cout << "   -x <string>  = output xsl transform results to the specified file. DEFAULT=\"results.html\"" << endl;
	cout << "\n";
	cout << "   -z           = return md5 of current definitions.xml" << endl;
	cout << endl;
}
