/******************************************************************************
fgexec - 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.
******************************************************************************/
// fgexec.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include "Process.h"

#define SERVICE_NAME	"fgexec"
#define BUFFER_SIZE		65535
#define PIPE_FORMAT		"\\\\%s\\pipe\\fgpipe"
#define PIPE_TIMEOUT	1000

SERVICE_STATUS_HANDLE g_hServiceStatus;
DWORD g_dwState = -1;
HANDLE hStopEvent = NULL;

bool ExecuteCommand(const char* lpszCommand, char* lpszParams, int nTimeout, char** lpszData, int nDataSize)
{
	bool bResult = false;

	try
	{
		Process p;

		HANDLE hProcess = p.CreateProcess(lpszCommand, lpszParams);
		if (hProcess != 0)
		{
			DWORD dwResult = WaitForSingleObject(hProcess, nTimeout);	// Wait for process to complete
			if (dwResult != WAIT_OBJECT_0)
			{
				bResult = false;
			}
			else
			{
				// Read from process's output
				p.ReadFromPipe(lpszData, nDataSize);
				bResult = true;
			}
		}
		else
			bResult = false;
	}
	catch(...)
	{
		bResult = false;
	}

	return bResult;
}

void WINAPI ControlHandler(DWORD dwControl)
{
	SERVICE_STATUS ss = { SERVICE_WIN32_OWN_PROCESS, SERVICE_RUNNING, SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN, 
						  NO_ERROR, 0, 0, 0 };

	if (g_dwState == dwControl)
		return;

	switch(dwControl)
	{
		case SERVICE_CONTROL_STOP:
			g_dwState = dwControl;
			SetEvent(hStopEvent);
			break;
		case SERVICE_CONTROL_PAUSE:
		case SERVICE_CONTROL_CONTINUE:
			g_dwState = dwControl;
			break;
		case SERVICE_CONTROL_SHUTDOWN:
			break;
		default:
			SetServiceStatus(g_hServiceStatus, &ss);
	}

}

void WINAPI ServiceMain(DWORD argc, LPTSTR argv[])
{
	bool bConnected; 
    LPTSTR lpszPipename = "\\\\.\\pipe\\fgpipe"; 
    CHAR chRequest[MAX_PATH]; 
    DWORD cbBytesRead, cbTotalBytes, cbBytesLeft; 
    bool bSuccess; 
	char* szData = new char[BUFFER_SIZE];
	char* szArguments = NULL;
    HANDLE hPipe; 
	SERVICE_STATUS ss = { SERVICE_WIN32_OWN_PROCESS, SERVICE_START_PENDING, 0, NO_ERROR, 0, 1, 5000 };
	
	hStopEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
	
	g_hServiceStatus = RegisterServiceCtrlHandler(SERVICE_NAME, ControlHandler);
	if (!g_hServiceStatus)
	{
		delete szData;
		return;
	}

	if (!SetServiceStatus(g_hServiceStatus, &ss))
	{
		delete szData;
		return;
	}

	ss.dwCheckPoint++;
	if (!SetServiceStatus(g_hServiceStatus, &ss))
	{
		delete szData;
		return;
	}

    hPipe = CreateNamedPipe(lpszPipename, 
                            PIPE_ACCESS_DUPLEX, // read/write access 
                            PIPE_TYPE_MESSAGE | // message type pipe 
                            PIPE_READMODE_MESSAGE | // message-read mode 
                            PIPE_NOWAIT, // non-blocking mode 
                            PIPE_UNLIMITED_INSTANCES, // max. instances 
                            MAX_PATH, // output buffer size 
                            MAX_PATH, // input buffer size 
                            1000, // client time-out 
                            NULL); // no security attribute 

    if (hPipe == INVALID_HANDLE_VALUE) 
	{
		delete szData;
        return;
	}
	
	ss.dwCheckPoint = 0;
	ss.dwCurrentState = SERVICE_RUNNING;
	ss.dwControlsAccepted = SERVICE_ACCEPT_STOP;
	if (!SetServiceStatus(g_hServiceStatus, &ss))
	{
		delete szData;
		return;
	}

    bConnected = ConnectNamedPipe(hPipe, NULL) ? true : (GetLastError() == ERROR_PIPE_CONNECTED || GetLastError() == ERROR_PIPE_LISTENING); 

    while(1 && bConnected) 
    { 
		if (WaitForSingleObject(hStopEvent, 1000) == WAIT_OBJECT_0)
		{
			ss.dwCheckPoint = 1;
			ss.dwCurrentState = SERVICE_STOP_PENDING;
			ss.dwWaitHint = 2000;
			SetServiceStatus(g_hServiceStatus, &ss);
			break;		// Stop the service now
		}

        if (bConnected) 
        { 
			memset(chRequest, 0, MAX_PATH);
			bSuccess = PeekNamedPipe(hPipe, chRequest, MAX_PATH, &cbBytesRead, &cbTotalBytes, &cbBytesLeft);
			if (bSuccess && cbBytesRead > 0)
			{
				chRequest[cbBytesRead] = '\0';
				FlushFileBuffers(hPipe); 

				// Try to execute the command
				::ZeroMemory(szData, BUFFER_SIZE);

				// See if there is a "||" sequence in the command - if so, it separates the arguments and the command
				if (strstr(chRequest, "||") != NULL)
				{
					szArguments = strstr(chRequest, "||");
					*szArguments = '\0';	// Make this the end of the string
					szArguments += 2;
				}
				if (ExecuteCommand(chRequest, szArguments, 120000, &szData, BUFFER_SIZE)) // 2 minute timeout
				{
					WriteFile(hPipe, szData, strlen(szData), &cbTotalBytes, NULL);
				}
				else
				{
					char szError[256];
					::ZeroMemory(szError, 256);
					_snprintf(szError, 256, "Exec failed, GetLastError returned %d\n", GetLastError());
					WriteFile(hPipe, szError, strlen(szError), &cbTotalBytes, NULL);
				}

				FlushFileBuffers(hPipe); 
				DisconnectNamedPipe(hPipe);
				bConnected = ConnectNamedPipe(hPipe, NULL) ? true : (GetLastError() == ERROR_PIPE_CONNECTED || GetLastError() == ERROR_PIPE_LISTENING); 
			}
        }
    } 

    CloseHandle(hPipe); 
	CloseHandle(hStopEvent);

	ss.dwCurrentState = SERVICE_STOPPED;
	SetServiceStatus(g_hServiceStatus, &ss);
	delete szData;
}

