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

#include "RPMVersionCompareProbe.h"

//****************************************************************************************//
//								RPMVersionCompareData Class								  //	
//****************************************************************************************//
RPMVersionCompareProbe *RPMVersionCompareProbe::instance = NULL;

RPMVersionCompareProbe::RPMVersionCompareProbe()
{
  //--------------------------------------------------------------------------//
  // ABSTRACT
  // 
  // Do nothing for now	
  //--------------------------------------------------------------------------//

}

RPMVersionCompareProbe::~RPMVersionCompareProbe()
{
  //--------------------------------------------------------------------------//
  // ABSTRACT
  // 
  // Do nothing for now	
  //--------------------------------------------------------------------------//

}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~  Public Members  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//
Probe* RPMVersionCompareProbe::Instance()
{

	// -----------------------------------------------------------------------
	//
	//  ABSTRACT
	//
	//	Ensure that the RPMVersionCompareProbe is a singleton.
	// -----------------------------------------------------------------------

	// Use lazy initialization
	if(instance == NULL) 
		instance = new RPMVersionCompareProbe();

	return instance;	
}

pdVector RPMVersionCompareProbe::Run(ProbeData *probeDataIn)
{
	//------------------------------------------------------------------------------------//
	//
	//  ABSTRACT
	//
	//  Get the data form the data object. Support pattern matching on name only.
	//  Ensure that at least one data node is written for each test_id. It does not really
	//  make sense to have pattern matching on anything other than name. For pattern match
	//  on name first get the list of names that match the pattern. Then use the provided
	//  epoch, version, and release to do the version compare.
	//
	//------------------------------------------------------------------------------------//
	RPMVersionCompareData *dataIn = (RPMVersionCompareData*)probeDataIn;
	pdVector resultVector;

	try {

		if(dataIn->rpm_name->type == literal) {
			// Gather new data and put in table.
			CompareRPMtoGood(dataIn, &resultVector);

		} else {

			// Gather new data and put in table.
			CompareRPMtoGoodPatternMatch(dataIn, &resultVector);

			// Add a record indicating the status of the pattern match
			RPMVersionCompareData *tmp = new RPMVersionCompareData();
			tmp->rpm_name->type = dataIn->rpm_name->type;
			tmp->rpm_name->object = dataIn->rpm_name->object;
			tmp->tested_epoch->object = dataIn->tested_epoch->object;
			tmp->tested_epoch->type = dataIn->tested_epoch->type;
			tmp->tested_version->object = dataIn->tested_version->object;
			tmp->tested_version->type = dataIn->tested_version->type;
			tmp->tested_release->object = dataIn->tested_release->object;
			tmp->tested_release->type = dataIn->tested_release->type;
			tmp->isPatternMatchObject = true;
			if(resultVector.size() == 0 ) {
				tmp->SetMessage("The specified pattern did not match any items on the system");
				tmp->SetStatus(doesNotExist);
			} else {
				tmp->SetMessage("The specified pattern matched at least one item on the system.");
				tmp->SetStatus(exists);
			}
			resultVector.push_back(tmp);
		}

	} catch(Exception ex) {
		RPMVersionCompareData *tmp = new RPMVersionCompareData();
		tmp->SetMessage(ex.GetErrorMessage());
		tmp->SetStatus(error);
		tmp->rpm_name->type = dataIn->rpm_name->type;
		tmp->rpm_name->object = dataIn->rpm_name->object;
		tmp->tested_epoch->object = dataIn->tested_epoch->object;
		tmp->tested_epoch->type = dataIn->tested_epoch->type;
		tmp->tested_version->object = dataIn->tested_version->object;
		tmp->tested_version->type = dataIn->tested_version->type;
		tmp->tested_release->object = dataIn->tested_release->object;
		tmp->tested_release->type = dataIn->tested_release->type;
		tmp->isPatternMatchObject = dataIn->isPatternMatchObject;
	      
		resultVector.push_back(tmp);

	} catch(...) {
		RPMVersionCompareData *tmp = new RPMVersionCompareData();
		tmp->SetMessage("Error: An unknown error occured while running a RPM Version Compare Test.");
		tmp->SetStatus(error);
		tmp->rpm_name->type = dataIn->rpm_name->type;
		tmp->rpm_name->object = dataIn->rpm_name->object;
		tmp->tested_epoch->object = dataIn->tested_epoch->object;
		tmp->tested_epoch->type = dataIn->tested_epoch->type;
		tmp->tested_version->object = dataIn->tested_version->object;
		tmp->tested_version->type = dataIn->tested_version->type;
		tmp->tested_release->object = dataIn->tested_release->object;
		tmp->tested_release->type = dataIn->tested_release->type;
		tmp->isPatternMatchObject = dataIn->isPatternMatchObject;

		resultVector.push_back(tmp);
	}   
	
	return resultVector;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~  Private Members  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//

int RPMVersionCompareProbe::sense(char *installed_epoch,char *installed_version,
					char *installed_release,
					const char *epoch, const char *version, const char *release)
{
  //--------------------------------------------------------------------------//
  // This function calls rpmvercmp over each part of the RPM version number.
  // rpmvercmp() only compares one part of the version number at a time.
  //--------------------------------------------------------------------------//

  // This code motivated (strongly) by librpm's rpmdsCompare().  We could use that
  // function, but we'd have to build a complete header from the E,V,R data that we
  // have.  We'll need to respect that function's license.

  int sense = 0;

  // rpmvercmp(a,b) returns 1 is a is newer than b, 0 if they are the same version
  // and -1 is b is newer than a.
  if (installed_epoch && *installed_epoch && epoch && *epoch)
    sense = rpmvercmp(installed_epoch,epoch);
  else if (installed_epoch && *installed_epoch && atol(installed_epoch) > 0) 
    sense = 1;
  else if (epoch && *epoch && atol(epoch) > 0)
    sense = -1;

  if (sense == 0) {
    sense = rpmvercmp(installed_version,version);
    if (sense == 0 && installed_release && *installed_release && release && *release) 
      sense = rpmvercmp(installed_release,release);
  }

  return(sense);

}

void RPMVersionCompareProbe::CompareRPMtoGood(RPMVersionCompareData *dataIn, pdVector *resultVector)
{
	//------------------------------------------------------------------------------------//
	//
	//  ABSTRACT
	//
	//  Get the results from a version compare for each package that matches the 
	//  specified name.
	//
	//------------------------------------------------------------------------------------//

	RPMVersionCompareData *tmp = NULL;

	/* Transaction sets are the modern way to read the RPM database. */
	rpmts ts;
	/* We use an iterator to walk the RPM database. */
	rpmdbMatchIterator iterator;
	/* Header object for the installed package. */
	Header header;
	/* Epoch, version, name,  and release for both packages. */
	char *installed_epoch,*installed_version, *installed_release, *installed_rpm_name;
	/* Is the RPM earlier, later or exactly right? */
	int comparison_sense = 1;
	/* Was the RPM installed? */
	bool rpm_installed = false;
	/* Result string */
	string result = "";

	/* Read in the RPM config files */
	if (rpmReadConfigFiles( (const char*) NULL, (const char*) NULL))
		throw ProbeException("Error: Could not read RPM config files, which is necessary to read the RPM database.");
		
	/* Create an rpm database transaction set. */
	ts = rpmtsCreate();

	/* Create an iterator to walk the database. */	
	iterator = rpmtsInitIterator(ts, RPMTAG_NAME, dataIn->rpm_name->object.c_str(), 0);

	/* Look at each installed package matching this name.  Generally, there is only one.*/
	while ( (header = rpmdbNextIterator(iterator)) != NULL) {

		/* The installed_epoch is stored as an int_32, but rpmvercmp() compares
			c-strings.  So we want to convert an int to a char *. */
		int_32 int_installed_epoch = readHeaderInt32(header, RPMTAG_EPOCH);
		if (int_installed_epoch == -1) {
			installed_epoch = "NULL";
		} else {
			installed_epoch = new char[11];
			snprintf(installed_epoch,11,"%d",int_installed_epoch);
		}

		/* The version, name  and release require no such gymnastics. */
		installed_rpm_name = readHeaderString(header, RPMTAG_NAME);
		installed_version = readHeaderString(header, RPMTAG_VERSION);
		installed_release = readHeaderString(header, RPMTAG_RELEASE);

		/* Set flag indicating that the RPM is installed. */
		rpm_installed = true;

		comparison_sense = sense(installed_epoch, 
								installed_version, 
								installed_release,
                                dataIn->tested_epoch->object.c_str(), 
								dataIn->tested_version->object.c_str(), 
								dataIn->tested_release->object.c_str());

		/* First, convert the result to a string. */
		if (comparison_sense == -1 )
			result = "earlier";
		else if (comparison_sense == 0 )
			result = "equal";
		else if (comparison_sense == 1 )
			result = "later";
		else {
			throw ProbeException("RPM appears to be installed, but comparison_sense has an unexpected value.");
		}

		/* Add the data to the resultVector */
		tmp = new RPMVersionCompareData();
		tmp->SetStatus(exists);
		tmp->rpm_name->object = installed_rpm_name;
		tmp->rpm_name->type = literal;

		tmp->tested_epoch->object = dataIn->tested_epoch->object;
		tmp->tested_epoch->type = literal;

		tmp->tested_version->object = dataIn->tested_version->object;
		tmp->tested_version->type = literal;

		tmp->tested_release->object = dataIn->tested_release->object;
		tmp->tested_release->type = literal;

		tmp->installed_version->value = result;
		tmp->installed_version->dataType = stringType;
		tmp->installed_version->status = exists;

		resultVector->push_back(tmp);    
	}

	/* If the rpm is not installed set the result and add a data item to the resultVector. */
	if (!rpm_installed) {   
		tmp = new RPMVersionCompareData();
		tmp->rpm_name->object = dataIn->rpm_name->object;
		tmp->rpm_name->type = literal;
		tmp->tested_epoch->object = dataIn->tested_epoch->object;
		tmp->tested_epoch->type = literal;
		tmp->tested_version->object = dataIn->tested_version->object;
		tmp->tested_version->type = literal;
		tmp->tested_release->object = dataIn->tested_release->object;
		tmp->tested_release->type = literal;

		tmp->SetMessage("The specified RPM was not found on the system. ");
		tmp->SetStatus(doesNotExist);

		resultVector->push_back(tmp); 
	}

	/* Free the iterator and transaction set data structures. */
	rpmdbFreeIterator(iterator);
	rpmtsFree(ts);

}
void RPMVersionCompareProbe::CompareRPMtoGoodPatternMatch(RPMVersionCompareData *dataIn, pdVector *resultVector)
{
	//------------------------------------------------------------------------------------//
	//
	//  ABSTRACT
	//
	//  Get the results from a version compare for each package that matches the 
	//  specified name.
	//
	//------------------------------------------------------------------------------------//

	RPMVersionCompareData *tmp = NULL;

	/* Transaction sets are the modern way to read the RPM database. */
	rpmts ts;
	/* We use an iterator to walk the RPM database. */
	rpmdbMatchIterator iterator;
	/* Header object for the installed package. */
	Header header;
	/* Epoch, version, name,  and release for both packages. */
	char *installed_epoch,*installed_version, *installed_release, *installed_rpm_name;
	/* Is the RPM earlier, later or exactly right? */
	int comparison_sense = 1;
	/* Was the RPM installed? */
	int rpm_installed = 0;
	/* Result string */
	string result = "";

	/* Read in the RPM config files */
	if (rpmReadConfigFiles( (const char*) NULL, (const char*) NULL)) 
		throw ProbeException("Could not read RPM config files, which is necessary to read the RPM database.");

	/* Create an rpm database transaction set. */
	ts = rpmtsCreate();

	/* Create an iterator to walk the database. */	
	iterator = rpmtsInitIterator(ts, RPMTAG_NAME, dataIn->rpm_name->object.c_str(), 0);

	/* Look at each installed package matching this name.  Generally, there is only one.*/
	while ( (header = rpmdbNextIterator(iterator)) != NULL) {
		/* Get the rpm_name value for comparision. */
		installed_rpm_name = readHeaderString(header, RPMTAG_NAME);

		/* Check to see if name found matches input pattern. */
		if(myMatcher->IsMatch(dataIn->rpm_name->object.c_str(), installed_rpm_name)) {
			/* Get remaining data if a pattern match was successful. */

			/* The installed_epoch is stored as an int_32, but rpmvercmp() compares
				c-strings.  So we want to convert an int to a char *. */
			int_32 int_installed_epoch = readHeaderInt32(header, RPMTAG_EPOCH);
			if (int_installed_epoch == -1)
				installed_epoch = NULL;
			else {
				installed_epoch = new char[11];
				snprintf(installed_epoch,11,"%d",int_installed_epoch);
			}

			/* The version, name  and release require no such gymnastics. */
			installed_rpm_name = readHeaderString(header, RPMTAG_NAME);
			installed_version = readHeaderString(header, RPMTAG_VERSION);
			installed_release = readHeaderString(header, RPMTAG_RELEASE);

			/* Set flag indicating that the RPM is installed. */
			rpm_installed = 1;

			comparison_sense = sense(installed_epoch,
									installed_version,
									installed_release,
                                    dataIn->tested_epoch->object.c_str(),
									dataIn->tested_version->object.c_str(),
									dataIn->tested_release->object.c_str());


			/* First, convert the result to a string. */
			if (comparison_sense == -1 )
				result = "earlier";
			else if (comparison_sense == 0 )
				result = "equal";
			else if (comparison_sense == 1 )
				result = "later";
			else
				throw ProbeException("Error: RPM appears to be installed, but comparison_sense has an unexpected value.");

			/* Add the data to the resultVector */
			tmp = new RPMVersionCompareData();
			tmp->SetStatus(exists);
			tmp->rpm_name->object = installed_rpm_name;
			tmp->rpm_name->type = literal;

			tmp->tested_epoch->object = dataIn->tested_epoch->object;
			tmp->tested_epoch->type = literal;

			tmp->tested_version->object = dataIn->tested_version->object;
			tmp->tested_version->type = literal;

			tmp->tested_release->object = dataIn->tested_release->object;
			tmp->tested_release->type = literal;

			tmp->installed_version->value = result;
			tmp->installed_version->dataType = stringType;
			tmp->installed_version->status = exists;

			resultVector->push_back(tmp);
		}
	}
  
  /* Free the iterator and transaction set data structures. */
  rpmdbFreeIterator(iterator);
  rpmtsFree(ts);
}

char *RPMVersionCompareProbe::readHeaderString(Header header, int_32 tag_id)
{
  // This function is from the Red Hat RPM Guide //
  int_32 type;
  void *pointer;
  int_32 data_size;

  int header_status = headerGetEntry(header,
				     tag_id,
				     &type,
				     &pointer,
				     &data_size);

	
  if (header_status) {
    if (type == RPM_STRING_TYPE) {
      return (char *) pointer;
    }
  }
  return( (char *) NULL);
}


int_32 RPMVersionCompareProbe::readHeaderInt32(Header header, int_32 tag_id)
{
  // This function is from the Red Hat RPM Guide //
  int_32 type;
  void *pointer;
  int_32 data_size;

  int header_status = headerGetEntry(header,
				     tag_id,
				     &type,
				     (void **) &pointer,
				     &data_size);

	
  if (header_status) {
    if (type == RPM_INT32_TYPE) {
      int_32 *p = (int_32 *) pointer;
      return *p;
    }
  }
  return( -1 );
}




