//
// $Id: Analyzer.cpp,v 1.21 2006/10/31 16:14:15 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 "Analyzer.h"

DOMElement* Analyzer::definitionsElm = NULL;
DOMElement* Analyzer::testsElm = NULL;
StringPairVector Analyzer::trueResults;
StringPairVector Analyzer::falseResults;
StringPairVector Analyzer::unknownResults;
StringPairVector Analyzer::errorResults;
StringPairVector Analyzer::notEvaluatedResults;
StringPairVector Analyzer::notApplicableResults;

//****************************************************************************************//
//								Analyzer Class											  //	
//****************************************************************************************//

Analyzer::Analyzer() {
	// -----------------------------------------------------------------------
	//	Abstract
	//
	//	Create the oval analyzer
	// -----------------------------------------------------------------------
	this->resultsSystemElm = NULL;
	this->resultsElm = NULL;
}

Analyzer::~Analyzer() {
	// -----------------------------------------------------------------------
	//	Abstract
	//
	// -----------------------------------------------------------------------

}

// ***************************************************************************************	//
//								Public members												//
// ***************************************************************************************	//

DOMElement* Analyzer::GetResultsTestsElm() {
	// -----------------------------------------------------------------------
	//	Abstract
	//
	//	Return a ptr to the tests element in the results doc
	//
	// -----------------------------------------------------------------------

	return Analyzer::testsElm;

}

DOMElement* Analyzer::GetResultsDefinitionsElm() {
	// -----------------------------------------------------------------------
	//	Abstract
	//
	//	Return a ptr to the definitions element in the results document
	//
	// -----------------------------------------------------------------------

	return Analyzer::definitionsElm;
}

void Analyzer::AppendTrueResult(StringPair* pair) {
	// -----------------------------------------------------------------------
	//	Abstract
	//
	//	Append a true result
	//
	// -----------------------------------------------------------------------

	return Analyzer::trueResults.push_back(pair);
}

void Analyzer::AppendFalseResult(StringPair* pair) {
	// -----------------------------------------------------------------------
	//	Abstract
	//
	//	Append a false result
	//
	// -----------------------------------------------------------------------

	return Analyzer::falseResults.push_back(pair);
}

void Analyzer::AppendErrorResult(StringPair* pair) {
	// -----------------------------------------------------------------------
	//	Abstract
	//
	//	Append an error result
	//
	// -----------------------------------------------------------------------

	return Analyzer::errorResults.push_back(pair);
}

void Analyzer::AppendUnknownResult(StringPair* pair) {
	// -----------------------------------------------------------------------
	//	Abstract
	//
	//	Append an unknown result
	//
	// -----------------------------------------------------------------------

	return Analyzer::unknownResults.push_back(pair);
}

void Analyzer::AppendNotApplicableResult(StringPair* pair) {
	// -----------------------------------------------------------------------
	//	Abstract
	//
	//	Append a not applicable result
	//
	// -----------------------------------------------------------------------

	return Analyzer::notApplicableResults.push_back(pair);
}

void Analyzer::AppendNotEvaluatedResult(StringPair* pair) {
	// -----------------------------------------------------------------------
	//	Abstract
	//
	//	Append a not evaluated result
	//
	// -----------------------------------------------------------------------

	return Analyzer::notEvaluatedResults.push_back(pair);
}


