
/******************************************************************************
**
**  This program is free software; you can redistribute it and/or
**  modify it, however, you cannot sell it.
**
**  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.
**
**  You should have received a copy of the license attached to the
**  use of this software.  If not, visit www.shmoo.com/osiris for
**  details.
**
******************************************************************************/

/*****************************************************************************
**
**  File:    windows_service.c
**  Date:    August 7, 2002
**
**  Author:  Brian Wotring
**  Purpose: install and control this daemon as a service.
**
*****************************************************************************/

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

#include "osi_common.h"
#include "windows_service.h"


/* global flag used to signal the need to stop  */
/* the main event loop for the service.         */

HANDLE stop_server_event = NULL;

/* internal variables */

SERVICE_STATUS          status;
SERVICE_STATUS_HANDLE   status_handle;
DWORD                   error = 0;
TCHAR                   error_message[256];

HANDLE                  events[2] = { NULL, NULL };

/* internal function prototypes */

VOID WINAPI service_ctrl( DWORD dwCtrlCode );
VOID WINAPI service_main( DWORD dwArgc, LPTSTR *lpszArgv );

BOOL WINAPI ControlHandler ( DWORD dwCtrlType );
LPTSTR GetLastErrorText( LPTSTR lpszBuf, DWORD dwSize );



/******************************************************************************
**
**    Function: service_main
**
**    Purpose:  To perform actual initialization of the service.
**
******************************************************************************/

void WINAPI service_main( DWORD argc, LPTSTR *argv )
{

   /* register our service control handler: */

    status_handle = RegisterServiceCtrlHandler( TEXT( SERVICE_NAME ),
                                                service_ctrl );

    if( !status_handle )
    {
        goto cleanup;
    }

    status.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
    status.dwServiceSpecificExitCode = 0;

    /* report the status to the service control manager. */

    if( !ReportStatusToSCMgr( SERVICE_START_PENDING, NO_ERROR, 3000 ) )
    {
	    goto cleanup;
    }

    service_start( argc, argv );
    cleanup:

    /* try to report the stopped status to the service control manager. */

    if( status_handle )
    {
        ReportStatusToSCMgr( SERVICE_STOPPED, error, 0 );
    }

   return;
}


/*****************************************************************************
**
**    Function: service_ctrl
**
**    Purpose:  This function is called by the SCM whenever
**          	ControlService() is called on this service.
**
******************************************************************************/

VOID WINAPI service_ctrl( DWORD dwCtrlCode )
{
    /* Handle the requested control code. */

    switch (dwCtrlCode)
    {

        case SERVICE_CONTROL_STOP:

            ReportStatusToSCMgr( SERVICE_STOP_PENDING, NO_ERROR, 0);
            service_stop();
            return;

        /* update the service status. */

        case SERVICE_CONTROL_INTERROGATE:
            break;

        /* invalid control code */

        default:
            break;
    }

    ReportStatusToSCMgr( status.dwCurrentState, NO_ERROR, 0 );
}


/******************************************************************************
**
**    Function: ReportStatusToSCMgr
**
**    Purpose:  sets the current status of the service and
**           	reports it to the Service Control Manager
**
******************************************************************************/

BOOL ReportStatusToSCMgr( DWORD dwCurrentState, DWORD dwWin32ExitCode,
                          DWORD dwWaitHint )
{
    static DWORD dwCheckPoint = 1;
    BOOL result = TRUE;

    if( dwCurrentState == SERVICE_START_PENDING )
    {
         status.dwControlsAccepted = 0;
    }

    else
    {
         status.dwControlsAccepted = SERVICE_ACCEPT_STOP;
    }

    status.dwCurrentState  = dwCurrentState;
    status.dwWin32ExitCode = dwWin32ExitCode;
    status.dwWaitHint      = dwWaitHint;

    if ( ( dwCurrentState == SERVICE_RUNNING ) ||
         ( dwCurrentState == SERVICE_STOPPED ) )
    {
         status.dwCheckPoint = 0;
    }

    else
    {
         status.dwCheckPoint = dwCheckPoint++;
    }

      /* Report the status of the service to the service control manager. */

    if( !( result = SetServiceStatus( status_handle, &status ) ) )
    {
        AddToMessageLog( TEXT( "SetServiceStatus" ) );
    }

    return result;
}



