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

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~		 Class SystemInfoProbe		~~~~~~~~~~~~~~~~~~~~~~~~~~//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//
SystemInfoProbe *SystemInfoProbe::instance = NULL;

SystemInfoProbe::SystemInfoProbe()
{
	// Do nothing for now
}

SystemInfoProbe::~SystemInfoProbe()
{
	// Do nothing for now
}

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

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

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

	return instance;	
}
pdVector SystemInfoProbe::Run(ProbeData* probeDataIn)
{
	//------------------------------------------------------------------------------------//
	//
	//  ABSTRACT
	//
	//  Run the system info probe. Return a vector of SystemInfoData objects. 
	//	Note the input to this probe is ignored, 
	//
	//------------------------------------------------------------------------------------//

	SystemInfoData *sysInfoData = new SystemInfoData();

	try {
	  GetOSInfo((SystemInfoData*)sysInfoData);
	} catch(Exception ex) {
	  Log::WriteLog(ex.GetErrorMessage());
	  sysInfoData->os_name = "ERROR";
	  sysInfoData->os_version = "ERROR";
	  sysInfoData->architecture = "ERROR";
	  sysInfoData->primary_host_name = "ERROR";

	} catch(...) {
	  Log::WriteLog("Error: An unknwon error occured while attempting to get operating system information.");
	  sysInfoData->os_name = "ERROR";
	  sysInfoData->os_version = "ERROR";
	  sysInfoData->architecture = "ERROR";
	  sysInfoData->primary_host_name = "ERROR";
	}

	try {
	  ((SystemInfoData*)sysInfoData)->interfaces = GetInterfaces();
	} catch(Exception ex) {
	  Log::WriteLog(ex.GetErrorMessage());
	} catch(...) {
	  Log::WriteLog("Error: An unknwon error occured while attempting to get interface information.");
	}

	pdVector result;
	result.push_back(sysInfoData);

	return result;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~  Private Members  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//
#define MAXHOSTNAMELENGTH 256

void SystemInfoProbe::GetOSInfo(SystemInfoData *sysInfoData)
{
	//------------------------------------------------------------------------------------//
	//
	//  ABSTRACT
	//
	//  Get the OS name and version, the architecture, and the primary host name
	//	for the system.
	//
	//------------------------------------------------------------------------------------/

	//////////////////////////////////////////////////////////
	/////////////	     Get os info	//////////////////
	//////////////////////////////////////////////////////////
	// Simply call the uname probe	
  	Probe *unameProbe = UnameProbe::Instance();
	UnameData *tmpUnameData = new UnameData();
	pdVector unameResults = unameProbe->Run(tmpUnameData);
	UnameData *unameData = (UnameData*)unameResults.at(0);

	sysInfoData->os_name = unameData->os_name->value;
	sysInfoData->os_version = unameData->os_version->value;
	sysInfoData->architecture = unameData->machine_class->value;

	unameProbe = NULL;
	delete tmpUnameData;
	delete unameData;
  
	//////////////////////////////////////////////////////////
	////////// Get fully qulaified host name     /////////////
	//////////////////////////////////////////////////////////

	// First make a call to gethostname()
	string strHostName = "";
	char *chHostName = (char*)malloc(MAXHOSTNAMELENGTH);
	int res = 0;
	res = gethostname(chHostName, MAXHOSTNAMELENGTH);

	if(res != 0)
		throw ProbeException("Error: Unable to determine the host name.");

	strHostName = chHostName;
	// Next get the fqdn with a call to gethostbyname
	struct hostent *hostData = NULL;
	hostData = gethostbyname((const char*)chHostName); 
	if(hostData == NULL)
	  throw SystemInfoProbeException("Error: Unable to get the fully qualified domain name.");
	
	// Process the hostData structure
	sysInfoData->primary_host_name = hostData->h_name;
}

ifVector SystemInfoProbe::GetInterfaces()
{
	//------------------------------------------------------------------------------------//
	//
	//  ABSTRACT
	//
	//  Create a vector of IfData objects that will represent all the available
	//	interfaces on the system.
	//
	//	Must get interface_name, ip_address, and mac_address for each interface
	//------------------------------------------------------------------------------------//

	ifVector interfaces;
	/*
	struct ifconf conf;
	struct sockaddr_in *s_in;
	struct  sockaddr_in *hwAddr;
	int sock, count;

	// Open dummy socket
	if((sock = socket(PF_INET, SOCK_DGRAM, 0)) == -1) {
	  throw SystemInfoProbeException("Error: Unable to open socket.");
	}

	// Get the list of devices - only gets 20
	memset(&conf, 0, sizeof(conf));
	conf.ifc_len = sizeof(struct ifreq) * 20;
	conf.ifc_buf = (char*)malloc(conf.ifc_len);

	if(ioctl(sock, SIOCGIFCONF, &conf) == -1) {
	  throw SystemInfoProbeException("Error: Unable to get a device list.");
	}

	count = conf.ifc_len/sizeof(struct ifreq);
	for(int i = 0; i < count; i++) {
	  IfData *tmpIfData = new IfData();
	  s_in = (struct sockaddr_in*)&conf.ifc_req[i].ifr_addr;
	  tmpIfData->ifName = conf.ifc_req[i].ifr_name;
	  tmpIfData->ipAddress = inet_ntoa(s_in->sin_addr);

	  hwAddr = (struct sockaddr_in*)&conf.ifc_req[i].ifr_hwaddr;
	  tmpIfData->macAddress = inet_ntoa(hwAddr->sin_addr);

	  tmpIfData->macAddress = " UNKNOWN ";
	  
	  interfaces.push_back(tmpIfData);
	}

	free(conf.ifc_buf);	  
*/

	/* here is the test sample code i found on the net
	*/

	unsigned char      *u;
	int                sockfd, size  = 1;
	struct ifreq       *ifr;
	struct ifconf      ifc;
	struct sockaddr_in sa;

	if (0 > (sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP))) {
	  throw SystemInfoProbeException("Error: Unable to open socket.");
	}	

	ifc.ifc_len = IFRSIZE;
	ifc.ifc_req = NULL;

	do {
	++size;
	/* realloc buffer size until no overflow occurs  */

		if (NULL == (ifc.ifc_req = (ifreq*)realloc(ifc.ifc_req, IFRSIZE))) {
		  throw SystemInfoProbeException("Error: Unable to allocate mememory.");
		}
		ifc.ifc_len = IFRSIZE;
		if (ioctl(sockfd, SIOCGIFCONF, &ifc)) {
		  throw SystemInfoProbeException("Error: ioctl SIOCFIFCONF.");
		}
	} while  (IFRSIZE <= ifc.ifc_len);

	ifr = ifc.ifc_req;

	for (;(char *) ifr < (char *) ifc.ifc_req + ifc.ifc_len; ++ifr) {

		if (ifr->ifr_addr.sa_data == (ifr+1)->ifr_addr.sa_data) {
		  continue;  // duplicate, skip it
		}

		if (ioctl(sockfd, SIOCGIFFLAGS, ifr)) {
		  continue;  // failed to get flags, skip it
		}

		//printf("Interface:  %s\n", ifr->ifr_name);
		//printf("IP Address: %s\n", inet_ntoa(inaddrr(ifr_addr.sa_data)));
		IfData *tmpIfData = new IfData();
		tmpIfData->ifName = ifr->ifr_name;
		tmpIfData->ipAddress = inet_ntoa(inaddrr(ifr_addr.sa_data));

		if (0 == ioctl(sockfd, SIOCGIFHWADDR, ifr)) {

			/* Select which  hardware types to process.
			*
			*    See list in system include file included from
			*    /usr/include/net/if_arp.h  (For example, on
			*    Linux see file /usr/include/linux/if_arp.h to
			*    get the list.)
			*/
		  /* No used 
			switch (ifr->ifr_hwaddr.sa_family) {

				default:
					printf("\n");
					continue;
				case  ARPHRD_NETROM:  case  ARPHRD_ETHER:  case  ARPHRD_PPP:
				case  ARPHRD_EETHER:  case  ARPHRD_IEEE802: break;
			}
		  */

		  
			u = (unsigned char *) &ifr->ifr_addr.sa_data;
			char *macStr = (char*)malloc(sizeof(char*)*128);
			memset(macStr, 0, 128);
			if (u[0] + u[1] + u[2] + u[3] + u[4] + u[5]) {
			  //printf("HW Address: %2.2x.%2.2x.%2.2x.%2.2x.%2.2x.%2.2x\n", u[0], u[1], u[2], u[3], u[4], u[5]);
			  sprintf(macStr, "%2.2x.%2.2x.%2.2x.%2.2x.%2.2x.%2.2x", u[0], u[1], u[2], u[3], u[4], u[5]); 
			  tmpIfData->macAddress = macStr;
			}
			free(macStr);

			interfaces.push_back(tmpIfData);
		}

		/* netmask 
		if (0 == ioctl(sockfd, SIOCGIFNETMASK, ifr) && strcmp("255.255.255.255", inet_ntoa(inaddrr(ifr_addr.sa_data)))) {

			printf("Netmask:    %s\n", inet_ntoa(inaddrr(ifr_addr.sa_data)));
		}
		*/

		/* broadcast
		if (ifr->ifr_flags & IFF_BROADCAST) {

			if (0 == ioctl(sockfd, SIOCGIFBRDADDR, ifr) && strcmp("0.0.0.0", inet_ntoa(inaddrr(ifr_addr.sa_data)))) {
				printf("Broadcast:  %s\n", inet_ntoa(inaddrr(ifr_addr.sa_data)));
			}
		}
		*/
		
		/* MTU
		if (0 == ioctl(sockfd, SIOCGIFMTU, ifr)) {

			printf("MTU:        %u\n",  ifr->ifr_mtu);
		}
		*/

		/* Metric
		if (0 == ioctl(sockfd, SIOCGIFMETRIC, ifr)) {
			printf("Metric:     %u\n",  ifr->ifr_metric);
		}
		printf("\n");
		*/
	}

	close(sockfd);

	/* end sample code from the net
	*/

	return interfaces;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//
//~~~~~~~~~~~~~~~~~~~~~~~~  Class SystemInfoProbeException  ~~~~~~~~~~~~~~~~~~~~~~~~~//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//
SystemInfoProbeException::SystemInfoProbeException(string errMsgIn, int severity) : Exception(errMsgIn, severity)
{
	// Set the error message and then set the severity to ERROR_FATAL. This is done with
	// the explicit call to the Exception class constructor that takes a single string
	// param.
}

SystemInfoProbeException::~SystemInfoProbeException()
{
	// Do nothing for now
}
