//
// $Id: FileProbe.cpp,v 1.9 2005/08/10 19:15:53 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 "FileProbe.h"

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

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

}

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

}

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

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

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

	return instance;	
}

pdVector FileProbe::Run(ProbeData *probeDataIn)
{
	//------------------------------------------------------------------------------------//
	//
	//  ABSTRACT
	//
	//  Simply call the GetData method
	//------------------------------------------------------------------------------------//

	FileData *dataIn = (FileData*)probeDataIn;

	pdVector resultVector = GetData(dataIn);

	return resultVector;
}

pdVector FileProbe::GetData(FileData *dataIn)
{
	//------------------------------------------------------------------------------------//
	//
	//  ABSTRACT
	//
	//  Get all the file attributes and all the permissionions for the file
	//------------------------------------------------------------------------------------//

	pdVector resultVector;
	FileData *tmp = NULL;
	sVector files;

	if(dataIn->path->type == literal) {
		//	Create a new data object
		tmp = new FileData();
		tmp->path->object = dataIn->path->object;
		tmp->path->type = literal;
		tmp->isPatternMatchObject = dataIn->isPatternMatchObject;

		//	Get the file data
		GetFileData(tmp);

		//	Add the new data itme to the result vector
		resultVector.push_back(tmp);
	
	} else {
		
		//	Reset the pattern mathing object
		myMatcher->Reset();

		//	Get the matching files
		FileFinder fileFinder;

		files = fileFinder.Search(dataIn->path->object);
		//FindMatchingFiles(dataIn->path->object, &files, "/");
		
		//	Loop through each file contained in the files vector and gather 
		//	file attributes information.
		sVector::iterator fIterator;
		for (fIterator=files.begin(); fIterator!=files.end(); fIterator++) {

			//	Create a new data object
			tmp = new FileData();
			tmp->path->object = (*fIterator);
			tmp->path->type = literal;

			//	Get the file data
			GetFileData(tmp);

			//	Add the new data item to the resultVector
			resultVector.push_back(tmp);
		}

		// Add an object indicateing that the patterns either matched an object or did not
		tmp = new FileData();
		tmp->path->object = dataIn->path->object;
		tmp->path->type = dataIn->path->type;
		tmp->isPatternMatchObject = true;
		if(files.size() == 0) {
			
			tmp->SetMessage("No matches were found on the system for the specified pattern.");
			tmp->SetStatus(doesNotExist);

		} else {

			tmp->SetMessage("The specified pattern matched at least one file on the system.");
			tmp->SetStatus(exists);
		}
		resultVector.push_back(tmp);
	}

	return resultVector;
}

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