/******************************************************************************
**
**    Function: AddToMessageLog
**
**    Purpose:  allows any thread to log an error message.
**
******************************************************************************/

VOID AddToMessageLog(LPTSTR lpszMsg)
{
    TCHAR   szMsg[256];
    HANDLE  event_source;
    LPTSTR  lpszStrings[2];

    error = GetLastError();

      /* use event logging to log the error. */

    event_source = RegisterEventSource( NULL, TEXT(SERVICE_NAME) );
    _stprintf( szMsg, TEXT( "%s error: %d" ), TEXT(SERVICE_NAME), error );

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

    if( event_source != NULL)
    {
	    ReportEvent( event_source, 	       /* 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 */

        DeregisterEventSource( event_source );
    }
}



/******************************************************************************
**
**    Function: ControlHandler
**
**    Purpose:  handled console control events.
**
******************************************************************************/

BOOL WINAPI ControlHandler ( DWORD dwCtrlType )
{
    switch ( dwCtrlType )
    {
        case CTRL_BREAK_EVENT:
        case CTRL_C_EVENT:
            _tprintf(TEXT("stopping %s.\n"), TEXT(SERVICE_DISPLAY_NAME));
            service_stop();
            return TRUE;
            break;
    }

    return FALSE;
}


/******************************************************************************
**
**    Function: GetLastErrorText
**
**    Purpose:  copies error message text to string.
**
******************************************************************************/

LPTSTR 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 );

    if( !dwRet || ( (long)dwSize < (long)dwRet+14 ) )
    {
        lpszBuf[0] = TEXT('\0');
    }

    else
    {
        lpszTemp[lstrlen(lpszTemp)-2] = TEXT('\0'); 
        _stprintf( lpszBuf, TEXT("%s (0x%x)"), lpszTemp, GetLastError() );
    }

    if( lpszTemp )
    {
        LocalFree( (HLOCAL)lpszTemp );
    }

    return lpszBuf;
}

/******************************************************************************
**
**    Function: install_windows_service
**
**    Purpose:  install this daemon as a service on the local machine.
**
******************************************************************************/

void install_windows_service()
{
    SC_HANDLE   schService;
    SC_HANDLE   schSCManager;
    DWORD err_code;

    TCHAR szPath[512];

    if( GetModuleFileName( NULL, szPath, 512 ) == 0 )
    {
        fprintf( stderr, "unable to install %s - %s\n",
                 TEXT(SERVICE_DISPLAY_NAME),
                 GetLastErrorText( error_message, 256 ) );
        return;
    }

    schSCManager = OpenSCManager( NULL, NULL, SC_MANAGER_ALL_ACCESS );

    if( schSCManager )
    {
        schService = CreateService(
                schSCManager,
                TEXT(SERVICE_NAME),
                TEXT(SERVICE_DISPLAY_NAME),
                SERVICE_ALL_ACCESS | SERVICE_INTERACTIVE_PROCESS,
                SERVICE_WIN32_OWN_PROCESS,
                SERVICE_AUTO_START,
                SERVICE_ERROR_NORMAL,
                szPath,
                NULL,                       /* no load ordering group */
                NULL,                       /* no tag identifier */
                TEXT(SERVICE_DEPENDENCIES),
                NULL,                       /* localSystem account */
                NULL);                      /* no password */

        if( schService )
        {
            fprintf( stderr, "%s installed.\n", TEXT(SERVICE_DISPLAY_NAME) );

            if( ( StartService( schService, 0, NULL ) ) == 0 )
            {
                err_code = GetLastError();
                fprintf( stderr, TEXT("error code  = %lx\n"), err_code );
            }

            fprintf( stderr, "%s started.\n", TEXT(SERVICE_DISPLAY_NAME ) );
            CloseServiceHandle( schService );
        }

        else
        {
            fprintf( stderr, "CreateService failed - %s\n",
                     GetLastErrorText( error_message, 256 ) );
        }

        CloseServiceHandle( schSCManager );
    }

    else
    {
        fprintf( stderr, "OpenSCManager failed - %s\n",
                 GetLastErrorText( error_message, 256 ) );
    }
}

/******************************************************************************
**
**    Function: uninstall_windows_service
**
**    Purpose:  remove this daemon as a service from the local machine.
**
******************************************************************************/

