/* * Process Hacker Extended Services - * recovery information * * Copyright (C) 2010-2011 wj32 * * This file is part of Process Hacker. * * Process Hacker 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. * * Process Hacker 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 Process Hacker. If not, see . */ #include "extsrv.h" typedef struct _SERVICE_RECOVERY_CONTEXT { PPH_SERVICE_ITEM ServiceItem; ULONG NumberOfActions; BOOLEAN EnableFlagCheckBox; ULONG RebootAfter; // in ms PPH_STRING RebootMessage; BOOLEAN Ready; BOOLEAN Dirty; } SERVICE_RECOVERY_CONTEXT, *PSERVICE_RECOVERY_CONTEXT; static PH_KEY_VALUE_PAIR ServiceActionPairs[] = { SIP(L"Take no action", SC_ACTION_NONE), SIP(L"Restart the service", SC_ACTION_RESTART), SIP(L"Run a program", SC_ACTION_RUN_COMMAND), SIP(L"Restart the computer", SC_ACTION_REBOOT) }; INT_PTR CALLBACK RestartComputerDlgProc( _In_ HWND hwndDlg, _In_ UINT uMsg, _In_ WPARAM wParam, _In_ LPARAM lParam ); VOID EspAddServiceActionStrings( _In_ HWND ComboBoxHandle ) { ULONG i; for (i = 0; i < sizeof(ServiceActionPairs) / sizeof(PH_KEY_VALUE_PAIR); i++) ComboBox_AddString(ComboBoxHandle, (PWSTR)ServiceActionPairs[i].Key); PhSelectComboBoxString(ComboBoxHandle, (PWSTR)ServiceActionPairs[0].Key, FALSE); } SC_ACTION_TYPE EspStringToServiceAction( _In_ PWSTR String ) { ULONG integer; if (PhFindIntegerSiKeyValuePairs(ServiceActionPairs, sizeof(ServiceActionPairs), String, &integer)) return integer; else return 0; } PWSTR EspServiceActionToString( _In_ SC_ACTION_TYPE ActionType ) { PWSTR string; if (PhFindStringSiKeyValuePairs(ServiceActionPairs, sizeof(ServiceActionPairs), ActionType, &string)) return string; else return NULL; } SC_ACTION_TYPE ComboBoxToServiceAction( _In_ HWND ComboBoxHandle ) { PPH_STRING string; string = PH_AUTO(PhGetComboBoxString(ComboBoxHandle, ComboBox_GetCurSel(ComboBoxHandle))); if (!string) return SC_ACTION_NONE; return EspStringToServiceAction(string->Buffer); } VOID ServiceActionToComboBox( _In_ HWND ComboBoxHandle, _In_ SC_ACTION_TYPE ActionType ) { PWSTR string; if (string = EspServiceActionToString(ActionType)) PhSelectComboBoxString(ComboBoxHandle, string, FALSE); else PhSelectComboBoxString(ComboBoxHandle, (PWSTR)ServiceActionPairs[0].Key, FALSE); } VOID EspFixControls( _In_ HWND hwndDlg, _In_ PSERVICE_RECOVERY_CONTEXT Context ) { SC_ACTION_TYPE action1; SC_ACTION_TYPE action2; SC_ACTION_TYPE actionS; BOOLEAN enableRestart; BOOLEAN enableReboot; BOOLEAN enableCommand; action1 = ComboBoxToServiceAction(GetDlgItem(hwndDlg, IDC_FIRSTFAILURE)); action2 = ComboBoxToServiceAction(GetDlgItem(hwndDlg, IDC_SECONDFAILURE)); actionS = ComboBoxToServiceAction(GetDlgItem(hwndDlg, IDC_SUBSEQUENTFAILURES)); EnableWindow(GetDlgItem(hwndDlg, IDC_ENABLEFORERRORSTOPS), Context->EnableFlagCheckBox); enableRestart = action1 == SC_ACTION_RESTART || action2 == SC_ACTION_RESTART || actionS == SC_ACTION_RESTART; enableReboot = action1 == SC_ACTION_REBOOT || action2 == SC_ACTION_REBOOT || actionS == SC_ACTION_REBOOT; enableCommand = action1 == SC_ACTION_RUN_COMMAND || action2 == SC_ACTION_RUN_COMMAND || actionS == SC_ACTION_RUN_COMMAND; EnableWindow(GetDlgItem(hwndDlg, IDC_RESTARTSERVICEAFTER_LABEL), enableRestart); EnableWindow(GetDlgItem(hwndDlg, IDC_RESTARTSERVICEAFTER), enableRestart); EnableWindow(GetDlgItem(hwndDlg, IDC_RESTARTSERVICEAFTER_MINUTES), enableRestart); EnableWindow(GetDlgItem(hwndDlg, IDC_RESTARTCOMPUTEROPTIONS), enableReboot); EnableWindow(GetDlgItem(hwndDlg, IDC_RUNPROGRAM_GROUP), enableCommand); EnableWindow(GetDlgItem(hwndDlg, IDC_RUNPROGRAM_LABEL), enableCommand); EnableWindow(GetDlgItem(hwndDlg, IDC_RUNPROGRAM), enableCommand); EnableWindow(GetDlgItem(hwndDlg, IDC_BROWSE), enableCommand); EnableWindow(GetDlgItem(hwndDlg, IDC_RUNPROGRAM_INFO), enableCommand); } NTSTATUS EspLoadRecoveryInfo( _In_ HWND hwndDlg, _In_ PSERVICE_RECOVERY_CONTEXT Context ) { NTSTATUS status = STATUS_SUCCESS; SC_HANDLE serviceHandle; LPSERVICE_FAILURE_ACTIONS failureActions; SERVICE_FAILURE_ACTIONS_FLAG failureActionsFlag; SC_ACTION_TYPE lastType; ULONG returnLength; ULONG i; if (!(serviceHandle = PhOpenService(Context->ServiceItem->Name->Buffer, SERVICE_QUERY_CONFIG))) return NTSTATUS_FROM_WIN32(GetLastError()); if (!(failureActions = PhQueryServiceVariableSize(serviceHandle, SERVICE_CONFIG_FAILURE_ACTIONS))) { CloseServiceHandle(serviceHandle); return NTSTATUS_FROM_WIN32(GetLastError()); } // Failure action types Context->NumberOfActions = failureActions->cActions; if (failureActions->cActions != 0 && failureActions->cActions != 3) status = STATUS_SOME_NOT_MAPPED; // If failure actions are not defined for a particular fail count, the // last failure action is used. Here we duplicate this behaviour when there // are fewer than 3 failure actions. lastType = SC_ACTION_NONE; ServiceActionToComboBox(GetDlgItem(hwndDlg, IDC_FIRSTFAILURE), failureActions->cActions >= 1 ? (lastType = failureActions->lpsaActions[0].Type) : lastType); ServiceActionToComboBox(GetDlgItem(hwndDlg, IDC_SECONDFAILURE), failureActions->cActions >= 2 ? (lastType = failureActions->lpsaActions[1].Type) : lastType); ServiceActionToComboBox(GetDlgItem(hwndDlg, IDC_SUBSEQUENTFAILURES), failureActions->cActions >= 3 ? (lastType = failureActions->lpsaActions[2].Type) : lastType); // Reset fail count after SetDlgItemInt(hwndDlg, IDC_RESETFAILCOUNT, failureActions->dwResetPeriod / (60 * 60 * 24), FALSE); // s to days // Restart service after SetDlgItemText(hwndDlg, IDC_RESTARTSERVICEAFTER, L"1"); for (i = 0; i < failureActions->cActions; i++) { if (failureActions->lpsaActions[i].Type == SC_ACTION_RESTART) { if (failureActions->lpsaActions[i].Delay != 0) { SetDlgItemInt(hwndDlg, IDC_RESTARTSERVICEAFTER, failureActions->lpsaActions[i].Delay / (1000 * 60), FALSE); // ms to min } break; } } // Enable actions for stops with errors // This is Vista and above only. if (WindowsVersion >= WINDOWS_VISTA && QueryServiceConfig2( serviceHandle, SERVICE_CONFIG_FAILURE_ACTIONS_FLAG, (BYTE *)&failureActionsFlag, sizeof(SERVICE_FAILURE_ACTIONS_FLAG), &returnLength )) { Button_SetCheck(GetDlgItem(hwndDlg, IDC_ENABLEFORERRORSTOPS), failureActionsFlag.fFailureActionsOnNonCrashFailures ? BST_CHECKED : BST_UNCHECKED); Context->EnableFlagCheckBox = TRUE; } else { Context->EnableFlagCheckBox = FALSE; } // Restart computer options Context->RebootAfter = 1 * 1000 * 60; for (i = 0; i < failureActions->cActions; i++) { if (failureActions->lpsaActions[i].Type == SC_ACTION_REBOOT) { if (failureActions->lpsaActions[i].Delay != 0) Context->RebootAfter = failureActions->lpsaActions[i].Delay; break; } } if (failureActions->lpRebootMsg && failureActions->lpRebootMsg[0] != 0) PhMoveReference(&Context->RebootMessage, PhCreateString(failureActions->lpRebootMsg)); else PhClearReference(&Context->RebootMessage); // Run program SetDlgItemText(hwndDlg, IDC_RUNPROGRAM, failureActions->lpCommand); PhFree(failureActions); CloseServiceHandle(serviceHandle); return status; } INT_PTR CALLBACK EspServiceRecoveryDlgProc( _In_ HWND hwndDlg, _In_ UINT uMsg, _In_ WPARAM wParam, _In_ LPARAM lParam ) { PSERVICE_RECOVERY_CONTEXT context; if (uMsg == WM_INITDIALOG) { context = PhAllocate(sizeof(SERVICE_RECOVERY_CONTEXT)); memset(context, 0, sizeof(SERVICE_RECOVERY_CONTEXT)); SetProp(hwndDlg, L"Context", (HANDLE)context); } else { context = (PSERVICE_RECOVERY_CONTEXT)GetProp(hwndDlg, L"Context"); if (uMsg == WM_DESTROY) RemoveProp(hwndDlg, L"Context"); } if (!context) return FALSE; switch (uMsg) { case WM_INITDIALOG: { NTSTATUS status; LPPROPSHEETPAGE propSheetPage = (LPPROPSHEETPAGE)lParam; PPH_SERVICE_ITEM serviceItem = (PPH_SERVICE_ITEM)propSheetPage->lParam; context->ServiceItem = serviceItem; EspAddServiceActionStrings(GetDlgItem(hwndDlg, IDC_FIRSTFAILURE)); EspAddServiceActionStrings(GetDlgItem(hwndDlg, IDC_SECONDFAILURE)); EspAddServiceActionStrings(GetDlgItem(hwndDlg, IDC_SUBSEQUENTFAILURES)); status = EspLoadRecoveryInfo(hwndDlg, context); if (status == STATUS_SOME_NOT_MAPPED) { if (context->NumberOfActions > 3) { PhShowWarning( hwndDlg, L"The service has %lu failure actions configured, but this program only supports editing 3. " L"If you save the recovery information using this program, the additional failure actions will be lost.", context->NumberOfActions ); } } else if (!NT_SUCCESS(status)) { SetDlgItemText(hwndDlg, IDC_RESETFAILCOUNT, L"0"); if (WindowsVersion >= WINDOWS_VISTA) { context->EnableFlagCheckBox = TRUE; EnableWindow(GetDlgItem(hwndDlg, IDC_ENABLEFORERRORSTOPS), TRUE); } PhShowWarning(hwndDlg, L"Unable to query service recovery information: %s", ((PPH_STRING)PH_AUTO(PhGetNtMessage(status)))->Buffer); } EspFixControls(hwndDlg, context); context->Ready = TRUE; } break; case WM_DESTROY: { PhClearReference(&context->RebootMessage); PhFree(context); } break; case WM_COMMAND: { switch (LOWORD(wParam)) { case IDC_FIRSTFAILURE: case IDC_SECONDFAILURE: case IDC_SUBSEQUENTFAILURES: { if (HIWORD(wParam) == CBN_SELCHANGE) { EspFixControls(hwndDlg, context); } } break; case IDC_RESTARTCOMPUTEROPTIONS: { DialogBoxParam( PluginInstance->DllBase, MAKEINTRESOURCE(IDD_RESTARTCOMP), hwndDlg, RestartComputerDlgProc, (LPARAM)context ); } break; case IDC_BROWSE: { static PH_FILETYPE_FILTER filters[] = { { L"Executable files (*.exe;*.cmd;*.bat)", L"*.exe;*.cmd;*.bat" }, { L"All files (*.*)", L"*.*" } }; PVOID fileDialog; PPH_STRING fileName; fileDialog = PhCreateOpenFileDialog(); PhSetFileDialogFilter(fileDialog, filters, sizeof(filters) / sizeof(PH_FILETYPE_FILTER)); fileName = PhaGetDlgItemText(hwndDlg, IDC_RUNPROGRAM); PhSetFileDialogFileName(fileDialog, fileName->Buffer); if (PhShowFileDialog(hwndDlg, fileDialog)) { fileName = PH_AUTO(PhGetFileDialogFileName(fileDialog)); SetDlgItemText(hwndDlg, IDC_RUNPROGRAM, fileName->Buffer); } PhFreeFileDialog(fileDialog); } break; case IDC_ENABLEFORERRORSTOPS: { context->Dirty = TRUE; } break; } switch (HIWORD(wParam)) { case EN_CHANGE: case CBN_SELCHANGE: { if (context->Ready) context->Dirty = TRUE; } break; } } break; case WM_NOTIFY: { LPNMHDR header = (LPNMHDR)lParam; switch (header->code) { case PSN_KILLACTIVE: { SetWindowLongPtr(hwndDlg, DWLP_MSGRESULT, FALSE); } return TRUE; case PSN_APPLY: { NTSTATUS status; PPH_SERVICE_ITEM serviceItem = context->ServiceItem; SC_HANDLE serviceHandle; ULONG restartServiceAfter; SERVICE_FAILURE_ACTIONS failureActions; SC_ACTION actions[3]; ULONG i; BOOLEAN enableRestart = FALSE; SetWindowLongPtr(hwndDlg, DWLP_MSGRESULT, PSNRET_NOERROR); if (!context->Dirty) { return TRUE; } // Build the failure actions structure. failureActions.dwResetPeriod = GetDlgItemInt(hwndDlg, IDC_RESETFAILCOUNT, NULL, FALSE) * 60 * 60 * 24; failureActions.lpRebootMsg = PhGetStringOrEmpty(context->RebootMessage); failureActions.lpCommand = PhaGetDlgItemText(hwndDlg, IDC_RUNPROGRAM)->Buffer; failureActions.cActions = 3; failureActions.lpsaActions = actions; actions[0].Type = ComboBoxToServiceAction(GetDlgItem(hwndDlg, IDC_FIRSTFAILURE)); actions[1].Type = ComboBoxToServiceAction(GetDlgItem(hwndDlg, IDC_SECONDFAILURE)); actions[2].Type = ComboBoxToServiceAction(GetDlgItem(hwndDlg, IDC_SUBSEQUENTFAILURES)); restartServiceAfter = GetDlgItemInt(hwndDlg, IDC_RESTARTSERVICEAFTER, NULL, FALSE) * 1000 * 60; for (i = 0; i < 3; i++) { switch (actions[i].Type) { case SC_ACTION_RESTART: actions[i].Delay = restartServiceAfter; enableRestart = TRUE; break; case SC_ACTION_REBOOT: actions[i].Delay = context->RebootAfter; break; case SC_ACTION_RUN_COMMAND: actions[i].Delay = 0; break; } } // Try to save the changes. serviceHandle = PhOpenService( serviceItem->Name->Buffer, SERVICE_CHANGE_CONFIG | (enableRestart ? SERVICE_START : 0) // SC_ACTION_RESTART requires SERVICE_START ); if (serviceHandle) { if (ChangeServiceConfig2( serviceHandle, SERVICE_CONFIG_FAILURE_ACTIONS, &failureActions )) { if (context->EnableFlagCheckBox) { SERVICE_FAILURE_ACTIONS_FLAG failureActionsFlag; failureActionsFlag.fFailureActionsOnNonCrashFailures = Button_GetCheck(GetDlgItem(hwndDlg, IDC_ENABLEFORERRORSTOPS)) == BST_CHECKED; ChangeServiceConfig2( serviceHandle, SERVICE_CONFIG_FAILURE_ACTIONS_FLAG, &failureActionsFlag ); } CloseServiceHandle(serviceHandle); } else { CloseServiceHandle(serviceHandle); goto ErrorCase; } } else { if (GetLastError() == ERROR_ACCESS_DENIED && !PhGetOwnTokenAttributes().Elevated) { // Elevate using phsvc. if (PhUiConnectToPhSvc(hwndDlg, FALSE)) { if (NT_SUCCESS(status = PhSvcCallChangeServiceConfig2( serviceItem->Name->Buffer, SERVICE_CONFIG_FAILURE_ACTIONS, &failureActions ))) { if (context->EnableFlagCheckBox) { SERVICE_FAILURE_ACTIONS_FLAG failureActionsFlag; failureActionsFlag.fFailureActionsOnNonCrashFailures = Button_GetCheck(GetDlgItem(hwndDlg, IDC_ENABLEFORERRORSTOPS)) == BST_CHECKED; PhSvcCallChangeServiceConfig2( serviceItem->Name->Buffer, SERVICE_CONFIG_FAILURE_ACTIONS_FLAG, &failureActionsFlag ); } } PhUiDisconnectFromPhSvc(); if (!NT_SUCCESS(status)) { SetLastError(PhNtStatusToDosError(status)); goto ErrorCase; } } else { // User cancelled elevation. SetWindowLongPtr(hwndDlg, DWLP_MSGRESULT, PSNRET_INVALID); } } else { goto ErrorCase; } } return TRUE; ErrorCase: if (PhShowMessage( hwndDlg, MB_ICONERROR | MB_RETRYCANCEL, L"Unable to change service recovery information: %s", ((PPH_STRING)PH_AUTO(PhGetWin32Message(GetLastError())))->Buffer ) == IDRETRY) { SetWindowLongPtr(hwndDlg, DWLP_MSGRESULT, PSNRET_INVALID); } } return TRUE; } } break; } return FALSE; } INT_PTR CALLBACK EspServiceRecovery2DlgProc( _In_ HWND hwndDlg, _In_ UINT uMsg, _In_ WPARAM wParam, _In_ LPARAM lParam ) { return FALSE; } INT_PTR CALLBACK RestartComputerDlgProc( _In_ HWND hwndDlg, _In_ UINT uMsg, _In_ WPARAM wParam, _In_ LPARAM lParam ) { PSERVICE_RECOVERY_CONTEXT context; if (uMsg == WM_INITDIALOG) { context = (PSERVICE_RECOVERY_CONTEXT)lParam; SetProp(hwndDlg, L"Context", (HANDLE)context); } else { context = (PSERVICE_RECOVERY_CONTEXT)GetProp(hwndDlg, L"Context"); if (uMsg == WM_DESTROY) RemoveProp(hwndDlg, L"Context"); } if (!context) return FALSE; switch (uMsg) { case WM_INITDIALOG: { PhCenterWindow(hwndDlg, GetParent(hwndDlg)); SetDlgItemInt(hwndDlg, IDC_RESTARTCOMPAFTER, context->RebootAfter / (1000 * 60), FALSE); // ms to min Button_SetCheck(GetDlgItem(hwndDlg, IDC_ENABLERESTARTMESSAGE), context->RebootMessage ? BST_CHECKED : BST_UNCHECKED); SetDlgItemText(hwndDlg, IDC_RESTARTMESSAGE, PhGetString(context->RebootMessage)); SendMessage(hwndDlg, WM_NEXTDLGCTL, (WPARAM)GetDlgItem(hwndDlg, IDC_RESTARTCOMPAFTER), TRUE); Edit_SetSel(GetDlgItem(hwndDlg, IDC_RESTARTCOMPAFTER), 0, -1); } break; case WM_COMMAND: { switch (LOWORD(wParam)) { case IDCANCEL: EndDialog(hwndDlg, IDCANCEL); break; case IDOK: { context->RebootAfter = GetDlgItemInt(hwndDlg, IDC_RESTARTCOMPAFTER, NULL, FALSE) * 1000 * 60; if (Button_GetCheck(GetDlgItem(hwndDlg, IDC_ENABLERESTARTMESSAGE)) == BST_CHECKED) PhMoveReference(&context->RebootMessage, PhGetWindowText(GetDlgItem(hwndDlg, IDC_RESTARTMESSAGE))); else PhClearReference(&context->RebootMessage); context->Dirty = TRUE; EndDialog(hwndDlg, IDOK); } break; case IDC_USEDEFAULTMESSAGE: { PPH_STRING message; PWSTR computerName; ULONG bufferSize; BOOLEAN allocated = TRUE; // Get the computer name. bufferSize = 64; computerName = PhAllocate((bufferSize + 1) * sizeof(WCHAR)); if (!GetComputerName(computerName, &bufferSize)) { PhFree(computerName); computerName = PhAllocate((bufferSize + 1) * sizeof(WCHAR)); if (!GetComputerName(computerName, &bufferSize)) { PhFree(computerName); computerName = L"(unknown)"; allocated = FALSE; } } // This message is exactly the same as the one in the Services console, // except the double spaces are replaced by single spaces. message = PhaFormatString( L"Your computer is connected to the computer named %s. " L"The %s service on %s has ended unexpectedly. " L"%s will restart automatically, and then you can reestablish the connection.", computerName, context->ServiceItem->Name->Buffer, computerName, computerName ); SetDlgItemText(hwndDlg, IDC_RESTARTMESSAGE, message->Buffer); if (allocated) PhFree(computerName); Button_SetCheck(GetDlgItem(hwndDlg, IDC_ENABLERESTARTMESSAGE), BST_CHECKED); } break; case IDC_RESTARTMESSAGE: { if (HIWORD(wParam) == EN_CHANGE) { // A zero length restart message disables it, so we might as well uncheck the box. Button_SetCheck(GetDlgItem(hwndDlg, IDC_ENABLERESTARTMESSAGE), GetWindowTextLength(GetDlgItem(hwndDlg, IDC_RESTARTMESSAGE)) != 0 ? BST_CHECKED : BST_UNCHECKED); } } break; } } break; } return FALSE; }