void FileProbe::GetFileData(FileData *dataOut)
{
	//------------------------------------------------------------------------------------//
	//
	//  ABSTRACT
	//
	//  Populate the FileAttributeData object
	//------------------------------------------------------------------------------------//

	FILE* fp;
	char buf[1024];

	//////////////////////////////////////////////////////
	/////////////////////  FileType  /////////////////////
	//////////////////////////////////////////////////////

	struct stat sbuf;
	if (stat(dataOut->path->object.c_str(), &sbuf) != 0) {
		string errorMessage = "";
		errorMessage.append("(FileProbe) Call to stat failed for file '");
		errorMessage.append(dataOut->path->object);
		errorMessage.append("' - ");
		errorMessage.append(strerror(errno));
		
		dataOut->SetMessage(errorMessage);
		dataOut->SetStatus(error);

		return;
	}

	// Set the status of the file to exists
	dataOut->SetStatus(exists);

	mode_t mode;
	mode = sbuf.st_mode;

	if ((mode & 0xF000) == S_IFIFO) dataOut->type->value = "fifo";
	else if ((mode & 0xF000) == S_IFCHR) dataOut->type->value = "character";
	else if ((mode & 0xF000) == S_IFDIR) dataOut->type->value = "directory";
	else if ((mode & 0xF000) == S_IFBLK) dataOut->type->value = "block special";
	else if ((mode & 0xF000) == S_IFREG) dataOut->type->value = "regular";
	else if ((mode & 0xF000) == S_IFLNK) dataOut->type->value = "symbolic link";
	else if ((mode & 0xF000) == S_IFSOCK) dataOut->type->value = "socket";

	dataOut->type->status = exists;
	dataOut->type->dataType = stringType;

	//////////////////////////////////////////////////////
	/////////////////////  a_time	 /////////////////////
	//////////////////////////////////////////////////////
	dataOut->a_time->value = Common::ToString((long)sbuf.st_atime);
	dataOut->a_time->status = exists;
	dataOut->a_time->dataType = stringType;

	//////////////////////////////////////////////////////
	/////////////////////  c_time	 /////////////////////
	//////////////////////////////////////////////////////
	dataOut->c_time->value = Common::ToString((long)sbuf.st_atime); 
	dataOut->c_time->status = exists;
	dataOut->c_time->dataType = stringType;

	//////////////////////////////////////////////////////
	/////////////////////  m_time	 /////////////////////
	//////////////////////////////////////////////////////
	dataOut->m_time->value = Common::ToString((long)sbuf.st_atime);
	dataOut->m_time->status = exists;
	dataOut->m_time->dataType = stringType;

	//////////////////////////////////////////////////////
	////////////////////////  MD5  ///////////////////////
	//////////////////////////////////////////////////////

	fp = fopen(dataOut->path->object.c_str(), "r");
	if (fp == NULL)
	{
		dataOut->md5->value = "";
		dataOut->md5->status = error;
		dataOut->md5->dataType = stringType;
	}
	else
	{
		// Create the md5 hash.  This constructor creates a new md5 object, updates the hash,
		// finalizes the hash, and closes the FILE object.
		
		MD5 context(fp);

		memset(buf, '\0', sizeof(buf));
		snprintf(buf, sizeof(buf)-1, "%s", context.hex_digest());
		buf[sizeof(buf)-1] = '\0';

		dataOut->md5->value = buf;
		dataOut->md5->status = exists;
		dataOut->md5->dataType = stringType;
	}

	//////////////////////////////////////////////////////
	//////////////////////  size     /////////////////////
	//////////////////////////////////////////////////////
	// no currently collected
	dataOut->size->value = "0";
	dataOut->md5->status = notCollected;
	dataOut->md5->dataType = integerType;

	//////////////////////////////////////////////////////
	//////////////////////  user_id  /////////////////////
	//////////////////////////////////////////////////////

	char uidBuf[16];
    snprintf(uidBuf, sizeof(uidBuf), "%u", sbuf.st_uid);
	dataOut->user_id->value = uidBuf;
	dataOut->user_id->status = exists;
	dataOut->user_id->dataType = integerType;

	//////////////////////////////////////////////////////
	/////////////////////// group_id /////////////////////
	//////////////////////////////////////////////////////
	char gidBuf[16];
    snprintf(gidBuf, sizeof(gidBuf), "%u", sbuf.st_gid);
	dataOut->group_id->value = gidBuf;
	dataOut->group_id->status = exists;
	dataOut->group_id->dataType = integerType;

	// File permissions

	//////////////////////////////////////////////////////
	///////////////////////  SUID  ///////////////////////
	//////////////////////////////////////////////////////

	if (mode & S_ISUID) dataOut->suid->value = "1";
	else dataOut->suid->value = "0";

	dataOut->suid->status = exists;
	dataOut->suid->dataType = booleanType;

	//////////////////////////////////////////////////////
	///////////////////////  SGID  ///////////////////////
	//////////////////////////////////////////////////////

	if (mode & S_ISGID) dataOut->sgid->value = "1";
	else dataOut->sgid->value = "0";

	dataOut->sgid->status = exists;
	dataOut->sgid->dataType = booleanType;

	//////////////////////////////////////////////////////
	//////////////////////  STICKY  //////////////////////
	//////////////////////////////////////////////////////

	if (mode & S_ISVTX) dataOut->sticky->value = "1";
 	else dataOut->sticky->value = "0";

	dataOut->sticky->status = exists;
	dataOut->sticky->dataType = booleanType;

	//////////////////////////////////////////////////////
	///////////////////////  UREAD  //////////////////////
	//////////////////////////////////////////////////////

	if (mode & S_IRUSR) dataOut->uread->value = "1";
	else dataOut->uread->value = "0";

	dataOut->uread->status = exists;
	dataOut->uread->dataType = booleanType;

	//////////////////////////////////////////////////////
	//////////////////////  UWRITE  //////////////////////
	//////////////////////////////////////////////////////

	if (mode & S_IWUSR) dataOut->uwrite->value = "1";
	else dataOut->uwrite->value = "0";

	dataOut->uwrite->status = exists;
	dataOut->uwrite->dataType = booleanType;

	//////////////////////////////////////////////////////
	///////////////////////  UEXEC  //////////////////////
	//////////////////////////////////////////////////////

	if (mode & S_IXUSR) dataOut->uexec->value = "1";
	else dataOut->uexec->value = "0";

	dataOut->uexec->status = exists;
	dataOut->uexec->dataType = booleanType;

	//////////////////////////////////////////////////////
	///////////////////////  GREAD  //////////////////////
	//////////////////////////////////////////////////////

	if (mode & S_IRGRP) dataOut->gread->value = "1";
	else dataOut->gread->value = "0";

	dataOut->gread->status = exists;
	dataOut->gread->dataType = booleanType;

	//////////////////////////////////////////////////////
	//////////////////////  GWRITE  //////////////////////
	//////////////////////////////////////////////////////

	if (mode & S_IWGRP) dataOut->gwrite->value = "1";
	else dataOut->gwrite->value = "0";

	dataOut->gwrite->status = exists;
	dataOut->gwrite->dataType = booleanType;

	//////////////////////////////////////////////////////
	///////////////////////  GEXEC  //////////////////////
	//////////////////////////////////////////////////////

	if (mode & S_IXGRP) dataOut->gexec->value = "1";
	else dataOut->gexec->value = "0";

	dataOut->gexec->status = exists;
	dataOut->gexec->dataType = booleanType;

	//////////////////////////////////////////////////////
	///////////////////////  OREAD  //////////////////////
	//////////////////////////////////////////////////////

	if (mode & S_IROTH) dataOut->oread->value = "1";
	else dataOut->oread->value = "0";

	dataOut->oread->status = exists;
	dataOut->oread->dataType = booleanType;

	//////////////////////////////////////////////////////
	//////////////////////  OWRITE  //////////////////////
	//////////////////////////////////////////////////////

	if (mode & S_IWOTH) dataOut->owrite->value = "1";
	else dataOut->owrite->value = "0";

	dataOut->owrite->status = exists;
	dataOut->owrite->dataType = booleanType;

	//////////////////////////////////////////////////////
	///////////////////////  OEXEC  //////////////////////
	//////////////////////////////////////////////////////

	if (mode & S_IXOTH) dataOut->oexec->value = "1";
	else dataOut->oexec->value = "0";

	dataOut->oexec->status = exists;
	dataOut->oexec->dataType = booleanType;
}