void uninstall_windows_service()
{
    SC_HANDLE   schService;
    SC_HANDLE   schSCManager;

    schSCManager = OpenSCManager( NULL, NULL, SC_MANAGER_ALL_ACCESS );

    if( schSCManager )
    {
        schService = OpenService( schSCManager,
                                  TEXT(SERVICE_NAME),
                                  SERVICE_ALL_ACCESS );

        /* first stop the service. */

        if( schService )
        {
            if( ControlService( schService, SERVICE_CONTROL_STOP, &status ) )
            {
                fprintf( stderr, "stopping %s.", TEXT(SERVICE_DISPLAY_NAME));
                Sleep( 1000 );

                while( QueryServiceStatus( schService, &status ) )
                {
                    if( status.dwCurrentState == SERVICE_STOP_PENDING )
                    {
                        fprintf( stderr, "." );
                        Sleep( 1000 );
                    }

                    else
                    {
                        break;
                    }
                }

                if( status.dwCurrentState == SERVICE_STOPPED )
                {
                    fprintf( stderr, "\n%s stopped.\n",
                             TEXT(SERVICE_DISPLAY_NAME) );
                }

                else
                {
                    fprintf( stderr, "\n%s failed to stop.\n",
                             TEXT(SERVICE_DISPLAY_NAME) );
                }
            }

            /* now remove the service */

            if( DeleteService( schService ) )
            {
                fprintf( stderr, "%s removed.\n", TEXT(SERVICE_DISPLAY_NAME) );
            }

            else
            {
                fprintf( stderr, "DeleteService failed - %s\n",
                         GetLastErrorText( error_message, 256 ) );
            }

            CloseServiceHandle( schService );
        }

        else
        {
            fprintf( stderr, "OpenService failed - %s\n",
                     GetLastErrorText(error_message,256));
        }

        CloseServiceHandle(schSCManager);
    }

    else
    {
        fprintf( stderr, "OpenSCManager failed - %s\n",
                 GetLastErrorText( error_message,256 ) );
    }
}


void service_start( DWORD dwArgc, LPTSTR *lpszArgv )
{
    DWORD stop_wait;
    HANDLE thread_handle     = 0;
    unsigned int thread_pid  = 0;

   /* report the status to the service control manager. */

    if( !ReportStatusToSCMgr( SERVICE_START_PENDING, NO_ERROR, 3000 ) )
    {
        goto cleanup;
    }

    /* create the event object. The control handler function signals */
    /* this event when it receives the "stop" control code.	         */

    stop_server_event = CreateEvent( NULL, TRUE, FALSE, NULL );

    if( stop_server_event == NULL )
    {
        goto cleanup;
    }

    events[0] = stop_server_event;

    /* report the status to the service control manager. */

    if( !ReportStatusToSCMgr( SERVICE_START_PENDING, NO_ERROR, 3000 ) )
    {
        goto cleanup;
    }

    events[1] = CreateEvent( NULL, TRUE, FALSE, NULL );

    if( events[1] == NULL )
    {
        goto cleanup;
    }

    /* report the status to the service control manager. */

    if( !ReportStatusToSCMgr( SERVICE_START_PENDING, NO_ERROR, 3000 ) )
    {
        goto cleanup;
    }

    /* report the status to the service control manager. */

    if( !ReportStatusToSCMgr( SERVICE_START_PENDING, NO_ERROR, 3000 ) )
    {
        goto cleanup;
    }

    /* report the status to the service control manager. */

    if( !ReportStatusToSCMgr( SERVICE_RUNNING, NO_ERROR, 0 ) )
    {
        goto cleanup;
    }

    thread_handle = (HANDLE)_beginthreadex( NULL, 0, &run, NULL, 0,
                                            &thread_pid );

    do
    {
        stop_wait = WaitForMultipleObjects( 2, events, FALSE, INFINITE );

    } while( stop_wait == ( WAIT_OBJECT_0 + 1 ) );

cleanup:

    if( stop_server_event )
    {
        CloseHandle( stop_server_event );
    }

    if( events[1] )
    {
      CloseHandle( events[1] );
    }
}

/******************************************************************************
**
**    Function: service_stop
**
**    Purpose:  issue the stop service event.
**
******************************************************************************/

void service_stop()
{
    if( stop_server_event )
    {
        SetEvent( stop_server_event );
    }
}

