///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Winnov L.P., 1996.  All rights reserved
// shell.c: CShell class implementation
///////////////////////////////////////////////////////////////////////////////

// This class is a generic command processor for a windows NT Service.
// The generic service is implemented seperately by the CService class.
// Only a single instance of the CShell class is permitted in a process.

#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <process.h>
#include <tchar.h>

#include "srvcname.h"
#include "debug.h"
#include "shell.h"
#include "service.h"
#include "resource.h"

/////////////////////////////////////////////////////////////////////////
// internal static 

VOID WINAPI Static_service_ctrl(DWORD dwCtrlCode);
VOID WINAPI Static_service_main(DWORD dwArgc, LPTSTR *lpszArgv);
BOOL WINAPI Static_ControlHandler ( DWORD dwCtrlType );
BOOL Static_ReportStatusToSCMgr(DWORD dwCurrentState,
                         DWORD dwWin32ExitCode,
                         DWORD dwWaitHint);

SERVICE_STATUS_HANDLE   Static_sshStatusHandle;
DWORD	                Static_dwErr = 0;
BOOL                    Static_bDebug;
DWORD			Static_dwLastStateReported = 0;
DWORD			Static_dwCheckPoint = 1;

/////////////////////////////////////////////////////////////////////////


//#############################################################################
//#############  Begin Non-instanced (Static_XXX) local routines ##############
//#############################################################################

///////////////////////////////////////////////////////////////////////////////
// Function: Static_AddToMessageLog
//
// Purpose: Log an error message
//
// Parameter descriptions:
//
// lpszMsg: Pointer to message text to be logged.
//
// return: none
//
///////////////////////////////////////////////////////////////////////////////

VOID Static_AddToMessageLog (UINT nErr)
{
    TCHAR   szMsg[256];
    HANDLE  hEventSource;
    LPCTSTR  lpszStrings[2];
    TCHAR szErr [256];


    if ( !Static_bDebug )
    {
        Static_dwErr = GetLastError();

        // Use event logging to log the error.
        //
        hEventSource = RegisterEventSource(NULL, TEXT(SZSERVICENAME));

        _stprintf(szMsg, TEXT("%s error: %d"), TEXT(SZSERVICENAME), Static_dwErr);

	LoadString (GetModuleHandle (NULL), nErr, szErr, sizeof (szErr));

        lpszStrings[0] = szMsg;
        lpszStrings[1] = szErr;

        if (hEventSource != NULL) {
            ReportEvent(hEventSource, // handle of event source
                EVENTLOG_ERROR_TYPE,  // event type
                0,                    // event category
                0,                    // event ID
                NULL,                 // current user's SID
                2,                    // strings in lpszStrings
                0,                    // no bytes of raw data
                lpszStrings,	      // array of error strings
                NULL);                // no raw data

            (VOID) DeregisterEventSource(hEventSource);
        }
    }
}

///////////////////////////////////////////////////////////////////////////////
// Function: Static_ServiceStart
//
// Purpose: Start the service
//
// Parameter descriptions: same as "main"
//
// return: none
//
// comments:
// service start is funneled thru this routine so that only
// a single instance of the service object can exist.
//
///////////////////////////////////////////////////////////////////////////////

CService *gpService = NULL;

void Static_ServiceStart( int nArgc, LPTSTR *lpszArgv )
{
    DWORD dwResult;

    gpService = new CService (Static_ReportStatusToSCMgr);

    dwResult = gpService->Start (nArgc, lpszArgv);
    if (dwResult)
    {
	SetLastError (dwResult);
	Static_AddToMessageLog (IDS_STARTERROR);
    }

    delete gpService;
    gpService = NULL;
}

///////////////////////////////////////////////////////////////////////////////
// Function: Static_ServiceStop
//
// Purpose: Stop the service
//
// Parameter descriptions: None
//
// return: none
//
///////////////////////////////////////////////////////////////////////////////

void Static_ServiceStop (void)
{
    if (gpService)
	gpService->Stop ();
}

///////////////////////////////////////////////////////////////////////////////
// Function: Static_ReportStatusToSCMgr
//
// Purpose: Report the current status to the service manager.
//
// Parameter descriptions:
//
// dwCurrentState: New state of service.
// dwWin32ExitCode: Error code
// dwWaitHint: Timeout for next checkpoint
//
// return: TRUE if successful
//
///////////////////////////////////////////////////////////////////////////////

