/* Copyright (C) 2008 Andreas Baumann 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 3 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, see . */ #include "service/service.h" #include "log/log.h" /* for logging */ #include "log/messages.h" /* for i18n */ #include "i18n/gettext.h" /* for i18n */ #include /* for service functions */ static SERVICE_STATUS_HANDLE service_status_handle; static SERVICE_STATUS service_status; static HANDLE service_stop_event = NULL; static LPSERVICE_MAIN_FUNCTION service_service_main = NULL; LPWOLF_SERVICE_SUSPEND_FUNCTION wolf_service_events_suspend = NULL; #define SERVICE_NAME "testservice" wolf_error_t wolf_service_install( wolf_service_params_t params ) { SC_HANDLE scm; SC_HANDLE service; char errbuf[512]; DWORD res; TCHAR binary_path[MAX_PATH]; BOOL rt; SERVICE_DESCRIPTION descr; wolf_log( WOLF_LOG_DEBUG, WOLF_CATEGORY_SERVICE, WOLF_MSG_SERVICE_INSTALLING_SERVICE, _( "Installing service '%s'" ), params.service_name ); /* get service control manager with enough rights on the local * machine to install the service */ scm = (SC_HANDLE)OpenSCManager( NULL, /* local computer */ SERVICES_ACTIVE_DATABASE, SC_MANAGER_ALL_ACCESS ); if( scm == NULL ) { wolf_system_error_msg( errbuf, 512 ); wolf_log( WOLF_LOG_ERR, WOLF_CATEGORY_SERVICE, WOLF_MSG_SERVICE_CANT_OPEN_SCM_FOR_INSTALL, _( "Unable to open the service control manager to register service '%s': %s (%d)" ), params.service_name, errbuf, GetLastError( ) ); return WOLF_ERR_INTERNAL; } /* Get the full path of the service binary */ res = GetModuleFileName( NULL, /* current process */ binary_path, MAX_PATH ); if( res == MAX_PATH ) { wolf_system_error_msg( errbuf, 512 ); wolf_log( WOLF_LOG_ERR, WOLF_CATEGORY_SERVICE, WOLF_MSG_SERVICE_CANT_GET_PATH_OF_BINARY, _( "Unable to get full path of the binary of service '%s': %s (%d)" ), params.service_name, errbuf, GetLastError( ) ); CloseServiceHandle( scm ); return WOLF_ERR_INTERNAL; } /* create the service */ service = CreateService( scm, params.service_name, params.service_display_name, SERVICE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS, /* run isolated in its own process */ SERVICE_DEMAND_START, /* let the user choose the start type */ SERVICE_ERROR_NORMAL, /* report error and continue boot */ binary_path, NULL, /* order group */ NULL, /* tag in the order group */ NULL, /* service dependencies */ NULL, /* Local System Account */ NULL ); if( service == NULL ) { wolf_system_error_msg( errbuf, 512 ); wolf_log( WOLF_LOG_ERR, WOLF_CATEGORY_SERVICE, WOLF_MSG_SERVICE_CANT_CREATE_SERVICE, _( "Unable to create service '%s': %s (%d)" ), params.service_name, errbuf, GetLastError( ) ); (void)CloseServiceHandle( scm ); return WOLF_ERR_INTERNAL; } /* set description of the service */ descr.lpDescription = (LPTSTR)params.service_description; rt = ChangeServiceConfig2( service, SERVICE_CONFIG_DESCRIPTION, &descr ); if( !rt ) { wolf_system_error_msg( errbuf, 512 ); wolf_log( WOLF_LOG_ERR, WOLF_CATEGORY_SERVICE, WOLF_MSG_SERVICE_CANT_SET_SERVICE_DESCRIPTION, _( "Unable to set description of service '%s': %s (%d)" ), params.service_name, errbuf, GetLastError( ) ); (void)CloseServiceHandle( service ); (void)CloseServiceHandle( scm ); return WOLF_ERR_INTERNAL; } (void)CloseServiceHandle( service ); (void)CloseServiceHandle( scm ); wolf_log( WOLF_LOG_DEBUG, WOLF_CATEGORY_SERVICE, WOLF_MSG_SERVICE_SERVICE_INSTALLED, _( "Service '%s' installed successfully" ), params.service_name ); return WOLF_OK; } wolf_error_t wolf_service_remove( LPCTSTR service_name ) { SC_HANDLE scm; char errbuf[512]; SC_HANDLE service; DWORD res; wolf_log( WOLF_LOG_DEBUG, WOLF_CATEGORY_SERVICE, WOLF_MSG_SERVICE_UNINSTALLING_SERVICE, _( "Unistalling service '%s'" ), service_name ); /* get service control manager with enough rights on the local * machine to uninstall the service */ scm = (SC_HANDLE)OpenSCManager( NULL, /* local computer */ SERVICES_ACTIVE_DATABASE, SC_MANAGER_ALL_ACCESS ); if( scm == NULL ) { wolf_system_error_msg( errbuf, 512 ); wolf_log( WOLF_LOG_ERR, WOLF_CATEGORY_SERVICE, WOLF_MSG_SERVICE_CANT_OPEN_SCM_FOR_UNINSTALL, _( "Unable to open the service control manager to uninstall '%s': %s (%d)" ), service_name, errbuf, GetLastError( ) ); return WOLF_ERR_INTERNAL; } /* open the service handle and delete the service */ service = OpenService( scm, service_name, SERVICE_ALL_ACCESS ); if( service == NULL ) { wolf_system_error_msg( errbuf, 512 ); wolf_log( WOLF_LOG_ERR, WOLF_CATEGORY_SERVICE, WOLF_MSG_SERVICE_CANT_OPEN_SERVICE, _( "Unable to open service '%s': %s (%d)" ), service_name, errbuf, GetLastError( ) ); (void)CloseServiceHandle( scm ); return WOLF_ERR_INTERNAL; } res = DeleteService( service ); if( res == 0 ) { wolf_system_error_msg( errbuf, 512 ); wolf_log( WOLF_LOG_ERR, WOLF_CATEGORY_SERVICE, WOLF_MSG_SERVICE_CANT_DELETE_SERVICE, _( "Unable to uninstall service '%s': %s (%d)" ), service_name, errbuf, GetLastError( ) ); (void)CloseServiceHandle( service ); (void)CloseServiceHandle( scm ); return WOLF_ERR_INTERNAL; } (void)CloseServiceHandle( service ); (void)CloseServiceHandle( scm ); wolf_log( WOLF_LOG_DEBUG, WOLF_CATEGORY_SERVICE, WOLF_MSG_SERVICE_SERVICE_UNINSTALLED, _( "Service '%s' uninstalled successfully" ), service_name ); return WOLF_OK; } /* constants and values from WinSvc.h: */ static const char *wolf_service_state_to_str( DWORD current_state ) { switch( current_state ) { case SERVICE_STOPPED: return "SERVICE_STOPPED"; case SERVICE_START_PENDING: return "SERVICE_START_PENDING"; case SERVICE_STOP_PENDING: return "SERVICE_STOP_PENDING"; case SERVICE_RUNNING: return "SERVICE_RUNNING"; case SERVICE_CONTINUE_PENDING: return "SERVICE_CONTINUE_PENDING"; case SERVICE_PAUSE_PENDING: return "SERVICE_PAUSE_PENDING"; case SERVICE_PAUSED: return "SERVICE_PAUSED"; default: return ""; } } /* constants and values from WinSvc.h: */ static const char *wolf_service_control_to_str( DWORD control ) { switch( control ) { case SERVICE_CONTROL_STOP: return "SERVICE_CONTROL_STOP"; case SERVICE_CONTROL_PAUSE: return "SERVICE_CONTROL_PAUSE"; case SERVICE_CONTROL_CONTINUE: return "SERVICE_CONTROL_CONTINUE"; case SERVICE_CONTROL_INTERROGATE: return "SERVICE_CONTROL_INTERROGATE"; case SERVICE_CONTROL_SHUTDOWN: return "SERVICE_CONTROL_SHUTDOWN"; case SERVICE_CONTROL_PARAMCHANGE: return "SERVICE_CONTROL_PARAMCHANGE"; case SERVICE_CONTROL_NETBINDADD: return "SERVICE_CONTROL_NETBINDADD"; case SERVICE_CONTROL_NETBINDREMOVE: return "SERVICE_CONTROL_NETBINDREMOVE"; case SERVICE_CONTROL_NETBINDENABLE: return "SERVICE_CONTROL_NETBINDENABLE"; case SERVICE_CONTROL_NETBINDDISABLE: return "SERVICE_CONTROL_NETBINDDISABLE"; case SERVICE_CONTROL_DEVICEEVENT: return "SERVICE_CONTROL_DEVICEEVENT"; case SERVICE_CONTROL_HARDWAREPROFILECHANGE: return "SERVICE_CONTROL_HARDWAREPROFILECHANGE"; case SERVICE_CONTROL_POWEREVENT: return "SERVICE_CONTROL_POWEREVENT"; case SERVICE_CONTROL_SESSIONCHANGE: return "SERVICE_CONTROL_SESSIONCHANGE"; default: return ""; } } static void wolf_service_report_status( DWORD current_state, DWORD exit_code, DWORD wait_hint ) { BOOL res; char errbuf[512]; wolf_log( WOLF_LOG_DEBUG, WOLF_CATEGORY_SERVICE, WOLF_MSG_SERVICE_REPORT_STATUS, _( "reporting status with new state '%s' (%d) (currently in state '%s' (%d))" ), wolf_service_state_to_str( current_state ), current_state, wolf_service_state_to_str( service_status.dwCurrentState ), service_status.dwCurrentState ); service_status.dwServiceType = SERVICE_WIN32_OWN_PROCESS; service_status.dwCurrentState = current_state; service_status.dwWin32ExitCode = exit_code; service_status.dwServiceSpecificExitCode = 0; service_status.dwWaitHint = wait_hint; service_status.dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN; switch( current_state ) { case SERVICE_START_PENDING: /* during startup we should not accept events, otherwise our * state machine could get troubled! */ service_status.dwControlsAccepted = 0; service_status.dwCheckPoint++; break; case SERVICE_RUNNING: case SERVICE_STOPPED: /* reset checkpoint for status bar in final states */ service_status.dwCheckPoint = 0; break; default: /* increate the tick for transient states */ service_status.dwCheckPoint++; break; } res = SetServiceStatus( service_status_handle, &service_status ); if( !res ) { wolf_system_error_msg( errbuf, 512 ); wolf_log( WOLF_LOG_ERR, WOLF_CATEGORY_SERVICE, WOLF_MSG_SERVICE_CANT_REPORT_STATUS, _( "Unable to report state '%s' (%d) of service '%s' to SCM '%s (%d)" ), wolf_service_state_to_str( current_state ), current_state, SERVICE_NAME, errbuf, GetLastError( ) ); return; } } /* we get called here by the system and the service control manager, * be as short as possible, report states and trigger new events at * most! */ void WINAPI wolf_service_ctrl_handler( DWORD control ) { char errbuf[512]; BOOL res; wolf_log( WOLF_LOG_DEBUG, WOLF_CATEGORY_SERVICE, WOLF_MSG_SERVICE_HANDLING_EVENT, _( "service handler received status change '%s' (%d)" ), wolf_service_control_to_str( control ), control ); switch( control ) { case SERVICE_CONTROL_STOP: case SERVICE_CONTROL_SHUTDOWN: wolf_service_report_status( SERVICE_STOP_PENDING, NO_ERROR, 1000 ); SetEvent( service_stop_event ); break; case SERVICE_CONTROL_INTERROGATE: /* fall through to send current status */ break; } /* report current state */ wolf_service_report_status( service_status.dwCurrentState, NO_ERROR, 1000 ); } void WINAPI wolf_service_main( DWORD argc, LPTSTR *argv ) { char errbuf[512]; BOOL res; /* close all loggers but the logging to the event log. If the service runs * with interactive right, it could open a console on the desktop (maybe this * is something for debugging later?). */ wolf_log_closelogtofile( ); wolf_log_closelogtostderr( ); /* register the event callback where we get called by the service * manager and the system */ service_status_handle = RegisterServiceCtrlHandler( SERVICE_NAME, wolf_service_ctrl_handler ); if( service_status_handle == 0 ) { wolf_system_error_msg( errbuf, 512 ); wolf_log( WOLF_LOG_ERR, WOLF_CATEGORY_SERVICE, WOLF_MSG_SERVICE_CANT_REGISTER_SERVICE_CTRL_HANDLER, _( "Unable to register service control handler function for service '%s': %s (%d)" ), SERVICE_NAME, errbuf, GetLastError( ) ); return; } /* signal that we are now up and running */ wolf_service_report_status( SERVICE_START_PENDING, NO_ERROR, 3000 ); /* register a stop event, the service control handler will send * the event. From now on we have a service event handler installed, * so if something goes wrong we can set the service state to * SERVICE_STOPPED and terminate gracefully. */ service_stop_event = CreateEvent( NULL, /* default security attributes */ TRUE, /* manual reset event */ FALSE, /* not signalled */ NULL ); /* no name */ if( service_stop_event == NULL ) { wolf_system_error_msg( errbuf, 512 ); wolf_log( WOLF_LOG_ERR, WOLF_CATEGORY_SERVICE, WOLF_MSG_SERVICE_CANT_CREATE_STOP_EVENT, _( "Unable to create the stop event for service '%s': %s (%d)" ), SERVICE_NAME, errbuf, GetLastError( ) ); wolf_service_report_status( SERVICE_STOPPED, NO_ERROR, 1000 ); return; } wolf_service_report_status( SERVICE_RUNNING, NO_ERROR, 1000 ); /* now call the user-defined service main function */ service_service_main( argc, argv ); wolf_service_report_status( SERVICE_STOPPED, NO_ERROR, 1000 ); wolf_log_closelogtoeventlog( ); return; } wolf_service_event_t wolf_service_events_suspend_console( int timeout, wolf_error_t *error ) { DWORD res; char errbuf[512]; res = WaitForSingleObject( service_stop_event, timeout * 1000 ); switch( res ) { case WAIT_OBJECT_0: /* stop signal received */ *error = WOLF_OK; return WOLF_SERVICE_EVENT_TERMINATE; case WAIT_TIMEOUT: *error = WOLF_ERR_TIMEOUT; return WOLF_SERVICE_NO_EVENT; default: wolf_system_error_msg( errbuf, 512 ); wolf_log( WOLF_LOG_ERR, WOLF_CATEGORY_SERVICE, WOLF_MSG_SERVICE_WAIT_FOR_OBJECT_FAILED, _( "Waiting for events in the service '%s' failed: %s (%d)" ), SERVICE_NAME, errbuf, GetLastError( ) ); *error = WOLF_ERR_INTERNAL; return WOLF_SERVICE_NO_EVENT; } } wolf_service_event_t wolf_service_events_suspend_service( int timeout, wolf_error_t *error ) { DWORD res; char errbuf[512]; res = WaitForSingleObject( service_stop_event, timeout * 1000 ); switch( res ) { case WAIT_OBJECT_0: /* stop signal received */ wolf_service_report_status( SERVICE_STOP_PENDING, NO_ERROR, 1000 ); *error = WOLF_OK; return WOLF_SERVICE_EVENT_TERMINATE; case WAIT_TIMEOUT: *error = WOLF_ERR_TIMEOUT; return WOLF_SERVICE_NO_EVENT; default: wolf_system_error_msg( errbuf, 512 ); wolf_log( WOLF_LOG_ERR, WOLF_CATEGORY_SERVICE, WOLF_MSG_SERVICE_WAIT_FOR_OBJECT_FAILED, _( "Waiting for events in the service '%s' failed: %s (%d)" ), SERVICE_NAME, errbuf, GetLastError( ) ); wolf_service_report_status( SERVICE_STOPPED, NO_ERROR, 1000 ); *error = WOLF_ERR_INTERNAL; return WOLF_SERVICE_NO_EVENT; } } /* constants and values from WinCon.h: */ static const char *wolf_service_console_ctrl_to_str( DWORD ctrl ) { switch( ctrl ) { case CTRL_C_EVENT: return "CTRL_C_EVENT"; case CTRL_BREAK_EVENT: return "CTRL_BREAK_EVENT"; case CTRL_CLOSE_EVENT: return "CTRL_CLOSE_EVENT"; case CTRL_LOGOFF_EVENT: return "CTRL_LOGOFF_EVENT"; case CTRL_SHUTDOWN_EVENT: return "CTRL_SHUTDOWN_EVENT"; default: return ""; } } static BOOL WINAPI wolf_service_console_ctrl_handler( DWORD ctrl ) { wolf_log( WOLF_LOG_DEBUG, WOLF_CATEGORY_SERVICE, WOLF_MSG_SERVICE_CONSOLE_CTRL_RECEIVED, _( "Got a console control event '%s' (%d)" ), wolf_service_console_ctrl_to_str( ctrl ), ctrl ); switch( ctrl ) { case CTRL_C_EVENT: case CTRL_BREAK_EVENT: case CTRL_CLOSE_EVENT: case CTRL_LOGOFF_EVENT: case CTRL_SHUTDOWN_EVENT: SetEvent( service_stop_event ); return TRUE; } return FALSE; } wolf_error_t wolf_service_start_console( LPTSTR service_name, LPSERVICE_MAIN_FUNCTION service_main, DWORD argc, LPTSTR *argv ) { BOOL res; char errbuf[512]; /* register the user-defined service main (what he should actually program */ service_service_main = service_main; /* register the right suspend function */ wolf_service_events_suspend = wolf_service_events_suspend_console; /* register a stop event, the console control handler will send * the event. */ service_stop_event = CreateEvent( NULL, /* default security attributes */ TRUE, /* manual reset event */ FALSE, /* not signalled */ NULL ); /* no name */ if( service_stop_event == NULL ) { wolf_system_error_msg( errbuf, 512 ); wolf_log( WOLF_LOG_ERR, WOLF_CATEGORY_SERVICE, WOLF_MSG_SERVICE_CANT_CREATE_STOP_EVENT, _( "Unable to create the stop event for service '%s': %s (%d)" ), SERVICE_NAME, errbuf, GetLastError( ) ); return WOLF_ERR_INTERNAL; } /* catch console events for proper termination of the service in console mode */ res = SetConsoleCtrlHandler( wolf_service_console_ctrl_handler, TRUE ); if( !res ) { wolf_system_error_msg( errbuf, 512 ); wolf_log( WOLF_LOG_ERR, WOLF_CATEGORY_SERVICE, WOLF_MSG_SERVICE_CANT_REGISTER_CONSOLE_CTRL_HANDLER, _( "Unable to register the console control handler for foreground service '%s': %s (%d)" ), service_name, errbuf, GetLastError( ) ); return WOLF_ERR_INTERNAL; } /* now call the user-defined service main function */ service_service_main( argc, argv ); return WOLF_OK; } wolf_error_t wolf_service_start( LPTSTR service_name, LPSERVICE_MAIN_FUNCTION service_main ) { BOOL res; char errbuf[512]; SERVICE_TABLE_ENTRY dispatch_table[2] = { { service_name, wolf_service_main }, { NULL, NULL } }; /* register the user-defined service main (what he should actually program */ service_service_main = service_main; /* register the right suspend function */ wolf_service_events_suspend = wolf_service_events_suspend_service; /* register the wolf_service_main to the SCM, which is the entry point for * the service (doing some things we want to hide from the service developer), * and which in the end calls 'service_service_main' (the user-defined main * service function. */ res = StartServiceCtrlDispatcher( dispatch_table ); if( !res ) { /* this is as far as I know the only way to determine * whether the binary runs not as a service.. */ if( GetLastError( ) == ERROR_FAILED_SERVICE_CONTROLLER_CONNECT ) { return WOLF_ERR_INVALID_STATE; } wolf_system_error_msg( errbuf, 512 ); wolf_log( WOLF_LOG_ERR, WOLF_CATEGORY_SERVICE, WOLF_MSG_SERVICE_CANT_DISPATCH_SERVICE, _( "Unable to dispatch service '%s': %s (%d)" ), service_name, errbuf, GetLastError( ) ); return WOLF_ERR_INTERNAL; } return WOLF_OK; }