/*****************************************************************************
 ** osr_service.cpp - osr service
 **
 ** Copyright (C) 2002 Rodrigo OSORIO. All rights reserved.
 **
 ** Redistribution and use in source and binary forms, with
 ** or  without  modification, are permitted  provided that 
 ** the following conditions are met:
 **
 ** 1. Redistributions of source code must retain the above 
 **    copyright notice, this  list of  conditions  and the 
 **    following disclaimer. 
 **    
 ** 2. 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. 
 **
 ** 3. Neither the name of this application 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 "stdafx.h"

#include "osr_service.h"
#include "osr_service_internals.h"

#define COUNTER_MAX_LOOP                         25

SERVICE_STATUS        OsrServiceStatus           = {0,0,0,0,0,0,0};
SERVICE_STATUS_HANDLE OsrServiceStatusHandle     = NULL;
U32BIT                OsrServiceAcceptCommands   = 0;
UCHAR                 OsrServiceName[STR_CHAR32] = {0};
U32BIT                OsrServiceStartFlag        = OSR_SERVICE_DONE;

/****************************************
 * Rodrigo Osorio - Creation
 * ServiceInstall function
 * This functions add the current application to the service manager list
 ****************************************/
OSR_ERROR OSR_CALL ServiceInstall(OSR_SERVICE_PARAMS * Params)
{
	char szServicePath[1024];
	SC_HANDLE schService = NULL;
	SC_HANDLE schSCManager = NULL;
	int i = 0,done = FALSE;
	OSR_ERROR problem = OSR_SUCCESS;
	char  * LocalLogin  = NULL,
		  * LocalPasswd = NULL;
	while((!done) && (!problem))
	{
		switch(i++)
		{
			case 0:
				if(Params == NULL)
					OSR_NULL_POINTER_ERROR;
				else
				{
					// Prevent string manipulation errors
					//*************************************************
					Params->ServiceDependencies[STR_CHAR64_ZERO-1] = 0;
					Params->ServiceDependencies[STR_CHAR64_ZERO]   = 0;
					Params->ServiceLogin[STR_CHAR32_ZERO]          = 0;
					Params->ServicePassword[STR_CHAR32_ZERO]       = 0;
					Params->ServiceName[STR_CHAR32_ZERO]           = 0;
					Params->ServiceNameDisplay[STR_CHAR32_ZERO]    = 0;

					if(strlen((char *)Params->ServiceName) == 0)
						problem = OSR_INVALID_PARAMETER;
					if(strlen((char *)Params->ServiceNameDisplay) == 0)
						problem = OSR_INVALID_PARAMETER;
					// If  request a Local System login mode
					//*************************************************
					if(strlen((char *)Params->ServiceLogin) != 0)
					{
						LocalLogin  = (char *)Params->ServiceLogin;
						LocalPasswd = (char *)Params->ServicePassword;
					}
				}
			break;

			case 1:
				/* Recover the service executable path */
				if(GetModuleFileNameA(NULL,szServicePath,1024)==0)
					problem = OSR_SYSTEM_ERROR;
			break;

			case 2:
				// Establish a connection with the service control manager
				//**********************************************************************
				schSCManager = OpenSCManager( NULL,                   // local machine
											  NULL,                   // local database
											  SC_MANAGER_ALL_ACCESS); // access required

				if(! schSCManager) problem = OSR_INVALID_HANDLE;
			break;

			case 3:
				// Create the service object
				//********************************************************************
				schService = CreateServiceA(
						schSCManager,						// service manager database
						(char *)Params->ServiceName,		// name of service
						(char *)Params->ServiceNameDisplay,	// name of service to display
						Params->ServiceDesiredAccess,		// desired access
						SERVICE_WIN32_OWN_PROCESS|
						SERVICE_INTERACTIVE_PROCESS ,			// service type
						Params->ServiceStartType,			// start type
						SERVICE_ERROR_NORMAL,				// error control type
						szServicePath,						// service binary path
						NULL,								// no load ordering group
						NULL,								// no tag identifier
						(char *)Params->ServiceDependencies,// list of services dependencies
						LocalLogin ,						// account
						LocalPasswd);						// password
				
				if(! schService) problem = OSR_INVALID_HANDLE;
			break;

			case 4:
				done = true;
			break;
		}

	}
	
	LOG_ERROR(i,problem);

	// Close the handles
	//*********************************
	if(schService)
		CloseServiceHandle(schService);

	if(schSCManager)
		CloseServiceHandle(schSCManager);
	
	return problem;
}