BOOL Static_ReportStatusToSCMgr (
    DWORD dwCurrentState,
    DWORD dwWin32ExitCode,
    DWORD dwWaitHint)
{
    BOOL fResult = TRUE;
    SERVICE_STATUS          ssStatus;       // current status of the service

    // SERVICE_STATUS members that don't change in example
    //
    ssStatus.dwServiceType 
	= SERVICE_WIN32_OWN_PROCESS;
    ssStatus.dwServiceSpecificExitCode = 0;

    if ( !Static_bDebug ) // when debugging we don't report to the SCM
    {
#if 0
        if (dwCurrentState == SERVICE_START_PENDING)
            ssStatus.dwControlsAccepted = 0;
        else
#endif
            ssStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN;

        ssStatus.dwCurrentState = dwCurrentState;
        ssStatus.dwWin32ExitCode = dwWin32ExitCode;
        ssStatus.dwWaitHint = dwWaitHint;

        if ( ( dwCurrentState == SERVICE_RUNNING ) ||
             ( dwCurrentState == SERVICE_STOPPED ) )
            ssStatus.dwCheckPoint = 0;
        else
            ssStatus.dwCheckPoint = Static_dwCheckPoint++;

        // Report the status of the service to the service control manager.
        //
        if (!(fResult = SetServiceStatus( Static_sshStatusHandle, &ssStatus))) 
	{
            Static_AddToMessageLog (IDS_SETFAIL);
        }
	Static_dwLastStateReported = ssStatus.dwCurrentState;
    }
    return fResult;
}

///////////////////////////////////////////////////////////////////////////////
// Function: Static_service_main
//
// Purpose: Initialize the service.
//
// Parameter descriptions: same as "main"
//
// return: None
//
// Comments:
//   This routine is not a member of the CShell Class.
//   This is mainly due to the fact that there is no way to
//   pass the class instance to the routine.
///////////////////////////////////////////////////////////////////////////////

void WINAPI Static_service_main (DWORD dwArgc, LPTSTR *lpszArgv)
{
    // register our service control handler:
    //
    Static_sshStatusHandle = RegisterServiceCtrlHandler ( 
	TEXT(SZSERVICENAME), 
	Static_service_ctrl);

    if (!Static_sshStatusHandle)
    {
	Static_AddToMessageLog (IDS_REGISTERFAIL);
        goto cleanup;
    }

    // report the status to the service control manager.
    //
    if (!Static_ReportStatusToSCMgr(
        SERVICE_START_PENDING, // service state
        NO_ERROR,              // exit code
        1000))                 // wait hint
    {
	Static_AddToMessageLog (IDS_REPORTFAIL);
        goto cleanup;
    }

    Static_ServiceStart ((int)dwArgc, lpszArgv);

cleanup:

    // try to report the stopped status to the service control manager.
    //
    if (Static_sshStatusHandle)
	Static_ReportStatusToSCMgr (
	    SERVICE_STOPPED,
            Static_dwErr,
            100);
}

///////////////////////////////////////////////////////////////////////////////
// Function: Static_ControlHandler
//
// Purpose: Console event handler
//
// Parameter descriptions:
//
// dwCtrlType: event type
//
// return: TRUE if event is handled.
//
///////////////////////////////////////////////////////////////////////////////

BOOL WINAPI Static_ControlHandler ( DWORD dwCtrlType )
{
    switch( dwCtrlType )
    {
        case CTRL_BREAK_EVENT:  // use Ctrl+C or Ctrl+Break to simulate
        case CTRL_C_EVENT:      // SERVICE_CONTROL_STOP in debug mode
            _tprintf(TEXT("Stopping %s.\n"), TEXT(SZSERVICEDISPLAYNAME));
            Static_ServiceStop();
            return TRUE;
            break;

    }
    return FALSE;
}

///////////////////////////////////////////////////////////////////////////////
// Function: Static_service_ctrl
//
// Purpose: ControlService () from SCM handler
//
// Parameter descriptions:
//
// dwCtrlType: Control request type.
//
// return: None
//
///////////////////////////////////////////////////////////////////////////////

