//
// $Id: ProcessProbe.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 "ProcessProbe.h"

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

ProcessProbe::ProcessProbe()
{
	// -----------------------------------------------------------------------
	//	Abstract
	//
	//	Do nothing for now
	// -----------------------------------------------------------------------
}

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

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

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

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

	return instance;	
}

pdVector ProcessProbe::Run(ProbeData *probeDataIn)
{
	//------------------------------------------------------------------------------------//
	//
	//  ABSTRACT
	//  
	//  Read the command passed in the data object and get all process information for 
	//  any process that matches the input command string.
	//  Support pattern matching on the command string.
	//  This probe will prepend a '^' and append a '$' to all litteral command strings.
	//  Then pattern matching can be used for all tests. This is different than the 
	//  rest of the probes.
	//
	//------------------------------------------------------------------------------------//
	ProcessData *dataIn = (ProcessData*)probeDataIn;
	pdVector resultVector;

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

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

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

	try {
		// Gather new data and add to resultVector
		GetPSInfo(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(runningDataIn->command->type == pattern_match) {
			
			ProcessData *tmp = new ProcessData();
			tmp->command->object = dataIn->command->object;
			tmp->command->type = dataIn->command->type;
			tmp->isPatternMatchObject = true;

			// 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);
			}
			resultVector.push_back(tmp);
			tmp = NULL;	
		}
	} catch(ProbeException ex) {

		ProcessData *tmp = new ProcessData();
		tmp->command->object = dataIn->command->object;
		tmp->command->type = dataIn->command->type;
		tmp->isPatternMatchObject = dataIn->isPatternMatchObject;
		tmp->SetMessage(ex.GetErrorMessage());
		tmp->SetStatus(error);
		resultVector.push_back(tmp);
		tmp = NULL;

	} catch(...) {
		ProcessData *tmp = new ProcessData();
		tmp->command->object = dataIn->command->object;
		tmp->command->type = dataIn->command->type;
		tmp->isPatternMatchObject = dataIn->isPatternMatchObject;
		tmp->SetMessage("Error: An unknown error occured while processing a process test.");
		tmp->SetStatus(error);
		resultVector.push_back(tmp);
		tmp = NULL;
	}

  return resultVector;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~  Private Members  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//
void ProcessProbe::GetPSInfo(ProcessData *dataIn, pdVector *resultVector)
{
	//------------------------------------------------------------------------------------//
	//
	//  ABSTRACT
	//  
	//  Main method for gathering process information.
	//------------------------------------------------------------------------------------//
	string errMsg = "";
	ProcessData *tmp = NULL;

	// Time parameters
	time_t currentTime = 0;
	unsigned long adjustedStartTime, execTime = 0;

	// TTY String
	char ttyName[TTY_LEN + 1];

	// Process Parameters
	char cmdline[CMDLINE_LEN + 1];
	char schedulingClass[SCHED_CLASS_LEN + 1];

	int uid, pid, ppid;
	long priority = 0;
	unsigned long starttime = 0;

	int status = 0;

	// Grab the current time and uptime(Linux only) to calculate start and exec times later
	time(&currentTime);

	unsigned long uptime = 0;
	status = RetrieveUptime(&uptime, &errMsg);
	if(status < 0) {
		throw ProbeException(errMsg);
    }

	DIR *proc;
	struct dirent *readProc;

	// Step into the /proc directory
	if((proc = opendir("/proc")) == NULL) {
		errMsg.append("ProcessProbe: Could not open /proc");
		throw ProbeException(errMsg);

	} else {

		// Loop through all of the files - we're only concerned with those that
		// start with a digit
		while((readProc = readdir(proc)) != NULL) {
			if(isdigit(readProc->d_name[0])) {
				// Clear the ps values
				memset(cmdline, 0, CMDLINE_LEN + 1);
				memset(schedulingClass, 0, SCHED_CLASS_LEN + 1);
				memset(ttyName, 0, TTY_LEN + 1);
				uid = pid = ppid = priority = starttime = 0;
				adjustedStartTime = execTime = 0;
				errMsg = "";

				// Retrieve the command line with arguments
				status = RetrieveCommandLine(readProc->d_name, cmdline, &errMsg);
				if(status < 0) { 
					throw ProbeException(errMsg);

				// If the command line matches the input command line get the remaining 
				// data and add a new data object tot he resultVector
				} else if(status == 0 && myMatcher->IsMatch(dataIn->command->object.c_str(), cmdline)) {

					// Read the 'stat' file for the remaining process parameters
					status = RetrieveStatFile(readProc->d_name, &uid, &pid, &ppid, &priority, &starttime, &errMsg);
					if(status < 0) {
						tmp = new ProcessData();
						tmp->SetStatus(error);
						tmp->command->object = cmdline;
						tmp->SetMessage(errMsg);
						resultVector->push_back(tmp); 
						continue;
					}

					// We can retrieve a value for the tty from the 'stat' file, but it's unclear
					// how you convert that to a device name.  Therefore, we ignore that value
					// and grab the device stdout(fd/0) is attached to.
					RetrieveTTY(readProc->d_name, ttyName);

					// The Linux start time is represented as the number of jiffies (1/100 sec)
					// that the application was started after the last system reboot.  To get an
					// actual timestamp, we have to do some gymnastics.  We then use the calculated
					// start time to determine the exec time.
					//
					if(uptime > 0) {
						adjustedStartTime = currentTime - (uptime - (starttime/100));
						execTime = currentTime - adjustedStartTime;
					}

					// Add the data to a new data object and add th resultVector
					tmp = new ProcessData();
					tmp->SetStatus(exists);
					tmp->SetMessage(errMsg);

					tmp->command->object = cmdline;
					tmp->command->type = literal;

					tmp->scheduling_class->value = "-";
					tmp->scheduling_class->status = exists;
					tmp->scheduling_class->dataType = stringType;

					tmp->user_id->value = Common::ToString(uid);
					tmp->user_id->status = exists;
					tmp->user_id->dataType = integerType;

					tmp->pid->value = Common::ToString(pid);
					tmp->pid->status = exists;
					tmp->pid->dataType = integerType;

					tmp->ppid->value = Common::ToString(ppid);
					tmp->ppid->status = exists;
					tmp->ppid->dataType = integerType;

					tmp->priority->value = Common::ToString(priority);
					tmp->priority->status = exists;
					tmp->priority->dataType = integerType;

					tmp->start_time->value = Common::ToString(adjustedStartTime);
					tmp->start_time->status = exists;
					tmp->start_time->dataType = stringType;

					tmp->tty->value = ttyName;
					tmp->tty->status = exists;
					tmp->tty->dataType = stringType;

					tmp->exec_time->value = Common::ToString(execTime);
					tmp->exec_time->status = exists;
					tmp->exec_time->dataType = stringType;
					
					resultVector->push_back(tmp); 
				}
			}
		} // else
    closedir(proc);
  }
}

// Read /proc/<pid>/cmdline to gather the application name and startup arguments
//
int ProcessProbe::RetrieveCommandLine(char *process, char *cmdline, string *errMsg) {
	int i = 0;
	int bytes = 0;
	FILE *cmdlineFile = NULL;

	// Build the absolute path to the command line file
	string cmdlinePath = "/proc/";
	cmdlinePath.append(process);
	cmdlinePath.append("/cmdline");

	// Open the file for reading
	if((cmdlineFile = fopen(cmdlinePath.c_str(), "r")) == NULL) {
		errMsg->append("ProcessProbe: Unable to obtain command line for pid: ");
		errMsg->append(process);
		return(-1);

	} else {
    
		// Read the contents of the file, and convert all of the NULL's
		// separating the args to spaces.
		//
		bytes = fread(cmdline, sizeof(char), CMDLINE_LEN, cmdlineFile);
	    
		// Remember to leave the trailing NULL (bytes - 1).
		for(i = 0; i < bytes - 1; i++) {
			if(cmdline[i] == '\0') {
				cmdline[i] = ' ';
			}
		}
	}
  return(0);
}


// Read the stat file for a specific process
//
int ProcessProbe::RetrieveStatFile(char *process, int *uid, int *pid, int *ppid, long *priority, unsigned long *starttime, string *errMsg) {

  // Stat File parameters.  While we're really only concerned with gathering the parameters
  // that are passed in, these variables are good placeholders in case we decide to collect
  // something else in the future.
  //
  int pgrp, session, tty, tpgid, exit_signal, processor = 0;
  long cutime, cstime, nice, placeholder, itrealvalue, rss = 0;
  unsigned long flags, minflt, cminflt, majflt, cmajflt, utime, stime, vsize, rlim = 0;
  unsigned long startcode, endcode, startstack, kstkesp, kstkeip, signal, blocked, sigignore = 0;
  unsigned long sigcatch, wchan, nswap, cnswap = 0;
  char comm[PATH_MAX];
  char state;

  FILE *statFile = NULL;

  // Generate the absolute path name for the 'stat' file
  string statPath = "/proc/";
  statPath.append(process);
  statPath.append("/stat");

  // While we're here, stat() the 'stat' file to get the uid for the process.  If
  // we want to convert this to a user name, feed the uid to getpwuid().
  //
  struct stat statBuf;

  if((stat(statPath.c_str(), &statBuf)) < 0) {
    errMsg->append("ProcessProbe: Unable to obtain uid information for pid:  ");
    errMsg->append(process);
    return(-1);
  } else {
    *uid = statBuf.st_uid;
  }
    
  // Open the 'stat' file and read the contents
  if((statFile = fopen(statPath.c_str(), "r")) == NULL) {
    errMsg->append("ProcessProbe: Unable to obtain process information for pid: ");
    errMsg->append(process);
    return(-1);

  } else {

    // Linux gives us a nicely formatted file for fscanf to pull in
    fscanf(statFile, "%d %s %c %d %d %d %d %d %lu %lu %lu %lu %lu %lu %lu %ld %ld %ld %ld %ld %ld %lu %lu %ld %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %d %d", pid, comm, &state, ppid, &pgrp, &session, &tty, &tpgid, &flags, &minflt, &cminflt, &majflt, &cmajflt, &utime, &stime, &cutime, &cstime, priority, &nice, &placeholder, &itrealvalue, starttime, &vsize, &rss, &rlim, &startcode, &endcode, &startstack, &kstkesp, &kstkeip, &signal, &blocked, &sigignore, &sigcatch, &wchan, &nswap, &cnswap, &exit_signal, &processor);

  }
  fclose(statFile);

  return(0);
}


// Since there appears to be no simple way to convert the 'tty' value contained in
// '/proc/<pid>/stat' into a device name, we instead use '/proc/<pid>/fd/0', which is
// normally linked to a device.  Note, the 'fd' directory is set read-only user, so
// if this probe is not run as root, many of these reads will fail.  In that case, we
// return '?' as the tty value.
//
void ProcessProbe::RetrieveTTY(char *process, char *ttyName) {
  int bytes = 0;

  // Generate the absolute path name for the stdout(0) file in 'fd'
  string ttyPath = "/proc/";
  ttyPath.append(process);
  ttyPath.append("/fd/0");

  // Attempt to read the name of the file linked to '0'
  bytes = readlink(ttyPath.c_str(), ttyName, TTY_LEN);

  // If there is an error, set the tty string to '?'
  if(bytes < 0 || strncmp(ttyName, "/dev", 4) != 0) {
    strncpy(ttyName, "?\0", 2);
  }
}


// Read the value contained in '/proc/uptime/' so that we can calculate
// the start time and exec time of the running processes.
//
int  ProcessProbe::RetrieveUptime(unsigned long *uptime, string *errMsg) {
  // The second value in this file represents idle time - we're not concerned with this.
  unsigned long idle = 0;
  FILE *uptimeHandle = NULL;

  // Open and parse the 'uptime' file
  if((uptimeHandle = fopen("/proc/uptime", "r")) == NULL) {
    errMsg->append("ProcessProbe: Could not open /proc/uptime");
    uptime = 0;
    return(-1);
  } else {
    fscanf(uptimeHandle, "%lu %lu", uptime, &idle);
  }
  fclose(uptimeHandle);

  return(0);
}