/****************************************
 * Rodrigo Osorio - Creation
 * ServiceUninstall function
 * This functions remove the current application from
 * the service manager list
 ****************************************/
OSR_ERROR OSR_CALL ServiceUninstall(OSR_SERVICE_PARAMS * Params)
{
	
	SC_HANDLE schService = NULL;
	SC_HANDLE schSCManager = NULL;
	int i = 0,done = FALSE;
	OSR_ERROR problem = OSR_SUCCESS;

	while((!done) && (!problem))
	{
		switch(i++)
		{
			case 0:
				if(Params == NULL)
					OSR_NULL_POINTER_ERROR;
				else
				{
					// Prevent string manipulation errors
					//*************************************************
					Params->ServiceDependencies[STR_CHAR64_ZERO-1] = 0;
					Params->ServiceDependencies[STR_CHAR64_ZERO]   = 0;
					Params->ServiceLogin[STR_CHAR32_ZERO]          = 0;
					Params->ServicePassword[STR_CHAR32_ZERO]       = 0;
					Params->ServiceName[STR_CHAR32_ZERO]           = 0;
					Params->ServiceNameDisplay[STR_CHAR32_ZERO]    = 0;

					if(strlen((char *)Params->ServiceName) == 0)
						problem = OSR_INVALID_PARAMETER;
					if(strlen((char *)Params->ServiceNameDisplay) == 0)
						problem = OSR_INVALID_PARAMETER;
					
				}
				
			break;
	

			case 2:
				// Establish a connection with the service control manager
				//*********************************************************
				schSCManager = OpenSCManager(
											NULL,                   // local machine
											NULL,                   // local database
											SC_MANAGER_ALL_ACCESS); // access required
				
				if(! schSCManager) problem = OSR_INVALID_HANDLE;
			break;
			
			case 3:
				
	
				schService = OpenServiceA(
							schSCManager,						// service manager database      
							(char *)Params->ServiceName,		// name of service               
							SERVICE_ALL_ACCESS);				// access required               

				if(! schService) problem = OSR_INVALID_HANDLE;
			break;


			case 4:
				// Stop the service
				//******************************************
				QueryServiceStatus(schService,&OsrServiceStatus);
				if(OsrServiceStatus.dwCurrentState != SERVICE_STOPPED)
				{
					ControlService(schService,SERVICE_CONTROL_STOP,&OsrServiceStatus);
					Sleep(1000);
					while(OsrServiceStatus.dwCurrentState == SERVICE_STOP_PENDING)
					{
						if(QueryServiceStatus(schService,&OsrServiceStatus))
							Sleep(1000);
						else break;
					}
					
					if(!(OsrServiceStatus.dwCurrentState == SERVICE_STOPPED))
						problem =  OSR_SYSTEM_ERROR;
				}
			break;

			case 5:
				// Delete the service 
				//***************************
				if(!DeleteService(schService))
				{
					switch(GetLastError())
					{
					case ERROR_ACCESS_DENIED:
						problem = OSR_UNINITIALIZED_HANDLE;
						SET_ERROR_MSG("Handle without delete access");
					break;
					
					case ERROR_INVALID_HANDLE:
						problem = OSR_INVALID_HANDLE;
					break;

					case ERROR_SERVICE_MARKED_FOR_DELETE:
						problem = OSR_ACCEPTABLE_ERROR;
						SET_ERROR_MSG("The specified service has already been marked for deletion.");
					break;

					default:
						problem = OSR_SYSTEM_ERROR;
					break;
					}
				}
			break;

			case 6:
				done = TRUE;
			break;
		}
	}

	LOG_ERROR(i,problem);

	// Close the handles
	//*********************************
	if(schService)
		CloseServiceHandle(schService);

	if(schSCManager)
		CloseServiceHandle(schSCManager);
	
	return problem;					
}