VOID WINAPI Static_service_ctrl (DWORD dwCtrlCode)
{
    // Handle the requested control code.
    //
    switch (dwCtrlCode)
    {
        // Stop the service.
        //
        case SERVICE_CONTROL_STOP:
	case SERVICE_CONTROL_SHUTDOWN:
	    // SERVICE_STATUS members that don't change in example
	    //
	    TraceString ("\nStatic_service_ctrl: SERVICE_STOP_PENDING received");
	    Static_ReportStatusToSCMgr (SERVICE_STOP_PENDING, NO_ERROR, 1000);
            Static_ServiceStop ();
            break;

        // Update the service status.
        //
        case SERVICE_CONTROL_INTERROGATE:
	    Static_ReportStatusToSCMgr (Static_dwLastStateReported, NO_ERROR, 0);
            break;

        // invalid control code
        //
        default:
	    Static_ReportStatusToSCMgr (Static_dwLastStateReported, NO_ERROR, 0);
            break;
    }
}

//#############################################################################
//######################  Begin CShell PRIVATE Methods #########################
//#############################################################################

///////////////////////////////////////////////////////////////////////////////
// Private Method: GetLastErrorText
//
// Purpose: Convert last error to text string
//
// Parameter descriptions:
//
// lpszBuf: destination for text string
//
// dwSize: size of destinaton buffer
//
// return: Pointer to destination buffer
//
///////////////////////////////////////////////////////////////////////////////

LPTSTR CShell::GetLastErrorText( LPTSTR lpszBuf, DWORD dwSize )
{
    DWORD dwRet;
    LPTSTR lpszTemp = NULL;

    dwRet = FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |FORMAT_MESSAGE_ARGUMENT_ARRAY,
                           NULL,
                           GetLastError(),
                           LANG_NEUTRAL,
                           (LPTSTR)&lpszTemp,
                           0,
                           NULL );

    // supplied buffer is not long enough
    if ( !dwRet || ( (long)dwSize < (long)dwRet+14 ) )
        lpszBuf[0] = TEXT('\0');
    else
    {
        lpszTemp[lstrlen(lpszTemp)-2] = TEXT('\0');  //remove cr and newline character
        _stprintf( lpszBuf, TEXT("%s (0x%x)"), lpszTemp, GetLastError() );
    }

    if ( lpszTemp )
        LocalFree((HLOCAL) lpszTemp );

    return lpszBuf;
}

//#############################################################################
//######################  Begin CShell PUBLIC Methods #########################
//#############################################################################

///////////////////////////////////////////////////////////////////////////////
// Method: CShell
//
// Purpose: Constructor
//
// Parameter descriptions: None
//
// return: None
//
///////////////////////////////////////////////////////////////////////////////

CShell::CShell ()
{
    Static_bDebug = FALSE;
}

///////////////////////////////////////////////////////////////////////////////
// Method: ~CShell
//
// Purpose: Destructor
//
// Parameter descriptions: None
//
// return: None
//
///////////////////////////////////////////////////////////////////////////////

CShell::~CShell ()
{
}

///////////////////////////////////////////////////////////////////////////////
// Method: CmdInstallService
//
// Purpose: Install the service
//
// Parameter descriptions: None
//
// return: None
//
///////////////////////////////////////////////////////////////////////////////

void CShell::CmdInstallService (void)
{
    SC_HANDLE   schService;
    SC_HANDLE   schSCManager;

    TCHAR szPath[512];

    if ( GetModuleFileName( NULL, szPath, 512 ) == 0 )
    {
        _tprintf(TEXT("Unable to install %s - %s\n"), TEXT(SZSERVICEDISPLAYNAME), GetLastErrorText(szErr, 256));
        return;
    }

    schSCManager = OpenSCManager(
	NULL,                   // machine (NULL == local)
        NULL,                   // database (NULL == default)
        SC_MANAGER_ALL_ACCESS); // access required
    if (schSCManager)
    {
        schService = CreateService(
            schSCManager,               // SCManager database
            TEXT(SZSERVICENAME),        // name of service
            TEXT(SZSERVICEDISPLAYNAME), // name to display
            SERVICE_ALL_ACCESS,         // desired access
            SERVICE_WIN32_OWN_PROCESS,  // service type
            SERVICE_DEMAND_START,       // start type
            SERVICE_ERROR_NORMAL,       // error control type
            szPath,                     // service's binary
            NULL,                       // no load ordering group
            NULL,                       // no tag identifier
            TEXT(SZDEPENDENCIES),       // dependencies
            NULL,                       // LocalSystem account
            NULL);                      // no password

        if ( schService )
        {
            _tprintf(TEXT("%s installed.\n"), TEXT(SZSERVICEDISPLAYNAME) );
            CloseServiceHandle(schService);
        }
        else
        {
            _tprintf(TEXT("CreateService failed - %s\n"), GetLastErrorText(szErr, 256));
        }

        CloseServiceHandle(schSCManager);
    }
    else
        _tprintf(TEXT("OpenSCManager failed - %s\n"), GetLastErrorText(szErr,256));
}

