//
// $Id: Main.cpp,v 1.3 2004/07/12 17:56:22 bakerj Exp $
//
//************************** Property of the MITRE Corporation ***************************//
//
// Copyright (c) 2003 - The MITRE Corporation
//
// This file is part of the OVAL Query Interpreter.
//
// The OVAL Query 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 Query 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
// Query 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.
	//
	//------------------------------------------------------------------------------------//

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

	string programName = argv[0];

	// There must be at least two arguments.  The program name and the datafile 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::GetVerifyDatafile() == true))
			{
				Common::SetDatafileMD5(argv[1]);
				++argv;
				--argc;
				continue;
			}
			else
			{
				Usage(programName);
				exit(0);
			}
		}

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

		switch (argv[1][1])
		{
			// **********  Create this database  ********** //

			case 'd':

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

				break;

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

			case 'h':

				Usage(programName);
				exit(0);

				break;

			// **********  Use this database  ********** //

			case 'i':

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

				break;

			// **********  compare datafile to MD5 hash  ********** //

			case 'm':

				Common::SetVerifyDatafile(false);

				break;

			// **********  path to definitions.sql file  ********** //

			case 'o':

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

				break;

			// **********  save results in the spcified HTML file  ********** //

			case 'r':

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

				break;

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

			case 'v':

				Log::verboseMode = true;

				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 datafile 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::GetDatafile().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 Query 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::GetVerifyDatafile() == true) &&
		(Common::GetDatafileMD5().empty() == true))
	{
		cerr << endl << "You must supply the MD5 hash for the datafile or use the -m ";
		cerr << "command to skip the MD5 check." << endl;
		Usage(programName);
		exit(0);
	}

