/*
 *
 * Copyright (C) 1997 Magnus Hagander <mha@solnet.sollentuna.se>
 *
 * You can find the latest version of the program at:
 *   ftp://ftp.des.sollentuna.se/pub/utils/dessvc.exe
 * or the source at
 *   ftp://ftp.des.sollentuna.se/pub/utils/source/dessvc.cpp
 * and finally the usage instructions, which may very well be out of date on
 *   http://www.des.sollentuna.se/dessvc.html
 *
 * Permission to compile and distribute yourself. 
 * If you make any modifications, chose another name and clearly state
 * that it is not the official version. Also state that it is based on
 * this code (C) 1997 Magnus Hagander.
 *
 * Note that this was a fast hack, to answer a huge demand for a simple
 * program. 
 * Therefor, it does not follow all the neat specifications on how to 
 * write nice code. It also does not do range-checking and validity-checking 
 * at all the places it should.
 * However, and I see this as a big point, it works.
 *
 * If you want to add functions/features to this program, I ask you to
 * please submit a patch to me instead of releasing your own version of 
 * it. Naturally, credit will be awarded to those who help out.
 *
 *
 * The piping in here would not have been done unless I had the help from
 * "MarkG's Win32 Programming Page", http://markg.pptnet.com
 *
 * The service framework is also originating from a webpage, but I no longer
 * have the address for that page available. 
 *
 * -----------------------------------------------------------------------
 * The above text is the original one, only minor textual changes are made 
 * by me.. darth@vader.dk, 19980401
 */

#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <direct.h>					//Required to do _chdir()

static SERVICE_STATUS_HANDLE serviceStatusHandle;
static HANDLE hThreadEvent;
static HANDLE killServiceEvent;
static int serviceCurrentStatus;

static char workdir[1024];
static char commandline[1024];
static char logfile[1024];


/*
 * Stop the current service
 */
void KillService(void) {
	SetEvent(hThreadEvent);
	Sleep(2000);
	SetEvent(killServiceEvent);
}

/*
 * Run a program <cmd> and pipe the output to the file <fn>
 */
bool PipeRun(char *cmd, char *fn) {
   STARTUPINFO si;
   PROCESS_INFORMATION pi;
   HANDLE hInputRead, hInputWrite;
   HANDLE hOutputRead, hOutputWrite;
   HANDLE hDupInputWrite;
   HANDLE hDupOutputRead;
   BOOL bStatus;
   DWORD dwRead, dwAvail, dwMsg;
   char szInput[ 1024 ];

   /*
    * Check if the file can be opened. If not, we bail out with error
	*/
   FILE *f = fopen(fn,"a+");
   if (!f) 
	   return false;
   fclose(f);


   memset( &si, 0, sizeof(STARTUPINFO) );             //  Initialize structures
   memset( &pi, 0, sizeof(PROCESS_INFORMATION) );
										//  Create pipe for console -> file data
   bStatus = CreatePipe( &hInputRead, &hInputWrite, NULL, 1024 );
   if( ! bStatus )
      return( FALSE );
                                        //  Create pipe for file -> console data
   bStatus = CreatePipe( &hOutputRead, &hOutputWrite, NULL, 1024 );
   if( ! bStatus )
   {
      CloseHandle( hInputRead );           //  Close first pipe if second fails
      CloseHandle( hInputWrite );
      return( FALSE );
   }
                                   //  Make an inheritable pipe endpoint handle
   DuplicateHandle( GetCurrentProcess(), hInputWrite,
                    GetCurrentProcess(), &hDupInputWrite, 0L, TRUE,
                    DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS );
                                            //  and a second for GUI -> console
   DuplicateHandle( GetCurrentProcess(), hOutputRead,
                    GetCurrentProcess(), &hDupOutputRead, 0L, TRUE,
                    DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS );

   si.cb = sizeof(STARTUPINFO);
   si.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES;
   si.wShowWindow = SW_HIDE;        //  Don't show the console window (DOS box)
   si.hStdOutput = hDupInputWrite;             //  Redirect command line to GUI
   si.hStdError = hDupInputWrite;
   si.hStdInput = hDupOutputRead;  //  and send stuff from here to command line

   bStatus = CreateProcess(NULL,cmd,NULL,NULL,TRUE,0,NULL,NULL,&si,&pi);

   CloseHandle( hDupInputWrite );               //  Close duplicates so GUI end
   CloseHandle( hDupOutputRead );                         //  can use the pipes

   if( ! bStatus )
   {
      CloseHandle( hInputRead );
      CloseHandle( hInputWrite );
      CloseHandle( hOutputRead );
      CloseHandle( hOutputWrite );
      return( FALSE );
   }
                     //  Loop until either the window closure sets the event or
   int result;
   do {
	   HANDLE hArray[3] = {hThreadEvent,hInputRead,pi.hProcess}; 

	   result = WaitForMultipleObjects(3,hArray,false,INFINITE);
	   if (result == WAIT_OBJECT_0)
		   break;		//This event is set if the service should stop, so quit looping

	   if (result == WAIT_OBJECT_0+2) {
		   pi.hProcess = NULL;
			f = fopen(fn,"a+");
			fprintf(f,"Child program terminated!\n");
			fclose(f);

		   break;
	   }
	   if (result == WAIT_OBJECT_0+1) {

		   //Yep, something coming in on the pipe
		   //However, this seems to happen when things are not coming in on the pipe too :-(
	       //  If there's anything to read
			if( PeekNamedPipe( hInputRead, szInput, 1024, &dwRead,
								&dwAvail, &dwMsg )  && dwAvail > 0)
			{                                               //  Read it into a buffer
				ReadFile( hInputRead, szInput, dwAvail, &dwRead, NULL );
				szInput[dwRead] = 0x00;
    			f = fopen(fn,"a+");
				fprintf(f,"%s",szInput);
				fclose(f);
			}
			Sleep(500); //If not, we get a runaway using 100% CPU :-(
	   }
   } while (result != WAIT_OBJECT_0);
	
   /*
    * Write shutdown data to the log
	*/
   f = fopen(fn,"a+");
   fprintf(f,"Service shutting down!\n");	
   fclose(f);

   if( pi.hProcess )                     //  If the dos box is running, kill it
      TerminateProcess( pi.hProcess, 0 );

   CloseHandle( hInputWrite );
   CloseHandle( hOutputRead );
   return( TRUE );                                      //  Back to thread wrapper
}