OSR_ERROR OSR_CALL IsServiceInstalled(UCHAR * ServiceName)
{
	
	SC_HANDLE schService = NULL;
	SC_HANDLE schSCManager = NULL;
	int i = 0,done = FALSE;
	OSR_ERROR problem = OSR_SUCCESS;

	while((!done) && (!problem))
	{
		switch(i++)
		{
			case 0:
				if(ServiceName == NULL)
					problem = OSR_NULL_POINTER_ERROR;
			break;

			case 1:
				// Establish a connection with the service control manager 
				//********************************************************
				schSCManager = OpenSCManager(
												NULL,					// local machine
												NULL,					// local database
												SC_MANAGER_ALL_ACCESS); // access require
				
				if(! schSCManager) problem = OSR_INVALID_HANDLE;
			break;
			
			case 2:
				schService = OpenServiceA(
												schSCManager,			// service manager database      
												(char *)ServiceName,	// name of service               
												SERVICE_ALL_ACCESS);	// access required
				if(schService) done = TRUE;	
				else problem = OSR_ACCEPTABLE_ERROR;
			break;
		}
	}

	LOG_ERROR(i,problem);

	// Close the handles
	//*********************************
	if(schService)
		CloseServiceHandle(schService);

	if(schSCManager)
		CloseServiceHandle(schSCManager);
	
	return problem;					
}


OSR_ERROR OSR_CALL ServiceReportStatus(DWORD ServiceCurrentState)
{
	static DWORD dwCheckPoint        = 1;
	OSR_ERROR problem = OSR_SUCCESS;

	/*initialize the globla status structure */
	OsrServiceStatus.dwServiceType  = SERVICE_WIN32_OWN_PROCESS;
	OsrServiceStatus.dwCurrentState = ServiceCurrentState;

	if( ServiceCurrentState == SERVICE_START_PENDING ||
		ServiceCurrentState == SERVICE_STOP_PENDING  ||
		ServiceCurrentState == SERVICE_PAUSE_PENDING ||
		ServiceCurrentState == SERVICE_CONTINUE_PENDING )
	{
		OsrServiceStatus.dwWaitHint   = OSR_SERVICE_WAIT_HINT ;
		OsrServiceStatus.dwCheckPoint = dwCheckPoint++;
		OsrServiceStatus.dwControlsAccepted = 0;
	}
	else
	{
		OsrServiceStatus.dwWaitHint   = 0;
		OsrServiceStatus.dwCheckPoint = 0;
		OsrServiceStatus.dwControlsAccepted = OsrServiceAcceptCommands;
	}

	
	OsrServiceStatus.dwWin32ExitCode           = OSR_SUCCESS; //0
	OsrServiceStatus.dwServiceSpecificExitCode = OSR_SUCCESS; //0

	
	// now report the status 
	//******************************************************
	if(!SetServiceStatus(OsrServiceStatusHandle,&OsrServiceStatus))
		problem = OSR_SYSTEM_ERROR;
	
	LOG_ERROR(0,problem);
	
	return problem;
}

/****************************************
 * Rodrigo Osorio - Creation
 * ServiceCtrlHandler function
 * This functions is called by the main service manager
 * to request service action and status
 ****************************************/
void WINAPI ServiceCtrlHandler(DWORD ServiceControlRequest)
{
	int RequestResult;

	switch(ServiceControlRequest)
	{
	case SERVICE_CONTROL_STOP:
		RequestResult = OnServiceStop();
		break;

	case SERVICE_CONTROL_PAUSE:
		RequestResult = OnServicePause();
		break;

	case SERVICE_CONTROL_CONTINUE:
		RequestResult = OnServiceContinue();
		break;

	case SERVICE_CONTROL_INTERROGATE:
		RequestResult = ServiceReportStatus(OsrServiceStatus.dwCurrentState);
		break; 

	case SERVICE_CONTROL_SHUTDOWN:
		RequestResult = OnServiceShutDown();
		break;
	}
	Sleep(1);
}



