Source code of Windows XP (NT5)
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

  1. /*++
  2. Copyright (c) 1992 Microsoft Corporation
  3. Module Name:
  4. crash.cxx
  5. Abstract:
  6. Contains code concerned with recovery actions that are taken when
  7. a service crashes. This file contains the following functions:
  8. ScQueueRecoveryAction
  9. CCrashRecord::IncrementCount
  10. CRestartContext::Perform
  11. CRebootMessageContext::Perform
  12. CRebootContext::Perform
  13. CRunCommandContext::Perform
  14. Author:
  15. Anirudh Sahni (anirudhs) 02-Dec-1996
  16. Environment:
  17. User Mode -Win32
  18. Revision History:
  19. 22-Oct-1998 jschwart
  20. Convert SCM to use NT thread pool APIs
  21. 02-Dec-1996 AnirudhS
  22. Created
  23. --*/
  24. //
  25. // INCLUDES
  26. //
  27. #include "precomp.hxx"
  28. #include <lmcons.h> // needed for other lm headers
  29. #include <lmerr.h> // NERR_Success
  30. #include <lmshare.h> // NetSessionEnum
  31. #include <lmmsg.h> // NetMessageBufferSend
  32. #include <lmapibuf.h> // NetApiBufferFree
  33. #include <valid.h> // ACTION_TYPE_INVALID
  34. #include <svcslib.h> // CWorkItemContext
  35. #include "smartp.h" // CHeapPtr
  36. #include "scconfig.h" // ScReadFailureActions, etc.
  37. #include "depend.h" // ScStartServiceAndDependencies
  38. #include "account.h" // ScLogonService
  39. #include "scseclib.h" // ScCreateAndSetSD
  40. #include "start.h" // ScAllowInteractiveServices, ScInitStartupInfo
  41. #include "resource.h" // IDS_SC_ACTION_BASE
  42. //
  43. // Defines and Typedefs
  44. //
  45. #define FILETIMES_PER_SEC ((__int64) 10000000) // (1 second)/(100 ns)
  46. #define LENGTH(array) (sizeof(array)/sizeof((array)[0]))
  47. //
  48. // Globals
  49. //
  50. //
  51. // Local Function Prototypes
  52. //
  53. VOID
  54. ScLogRecoveryFailure(
  55. IN SC_ACTION_TYPE ActionType,
  56. IN LPCWSTR ServiceDisplayName,
  57. IN DWORD Error
  58. );
  59. inline LPWSTR
  60. LocalDup(
  61. LPCWSTR String
  62. )
  63. {
  64. LPWSTR Dup = (LPWSTR) LocalAlloc(0, WCSSIZE(String));
  65. if (Dup != NULL)
  66. {
  67. wcscpy(Dup, String);
  68. }
  69. return Dup;
  70. }
  71. //
  72. // Callback context for restarting a service
  73. //
  74. class CRestartContext : public CWorkItemContext
  75. {
  76. DECLARE_CWorkItemContext
  77. public:
  78. CRestartContext(
  79. IN LPSERVICE_RECORD ServiceRecord
  80. ) :
  81. _ServiceRecord(ServiceRecord)
  82. {
  83. ServiceRecord->UseCount++;
  84. SC_LOG2(USECOUNT, "CRestartContext: %ws increment "
  85. "USECOUNT=%lu\n", ServiceRecord->ServiceName,
  86. ServiceRecord->UseCount);
  87. }
  88. ~CRestartContext()
  89. {
  90. CServiceRecordExclusiveLock RLock;
  91. ScDecrementUseCountAndDelete(_ServiceRecord);
  92. }
  93. private:
  94. LPSERVICE_RECORD _ServiceRecord;
  95. };
  96. //
  97. // Callback context for broadcasting a reboot message
  98. //
  99. class CRebootMessageContext : public CWorkItemContext
  100. {
  101. DECLARE_CWorkItemContext
  102. public:
  103. CRebootMessageContext(
  104. IN LPWSTR RebootMessage,
  105. IN DWORD Delay,
  106. IN LPWSTR DisplayName
  107. ) :
  108. _RebootMessage(RebootMessage),
  109. _Delay(Delay),
  110. _DisplayName(LocalDup(DisplayName))
  111. { }
  112. ~CRebootMessageContext()
  113. {
  114. LocalFree(_RebootMessage);
  115. }
  116. private:
  117. LPWSTR _RebootMessage;
  118. DWORD _Delay;
  119. LPWSTR _DisplayName;
  120. };
  121. //
  122. // Callback context for a reboot
  123. // (The service name is used only for logging)
  124. //
  125. class CRebootContext : public CWorkItemContext
  126. {
  127. DECLARE_CWorkItemContext
  128. public:
  129. CRebootContext(
  130. IN DWORD ActionDelay,
  131. IN LPWSTR DisplayName
  132. ) :
  133. _Delay(ActionDelay),
  134. _DisplayName(DisplayName)
  135. { }
  136. ~CRebootContext()
  137. {
  138. LocalFree(_DisplayName);
  139. }
  140. private:
  141. DWORD _Delay;
  142. LPWSTR _DisplayName;
  143. };
  144. //
  145. // Callback context for running a recovery command
  146. //
  147. class CRunCommandContext : public CWorkItemContext
  148. {
  149. DECLARE_CWorkItemContext
  150. public:
  151. CRunCommandContext(
  152. IN LPSERVICE_RECORD ServiceRecord,
  153. IN LPWSTR FailureCommand
  154. ) :
  155. _ServiceRecord(ServiceRecord),
  156. _FailureCommand(FailureCommand)
  157. {
  158. //
  159. // The service record is used to get the
  160. // account name to run the command in.
  161. //
  162. ServiceRecord->UseCount++;
  163. SC_LOG2(USECOUNT, "CRunCommandContext: %ws increment "
  164. "USECOUNT=%lu\n", ServiceRecord->ServiceName,
  165. ServiceRecord->UseCount);
  166. }
  167. ~CRunCommandContext()
  168. {
  169. LocalFree(_FailureCommand);
  170. CServiceRecordExclusiveLock RLock;
  171. ScDecrementUseCountAndDelete(_ServiceRecord);
  172. }
  173. private:
  174. LPSERVICE_RECORD _ServiceRecord;
  175. LPWSTR _FailureCommand;
  176. };
  177. /****************************************************************************/
  178. VOID
  179. ScQueueRecoveryAction(
  180. IN LPSERVICE_RECORD ServiceRecord
  181. )
  182. /*++
  183. Routine Description:
  184. Arguments:
  185. Return Value:
  186. none.
  187. --*/
  188. {
  189. SC_ACTION_TYPE ActionType = SC_ACTION_NONE;
  190. DWORD ActionDelay = 0;
  191. DWORD FailNum = 1;
  192. NTSTATUS ntStatus;
  193. //
  194. // See if there is any recovery action configured for this service.
  195. //
  196. HKEY Key = NULL;
  197. {
  198. DWORD ResetPeriod = INFINITE;
  199. LPSERVICE_FAILURE_ACTIONS_WOW64 psfa = NULL;
  200. DWORD Error = ScOpenServiceConfigKey(
  201. ServiceRecord->ServiceName,
  202. KEY_READ,
  203. FALSE, // don't create if missing
  204. &Key
  205. );
  206. if (Error == ERROR_SUCCESS)
  207. {
  208. Error = ScReadFailureActions(Key, &psfa);
  209. }
  210. if (Error != ERROR_SUCCESS)
  211. {
  212. SC_LOG(ERROR, "Couldn't read service's failure actions, %lu\n", Error);
  213. }
  214. else if (psfa != NULL && psfa->cActions > 0)
  215. {
  216. ResetPeriod = psfa->dwResetPeriod;
  217. }
  218. //
  219. // Allocate a crash record for the service.
  220. // Increment the service's crash count, subject to the reset period
  221. // we just read from the registry (INFINITE if we read none).
  222. //
  223. if (ServiceRecord->CrashRecord == NULL)
  224. {
  225. ServiceRecord->CrashRecord = new CCrashRecord;
  226. }
  227. if (ServiceRecord->CrashRecord == NULL)
  228. {
  229. SC_LOG0(ERROR, "Couldn't allocate service's crash record\n");
  230. //
  231. // NOTE: We still continue, taking the failure count to be 1.
  232. // (The crash record is used only in the "else" clause.)
  233. //
  234. }
  235. else
  236. {
  237. FailNum = ServiceRecord->CrashRecord->IncrementCount(ResetPeriod);
  238. }
  239. //
  240. // Figure out which recovery action we're going to take.
  241. //
  242. if (psfa != NULL && psfa->cActions > 0)
  243. {
  244. SC_ACTION * lpsaActions = (SC_ACTION *) ((LPBYTE) psfa + psfa->dwsaActionsOffset);
  245. DWORD i = min(FailNum, psfa->cActions);
  246. ActionType = lpsaActions[i - 1].Type;
  247. ActionDelay = lpsaActions[i - 1].Delay;
  248. if (ACTION_TYPE_INVALID(ActionType))
  249. {
  250. SC_LOG(ERROR, "Service has invalid action type %lu\n", ActionType);
  251. ActionType = SC_ACTION_NONE;
  252. }
  253. }
  254. LocalFree(psfa);
  255. }
  256. //
  257. // Log an event about this service failing, and about the proposed
  258. // recovery action.
  259. //
  260. if (ActionType != SC_ACTION_NONE)
  261. {
  262. WCHAR wszActionString[50];
  263. if (!LoadString(GetModuleHandle(NULL),
  264. IDS_SC_ACTION_BASE + ActionType,
  265. wszActionString,
  266. LENGTH(wszActionString)))
  267. {
  268. SC_LOG(ERROR, "LoadString failed %lu\n", GetLastError());
  269. wszActionString[0] = L'\0';
  270. }
  271. SC_LOG2(ERROR, "The following recovery action will be taken in %d ms: %ws.\n",
  272. ActionDelay, wszActionString);
  273. ScLogEvent(NEVENT_SERVICE_CRASH,
  274. ServiceRecord->DisplayName,
  275. FailNum,
  276. ActionDelay,
  277. ActionType,
  278. wszActionString);
  279. }
  280. else
  281. {
  282. ScLogEvent(NEVENT_SERVICE_CRASH_NO_ACTION,
  283. ServiceRecord->DisplayName,
  284. FailNum);
  285. }
  286. //
  287. // Queue a work item that will actually carry out the action after the
  288. // delay has elapsed.
  289. //
  290. switch (ActionType)
  291. {
  292. case SC_ACTION_NONE:
  293. break;
  294. case SC_ACTION_RESTART:
  295. {
  296. CRestartContext * pCtx = new CRestartContext(ServiceRecord);
  297. if (pCtx == NULL)
  298. {
  299. SC_LOG0(ERROR, "Couldn't allocate restart context\n");
  300. break;
  301. }
  302. ntStatus = pCtx->AddDelayedWorkItem(ActionDelay,
  303. WT_EXECUTEONLYONCE);
  304. if (!NT_SUCCESS(ntStatus))
  305. {
  306. SC_LOG(ERROR, "Couldn't add restart work item 0x%x\n", ntStatus);
  307. delete pCtx;
  308. }
  309. break;
  310. }
  311. case SC_ACTION_REBOOT:
  312. {
  313. //
  314. // Get the reboot message for the service, if any
  315. //
  316. LPWSTR RebootMessage = NULL;
  317. ScReadRebootMessage(Key, &RebootMessage);
  318. if (RebootMessage != NULL)
  319. {
  320. //
  321. // Broadcast the message to all users. Do this in a separate
  322. // thread so that we can release our exclusive lock on the
  323. // service database quickly.
  324. //
  325. CRebootMessageContext * pCtx = new CRebootMessageContext(
  326. RebootMessage,
  327. ActionDelay,
  328. ServiceRecord->DisplayName
  329. );
  330. if (pCtx == NULL)
  331. {
  332. SC_LOG0(ERROR, "Couldn't allocate restart context\n");
  333. LocalFree(RebootMessage);
  334. break;
  335. }
  336. ntStatus = pCtx->AddWorkItem(WT_EXECUTEONLYONCE);
  337. if (!NT_SUCCESS(ntStatus))
  338. {
  339. SC_LOG(ERROR, "Couldn't add restart work item 0x%x\n", ntStatus);
  340. delete pCtx;
  341. }
  342. }
  343. else
  344. {
  345. //
  346. // Queue a work item to perform the reboot after the delay has
  347. // elapsed.
  348. // (CODEWORK Share this code with CRebootMessageContext::Perform)
  349. //
  350. LPWSTR DisplayNameCopy = LocalDup(ServiceRecord->DisplayName);
  351. CRebootContext * pCtx = new CRebootContext(
  352. ActionDelay,
  353. DisplayNameCopy
  354. );
  355. if (pCtx == NULL)
  356. {
  357. SC_LOG0(ERROR, "Couldn't allocate reboot context\n");
  358. LocalFree(DisplayNameCopy);
  359. }
  360. else
  361. {
  362. ntStatus = pCtx->AddWorkItem(WT_EXECUTEONLYONCE);
  363. if (!NT_SUCCESS(ntStatus))
  364. {
  365. SC_LOG(ERROR, "Couldn't add reboot work item 0x%x\n", ntStatus);
  366. delete pCtx;
  367. }
  368. }
  369. }
  370. }
  371. break;
  372. case SC_ACTION_RUN_COMMAND:
  373. {
  374. //
  375. // Get the failure command for the service, if any
  376. //
  377. CHeapPtr<LPWSTR> FailureCommand;
  378. ScReadFailureCommand(Key, &FailureCommand);
  379. if (FailureCommand == NULL)
  380. {
  381. SC_LOG0(ERROR, "Asked to run a failure command, but found "
  382. "none for this service\n");
  383. ScLogRecoveryFailure(
  384. SC_ACTION_RUN_COMMAND,
  385. ServiceRecord->DisplayName,
  386. ERROR_NO_RECOVERY_PROGRAM
  387. );
  388. break;
  389. }
  390. //
  391. // Replace %1% in the failure command with the failure count.
  392. // (FormatMessage is *useless* for this purpose because it AV's
  393. // if the failure command contains a %2, %3 etc.!)
  394. //
  395. UNICODE_STRING Formatted;
  396. {
  397. UNICODE_STRING Unformatted;
  398. RtlInitUnicodeString(&Unformatted, FailureCommand);
  399. Formatted.Length = 0;
  400. Formatted.MaximumLength = Unformatted.MaximumLength + 200;
  401. Formatted.Buffer =
  402. (LPWSTR) LocalAlloc(0, Formatted.MaximumLength);
  403. if (Formatted.Buffer == NULL)
  404. {
  405. SC_LOG(ERROR, "Couldn't allocate formatted string, %lu\n", GetLastError());
  406. break;
  407. }
  408. WCHAR Environment[30];
  409. wsprintf(Environment, L"1=%lu%c", FailNum, L'\0');
  410. NTSTATUS ntstatus = RtlExpandEnvironmentStrings_U(
  411. Environment,
  412. &Unformatted,
  413. &Formatted,
  414. NULL);
  415. if (!NT_SUCCESS(ntstatus))
  416. {
  417. SC_LOG(ERROR, "RtlExpandEnvironmentStrings_U failed %#lx\n", ntstatus);
  418. wcscpy(Formatted.Buffer, FailureCommand);
  419. }
  420. }
  421. CRunCommandContext * pCtx =
  422. new CRunCommandContext(ServiceRecord, Formatted.Buffer);
  423. if (pCtx == NULL)
  424. {
  425. SC_LOG0(ERROR, "Couldn't allocate RunCommand context\n");
  426. LocalFree(Formatted.Buffer);
  427. break;
  428. }
  429. ntStatus = pCtx->AddDelayedWorkItem(ActionDelay,
  430. WT_EXECUTEONLYONCE);
  431. if (!NT_SUCCESS(ntStatus))
  432. {
  433. SC_LOG(ERROR, "Couldn't add RunCommand work item 0x%x\n", ntStatus);
  434. delete pCtx;
  435. }
  436. }
  437. break;
  438. default:
  439. SC_ASSERT(0);
  440. }
  441. if (Key != NULL)
  442. {
  443. ScRegCloseKey(Key);
  444. }
  445. }
  446. DWORD
  447. CCrashRecord::IncrementCount(
  448. DWORD ResetSeconds
  449. )
  450. /*++
  451. Routine Description:
  452. Increments a service's crash count.
  453. Arguments:
  454. ResetSeconds - Length, in seconds, of a period of no crashes after which
  455. the crash count should be reset to zero.
  456. Return Value:
  457. The service's new crash count.
  458. --*/
  459. {
  460. __int64 SecondLastCrashTime = _LastCrashTime;
  461. GetSystemTimeAsFileTime((FILETIME *) &_LastCrashTime);
  462. if (ResetSeconds == INFINITE ||
  463. SecondLastCrashTime + ResetSeconds * FILETIMES_PER_SEC > _LastCrashTime)
  464. {
  465. _Count++;
  466. }
  467. else
  468. {
  469. SC_LOG(CONFIG_API, "More than %lu seconds have elapsed since last "
  470. "crash, resetting crash count.\n",
  471. ResetSeconds);
  472. _Count = 1;
  473. }
  474. SC_LOG(CONFIG_API, "Service's crash count is now %lu\n", _Count);
  475. return _Count;
  476. }
  477. VOID
  478. CRestartContext::Perform(
  479. IN BOOLEAN fWaitStatus
  480. )
  481. /*++
  482. Routine Description:
  483. --*/
  484. {
  485. //
  486. // Make sure we were called because of a timeout
  487. //
  488. SC_ASSERT(fWaitStatus == TRUE);
  489. SC_LOG(CONFIG_API, "Restarting %ws service...\n", _ServiceRecord->ServiceName);
  490. RemoveDelayedWorkItem();
  491. //
  492. // CODEWORK Allow arguments to the service.
  493. //
  494. DWORD status = ScStartServiceAndDependencies(_ServiceRecord, 0, NULL, FALSE);
  495. if (status == NO_ERROR)
  496. {
  497. status = _ServiceRecord->StartError;
  498. SC_LOG(CONFIG_API, "ScStartServiceAndDependencies succeeded, StartError = %lu\n",
  499. status);
  500. }
  501. else
  502. {
  503. SC_LOG(CONFIG_API, "ScStartServiceAndDependencies failed, %lu\n", status);
  504. //
  505. // Should we treat ERROR_SERVICE_ALREADY_RUNNING as a success?
  506. // No, because it could alert the administrator to a less-than-
  507. // optimal system configuration wherein something else is
  508. // restarting the service.
  509. //
  510. ScLogRecoveryFailure(
  511. SC_ACTION_RESTART,
  512. _ServiceRecord->DisplayName,
  513. status
  514. );
  515. }
  516. delete this;
  517. }
  518. VOID
  519. CRebootMessageContext::Perform(
  520. IN BOOLEAN fWaitStatus
  521. )
  522. /*++
  523. Routine Description:
  524. --*/
  525. {
  526. //
  527. // Broadcast the reboot message to all users
  528. //
  529. SESSION_INFO_0 * Buffer = NULL;
  530. DWORD EntriesRead = 0, TotalEntries = 0;
  531. NTSTATUS ntStatus;
  532. NET_API_STATUS Status = NetSessionEnum(
  533. NULL, // servername
  534. NULL, // UncClientName
  535. NULL, // username
  536. 0, // level
  537. (LPBYTE *) &Buffer,
  538. 0xFFFFFFFF, // prefmaxlen
  539. &EntriesRead,
  540. &TotalEntries,
  541. NULL // resume_handle
  542. );
  543. if (EntriesRead > 0)
  544. {
  545. SC_ASSERT(EntriesRead == TotalEntries);
  546. SC_ASSERT(Status == NERR_Success);
  547. WCHAR ComputerName[MAX_COMPUTERNAME_LENGTH + 1];
  548. DWORD nSize = LENGTH(ComputerName);
  549. if (!GetComputerName(ComputerName, &nSize))
  550. {
  551. SC_LOG(ERROR, "GetComputerName failed! %lu\n", GetLastError());
  552. }
  553. else
  554. {
  555. DWORD MsgLen = (DWORD) WCSSIZE(_RebootMessage);
  556. for (DWORD i = 0; i < EntriesRead; i++)
  557. {
  558. Status = NetMessageBufferSend(
  559. NULL, // servername
  560. Buffer[i].sesi0_cname, // msgname
  561. ComputerName, // fromname
  562. (LPBYTE) _RebootMessage,// buf
  563. MsgLen // buflen
  564. );
  565. if (Status != NERR_Success)
  566. {
  567. SC_LOG2(ERROR, "NetMessageBufferSend to %ws failed %lu\n",
  568. Buffer[i].sesi0_cname, Status);
  569. }
  570. }
  571. }
  572. }
  573. else if (Status != NERR_Success)
  574. {
  575. SC_LOG(ERROR, "NetSessionEnum failed %lu\n", Status);
  576. }
  577. if (Buffer != NULL)
  578. {
  579. NetApiBufferFree(Buffer);
  580. }
  581. //
  582. // Queue a work item to perform the reboot after the delay has elapsed.
  583. // Note: We're counting the delay from the time that the broadcast finished.
  584. //
  585. CRebootContext * pCtx = new CRebootContext(_Delay, _DisplayName);
  586. if (pCtx == NULL)
  587. {
  588. SC_LOG0(ERROR, "Couldn't allocate reboot context\n");
  589. }
  590. else
  591. {
  592. _DisplayName = NULL; // pCtx will free it
  593. ntStatus = pCtx->AddWorkItem(WT_EXECUTEONLYONCE);
  594. if (!NT_SUCCESS(ntStatus))
  595. {
  596. SC_LOG(ERROR, "Couldn't add reboot work item 0x%x\n", ntStatus);
  597. delete pCtx;
  598. }
  599. }
  600. delete this;
  601. }
  602. VOID
  603. CRebootContext::Perform(
  604. IN BOOLEAN fWaitStatus
  605. )
  606. /*++
  607. Routine Description:
  608. --*/
  609. {
  610. SC_LOG0(CONFIG_API, "Rebooting machine...\n");
  611. // Write an event log entry?
  612. //
  613. // Enable our shutdown privilege. Since we are shutting down, don't
  614. // bother doing it for only the current thread and don't bother
  615. // disabling it afterwards.
  616. //
  617. BOOLEAN WasEnabled;
  618. NTSTATUS Status = RtlAdjustPrivilege(
  619. SE_SHUTDOWN_PRIVILEGE,
  620. TRUE, // enable
  621. FALSE, // this thread only? - No
  622. &WasEnabled);
  623. if (!NT_SUCCESS(Status))
  624. {
  625. SC_LOG(ERROR, "RtlAdjustPrivilege failed! %#lx\n", Status);
  626. SC_ASSERT(0);
  627. }
  628. else
  629. {
  630. WCHAR wszShutdownText[128];
  631. WCHAR wszPrintableText[128 + MAX_SERVICE_NAME_LENGTH];
  632. if (LoadString(GetModuleHandle(NULL),
  633. IDS_SC_REBOOT_MESSAGE,
  634. wszShutdownText,
  635. LENGTH(wszShutdownText)))
  636. {
  637. wsprintf(wszPrintableText, wszShutdownText, _DisplayName);
  638. }
  639. else
  640. {
  641. //
  642. // If LoadString failed, it probably means the buffer
  643. // is too small to hold the localized string
  644. //
  645. SC_LOG(ERROR, "LoadString failed! %lu\n", GetLastError());
  646. SC_ASSERT(FALSE);
  647. wszShutdownText[0] = L'\0';
  648. }
  649. if (!InitiateSystemShutdown(NULL, // machine name
  650. wszPrintableText, // reboot message
  651. _Delay / 1000, // timeout in seconds
  652. TRUE, // force apps closed
  653. TRUE)) // reboot
  654. {
  655. DWORD dwError = GetLastError();
  656. //
  657. // If two services fail simultaneously and both are configured
  658. // to reboot the machine, InitiateSystemShutdown will fail all
  659. // calls past the first with ERROR_SHUTDOWN_IN_PROGRESS. We
  660. // don't want to log an event in this case.
  661. //
  662. if (dwError != ERROR_SHUTDOWN_IN_PROGRESS) {
  663. SC_LOG(ERROR, "InitiateSystemShutdown failed! %lu\n", dwError);
  664. ScLogRecoveryFailure(
  665. SC_ACTION_REBOOT,
  666. _DisplayName,
  667. dwError
  668. );
  669. }
  670. }
  671. }
  672. delete this;
  673. }
  674. VOID
  675. CRunCommandContext::Perform(
  676. IN BOOLEAN fWaitStatus
  677. )
  678. /*++
  679. Routine Description:
  680. CODEWORK Share this code with ScLogonAndStartImage
  681. --*/
  682. {
  683. //
  684. // Make sure we were called because of a timeout
  685. //
  686. SC_ASSERT(fWaitStatus == TRUE);
  687. DWORD status = NO_ERROR;
  688. HANDLE Token = NULL;
  689. PSID ServiceSid = NULL; // SID is returned only if not LocalSystem
  690. LPWSTR AccountName = NULL;
  691. SECURITY_ATTRIBUTES SaProcess; // Process security info (used only if not LocalSystem)
  692. STARTUPINFOW StartupInfo;
  693. PROCESS_INFORMATION ProcessInfo;
  694. RemoveDelayedWorkItem();
  695. //
  696. // Get the Account Name for the service. A NULL Account Name means the
  697. // service is configured to run in the LocalSystem account.
  698. //
  699. status = ScLookupServiceAccount(
  700. _ServiceRecord->ServiceName,
  701. &AccountName
  702. );
  703. // We only need to log on if it's not the LocalSystem account
  704. if (AccountName != NULL)
  705. {
  706. //
  707. // CODEWORK: Keep track of recovery EXEs spawned so we can
  708. // load/unload the user profile for the process.
  709. //
  710. status = ScLogonService(
  711. _ServiceRecord->ServiceName,
  712. AccountName,
  713. &Token,
  714. NULL,
  715. &ServiceSid
  716. );
  717. if (status != NO_ERROR)
  718. {
  719. SC_LOG(ERROR, "CRunCommandContext: ScLogonService failed, %lu\n", status);
  720. goto Clean0;
  721. }
  722. SaProcess.nLength = sizeof(SECURITY_ATTRIBUTES);
  723. SaProcess.bInheritHandle = FALSE;
  724. SC_ACE_DATA AceData[] =
  725. {
  726. {ACCESS_ALLOWED_ACE_TYPE, 0, 0,
  727. PROCESS_ALL_ACCESS, &ServiceSid},
  728. {ACCESS_ALLOWED_ACE_TYPE, 0, 0,
  729. PROCESS_SET_INFORMATION |
  730. PROCESS_TERMINATE |
  731. SYNCHRONIZE, &LocalSystemSid}
  732. };
  733. NTSTATUS ntstatus = ScCreateAndSetSD(
  734. AceData, // AceData
  735. LENGTH(AceData), // AceCount
  736. NULL, // OwnerSid (optional)
  737. NULL, // GroupSid (optional)
  738. &SaProcess.lpSecurityDescriptor
  739. // pNewDescriptor
  740. );
  741. LocalFree(ServiceSid);
  742. if (! NT_SUCCESS(ntstatus))
  743. {
  744. SC_LOG(ERROR, "CRunCommandContext: ScCreateAndSetSD failed %#lx\n", ntstatus);
  745. status = RtlNtStatusToDosError(ntstatus);
  746. goto Clean1;
  747. }
  748. SC_LOG2(CONFIG_API,"CRunCommandContext: about to spawn recovery program in account %ws: %ws\n",
  749. AccountName, _FailureCommand);
  750. //
  751. // Impersonate the user so we don't give access to
  752. // EXEs that have been locked down for the account.
  753. //
  754. if (!ImpersonateLoggedOnUser(Token))
  755. {
  756. status = GetLastError();
  757. SC_LOG1(ERROR,
  758. "ScLogonAndStartImage: ImpersonateLoggedOnUser failed %d\n",
  759. status);
  760. goto Clean2;
  761. }
  762. //
  763. // Spawn the Image Process
  764. //
  765. ScInitStartupInfo(&StartupInfo, FALSE);
  766. if (!CreateProcessAsUserW(
  767. Token, // logon token
  768. NULL, // lpApplicationName
  769. _FailureCommand, // lpCommandLine
  770. &SaProcess, // process' security attributes
  771. NULL, // first thread's security attributes
  772. FALSE, // whether new process inherits handles
  773. CREATE_NEW_CONSOLE, // creation flags
  774. NULL, // environment block
  775. NULL, // current directory
  776. &StartupInfo, // startup info
  777. &ProcessInfo // process info
  778. ))
  779. {
  780. status = GetLastError();
  781. SC_LOG(ERROR, "CRunCommandContext: CreateProcessAsUser failed %lu\n", status);
  782. RevertToSelf();
  783. goto Clean2;
  784. }
  785. RevertToSelf();
  786. }
  787. else
  788. {
  789. //
  790. // It's the LocalSystem account
  791. //
  792. //
  793. // If the process is to be interactive, set the appropriate flags.
  794. //
  795. BOOL bInteractive = FALSE;
  796. if (AccountName == NULL &&
  797. _ServiceRecord->ServiceStatus.dwServiceType & SERVICE_INTERACTIVE_PROCESS)
  798. {
  799. bInteractive = ScAllowInteractiveServices();
  800. if (!bInteractive)
  801. {
  802. //
  803. // Write an event to indicate that an interactive service
  804. // was started, but the system is configured to not allow
  805. // services to be interactive.
  806. //
  807. ScLogEvent(NEVENT_SERVICE_NOT_INTERACTIVE,
  808. _ServiceRecord->DisplayName);
  809. }
  810. }
  811. ScInitStartupInfo(&StartupInfo, bInteractive);
  812. SC_LOG1(CONFIG_API,"CRunCommandContext: about to spawn recovery program in "
  813. "the LocalSystem account: %ws\n", _FailureCommand);
  814. //
  815. // Spawn the Image Process
  816. //
  817. if (!CreateProcessW(
  818. NULL, // lpApplicationName
  819. _FailureCommand, // lpCommandLine
  820. NULL, // process' security attributes
  821. NULL, // first thread's security attributes
  822. FALSE, // whether new process inherits handles
  823. CREATE_NEW_CONSOLE, // creation flags
  824. NULL, // environment block
  825. NULL, // current directory
  826. &StartupInfo, // startup info
  827. &ProcessInfo // process info
  828. ))
  829. {
  830. status = GetLastError();
  831. SC_LOG(ERROR, "CRunCommandContext: CreateProcess failed %lu\n", status);
  832. goto Clean2;
  833. }
  834. }
  835. SC_LOG0(CONFIG_API, "Recovery program spawned successfully.\n");
  836. CloseHandle(ProcessInfo.hThread);
  837. CloseHandle(ProcessInfo.hProcess);
  838. Clean2:
  839. if (AccountName != NULL)
  840. {
  841. RtlDeleteSecurityObject(&SaProcess.lpSecurityDescriptor);
  842. }
  843. Clean1:
  844. if (AccountName != NULL)
  845. {
  846. CloseHandle(Token);
  847. }
  848. Clean0:
  849. if (status != NO_ERROR)
  850. {
  851. ScLogRecoveryFailure(
  852. SC_ACTION_RUN_COMMAND,
  853. _ServiceRecord->DisplayName,
  854. status
  855. );
  856. }
  857. delete this;
  858. }
  859. VOID
  860. ScLogRecoveryFailure(
  861. IN SC_ACTION_TYPE ActionType,
  862. IN LPCWSTR ServiceDisplayName,
  863. IN DWORD Error
  864. )
  865. /*++
  866. Routine Description:
  867. --*/
  868. {
  869. WCHAR wszActionString[50];
  870. if (!LoadString(GetModuleHandle(NULL),
  871. IDS_SC_ACTION_BASE + ActionType,
  872. wszActionString,
  873. LENGTH(wszActionString)))
  874. {
  875. SC_LOG(ERROR, "LoadString failed %lu\n", GetLastError());
  876. wszActionString[0] = L'\0';
  877. }
  878. ScLogEvent(
  879. NEVENT_SERVICE_RECOVERY_FAILED,
  880. ActionType,
  881. wszActionString,
  882. (LPWSTR) ServiceDisplayName,
  883. Error
  884. );
  885. }