/*
 * Simple wrapper for the working thread 
 */
DWORD WINAPI WorkerThread(LPDWORD param) {
   _chdir(workdir);
   PipeRun(commandline,logfile);
   KillService();
   return 0;
}

/*
 * An even simplier wrapper that just starts the working thread
 */
void StartServiceThread(void) {
	DWORD dwd;
	CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)WorkerThread,NULL,0,&dwd);
}


/*
 * Update the service status (in the SCM)
 */
BOOL UpdateSCMStatus (DWORD dwCurrentState,
                      DWORD dwWin32ExitCode,
                      DWORD dwServiceSpecificExitCode,
                      DWORD dwCheckPoint,
                      DWORD dwWaitHint)
{
   BOOL success;
   SERVICE_STATUS serviceStatus;
   // Fill in all of the SERVICE_STATUS fields
   serviceStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS; //We don't want to mess up the system
   serviceStatus.dwCurrentState = dwCurrentState;
   // If in the process of something, then accept
   // no control events, else accept anything
   if (dwCurrentState == SERVICE_START_PENDING)
   {
      serviceStatus.dwControlsAccepted = 0;
   }
   else
   {
      serviceStatus.dwControlsAccepted =
         SERVICE_ACCEPT_STOP |
         SERVICE_ACCEPT_SHUTDOWN;
   }
   // if a specific exit code is defines, set up
   // the Win32 exit code properly
   if (dwServiceSpecificExitCode == 0)
   {
      serviceStatus.dwWin32ExitCode = dwWin32ExitCode;
   }
   else
   {
      serviceStatus.dwWin32ExitCode = ERROR_SERVICE_SPECIFIC_ERROR;
   }
   serviceStatus.dwServiceSpecificExitCode =   dwServiceSpecificExitCode;
   serviceStatus.dwCheckPoint = dwCheckPoint;
   serviceStatus.dwWaitHint = dwWaitHint;
   // Pass the status record to the SCM
   success = SetServiceStatus (serviceStatusHandle, &serviceStatus);
   if (!success)
   {
	   /*
	    * Ouch. We couldn't update the status. Well. Better exit, then
		*/
      KillService();
   }
   return success;
}

/*
 * Signal we are down
 */
void terminateService (int code, int wincode) {
   UpdateSCMStatus(SERVICE_STOPPED,wincode?wincode:ERROR_SERVICE_SPECIFIC_ERROR,(wincode)?0:code,0,0);
   return;
}

