mirror of https://github.com/tongzx/nt5src
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1065 lines
30 KiB
1065 lines
30 KiB
/*++
|
|
|
|
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
|
|
);
|
|
}
|