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

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

InetListeningServersProbe::InetListeningServersProbe()
{
	// -----------------------------------------------------------------------
	//	Abstract
	//
	//	Do nothing for now
	//
	// -----------------------------------------------------------------------

}

InetListeningServersProbe::~InetListeningServersProbe()
{
	// -----------------------------------------------------------------------
	//	Abstract
	//
	//	Do nothing for now
	//
	// -----------------------------------------------------------------------

}

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

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

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

	return instance;	
}

pdVector InetListeningServersProbe::Run(ProbeData *probeDataIn)
{
  //------------------------------------------------------------------------------------//
  //  ABSTRACT
  //  
  //  Read the program_name passed in the data object and gather inet listening server
  //  information for all records that have a matching program name
  //  Support pattern matching on the program_name string.
  //  This probe will prepend a '^' and append a '$' to all litteral command strings.
  //  Then pattern matching can be used for all tests. 
  //------------------------------------------------------------------------------------//
  
	InetListeningServersData *dataIn = (InetListeningServersData*)probeDataIn;

	pdVector resultVector;

	// make a copy of the provided data object
	InetListeningServersData *runningDataIn = new InetListeningServersData();
	runningDataIn->program_name->object = dataIn->program_name->object;
	runningDataIn->program_name->type = dataIn->program_name->type;

	// set up the literal stirng as a regular expression
	if(runningDataIn->program_name->type == literal) {
		// escape any regex chars 
		runningDataIn->program_name->object = myMatcher->EscapeRegexChars(runningDataIn->program_name->object);

		// add begin and end string designators
		runningDataIn->program_name->object = "^" + runningDataIn->program_name->object + "$";
	}

	try {
		// Gather new data and add to resultVector
		GetNetstat(dataIn, &resultVector);

		// Ensure that if the provided object specified a pettern match the 
		// results of the pattern match are written to the sc file.
		if(dataIn->program_name->type == pattern_match) {
			
			InetListeningServersData *tmp = new InetListeningServersData();
			tmp->program_name->object = dataIn->program_name->object;
			tmp->program_name->type = dataIn->program_name->type;

			// Check the size of the result Vector
			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);
			}
			tmp->isPatternMatchObject = true;
			resultVector.push_back(tmp);
			tmp = NULL;	
		}
	} catch(Exception ex) {
		InetListeningServersData *tmp = new InetListeningServersData();
		tmp->SetMessage(ex.GetErrorMessage());
		tmp->SetStatus(error);
		tmp->program_name->object = dataIn->program_name->object;
		tmp->program_name->type = dataIn->program_name->type;
		tmp->isPatternMatchObject = dataIn->isPatternMatchObject;

		resultVector.push_back(tmp);      

	} catch(...) {
		InetListeningServersData *tmp = new InetListeningServersData();
		tmp->SetMessage("Error: An unknown error has ocured in InetListeningServersProbe::GetNetstat().");
		tmp->SetStatus(error);
		tmp->program_name->object = dataIn->program_name->object;
		tmp->program_name->type = dataIn->program_name->type;
		tmp->isPatternMatchObject = dataIn->isPatternMatchObject;

		resultVector.push_back(tmp);      
	}

	delete runningDataIn;

	return resultVector;
}

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