/*
void FileProbe::FindMatchingFiles(string pattern, sVector *matches, string fullpath)
{
	//------------------------------------------------------------------------------------//
	//
	//  ABSTRACT
	//
	//  Traverse the file system looking for matching file paths. Each matching path is 
	//	pushed onto the matches vector. 
	//
	//	fullpath may be a file or a directory. 
	//		- if it is a file check if it matches the pattern
	//		- if a directory call self recursively for each name in the directory
	//	
	//------------------------------------------------------------------------------------//

	struct stat statbuf;
	struct dirent *dirp;
	DIR *dp;
	string tmp = "";

	//	Call stat 
	if(lstat(fullpath.c_str(), &statbuf) < 0) {
		//	stat error
		//	not sure I want to report this now
		return;
	}

	//	If not a directory check if a match and return
	if(S_ISDIR(statbuf.st_mode) == 0) {
		if(myMatcher->IsMatch(pattern.c_str(), fullpath.c_str()))
		  matches->push_back(fullpath);

		return;
	}
	
	///////////////////////////////////////////////////////
	//	It is a directory
	///////////////////////////////////////////////////////

	//	Append a '/'
	if(fullpath.at(fullpath.length()-1) != '/')
	  fullpath.append("/");

	//	Open the directory
	dp = opendir(fullpath.c_str());
	if(dp == NULL) {
		//	Error opening directory
		//	not sure this error rmatters
		// cout << "Failed to open the directory" << endl;
		return;
	}

	//	Loop through all names in the directory
	while((dirp = readdir(dp)) != NULL) {
		//	Ignore dot and dot-dot
		if(strcmp(dirp->d_name, ".") == 0 || strcmp(dirp->d_name, "..") == 0)
			continue;

		//	append the name after the "/"
		tmp = fullpath;
		tmp.append(dirp->d_name);

		// Nake recursive call
		FindMatchingFiles(pattern, matches, tmp);
	}


	//	Close the directory
	if(closedir(dp) < 0) {
		//	Error closing the directory
		//	not sure this error matters
		// cout << "Failed to close the directory" << endl;
		return;
	}
}
*/