OSR_ERROR OSR_CALL OnServiceStart()
{
	int i = 0;
	int return_value;
	OSR_ERROR problem = OSR_SUCCESS;

	// Transmit the request to the application
	//*****************************************
	do
	{
		problem = ServiceReportStatus(SERVICE_START_PENDING);
		return_value = OSR_APP_START(i++);
	}	
	while(( return_value == OSR_SERVICE_WAIT ) && (!problem));

	// If the application return a regular status
	//********************************************
	if(return_value == OSR_SERVICE_DONE)
		problem = ServiceReportStatus(SERVICE_RUNNING);
	else
		problem = OSR_STATEMENT_OVERRUN;
	
	return problem;

}


OSR_ERROR OSR_CALL OnServiceStop()
{
	int i = 0;
	int return_value;
	OSR_ERROR problem = OSR_SUCCESS;

	// Transmit the request to the application
	//*****************************************
	do
	{
		problem = ServiceReportStatus(SERVICE_STOP_PENDING);
		return_value = OSR_APP_STOP(i++);
	}	
	while(( return_value == OSR_SERVICE_WAIT ) && (!problem));

	// If the application return a regular status
	//********************************************
	if(return_value == OSR_SERVICE_DONE)
		ServiceReportStatus(SERVICE_STOPPED);
	else
		problem = OSR_STATEMENT_OVERRUN;
	
	return problem;

}

OSR_ERROR OSR_CALL OnServicePause()
{
	int i = 0;
	int return_value;
	OSR_ERROR problem = OSR_SUCCESS;

	// Transmit the request to the application
	//****************************************
	do
	{
		problem = ServiceReportStatus(SERVICE_PAUSE_PENDING);
		return_value = OSR_APP_PAUSE(i++);
	}	
	while(( return_value == OSR_SERVICE_WAIT ) && (!problem));

	// If the application return a regular status
	//********************************************
	if(return_value == OSR_SERVICE_DONE)
		problem = ServiceReportStatus(SERVICE_PAUSED);
	else
		problem = OSR_STATEMENT_OVERRUN;
	
	return problem;
}

OSR_ERROR OSR_CALL OnServiceContinue()
{
	int i = 0;
	int return_value;
	OSR_ERROR problem = OSR_SUCCESS;

	// Transmit the request to the application
	//****************************************
	do
	{
		problem = ServiceReportStatus(SERVICE_CONTINUE_PENDING);
		return_value = OSR_APP_CONTINUE(i++);
	}	
	while(( return_value == OSR_SERVICE_WAIT ) && (!problem));

	// If the application return a regular status
	//********************************************
	if(return_value == OSR_SERVICE_DONE)
		problem = ServiceReportStatus(SERVICE_RUNNING);
	else
		problem = OSR_STATEMENT_OVERRUN;
	
	return problem;
}

OSR_ERROR OSR_CALL OnServiceShutDown()
{
	int i = 0;
	int return_value;
	OSR_ERROR problem = OSR_SUCCESS;

	// Transmit the request to the application
	//****************************************
	do
	{
		problem = ServiceReportStatus(SERVICE_STOP_PENDING);
		return_value = OSR_APP_SHUTDOWN(i++);
	}	
	while(( return_value == OSR_SERVICE_WAIT ) && (!problem));

	// If the application return a regular status
	//********************************************
	if(return_value == OSR_SERVICE_DONE)
		problem = ServiceReportStatus(SERVICE_STOPPED);
	else
		problem = OSR_STATEMENT_OVERRUN;
	
	return problem;	
}


VOID WINAPI OsrServiceMainFunction(DWORD dwArgc,LPTSTR *lpszArgv)
{
	OSR_ERROR problem = OSR_SUCCESS;
	int i = 0;

	OsrServiceStatusHandle = RegisterServiceCtrlHandlerA((char *)OsrServiceName,ServiceCtrlHandler);
	if(OsrServiceStatusHandle == (SERVICE_STATUS_HANDLE)0)
	{
		problem = OSR_SYSTEM_ERROR;
		OsrServiceStartFlag = OSR_SERVICE_ERROR;
	}
	else
	{
		OsrServiceStartFlag = OSR_SERVICE_DONE;
	}

	LOG_ERROR(0,problem);
	
	if(problem)
		exit(problem);		
}