void InetListeningServersProbe::GetNetstat(InetListeningServersData *dataIn, pdVector *resultVector)
{
  //------------------------------------------------------------------------------------//
  //  ABSTRACT
  //  
  //  Gather the listening servers data that matches dataIn's program name
  //  Add each match to the result vector with the test id from dataIn
  //
  //------------------------------------------------------------------------------------//

	InetListeningServersData *tmp = NULL;
	int fd1[2];
	int fd2[2];
	int pid = 0;
	//char *buf = NULL;
	//char *errbuf = NULL;
	NetstatResult *netstatData = NULL;

	// Open communication pipes between processes
	if (pipe(fd1) < 0 || pipe(fd2) < 0)
		throw ProbeException("Error: (InetListeningServersProbe) Could not open pipe.");

	if ((pid = fork()) < 0) {
		throw ProbeException("Error: (InetListeningServersProbe) fork error before running netstat -tuwlnpe.");

	// Child process
	} else if (pid == 0) {

		// Close unnecessary pipes
		close (fd1[0]);
		close (fd2[0]);  

		ChildExecNetstat(fd1[1], fd2[1]);

	// Parent process
	} else {
	
		// Close unnecessary pipes
		close (fd1[1]);
		close (fd2[1]);

		// Get the results of netstat
		netstatData = ParentGetChildResult(fd1[0], fd2[0], pid);
	    
		// Check the results of running netstat
		if(netstatData->text.compare("") == 0) {
			tmp = new InetListeningServersData();
			tmp->SetStatus(error);
			tmp->SetMessage("Error: netstat returned no data.");

			if(netstatData->errText.compare("") == 0)
				tmp->SetMessage(tmp->GetMessage()+ " " + netstatData->errText);

			resultVector->push_back(tmp);
			return;
		}
		  
		// Walk through the netstat output lines, parsing them.
		string tmpSTR;
		string PIDandProgramName;

		int curDelim;
		int preDelim;
		int curColon;
		int preColon;
		unsigned int curPos;
		unsigned int tmpPos;

		string Protocol;
		string LocalFullAddress;
		string LocalAddress;
		string LocalPort;
		string ForeignFullAddress;
		string ForeignAddress;
		string ForeignPort;
		string UserID;
		string PID;
		string ProgramName;
		bool skip;

		// Toss out first two lines
		curPos = netstatData->text.find("\n") + 1;
		curPos = netstatData->text.find("\n", curPos) + 1;
		
		// Get the first line end 
		tmpPos = netstatData->text.find("\n", curPos);

		// Parse the remaining lines.
		while (tmpPos != string::npos) {
		    
			// Get the next string
			tmpSTR = netstatData->text.substr(curPos, (tmpPos - curPos));
			curPos = tmpPos + 1;
			
			curDelim = 0;
			preDelim = 0;

			////////////////////////////////////////////
			///// Protocol:  TCP or UDP ////////////////
			////////////////////////////////////////////

			// Find the first space and grab the characters between preDelim and that point
			curDelim = tmpSTR.find(" ",preDelim);
			Protocol = tmpSTR.substr(preDelim,(curDelim - preDelim));

			// Now find the end of that whitespace
			preDelim = tmpSTR.find_first_not_of(" ",curDelim);

			////////////////////////////////////////////
			///// Skip two fields //////////////////////
			////////////////////////////////////////////
			
			// Find the next space and then skip past it
			curDelim = tmpSTR.find(" ",preDelim);
			preDelim = tmpSTR.find_first_not_of(" ",curDelim); 

			// Do that again.
			curDelim = tmpSTR.find(" ",preDelim);
			preDelim = tmpSTR.find_first_not_of(" ",curDelim); 

			////////////////////////////////////////////
			///// LocalFullAddress //// ////////////////
			////////////////////////////////////////////

			curDelim = tmpSTR.find(" ",preDelim);
			LocalFullAddress = tmpSTR.substr(preDelim,(curDelim - preDelim));
			preDelim = tmpSTR.find_first_not_of(" ",curDelim);

			////////////////////////////////////////////
			///// LocalAddress /////////////////////////
			////////////////////////////////////////////

			preColon = 0;
			curColon = LocalFullAddress.find(":",preColon);
			LocalAddress = LocalFullAddress.substr(preColon,(curColon - preColon));

			////////////////////////////////////////////
			///// LocalPort ////////////////////////////
			////////////////////////////////////////////

			preColon = curColon + 1;
			curColon = LocalFullAddress.length();
			LocalPort = LocalFullAddress.substr(preColon,(curColon - preColon));

			////////////////////////////////////////////
			///// ForeignFullAddress ///////////////////
			////////////////////////////////////////////

			curDelim = tmpSTR.find(" ",preDelim);
			ForeignFullAddress = tmpSTR.substr(preDelim,(curDelim - preDelim));
			preDelim = tmpSTR.find_first_not_of(" ",curDelim);

			if((preDelim - curDelim) > 20)
				skip = false;
			else
				skip = true;

			////////////////////////////////////////////
			///// ForeignAddress ///////////////////////
			////////////////////////////////////////////

			preColon = 0;
			curColon = ForeignFullAddress.find(":",preColon);
			ForeignAddress = ForeignFullAddress.substr(preColon,(curColon - preColon));

			////////////////////////////////////////////
			///// ForeignPort //////////////////////////
			////////////////////////////////////////////

			preColon = curColon + 1;
			curColon = ForeignFullAddress.length();
			ForeignPort = ForeignFullAddress.substr(preColon,(curColon - preColon));

			///////////////////////////////////////////
			///// Skip one field //////////////////////
			///////////////////////////////////////////
			if(skip) {
				curDelim = tmpSTR.find(" ",preDelim);
				preDelim = tmpSTR.find_first_not_of(" ",curDelim);
			}
			///////////////////////////////////////////
			///// UserID //////////////////////////////
			///////////////////////////////////////////

			curDelim = tmpSTR.find(" ",preDelim);
			UserID = tmpSTR.substr(preDelim,(curDelim - preDelim));
			preDelim = tmpSTR.find_first_not_of(" ",curDelim);

			///////////////////////////////////////////
			///// Skip one field //////////////////////
			///////////////////////////////////////////
	
			curDelim = tmpSTR.find(" ",preDelim);
			preDelim = tmpSTR.find_first_not_of(" ",curDelim);

			///////////////////////////////////////////
			///// PID/ProgramName /////////////////////
			///////////////////////////////////////////

			curDelim = tmpSTR.find(" ",preDelim);
			PIDandProgramName = tmpSTR.substr(preDelim,(curDelim - preDelim));
			preDelim = tmpSTR.find_first_not_of(" ",curDelim);
			
			////////////////////////////////////////////
			///// PID  /////////////////////////////////
			////////////////////////////////////////////

			preColon = 0;
			curColon = PIDandProgramName.find("/",preColon);
			PID = PIDandProgramName.substr(preColon,(curColon - preColon));
			    
			////////////////////////////////////////////
			///// ProgramName //////////////////////////
			////////////////////////////////////////////

			preColon = curColon + 1;
			curColon = PIDandProgramName.length();
			ProgramName = PIDandProgramName.substr(preColon,(curColon - preColon));

			////////////////////////////////////////////
			//////////////////////////////////////////// 


			// Check if the program name is a match
			if(myMatcher->IsMatch(dataIn->program_name->object.c_str(), ProgramName.c_str())) {
				tmp = new InetListeningServersData();
				tmp->SetStatus(exists);
				tmp->program_name->object = ProgramName;
				tmp->program_name->type = literal;

				tmp->local_full_address->value = LocalFullAddress;
				tmp->local_full_address->dataType = stringType;
				tmp->local_full_address->status = exists;

				tmp->local_address->value = LocalAddress;
				tmp->local_address->dataType = stringType;
				tmp->local_address->status = exists;

				tmp->local_port->value = LocalPort;
				tmp->local_port->dataType = integerType;
				tmp->local_port->status = exists;

				tmp->foreign_full_address->value = ForeignFullAddress;
				tmp->foreign_full_address->dataType = stringType;
				tmp->foreign_full_address->status = exists;

				tmp->foreign_address->value = ForeignAddress;
				tmp->foreign_address->dataType = stringType;
				tmp->foreign_address->status = exists;

				tmp->foreign_port->value = ForeignPort;
				tmp->foreign_port->dataType = integerType;
				tmp->foreign_port->status = exists;

				tmp->user_id->value = UserID;
				tmp->user_id->dataType = integerType;
				tmp->user_id->status = exists;

				tmp->pid->value = PID;
				tmp->pid->dataType = integerType;
				tmp->pid->status = exists;
				
				tmp->protocol->value = Protocol;
				tmp->protocol->dataType = stringType;
				tmp->protocol->status = exists;
				
				if(netstatData->errText.compare("") != 0) {
					tmp->SetStatus(error);
					tmp->SetMessage("Error: " + netstatData->errText);
				}
				
				resultVector->push_back(tmp);
			}

			// Get the next line end 
			tmpPos = netstatData->text.find("\n", curPos);  
		}    
	}
}