//   Handles the events dispatched by the Service Control Manager.
VOID ServiceCtrlHandler (DWORD controlCode)
{
   switch(controlCode)
   {
      // There is no START option because
      // ServiceMain gets called on a start
      // Update the current status for the SCM.
      case SERVICE_CONTROL_INTERROGATE:
         // This does nothing, here we will just fall through to the end
         // and send our current status.
         break;
      // For a shutdown, we can do cleanup but it must take place quickly
      // because the system will go down out from under us.
      // For this app we have time to stop here, which I do by just falling
      // through to the stop message.
      case SERVICE_CONTROL_SHUTDOWN:
      // Stop the service
      case SERVICE_CONTROL_STOP:
         // Tell the SCM we're about to Stop.
         serviceCurrentStatus = SERVICE_STOP_PENDING;
         UpdateSCMStatus(SERVICE_STOP_PENDING, NO_ERROR, 0, 1, 5000);
         KillService();
         UpdateSCMStatus(SERVICE_STOPPED,NO_ERROR,0,0,0);
         return;
      default:
          break;
   }
   UpdateSCMStatus(serviceCurrentStatus, NO_ERROR, 0, 0, 0);
}


VOID ServiceMain(DWORD argc, LPTSTR *argv)
{
   BOOL success;
   HKEY MyKey;
   DWORD type=0,size=0;


   // First we must call the Registration function
   serviceStatusHandle = RegisterServiceCtrlHandler("DancerSvc",
                           (LPHANDLER_FUNCTION) ServiceCtrlHandler);
   if (!serviceStatusHandle)
   {
      terminateService(1,GetLastError());
      return;
   }
   // Next Notify the Service Control Manager of progress
   success = UpdateSCMStatus(SERVICE_START_PENDING, NO_ERROR, 0, 1, 1000);
   if (!success)
   {
      terminateService(2,GetLastError());
      return;
   }

   killServiceEvent = CreateEvent(NULL,true,false,NULL);
   hThreadEvent = CreateEvent(NULL,true,false,NULL);

   if (!killServiceEvent || !hThreadEvent) {
	   terminateService(99,GetLastError());
	   return;
   }

   //Now open the registry key, query values and close it
   if (RegOpenKeyEx(HKEY_LOCAL_MACHINE,"SYSTEM\\CurrentControlSet\\Services\\DancerSvc",NULL,KEY_QUERY_VALUE,&MyKey) != ERROR_SUCCESS) {
      //Umm. No good!
      terminateService(3,GetLastError());
      return;
   }
   size = sizeof(workdir);

   if (RegQueryValueEx(MyKey,"WorkDir",NULL,&type,(unsigned char *)workdir,&size) != ERROR_SUCCESS) {
      //Umm. No good!
      terminateService(4,GetLastError());
      return;
   }

   size = sizeof(commandline);
   if (RegQueryValueEx(MyKey,"Command",NULL,&type,(unsigned char *)commandline,&size) != ERROR_SUCCESS) {
      //Umm. No good!
      terminateService(5,GetLastError());
      return;
   }

   size = sizeof(logfile);
   if (RegQueryValueEx(MyKey,"Logfile",NULL,&type,(unsigned char *)logfile,&size) != ERROR_SUCCESS) {
      //F**
      terminateService(6,GetLastError());
      return;
   }
	
   RegCloseKey(MyKey);

   success = UpdateSCMStatus(SERVICE_START_PENDING, NO_ERROR, 0, 2, 1000);
   if (!success)
   {
      terminateService(2,GetLastError());
      return;
   }

   // Start the service execution thread by calling our StartServiceThread function...
   StartServiceThread();
   // The service is now running.  Notify the SCM of this fact.
   serviceCurrentStatus = SERVICE_RUNNING;
   success = UpdateSCMStatus(SERVICE_RUNNING, NO_ERROR, 0, 0, 0);
   if (!success)
   {
      terminateService(6,GetLastError());
      return;
   }
   // Now just wait for our killed service signal, and then exit, which
   // terminates the service!
   WaitForSingleObject (killServiceEvent, INFINITE);
//   success = UpdateSCMStatus(SERVICE_STOPPED, NO_ERROR, 0, 0, 0);
}

/*
 * Misc functions - not actual part of the service!
 */

/*
 * Install the service in the SCM
 */
void InstallService(void) {
   SC_HANDLE myService, scm;

   char modname[256];
   GetModuleFileName(NULL,modname,sizeof(modname));

   
   printf("Installing service...\n");

   scm = OpenSCManager(0,0,SC_MANAGER_CREATE_SERVICE);
   if (!scm) {
      printf("Failed to open Service Control Manager! (Error code %i)\n",GetLastError());
      return;
   }

   myService = CreateService(scm,
          "DancerSvc",												//Internal service name
          "Dancer IRC Bot service",							//Show name
          SERVICE_ALL_ACCESS,									//We want full control
          SERVICE_WIN32_OWN_PROCESS,						//Let's not mess it up for somebody else..
          SERVICE_DEMAND_START,								//The service requires manual start
          SERVICE_ERROR_NORMAL,								//Normal handling when error in startup
          modname,												//Binary file
          0,0,0,0,0);											//Misc :)

   if (!myService) {
      printf("Failed to create the service! (Error code %i)\n",GetLastError());
      CloseServiceHandle(scm);
      return;
   }

   printf("Service successfully installed.\nYou may now start it manually, or set it for automatic startup.\n");
   CloseServiceHandle(myService);
   CloseServiceHandle(scm);
}