OSR_ERROR OSR_CALL ServiceStart(OSR_SERVICE_PARAMS * Params)
{
	OSR_ERROR problem = OSR_SUCCESS;
	int i = 0, done = FALSE;
	int counter = 0;
	DWORD ThreadId = NULL;

	while(!problem && !done)
	{
		switch(i++)
		{
		case 0:
			if(Params == NULL)
					OSR_NULL_POINTER_ERROR;
				else
				{
					// Prevent string manipulation errors
					//*************************************************
					Params->ServiceDependencies[STR_CHAR64_ZERO-1] = 0;
					Params->ServiceDependencies[STR_CHAR64_ZERO]   = 0;
					Params->ServiceLogin[STR_CHAR32_ZERO]          = 0;
					Params->ServicePassword[STR_CHAR32_ZERO]       = 0;
					Params->ServiceName[STR_CHAR32_ZERO]           = 0;
					Params->ServiceNameDisplay[STR_CHAR32_ZERO]    = 0;

					if(strlen((char *)Params->ServiceName) == 0)
						problem = OSR_INVALID_PARAMETER;
					if(strlen((char *)Params->ServiceNameDisplay) == 0)
						problem = OSR_INVALID_PARAMETER;
					
				}
		break;

		case 1:
			strcpy_s((char *)OsrServiceName,STR_CHAR32,(char *)Params->ServiceName);
			OsrServiceAcceptCommands = Params->ServiceAcceptedCommands;
			OsrServiceStartFlag = OSR_SERVICE_WAIT;
		break;

		case 2:
			// Launch the thread responsable of the service messages dispatch
			//****************************************************************
			if(CreateThread(NULL,0,OsrStartServiceDiapatcher,NULL,0,NULL) == NULL)
			{
				problem = OSR_SYSTEM_ERROR;
			}
		break;

		case 3:
			// Reset the counter
			//*****************************************************************
			counter = 0;
			OsrServiceStartFlag = OSR_SERVICE_WAIT;

			// Wait for a responte or fall in time out
			//*****************************************************************
			while((OsrServiceStartFlag == OSR_SERVICE_WAIT) && (counter < COUNTER_MAX_LOOP))
			{
				// Wait 500 ms
				//*************************************************************
				Sleep(500);
				counter++;
			}

			switch (OsrServiceStartFlag)
			{
			case OSR_SERVICE_DONE:
				// No problem
				//**************************************************************
			break;

			case OSR_SERVICE_WAIT:
				// Time out detected
				//**************************************************************
				problem = OSR_TIMEOUT;
			break;

			case OSR_SERVICE_ERROR:
			
				// This is an error
				//**************************************************************
				problem = OSR_SYSTEM_ERROR;
			}

		break;

		case 4:
			problem = OnServiceStart();
		break;

		case 5:
			done = TRUE;
		break;

		}		
	}

	LOG_ERROR(i,problem);

	return problem;
}

DWORD WINAPI OsrStartServiceDiapatcher(LPVOID lpParameter)
{
	SERVICE_TABLE_ENTRY ServiceTableEntry[2];

	ServiceTableEntry[0].lpServiceName = (char *)OsrServiceName;
	ServiceTableEntry[0].lpServiceProc = OsrServiceMainFunction;

	ServiceTableEntry[1].lpServiceName = NULL;
	ServiceTableEntry[1].lpServiceProc = NULL;

	if(!StartServiceCtrlDispatcher(ServiceTableEntry))
  {
    DWORD dwError = GetLastError();
    if (dwError == ERROR_FAILED_SERVICE_CONTROLLER_CONNECT)
      printf("Service start error: ERROR_FAILED_SERVICE_CONTROLLER_CONNECT");
    else
    if (dwError == ERROR_INVALID_DATA)
      printf("Service start error: ERROR_INVALID_DATA");
    else
    if (dwError == ERROR_SERVICE_ALREADY_RUNNING)
      printf("Service start error: ERROR_SERVICE_ALREADY_RUNNING");
    else
      printf("Service start error: Unknown error");
		OsrServiceStartFlag = OSR_SERVICE_ERROR;
  }

	return 0;
}