//
// $Id: Analyzer.cpp,v 1.17 2006/09/20 13:56:02 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;

//****************************************************************************************//
//								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::Run() {
	// -----------------------------------------------------------------------
	//	Abstract
	//
	//	Run the set of defintions in the defintions file against the sc file 
	//	to generate a set of oval results.
	//
	// -----------------------------------------------------------------------

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

	this->InitResultsDocument();

	// get the definitions element in the defintions file
	int prevIdLength = 1;
	int curIdLength = 1;
	DOMElement* definitionsElm = XmlCommon::FindElementNS(DocumentManager::GetDefinitionDocument(), "definitions");
	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();
}
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("    VULNERABILITIES FOUND:\n");
	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());
	XmlCommon::AddChildElement(DocumentManager::GetResultDocument(), generatorElm, "oval:schema_version", "5.0");
	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
	//
	// -----------------------------------------------------------------------
}