int RunClient(char* szServer, char* szCommand, char* szArguments)
{
	HANDLE hFile;
	BOOL flg;
	DWORD dwWrite;
	TCHAR chBuf[BUFSIZE]; 
	BOOL fSuccess; 
	DWORD cbRead, dwMode;
	char szPipeName[MAX_PATH];
	char szOutputBuffer[2 * MAX_PATH];

	::ZeroMemory(szPipeName, MAX_PATH);
	::ZeroMemory(szOutputBuffer, 2 * MAX_PATH);
	_snprintf(szPipeName, MAX_PATH, PIPE_FORMAT, szServer);
	if (szArguments == NULL)
        _snprintf(szOutputBuffer, 2 * MAX_PATH, "%s", szCommand);
	else
        _snprintf(szOutputBuffer, 2 * MAX_PATH, "%s||%s", szCommand, szArguments);

	hFile = CreateFile(szPipeName, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
	if(hFile == INVALID_HANDLE_VALUE)
	{ 
		printf("fgexec CreateFile failed to create a new client-side pipe\n" );
		return -1;
	}
	else
	{
		dwMode = PIPE_READMODE_MESSAGE; 
		fSuccess = SetNamedPipeHandleState(hFile, &dwMode, NULL, NULL); 
		if (!fSuccess) 
		{
			printf("SetNamedPipeHandleState failed\n"); 
			return -1;
		}

		flg = WriteFile(hFile, szOutputBuffer, strlen(szOutputBuffer), &dwWrite, NULL);
		if (FALSE == flg)
		{
			printf("WriteFile failed on client-side\n");
			return -1;
		}

		do 
		{ 
			::ZeroMemory(chBuf, BUFSIZE);
			fSuccess = ReadFile(hFile, chBuf, BUFSIZE, &cbRead, NULL); 
			if (!fSuccess && GetLastError() != ERROR_MORE_DATA) 
				break; 

			_tprintf( TEXT("%s\n"), chBuf ); 

		} while (!fSuccess);  // repeat loop if ERROR_MORE_DATA 
 
		CloseHandle(hFile);
	}

	return 0;
}

int _tmain(int argc, _TCHAR* argv[])
{
	if (argc > 1)
	{
		if (strcmp(argv[1], "/?") == 0)
		{
			printf("fgexec Remote Process Execution Tool v1.0\n");
			printf("fizzgig and the mighty foofus.net team\n\n");
			printf("Usage:\n\n");
			printf("%s\n", argv[0]);
			printf("\tRun as a service (use this on the target machine)\n\n");
			printf("%s target command [arguments]\n", argv[0]);
			printf("\tRun as a client, causing command to be executed on target.\n\tThe FULL PATH (including extension) must be passed for the command.\n");
			printf("\n");
			return -1;
		}
	}

	if (argc == 1)
	{
		// Run as a service
		SERVICE_TABLE_ENTRY svcTable[] = { { _T(SERVICE_NAME), ServiceMain }, { NULL, NULL } };

		if (!StartServiceCtrlDispatcher(svcTable))
			return -1;

		return 0;
	}
	else
	{
		// Run as a client
		if (argc >= 4)
		{
			char szParams[MAX_PATH];
			::ZeroMemory(szParams, MAX_PATH);

			for (int i = 3; i < argc; i++)
			{
				strcat(szParams, " ");
				strcat(szParams, argv[i]);
			}
			
			return RunClient(argv[1], argv[2], szParams);
		}
		else
			return RunClient(argv[1], argv[2], NULL);
	}


}

