1066 lines
30 KiB
C++
1066 lines
30 KiB
C++
/*++
|
||
|
||
Copyright (c) 1992 Microsoft Corporation
|
||
|
||
Module Name:
|
||
|
||
crash.cxx
|
||
|
||
Abstract:
|
||
|
||
Contains code concerned with recovery actions that are taken when
|
||
a service crashes. This file contains the following functions:
|
||
ScQueueRecoveryAction
|
||
CCrashRecord::IncrementCount
|
||
CRestartContext::Perform
|
||
CRebootMessageContext::Perform
|
||
CRebootContext::Perform
|
||
CRunCommandContext::Perform
|
||
|
||
Author:
|
||
|
||
Anirudh Sahni (anirudhs) 02-Dec-1996
|
||
|
||
Environment:
|
||
|
||
User Mode -Win32
|
||
|
||
Revision History:
|
||
|
||
22-Oct-1998 jschwart
|
||
Convert SCM to use NT thread pool APIs
|
||
|
||
02-Dec-1996 AnirudhS
|
||
Created
|
||
|
||
--*/
|
||
|
||
//
|
||
// INCLUDES
|
||
//
|
||
|
||
#include "precomp.hxx"
|
||
#include <lmcons.h> // needed for other lm headers
|
||
#include <lmerr.h> // NERR_Success
|
||
#include <lmshare.h> // NetSessionEnum
|
||
#include <lmmsg.h> // NetMessageBufferSend
|
||
#include <lmapibuf.h> // NetApiBufferFree
|
||
#include <valid.h> // ACTION_TYPE_INVALID
|
||
#include <svcslib.h> // CWorkItemContext
|
||
#include "smartp.h" // CHeapPtr
|
||
#include "scconfig.h" // ScReadFailureActions, etc.
|
||
#include "depend.h" // ScStartServiceAndDependencies
|
||
#include "account.h" // ScLogonService
|
||
#include "scseclib.h" // ScCreateAndSetSD
|
||
#include "start.h" // ScAllowInteractiveServices, ScInitStartupInfo
|
||
#include "resource.h" // IDS_SC_ACTION_BASE
|
||
|
||
//
|
||
// Defines and Typedefs
|
||
//
|
||
#define FILETIMES_PER_SEC ((__int64) 10000000) // (1 second)/(100 ns)
|
||
#define LENGTH(array) (sizeof(array)/sizeof((array)[0]))
|
||
|
||
//
|
||
// Globals
|
||
//
|
||
|
||
|
||
//
|
||
// Local Function Prototypes
|
||
//
|
||
VOID
|
||
ScLogRecoveryFailure(
|
||
IN SC_ACTION_TYPE ActionType,
|
||
IN LPCWSTR ServiceDisplayName,
|
||
IN DWORD Error
|
||
);
|
||
|
||
inline LPWSTR
|
||
LocalDup(
|
||
LPCWSTR String
|
||
)
|
||
{
|
||
LPWSTR Dup = (LPWSTR) LocalAlloc(0, WCSSIZE(String));
|
||
if (Dup != NULL)
|
||
{
|
||
wcscpy(Dup, String);
|
||
}
|
||
return Dup;
|
||
}
|
||
|
||
//
|
||
// Callback context for restarting a service
|
||
//
|
||
class CRestartContext : public CWorkItemContext
|
||
{
|
||
DECLARE_CWorkItemContext
|
||
public:
|
||
CRestartContext(
|
||
IN LPSERVICE_RECORD ServiceRecord
|
||
) :
|
||
_ServiceRecord(ServiceRecord)
|
||
{
|
||
ServiceRecord->UseCount++;
|
||
SC_LOG2(USECOUNT, "CRestartContext: %ws increment "
|
||
"USECOUNT=%lu\n", ServiceRecord->ServiceName,
|
||
ServiceRecord->UseCount);
|
||
}
|
||
|
||
~CRestartContext()
|
||
{
|
||
CServiceRecordExclusiveLock RLock;
|
||
ScDecrementUseCountAndDelete(_ServiceRecord);
|
||
}
|
||
|
||
private:
|
||
LPSERVICE_RECORD _ServiceRecord;
|
||
};
|
||
|
||
//
|
||
// Callback context for broadcasting a reboot message
|
||
//
|
||
class CRebootMessageContext : public CWorkItemContext
|
||
{
|
||
DECLARE_CWorkItemContext
|
||
public:
|
||
CRebootMessageContext(
|
||
IN LPWSTR RebootMessage,
|
||
IN DWORD Delay,
|
||
IN LPWSTR DisplayName
|
||
) :
|
||
_RebootMessage(RebootMessage),
|
||
_Delay(Delay),
|
||
_DisplayName(LocalDup(DisplayName))
|
||
{ }
|
||
|
||
~CRebootMessageContext()
|
||
{
|
||
LocalFree(_RebootMessage);
|
||
}
|
||
|
||
private:
|
||
LPWSTR _RebootMessage;
|
||
DWORD _Delay;
|
||
LPWSTR _DisplayName;
|
||
};
|
||
|
||
//
|
||
// Callback context for a reboot
|
||
// (The service name is used only for logging)
|
||
//
|
||
class CRebootContext : public CWorkItemContext
|
||
{
|
||
DECLARE_CWorkItemContext
|
||
public:
|
||
CRebootContext(
|
||
IN DWORD ActionDelay,
|
||
IN LPWSTR DisplayName
|
||
) :
|
||
_Delay(ActionDelay),
|
||
_DisplayName(DisplayName)
|
||
{ }
|
||
|
||
~CRebootContext()
|
||
{
|
||
LocalFree(_DisplayName);
|
||
}
|
||
|
||
private:
|
||
DWORD _Delay;
|
||
LPWSTR _DisplayName;
|
||
};
|
||
|
||
//
|
||
// Callback context for running a recovery command
|
||
//
|
||
class CRunCommandContext : public CWorkItemContext
|
||
{
|
||
DECLARE_CWorkItemContext
|
||
public:
|
||
CRunCommandContext(
|
||
IN LPSERVICE_RECORD ServiceRecord,
|
||
IN LPWSTR FailureCommand
|
||
) :
|
||
_ServiceRecord(ServiceRecord),
|
||
_FailureCommand(FailureCommand)
|
||
{
|
||
//
|
||
// The service record is used to get the
|
||
// account name to run the command in.
|
||
//
|
||
ServiceRecord->UseCount++;
|
||
SC_LOG2(USECOUNT, "CRunCommandContext: %ws increment "
|
||
"USECOUNT=%lu\n", ServiceRecord->ServiceName,
|
||
ServiceRecord->UseCount);
|
||
}
|
||
|
||
~CRunCommandContext()
|
||
{
|
||
LocalFree(_FailureCommand);
|
||
CServiceRecordExclusiveLock RLock;
|
||
ScDecrementUseCountAndDelete(_ServiceRecord);
|
||
}
|
||
|
||
private:
|
||
LPSERVICE_RECORD _ServiceRecord;
|
||
LPWSTR _FailureCommand;
|
||
};
|
||
|
||
|
||
|
||
/****************************************************************************/
|
||
|
||
VOID
|
||
ScQueueRecoveryAction(
|
||
IN LPSERVICE_RECORD ServiceRecord
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
|
||
Arguments:
|
||
|
||
|
||
Return Value:
|
||
|
||
none.
|
||
|
||
--*/
|
||
{
|
||
SC_ACTION_TYPE ActionType = SC_ACTION_NONE;
|
||
DWORD ActionDelay = 0;
|
||
DWORD FailNum = 1;
|
||
NTSTATUS ntStatus;
|
||
|
||
//
|
||
// See if there is any recovery action configured for this service.
|
||
//
|
||
HKEY Key = NULL;
|
||
{
|
||
DWORD ResetPeriod = INFINITE;
|
||
LPSERVICE_FAILURE_ACTIONS_WOW64 psfa = NULL;
|
||
|
||
DWORD Error = ScOpenServiceConfigKey(
|
||
ServiceRecord->ServiceName,
|
||
KEY_READ,
|
||
FALSE, // don't create if missing
|
||
&Key
|
||
);
|
||
|
||
if (Error == ERROR_SUCCESS)
|
||
{
|
||
Error = ScReadFailureActions(Key, &psfa);
|
||
}
|
||
|
||
if (Error != ERROR_SUCCESS)
|
||
{
|
||
SC_LOG(ERROR, "Couldn't read service's failure actions, %lu\n", Error);
|
||
}
|
||
else if (psfa != NULL && psfa->cActions > 0)
|
||
{
|
||
ResetPeriod = psfa->dwResetPeriod;
|
||
}
|
||
|
||
//
|
||
// Allocate a crash record for the service.
|
||
// Increment the service's crash count, subject to the reset period
|
||
// we just read from the registry (INFINITE if we read none).
|
||
//
|
||
if (ServiceRecord->CrashRecord == NULL)
|
||
{
|
||
ServiceRecord->CrashRecord = new CCrashRecord;
|
||
}
|
||
|
||
if (ServiceRecord->CrashRecord == NULL)
|
||
{
|
||
SC_LOG0(ERROR, "Couldn't allocate service's crash record\n");
|
||
//
|
||
// NOTE: We still continue, taking the failure count to be 1.
|
||
// (The crash record is used only in the "else" clause.)
|
||
//
|
||
}
|
||
else
|
||
{
|
||
FailNum = ServiceRecord->CrashRecord->IncrementCount(ResetPeriod);
|
||
}
|
||
|
||
//
|
||
// Figure out which recovery action we're going to take.
|
||
//
|
||
if (psfa != NULL && psfa->cActions > 0)
|
||
{
|
||
SC_ACTION * lpsaActions = (SC_ACTION *) ((LPBYTE) psfa + psfa->dwsaActionsOffset);
|
||
DWORD i = min(FailNum, psfa->cActions);
|
||
|
||
ActionType = lpsaActions[i - 1].Type;
|
||
ActionDelay = lpsaActions[i - 1].Delay;
|
||
|
||
if (ACTION_TYPE_INVALID(ActionType))
|
||
{
|
||
SC_LOG(ERROR, "Service has invalid action type %lu\n", ActionType);
|
||
ActionType = SC_ACTION_NONE;
|
||
}
|
||
}
|
||
|
||
LocalFree(psfa);
|
||
}
|
||
|
||
//
|
||
// Log an event about this service failing, and about the proposed
|
||
// recovery action.
|
||
//
|
||
|
||
if (ActionType != SC_ACTION_NONE)
|
||
{
|
||
WCHAR wszActionString[50];
|
||
if (!LoadString(GetModuleHandle(NULL),
|
||
IDS_SC_ACTION_BASE + ActionType,
|
||
wszActionString,
|
||
LENGTH(wszActionString)))
|
||
{
|
||
SC_LOG(ERROR, "LoadString failed %lu\n", GetLastError());
|
||
wszActionString[0] = L'\0';
|
||
}
|
||
|
||
SC_LOG2(ERROR, "The following recovery action will be taken in %d ms: %ws.\n",
|
||
ActionDelay, wszActionString);
|
||
|
||
ScLogEvent(NEVENT_SERVICE_CRASH,
|
||
ServiceRecord->DisplayName,
|
||
FailNum,
|
||
ActionDelay,
|
||
ActionType,
|
||
wszActionString);
|
||
}
|
||
else
|
||
{
|
||
ScLogEvent(NEVENT_SERVICE_CRASH_NO_ACTION,
|
||
ServiceRecord->DisplayName,
|
||
FailNum);
|
||
}
|
||
|
||
//
|
||
// Queue a work item that will actually carry out the action after the
|
||
// delay has elapsed.
|
||
//
|
||
switch (ActionType)
|
||
{
|
||
case SC_ACTION_NONE:
|
||
break;
|
||
|
||
case SC_ACTION_RESTART:
|
||
{
|
||
CRestartContext * pCtx = new CRestartContext(ServiceRecord);
|
||
if (pCtx == NULL)
|
||
{
|
||
SC_LOG0(ERROR, "Couldn't allocate restart context\n");
|
||
break;
|
||
}
|
||
|
||
ntStatus = pCtx->AddDelayedWorkItem(ActionDelay,
|
||
WT_EXECUTEONLYONCE);
|
||
|
||
if (!NT_SUCCESS(ntStatus))
|
||
{
|
||
SC_LOG(ERROR, "Couldn't add restart work item 0x%x\n", ntStatus);
|
||
delete pCtx;
|
||
}
|
||
|
||
break;
|
||
}
|
||
|
||
case SC_ACTION_REBOOT:
|
||
{
|
||
//
|
||
// Get the reboot message for the service, if any
|
||
//
|
||
LPWSTR RebootMessage = NULL;
|
||
ScReadRebootMessage(Key, &RebootMessage);
|
||
if (RebootMessage != NULL)
|
||
{
|
||
//
|
||
// Broadcast the message to all users. Do this in a separate
|
||
// thread so that we can release our exclusive lock on the
|
||
// service database quickly.
|
||
//
|
||
CRebootMessageContext * pCtx = new CRebootMessageContext(
|
||
RebootMessage,
|
||
ActionDelay,
|
||
ServiceRecord->DisplayName
|
||
);
|
||
if (pCtx == NULL)
|
||
{
|
||
SC_LOG0(ERROR, "Couldn't allocate restart context\n");
|
||
LocalFree(RebootMessage);
|
||
break;
|
||
}
|
||
|
||
ntStatus = pCtx->AddWorkItem(WT_EXECUTEONLYONCE);
|
||
|
||
if (!NT_SUCCESS(ntStatus))
|
||
{
|
||
SC_LOG(ERROR, "Couldn't add restart work item 0x%x\n", ntStatus);
|
||
delete pCtx;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
//
|
||
// Queue a work item to perform the reboot after the delay has
|
||
// elapsed.
|
||
// (CODEWORK Share this code with CRebootMessageContext::Perform)
|
||
//
|
||
LPWSTR DisplayNameCopy = LocalDup(ServiceRecord->DisplayName);
|
||
CRebootContext * pCtx = new CRebootContext(
|
||
ActionDelay,
|
||
DisplayNameCopy
|
||
);
|
||
if (pCtx == NULL)
|
||
{
|
||
SC_LOG0(ERROR, "Couldn't allocate reboot context\n");
|
||
LocalFree(DisplayNameCopy);
|
||
}
|
||
else
|
||
{
|
||
ntStatus = pCtx->AddWorkItem(WT_EXECUTEONLYONCE);
|
||
|
||
if (!NT_SUCCESS(ntStatus))
|
||
{
|
||
SC_LOG(ERROR, "Couldn't add reboot work item 0x%x\n", ntStatus);
|
||
delete pCtx;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
break;
|
||
|
||
case SC_ACTION_RUN_COMMAND:
|
||
{
|
||
//
|
||
// Get the failure command for the service, if any
|
||
//
|
||
CHeapPtr<LPWSTR> FailureCommand;
|
||
ScReadFailureCommand(Key, &FailureCommand);
|
||
if (FailureCommand == NULL)
|
||
{
|
||
SC_LOG0(ERROR, "Asked to run a failure command, but found "
|
||
"none for this service\n");
|
||
ScLogRecoveryFailure(
|
||
SC_ACTION_RUN_COMMAND,
|
||
ServiceRecord->DisplayName,
|
||
ERROR_NO_RECOVERY_PROGRAM
|
||
);
|
||
break;
|
||
}
|
||
|
||
//
|
||
// Replace %1% in the failure command with the failure count.
|
||
// (FormatMessage is *useless* for this purpose because it AV's
|
||
// if the failure command contains a %2, %3 etc.!)
|
||
//
|
||
UNICODE_STRING Formatted;
|
||
{
|
||
UNICODE_STRING Unformatted;
|
||
RtlInitUnicodeString(&Unformatted, FailureCommand);
|
||
|
||
Formatted.Length = 0;
|
||
Formatted.MaximumLength = Unformatted.MaximumLength + 200;
|
||
Formatted.Buffer =
|
||
(LPWSTR) LocalAlloc(0, Formatted.MaximumLength);
|
||
if (Formatted.Buffer == NULL)
|
||
{
|
||
SC_LOG(ERROR, "Couldn't allocate formatted string, %lu\n", GetLastError());
|
||
break;
|
||
}
|
||
|
||
WCHAR Environment[30];
|
||
wsprintf(Environment, L"1=%lu%c", FailNum, L'\0');
|
||
|
||
NTSTATUS ntstatus = RtlExpandEnvironmentStrings_U(
|
||
Environment,
|
||
&Unformatted,
|
||
&Formatted,
|
||
NULL);
|
||
|
||
if (!NT_SUCCESS(ntstatus))
|
||
{
|
||
SC_LOG(ERROR, "RtlExpandEnvironmentStrings_U failed %#lx\n", ntstatus);
|
||
wcscpy(Formatted.Buffer, FailureCommand);
|
||
}
|
||
}
|
||
|
||
CRunCommandContext * pCtx =
|
||
new CRunCommandContext(ServiceRecord, Formatted.Buffer);
|
||
if (pCtx == NULL)
|
||
{
|
||
SC_LOG0(ERROR, "Couldn't allocate RunCommand context\n");
|
||
LocalFree(Formatted.Buffer);
|
||
break;
|
||
}
|
||
|
||
ntStatus = pCtx->AddDelayedWorkItem(ActionDelay,
|
||
WT_EXECUTEONLYONCE);
|
||
|
||
if (!NT_SUCCESS(ntStatus))
|
||
{
|
||
SC_LOG(ERROR, "Couldn't add RunCommand work item 0x%x\n", ntStatus);
|
||
delete pCtx;
|
||
}
|
||
}
|
||
break;
|
||
|
||
default:
|
||
SC_ASSERT(0);
|
||
}
|
||
|
||
if (Key != NULL)
|
||
{
|
||
ScRegCloseKey(Key);
|
||
}
|
||
}
|
||
|
||
|
||
|
||
DWORD
|
||
CCrashRecord::IncrementCount(
|
||
DWORD ResetSeconds
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Increments a service's crash count.
|
||
|
||
Arguments:
|
||
|
||
ResetSeconds - Length, in seconds, of a period of no crashes after which
|
||
the crash count should be reset to zero.
|
||
|
||
Return Value:
|
||
|
||
The service's new crash count.
|
||
|
||
--*/
|
||
{
|
||
__int64 SecondLastCrashTime = _LastCrashTime;
|
||
GetSystemTimeAsFileTime((FILETIME *) &_LastCrashTime);
|
||
|
||
if (ResetSeconds == INFINITE ||
|
||
SecondLastCrashTime + ResetSeconds * FILETIMES_PER_SEC > _LastCrashTime)
|
||
{
|
||
_Count++;
|
||
}
|
||
else
|
||
{
|
||
SC_LOG(CONFIG_API, "More than %lu seconds have elapsed since last "
|
||
"crash, resetting crash count.\n",
|
||
ResetSeconds);
|
||
_Count = 1;
|
||
}
|
||
|
||
SC_LOG(CONFIG_API, "Service's crash count is now %lu\n", _Count);
|
||
return _Count;
|
||
}
|
||
|
||
|
||
|
||
VOID
|
||
CRestartContext::Perform(
|
||
IN BOOLEAN fWaitStatus
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
--*/
|
||
{
|
||
//
|
||
// Make sure we were called because of a timeout
|
||
//
|
||
SC_ASSERT(fWaitStatus == TRUE);
|
||
|
||
SC_LOG(CONFIG_API, "Restarting %ws service...\n", _ServiceRecord->ServiceName);
|
||
|
||
RemoveDelayedWorkItem();
|
||
|
||
//
|
||
// CODEWORK Allow arguments to the service.
|
||
//
|
||
DWORD status = ScStartServiceAndDependencies(_ServiceRecord, 0, NULL, FALSE);
|
||
|
||
if (status == NO_ERROR)
|
||
{
|
||
status = _ServiceRecord->StartError;
|
||
SC_LOG(CONFIG_API, "ScStartServiceAndDependencies succeeded, StartError = %lu\n",
|
||
status);
|
||
}
|
||
else
|
||
{
|
||
SC_LOG(CONFIG_API, "ScStartServiceAndDependencies failed, %lu\n", status);
|
||
//
|
||
// Should we treat ERROR_SERVICE_ALREADY_RUNNING as a success?
|
||
// No, because it could alert the administrator to a less-than-
|
||
// optimal system configuration wherein something else is
|
||
// restarting the service.
|
||
//
|
||
ScLogRecoveryFailure(
|
||
SC_ACTION_RESTART,
|
||
_ServiceRecord->DisplayName,
|
||
status
|
||
);
|
||
}
|
||
|
||
delete this;
|
||
}
|
||
|
||
|
||
|
||
VOID
|
||
CRebootMessageContext::Perform(
|
||
IN BOOLEAN fWaitStatus
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
--*/
|
||
{
|
||
//
|
||
// Broadcast the reboot message to all users
|
||
//
|
||
SESSION_INFO_0 * Buffer = NULL;
|
||
DWORD EntriesRead = 0, TotalEntries = 0;
|
||
NTSTATUS ntStatus;
|
||
|
||
NET_API_STATUS Status = NetSessionEnum(
|
||
NULL, // servername
|
||
NULL, // UncClientName
|
||
NULL, // username
|
||
0, // level
|
||
(LPBYTE *) &Buffer,
|
||
0xFFFFFFFF, // prefmaxlen
|
||
&EntriesRead,
|
||
&TotalEntries,
|
||
NULL // resume_handle
|
||
);
|
||
|
||
if (EntriesRead > 0)
|
||
{
|
||
SC_ASSERT(EntriesRead == TotalEntries);
|
||
SC_ASSERT(Status == NERR_Success);
|
||
WCHAR ComputerName[MAX_COMPUTERNAME_LENGTH + 1];
|
||
DWORD nSize = LENGTH(ComputerName);
|
||
if (!GetComputerName(ComputerName, &nSize))
|
||
{
|
||
SC_LOG(ERROR, "GetComputerName failed! %lu\n", GetLastError());
|
||
}
|
||
else
|
||
{
|
||
DWORD MsgLen = (DWORD) WCSSIZE(_RebootMessage);
|
||
for (DWORD i = 0; i < EntriesRead; i++)
|
||
{
|
||
Status = NetMessageBufferSend(
|
||
NULL, // servername
|
||
Buffer[i].sesi0_cname, // msgname
|
||
ComputerName, // fromname
|
||
(LPBYTE) _RebootMessage,// buf
|
||
MsgLen // buflen
|
||
);
|
||
if (Status != NERR_Success)
|
||
{
|
||
SC_LOG2(ERROR, "NetMessageBufferSend to %ws failed %lu\n",
|
||
Buffer[i].sesi0_cname, Status);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
else if (Status != NERR_Success)
|
||
{
|
||
SC_LOG(ERROR, "NetSessionEnum failed %lu\n", Status);
|
||
}
|
||
|
||
if (Buffer != NULL)
|
||
{
|
||
NetApiBufferFree(Buffer);
|
||
}
|
||
|
||
//
|
||
// Queue a work item to perform the reboot after the delay has elapsed.
|
||
// Note: We're counting the delay from the time that the broadcast finished.
|
||
//
|
||
CRebootContext * pCtx = new CRebootContext(_Delay, _DisplayName);
|
||
if (pCtx == NULL)
|
||
{
|
||
SC_LOG0(ERROR, "Couldn't allocate reboot context\n");
|
||
}
|
||
else
|
||
{
|
||
_DisplayName = NULL; // pCtx will free it
|
||
|
||
ntStatus = pCtx->AddWorkItem(WT_EXECUTEONLYONCE);
|
||
|
||
if (!NT_SUCCESS(ntStatus))
|
||
{
|
||
SC_LOG(ERROR, "Couldn't add reboot work item 0x%x\n", ntStatus);
|
||
delete pCtx;
|
||
}
|
||
}
|
||
|
||
delete this;
|
||
}
|
||
|
||
|
||
|
||
VOID
|
||
CRebootContext::Perform(
|
||
IN BOOLEAN fWaitStatus
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
--*/
|
||
{
|
||
SC_LOG0(CONFIG_API, "Rebooting machine...\n");
|
||
// Write an event log entry?
|
||
|
||
//
|
||
// Enable our shutdown privilege. Since we are shutting down, don't
|
||
// bother doing it for only the current thread and don't bother
|
||
// disabling it afterwards.
|
||
//
|
||
BOOLEAN WasEnabled;
|
||
NTSTATUS Status = RtlAdjustPrivilege(
|
||
SE_SHUTDOWN_PRIVILEGE,
|
||
TRUE, // enable
|
||
FALSE, // this thread only? - No
|
||
&WasEnabled);
|
||
|
||
if (!NT_SUCCESS(Status))
|
||
{
|
||
SC_LOG(ERROR, "RtlAdjustPrivilege failed! %#lx\n", Status);
|
||
SC_ASSERT(0);
|
||
}
|
||
else
|
||
{
|
||
WCHAR wszShutdownText[128];
|
||
WCHAR wszPrintableText[128 + MAX_SERVICE_NAME_LENGTH];
|
||
|
||
if (LoadString(GetModuleHandle(NULL),
|
||
IDS_SC_REBOOT_MESSAGE,
|
||
wszShutdownText,
|
||
LENGTH(wszShutdownText)))
|
||
{
|
||
wsprintf(wszPrintableText, wszShutdownText, _DisplayName);
|
||
}
|
||
else
|
||
{
|
||
//
|
||
// If LoadString failed, it probably means the buffer
|
||
// is too small to hold the localized string
|
||
//
|
||
SC_LOG(ERROR, "LoadString failed! %lu\n", GetLastError());
|
||
SC_ASSERT(FALSE);
|
||
|
||
wszShutdownText[0] = L'\0';
|
||
}
|
||
|
||
|
||
if (!InitiateSystemShutdown(NULL, // machine name
|
||
wszPrintableText, // reboot message
|
||
_Delay / 1000, // timeout in seconds
|
||
TRUE, // force apps closed
|
||
TRUE)) // reboot
|
||
{
|
||
DWORD dwError = GetLastError();
|
||
|
||
//
|
||
// If two services fail simultaneously and both are configured
|
||
// to reboot the machine, InitiateSystemShutdown will fail all
|
||
// calls past the first with ERROR_SHUTDOWN_IN_PROGRESS. We
|
||
// don't want to log an event in this case.
|
||
//
|
||
if (dwError != ERROR_SHUTDOWN_IN_PROGRESS) {
|
||
|
||
SC_LOG(ERROR, "InitiateSystemShutdown failed! %lu\n", dwError);
|
||
ScLogRecoveryFailure(
|
||
SC_ACTION_REBOOT,
|
||
_DisplayName,
|
||
dwError
|
||
);
|
||
}
|
||
}
|
||
}
|
||
|
||
delete this;
|
||
}
|
||
|
||
|
||
|
||
VOID
|
||
CRunCommandContext::Perform(
|
||
IN BOOLEAN fWaitStatus
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
CODEWORK Share this code with ScLogonAndStartImage
|
||
|
||
--*/
|
||
{
|
||
//
|
||
// Make sure we were called because of a timeout
|
||
//
|
||
SC_ASSERT(fWaitStatus == TRUE);
|
||
|
||
DWORD status = NO_ERROR;
|
||
|
||
HANDLE Token = NULL;
|
||
PSID ServiceSid = NULL; // SID is returned only if not LocalSystem
|
||
LPWSTR AccountName = NULL;
|
||
SECURITY_ATTRIBUTES SaProcess; // Process security info (used only if not LocalSystem)
|
||
STARTUPINFOW StartupInfo;
|
||
PROCESS_INFORMATION ProcessInfo;
|
||
|
||
RemoveDelayedWorkItem();
|
||
|
||
//
|
||
// Get the Account Name for the service. A NULL Account Name means the
|
||
// service is configured to run in the LocalSystem account.
|
||
//
|
||
status = ScLookupServiceAccount(
|
||
_ServiceRecord->ServiceName,
|
||
&AccountName
|
||
);
|
||
|
||
// We only need to log on if it's not the LocalSystem account
|
||
if (AccountName != NULL)
|
||
{
|
||
//
|
||
// CODEWORK: Keep track of recovery EXEs spawned so we can
|
||
// load/unload the user profile for the process.
|
||
//
|
||
status = ScLogonService(
|
||
_ServiceRecord->ServiceName,
|
||
AccountName,
|
||
&Token,
|
||
NULL,
|
||
&ServiceSid
|
||
);
|
||
|
||
if (status != NO_ERROR)
|
||
{
|
||
SC_LOG(ERROR, "CRunCommandContext: ScLogonService failed, %lu\n", status);
|
||
goto Clean0;
|
||
}
|
||
|
||
SaProcess.nLength = sizeof(SECURITY_ATTRIBUTES);
|
||
SaProcess.bInheritHandle = FALSE;
|
||
|
||
SC_ACE_DATA AceData[] =
|
||
{
|
||
{ACCESS_ALLOWED_ACE_TYPE, 0, 0,
|
||
PROCESS_ALL_ACCESS, &ServiceSid},
|
||
|
||
{ACCESS_ALLOWED_ACE_TYPE, 0, 0,
|
||
PROCESS_SET_INFORMATION |
|
||
PROCESS_TERMINATE |
|
||
SYNCHRONIZE, &LocalSystemSid}
|
||
};
|
||
|
||
NTSTATUS ntstatus = ScCreateAndSetSD(
|
||
AceData, // AceData
|
||
LENGTH(AceData), // AceCount
|
||
NULL, // OwnerSid (optional)
|
||
NULL, // GroupSid (optional)
|
||
&SaProcess.lpSecurityDescriptor
|
||
// pNewDescriptor
|
||
);
|
||
|
||
LocalFree(ServiceSid);
|
||
|
||
if (! NT_SUCCESS(ntstatus))
|
||
{
|
||
SC_LOG(ERROR, "CRunCommandContext: ScCreateAndSetSD failed %#lx\n", ntstatus);
|
||
status = RtlNtStatusToDosError(ntstatus);
|
||
goto Clean1;
|
||
}
|
||
|
||
SC_LOG2(CONFIG_API,"CRunCommandContext: about to spawn recovery program in account %ws: %ws\n",
|
||
AccountName, _FailureCommand);
|
||
|
||
//
|
||
// Impersonate the user so we don't give access to
|
||
// EXEs that have been locked down for the account.
|
||
//
|
||
if (!ImpersonateLoggedOnUser(Token))
|
||
{
|
||
status = GetLastError();
|
||
|
||
SC_LOG1(ERROR,
|
||
"ScLogonAndStartImage: ImpersonateLoggedOnUser failed %d\n",
|
||
status);
|
||
|
||
goto Clean2;
|
||
}
|
||
|
||
|
||
//
|
||
// Spawn the Image Process
|
||
//
|
||
|
||
ScInitStartupInfo(&StartupInfo, FALSE);
|
||
|
||
if (!CreateProcessAsUserW(
|
||
Token, // logon token
|
||
NULL, // lpApplicationName
|
||
_FailureCommand, // lpCommandLine
|
||
&SaProcess, // process' security attributes
|
||
NULL, // first thread's security attributes
|
||
FALSE, // whether new process inherits handles
|
||
CREATE_NEW_CONSOLE, // creation flags
|
||
NULL, // environment block
|
||
NULL, // current directory
|
||
&StartupInfo, // startup info
|
||
&ProcessInfo // process info
|
||
))
|
||
{
|
||
status = GetLastError();
|
||
SC_LOG(ERROR, "CRunCommandContext: CreateProcessAsUser failed %lu\n", status);
|
||
RevertToSelf();
|
||
goto Clean2;
|
||
}
|
||
|
||
RevertToSelf();
|
||
}
|
||
else
|
||
{
|
||
//
|
||
// It's the LocalSystem account
|
||
//
|
||
|
||
//
|
||
// If the process is to be interactive, set the appropriate flags.
|
||
//
|
||
|
||
BOOL bInteractive = FALSE;
|
||
|
||
if (AccountName == NULL &&
|
||
_ServiceRecord->ServiceStatus.dwServiceType & SERVICE_INTERACTIVE_PROCESS)
|
||
{
|
||
bInteractive = ScAllowInteractiveServices();
|
||
|
||
if (!bInteractive)
|
||
{
|
||
//
|
||
// Write an event to indicate that an interactive service
|
||
// was started, but the system is configured to not allow
|
||
// services to be interactive.
|
||
//
|
||
|
||
ScLogEvent(NEVENT_SERVICE_NOT_INTERACTIVE,
|
||
_ServiceRecord->DisplayName);
|
||
}
|
||
}
|
||
|
||
ScInitStartupInfo(&StartupInfo, bInteractive);
|
||
|
||
SC_LOG1(CONFIG_API,"CRunCommandContext: about to spawn recovery program in "
|
||
"the LocalSystem account: %ws\n", _FailureCommand);
|
||
|
||
//
|
||
// Spawn the Image Process
|
||
//
|
||
|
||
if (!CreateProcessW(
|
||
NULL, // lpApplicationName
|
||
_FailureCommand, // lpCommandLine
|
||
NULL, // process' security attributes
|
||
NULL, // first thread's security attributes
|
||
FALSE, // whether new process inherits handles
|
||
CREATE_NEW_CONSOLE, // creation flags
|
||
NULL, // environment block
|
||
NULL, // current directory
|
||
&StartupInfo, // startup info
|
||
&ProcessInfo // process info
|
||
))
|
||
{
|
||
status = GetLastError();
|
||
SC_LOG(ERROR, "CRunCommandContext: CreateProcess failed %lu\n", status);
|
||
goto Clean2;
|
||
}
|
||
}
|
||
|
||
SC_LOG0(CONFIG_API, "Recovery program spawned successfully.\n");
|
||
|
||
CloseHandle(ProcessInfo.hThread);
|
||
CloseHandle(ProcessInfo.hProcess);
|
||
|
||
Clean2:
|
||
if (AccountName != NULL)
|
||
{
|
||
RtlDeleteSecurityObject(&SaProcess.lpSecurityDescriptor);
|
||
}
|
||
|
||
Clean1:
|
||
if (AccountName != NULL)
|
||
{
|
||
CloseHandle(Token);
|
||
}
|
||
|
||
Clean0:
|
||
if (status != NO_ERROR)
|
||
{
|
||
ScLogRecoveryFailure(
|
||
SC_ACTION_RUN_COMMAND,
|
||
_ServiceRecord->DisplayName,
|
||
status
|
||
);
|
||
}
|
||
|
||
delete this;
|
||
}
|
||
|
||
|
||
|
||
VOID
|
||
ScLogRecoveryFailure(
|
||
IN SC_ACTION_TYPE ActionType,
|
||
IN LPCWSTR ServiceDisplayName,
|
||
IN DWORD Error
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
--*/
|
||
{
|
||
WCHAR wszActionString[50];
|
||
if (!LoadString(GetModuleHandle(NULL),
|
||
IDS_SC_ACTION_BASE + ActionType,
|
||
wszActionString,
|
||
LENGTH(wszActionString)))
|
||
{
|
||
SC_LOG(ERROR, "LoadString failed %lu\n", GetLastError());
|
||
wszActionString[0] = L'\0';
|
||
}
|
||
|
||
ScLogEvent(
|
||
NEVENT_SERVICE_RECOVERY_FAILED,
|
||
ActionType,
|
||
wszActionString,
|
||
(LPWSTR) ServiceDisplayName,
|
||
Error
|
||
);
|
||
}
|