///////////////////////////////////////////////////////////////////////////////
// Method: CmdRemoveService
//
// Purpose: Stop the service and remove it.
//
// Parameter descriptions: None
//
// return: None
//
///////////////////////////////////////////////////////////////////////////////

void CShell::CmdRemoveService (void)
{
    SC_HANDLE   schService;
    SC_HANDLE   schSCManager;
    SERVICE_STATUS          ssStatus;       // current status of the service

    schSCManager = OpenSCManager(
	NULL,                   // machine (NULL == local)
        NULL,                   // database (NULL == default)
        SC_MANAGER_ALL_ACCESS); // access required
    if (schSCManager)
    {
        schService = OpenService(schSCManager, TEXT(SZSERVICENAME), SERVICE_ALL_ACCESS);

        if (schService)
        {
            // try to stop the service
            if (ControlService (schService, SERVICE_CONTROL_STOP, &ssStatus ))
            {
                _tprintf(TEXT("Stopping %s."), TEXT(SZSERVICEDISPLAYNAME));
                Sleep (1000);

                while (QueryServiceStatus( schService, &ssStatus))
                {
                    if (ssStatus.dwCurrentState == SERVICE_STOP_PENDING)
                    {
                        _tprintf(TEXT("."));
                        Sleep (1000);
                    }
                    else
                        break;
                }

                if ( ssStatus.dwCurrentState == SERVICE_STOPPED )
                    _tprintf(TEXT("\n%s stopped.\n"), TEXT(SZSERVICEDISPLAYNAME) );
                else
                    _tprintf(TEXT("\n%s failed to stop.\n"), TEXT(SZSERVICEDISPLAYNAME) );

            }

            // now remove the service
            if( DeleteService(schService) )
                _tprintf(TEXT("%s removed.\n"), TEXT(SZSERVICEDISPLAYNAME) );
            else
                _tprintf(TEXT("DeleteService failed - %s\n"), GetLastErrorText(szErr,256));


            CloseServiceHandle(schService);
        }
        else
            _tprintf(TEXT("OpenService failed - %s\n"), GetLastErrorText(szErr,256));

        CloseServiceHandle(schSCManager);
    }
    else
        _tprintf(TEXT("OpenSCManager failed - %s\n"), GetLastErrorText(szErr,256));
}

///////////////////////////////////////////////////////////////////////////////
// Method: CmdDebugService
//
// Purpose: Run the service as a console application for debugging.
//
// Parameter descriptions: same as "main"
//
// return: None
//
///////////////////////////////////////////////////////////////////////////////

void CShell::CmdDebugService(int argc, char ** argv)
{
    int nArgc;
    LPTSTR *lpszArgv;

    Static_bDebug = TRUE;

#ifdef UNICODE
    lpszArgv = CommandLineToArgvW(GetCommandLineW(), &(nArgc) );
#else
    nArgc   = argc;
    lpszArgv = argv;
#endif

    _tprintf(TEXT("Debugging %s.\n"), TEXT(SZSERVICEDISPLAYNAME));

    SetConsoleCtrlHandler( Static_ControlHandler, TRUE );

    Static_ServiceStart( (int)nArgc, lpszArgv );
}

///////////////////////////////////////////////////////////////////////////////
// Method: CmdStartService
//
// Purpose: Service Entrypoint..
//
// Parameter descriptions:
//
// argc: number of command line parameters
//
// argv: array of pointer to command line parameters
//
// return: None
//
///////////////////////////////////////////////////////////////////////////////

void CShell::CmdStartService (void)
{
    SERVICE_TABLE_ENTRY dispatchTable[] =
    {
        { TEXT(SZSERVICENAME), (LPSERVICE_MAIN_FUNCTION)Static_service_main },
        { NULL, NULL }
    };

    if (!StartServiceCtrlDispatcher(dispatchTable))
    {
	Static_AddToMessageLog (IDS_STARTFAIL);
    }
}



