Leaked source code of windows server 2003
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.

1826 lines
59 KiB

  1. //+----------------------------------------------------------------------------
  2. //
  3. // Scheduling Agent Service
  4. //
  5. // Microsoft Windows
  6. // Copyright (C) Microsoft Corporation, 1992 - 1996.
  7. //
  8. // File: runjob.cxx
  9. //
  10. // Contents: Functions to run the target file.
  11. //
  12. // History: 02-Jul-96 EricB created
  13. //
  14. //-----------------------------------------------------------------------------
  15. #include "..\pch\headers.hxx"
  16. #pragma hdrstop
  17. #include <lmerr.h> // NERR_Success
  18. #include <dsgetdc.h> // DsGetDcName
  19. #include <lmaccess.h> // NetUserGetInfo
  20. #include <lmapibuf.h> // NetApiBufferFree
  21. #include <netevent.h> // for logging to event log
  22. #include "globals.hxx"
  23. #include "svc_core.hxx"
  24. #include "..\inc\resource.h"
  25. #include "path.hxx"
  26. #include "..\inc\common.hxx"
  27. #include "..\inc\runobj.hxx"
  28. #include <wtsapi32.h>
  29. HRESULT
  30. ComposeBatchParam(
  31. LPCTSTR pwszPrefix,
  32. LPCTSTR wszAppPathName,
  33. LPCTSTR pwszParameters,
  34. LPTSTR * ppwszCmdLine
  35. );
  36. HRESULT
  37. ComposeParam(BOOL fTargetIsExe,
  38. LPTSTR ptszRunTarget,
  39. LPTSTR ptszParameters,
  40. LPTSTR * pptszCmdLine);
  41. #include <userenv.h> // LoadUserProfile
  42. BOOL AllowInteractiveServices(void);
  43. BOOL LogonAccount(
  44. LPCWSTR pwszJobFile,
  45. CJob * pJob,
  46. DWORD * pdwErrorMsg,
  47. HRESULT * pdwSpecificError,
  48. //CRun * pRun,
  49. HANDLE * phToken,
  50. BOOL * pfTokenIsShellToken,
  51. LPWSTR * ppwsz,
  52. HANDLE * phUserProfile);
  53. HANDLE
  54. LoadAccountProfile(
  55. HANDLE hToken,
  56. LPCWSTR pwszUser,
  57. LPCWSTR pwszDomain);
  58. BOOL GetUserTokenFromSession(
  59. LPTSTR lpszUsername,
  60. LPTSTR lpszDomain,
  61. PHANDLE phUserToken
  62. );
  63. DWORD SchedUPNToAccountName(
  64. IN LPCWSTR lpUPN,
  65. OUT LPWSTR *ppAccountName);
  66. void InitializeStartupInfo(
  67. CJob * pJob,
  68. LPTSTR ptszDesktop,
  69. STARTUPINFO * psi);
  70. HRESULT MapFindExecutableError(HINSTANCE hRet);
  71. BOOL WaitForStubExe(HANDLE hProcess);
  72. #define WSZ_INTERACTIVE_DESKTOP L"WinSta0\\Default"
  73. #define WSZ_SA_DESKTOP L"SAWinSta\\SADesktop"
  74. #define CMD_PREFIX TEXT("cmd.exe /c ")
  75. #define STUB_PREFIX L"RUNDLL32.EXE Shell32.DLL,ShellExec_RunDLL ?0x400?"
  76. // 0x400 is SEE_MASK_FLAG_NO_UI
  77. #define USERNAME L"USERNAME"
  78. #define USERDOMAIN L"USERDOMAIN"
  79. #define USERPROFILE L"USERPROFILE"
  80. #define DQUOTE TEXT("\"")
  81. #define SPACE TEXT(" ")
  82. //+----------------------------------------------------------------------------
  83. //
  84. // Member: CSchedWorker::RunNTJob
  85. //
  86. // Synopsis: Run an NT Job.
  87. //
  88. // Arguments: [pJob] - the job object to be run.
  89. // [pRun] - the run information object.
  90. // [phrRet] - a place to return launch failure info.
  91. // [pdwErrMsgID] - message ID for failure reporting.
  92. //
  93. // Returns: S_OK - if job launched.
  94. // S_FALSE - if job not launched.
  95. // HRESULT - other, fatal, error.
  96. //
  97. //-----------------------------------------------------------------------------
  98. HRESULT
  99. CSchedWorker::RunNTJob(CJob * pJob, CRun * pRun, HRESULT * phrRet,
  100. DWORD * pdwErrMsgID)
  101. {
  102. LPWSTR pwszRunTarget,
  103. pwszWorkingDir = pJob->m_pwszWorkingDirectory,
  104. pwszParameters = pJob->m_pwszParameters;
  105. DWORD cch;
  106. BOOL fRanJob = FALSE;
  107. LPTSTR ptszDesktop = NULL;
  108. HANDLE hImpersonationHandle = NULL;
  109. HANDLE hToken = NULL;
  110. BOOL fTokenIsShellToken = FALSE;
  111. HANDLE hUserProfile = NULL;
  112. BOOL fTargetIsExe = FALSE;
  113. BOOL fTargetIsBinaryExe = FALSE;
  114. BOOL fUseStubExe = FALSE;
  115. *pdwErrMsgID = IDS_LOG_JOB_ERROR_FAILED_START;
  116. size_t cchBuff = lstrlen(pJob->m_pwszApplicationName) + 1;
  117. pwszRunTarget = new WCHAR[cchBuff];
  118. if (pwszRunTarget == NULL)
  119. {
  120. LogServiceError(IDS_NON_FATAL_ERROR,
  121. ERROR_OUTOFMEMORY,
  122. IDS_HELP_HINT_CLOSE_APPS);
  123. ERR_OUT("CSchedWorker::RunNTJob", E_OUTOFMEMORY);
  124. return E_OUTOFMEMORY;
  125. }
  126. StringCchCopy(pwszRunTarget, cchBuff, pJob->m_pwszApplicationName);
  127. schDebugOut((DEB_TRACE, "*** Running job %S\n", pJob->m_ptszFileName));
  128. schDebugOut((DEB_USER3, "*** with MaxRunTime of %u\n", pJob->m_dwMaxRunTime));
  129. HRESULT hr = S_OK;
  130. WCHAR wszAppPathName[MAX_PATH + 1];
  131. LPWSTR pwszCmdLine = NULL;
  132. //
  133. // Logon the account associated with the job and set the security token
  134. // and desktop appropriately based on several factors: is this an AT job,
  135. // is the account the same as the currently logged-on user, etc.
  136. //
  137. if (!LogonAccount(pJob->m_ptszFileName,
  138. pJob,
  139. pdwErrMsgID,
  140. phrRet,
  141. //pRun,
  142. &hToken,
  143. &fTokenIsShellToken,
  144. &ptszDesktop,
  145. &hUserProfile))
  146. {
  147. hr = S_FALSE;
  148. goto cleanup;
  149. }
  150. if( NULL != ptszDesktop )
  151. {
  152. hr = pRun->SetDesktop( _tcschr( ptszDesktop, '\\' ) + 1 );
  153. if( FAILED( hr ) )
  154. {
  155. LogServiceError(IDS_NON_FATAL_ERROR,
  156. ERROR_OUTOFMEMORY,
  157. IDS_HELP_HINT_CLOSE_APPS);
  158. ERR_OUT("CSchedWorker::RunNTJob", E_OUTOFMEMORY);
  159. goto cleanup;
  160. }
  161. TCHAR ptszStation[MAX_PATH];
  162. SecureZeroMemory( ptszStation, sizeof(ptszStation) );
  163. wcsncpy( ptszStation, ptszDesktop,
  164. ( _tcschr( ptszDesktop, '\\' ) - ptszDesktop ) );
  165. hr = pRun->SetStation( ptszStation );
  166. if( FAILED( hr ) )
  167. {
  168. LogServiceError(IDS_NON_FATAL_ERROR,
  169. ERROR_OUTOFMEMORY,
  170. IDS_HELP_HINT_CLOSE_APPS);
  171. ERR_OUT("CSchedWorker::RunNTJob", E_OUTOFMEMORY);
  172. goto cleanup;
  173. }
  174. }
  175. //
  176. // NOTE: After this point, if fTokenIsShellToken is TRUE, we must leave
  177. // gUserLogonInfo.CritSection.
  178. //
  179. //
  180. // For all but AT jobs, impersonate the user to ensure the user
  181. // gets access checked correctly on the file executed.
  182. //
  183. hImpersonationHandle = NULL;
  184. if (!pJob->IsFlagSet(JOB_I_FLAG_NET_SCHEDULE))
  185. {
  186. if (fTokenIsShellToken)
  187. {
  188. hImpersonationHandle = ImpersonateLoggedInUser();
  189. }
  190. else
  191. {
  192. hImpersonationHandle = ImpersonateUser(hToken,
  193. hImpersonationHandle);
  194. }
  195. if (hImpersonationHandle == NULL)
  196. {
  197. *phrRet = 0;
  198. *pdwErrMsgID = IDS_ACCOUNT_LOGON_FAILED;
  199. hr = S_FALSE;
  200. goto cleanup2;
  201. }
  202. }
  203. //
  204. // Change to the job's working dir before searching for the
  205. // executable.
  206. //
  207. if (pwszWorkingDir != NULL)
  208. {
  209. if (!SetCurrentDirectory(pwszWorkingDir))
  210. {
  211. //
  212. // An invalid working directory may not prevent the job from
  213. // running, so this is not a fatal error. Log it though, to
  214. // inform the user.
  215. //
  216. TCHAR tszExeName[MAX_PATH + 1];
  217. GetExeNameFromCmdLine(pJob->GetCommand(), MAX_PATH + 1, tszExeName);
  218. LogTaskError(pRun->GetName(),
  219. tszExeName,
  220. IDS_LOG_SEVERITY_WARNING,
  221. IDS_LOG_JOB_WARNING_BAD_DIR,
  222. NULL,
  223. GetLastError(),
  224. IDS_HELP_HINT_BADDIR);
  225. //
  226. // Set the pointer to NULL so that CreateProcess will ignore it.
  227. //
  228. pwszWorkingDir = NULL;
  229. }
  230. }
  231. //
  232. // Check if the run target has an extension and determine if the run
  233. // target is a program, a batch or command file (.bat or .cmd), or a
  234. // document. If there is no extension, then it is assumed that it is a
  235. // program.
  236. //
  237. WCHAR* pExtension = PathFindExtension(pwszRunTarget);
  238. if (*pExtension == '\0')
  239. {
  240. fTargetIsExe = TRUE;
  241. fTargetIsBinaryExe = TRUE;
  242. }
  243. else if (PathIsExe(pwszRunTarget))
  244. {
  245. fTargetIsExe = TRUE;
  246. if (PathIsBinaryExe(pwszRunTarget))
  247. {
  248. fTargetIsBinaryExe = TRUE;
  249. }
  250. }
  251. if (fTargetIsExe)
  252. {
  253. if (fTargetIsBinaryExe)
  254. {
  255. DBG_OUT("Job target is a binary executable");
  256. }
  257. else
  258. {
  259. DBG_OUT("Job target is a batch file");
  260. }
  261. if (pwszRunTarget[1] == L':' || pwszRunTarget[1] == L'\\')
  262. {
  263. //
  264. // If the second character is a colon or a backslash, then this
  265. // must be a fully qualified path. If so, don't call SearchPath.
  266. //
  267. StringCchCopy(wszAppPathName, MAX_PATH + 1, pwszRunTarget);
  268. }
  269. else
  270. {
  271. //
  272. // Build a full path name for the application.
  273. //
  274. DWORD cchFound;
  275. cchFound = SearchPath(NULL, pwszRunTarget, DOTEXE, MAX_PATH + 1,
  276. wszAppPathName, NULL);
  277. if (!cchFound || cchFound >= MAX_PATH)
  278. {
  279. //
  280. // Error, cannot locate job target application. Note that this
  281. // is not a fatal error (probably file-not-found) so
  282. // processing will continue with other jobs in the list.
  283. //
  284. //
  285. // phrRet and pdwErrMsgId are used by LogTaskError on return.
  286. //
  287. *phrRet = HRESULT_FROM_WIN32(GetLastError());
  288. *pdwErrMsgID = IDS_LOG_JOB_ERROR_FAILED_START;
  289. hr = S_FALSE;
  290. goto cleanup3;
  291. }
  292. }
  293. if (fTargetIsBinaryExe)
  294. {
  295. schDebugOut((DEB_ITRACE, "*** Running '%S'\n", wszAppPathName));
  296. }
  297. }
  298. else
  299. {
  300. DBG_OUT("Job target is a document");
  301. HINSTANCE hRet = FindExecutable(pwszRunTarget,
  302. pwszWorkingDir,
  303. wszAppPathName);
  304. if (hRet == (HINSTANCE)31)
  305. {
  306. //
  307. // No association found. Try using rundll32.exe with ShellExecute
  308. // to run the document.
  309. //
  310. fUseStubExe = TRUE;
  311. fTargetIsExe = TRUE;
  312. fTargetIsBinaryExe = FALSE;
  313. StringCchCopy(wszAppPathName, MAX_PATH + 1, pwszRunTarget);
  314. }
  315. else if (hRet < (HINSTANCE)32)
  316. {
  317. //
  318. // This is not a fatal error, so RunJobs will just log the failure
  319. // and continue with any other pending jobs.
  320. //
  321. //
  322. // phrRet and pdwErrMsgId are used by LogTaskError on return.
  323. //
  324. schDebugOut((DEB_ERROR, "FindExecutable FAILED with %d for '%ws'\n",
  325. hRet, pwszRunTarget));
  326. *phrRet = MapFindExecutableError(hRet);
  327. *pdwErrMsgID = IDS_LOG_JOB_ERROR_FAILED_START;
  328. hr = S_FALSE;
  329. goto cleanup3;
  330. }
  331. else
  332. {
  333. //
  334. // If running a document by association, the parameter property is
  335. // ignored and the run target property is passed as the parameter.
  336. //
  337. pwszParameters = pwszRunTarget;
  338. pwszRunTarget = wszAppPathName;
  339. schDebugOut((DEB_ITRACE, "*** Running '%S'\n", pwszParameters));
  340. }
  341. }
  342. if (fTargetIsExe && !fTargetIsBinaryExe)
  343. {
  344. hr = ComposeBatchParam(fUseStubExe ? STUB_PREFIX : CMD_PREFIX,
  345. wszAppPathName,
  346. pwszParameters,
  347. &pwszCmdLine);
  348. if (FAILED(hr))
  349. {
  350. goto cleanup3;
  351. }
  352. schDebugOut((DEB_ITRACE, "*** Running batch file '%S'\n", pwszCmdLine));
  353. }
  354. else
  355. {
  356. //
  357. // Add the app name as the first token of the command line param.
  358. //
  359. if (pwszParameters != NULL)
  360. {
  361. hr = ComposeParam(fTargetIsExe,
  362. pwszRunTarget,
  363. pwszParameters,
  364. &pwszCmdLine);
  365. if (FAILED(hr))
  366. {
  367. goto cleanup3;
  368. }
  369. schDebugOut((DEB_ITRACE, "*** With cmd line '%S'\n", pwszCmdLine));
  370. }
  371. }
  372. STARTUPINFO startupinfo;
  373. InitializeStartupInfo(pJob, ptszDesktop, &startupinfo);
  374. if (pJob->IsFlagSet(TASK_FLAG_HIDDEN))
  375. {
  376. startupinfo.wShowWindow = SW_HIDE;
  377. }
  378. //
  379. // Modify the path if the application has an app path registry entry
  380. //
  381. BOOL fChangedPath;
  382. LPWSTR pwszSavedPath;
  383. fChangedPath = SetAppPath(wszAppPathName, &pwszSavedPath);
  384. //
  385. // Launch job.
  386. //
  387. // NB : Must call CreateProcess when the token handle is
  388. // NULL (in the case of AT jobs running as local system),
  389. // since CreateProcessAsUser rejects NULL handles.
  390. // Alternatively, OpenProcessToken could be used,
  391. // but then we have to deal with the failure cases,
  392. // logging appropriate errors, closing the token
  393. // handle, etc.
  394. //
  395. HANDLE hProcess = NULL;
  396. HANDLE hThread = NULL;
  397. DWORD dwProcessId = 0;
  398. if (hToken == NULL)
  399. {
  400. PROCESS_INFORMATION processinfo;
  401. ZeroMemory(&processinfo, sizeof(PROCESS_INFORMATION));
  402. fRanJob = CreateProcess((fTargetIsExe && !fTargetIsBinaryExe) ?
  403. NULL : wszAppPathName,
  404. pwszCmdLine,
  405. NULL,
  406. NULL,
  407. FALSE,
  408. pJob->m_dwPriority |
  409. CREATE_NEW_CONSOLE |
  410. CREATE_SEPARATE_WOW_VDM,
  411. NULL,
  412. pwszWorkingDir,
  413. &startupinfo,
  414. &processinfo);
  415. hProcess = processinfo.hProcess;
  416. hThread = processinfo.hThread;
  417. dwProcessId = processinfo.dwProcessId;
  418. }
  419. else
  420. {
  421. LPVOID lpEnvironment;
  422. //
  423. // Launch the job with the appropriate environment
  424. //
  425. schDebugOut((DEB_ITRACE, "Calling CreateEnvironmentBlock...\n"));
  426. if (!CreateEnvironmentBlock(&lpEnvironment,
  427. hToken,
  428. FALSE))
  429. {
  430. ERR_OUT("CreateEnvironmentBlock", GetLastError());
  431. lpEnvironment = NULL;
  432. }
  433. else
  434. {
  435. schDebugOut((DEB_ITRACE, "... CreateEnvironmentBlock succeded\n"));
  436. }
  437. PROCESS_INFORMATION processinfo;
  438. ZeroMemory(&processinfo, sizeof(PROCESS_INFORMATION));
  439. fRanJob = CreateProcessAsUser(hToken,
  440. (fTargetIsExe && !fTargetIsBinaryExe) ?
  441. NULL : wszAppPathName,
  442. pwszCmdLine,
  443. NULL,
  444. NULL,
  445. FALSE,
  446. pJob->m_dwPriority |
  447. CREATE_NEW_CONSOLE |
  448. CREATE_SEPARATE_WOW_VDM |
  449. CREATE_UNICODE_ENVIRONMENT,
  450. lpEnvironment,
  451. pwszWorkingDir,
  452. &startupinfo,
  453. &processinfo);
  454. hProcess = processinfo.hProcess;
  455. hThread = processinfo.hThread;
  456. dwProcessId = processinfo.dwProcessId;
  457. //
  458. // DestroyEnvironmentBlock handles NULL
  459. //
  460. DestroyEnvironmentBlock(lpEnvironment);
  461. }
  462. if (fRanJob && fUseStubExe)
  463. {
  464. //
  465. // If we launched the stub exe, we must wait for it to exit, and check
  466. // its exit code.
  467. //
  468. fRanJob = WaitForStubExe(hProcess);
  469. //
  470. // It's not interesting to copy info about the stub exe into pRun
  471. //
  472. CloseHandle(hProcess);
  473. CloseHandle(hThread);
  474. hProcess = NULL;
  475. dwProcessId = NULL;
  476. }
  477. if (fRanJob)
  478. {
  479. //
  480. // Successfully launched job.
  481. //
  482. hr = S_OK;
  483. pRun->SetHandle(hProcess);
  484. pRun->SetProcessId(dwProcessId);
  485. if (fUseStubExe)
  486. {
  487. pRun->ClearFlag(RUN_STATUS_RUNNING); // was set by SetHandle
  488. fRanJob = FALSE;
  489. // HMH: okay, I don't like this logic, but that's the way it worked
  490. // when I got here. It seems like the process launched by the stub
  491. // would want the user profile, etc, available...
  492. // ... but then we wouldn't know when to close it!
  493. if (hUserProfile)
  494. UnloadUserProfile(hToken, hUserProfile);
  495. if (hToken && !fTokenIsShellToken)
  496. CloseHandle(hToken);
  497. }
  498. else
  499. {
  500. CloseHandle(hThread);
  501. pRun->SetProfileHandles(hToken, hUserProfile, !fTokenIsShellToken);
  502. }
  503. }
  504. else
  505. {
  506. //
  507. // Job launch failed.
  508. //
  509. hr = S_FALSE;
  510. // so, let's clean up - shall we?
  511. if (hUserProfile)
  512. UnloadUserProfile(hToken, hUserProfile);
  513. if (hToken && !fTokenIsShellToken)
  514. CloseHandle(hToken);
  515. //
  516. // phrRet and pdwErrMsgId are used by LogTaskError on return.
  517. //
  518. *phrRet = HRESULT_FROM_WIN32(GetLastError());
  519. *pdwErrMsgID = IDS_LOG_JOB_ERROR_FAILED_START;
  520. schDebugOut((DEB_ERROR, "*** CreateProcess for job %S failed, %lu\n",
  521. pJob->m_ptszFileName, GetLastError()));
  522. }
  523. if (fChangedPath)
  524. {
  525. SetEnvironmentVariable(L"PATH", pwszSavedPath);
  526. delete [] pwszSavedPath;
  527. pwszSavedPath = NULL;
  528. }
  529. //
  530. // If impersonating, stop.
  531. //
  532. cleanup3:
  533. if (!pJob->IsFlagSet(JOB_I_FLAG_NET_SCHEDULE))
  534. {
  535. StopImpersonating(hImpersonationHandle, !fTokenIsShellToken);
  536. }
  537. cleanup2:
  538. if (fTokenIsShellToken)
  539. {
  540. LeaveCriticalSection(gUserLogonInfo.CritSection);
  541. }
  542. cleanup:
  543. //
  544. // If the job ran successfully, then the CRun object pointed to by pRun
  545. // has the profile and user tokens and will release them when the job
  546. // quits.
  547. //
  548. // Change back to the service's working directory.
  549. //
  550. if (pwszWorkingDir != NULL)
  551. {
  552. if (!SetCurrentDirectory(m_ptszSvcDir))
  553. {
  554. LogServiceError(IDS_NON_FATAL_ERROR, GetLastError(), 0);
  555. ERR_OUT("RunJobs: changing back to the service's directory",
  556. HRESULT_FROM_WIN32(GetLastError()));
  557. }
  558. }
  559. if (pwszRunTarget != wszAppPathName)
  560. {
  561. delete [] pwszRunTarget;
  562. }
  563. else
  564. {
  565. delete [] pwszParameters;
  566. }
  567. if (pwszCmdLine != NULL)
  568. {
  569. delete [] pwszCmdLine;
  570. }
  571. return hr;
  572. }
  573. //+----------------------------------------------------------------------------
  574. //
  575. // Function: LogonAccount
  576. //
  577. // Synopsis: Retrieve the account information associated with the job
  578. // and logon.
  579. //
  580. // Non-AT jobs:
  581. // Account == Current logged on user:
  582. // If the logon succeeds and the account matches that of the
  583. // currently logged on user, return the shell security token
  584. // to be used with CreateProcessAsUser. This enables jobs to
  585. // show up on the user's desktop.
  586. //
  587. // Account != Current logged on user or no user logged on:
  588. // If the logon succeeds, but the currently logged on user is
  589. // different than the account, or there is no-one logged on,
  590. // return the account token. Also return the scheduling agent's
  591. // desktop name in the desktop return argument so this job can
  592. // run on it.
  593. //
  594. // AT jobs:
  595. // Ensure the AT job owner is an administrator and return an
  596. // account token. AT jobs never get the shell token, since
  597. // that's how the original schedule service worked. Also
  598. // return the desktop name, "WinSta0\Default".
  599. //
  600. // Arguments: [pwszJobFile] -- Path to the job object file.
  601. // [pJob] -- Job object to execute under the
  602. // associated account.
  603. // [pdwErrorMsg] -- SCHED_E error message on error.
  604. // [pdwSpecificError] -- HRESULT on error.
  605. // [phToken] -- Returned token.
  606. // [pfTokenIsShellToken] -- If TRUE, the token returned is the
  607. // shell's.
  608. // [ppwszDesktop] -- Desktop to launch the job on.
  609. // [phUserProfile] -- user profile token
  610. //
  611. // Returns: TRUE -- Logon successful.
  612. // FALSE -- Logon failure.
  613. //
  614. // Notes: DO NOT delete:
  615. // *pptszDestkop. If non-NULL, it refers to a static string.
  616. // *phToken if *pfTokenIsShellToken == TRUE. This token
  617. // cannot be duplicated. You delete it and you've got
  618. // problems.
  619. // If *pfTokenIsShellToken, the logon session critical section
  620. // has been entered! Right after the call to CreateProcessAsUser
  621. // leave this critical section if this flag value is TRUE.
  622. //
  623. //-----------------------------------------------------------------------------
  624. BOOL
  625. LogonAccount(LPCWSTR pwszJobFile,
  626. CJob * pJob,
  627. DWORD * pdwErrorMsg,
  628. HRESULT * phrSpecificError,
  629. // CRun * pRun,
  630. HANDLE * phToken,
  631. BOOL * pfTokenIsShellToken,
  632. LPWSTR * ppwszDesktop,
  633. HANDLE * phUserProfile)
  634. {
  635. JOB_CREDENTIALS jc;
  636. HANDLE hToken = NULL;
  637. HRESULT hr;
  638. WCHAR wszProfilePath[MAX_PATH+1] = L"";
  639. ULONG cchPath = ARRAY_LEN(wszProfilePath);
  640. *pdwErrorMsg = 0;
  641. *phrSpecificError = S_OK;
  642. *phToken = NULL;
  643. *pfTokenIsShellToken = FALSE;
  644. *ppwszDesktop = NULL;
  645. *phUserProfile = NULL;
  646. if (pJob->IsFlagSet(JOB_I_FLAG_NET_SCHEDULE))
  647. {
  648. //
  649. // Verify the job's signature.
  650. //
  651. if (! pJob->VerifySignature())
  652. {
  653. *phrSpecificError = HRESULT_FROM_WIN32(ERROR_ACCESS_DENIED);
  654. *pdwErrorMsg = IDS_FILE_ACCESS_DENIED;
  655. return(FALSE);
  656. }
  657. *ppwszDesktop = WSZ_INTERACTIVE_DESKTOP;
  658. hr = GetNSAccountInformation(&jc);
  659. if (SUCCEEDED(hr))
  660. {
  661. if (hr == S_OK)
  662. {
  663. if (!LogonUser(jc.wszAccount,
  664. jc.wszDomain,
  665. jc.wszPassword,
  666. LOGON32_LOGON_BATCH,
  667. LOGON32_PROVIDER_DEFAULT,
  668. &hToken))
  669. {
  670. *pdwErrorMsg = IDS_NS_ACCOUNT_LOGON_FAILED;
  671. *phrSpecificError = HRESULT_FROM_WIN32(GetLastError());
  672. }
  673. //
  674. // Don't leave the plain-text password on the stack.
  675. //
  676. ZERO_PASSWORD(jc.wszPassword);
  677. if (*phrSpecificError)
  678. {
  679. return(FALSE);
  680. }
  681. // If the job was scheduled to run in any account other than LocalSystem account
  682. if ((!jc.fIsPasswordNull) ||(jc.wszAccount[0] != L'\0'))
  683. {
  684. // If Fast User Switching is enabled and the task user
  685. // is logged on in any of the sessions then use the session's
  686. // user token in place of that obtained above so any UI associated
  687. // with the job can show up on the user's desktop.
  688. // If terminal serveice is running but FUS is disabled, WTSEnumerateSessions
  689. // will return the only user logged on. If the username and domain name of that
  690. // user matches with the jc.wszDomain and jc.wszAccount respectively, then
  691. // GetUserTokenFromSession will return TRUE with the token of that user
  692. HANDLE hSessionUserToken = INVALID_HANDLE_VALUE;
  693. BOOL bUserLoggedOn = GetUserTokenFromSession(jc.wszAccount,jc.wszDomain,&hSessionUserToken);
  694. if(bUserLoggedOn)
  695. {
  696. schDebugOut((DEB_TRACE, "*** user session found\n"));
  697. if (!jc.fIsPasswordNull)
  698. {
  699. // pRun->SetProfileHandles(hSessionUserToken, *phUserProfile);
  700. *ppwszDesktop = WSZ_INTERACTIVE_DESKTOP;
  701. }
  702. hToken = hSessionUserToken;
  703. }
  704. }
  705. *phToken = hToken;
  706. *phUserProfile = LoadAccountProfile(hToken,
  707. jc.wszAccount,
  708. jc.wszDomain);
  709. }
  710. }
  711. else
  712. {
  713. CHECK_HRESULT(hr);
  714. *pdwErrorMsg = IDS_FAILED_NS_ACCOUNT_RETRIEVAL;
  715. *phrSpecificError = hr;
  716. return(FALSE);
  717. }
  718. }
  719. else
  720. {
  721. hr = GetAccountInformation(pJob->GetFileName(), &jc);
  722. if (FAILED(hr))
  723. {
  724. CHECK_HRESULT(hr);
  725. *pdwErrorMsg = IDS_FAILED_ACCOUNT_RETRIEVAL;
  726. *phrSpecificError = hr;
  727. return(FALSE);
  728. }
  729. //
  730. // If the job was set with a NULL password, we don't need to log it on.
  731. //
  732. if (jc.fIsPasswordNull)
  733. {
  734. //
  735. // If the job was scheduled to run in the LocalSystem account
  736. // (Accountname is the empty string), the NULL password is valid.
  737. //
  738. if (jc.wszAccount[0] == L'\0')
  739. {
  740. //
  741. // It's LocalSystem, so we don't need to log on the account.
  742. // Since the token is zeroed out above, this works
  743. //
  744. schDebugOut((DEB_TRACE, "Running %ws as LocalSystem\n",
  745. pJob->GetFileName()));
  746. *ppwszDesktop = WSZ_SA_DESKTOP;
  747. return(TRUE);
  748. }
  749. else
  750. {
  751. //
  752. // It's not LocalSystem, so make sure this job has
  753. // the appropriate flags for a NULL password set
  754. //
  755. if (!pJob->IsFlagSet(TASK_FLAG_RUN_ONLY_IF_LOGGED_ON))
  756. {
  757. schDebugOut((DEB_ERROR, "%ws is scheduled to run in "
  758. "a user account with a NULL password, but"
  759. " lacks TASK_FLAG_RUN_ONLY_IF_LOGGED_ON\n",
  760. pJob->GetFileName()));
  761. //
  762. // Not a completely accurate error message, but since there
  763. // is no UI for this task option, it's good enough.
  764. //
  765. *pdwErrorMsg = IDS_ACCOUNT_LOGON_FAILED;
  766. *phrSpecificError = SCHED_E_UNSUPPORTED_ACCOUNT_OPTION;
  767. }
  768. }
  769. }
  770. else
  771. {
  772. //
  773. // If the name was stored as a UPN, convert it to a SAM name first.
  774. //
  775. if (jc.wszDomain[0] == L'\0')
  776. {
  777. LPWSTR pwszSamName;
  778. DWORD dwErr = SchedUPNToAccountName(jc.wszAccount, &pwszSamName);
  779. if (dwErr != NO_ERROR)
  780. {
  781. *pdwErrorMsg = IDS_ACCOUNT_LOGON_FAILED;
  782. *phrSpecificError = HRESULT_FROM_WIN32(dwErr);
  783. CHECK_HRESULT(*phrSpecificError);
  784. }
  785. else
  786. {
  787. WCHAR * pSlash = wcschr(pwszSamName, L'\\');
  788. schAssert(pSlash);
  789. *pSlash = L'\0';
  790. StringCchCopy(jc.wszDomain, MAX_DOMAINNAME + 1, pwszSamName);
  791. StringCchCopy(jc.wszAccount, MAX_USERNAME + 1, pSlash + 1);
  792. delete pwszSamName;
  793. }
  794. }
  795. if (SUCCEEDED(*phrSpecificError))
  796. {
  797. if (!LogonUser(jc.wszAccount,
  798. jc.wszDomain,
  799. jc.wszPassword,
  800. LOGON32_LOGON_BATCH,
  801. LOGON32_PROVIDER_DEFAULT,
  802. &hToken))
  803. {
  804. *pdwErrorMsg = IDS_ACCOUNT_LOGON_FAILED;
  805. *phrSpecificError = HRESULT_FROM_WIN32(GetLastError());
  806. }
  807. }
  808. }
  809. //
  810. // Don't leave the plain-text password on the stack.
  811. //
  812. ZERO_PASSWORD(jc.wszPassword);
  813. if (*phrSpecificError)
  814. {
  815. return(FALSE);
  816. }
  817. //
  818. // Load the user profile associated with the account just logged on.
  819. // (If the user is already logged on, this will just increment the
  820. // profile ref count, to be decremented when the job stops.)
  821. // Don't bother doing this if the job is "run-only-if-logged-on", in
  822. // which case it's OK to unload the profile when the user logs off.
  823. //
  824. if (!pJob->IsFlagSet(TASK_FLAG_RUN_ONLY_IF_LOGGED_ON))
  825. {
  826. *phToken = hToken;
  827. *phUserProfile = LoadAccountProfile(*phToken,
  828. jc.wszAccount,
  829. jc.wszDomain);
  830. }
  831. // If Fast User Switching is enabled and the task user
  832. // is logged on in any of the sessions then use the session's
  833. // user token in place of that obtained above so any UI associated
  834. // with the job can show up on the user's desktop.
  835. // If terminal serveice is running but FUS is disabled, WTSEnumerateSessions
  836. // will return the only user logged on. If the username and domain name of that
  837. // user matches with the jc.wszDomain and jc.wszAccount respectively, then
  838. // GetUserTokenFromSession will return TRUE with the token of that user
  839. HANDLE hSessionUserToken = INVALID_HANDLE_VALUE;
  840. BOOL bUserLoggedOn = GetUserTokenFromSession(jc.wszAccount,jc.wszDomain,&hSessionUserToken);
  841. if(bUserLoggedOn)
  842. {
  843. schDebugOut((DEB_TRACE, "*** Terminal services running and user session found\n"));
  844. if (!jc.fIsPasswordNull)
  845. {
  846. *ppwszDesktop = WSZ_INTERACTIVE_DESKTOP;
  847. }
  848. // we're not passing this one out, close it
  849. if (hToken)
  850. CloseHandle(hToken);
  851. *phToken = hSessionUserToken;
  852. *pfTokenIsShellToken = FALSE;
  853. }
  854. else
  855. {
  856. schDebugOut((DEB_TRACE, "*** user session not found executing old code\n"));
  857. //
  858. // Providing a user is logged on locally, is the account logged
  859. // on above the same as that of the currently logged on user?
  860. // If so, use the shell's security token in place of that
  861. // obtained above so any UI associated with the job can
  862. // show up on the user's desktop.
  863. //
  864. // ** Important **
  865. //
  866. // Only perform this check if the account logon succeeded
  867. // above. Otherwise, it would be possible to specify an
  868. // account name with an invalid password and have the job
  869. // run anyway.
  870. //
  871. EnterCriticalSection(gUserLogonInfo.CritSection);
  872. GetLoggedOnUser();
  873. if (gUserLogonInfo.DomainUserName != NULL &&
  874. _wcsicmp(jc.wszAccount, gUserLogonInfo.UserName) == 0 &&
  875. _wcsicmp(jc.wszDomain, gUserLogonInfo.DomainName) == 0)
  876. {
  877. if (!jc.fIsPasswordNull)
  878. {
  879. *ppwszDesktop = WSZ_INTERACTIVE_DESKTOP;
  880. }
  881. // we're not passing this one out, so close it
  882. if (hToken)
  883. CloseHandle(hToken);
  884. *phToken = gUserLogonInfo.ShellToken;
  885. *pfTokenIsShellToken = TRUE;
  886. }
  887. else
  888. {
  889. LeaveCriticalSection(gUserLogonInfo.CritSection);
  890. //
  891. // Is this "run-only-if-logged-on"?
  892. //
  893. if (pJob->IsFlagSet(TASK_FLAG_RUN_ONLY_IF_LOGGED_ON))
  894. {
  895. //
  896. // The job is "run-only-if-logged-on" and the user is
  897. // not currently logged on, so fail silently
  898. //
  899. schDebugOut((DEB_TRACE, "Not running %ws because user is not logged on\n",
  900. pJob->GetFileName()));
  901. *pdwErrorMsg = IDS_ACCOUNT_LOGON_FAILED; // not really used
  902. *phrSpecificError = S_FALSE; // suppress error logging
  903. if (!jc.fIsPasswordNull)
  904. {
  905. CloseHandle(hToken);
  906. }
  907. return(FALSE);
  908. }
  909. *phToken = hToken;
  910. *ppwszDesktop = WSZ_SA_DESKTOP;
  911. //
  912. // Load the user profile associated with the account just logged on.
  913. // (If the user is already logged on, this will just increment the
  914. // profile ref count, to be decremented when the job stops.)
  915. // Don't bother doing this if the job is "run-only-if-logged-on", in
  916. // which case it's OK to unload the profile when the user logs off.
  917. //
  918. if (!pJob->IsFlagSet(TASK_FLAG_RUN_ONLY_IF_LOGGED_ON) && (NULL == *phUserProfile))
  919. {
  920. *phUserProfile = LoadAccountProfile(*phToken,
  921. jc.wszAccount,
  922. jc.wszDomain);
  923. }
  924. }
  925. }
  926. }
  927. return(TRUE);
  928. }
  929. //+---------------------------------------------------------------------------
  930. //
  931. // Function: LoadAccountProfile
  932. //
  933. // Synopsis: Attempt to load the profile for the specified user.
  934. //
  935. // Arguments: [hToken] - handle representing user
  936. // [pwszUser] - user name
  937. // [pwszDomain] - domain name
  938. //
  939. // Returns: Profile handle or NULL.
  940. //
  941. // History: 10-04-96 DavidMun Created
  942. // 07-07-99 AnirudhS Rewrote to use NetUserGetInfo
  943. //
  944. // Notes: Returned profile handle must be closed with
  945. // UnloadUserProfile(hToken, hUserProfile);
  946. // CODEWORK Delay-load netapi32.dll.
  947. //
  948. //----------------------------------------------------------------------------
  949. HANDLE
  950. LoadAccountProfile(
  951. HANDLE hToken,
  952. LPCWSTR pwszUser,
  953. LPCWSTR pwszDomain
  954. )
  955. {
  956. schDebugOut((DEB_TRACE, "Loading profile for '%ws%'\\'%ws'\n",
  957. pwszDomain, pwszUser));
  958. //
  959. // Determine the server on which to look up the account info
  960. // Skip this for for local accounts
  961. // CODEWORK lstrcmpi won't work if pwszDomain is a DNS name.
  962. //
  963. PDOMAIN_CONTROLLER_INFO pDcInfo = NULL;
  964. LPWSTR pwszDC = NULL;
  965. if (lstrcmpi(pwszDomain, gpwszComputerName) != 0)
  966. {
  967. DWORD err = DsGetDcName(NULL, pwszDomain, NULL, NULL, 0, &pDcInfo);
  968. if (err == NO_ERROR)
  969. {
  970. pwszDC = pDcInfo->DomainControllerName;
  971. }
  972. else
  973. {
  974. schDebugOut((DEB_ERROR, "DsGetDcName for '%ws' FAILED, %u\n",
  975. pwszDomain, err));
  976. // continue anyway, using NULL as the server
  977. }
  978. }
  979. //
  980. // Impersonate the user before calling NetUserGetInfo
  981. //
  982. if (!ImpersonateLoggedOnUser(hToken))
  983. {
  984. ERR_OUT("ImpersonateLoggedOnUser", GetLastError());
  985. }
  986. //
  987. // Look up the path to the profile for the account
  988. //
  989. LPUSER_INFO_3 pUserInfo = NULL;
  990. NET_API_STATUS nerr = NetUserGetInfo(pwszDC, pwszUser, 3,
  991. (LPBYTE *) &pUserInfo);
  992. //
  993. // Stop impersonating
  994. //
  995. if (!RevertToSelf())
  996. {
  997. ERR_OUT("RevertToSelf", GetLastError());
  998. }
  999. if (nerr != NERR_Success)
  1000. {
  1001. schDebugOut((DEB_ERROR, "NetUserGetInfo on '%ws' for '%ws' FAILED, %u\n",
  1002. pwszDC, pwszUser, nerr));
  1003. NetApiBufferFree(pDcInfo);
  1004. SetLastError(nerr);
  1005. return NULL;
  1006. }
  1007. NetApiBufferFree(pDcInfo);
  1008. schDebugOut((DEB_USER3, "Profile path is '%ws'\n", pUserInfo->usri3_profile));
  1009. //
  1010. // LoadUserProfile changes our USERPROFILE environment variable, so save
  1011. // its value before calling LoadUserProfile
  1012. //
  1013. WCHAR wszOrigUserProfile[MAX_PATH + 1] = L"";
  1014. GetEnvironmentVariable(USERPROFILE,
  1015. wszOrigUserProfile,
  1016. ARRAY_LEN(wszOrigUserProfile));
  1017. //
  1018. // Load the profile
  1019. //
  1020. PROFILEINFO ProfileInfo;
  1021. SecureZeroMemory(&ProfileInfo, sizeof(ProfileInfo));
  1022. ProfileInfo.dwSize = sizeof(ProfileInfo);
  1023. ProfileInfo.dwFlags = PI_NOUI;
  1024. ProfileInfo.lpUserName = (LPWSTR) pwszUser;
  1025. if (pUserInfo != NULL)
  1026. {
  1027. ProfileInfo.lpProfilePath = pUserInfo->usri3_profile;
  1028. }
  1029. if (!LoadUserProfile(hToken, &ProfileInfo))
  1030. {
  1031. schDebugOut((DEB_ERROR, "LoadUserProfile from '%ws' FAILED, %lu\n",
  1032. ProfileInfo.lpProfilePath, GetLastError()));
  1033. ProfileInfo.hProfile = NULL;
  1034. }
  1035. NetApiBufferFree(pUserInfo);
  1036. //
  1037. // Restore environment variables changed by LoadUserProfile
  1038. //
  1039. SetEnvironmentVariable(USERPROFILE, wszOrigUserProfile);
  1040. return ProfileInfo.hProfile;
  1041. }
  1042. //+----------------------------------------------------------------------------
  1043. //
  1044. // Function: AllowInteractiveServices
  1045. //
  1046. // Synopsis: Tests the NoInteractiveServices value in the Microsoft\Windows
  1047. // key. If the value is present and its value is non-zero return
  1048. // FALSE; return TRUE otherwise.
  1049. //
  1050. // Arguments: None.
  1051. //
  1052. // Returns: TRUE -- Allow interactive services.
  1053. // FALSE -- Disallow interactive services.
  1054. //
  1055. // Notes: None.
  1056. //
  1057. //-----------------------------------------------------------------------------
  1058. BOOL
  1059. AllowInteractiveServices(void)
  1060. {
  1061. #define WINDOWS_REGISTRY_PATH L"System\\CurrentControlSet\\Control\\Windows"
  1062. #define NOINTERACTIVESERVICES L"NoInteractiveServices"
  1063. HKEY hKey;
  1064. DWORD dwNoInteractiveServices, dwSize, dwType;
  1065. if (RegOpenKeyEx(HKEY_LOCAL_MACHINE,
  1066. WINDOWS_REGISTRY_PATH,
  1067. 0L,
  1068. KEY_READ,
  1069. &hKey) == ERROR_SUCCESS)
  1070. {
  1071. dwSize = sizeof(dwNoInteractiveServices);
  1072. if (RegQueryValueEx(hKey,
  1073. NOINTERACTIVESERVICES,
  1074. NULL,
  1075. &dwType,
  1076. (LPBYTE)&dwNoInteractiveServices,
  1077. &dwSize) == ERROR_SUCCESS)
  1078. {
  1079. if (dwType == REG_DWORD)
  1080. {
  1081. return(dwNoInteractiveServices == 0);
  1082. }
  1083. }
  1084. RegCloseKey(hKey);
  1085. }
  1086. //
  1087. // I really hate to have this be the default, but AT does this currently.
  1088. //
  1089. return(TRUE);
  1090. }
  1091. //+----------------------------------------------------------------------------
  1092. //
  1093. // Function: InitializeStartupInfo
  1094. //
  1095. // Synopsis: Initialize the STARTUPINFO structure passed. If the job is
  1096. // an AT interactive job, set structure fields accordingly.
  1097. //
  1098. // Arguments: [pJob] -- Job object.
  1099. // [ptszDesktop] -- Desktop name.
  1100. // [psi] -- Structure to initialized.
  1101. //
  1102. // Returns: None.
  1103. //
  1104. // Notes: None.
  1105. //
  1106. //-----------------------------------------------------------------------------
  1107. void
  1108. InitializeStartupInfo(
  1109. CJob * pJob,
  1110. LPTSTR ptszDesktop,
  1111. STARTUPINFO * psi)
  1112. {
  1113. //
  1114. // NT only.
  1115. //
  1116. // Check if the job is to run interactively. Applicable only for AT jobs.
  1117. //
  1118. // If the job is an AT job AND
  1119. // if the interactive flag is set AND
  1120. // if the NoInteractiveServices is not set in the registry THEN
  1121. //
  1122. // initialize the STARTUPINFO struct such that the AT job will run
  1123. // interactively.
  1124. //
  1125. BOOL fInteractive = pJob->IsFlagSet(JOB_I_FLAG_NET_SCHEDULE) &&
  1126. pJob->IsFlagSet(TASK_FLAG_INTERACTIVE) &&
  1127. AllowInteractiveServices();
  1128. //
  1129. // Emulate the NT4 AT_SVC and log an error to the event log, if the
  1130. // task is supposed to be interactive, but we can't be, due to
  1131. // system settings.
  1132. //
  1133. // Note this query is NOT fInteractive.
  1134. //
  1135. if (pJob->IsFlagSet(JOB_I_FLAG_NET_SCHEDULE) &&
  1136. pJob->IsFlagSet(TASK_FLAG_INTERACTIVE) &&
  1137. !AllowInteractiveServices())
  1138. {
  1139. LPWSTR StringArray[1];
  1140. HRESULT hr;
  1141. //
  1142. // EVENT_COMMAND_NOT_INTERACTIVE
  1143. // The %1 command is marked as an interactive command. However, the system is
  1144. // configured to not allow interactive command execution. This command may not
  1145. // function properly.
  1146. //
  1147. hr = pJob->GetCurFile(&StringArray[0]);
  1148. if (FAILED(hr))
  1149. {
  1150. ERR_OUT("Failed to obtain file name for non-interactive AT job", hr);
  1151. }
  1152. else
  1153. {
  1154. if (! ReportEvent(g_hAtEventSource, // source handle
  1155. EVENTLOG_WARNING_TYPE, // event type
  1156. 0, // event category
  1157. EVENT_COMMAND_NOT_INTERACTIVE, // event id
  1158. NULL, // user sid
  1159. 1, // number of strings
  1160. 0, // data block length
  1161. (LPCWSTR *)StringArray, // string array
  1162. NULL)) // data block
  1163. {
  1164. // Not fatal, but why did we fail?
  1165. ERR_OUT("Failed to report the non-interactive event to the eventlog", GetLastError());
  1166. }
  1167. //
  1168. // Clean up -- Theoretically, we should use IMalloc::Free here, but we are in
  1169. // the same process, and the memory was allocated from CoTaskMemAlloc,
  1170. // so CoTaskMemFree is the appropriate call
  1171. //
  1172. CoTaskMemFree(StringArray[0]);
  1173. }
  1174. }
  1175. GetStartupInfo(psi);
  1176. psi->dwFlags |= STARTF_USESHOWWINDOW;
  1177. psi->wShowWindow = SW_SHOWNOACTIVATE;
  1178. if (pJob->IsFlagSet(JOB_I_FLAG_NET_SCHEDULE))
  1179. {
  1180. if (fInteractive)
  1181. {
  1182. psi->lpDesktop = ptszDesktop;
  1183. psi->dwFlags |= STARTF_DESKTOPINHERIT;
  1184. }
  1185. else
  1186. {
  1187. psi->lpDesktop = WSZ_SA_DESKTOP;
  1188. psi->dwFlags &= ~STARTF_DESKTOPINHERIT;
  1189. }
  1190. }
  1191. else
  1192. {
  1193. psi->lpDesktop = ptszDesktop;
  1194. }
  1195. }
  1196. //+----------------------------------------------------------------------------
  1197. //
  1198. // Function: ComposeBatchParam
  1199. //
  1200. // Synopsis: Builds the CreateProcess command line parameter
  1201. //
  1202. // Arguments: [pwszPrefix] -
  1203. // [pwszAppPathName] - The run target task property.
  1204. // [pwszParameters] - The parameters task propery.
  1205. // [ppwszCmdLine] - The command line to return.
  1206. //
  1207. // Returns: S_OK or E_OUTOFMEMORY.
  1208. //
  1209. //-----------------------------------------------------------------------------
  1210. HRESULT
  1211. ComposeBatchParam(
  1212. LPCTSTR pwszPrefix,
  1213. LPCTSTR pwszAppPathName,
  1214. LPCTSTR pwszParameters,
  1215. LPTSTR * ppwszCmdLine)
  1216. {
  1217. ULONG cchCmdLine;
  1218. BOOL fBatchNameHasSpaces = HasSpaces(pwszAppPathName);
  1219. //
  1220. // Space for the command line is length of prefix "cmd /c " plus batch
  1221. // file name, plus two if the batch file name will be surrounded with
  1222. // spaces, plus length of parameters, if any, plus one for the space
  1223. // preceding the parameters, plus one for the terminating nul.
  1224. //
  1225. cchCmdLine = lstrlen(pwszPrefix) + 1 +
  1226. lstrlen(pwszAppPathName) +
  1227. (fBatchNameHasSpaces ? 2 : 0) +
  1228. (pwszParameters ? 1 + lstrlen(pwszParameters) : 0);
  1229. *ppwszCmdLine = new TCHAR[cchCmdLine];
  1230. if (!*ppwszCmdLine)
  1231. {
  1232. schDebugOut((DEB_ERROR,
  1233. "RunNTJob: Can't allocate %u for cmdline\n",
  1234. cchCmdLine));
  1235. return E_OUTOFMEMORY;
  1236. }
  1237. StringCchCopy(*ppwszCmdLine, cchCmdLine, pwszPrefix);
  1238. if (fBatchNameHasSpaces)
  1239. {
  1240. StringCchCat(*ppwszCmdLine, cchCmdLine, DQUOTE);
  1241. }
  1242. StringCchCat(*ppwszCmdLine, cchCmdLine, pwszAppPathName);
  1243. if (fBatchNameHasSpaces)
  1244. {
  1245. StringCchCat(*ppwszCmdLine, cchCmdLine, DQUOTE);
  1246. }
  1247. if (pwszParameters)
  1248. {
  1249. StringCchCat(*ppwszCmdLine, cchCmdLine, SPACE);
  1250. StringCchCat(*ppwszCmdLine, cchCmdLine, pwszParameters);
  1251. }
  1252. return S_OK;
  1253. }
  1254. //+----------------------------------------------------------------------------
  1255. //
  1256. // Function: ComposeParam
  1257. //
  1258. // Synopsis: Builds the CreateProcess command line parameter
  1259. //
  1260. // Arguments: [fTargetIsExe] - Is pwszRunTarget an exe or a document.
  1261. // [ptszRunTarget] - The run target task property.
  1262. // [ptszParameters] - The parameters task propery.
  1263. // [ptszCmdLine] - The command line to return.
  1264. //
  1265. // Returns: S_OK or E_OUTOFMEMORY.
  1266. //
  1267. //-----------------------------------------------------------------------------
  1268. HRESULT
  1269. ComposeParam(BOOL fTargetIsExe,
  1270. LPTSTR ptszRunTarget,
  1271. LPTSTR ptszParameters,
  1272. LPTSTR * pptszCmdLine)
  1273. {
  1274. LPTSTR ptszCmdLine;
  1275. //
  1276. // Check for whitespace in the app name.
  1277. //
  1278. BOOL fAppNameHasSpaces = HasSpaces(ptszRunTarget);
  1279. //
  1280. // If running a document, check for spaces in the doc path name.
  1281. //
  1282. BOOL fParamHasSpaces = FALSE;
  1283. if (!fTargetIsExe && HasSpaces(ptszParameters))
  1284. {
  1285. fParamHasSpaces = TRUE;
  1286. }
  1287. //
  1288. // Figure the length, adding 1 for the space and 1 for the null,
  1289. // plus 2 for the quotes, if needed.
  1290. //
  1291. DWORD cch = lstrlen(ptszRunTarget) + lstrlen(ptszParameters) + 2
  1292. + (fAppNameHasSpaces ? 2 : 0)
  1293. + (fParamHasSpaces ? 2 : 0);
  1294. ptszCmdLine = new TCHAR[cch];
  1295. if (!ptszCmdLine)
  1296. {
  1297. LogServiceError(IDS_NON_FATAL_ERROR,
  1298. ERROR_OUTOFMEMORY,
  1299. IDS_HELP_HINT_CLOSE_APPS);
  1300. ERR_OUT("CSchedWorker::RunWin95Job", E_OUTOFMEMORY);
  1301. *pptszCmdLine = NULL;
  1302. return E_OUTOFMEMORY;
  1303. }
  1304. if (fAppNameHasSpaces)
  1305. {
  1306. //
  1307. // Enclose the app name in quotes if it contains whitespace.
  1308. //
  1309. StringCchCopy(ptszCmdLine, cch, DQUOTE);
  1310. StringCchCat(ptszCmdLine, cch, ptszRunTarget);
  1311. StringCchCat(ptszCmdLine, cch, DQUOTE);
  1312. }
  1313. else
  1314. {
  1315. StringCchCopy(ptszCmdLine, cch, ptszRunTarget);
  1316. }
  1317. StringCchCat(ptszCmdLine, cch, SPACE);
  1318. if (fParamHasSpaces)
  1319. {
  1320. StringCchCat(ptszCmdLine, cch, DQUOTE);
  1321. }
  1322. StringCchCat(ptszCmdLine, cch, ptszParameters);
  1323. if (fParamHasSpaces)
  1324. {
  1325. StringCchCat(ptszCmdLine, cch, DQUOTE);
  1326. }
  1327. *pptszCmdLine = ptszCmdLine;
  1328. return S_OK;
  1329. }
  1330. //+----------------------------------------------------------------------------
  1331. //
  1332. // Function: MapFindExecutableError
  1333. //
  1334. // Synopsis: Converts the poorly designed error codes returned by the
  1335. // FindExecutable API to HRESULTs
  1336. //
  1337. // Arguments: [hRet] - Error return code from FindExecutable
  1338. //
  1339. // Returns: HRESULT (with FACILITY_WIN32) for the same error
  1340. //
  1341. //-----------------------------------------------------------------------------
  1342. HRESULT
  1343. MapFindExecutableError(HINSTANCE hRet)
  1344. {
  1345. schAssert((DWORD_PTR)hRet <= 32);
  1346. HRESULT hr;
  1347. if (hRet == 0)
  1348. {
  1349. hr = HRESULT_FROM_WIN32(ERROR_OUTOFMEMORY);
  1350. }
  1351. else if ((DWORD_PTR)hRet == 31)
  1352. {
  1353. hr = HRESULT_FROM_WIN32(ERROR_NO_ASSOCIATION);
  1354. }
  1355. else
  1356. {
  1357. hr = HRESULT_FROM_WIN32((DWORD_PTR)hRet);
  1358. }
  1359. return hr;
  1360. }
  1361. //+----------------------------------------------------------------------------
  1362. //
  1363. // Function: WaitForStubExe
  1364. //
  1365. // Synopsis: Waits for the stub exe to launch the job
  1366. //
  1367. // Arguments: [hProcess] - Handle to the stub exe process
  1368. //
  1369. // Returns: TRUE if stub exe launched job
  1370. // FALSE if stub exe didn't launch job. Last error is set to the
  1371. // exit code from the stub exe.
  1372. //
  1373. //-----------------------------------------------------------------------------
  1374. BOOL
  1375. WaitForStubExe(HANDLE hProcess)
  1376. {
  1377. BOOL fRanJob = FALSE;
  1378. DWORD dwWait = WaitForSingleObject(hProcess, 90000);
  1379. if (dwWait == WAIT_OBJECT_0)
  1380. {
  1381. DWORD dwExitCode;
  1382. if (!GetExitCodeProcess(hProcess, &dwExitCode))
  1383. {
  1384. ERR_OUT("GetExitCodeProcess", GetLastError());
  1385. }
  1386. else if (dwExitCode == 0)
  1387. {
  1388. fRanJob = TRUE;
  1389. }
  1390. else
  1391. {
  1392. ERR_OUT("Stub exe run", dwExitCode);
  1393. SetLastError(dwExitCode);
  1394. }
  1395. }
  1396. else if (dwWait == WAIT_TIMEOUT)
  1397. {
  1398. schAssert(!"Stub exe didn't exit in 90 sec!");
  1399. SetLastError(ERROR_TIMEOUT);
  1400. }
  1401. // else WAIT_FAILED - last error will be set to failure code
  1402. return fRanJob;
  1403. }
  1404. //+----------------------------------------------------------------------------
  1405. //
  1406. // Function: GetUserTokenFromSession
  1407. //
  1408. // Synopsis: Enumerates the sessions and returns token of the session in
  1409. // which the given user has logged on
  1410. //
  1411. // Arguments: [IN lpszUsername] - user account name
  1412. // [IN lpszDomain] - domain name
  1413. // [OUT phUserToken] - token to be returned
  1414. //
  1415. // Returns: FALSE if the given user is not found in the enumerated sessions
  1416. // TRUE if the user is found in the the enumerated sessions array
  1417. //
  1418. // This function enumerates the sessions and compared the user name
  1419. // and domainname of the session with the given username and domain
  1420. // name. If such session is found, it checks to see if it is a console
  1421. // session.
  1422. // If it is a console session, the search is terminated and the token
  1423. // given by the session is returned in phUserToken
  1424. // Else the token from first session saved and search is continued
  1425. //
  1426. // At the end of the search if no console session is found then
  1427. // the saved first session token is returned.
  1428. //
  1429. // Else if no session found whatsoever then the function returns FALSE
  1430. //-----------------------------------------------------------------------------
  1431. BOOL GetUserTokenFromSession(
  1432. LPTSTR lpszUsername, // user name
  1433. LPTSTR lpszDomain, // domain or server
  1434. PHANDLE phUserToken // receive tokens handle
  1435. )
  1436. {
  1437. PWTS_SESSION_INFO pWTSSessionInfo = NULL;
  1438. DWORD WTSSessionInfoCount = 0;
  1439. //WTS_CURRENT_SERVER_HANDLE indicates the terminal server
  1440. //on which your application is running
  1441. BOOL result = WTSEnumerateSessions(
  1442. WTS_CURRENT_SERVER_HANDLE,
  1443. 0, //Reserved; must be zero
  1444. 1, //version of the enumeration request. Must be 1
  1445. &pWTSSessionInfo,
  1446. &WTSSessionInfoCount);
  1447. if(!result)
  1448. {
  1449. schDebugOut((DEB_TRACE, "*** WTSEnumerateSessions failed\n"));
  1450. return (FALSE);
  1451. }
  1452. schDebugOut((DEB_TRACE, "*** WTSEnumerateSessions returned %d sessions\n",WTSSessionInfoCount));
  1453. LPTSTR pWTSDomainNameBuffer = NULL;
  1454. LPTSTR pWTSUserNameBuffer = NULL;
  1455. HANDLE hNewToken = INVALID_HANDLE_VALUE;
  1456. HANDLE hFirstToken = INVALID_HANDLE_VALUE;
  1457. HANDLE hConsoleToken = INVALID_HANDLE_VALUE;
  1458. // Get the session id of the session attached to the console. If there is
  1459. // no session attached to console then this return 0xFFFFFFFF
  1460. DWORD ConsoleSessionID = WTSGetActiveConsoleSessionId ();
  1461. BOOL bSuccess = FALSE;
  1462. //Check each session to see if the user name and the domain name matches with the
  1463. //ones that are passed to this function
  1464. for (DWORD i = 0; i < WTSSessionInfoCount; i++)
  1465. {
  1466. WTS_INFO_CLASS WTSInfoClass;
  1467. pWTSDomainNameBuffer = NULL;
  1468. pWTSUserNameBuffer = NULL;
  1469. DWORD BytesReturned = 0;
  1470. BOOL bDomainNameResult = WTSQuerySessionInformation(
  1471. WTS_CURRENT_SERVER_HANDLE,
  1472. pWTSSessionInfo[i].SessionId,
  1473. WTSDomainName, //the type of information to retrieve
  1474. &pWTSDomainNameBuffer,
  1475. &BytesReturned
  1476. );
  1477. BOOL bUserNameResult = WTSQuerySessionInformation(
  1478. WTS_CURRENT_SERVER_HANDLE,
  1479. pWTSSessionInfo[i].SessionId,
  1480. WTSUserName, //the type of information to retrieve
  1481. &pWTSUserNameBuffer,
  1482. &BytesReturned
  1483. );
  1484. if (bDomainNameResult && bUserNameResult)
  1485. {
  1486. schDebugOut((DEB_TRACE, "*** \n Comparing %s with %s and %s with %s",
  1487. lpszUsername,pWTSUserNameBuffer,
  1488. lpszDomain, pWTSDomainNameBuffer));
  1489. if (_wcsicmp(lpszUsername, pWTSUserNameBuffer) == 0 &&
  1490. _wcsicmp(lpszDomain, pWTSDomainNameBuffer) == 0)
  1491. {
  1492. // Call WTSQueryUserToken to retrieve a handle to the user access
  1493. // token for this session. Token returned by WTSQueryUserToken is
  1494. // a primary token and can be passed to CreateProcessAsUser
  1495. BOOL fRet = WTSQueryUserToken(pWTSSessionInfo[i].SessionId, &hNewToken);
  1496. if(fRet)
  1497. {
  1498. // Check to see if it is a console session
  1499. if(pWTSSessionInfo[i].SessionId == ConsoleSessionID)
  1500. {
  1501. schDebugOut((DEB_TRACE, "*** Console session found\n"));
  1502. // We have have found the user session that is attached to console
  1503. // No need to search the remaining So we can break from here
  1504. hConsoleToken = hNewToken;
  1505. WTSFreeMemory(pWTSUserNameBuffer);
  1506. WTSFreeMemory(pWTSDomainNameBuffer);
  1507. bSuccess = TRUE;
  1508. break;
  1509. }
  1510. // Else if this is the first token that matches then save it in hFirstToken
  1511. // and proceed to search for console session that matches with the user
  1512. // If such session is not found then we will use this token
  1513. else if (!bSuccess)
  1514. {
  1515. schDebugOut((DEB_TRACE, "*** First session found\n"));
  1516. hFirstToken = hNewToken;
  1517. bSuccess = TRUE;
  1518. }
  1519. else
  1520. {
  1521. if (hNewToken != INVALID_HANDLE_VALUE)
  1522. {
  1523. CloseHandle(hNewToken);
  1524. hNewToken = INVALID_HANDLE_VALUE;
  1525. }
  1526. }
  1527. // else keep seaching as we may get console session id in the remaining
  1528. // list
  1529. }
  1530. }
  1531. }
  1532. // pWTSUserNameBuffer may be non-null if bUserNameResult is false
  1533. if (pWTSUserNameBuffer)
  1534. {
  1535. WTSFreeMemory(pWTSUserNameBuffer);
  1536. }
  1537. // pWTSDomainNameBuffer may be non-null if bDomainNameResult is false
  1538. if (pWTSDomainNameBuffer)
  1539. {
  1540. WTSFreeMemory(pWTSDomainNameBuffer);
  1541. }
  1542. }
  1543. WTSFreeMemory(pWTSSessionInfo);
  1544. if(bSuccess)
  1545. {
  1546. // We may have either one or both open tokens.
  1547. // If we get hConsoleToken then we return hConsoleToken
  1548. // In that case if hFirstToken is open then we close hFirstToken
  1549. // If we dont get hConsoleToken then we return hFirstToken
  1550. if(hConsoleToken != INVALID_HANDLE_VALUE)
  1551. {
  1552. if(hFirstToken != INVALID_HANDLE_VALUE)
  1553. {
  1554. CloseHandle(hFirstToken);
  1555. }
  1556. *phUserToken = hConsoleToken;
  1557. schDebugOut((DEB_TRACE, "*** Returning TRUE with Console Session Token\n"));
  1558. }
  1559. else
  1560. {
  1561. *phUserToken = hFirstToken;
  1562. schDebugOut((DEB_TRACE, "*** Returning TRUE with First Session Token\n"));
  1563. }
  1564. }
  1565. else
  1566. {
  1567. schDebugOut((DEB_TRACE, "*** Returning FALSE\n"));
  1568. }
  1569. return (bSuccess);
  1570. }