void Analyzer::Run() {
	// -----------------------------------------------------------------------
	//	Abstract
	//
	//	Run the set of defintions in the defintions file against the sc file 
	//	to generate a set of oval results.
	//
	// -----------------------------------------------------------------------

	this->InitResultsDocument();

	// get the definitions element in the defintions file
	int prevIdLength = 1;
	int curIdLength = 1;
	DOMElement* definitionsElm = XmlCommon::FindElementNS(DocumentManager::GetDefinitionDocument(), "definitions");
	if(definitionsElm != NULL) {

		if(!Log::WriteToScreen())
			cout << "      Analyzing definition:  "; 

		DOMNodeList* definitionElms = definitionsElm->getChildNodes();
		unsigned int i = 0;
		while(i < definitionElms->getLength()) {
			DOMNode* tmpNode = definitionElms->item(i);
			if (tmpNode->getNodeType() == DOMNode::ELEMENT_NODE) {
				DOMElement *definitionElm = (DOMElement*)tmpNode;
				
				// get the dedfinition id and check the cache
				string definitionId = XmlCommon::GetAttributeByName(definitionElm, "id");
				if(Definition::SearchCache(definitionId) == NULL) {

					Log::Debug("Analyzing definition: " + definitionId);
					
					if(!Log::WriteToScreen()) {
						curIdLength = definitionId.length();
						string blankSpaces = "";
						if(prevIdLength > curIdLength)
							blankSpaces = Common::PadStringWithChar(blankSpaces, ' ', prevIdLength-curIdLength);

						string backSpaces = "";
						backSpaces = Common::PadStringWithChar(backSpaces, '\b', prevIdLength);
						string endBackSpaces = "";
						endBackSpaces = Common::PadStringWithChar(endBackSpaces, '\b', blankSpaces.length());
						cout << backSpaces << definitionId << blankSpaces << endBackSpaces;
					}

					Definition* def = new Definition();
					def->Parse(definitionElm);
					OvalEnum::ResultEnumeration result = def->Analyze();
					def->Write(Analyzer::GetResultsDefinitionsElm());
					/*StringPair* pair = new StringPair();
					pair->first = definitionId;
					pair->second = OvalEnum::ResultToString(result);
					if(result == OvalEnum::TRUE_RESULT) {
						this->trueResults.push_back(pair);
					} else if(result == OvalEnum::FALSE_RESULT) {
						this->falseResults.push_back(pair);
					} else if(result == OvalEnum::UNKNOWN_RESULT) {
						this->unknownResults.push_back(pair);
					} else if(result == OvalEnum::NOT_APPLICABLE_RESULT) {
						this->notApplicableResults.push_back(pair);
					} else if(result == OvalEnum::NOT_EVALUATED_RESULT) {
						this->notEvaluatedResults.push_back(pair);
					} else if(result == OvalEnum::ERROR_RESULT) {
						this->errorResults.push_back(pair);
					}*/
					
					prevIdLength = definitionId.length();
				}
   			}
			i++;
		}

		if(!Log::WriteToScreen()) {
			string fin = " FINISHED ";
			int curLen = fin.length();
			string blankSpaces = "";
			if(prevIdLength > curLen)
				blankSpaces = Common::PadStringWithChar(blankSpaces, ' ', prevIdLength-curLen);
			string backSpaces = "";
			backSpaces = Common::PadStringWithChar(backSpaces, '\b', prevIdLength);
			cout << backSpaces << fin << blankSpaces << endl;
		}

		Definition::ClearCache();
		Test::ClearCache();
		Item::ClearCache();
		State::ClearCache();
		VariableValue::ClearCache();

		this->FinializeResultsDocument();
	} else {
		string logMessage = "\n    No definitions found! \n";
		cout << logMessage;
		Log::UnalteredMessage(logMessage);
		throw AnalyzerException("");
	}
}
void Analyzer::PrintResults() {
	// -----------------------------------------------------------------------
	//	Abstract
	//
	//	Print the results of the analysis
	//
	// -----------------------------------------------------------------------

	///////////////////////////////////////////////////////////////////////////
	//	Display a header indicating that the definitions are being processed
	///////////////////////////////////////////////////////////////////////////
	cout << " ** OVAL definition results.\n" << endl;
	Log::UnalteredMessage(" ** OVAL definition results.\n\n");

	///////////////////////////////////////////////////////////////////////////
	//	Print the vulnerable results
	///////////////////////////////////////////////////////////////////////////
	cout << "    OVAL Id                            Result" << endl;
	cout << "    --------------------------------------------------" << endl;
	Log::UnalteredMessage("    OVAL Id                           Result\n");
	Log::UnalteredMessage("    --------------------------------------------------\n");

	// print each result value 
	StringPairVector::iterator it;
	for (it = this->trueResults.begin(); it != this->trueResults.end(); it++) {
		string resultStr = this->ResultPairToStr((*it));
		cout << resultStr << endl;
		Log::UnalteredMessage(resultStr + "\n");
	}
	for (it = this->falseResults.begin(); it != this->falseResults.end(); it++) {
		string resultStr = this->ResultPairToStr((*it));
		cout << resultStr << endl;
		Log::UnalteredMessage(resultStr + "\n");
	}

	for (it = this->unknownResults.begin(); it != this->unknownResults.end(); it++) {
		string resultStr = this->ResultPairToStr((*it));
		cout << resultStr << endl;
		Log::UnalteredMessage(resultStr + "\n");
	}

	for (it = this->errorResults.begin(); it != this->errorResults.end(); it++) {
		string resultStr = this->ResultPairToStr((*it));
		cout << resultStr << endl;
		Log::UnalteredMessage(resultStr + "\n");
	}

	for (it = this->notEvaluatedResults.begin(); it != this->notEvaluatedResults.end(); it++) {
		string resultStr = this->ResultPairToStr((*it));
		cout << resultStr << endl;
		Log::UnalteredMessage(resultStr + "\n");
	}

	for (it = this->notApplicableResults.begin(); it != this->notApplicableResults.end(); it++) {
		string resultStr = this->ResultPairToStr((*it));
		cout << resultStr << endl;
		Log::UnalteredMessage(resultStr + "\n");
	}

	cout << "    --------------------------------------------------\n" << endl;
	cout << "\n ** finished evaluating OVAL definitions.\n" << endl;	
	Log::UnalteredMessage("    --------------------------------------------------\n\n");
	Log::UnalteredMessage("\n ** finished evaluating OVAL definitions.\n\n");

}

