//
// $Id: RPMInfoProbe.cpp,v 1.7 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 "RPMInfoProbe.h"

//****************************************************************************************//
//								RPMInfoProbe Class										  //	
//****************************************************************************************//
RPMInfoProbe *RPMInfoProbe::instance = NULL;

RPMInfoProbe::RPMInfoProbe()
{
  //------------------------------------------------------------------------------------//
  //
  //  ABSTRACT
  // Do nothing for now.
  //------------------------------------------------------------------------------------//
 
}

RPMInfoProbe::~RPMInfoProbe()
{
  //------------------------------------------------------------------------------------//
  //
  //  ABSTRACT
  // Do nothing for now.
  //------------------------------------------------------------------------------------//
 
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~  Public Members  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//
Probe* RPMInfoProbe::Instance()
{

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

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

	return instance;	
}

pdVector RPMInfoProbe::Run(ProbeData *probeDataIn)
{
	//------------------------------------------------------------------------------------//
	//
	//  ABSTRACT
	//
	//  Take a probe data object determine if there is a pattern match specified. 
	//  If no pattern match just use the name provided and gather info. If a pattern
	//  match go through all names and get each matching name into a new data object
	//  then get info for each name.
	//------------------------------------------------------------------------------------//
	RPMInfoData *dataIn = (RPMInfoData*)probeDataIn;
	pdVector resultsVector;

	try {
		if(dataIn->name->type == literal) {
			
			// Gather new data and put in results vector
			GetRPMInfo(dataIn, &resultsVector);

		} else {

			// Gather new data and put in results vector
			GetRPMInfoPatternMatching(dataIn, &resultsVector);     

			// Write a record for the pattern match
			RPMInfoData *tmp = new RPMInfoData();
			tmp->name->object = dataIn->name->object;
			tmp->name->type = dataIn->name->type;
			tmp->isPatternMatchObject = true;
			if(resultsVector.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);
			}
			resultsVector.push_back(tmp);
		}

	} catch(Exception ex) {
		RPMInfoData *tmp = new RPMInfoData();
		tmp->name->object = dataIn->name->object;
		tmp->name->type = dataIn->name->type;
		tmp->isPatternMatchObject = dataIn->isPatternMatchObject;
		tmp->SetMessage(ex.GetErrorMessage());
		tmp->SetStatus(error);
		resultsVector.push_back(tmp); 

	} catch(...) {
		RPMInfoData *tmp = new RPMInfoData();
		tmp->name->object = dataIn->name->object;
		tmp->name->type = dataIn->name->type;
		tmp->isPatternMatchObject = dataIn->isPatternMatchObject;
		tmp->SetMessage("Error An unknown error orruced while collecting rpm info.");
		tmp->SetStatus(error);
		resultsVector.push_back(tmp); 
	}

  return resultsVector;
}  

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