void InetListeningServersProbe::ChildExecNetstat(int writeErrh, int writeh)
{
  //------------------------------------------------------------------------------------//
  //  ABSTRACT
  //  
  //  Redirect stdout and stderr to the provided pipes (writeh, and writeErrh). 
  //  Execute nestate with the correct options.
  //
  //------------------------------------------------------------------------------------//

  // Point STDOUT and STDERR of child at pipe.  When exec program, output and
  // all error messages will be sent down pipe instead of to screen.
  if (writeh != STDOUT_FILENO) {
    if (dup2(writeh, STDOUT_FILENO) != STDOUT_FILENO)
      exit(-1);
  }

  if (writeErrh != STDERR_FILENO) {
    if (dup2(writeErrh, STDERR_FILENO) != STDERR_FILENO)
      exit(-1);
  }

  // Output redirected (duplicated), no longer need pipe
  close (writeh);
  close (writeErrh);

  // Execute the command
  execl("/bin/netstat", "netstat", "-tuwlnpe", NULL);

  exit(0);
} 

NetstatResult* InetListeningServersProbe::ParentGetChildResult(int readErrh, int readh, int pid)//, char* buf, char* errbuf)
{
  //------------------------------------------------------------------------------------//
  //  ABSTRACT
  //  
  //  Read readErrh and readh until there is no more data to be read. Wait for the 
  //  child process to complere. Return a NetstatResult object with the data from
  //  netstat.
  //
  //------------------------------------------------------------------------------------//

  NetstatResult *result = new NetstatResult("", "");
  int bytes = 0;
  int maxFDS = 0;
  char *buf = NULL;
  fd_set readfds;
  bool errComplete = false;
  bool stdComplete = false;

  // Allocate memory for  buf
  buf = (char*)malloc(1024);
  if(buf == NULL) {

    // Wait for the child process to compelete
    waitpid (pid, NULL, 0);
      
    // Close the pipes
    close (readh);
    close (readErrh);

    // Set an error message
    result->errText.append("Error: unable to allocate memory to read netstat data into.");
    
    return result;
  }

  // Init the maxFDS value
  if(readh >= readErrh) {
    maxFDS = readh + 1;
  }else {
    maxFDS = readErrh + 1;
  }

  // Loop over the call to select without using a timmer
  // Only stop looping when select fails. select will
  // fail when the file descriptors are closed by the 
  // child process.
  while(!errComplete || !stdComplete) {

    // Reset the fd_set
    FD_ZERO(&readfds);
    FD_SET(readErrh, &readfds);
    FD_SET(readh, &readfds);

    if(select(maxFDS, &readfds, NULL, NULL, NULL) != -1) {
      if(FD_ISSET(readErrh, &readfds)) {
	// Read some error output from command.  
	memset(buf, '\0', 1024);
	bytes = read(readErrh, buf, 1023);
	result->errText.append(buf);

	if(bytes == 0) 
	  errComplete = true;
      }

      if(FD_ISSET(readh, &readfds)) { 
	// Read allsome std output from command. 
	memset(buf, '\0', 1024);
	bytes = read(readh, buf, 1023);
	result->text.append(buf);

	if(bytes == 0)
	  stdComplete = true;
      }
	  
    }else {	    
      break;
    }
  }
  
  // Wait for the child process to compelete
  if(waitpid (pid, NULL, 0) == -1) {
    result->errText.append("Execution of netstat in child process failed.");
    return result;
  }

  // Close the pipes
  close (readh);
  close (readErrh);

  return result;
}



  