/*
 * Remove the service from the SCM
 */
void RemoveService(void) {
   SC_HANDLE myService, scm;

   printf("Removing service...\n");

   scm = OpenSCManager(0,0,SC_MANAGER_CREATE_SERVICE);
   if (!scm) {
      printf("Failed to open Service Control Manager! (Error code %i)\n",GetLastError());
      return;
   }

   myService = OpenService(scm,"DancerSvc",SERVICE_ALL_ACCESS);
   if (!myService) {
      printf("Failed to open service! (Error code %i)\n",GetLastError());
      CloseServiceHandle(scm);
      return;
   }

   if (!DeleteService(myService)) {
      printf("Failed to delete service! (Error code %i)\n",GetLastError());
      CloseServiceHandle(myService);
      CloseServiceHandle(scm);
      return;
   }

   CloseServiceHandle(myService);
   CloseServiceHandle(scm);

   printf("Service successfully removed.\n");
}


/*
 * Update the configuration entries in the registry
 */
void ConfigureService(void) {
	char wd[1024];memset(wd,0,sizeof(wd));
	char cmd[1024];memset(cmd,0,sizeof(cmd));
	char lf[1024];memset(lf,0,sizeof(lf));
	HKEY MyKey;
    DWORD Disposition = 0;

	printf("Configuring service.\n\n");
	printf("Enter working directory:");
	gets(wd);
	printf("Enter command line, including program name:");
	gets(cmd);
	printf("Enter name of log file:");
	gets(lf);

   if (
      RegCreateKeyEx(HKEY_LOCAL_MACHINE,
                     "SYSTEM\\CurrentControlSet\\Services\\DancerSvc",
                     NULL, //Reserved
                     NULL, //Class
                     REG_OPTION_NON_VOLATILE,
                     KEY_ALL_ACCESS,
                     NULL, //Security attributes :)
                     &MyKey,
                     &Disposition)
      != ERROR_SUCCESS) {
         printf("Failed to open registry key!\n");
         return;
      }
   if (
       RegSetValueEx(MyKey,"WorkDir",NULL,REG_SZ,(unsigned char *)wd,strlen(wd)+1)
      != ERROR_SUCCESS) {
         printf("Failed to write working directory to registry!\n");
         RegCloseKey(MyKey);
         return;
      }
   if (
       RegSetValueEx(MyKey,"Command",NULL,REG_SZ,(unsigned char *)cmd,strlen(cmd)+1)
      != ERROR_SUCCESS) {
         printf("Failed to write command line to registry!\n");
         RegCloseKey(MyKey);
         return;
      }
   if (
       RegSetValueEx(MyKey,"LogFile",NULL,REG_SZ,(unsigned char *)lf,strlen(lf)+1)
      != ERROR_SUCCESS) {
         printf("Failed to write logfile name to registry!\n");
         RegCloseKey(MyKey);
         return;
      }

   RegCloseKey(MyKey);
   printf("Configuration successfully saved.\n");
}


/*
 * Guess...
 */
void Usage(void) {
   printf("Usage:\n\n"
          "   DancerSvc -install         To install service\n"
          "   DancerSvc -remove          To remove service\n"
          "   DancerSvc -conf            To configure service\n");
}

/*
 * main()
 *
 * As suspected, this is the entrypoint in the program :-)
 *
 * If no parameters are given, assume that we are started by the SCM,
 *    and there for start the Service Control Dispatcher.
 * If one parameter is given, it is checked against the allowed ones. 
 *    If one of them, the appropriate actionn is taken.
 * If wrong number of parameters or an incorrect parameter is given,
 *    the usage instructions are shown.
 *
 * May be a dirty way to find out if we are a service or not, but who cares
 */
int main(int argc, char *argv[]) {
   switch(argc) {
      case 1: {
            SERVICE_TABLE_ENTRY serviceTable[]= { {"DancerSvc", (LPSERVICE_MAIN_FUNCTION)ServiceMain},{NULL,NULL}};
            StartServiceCtrlDispatcher(serviceTable);
            }
            break;
      case 2:
            if (!stricmp(argv[1],"-install"))
               InstallService();
            else if (!stricmp(argv[1],"-remove"))
               RemoveService();
            else if (!strnicmp(argv[1],"-conf",5))
               ConfigureService();
            else
               Usage();
            break;
      default:
      		Usage();
   }
   return 0;
}


syntax highlighted by Code2HTML, v. 0.9.1