void RPMInfoProbe::GetRPMInfo(RPMInfoData *dataIn, pdVector *resultVector)
{
  //------------------------------------------------------------------------------------//
  //
  //  ABSTRACT
  //
  //  Get the data for all packages that have the name that matches rpm_name.  
  //
  //------------------------------------------------------------------------------------//

  /* Get the rpm_name form the data object. */
  const char *rpm_name = dataIn->name->object.c_str();

  /* Create a tmp data object reference. */
  RPMInfoData *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, release and architecture data for output. */
  string installed_epoch, installed_version, installed_release,installed_architecture;

  /* Read in the RPM config files */
  if (rpmReadConfigFiles( (const char*) NULL, (const char*) NULL)) 
	  throw ProbeException("Error: (RPMInfoProbe) 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, rpm_name, 0);
  if (iterator == NULL)
    throw ProbeException("Error: (RPMInfoProbe) Could not create an iterator to walk the RPM database.");

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

    /* epoch is an int_32 -- we'll display a string to handle the None case well. */
    char intermediate_string[11];
    int_32 epoch = readHeaderInt32(header, RPMTAG_EPOCH);
    if (epoch == -1 )
      installed_epoch = "NULL";
    else {
      snprintf(intermediate_string,11,"%d",epoch);
      installed_epoch = intermediate_string;
    }

    /* the remaining arguments are all normal strings */
    installed_version = readHeaderString(header, RPMTAG_VERSION);
    installed_release = readHeaderString(header, RPMTAG_RELEASE);
    installed_architecture = readHeaderString(header, RPMTAG_ARCH);

    /* Put the data in a data object. */
    tmp = new RPMInfoData();
    tmp->SetStatus(exists);
    tmp->name->object = rpm_name;
	tmp->name->type = literal;

    tmp->epoch->value = installed_epoch;
	tmp->epoch->dataType = stringType;
	tmp->epoch->status = exists;

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

    tmp->release->value = installed_release;
	tmp->release->dataType = stringType;
	tmp->release->status = exists;

    tmp->arch->value = installed_architecture;
	tmp->arch->dataType = stringType;
	tmp->arch->status = exists;

    /* add the new item to the vector. */
    resultVector->push_back(tmp);
  }

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

  // Ensure that the results vector has a result
  if(resultVector->size() == 0) {

    /* Put the data in a data object. */
    tmp = new RPMInfoData();
    tmp->SetStatus(doesNotExist);
    tmp->name->object = dataIn->name->object;
	tmp->name->type = literal;

    /* add the new item to the vector. */
    resultVector->push_back(tmp);
  }
}
void RPMInfoProbe::GetRPMInfoPatternMatching(RPMInfoData *dataIn, pdVector *resultVector)
{
	//------------------------------------------------------------------------------------//
	//
	//  ABSTRACT
	//
	//  Get the data for all packages that have the name that matches rpm_name.  
	//  Use pattern matching to determine if a match is found
	//------------------------------------------------------------------------------------//

	/* Get the rpm_name form the data object. */
	const char *rpm_name = dataIn->name->object.c_str();

	/* Create a tmp data object reference. */
	RPMInfoData *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, release and architecture data for output. */
	string installed_epoch;
	string installed_version;
	string installed_release;
	string installed_architecture;
	string installed_rpm_name;

	/* Read in the RPM config files */
	if (rpmReadConfigFiles( (const char*) NULL, (const char*) NULL)) 
		throw ProbeException("Error: (RPMInfoProbe) 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, NULL, 0);
	if (iterator == NULL)
		throw ProbeException("Error: (RPMInfoProbe) Could not create an iterator to walk the RPM database.");
	
	/* 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(rpm_name, installed_rpm_name.c_str())) {
			/* Get remaining data if a pattern match was successful. */

			/* epoch is an int_32 -- we'll display a string to handle the None case well. */
			char intermediate_string[11];
			int_32 epoch = readHeaderInt32(header, RPMTAG_EPOCH);
			if (epoch == -1 ) {
				installed_epoch = "NULL";
			} else {
				snprintf(intermediate_string,11,"%d",epoch);
				installed_epoch = intermediate_string;
			}

			/* the remaining arguments are all normal strings */
			installed_version = readHeaderString(header, RPMTAG_VERSION);
			installed_release = readHeaderString(header, RPMTAG_RELEASE);
			installed_architecture = readHeaderString(header, RPMTAG_ARCH);

			/* Put the data in a data object. */
			tmp = new RPMInfoData();
			tmp->SetStatus(exists);
			tmp->name->object = rpm_name;
			tmp->name->type = literal;

			tmp->epoch->value = installed_epoch;
			tmp->epoch->dataType = stringType;
			tmp->epoch->status = exists;

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

			tmp->release->value = installed_release;
			tmp->release->dataType = stringType;
			tmp->release->status = exists;

			tmp->arch->value = installed_architecture;
			tmp->arch->dataType = stringType;
			tmp->arch->status = exists;

			/* add the new item to the vector. */
			resultVector->push_back(tmp);
		}
	}

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

char* RPMInfoProbe::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 (NULL);
}

int_32 RPMInfoProbe::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 );
}