string Analyzer::ResultPairToStr(StringPair* pair) {
	// -----------------------------------------------------------------------
	//	Abstract
	//
	//	Format a pair of definition id and result value as a string for display.
	//
	// -----------------------------------------------------------------------

	string resultStr = "    ";
	resultStr.append(Common::PadString(pair->first, 35));
	resultStr.append(Common::PadString(pair->second, 15));

	return resultStr;
}

void Analyzer::InitResultsDocument() {
	// -----------------------------------------------------------------------
	//	Abstract
	//
	//	Initialize the results document adding the basic structure to it
	//
	// -----------------------------------------------------------------------

	// Add all the namespace information
	XmlCommon::AddXmlns(DocumentManager::GetResultDocument(), "http://oval.mitre.org/XMLSchema/oval-common-5", "oval");
	XmlCommon::AddXmlns(DocumentManager::GetResultDocument(), "http://oval.mitre.org/XMLSchema/oval-definitions-5", "oval-def");
	XmlCommon::AddXmlns(DocumentManager::GetResultDocument(), "http://oval.mitre.org/XMLSchema/oval-system-characteristics-5", "oval-sc");
	XmlCommon::AddXmlns(DocumentManager::GetResultDocument(), "http://oval.mitre.org/XMLSchema/oval-results-5", "oval-res");
	XmlCommon::AddXmlns(DocumentManager::GetResultDocument(), "http://www.w3.org/2001/XMLSchema-instance", "xsi");

	// Add all the schema locations to start out with
	// others will be added as they appear
	XmlCommon::AddSchemaLocation(DocumentManager::GetResultDocument(), "http://oval.mitre.org/XMLSchema/oval-common-5 oval-common-schema.xsd");
	XmlCommon::AddSchemaLocation(DocumentManager::GetResultDocument(), "http://oval.mitre.org/XMLSchema/oval-system-characteristics-5 oval-system-characteristics-schema.xsd");
	XmlCommon::AddSchemaLocation(DocumentManager::GetResultDocument(), "http://oval.mitre.org/XMLSchema/oval-definitions-5 oval-definitions-schema.xsd");
	XmlCommon::AddSchemaLocation(DocumentManager::GetResultDocument(), "http://oval.mitre.org/XMLSchema/oval-results-5 oval-results-schema.xsd");

	// add the generator element
	DOMElement *ovalResultsElm = DocumentManager::GetResultDocument()->getDocumentElement();
	DOMElement *generatorElm = XmlCommon::AddChildElement(DocumentManager::GetResultDocument(), ovalResultsElm, "generator");
	XmlCommon::AddChildElement(DocumentManager::GetResultDocument(), generatorElm, "oval:product_name", "OVAL Definition Interpreter");
	XmlCommon::AddChildElement(DocumentManager::GetResultDocument(), generatorElm, "oval:product_version", Version::GetVersion() + " Build: " + Version::GetBuild());
	XmlCommon::AddChildElement(DocumentManager::GetResultDocument(), generatorElm, "oval:schema_version", "5.1");
	XmlCommon::AddChildElement(DocumentManager::GetResultDocument(), generatorElm, "oval:timestamp", Common::GetTimeStamp());

	// add the directives
	DOMElement *directivesElm = XmlCommon::AddChildElement(DocumentManager::GetResultDocument(), ovalResultsElm, "directives");
	
	DOMElement* defTrueElm = XmlCommon::AddChildElement(DocumentManager::GetResultDocument(), directivesElm, "definition_true");
	XmlCommon::AddAttribute(defTrueElm, "reported", "true");
	XmlCommon::AddAttribute(defTrueElm, "content", "full");
	
	DOMElement* defFalseElm = XmlCommon::AddChildElement(DocumentManager::GetResultDocument(), directivesElm, "definition_false");
	XmlCommon::AddAttribute(defFalseElm, "reported", "true");
	XmlCommon::AddAttribute(defFalseElm, "content", "full");

	DOMElement* defUnknownElm = XmlCommon::AddChildElement(DocumentManager::GetResultDocument(), directivesElm, "definition_unknown");
	XmlCommon::AddAttribute(defUnknownElm, "reported", "true");
	XmlCommon::AddAttribute(defUnknownElm, "content", "full");

	DOMElement* defErrorElm = XmlCommon::AddChildElement(DocumentManager::GetResultDocument(), directivesElm, "definition_error");
	XmlCommon::AddAttribute(defErrorElm, "reported", "true");
	XmlCommon::AddAttribute(defErrorElm, "content", "full");

	DOMElement* defNotEvalElm = XmlCommon::AddChildElement(DocumentManager::GetResultDocument(), directivesElm, "definition_not_evaluated");
	XmlCommon::AddAttribute(defNotEvalElm, "reported", "true");
	XmlCommon::AddAttribute(defNotEvalElm, "content", "full");

	DOMElement* defNotAppElm = XmlCommon::AddChildElement(DocumentManager::GetResultDocument(), directivesElm, "definition_not_applicable");
	XmlCommon::AddAttribute(defNotAppElm, "reported", "true");
	XmlCommon::AddAttribute(defNotAppElm, "content", "full");

	// used to add the definition doc here

	// add the results element
	DOMElement *resultsElm = XmlCommon::AddChildElement(DocumentManager::GetResultDocument(), ovalResultsElm, "results");
	this->resultsElm = resultsElm;

	// add the system element
	DOMElement *systemElm = XmlCommon::AddChildElement(DocumentManager::GetResultDocument(), resultsElm, "system");
	this->resultsSystemElm = systemElm;

	// add the definitions element
	DOMElement *definitionsElm = XmlCommon::AddChildElement(DocumentManager::GetResultDocument(), systemElm, "definitions");
	Analyzer::definitionsElm = definitionsElm;

	// add the tests element
	DOMElement *testsElm = XmlCommon::AddChildElement(DocumentManager::GetResultDocument(), systemElm, "tests");
	Analyzer::testsElm = testsElm;

	// used to add the sc documnet here
}

