//
// $Id: RPMVersionCompareProbe.cpp,v 1.1 2004/06/01 17:05:21 bakerj Exp $
//
//************************** Property of the MITRE Corporation ***************************//
//
// Copyright (c) 2003 - The MITRE Corporation
//
// This file is part of the Query-based Network Assessment project.
//
// The Query-based Network Assessment 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 Query-based Network Assessment 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
// Query-based Network Assessment; if not, write to the Free Software Foundation, Inc.,
// 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
//
//****************************************************************************************//

#include "RPMVersionCompareProbe.h"

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

}

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

}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~  Public Members  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//

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;

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

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

  // Ensure that the resultVector has at least one item
  if(resultVector.size() == 0)
    {
      RPMVersionCompareData *tmp = new RPMVersionCompareData();
      tmp->SetTestId(dataIn->GetTestId());
      tmp->msg = "RPMVersionCompareProbe found no matching data.";
      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;
  string errMsg = "";

  /* 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)) 
    errMsg = "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->name->data.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 = 1;

    comparison_sense = sense(installed_epoch,installed_version,installed_release,
			     dataIn->tested_epoch.c_str(),dataIn->tested_version.c_str(),dataIn->tested_release.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
      errMsg = "RPM appears to be installed, but comparison_sense has an unexpected value.";

    /* Add the data to the resultVector */
    tmp = new RPMVersionCompareData();
    tmp->SetTestId(dataIn->GetTestId());
    tmp->name->data = installed_rpm_name;
    tmp->tested_epoch = dataIn->tested_epoch;
    tmp->tested_version = dataIn->tested_version;
    tmp->tested_release = dataIn->tested_release;
    tmp->installed_version = result;
    tmp->msg = errMsg;
    resultVector->push_back(tmp);    
  }

  /* If the rpm is not installed set the result and add a data item tot he resultVector. */
  if (!rpm_installed)
    {   
      tmp = new RPMVersionCompareData();
      tmp->SetTestId(dataIn->GetTestId());
      tmp->name->data = dataIn->name->data;
      tmp->tested_epoch = dataIn->tested_epoch;
      tmp->tested_version = dataIn->tested_version;
      tmp->tested_release = dataIn->tested_release;
      tmp->installed_version = "not installed";
      tmp->msg = errMsg;
      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;
  string errMsg = "";

  /* 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)) 
    errMsg = "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->name->data.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->name->data.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.c_str(),dataIn->tested_version.c_str(),dataIn->tested_release.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
	  errMsg = "RPM appears to be installed, but comparison_sense has an unexpected value.";

	/* Add the data to the resultVector */
	tmp = new RPMVersionCompareData();
	tmp->SetTestId(dataIn->GetTestId());
	tmp->name->data = installed_rpm_name;
	tmp->tested_epoch = dataIn->tested_epoch;
	tmp->tested_version = dataIn->tested_version;
	tmp->tested_release = dataIn->tested_release;
	tmp->installed_version = result;
	tmp->msg = errMsg;
	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->SetTestId(dataIn->GetTestId());
      tmp->name->data = dataIn->name->data;
      tmp->tested_epoch = dataIn->tested_epoch;
      tmp->tested_version = dataIn->tested_version;
      tmp->tested_release = dataIn->tested_release;
      tmp->installed_version = "not installed";
      tmp->msg = errMsg;
      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 );
}




