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.

571 lines
18 KiB

  1. //
  2. // runonce.c (shared runonce code between explorer.exe and runonce.exe)
  3. //
  4. #include <runonce.h>
  5. // Need this to avoid build errors in places where this file is
  6. // included but deperecated functions are still used
  7. #define STRSAFE_NO_DEPRECATE
  8. #include <strsafe.h>
  9. #if (_WIN32_WINNT >= 0x0500)
  10. // stolen from <tsappcmp.h>
  11. #define TERMSRV_COMPAT_WAIT_USING_JOB_OBJECTS 0x00008000
  12. #define CompatibilityApp 1
  13. typedef LONG TERMSRV_COMPATIBILITY_CLASS;
  14. typedef BOOL (* PFNGSETTERMSRVAPPINSTALLMODE)(BOOL bState);
  15. typedef BOOL (* PFNGETTERMSRVCOMPATFLAGSEX)(LPWSTR pwszApp, DWORD* pdwFlags, TERMSRV_COMPATIBILITY_CLASS tscc);
  16. // even though this function is in kernel32.lib, we need to have a LoadLibrary/GetProcAddress
  17. // thunk for downlevel components who include this
  18. STDAPI_(BOOL) SHSetTermsrvAppInstallMode(BOOL bState)
  19. {
  20. static PFNGSETTERMSRVAPPINSTALLMODE pfn = NULL;
  21. if (pfn == NULL)
  22. {
  23. // kernel32 should already be loaded
  24. HMODULE hmod = GetModuleHandle(TEXT("kernel32.dll"));
  25. if (hmod)
  26. {
  27. pfn = (PFNGSETTERMSRVAPPINSTALLMODE)GetProcAddress(hmod, "SetTermsrvAppInstallMode");
  28. }
  29. else
  30. {
  31. pfn = (PFNGSETTERMSRVAPPINSTALLMODE)-1;
  32. }
  33. }
  34. if (pfn && (pfn != (PFNGSETTERMSRVAPPINSTALLMODE)-1))
  35. {
  36. return pfn(bState);
  37. }
  38. else
  39. {
  40. return FALSE;
  41. }
  42. }
  43. STDAPI_(ULONG) SHGetTermsrCompatFlagsEx(LPWSTR pwszApp, DWORD* pdwFlags, TERMSRV_COMPATIBILITY_CLASS tscc)
  44. {
  45. static PFNGETTERMSRVCOMPATFLAGSEX pfn = NULL;
  46. if (pfn == NULL)
  47. {
  48. HMODULE hmod = LoadLibrary(TEXT("TSAppCMP.DLL"));
  49. if (hmod)
  50. {
  51. pfn = (PFNGETTERMSRVCOMPATFLAGSEX)GetProcAddress(hmod, "GetTermsrCompatFlagsEx");
  52. }
  53. else
  54. {
  55. pfn = (PFNGETTERMSRVCOMPATFLAGSEX)-1;
  56. }
  57. }
  58. if (pfn && (pfn != (PFNGETTERMSRVCOMPATFLAGSEX)-1))
  59. {
  60. return pfn(pwszApp, pdwFlags, tscc);
  61. }
  62. else
  63. {
  64. *pdwFlags = 0;
  65. return 0;
  66. }
  67. }
  68. HANDLE SetJobCompletionPort(HANDLE hJob)
  69. {
  70. HANDLE hRet = NULL;
  71. HANDLE hIOPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 1);
  72. if (hIOPort != NULL)
  73. {
  74. JOBOBJECT_ASSOCIATE_COMPLETION_PORT CompletionPort;
  75. CompletionPort.CompletionKey = hJob ;
  76. CompletionPort.CompletionPort = hIOPort;
  77. if (SetInformationJobObject(hJob,
  78. JobObjectAssociateCompletionPortInformation,
  79. &CompletionPort,
  80. sizeof(CompletionPort)))
  81. {
  82. hRet = hIOPort;
  83. }
  84. else
  85. {
  86. CloseHandle(hIOPort);
  87. }
  88. }
  89. return hRet;
  90. }
  91. STDAPI_(DWORD) WaitingThreadProc(void *pv)
  92. {
  93. HANDLE hIOPort = (HANDLE)pv;
  94. if (hIOPort)
  95. {
  96. while (TRUE)
  97. {
  98. DWORD dwCompletionCode;
  99. ULONG_PTR pCompletionKey;
  100. LPOVERLAPPED pOverlapped;
  101. if (!GetQueuedCompletionStatus(hIOPort, &dwCompletionCode, &pCompletionKey, &pOverlapped, INFINITE) ||
  102. (dwCompletionCode == JOB_OBJECT_MSG_ACTIVE_PROCESS_ZERO))
  103. {
  104. break;
  105. }
  106. }
  107. }
  108. return 0;
  109. }
  110. //
  111. // The following handles running an application and optionally waiting for it
  112. // to all the child procs to terminate. This is accomplished thru Kernel Job Objects
  113. // which is only available in NT5
  114. //
  115. BOOL _CreateRegJob(LPCTSTR pszCmd, BOOL bWait)
  116. {
  117. BOOL bRet = FALSE;
  118. HANDLE hJobObject = CreateJobObjectW(NULL, NULL);
  119. if (hJobObject)
  120. {
  121. HANDLE hIOPort = SetJobCompletionPort(hJobObject);
  122. if (hIOPort)
  123. {
  124. DWORD dwID;
  125. HANDLE hThread = CreateThread(NULL,
  126. 0,
  127. WaitingThreadProc,
  128. (void*)hIOPort,
  129. CREATE_SUSPENDED,
  130. &dwID);
  131. if (hThread)
  132. {
  133. PROCESS_INFORMATION pi = {0};
  134. STARTUPINFO si = {0};
  135. UINT fMask = SEE_MASK_FLAG_NO_UI;
  136. DWORD dwCreationFlags = CREATE_SUSPENDED;
  137. TCHAR sz[MAX_PATH * 2];
  138. TCHAR szAppPath[MAX_PATH];
  139. if (GetSystemDirectory(szAppPath, ARRAYSIZE(szAppPath)))
  140. {
  141. if (PathAppend(szAppPath, TEXT("RunDLL32.EXE")))
  142. {
  143. if (SUCCEEDED(StringCchPrintf(sz, ARRAYSIZE(sz),
  144. TEXT("RunDLL32.EXE Shell32.DLL,ShellExec_RunDLL ?0x%X?%s"), fMask, pszCmd)))
  145. {
  146. si.cb = sizeof(si);
  147. if (CreateProcess(szAppPath,
  148. sz,
  149. NULL,
  150. NULL,
  151. FALSE,
  152. dwCreationFlags,
  153. NULL,
  154. NULL,
  155. &si,
  156. &pi))
  157. {
  158. if (AssignProcessToJobObject(hJobObject, pi.hProcess))
  159. {
  160. // success!
  161. bRet = TRUE;
  162. ResumeThread(pi.hThread);
  163. ResumeThread(hThread);
  164. if (bWait)
  165. {
  166. SHProcessMessagesUntilEvent(NULL, hThread, INFINITE);
  167. }
  168. }
  169. else
  170. {
  171. TerminateProcess(pi.hProcess, ERROR_ACCESS_DENIED);
  172. }
  173. CloseHandle(pi.hProcess);
  174. CloseHandle(pi.hThread);
  175. }
  176. }
  177. }
  178. }
  179. if (!bRet)
  180. {
  181. TerminateThread(hThread, ERROR_ACCESS_DENIED);
  182. }
  183. CloseHandle(hThread);
  184. }
  185. CloseHandle(hIOPort);
  186. }
  187. CloseHandle(hJobObject);
  188. }
  189. return bRet;
  190. }
  191. BOOL _TryHydra(LPCTSTR pszCmd, RRA_FLAGS *pflags)
  192. {
  193. // See if the terminal-services is enabled in "Application Server" mode
  194. if (IsOS(OS_TERMINALSERVER) && SHSetTermsrvAppInstallMode(TRUE))
  195. {
  196. WCHAR sz[MAX_PATH];
  197. *pflags |= RRA_WAIT;
  198. // Changing timing blows up IE 4.0, but IE5 is ok!
  199. // we are on a TS machine, NT version 4 or 5, with admin priv
  200. // see if the app-compatability flag is set for this executable
  201. // to use the special job-objects for executing module
  202. // get the module name, without the arguments
  203. if (0 < PathProcessCommand(pszCmd, sz, ARRAYSIZE(sz), PPCF_NODIRECTORIES))
  204. {
  205. ULONG ulCompat;
  206. SHGetTermsrCompatFlagsEx(sz, &ulCompat, CompatibilityApp);
  207. // if the special flag for this module-name is set...
  208. if (ulCompat & TERMSRV_COMPAT_WAIT_USING_JOB_OBJECTS)
  209. {
  210. *pflags |= RRA_USEJOBOBJECTS;
  211. }
  212. }
  213. return TRUE;
  214. }
  215. return FALSE;
  216. }
  217. #endif // (_WIN32_WINNT >= 0x0500)
  218. //
  219. // On success: returns process handle or INVALID_HANDLE_VALUE if no process
  220. // was launched (i.e., launched via DDE).
  221. // On failure: returns INVALID_HANDLE_VALUE.
  222. //
  223. BOOL _ShellExecRegApp(LPCTSTR pszCmd, BOOL fNoUI, BOOL fWait)
  224. {
  225. TCHAR szQuotedCmdLine[MAX_PATH+2];
  226. LPTSTR pszArgs;
  227. SHELLEXECUTEINFO ei = {0};
  228. BOOL fNoError = TRUE;
  229. // Gross, but if the process command fails, copy the command line to let
  230. // shell execute report the errors
  231. if (PathProcessCommand((LPWSTR)pszCmd,
  232. (LPWSTR)szQuotedCmdLine,
  233. ARRAYSIZE(szQuotedCmdLine),
  234. PPCF_ADDARGUMENTS|PPCF_FORCEQUALIFY) == -1)
  235. {
  236. if (FAILED(StringCchCopy(szQuotedCmdLine, ARRAYSIZE(szQuotedCmdLine), pszCmd)))
  237. {
  238. fNoError = FALSE;
  239. }
  240. }
  241. if (fNoError)
  242. {
  243. pszArgs= PathGetArgs(szQuotedCmdLine);
  244. if (*pszArgs)
  245. {
  246. // Strip args
  247. *(pszArgs - 1) = 0;
  248. }
  249. PathUnquoteSpaces(szQuotedCmdLine);
  250. ei.cbSize = sizeof(SHELLEXECUTEINFO);
  251. ei.lpFile = szQuotedCmdLine;
  252. ei.lpParameters = pszArgs;
  253. ei.nShow = SW_SHOWNORMAL;
  254. ei.fMask = SEE_MASK_NOCLOSEPROCESS;
  255. if (fNoUI)
  256. {
  257. ei.fMask |= SEE_MASK_FLAG_NO_UI;
  258. }
  259. if (ShellExecuteEx(&ei))
  260. {
  261. if (ei.hProcess)
  262. {
  263. if (fWait)
  264. {
  265. SHProcessMessagesUntilEvent(NULL, ei.hProcess, INFINITE);
  266. }
  267. CloseHandle(ei.hProcess);
  268. }
  269. fNoError = TRUE;
  270. }
  271. else
  272. {
  273. fNoError = FALSE;
  274. }
  275. }
  276. return fNoError;
  277. }
  278. // The following handles running an application and optionally waiting for it
  279. // to terminate.
  280. STDAPI_(BOOL) ShellExecuteRegApp(LPCTSTR pszCmdLine, RRA_FLAGS fFlags)
  281. {
  282. BOOL bRet = FALSE;
  283. if (!pszCmdLine || !*pszCmdLine)
  284. {
  285. // Don't let empty strings through, they will endup doing something dumb
  286. // like opening a command prompt or the like
  287. return bRet;
  288. }
  289. #if (_WIN32_WINNT >= 0x0500)
  290. if (fFlags & RRA_USEJOBOBJECTS)
  291. {
  292. bRet = _CreateRegJob(pszCmdLine, fFlags & RRA_WAIT);
  293. }
  294. #endif
  295. if (!bRet)
  296. {
  297. // fallback if necessary.
  298. bRet = _ShellExecRegApp(pszCmdLine, fFlags & RRA_NOUI, fFlags & RRA_WAIT);
  299. }
  300. return bRet;
  301. }
  302. STDAPI_(BOOL) Cabinet_EnumRegApps(HKEY hkeyParent, LPCTSTR pszSubkey, RRA_FLAGS fFlags, PFNREGAPPSCALLBACK pfnCallback, LPARAM lParam)
  303. {
  304. HKEY hkey;
  305. BOOL bRet = TRUE;
  306. // With the addition of the ACL controlled "policy" run keys RegOpenKey
  307. // might fail on the pszSubkey. Use RegOpenKeyEx with MAXIMIM_ALLOWED
  308. // to ensure that we successfully open the subkey.
  309. if (RegOpenKeyEx(hkeyParent, pszSubkey, 0, MAXIMUM_ALLOWED, &hkey) == ERROR_SUCCESS)
  310. {
  311. DWORD cbValue;
  312. DWORD dwType;
  313. DWORD i;
  314. TCHAR szValueName[80];
  315. TCHAR szCmdLine[MAX_PATH];
  316. HDPA hdpaEntries = NULL;
  317. #ifdef DEBUG
  318. //
  319. // we only support named values so explicitly purge default values
  320. //
  321. LONG cbData = sizeof(szCmdLine);
  322. if (RegQueryValue(hkey, NULL, szCmdLine, &cbData) == ERROR_SUCCESS)
  323. {
  324. ASSERTMSG((cbData <= 2), "Cabinet_EnumRegApps: BOGUS default entry in <%s> '%s'", pszSubkey, szCmdLine);
  325. RegDeleteValue(hkey, NULL);
  326. }
  327. #endif
  328. // now enumerate all of the values.
  329. for (i = 0; !g_fEndSession ; i++)
  330. {
  331. LONG lEnum;
  332. DWORD cbData;
  333. cbValue = ARRAYSIZE(szValueName);
  334. cbData = sizeof(szCmdLine);
  335. lEnum = RegEnumValue(hkey, i, szValueName, &cbValue, NULL, &dwType, (LPBYTE)szCmdLine, &cbData);
  336. if (ERROR_MORE_DATA == lEnum)
  337. {
  338. // ERROR_MORE_DATA means the value name or data was too large
  339. // skip to the next item
  340. TraceMsg(TF_WARNING, "Cabinet_EnumRegApps: cannot run oversize entry '%s' in <%s>", szValueName, pszSubkey);
  341. continue;
  342. }
  343. else if (lEnum != ERROR_SUCCESS)
  344. {
  345. if (lEnum != ERROR_NO_MORE_ITEMS)
  346. {
  347. // we hit some kind of registry failure
  348. bRet = FALSE;
  349. }
  350. break;
  351. }
  352. if ((dwType == REG_SZ) || (dwType == REG_EXPAND_SZ))
  353. {
  354. REGAPP_INFO * prai;
  355. if (dwType == REG_EXPAND_SZ)
  356. {
  357. DWORD dwChars;
  358. TCHAR szCmdLineT[MAX_PATH];
  359. if (FAILED(StringCchCopy(szCmdLineT, ARRAYSIZE(szCmdLineT), szCmdLine)))
  360. {
  361. // bail on this value if string doesn't fit
  362. continue;
  363. }
  364. dwChars = SHExpandEnvironmentStrings(szCmdLineT,
  365. szCmdLine,
  366. ARRAYSIZE(szCmdLine));
  367. if ((dwChars == 0) || (dwChars > ARRAYSIZE(szCmdLine)))
  368. {
  369. // bail on this value if we failed the expansion, or if the string is > MAX_PATH
  370. TraceMsg(TF_WARNING, "Cabinet_EnumRegApps: expansion of '%s' in <%s> failed or is too long", szCmdLineT, pszSubkey);
  371. continue;
  372. }
  373. }
  374. TraceMsg(TF_GENERAL, "Cabinet_EnumRegApps: subkey = %s cmdline = %s", pszSubkey, szCmdLine);
  375. if (g_fCleanBoot && (szValueName[0] != TEXT('*')))
  376. {
  377. // only run things marked with a "*" in when in SafeMode
  378. continue;
  379. }
  380. // We used to execute each entry, wait for it to finish, and then make the next call to
  381. // RegEnumValue(). The problem with this is that some apps add themselves back to the runonce
  382. // after they are finished (test harnesses that reboot machines and want to be restarted) and
  383. // we dont want to delete them, so we snapshot the registry keys and execute them after we
  384. // have finished the enum.
  385. prai = (REGAPP_INFO *)LocalAlloc(LPTR, sizeof(REGAPP_INFO));
  386. if (prai)
  387. {
  388. if (SUCCEEDED(StringCchCopy(prai->szSubkey, ARRAYSIZE(prai->szSubkey), pszSubkey)) &&
  389. SUCCEEDED(StringCchCopy(prai->szValueName, ARRAYSIZE(prai->szValueName), szValueName)) &&
  390. SUCCEEDED(StringCchCopy(prai->szCmdLine, ARRAYSIZE(prai->szCmdLine), szCmdLine)))
  391. {
  392. if (!hdpaEntries)
  393. {
  394. hdpaEntries = DPA_Create(5);
  395. }
  396. if (!hdpaEntries || (DPA_AppendPtr(hdpaEntries, prai) == -1))
  397. {
  398. LocalFree(prai);
  399. }
  400. }
  401. }
  402. }
  403. }
  404. if (hdpaEntries)
  405. {
  406. int iIndex;
  407. int iTotal = DPA_GetPtrCount(hdpaEntries);
  408. for (iIndex = 0; iIndex < iTotal; iIndex++)
  409. {
  410. REGAPP_INFO* prai = (REGAPP_INFO*)DPA_GetPtr(hdpaEntries, iIndex);
  411. ASSERT(prai);
  412. // NB Things marked with a '!' mean delete after
  413. // the CreateProcess not before. This is to allow
  414. // certain apps (runonce.exe) to be allowed to rerun
  415. // to if the machine goes down in the middle of execing
  416. // them. Be very afraid of this switch.
  417. if ((fFlags & RRA_DELETE) && (prai->szValueName[0] != TEXT('!')))
  418. {
  419. // This delete can fail if the user doesn't have the privilege
  420. if (RegDeleteValue(hkey, prai->szValueName) != ERROR_SUCCESS)
  421. {
  422. TraceMsg(TF_WARNING, "Cabinet_EnumRegApps: skipping entry %s (cannot delete the value)", prai->szValueName);
  423. LocalFree(prai);
  424. continue;
  425. }
  426. }
  427. pfnCallback(prai->szSubkey, prai->szCmdLine, fFlags, lParam);
  428. // Post delete '!' things.
  429. if ((fFlags & RRA_DELETE) && (prai->szValueName[0] == TEXT('!')))
  430. {
  431. // This delete can fail if the user doesn't have the privilege
  432. if (RegDeleteValue(hkey, prai->szValueName) != ERROR_SUCCESS)
  433. {
  434. TraceMsg(TF_WARNING, "Cabinet_EnumRegApps: cannot delete the value %s ", prai->szValueName);
  435. }
  436. }
  437. LocalFree(prai);
  438. }
  439. DPA_Destroy(hdpaEntries);
  440. hdpaEntries = NULL;
  441. }
  442. RegCloseKey(hkey);
  443. }
  444. else
  445. {
  446. TraceMsg(TF_WARNING, "Cabinet_EnumRegApps: failed to open subkey %s !", pszSubkey);
  447. bRet = FALSE;
  448. }
  449. if (g_fEndSession)
  450. {
  451. // NOTE: this is for explorer only, other consumers of runonce.c must declare g_fEndSession but leave
  452. // it set to FALSE always.
  453. // if we rx'd a WM_ENDSESSION whilst running any of these keys we must exit the process.
  454. ExitProcess(0);
  455. }
  456. return bRet;
  457. }
  458. STDAPI_(BOOL) ExecuteRegAppEnumProc(LPCTSTR szSubkey, LPCTSTR szCmdLine, RRA_FLAGS fFlags, LPARAM lParam)
  459. {
  460. BOOL bRet;
  461. RRA_FLAGS flagsTemp = fFlags;
  462. BOOL fInTSInstallMode = FALSE;
  463. #if (_WIN32_WINNT >= 0x0500)
  464. // In here, We only attempt TS specific in app-install-mode
  465. // if RunOnce entries are being processed
  466. if (0 == lstrcmpi(szSubkey, REGSTR_PATH_RUNONCE))
  467. {
  468. fInTSInstallMode = _TryHydra(szCmdLine, &flagsTemp);
  469. }
  470. #endif
  471. bRet = ShellExecuteRegApp(szCmdLine, flagsTemp);
  472. #if (_WIN32_WINNT >= 0x0500)
  473. if (fInTSInstallMode)
  474. {
  475. SHSetTermsrvAppInstallMode(FALSE);
  476. }
  477. #endif
  478. return bRet;
  479. }