void Analyzer::FinializeResultsDocument() {
	// -----------------------------------------------------------------------
	//	Abstract
	//
	//	Finialize the results document copying the sc and definitions files
	//	into their appropriate locations
	//
	// -----------------------------------------------------------------------

	// add the oval_definitions element
	DOMElement *ovalResultsElm = DocumentManager::GetResultDocument()->getDocumentElement();
	DOMElement* definitionNode = (DOMElement*)DocumentManager::GetResultDocument()->importNode(DocumentManager::GetDefinitionDocument()->getDocumentElement(), true);
	ovalResultsElm->insertBefore(definitionNode, this->resultsElm);
	// need to clean up the attributes on the oval_definitiosn element.
	// copy all namespaces the documnet root
	// add all schema locations to the docuement root.
	// leave only the xmlns attribute on the element to seet the dedfault ns for all child elements.
	XmlCommon::CopyNamespaces(DocumentManager::GetDefinitionDocument(), DocumentManager::GetResultDocument());
	XmlCommon::CopySchemaLocation(DocumentManager::GetDefinitionDocument(), DocumentManager::GetResultDocument());
	XmlCommon::RemoveAttributes(definitionNode);


	// add the oval_system characteristics element
	DOMElement* scNode = (DOMElement*)DocumentManager::GetResultDocument()->importNode(DocumentManager::GetSystemCharacterisitcsDocument()->getDocumentElement(), true);
	this->resultsSystemElm->appendChild(scNode);
	// need to clean up the attributes on the oval_definitiosn element.
	// copy all namespaces the documnet root
	// add all schema locations to the docuement root.
	// leave only the xmlns attribute on the element to seet the dedfault ns for all child elements.
	XmlCommon::CopyNamespaces(DocumentManager::GetSystemCharacterisitcsDocument(), DocumentManager::GetResultDocument());
	XmlCommon::CopySchemaLocation(DocumentManager::GetSystemCharacterisitcsDocument(), DocumentManager::GetResultDocument());
	XmlCommon::RemoveAttributes(scNode);

}

//****************************************************************************************//
//								AnalyzerException Class									  //	
//****************************************************************************************//
AnalyzerException::AnalyzerException(string errMsgIn, int severity, Exception* ex) : Exception(errMsgIn, severity, ex) {
	// -----------------------------------------------------------------------
	//	Abstract
	//
	//	Set the error message and then set the severity to ERROR_FATAL. This is 
	//	done with the explicit call to the Exception class constructor
	//
	// -----------------------------------------------------------------------
}

AnalyzerException::~AnalyzerException() {
	// -----------------------------------------------------------------------
	//	Abstract
	//
	//	Do nothing for now
	//
	// -----------------------------------------------------------------------
}