#ifdef WIN32
	//////////////////////////////////////////////////////
	//////////////  Disable All Privileges  //////////////
	//////////////////////////////////////////////////////

	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 definitions.sql file  /////////////
	//////////////////////////////////////////////////////

	if (Common::GetVerifyDatafile() == true)
	{
		logMessage = " ** verifying definitions.sql file\n";
		cout << logMessage;
		Log::WriteLog(logMessage);

		// Open the datafile 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::GetDatafile().c_str(), "rb");
		if (fpVerify == NULL)
		{
			string errorMessage = "";

			errorMessage.append("\nERROR: Unable to open the '");
			errorMessage.append(Common::GetDatafile());
			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::GetDatafileMD5().c_str(), Common::GetDatafileMD5().length()) != 0) ||
			(STRNICMP(Common::GetDatafileMD5().c_str(), hashBuf.c_str(), hashBuf.length()) != 0))
		{
			string errorMessage = "";

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

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

			exit(0);
		}
	}

	//////////////////////////////////////////////////////
	//////////////  Read definitions.sql file  //////////////
	//////////////////////////////////////////////////////

	logMessage = " ** reading definitions.sql file\n";
	cout << logMessage;
	Log::WriteLog(logMessage);

	// Open the definitions.sql file.  A control flag of "r" says to open for reading. If the
	// file does not exist or cannot be found, the fopen call fails.

    FILE* fp = NULL;
    fp = fopen(Common::GetDatafile().c_str(), "r");
	if (fp == NULL)
	{
		string errorMessage = "";

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

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

		exit(0);
	}

	// Find the section headers.  A section header starts with a '['.  If the line read
	// is not a section header, then disregard it and read the next line.  Stop when the
	// end of the file has been reached.  (GetNextLine() will return an empty string)

	string nextLine = GetNextLine(fp);
    while (nextLine.empty() == false)
	{
		// **********  [Schema]   ********** //

		if (nextLine.compare("-- [Schema]") == 0)
		{
			// Look at each line after the [Schema] section header.  Make sure we stop if
			// GetNextLine() returns an empty string as this signifies the end of the file.

			while (nextLine.empty() == false)
			{
				nextLine = GetNextLine(fp);
				if (nextLine.empty() == true) break;

				// If the next section header has been reached, then break out of these
				// loops.  The line is a section header if it starts with a '-- ['.

				if (nextLine.substr(0, 4).compare("-- [") == 0) break;
				
				// Read in the next query.  The query can span multiple lines and must be
				// appended together.  Make sure that a space character is added between
				// appended lines.  The end of the query is signified by a line starting
				// with a ';' character.

				string schemaQuery = "";
				while (nextLine.empty() == false)
				{
					if (nextLine.at(0) == ';')
					{
						QueryIdPair* ptrQueryIdPair;
						ptrQueryIdPair = new QueryIdPair("0", schemaQuery);
						scVector.push_back(ptrQueryIdPair);
						break;
					}
					else
					{
						if (schemaQuery.empty() == false) schemaQuery.append(" ");
						schemaQuery.append(nextLine);
						nextLine = GetNextLine(fp);
					}
				}
			}
		}

		// **********   [Conf]    ********** //

		else if (nextLine.compare("-- [Conf]") == 0)
		{
			// Look at each line after the [Conf] section header.  Make sure we stop if
			// GetNextLine() returns an empty string as this signifies the end of the file.

			while (nextLine.empty() == false)
			{
				nextLine = GetNextLine(fp);
				if (nextLine.empty() == true) break;

				// If the next section header has been reached, then break out of these
				// loops.  The line is a section header if it starts with a '['.

				if (nextLine.substr(0, 4).compare("-- [") == 0) break;
				

				// Read in the next query.  The query can span multiple lines and must be
				// appended together.  Make sure that a space character is added between
				// appended lines.  The end of the query is signified by a line starting
				// with a ';' character.

				string confQuery = "";
				while (nextLine.empty() == false)
				{
					if (nextLine.at(0) == ';')
					{
						QueryIdPair* ptrQueryIdPair;
						ptrQueryIdPair = new QueryIdPair("", confQuery);
						cfVector.push_back(ptrQueryIdPair);
						break;
					}
					else
					{
						if (confQuery.empty() == false) confQuery.append(" ");
						confQuery.append(nextLine);
						nextLine = GetNextLine(fp);
					}
				}
			}
		}
		
		// **********   [Query]   ********** //

		else if (nextLine.compare("-- [Query]") == 0)
		{
			// Look at each line after the [Query] section header.  Make sure we stop if
			// GetNextLine() returns an empty string as this signifies the end of the file.
			//string previd = "";
			while (nextLine.empty() == false)
			{
				nextLine = GetNextLine(fp);
				if (nextLine.empty() == true) break;

				// If the next section header has been reached, then break out of these
				// loops.  The line is a section header if it starts with a '['.

				if (nextLine.substr(0, 3).compare("-- [") == 0) break;
				
				// Read in the OVAL query ID.

				string ovalID = "";
				ovalID = nextLine.substr(12, nextLine.length()-11);
				if (ovalID.length() > 16)
				{
					string errorMessage = "";

					errorMessage.append("\nERROR: There is an invalid ovalID.  ");
					errorMessage.append("Each id can be at most 16 characters.\n");

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

					exit(0);
				}

				//previd = ovalID;
				nextLine = GetNextLine(fp);

				// Read in the next query.  The query can span multiple lines and must be
				// appended together.  Make sure that a space character is added between
				// appended lines.  The end of the query is signified by a line starting
				// with a ';' character.

				string ovalQuery = "";
				while (nextLine.empty() == false)
				{
					if (nextLine.at(0) == ';')
					{
						QueryIdPair* ptrQueryIdPair;
						ptrQueryIdPair = new QueryIdPair(ovalID, ovalQuery);
						queryVector.push_back(ptrQueryIdPair);
						break;
					}
					else
					{
						if (ovalQuery.empty() == false) ovalQuery.append(" ");
						ovalQuery.append(nextLine);
						nextLine = GetNextLine(fp);
					}
				}
			}
		}

		// **********   *******   ********** //

		else
		{
			nextLine = GetNextLine(fp);
		}
	}

	// Close the definitions.sql file.

	fclose(fp);


	//////////////////////////////////////////////////////
	//////////////  Open the OVAL database  //////////////
	//////////////////////////////////////////////////////

	logMessage = " ** opening OVAL database\n";
	Log::WriteLog(logMessage);
	cout << logMessage;
	DBInterface *db;
	
	try 
	{
		db = new DBInterface(Common::GetDatabase(), Common::GetUseExistingData());

	}catch(Exception ex)
	{
		string errorMessage = "";

		errorMessage.append("\nERROR: There was an error connecting to the database '");
		errorMessage.append(Common::GetDatabase());
		errorMessage.append("'.  The program will terminate.\n");

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

		exit(0);

	}

	if(!Common::GetUseExistingData())
	{

		//////////////////////////////////////////////////////
		///////////////  Refresh Oval Schema  ////////////////
		//////////////////////////////////////////////////////

		logMessage = " ** refreshing OVAL schema\n";
		cout << logMessage;
		Log::WriteLog(logMessage);

		// Iterate through each OVAL schema query.  For each one, execute it.  Do not report
		// errors from here as they will mostly be for missing tables in DROP statements,
		// which would be corrected by the following CREATE statement.
		stringVector *svNULL = NULL;

		for (scIterator=scVector.begin(); scIterator!=scVector.end(); scIterator++)
		{
			db->ExecSQL((*(*scIterator)).query, svNULL);
		}

	}else {
		logMessage = " ** using provided database: " + Common::GetDatabase() + "\n";
		cout << logMessage;
		Log::WriteLog(logMessage);

	}

	//////////////////////////////////////////////////////
	/////////////////  Compare Versions  /////////////////
	//////////////////////////////////////////////////////

	logMessage = " ** checking file versions\n";
	cout << logMessage;
	Log::WriteLog(logMessage);
	
	string tmpVersion = "";

	//	Prepare a message with the version info
	string versionMessage = "";
	versionMessage.append("\n     - Query Interpreter - version ");
	versionMessage.append(Version::GetVersion());

	//	Create a query to get the version
	string sqlVersionQuery = "SELECT Placeholder FROM Placeholder";

	//	Create a string vector to store version query results in
	stringVector vVersionResults;

	// Execute the query and print the results.
	if (db->ExecSQL(sqlVersionQuery, &vVersionResults) == true)
	{
		if (vVersionResults.empty() == true)
		{
			string errorMessage = "";
			errorMessage.append("     - ERROR: There was an error retrieving the version of the specified definitions file.\n");
			cerr << errorMessage;
			Log::WriteLog(errorMessage);
			exit(0);
		}
		else 
			tmpVersion = vVersionResults.at(0);
	}
	else
	{
		string errorMessage = "";
		errorMessage.append("     - ERROR: There was an error retrieving the version of the specified definitions file.\n");
		cerr << errorMessage;
		Log::WriteLog(errorMessage);
		exit(0);
	}

	// Parse the version string into osName and datafileVersion.
	//	Version string is of the format:
	//	<family>_<Major Version>.<Minor Version>_<YYYYMMDD>
	//	For example: 'Windows_2.2_20040409'
	string supportedOs = SUPPORTED_FAMILY;
	supportedOs.append("_");
	int endOsName = tmpVersion.find("_", 0);
	int endVer = tmpVersion.find("_", endOsName + 1);
	osName = tmpVersion.substr(0, endOsName+1);
	if (STRICMP(osName.c_str(), supportedOs.c_str()) != 0)
	{
		string errorMessage = "";

		errorMessage.append("\nERROR: The osName in the version string in '");
		errorMessage.append(Common::GetDatafile());
		errorMessage.append("' is not valid.  It should start with '" + supportedOs + "_'.\n");

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

		exit(0);
	}
	datafileVersion = tmpVersion.substr(endOsName+1, endVer-(endOsName+1));

		
	// We only need to check the major version of each file.  Converting the version to an
	// int will drop the minor version.

	int tmpAppVer = atoi(&Version::GetVersion().at(0));
	int tmpDatafileVer = atoi(&datafileVersion.at(0));

	versionMessage.append("\n     - Datafile - version ");
	versionMessage.append(datafileVersion);
	versionMessage.append("\n\n");

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

	if (tmpAppVer < tmpDatafileVer)
	{
		string errorMessage = "";

		errorMessage.append("ERROR: The version of the Datafile is higher than that of the ");
		errorMessage.append("Query Interpreter.  You must upgrade to the latest version.  The ");
		errorMessage.append("program will terminate.\n");

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

		exit(0);
	}


	if(!Common::GetUseExistingData())
	{
		//////////////////////////////////////////////////////
		///////////////  Populate Conf Tables  ///////////////
		//////////////////////////////////////////////////////

		logMessage = " ** populating conf tables\n";
		cout << logMessage;
		Log::WriteLog(logMessage);

		bool errorOccured = false;

		// Iterate through each conf query.  For each one, execute it.

		for (cfIterator=cfVector.begin(); cfIterator!=cfVector.end(); cfIterator++)
		{
			if (db->ExecSQL((*(*cfIterator)).query) != true)
			{
				if (errorOccured == false)
				{
					cout << endl;
					errorOccured = true;
				}

				string errorMessage = "";

				errorMessage.append("     - ERROR: There was an error running this configuration ");
				errorMessage.append("insert statement - ");
				errorMessage.append((*(*cfIterator)).query);
				errorMessage.append("\n");

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

		if (errorOccured == true)
		{
			cout << endl;
		}

		//////////////////////////////////////////////////////
		//////////  Populate the database   //////////////////
		//////////////////////////////////////////////////////	
		DataCollector *myData = new DataCollector(db);
		myData->Run();
	}


	//////////////////////////////////////////////////////
	//////////  Loop through the OVAL queries  ///////////
	//////////////////////////////////////////////////////

	logMessage = " ** running OVAL queries\n";
	Log::WriteLog(logMessage);
	cout << logMessage;

	stringVector resultVector;
	stringVector notVulnerableVector;

	// Set up any external result file.

	StartOutput();

	// Iterate through each OVAL query.  For each one, execute it and output the CVE number
	// if the system is found vulnerable.

	for (qvIterator=queryVector.begin(); qvIterator!=queryVector.end(); qvIterator++)
	{
		string ovalQuery;

		// The EXISTS keyword is not understood by SQLite, so we therefore must rework the
		// OVAL queries.
		
		ovalQuery = ReplaceExists((*(*qvIterator)).query);

		// Clear the resultVector.
		resultVector.erase(resultVector.begin(), resultVector.end());

		// Execute the query and print the results.
		if (db->ExecSQL(ovalQuery, &resultVector) == true)
		{
			if (resultVector.empty() == false)
			{
				string resultMessage = "";
				resultMessage.append((*(*qvIterator)).id);
				resultMessage.append(" - ");
				resultMessage.append(resultVector.front());
				PrintOutput(resultMessage);
			}
			else
			{ 
			        notVulnerableVector.push_back((*(*qvIterator)).id);
			}
		}
		else
		{
			string errorMessage = "";
			errorMessage.append("     - ERROR: There was an error retrieving results for ");
			errorMessage.append("OVAL query - ");
			errorMessage.append((*(*qvIterator)).id);
			errorMessage.append("\n");
			cerr << errorMessage;
			Log::WriteLog(errorMessage);
		}
	}

	PrintNotVulnerableOutput(notVulnerableVector);

	// Close any external result file.

	StopOutput();

	//////////////////////////////////////////////////////
	/////////////  Close the OVAL database  //////////////
	//////////////////////////////////////////////////////

	logMessage = " ** closing OVAL database\n";
	Log::WriteLog(logMessage);
	cout << logMessage;

	delete db;

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

	// Create footer.

	string footerMessage = "";

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

	// Send footer to console and log file.

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

	return 0;
}

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

string GetNextLine(FILE* fpIn)
{
	//------------------------------------------------------------------------------------//
	//
	//  ABSTRACT
	//
	//  This function grabs the next valid line from the specified file.  To determine if
	//  a line is valid, first strip any leading or trailing whitespace.  If the resulting
	//  line is empty, or if it starts with a '--' (the line is a comment), then it is
	//  invalid and therefore skipped.  The next line is then looked at until a valid line
	//  is found.
	//
	//  If there are no more lines to read, then an empty string is returned.
	//
	//------------------------------------------------------------------------------------//

	char readBuffer[BUFFER_SIZE];
	string strBuffer = "";

	while (true)
	{
		strBuffer = "";

		// Clear the buffer and read in the next line from the file.  If there are no more
		// lines to read, (fgets() == NULL) then return an empty string.

		memset(readBuffer, '0', BUFFER_SIZE);
		if (fgets(readBuffer, BUFFER_SIZE, fpIn) == NULL) break;
		strBuffer = readBuffer;

		// Remove any leading and trailing whitespace.  In both cases, if no non-whitespace
		// characters are found, then strBuffer should be an empty string and will be
		// ignored by the next step.

		unsigned int pos = 0;
		char whitespace[] = " \t\r\n\v\f";
   
		pos = strBuffer.find_first_not_of(whitespace);
		if (pos != string::npos) strBuffer.replace(0, pos, "");
		else continue;

		pos = strBuffer.find_last_not_of(whitespace);
		if (pos != string::npos) strBuffer.replace(pos + 1, strBuffer.length() - pos, "");
		else continue;

		// If the line is blank, or if the line starts with a '--' (it is a comment), then
		// ignore it and read the next one.

		if (strBuffer.empty() == true) continue;
		if ((strBuffer.at(0) == '-') && (strBuffer.at(1) == '-') )
		{
			if(strBuffer.length() > 3)
			{
				if( (strBuffer.at(3) != '[') && 
					(strBuffer.at(3) != '<') &&
					(strBuffer.compare(0, 11, "-- OVAL-id:") != 0))
					continue;
			}else
			{
				continue;
			}
		}

		// We found a good line, exit this while loop.

		break;
	}

	return strBuffer;
}

void PrintNotVulnerableOutput(stringVector notVulnerableVectorIn)
{
	//------------------------------------------------------------------------------------//
	//
	//  ABSTRACT
	//
	//  Display the ids for the OVAL queries that did not produce a vulnerability found.
	//
	//------------------------------------------------------------------------------------//

	stringVector::iterator nvIterator;
	FILE* fp = NULL;

	// Open the specifed file.  A control flag of "a" says to open the file for writing
	// at the end of the file (appending) without removing the EOF marker before
	// writing new data to the file; creates the file first if it doesn’t exist.

	if (Common::GetOutputToFile() == true)
	{
		fp  = fopen(Common::GetOutputFilename().c_str(), "a+");
		if (fp == NULL)
		{
			cerr << "\nERROR: Unable to output to file.\n";
			return;
		}
	}

	// Send header to console screen and log file.

	string headerMessage = "";

	headerMessage.append("\n");
	headerMessage.append("    VULNERABILITIES NOT FOUND\n");
	headerMessage.append("    -------------------------\n");
	
	cout << headerMessage;
	Log::WriteLog(headerMessage);

	// Depending on the type of output specified, send text or html data to the file.

	if ((Common::GetOutputToFile() == true) && (fp != NULL))
	{
		fputs("    </UL>\n", fp);
		fputs("    <BR>\n", fp);
		fputs("    VULNERABILITIES NOT FOUND<BR>\n", fp);
		fputs("    <UL>\n", fp);
	}

	// Loop through each element in the not vulnerable vector.
	for (nvIterator=notVulnerableVectorIn.begin(); nvIterator!=notVulnerableVectorIn.end(); nvIterator++)
	{
		// Print the line of output to the console screen and log file.

		string notVulnerableOutputMessage = "";

		notVulnerableOutputMessage.append("     - ");
		notVulnerableOutputMessage.append(*nvIterator);
		notVulnerableOutputMessage.append("\n");

		cout << notVulnerableOutputMessage;
		Log::WriteLog(notVulnerableOutputMessage);
	
		// Send to html file if specified
		if ((Common::GetOutputToFile() == true) && (fp != NULL))
		{

			fputs("        <LI>", fp);
			fputs("<a href=http://oval.mitre.org/oval/definitions/pseudo/", fp);    
			fputs((*nvIterator).c_str(), fp);
			fputs(".html target=\"_blank\">", fp);
			fputs((*nvIterator).c_str(), fp);
			fputs("</a>\n", fp);

		}
	}

	// Close the file.
	if ((Common::GetOutputToFile() == true) && (fp != NULL))
	{
		fclose(fp);
	}
}

void PrintOutput(string addIn)
{
	//------------------------------------------------------------------------------------//
	//
	//  ABSTRACT
	//
	//  This function adds a line of output to the specified destinations.  If output to a
	//  file is specified, then a line of text or html is added to the file.  The output
	//  is always printed to the console screen.
	//
	//  NOTE: The output is currently just a CVE number representing a vulnerability that
	//  exits on the system.  We display the list of CVE numbers in a bulletted list.
	//  Therefore we must add the bullet to the line of output.
	//
	//------------------------------------------------------------------------------------//

	// Send output to file.
	if (Common::GetOutputToFile() == true)
	{
		FILE* fp = NULL;

		// Open the specifed file.  A control flag of "a" says to open the file for writing
		// at the end of the file (appending) without removing the EOF marker before
		// writing new data to the file; creates the file first if it doesn’t exist.

		fp  = fopen(Common::GetOutputFilename().c_str(), "a+");
		if (fp == NULL)
		{
			cerr << "\nERROR: Unable to output to file.\n";
			return;
		}

		// Send html data to the file.
		string cveNumber = "";
		string ovalID = "";
		int dashPos = 0;

		dashPos = addIn.find_first_of('-');
		ovalID = addIn.substr(0, dashPos-1);
		cveNumber = addIn.substr(dashPos+2);

		fputs("        <LI>", fp);
		fputs("<a href=http://oval.mitre.org/oval/definitions/pseudo/", fp);        
		fputs(ovalID.c_str(), fp);
		fputs(".html target=\"_blank\">", fp);
		fputs(ovalID.c_str(), fp);
		fputs("</a>", fp);
		fputs(" - ", fp);
		fputs("<a href=http://cve.mitre.org/cgi-bin/cvename.cgi?name=", fp);
		fputs(cveNumber.c_str(), fp);
		fputs(" target=\"_blank\">", fp);
		fputs(cveNumber.c_str(), fp);
		fputs("</a>\n", fp);

		// Close the file.

		fclose(fp);
	}

	// Print the line of output to the console screen and log file.

	string outputMessage = "";

	outputMessage.append("     - ");
	outputMessage.append(addIn);
	outputMessage.append("\n");

	cout << outputMessage;
	Log::WriteLog(outputMessage);
}

string ReplaceExists(string queryIn)
{
	//------------------------------------------------------------------------------------//
	//
	//  ABSTRACT
	//
	//  Replace all EXISTS and NOT EXISTS keywords in a SQL query with the corresponding
	//  NOTNULL and ISNULL.
	//
	//  existsType
	//  ----------
	//    0 = EXISTS
	//    1 = NOT EXISTS
	//
	//------------------------------------------------------------------------------------//

	unsigned int existsPos = 0;
	int existsType = 0;
	int parenCount = 0;
	int parenPos = 0;

	while (existsPos != string::npos)
	{
		existsPos = queryIn.find("EXISTS", existsPos);
		if (existsPos != string::npos)
		{
			// The keyword EXISTS was found.  Determine if a NOT preceeds it.  NOTE: We
			// must readjust the value of existsPos if NOT is found.

			if (((queryIn.at(existsPos-4) == 'N') || (queryIn.at(existsPos-4) == 'n')) &&
				((queryIn.at(existsPos-3) == 'O') || (queryIn.at(existsPos-3) == 'o')) &&
				((queryIn.at(existsPos-2) == 'T') || (queryIn.at(existsPos-2) == 't')))
			{
				existsPos = existsPos - 4;
				existsType = 1;
			}
			else
			{
				existsType = 0;
			}

			// Remove the EXISTS or NOT EXISTS keyword from the query.

			if (existsType == 0)
			{
				queryIn.erase(existsPos, 7);
			}
			else
			{
				queryIn.erase(existsPos, 11);
			}

			// Find the end of the subquery the EXISTS or NOT EXISTS refers to.  NOTE: Set
			// parenPos to the character before existsPos incase existsPos now points to a
			// bracket.

			parenCount = 0;
			parenPos = existsPos - 1;
			do
			{
				parenPos = queryIn.find_first_of("()", (parenPos+1));

				if (parenPos > 0)
				{
					if (queryIn.at(parenPos) == '(') parenCount++;
					else parenCount--;
				}
				else
				{
					// We reached the end of the query.  The parens must be unbalanced.

					// Hopefully this error will be caught elsewhere!  Namely, the query
					// should no longer be valid and should fail when it is executed.

					//******************************************************************
					//
					// TODO - error to fix
					//
					// A bug has been found when an invalid SQL query is passed to the QI.
					// If the parens are unbalanced, then it is possible to get into an
					// infinite loop.  Unbalanced parens will cause parenPos to equal -1
					// (not found) and we will end up here.  Without the break statement,
					// we will continue in the DO loop and start looking for parens at
					// the begining of the string.
					//
					// Fix this by adding a break statement.  This change will be made
					// in version 1.2 of the source code.
					//
					// if (Log::verboseMode == true)
					// {
					//	   string errorMessage = "\nERROR: The parens in the query are unbalanced.\n";
					//	   cerr << errorMessage;
					//	   Log::WriteLog(errorMessage);
					// }
					//
					// break;
					//
					//******************************************************************
				}
			} while (parenCount > 0);

			// Once found, insert NOTNULL or ISNULL to the end of the subquery.
			if (parenPos > 0)
			{
				if (existsType == 0) queryIn.insert((parenPos+1), " NOTNULL");
				else queryIn.insert((parenPos+1), " ISNULL");
			}
		}
	}

	return queryIn;
}

void StartOutput()
{
	//------------------------------------------------------------------------------------//
	//
	//  ABSTRACT
	//
	//  This function prints the header.  If output to a file is specifed, then the file
	//  is created and the header is sent to it.
	//
	//------------------------------------------------------------------------------------//

	// Send header to file.

	if (Common::GetOutputToFile() == true)
	{
		// Truncate any existing output file.
		FILE* fp = NULL;
		fp = fopen(Common::GetOutputFilename().c_str(), "w+"); 
		if (fp == NULL)
		{
			string errorMessage = "";
			errorMessage.append("\nERROR: Could not delete the existing output file - ");
			errorMessage.append(Common::GetOutputFilename());
			errorMessage.append("\n");

			cerr << errorMessage;
			Log::WriteLog(errorMessage);
			return;
		}else
		{
			fclose(fp);
	    }

		// Create the specifed file.  A control flag of "w" says to open the file and clear
		// it contents, if the file doesn't exist, then create it.

		fp = NULL;
		fp  = fopen(Common::GetOutputFilename().c_str(), "w");
		if (fp == NULL)
		{
			string errorMessage = "";

			errorMessage.append("\nERROR: Unable to create a new output file - ");
			errorMessage.append(Common::GetOutputFilename());
			errorMessage.append("\n");

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

			return;
		}

		// Send html data to the file.
		fputs("<HTML>\n", fp);
		fputs("<HEAD>\n", fp);
		fputs("  <TITLE>Oval Query Interpreter</TITLE>\n", fp);
		fputs("</HEAD>\n", fp);
		fputs("<BODY>\n", fp);
		fputs("  <H2>\n", fp);
		fputs("  ----------------------------------------------------<BR>\n", fp);
		fputs("  OVAL Query Interpreter<BR>\n", fp);
		fputs("  Copyright (c) 2004 - The MITRE Corporation<BR>\n", fp);
		fputs("  ----------------------------------------------------<BR>\n", fp);
		fputs("  </H2>\n", fp);
		fputs("  <P>\n    ", fp);
		fputs(Common::GetStartTime().c_str(), fp);
		fputs("    <BR>\n", fp);
		fputs("    <BR>\n", fp);
		fputs("    VULNERABILITIES FOUND<BR>\n", fp);
		fputs("    <UL>\n", fp);
		

		// Close the file.

		fclose(fp);
	}

	// Send header to console screen.

	string headerMessage = "";

	headerMessage.append("\n");
	headerMessage.append("    VULNERABILITIES FOUND\n");
	headerMessage.append("    ---------------------\n");

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

void StopOutput()
{
	//------------------------------------------------------------------------------------//
	//
	//  ABSTRACT
	//
	//  This function prints the footer.    If output to a file is specified, then the
	//  footer is sent to the file as text or html.  The footer is always printed to the
	//  console screen for the user to see, but his is done in the main() funtion and not
	//  here.
	//
	//------------------------------------------------------------------------------------//

	// Send footer to file

	if (Common::GetOutputToFile() == true)
	{
		FILE* fp = NULL;

		// Open the specifed file.  A control flag of "a" says to open the file for writing
		// at the end of the file (appending) without removing the EOF marker before
		// writing new data to the file; creates the file first if it doesn’t exist.

		fp  = fopen(Common::GetOutputFilename().c_str(), "a+");
		if (fp == NULL)
		{
			// No need to report this error.  Just return.

			return;
		}

		// Send html data to the file.
		fputs("    </UL>\n", fp);
		fputs("  </P>\n", fp);
		fputs("  <H2>\n", fp);
		fputs("  ----------------------------------------------------<BR>\n", fp);
		fputs("  </H2>\n", fp);
		fputs("</BODY>\n", fp);
		fputs("</HTML>\n", fp);

		// Close the file.

		fclose(fp);
	}

	// Send footer to console screen.

	string footerMessage = "";

	footerMessage.append("\n");

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

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

	cout << endl;
	cout << "Command Line: >ovalqi [option] MD5Hash" << endl;
	cout << endl;
	cout << "Options:" << endl;
	cout << "   -d <string> = save data to the specified SQLite database file DEFAULT=\"data.slt\"" << endl;
	cout << "   -h          = show options available from command line" << endl;
	cout << "   -i <string> = use data from input SQLite database file" << endl;
	cout << "   -m          = do not verify the *.sql file with an MD5 hash" << endl;
	cout << "   -o <string> = path to the *.sql file" << endl;
	cout << "   -r <string> = save results in a HTML file DEFALUT=\"results.html\"" << endl;
	cout << "   -v          = print all information and error messages" << endl;
	cout << "   -z          = return md5 of current definitions.sql" << endl;
	cout << endl;
}
