/* * * * Windows Service related function definitions * * By Raju Krishnappa(raju_krishnappa@yahoo.com) * * */ #include #include #include /* sprintf */ #include /* beginthreadex */ #include /* * * * External global variables used here */ /* * Application Name */ /* * This should be decalred by the application, which wants to register as * * windows servcie */ extern LPTSTR g_szAppName; /* * * * Declare global variable */ /* * Flag to indicate, whether process is running as Service */ BOOL g_fRunningAsService = FALSE; /* * Varibale to maintain Current Service status */ static SERVICE_STATUS ServiceStatus; /* * Service Handle */ static SERVICE_STATUS_HANDLE hServiceStatus = 0L; /* * Service Table Entry */ SERVICE_TABLE_ENTRY ServiceTableEntry[] = { NULL, ServiceMain, /* Service Main function */ NULL, NULL}; /* * Handle to Thread, to implement Pause,Resume and stop funcitonality */ static HANDLE hServiceThread = NULL; /* Thread Handle */ /* * Holds calling partys Function Entry point, that should started * * when entered to service mode */ static INT(*ServiceEntryPoint) (INT Argc, LPTSTR Argv[]) = 0L; /* * * * To hold Stop Function address, to be called when STOP request * * recived from the SCM */ static VOID(*StopFunction) () = 0L; /* * * * To register as Windows Service with SCM(Service Control Manager) * * Input - Service Name, Serivce Display Name,Service Description and * * Service startup arguments */ VOID RegisterService(LPCTSTR lpszServiceName, LPCTSTR lpszServiceDisplayName, LPCTSTR lpszServiceDescription, InputParams * StartUpArg) /* Startup argument to the service */ { TCHAR szServicePath[MAX_PATH]; /* To hold module File name */ TCHAR MsgErrorString[MAX_STR_SIZE]; /* Message or Error string */ TCHAR szServiceCommand[MAX_PATH + 9]; /* Command to execute */ SC_HANDLE hSCManager = NULL; SC_HANDLE hService = NULL; TCHAR szRegAppLogKey[] = "SYSTEM\\CurrentControlSet\\Services\\EventLog\\Application\\"; TCHAR szRegKey[512]; HKEY hKey = NULL; /* Key to registry entry */ HKEY hParamKey = NULL; /* To store startup parameters */ DWORD dwData; /* Type of logging supported */ DWORD i, j; /* Loop variables */ GetModuleFileName(NULL, szServicePath, MAX_PATH); __try { /* * Open Service Control Manager handle */ hSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_CREATE_SERVICE); if (hSCManager == NULL) { DisplayError(_T("Can't open SCM")); __leave; } /* * Generate the Command to be executed by SCM */ _stprintf(szServiceCommand, "%s %s", szServicePath, _T("-service")); /* * Create the Desired service */ hService = CreateService(hSCManager, lpszServiceName, lpszServiceDisplayName, SERVICE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS, SERVICE_AUTO_START, SERVICE_ERROR_NORMAL, szServiceCommand, NULL, /* load-order group */ NULL, /* group member tag */ NULL, /* dependencies */ NULL, /* account */ NULL); /* password */ if (hService == NULL) { /* * Generate Error String */ _stprintf(MsgErrorString, "%s %s", _T("Can't Create Service"), lpszServiceDisplayName); DisplayError(MsgErrorString); __leave; } /* * Create registry entires for EventLog */ /* * Create registry Application event log key */ _tcscpy(szRegKey, szRegAppLogKey); _tcscat(szRegKey, lpszServiceName); /* * Create registry key */ if (RegCreateKey(HKEY_LOCAL_MACHINE, szRegKey, &hKey) != ERROR_SUCCESS) { _stprintf(MsgErrorString, "%s %s", _T("Unable to create registry entires"), lpszServiceDisplayName); DisplayError(MsgErrorString); __leave; } /* * Add Event ID message file name to the 'EventMessageFile' subkey */ RegSetValueEx(hKey, "EventMessageFile", 0, REG_EXPAND_SZ, (CONST BYTE *) szServicePath, _tcslen(szServicePath) + sizeof(TCHAR)); /* * Set the supported types flags. */ dwData = EVENTLOG_ERROR_TYPE | EVENTLOG_WARNING_TYPE | EVENTLOG_INFORMATION_TYPE; RegSetValueEx(hKey, "TypesSupported", 0, REG_DWORD, (CONST BYTE *) & dwData, sizeof(DWORD)); /* * Close Registry key */ RegCloseKey(hKey); /* * Set Service Description String and save startup parameters if present */ if (lpszServiceDescription != NULL || StartUpArg->Argc > 2) { /* * Create Registry Key path */ _tcscpy(szRegKey, _T("SYSTEM\\CurrentControlSet\\Services\\")); _tcscat(szRegKey, g_szAppName); hKey = NULL; /* * Open Registry key */ if (RegOpenKeyEx (HKEY_LOCAL_MACHINE, szRegKey, 0, KEY_WRITE, /* * Create and Set access */ &hKey) != ERROR_SUCCESS) { _stprintf(MsgErrorString, "%s %s", _T("Unable to create registry entires"), lpszServiceDisplayName); DisplayError(MsgErrorString); __leave; } /* * Create description subkey and the set value */ if (lpszServiceDescription != NULL) { if (RegSetValueEx(hKey, "Description", 0, REG_SZ, (CONST BYTE *) lpszServiceDescription, _tcslen(lpszServiceDescription) + sizeof(TCHAR)) != ERROR_SUCCESS) { _stprintf(MsgErrorString, "%s %s", _T("Unable to create registry entires"), lpszServiceDisplayName); DisplayError(MsgErrorString); __leave; }; } /* * Save startup arguments if they are present */ if (StartUpArg->Argc > 2) { /* * Create Subkey parameters */ if (RegCreateKeyEx (hKey, "Parameters", 0, NULL, REG_OPTION_NON_VOLATILE, KEY_WRITE, NULL, &hParamKey, NULL) != ERROR_SUCCESS) { _stprintf(MsgErrorString, "%s %s", _T("Unable to create registry entires"), lpszServiceDisplayName); DisplayError(MsgErrorString); __leave; } /* * Save parameters */ /* * Loop through arguments */ for (i = 2, j = 1; i < StartUpArg->Argc; i++, j++) { _stprintf(szRegKey, "%s%d", _T("Param"), j); /* * Create registry key */ if (RegSetValueEx (hParamKey, szRegKey, 0, REG_SZ, (CONST BYTE *) StartUpArg->Argv[i], _tcslen(StartUpArg->Argv[i]) + sizeof(TCHAR)) != ERROR_SUCCESS) { _stprintf(MsgErrorString, "%s %s", _T("Unable to create registry entires"), lpszServiceDisplayName); DisplayError(MsgErrorString); __leave; }; } } /* * Everything is set, delete hKey */ RegCloseKey(hParamKey); RegCloseKey(hKey); } /* * Ready to Log messages */ /* * Successfully registered as service */ _stprintf(MsgErrorString, "%s %s", lpszServiceName, _T("- Successfully registered as Service")); /* * Log message to eventlog */ WriteToEventLog(EVENTLOG_INFORMATION_TYPE, MsgErrorString); MessageBox(NULL, MsgErrorString, g_szAppName, MB_ICONINFORMATION); } __finally { if (hSCManager) CloseServiceHandle(hSCManager); if (hService) CloseServiceHandle(hService); if (hKey) RegCloseKey(hKey); if (hParamKey) RegCloseKey(hParamKey); } } /* * * * Unregister the service with the Windows SCM * * Input - ServiceName * * */ VOID UnregisterService(LPCSTR lpszServiceName) { TCHAR MsgErrorString[MAX_STR_SIZE]; /* Message or Error string */ SC_HANDLE hSCManager = NULL; /* SCM handle */ SC_HANDLE hService = NULL; /* Service Handle */ SERVICE_STATUS sStatus; TCHAR szRegAppLogKey[] = "SYSTEM\\CurrentControlSet\\Services\\EventLog\\Application\\"; TCHAR szRegKey[512]; HKEY hKey = NULL; /* Key to registry entry */ __try { /* * Open Service Control Manager */ hSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_CREATE_SERVICE); if (hSCManager == NULL) { /* * Error while opening SCM */ MessageBox(NULL, _T("Can't open SCM"), g_szAppName, MB_ICONHAND); __leave; } /* * Open registered service */ hService = OpenService(hSCManager, lpszServiceName, SERVICE_ALL_ACCESS); if (hService == NULL) { _stprintf(MsgErrorString, "%s %s", _T("Can't open service"), lpszServiceName); MessageBox(NULL, MsgErrorString, g_szAppName, MB_ICONHAND); __leave; } /* * Query service status */ /* * If running stop before deleting */ if (QueryServiceStatus(hService, &sStatus)) { if (sStatus.dwCurrentState == SERVICE_RUNNING || sStatus.dwCurrentState == SERVICE_PAUSED) { /* * Shutdown the service */ ControlService(hService, SERVICE_CONTROL_STOP, &sStatus); } }; /* * Delete the service */ if (DeleteService(hService) == FALSE) { _stprintf(MsgErrorString, "%s %s", _T("Can't delete service"), lpszServiceName); MessageBox(NULL, MsgErrorString, g_szAppName, MB_ICONHAND); /* * Log message to eventlog */ WriteToEventLog(EVENTLOG_INFORMATION_TYPE, MsgErrorString); __leave; } /* * Service deleted successfully */ _stprintf(MsgErrorString, "%s %s", lpszServiceName, _T("- Service deleted")); /* * Log message to eventlog */ WriteToEventLog(EVENTLOG_INFORMATION_TYPE, MsgErrorString); /* * Delete registry entires for EventLog */ _tcscpy(szRegKey, szRegAppLogKey); _tcscat(szRegKey, lpszServiceName); RegDeleteKey(HKEY_LOCAL_MACHINE, szRegKey); MessageBox(NULL, MsgErrorString, g_szAppName, MB_ICONINFORMATION); } /* * Delete the handles */ __finally { if (hService) CloseServiceHandle(hService); if (hSCManager) CloseServiceHandle(hSCManager); } } /* * * * To write message to Windows Event log * * Input - Event Type, Message string * * */ VOID WriteToEventLog(WORD wType, LPCTSTR pszFormat,...) { TCHAR szMessage[512]; LPTSTR LogStr[1]; va_list ArgList; HANDLE hEventSource = NULL; va_start(ArgList, pszFormat); _vstprintf(szMessage, pszFormat, ArgList); va_end(ArgList); LogStr[0] = szMessage; hEventSource = RegisterEventSource(NULL, g_szAppName); if (hEventSource == NULL) return; ReportEvent(hEventSource, wType, 0, DISPLAY_MSG, /* To Just output the text to event log */ NULL, 1, 0, LogStr, NULL); DeregisterEventSource(hEventSource); if (!g_fRunningAsService) { /* * We are running in command mode, output the string */ _putts(szMessage); } } /* * * * Handle command-line arguments from the user. * * Serivce related options are: * * -register - registers the service * * -unregister - unregisters the service * * -service - run as serivce * * other command-line arguments are unaltered/ignored. * * They should supplied as first arguments(other wise they will be ignored * * Return: Type indicating the option specified */ INT ParseCmdLineForServiceOption(int argc, TCHAR * argv[]) { int nReturn = RUN_AS_CONSOLE; /* Defualted to run as console */ if (argc >= 2) { /* * second argument present */ if (lstrcmpi(_T("-register"), argv[1]) == 0) { nReturn = REGISTER_SERVICE; } else if (lstrcmpi(_T("-unregister"), argv[1]) == 0) { nReturn = UN_REGISTER_SERVICE; } else if (lstrcmpi(_T("-service"), argv[1]) == 0) { nReturn = RUN_AS_SERVICE; } } return nReturn; } /* * * * To Display an error message describing the last system error * * message, along with a title passed as a parameter. */ VOID DisplayError(LPCTSTR pszTitle) { LPVOID pErrorMsg; /* * Build Error String */ FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR) & pErrorMsg, 0, NULL); if (g_fRunningAsService != FALSE) { WriteToEventLog(EVENTLOG_ERROR_TYPE, pErrorMsg); } else { MessageBox(NULL, pErrorMsg, pszTitle, MB_ICONHAND); } LocalFree(pErrorMsg); } /* * * * To update current service status * * Sends the current service status to the SCM. Also updates * * the global service status structure. */ static BOOL UpdateServiceStatus(DWORD dwStatus, DWORD dwErrorCode, DWORD dwWaitHint) { BOOL fReturn = FALSE; DWORD static dwCheckpoint = 1; DWORD dwControls = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_PAUSE_CONTINUE; if (g_fRunningAsService == FALSE) return FALSE; ZeroMemory(&ServiceStatus, sizeof(ServiceStatus)); ServiceStatus.dwServiceType = SERVICE_WIN32; ServiceStatus.dwCurrentState = dwStatus; ServiceStatus.dwWaitHint = dwWaitHint; if (dwErrorCode) { ServiceStatus.dwWin32ExitCode = ERROR_SERVICE_SPECIFIC_ERROR; ServiceStatus.dwServiceSpecificExitCode = dwErrorCode; } /* * special cases that depend on the new state */ switch (dwStatus) { case SERVICE_START_PENDING: dwControls = 0; break; case SERVICE_RUNNING: case SERVICE_STOPPED: dwCheckpoint = 0; break; } ServiceStatus.dwCheckPoint = dwCheckpoint++; ServiceStatus.dwControlsAccepted = dwControls; return ReportCurrentServiceStatus(); } /* * * * Reports current Service status to SCM */ static BOOL ReportCurrentServiceStatus() { return SetServiceStatus(hServiceStatus, &ServiceStatus); } /* * * * The ServiceMain function to start service. */ VOID WINAPI ServiceMain(DWORD argc, LPTSTR argv[]) { SECURITY_ATTRIBUTES SecurityAttributes; DWORD dwThreadId; /* * Input Arguments to function startup */ DWORD ArgCount = 0; LPTSTR * ArgArray = NULL; TCHAR szRegKey[512]; TCHAR szValue[128]; DWORD nSize; HKEY hParamKey = NULL; /* To read startup parameters */ DWORD TotalParams = 0; DWORD i; InputParams ThreadInputParams; /* * Build the Input parameters to pass to thread */ /* * SCM sends Service Name as first arg, increment to point * * arguments user specified while starting contorl agent */ /* * Read registry parameter */ /* * Initialize count to 1 */ ArgCount = 1; /* * Create Registry Key path */ _stprintf(szRegKey, "%s%s\\%s", _T("SYSTEM\\CurrentControlSet\\Services\\"), g_szAppName, "Parameters"); if (RegOpenKeyEx (HKEY_LOCAL_MACHINE, szRegKey, 0, KEY_ALL_ACCESS, &hParamKey) == ERROR_SUCCESS) { /* * Read startup Configuration information */ /* * Find number of subkeys inside parameters */ if (RegQueryInfoKey (hParamKey, NULL, NULL, 0, NULL, NULL, NULL, &TotalParams, NULL, NULL, NULL, NULL) == ERROR_SUCCESS) { if (TotalParams != 0) { ArgCount += TotalParams; /* * Allocate memory to hold strings */ ArgArray = (LPTSTR *) malloc(sizeof(LPTSTR) * ArgCount); /* * Copy first argument */ ArgArray[0] = _tcsdup(argv[0]); for (i = 1; i <= TotalParams; i++) { /* * Create Subkey value name */ _stprintf(szRegKey, "%s%d", "Param", i); /* * Set size */ nSize = 128; RegQueryValueEx(hParamKey, szRegKey, 0, NULL, (LPBYTE) & szValue, &nSize); ArgArray[i] = _tcsdup(szValue); } } } RegCloseKey(hParamKey); } if (ArgCount == 1) { /* * No statup agrs are given */ ThreadInputParams.Argc = argc; ThreadInputParams.Argv = argv; } else { ThreadInputParams.Argc = ArgCount; ThreadInputParams.Argv = ArgArray; } /* * Register Serivce Control Handler */ hServiceStatus = RegisterServiceCtrlHandler(g_szAppName, ControlHandler); if (hServiceStatus == 0) { WriteToEventLog(EVENTLOG_ERROR_TYPE, _T("RegisterServiceCtrlHandler failed")); return; } /* * Update the service status to START_PENDING */ UpdateServiceStatus(SERVICE_START_PENDING, NO_ERROR, SCM_WAIT_INTERVAL); /* * Spin of worker thread, which does majority of the work */ __try { if (SetSimpleSecurityAttributes(&SecurityAttributes) == FALSE) { WriteToEventLog(EVENTLOG_ERROR_TYPE, _T("Couldn't init security attributes")); __leave; } hServiceThread = (void *) _beginthreadex(&SecurityAttributes, 0, ThreadFunction, (void *) &ThreadInputParams, 0, &dwThreadId); if (hServiceThread == NULL) { WriteToEventLog(EVENTLOG_ERROR_TYPE, _T("Couldn't start worker thread")); __leave; } /* * Set Service Status to Running */ UpdateServiceStatus(SERVICE_RUNNING, NO_ERROR, SCM_WAIT_INTERVAL); /* * Wait for termination event and worker thread to * * spin down. */ WaitForSingleObject(hServiceThread, INFINITE); } __finally { /* * Release resources */ UpdateServiceStatus(SERVICE_STOPPED, NO_ERROR, SCM_WAIT_INTERVAL); if (hServiceThread) CloseHandle(hServiceThread); FreeSecurityAttributes(&SecurityAttributes); /* * Delete allocated argument list */ if (ArgCount > 1 && ArgArray != NULL) { /* * Delete all strings */ for (i = 0; i < ArgCount; i++) { free(ArgArray[i]); } free(ArgArray); } } } /* * * * Function to start as Windows service * * The calling party should specify their entry point as input parameter * * Returns TRUE if the Service is started successfully */ BOOL RunAsService(INT(*ServiceFunction) (INT, LPTSTR *)) { /* * Set the ServiceEntryPoint */ ServiceEntryPoint = ServiceFunction; /* * By default, mark as Running as a service */ g_fRunningAsService = TRUE; /* * Initialize ServiceTableEntry table */ ServiceTableEntry[0].lpServiceName = g_szAppName; /* Application Name */ /* * Call SCM via StartServiceCtrlDispatcher to run as Service * * If the function returns TRUE we are running as Service, */ if (StartServiceCtrlDispatcher(ServiceTableEntry) == FALSE) { g_fRunningAsService = FALSE; /* * Some other error has occurred. */ WriteToEventLog(EVENTLOG_ERROR_TYPE, _T("Couldn't start service - %s"), g_szAppName); } return g_fRunningAsService; } /* * * * Service control handler function * * Responds to SCM commands/requests * * The service handles 4 commands * * commands - interrogate,pause, continue and stop. */ VOID WINAPI ControlHandler(DWORD dwControl) { switch (dwControl) { case SERVICE_CONTROL_STOP: ProcessServiceStop(); /* To stop the service */ break; case SERVICE_CONTROL_INTERROGATE: ProcessServiceInterrogate(); /* Report Current state of the Service */ break; case SERVICE_CONTROL_PAUSE: ProcessServicePause(); /* To puase service */ break; case SERVICE_CONTROL_CONTINUE: ProcessServiceContinue(); /* To continue Service */ break; } } /* * * * To stop the service. This invokes registered * * stop function to stop the service(gracefull exit) * * After stopping, Service status is set to STOP in * * main loop */ VOID ProcessServiceStop(VOID) { UpdateServiceStatus(SERVICE_STOP_PENDING, NO_ERROR, SCM_WAIT_INTERVAL); /* * Invoke registered Stop funciton */ if (StopFunction != NULL) { (*StopFunction) (); } else { /* * There is no registered stop function, so terminate the thread */ TerminateThread(hServiceThread, 0); } } /* * * * Returns the current state of the service to the SCM. */ VOID ProcessServiceInterrogate(VOID) { ReportCurrentServiceStatus(); } /* * * * To Create a security descriptor with a NULL ACL, which * * allows unlimited access. Returns a SECURITY_ATTRIBUTES * * structure that contains the security descriptor. * * The structure contains a dynamically allocated security * * descriptor that must be freed; either manually, or by * * calling FreeSecurityAttributes */ BOOL SetSimpleSecurityAttributes(SECURITY_ATTRIBUTES * pSecurityAttr) { BOOL fReturn = FALSE; SECURITY_DESCRIPTOR * pSecurityDesc = NULL; /* * If an invalid address passed as a parameter, return * * FALSE right away. */ if (!pSecurityAttr) return FALSE; pSecurityDesc = (SECURITY_DESCRIPTOR *) LocalAlloc(LPTR, SECURITY_DESCRIPTOR_MIN_LENGTH); if (!pSecurityDesc) return FALSE; fReturn = InitializeSecurityDescriptor(pSecurityDesc, SECURITY_DESCRIPTOR_REVISION); if (fReturn != FALSE) { fReturn = SetSecurityDescriptorDacl(pSecurityDesc, TRUE, NULL, FALSE); } if (fReturn != FALSE) { pSecurityAttr->nLength = sizeof(SECURITY_ATTRIBUTES); pSecurityAttr->lpSecurityDescriptor = pSecurityDesc; pSecurityAttr->bInheritHandle = TRUE; } else { /* * Couldn't initialize or set security descriptor. */ LocalFree(pSecurityDesc); } return fReturn; } /* * * * This funciton Frees the security descriptor owned by a SECURITY_ATTRIBUTES * * structure. */ VOID FreeSecurityAttributes(SECURITY_ATTRIBUTES * pSecurityAttr) { if (pSecurityAttr && pSecurityAttr->lpSecurityDescriptor) LocalFree(pSecurityAttr->lpSecurityDescriptor); } /* * TheadFunction * * This function is spawn as thread. * * Invokes registered service function * * Returns when called registered function returns */ DWORD WINAPI ThreadFunction(LPVOID lpParam) { /* * lpParam contains argc and argv, pass to service main function */ /* * Declare pointer to InputParams */ InputParams * pInputArg; pInputArg = (InputParams *) lpParam; return (*ServiceEntryPoint) (pInputArg->Argc, pInputArg->Argv); } /* * * * To register STOP function with the framework * * This function will be inovked when SCM sends * * STOP command */ VOID RegisterStopFunction(void (*StopFunc) ()) { StopFunction = StopFunc; } /* * * * To Pause the service whec SCM sends pause command * * Invokes PauseThread on worker Thread handle, only * * when Service status is Running */ VOID ProcessServicePause(VOID) { if (ServiceStatus.dwCurrentState == SERVICE_RUNNING) { UpdateServiceStatus(SERVICE_PAUSE_PENDING, NO_ERROR, SCM_WAIT_INTERVAL); /* * Invoke Thread pause on ThreadHandle */ if (SuspendThread(hServiceThread) != -1) { UpdateServiceStatus(SERVICE_PAUSED, NO_ERROR, SCM_WAIT_INTERVAL); } } } /* * * * To Continue paused service * * Invoke ResumeThread, if thread is paused */ VOID ProcessServiceContinue(VOID) { if (ServiceStatus.dwCurrentState == SERVICE_PAUSED) { UpdateServiceStatus(SERVICE_CONTINUE_PENDING, NO_ERROR, SCM_WAIT_INTERVAL); /* * Invoke Thread pause on ThreadHandle */ if (ResumeThread(hServiceThread) != -1) { UpdateServiceStatus(SERVICE_RUNNING, NO_ERROR, SCM_WAIT_INTERVAL); } } }