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.

568 lines
14 KiB

  1. /*++
  2. Copyright (c) 2000-2002 Microsoft Corporation
  3. Module Name:
  4. LimitFindFile.cpp
  5. Abstract:
  6. This shim was originally intended for QuickTime's qt32inst.exe which
  7. did a breadth-first search of the directory tree and would overflow
  8. the buffer in which it was keeping a list of subdirs yet to visit.
  9. With this shim you can limit the number of files that a single FindFile
  10. search will return, you can limit the number of subdirectories (aka the
  11. branching factor) returned, you can limit the "depth" to which any
  12. FindFile search will locate files, and you can specify whether these
  13. limits should be applied to all FindFiles or only fully-qualified FindFiles.
  14. You can also request that FindFile return only short filenames.
  15. The shim's arguments are:
  16. DEPTH=#
  17. BRANCH=#
  18. FILES=#
  19. SHORTFILENAMES or LONGFILENAMES
  20. LIMITRELATIVE or ALLOWRELATIVE
  21. The default behavior is:
  22. SHORTFILENAMES
  23. DEPTH = 4
  24. ALLOWRELATIVE
  25. ... but if any command line is specified, the behavior is only that which
  26. is specified on the command line (no default behavior).
  27. An example command line:
  28. COMMAND_LINE="FILES=100 LIMITRELATIVE"
  29. Which would limit every FindFile search to returning 100 or fewer files (but
  30. still returning any and all subdirectories).
  31. Note: Depth is a bit tricky. The method used is to count backslashes, so
  32. limiting depth to zero will allow no files to be found ("C:\autorun.bat" has
  33. 1 backslash).
  34. History:
  35. 08/24/2000 t-adams Created
  36. 03/14/2002 mnikkel Changed InitializeCriticalSection to InitializeCriticalSectionAndSpinCount
  37. changed to use strsafe.h
  38. --*/
  39. #include "precomp.h"
  40. IMPLEMENT_SHIM_BEGIN(LimitFindFile)
  41. #include "ShimHookMacro.h"
  42. APIHOOK_ENUM_BEGIN
  43. APIHOOK_ENUM_ENTRY(FindFirstFileA)
  44. APIHOOK_ENUM_ENTRY(FindNextFileA)
  45. APIHOOK_ENUM_ENTRY(FindClose)
  46. APIHOOK_ENUM_END
  47. // Linked list of FindFileHandles
  48. struct FFNode
  49. {
  50. FFNode *next;
  51. HANDLE hFF;
  52. DWORD dwBranches;
  53. DWORD dwFiles;
  54. };
  55. FFNode *g_FFList = NULL;
  56. // Default behaviors - overridden by Commandline
  57. BOOL g_bUseShortNames = TRUE;
  58. BOOL g_bLimitDepth = TRUE;
  59. DWORD g_dwDepthLimit = 4;
  60. BOOL g_bLimitRelative = FALSE;
  61. BOOL g_bLimitBranch = FALSE;
  62. DWORD g_dwBranchLimit = 0;
  63. BOOL g_bLimitFiles = FALSE;
  64. DWORD g_dwFileLimit = 0;
  65. CRITICAL_SECTION g_MakeThreadSafe;
  66. /*++
  67. Abstract:
  68. ApplyLimits applys the recently found file from lpFindFileData to the
  69. current node, checks that none of the limits have been violated, and
  70. shortens the filename if requested.
  71. It returns TRUE if within limits, FALSE if limits have been exceeded.
  72. History:
  73. 08/24/2000 t-adams Created
  74. --*/
  75. BOOL ApplyLimits(FFNode *pFFNode, LPWIN32_FIND_DATAA lpFindFileData)
  76. {
  77. BOOL bRet = TRUE;
  78. // If it's a directory
  79. if ( lpFindFileData->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY )
  80. {
  81. pFFNode->dwBranches++;
  82. if ( g_bLimitBranch && pFFNode->dwBranches > g_dwBranchLimit )
  83. {
  84. bRet = FALSE;
  85. goto exit;
  86. }
  87. }
  88. else
  89. { // else it's a file
  90. pFFNode->dwFiles++;
  91. if ( g_bLimitFiles && pFFNode->dwFiles > g_dwFileLimit )
  92. {
  93. bRet = FALSE;
  94. goto exit;
  95. }
  96. }
  97. // Change to short name if requested
  98. if ( g_bUseShortNames && NULL != lpFindFileData->cAlternateFileName[0])
  99. {
  100. if (S_OK != StringCchCopyA(lpFindFileData->cFileName,
  101. ARRAYSIZE(lpFindFileData->cFileName),
  102. lpFindFileData->cAlternateFileName))
  103. bRet = FALSE;
  104. }
  105. exit:
  106. return bRet;
  107. }
  108. /*++
  109. Abstract:
  110. CheckDepthLimit checks to see if the depth of the requested search
  111. is greater than is allowed. If we are limiting relative paths, then
  112. the current directory is prepended to the requested search string.
  113. It returns TRUE if within limits, FALSE if limits have been exceeded.
  114. History:
  115. 08/24/2000 t-adams Created
  116. --*/
  117. BOOL
  118. CheckDepthLimit(const CString & csFileName)
  119. {
  120. BOOL bRet = TRUE;
  121. CSTRING_TRY
  122. {
  123. // Check the depth of the requested file
  124. if ( g_bLimitDepth )
  125. {
  126. DWORD dwDepth = 0;
  127. int nIndex = 0;
  128. for(; nIndex >= 0; dwDepth++)
  129. {
  130. nIndex = csFileName.Find(L'\\', nIndex);
  131. }
  132. if ( dwDepth > g_dwDepthLimit )
  133. {
  134. bRet = FALSE;
  135. }
  136. }
  137. }
  138. CSTRING_CATCH
  139. {
  140. // Do nothing
  141. }
  142. return bRet;
  143. }
  144. /*++
  145. Abstract:
  146. This function checks the depth of the requested search (see comments above).
  147. If the depth check passes it performs the search, request limit application,
  148. and finally returns a successful handle only if within all limits.
  149. History:
  150. 08/24/2000 t-adams Created
  151. --*/
  152. HANDLE
  153. APIHOOK(FindFirstFileA)(
  154. LPCSTR lpFileName,
  155. LPWIN32_FIND_DATAA lpFindFileData)
  156. {
  157. HANDLE hRet = INVALID_HANDLE_VALUE;
  158. FFNode *pFFNode = NULL;
  159. BOOL bRelPath = FALSE;
  160. CString csFileName(lpFileName);
  161. // Determine if the path is relative to the CWD:
  162. CString csDrive;
  163. csFileName.GetDrivePortion(csDrive);
  164. bRelPath = csDrive.IsEmpty();
  165. // If it is a relative path & we're not limiting such, then just do
  166. // the FindFile and get out.
  167. if ( bRelPath)
  168. {
  169. if (!g_bLimitRelative)
  170. {
  171. return ORIGINAL_API(FindFirstFileA)(lpFileName, lpFindFileData);
  172. }
  173. // We need to expand the directory portion of lpFileName to its full path
  174. CString csPath;
  175. CString csFile;
  176. csFileName.GetNotLastPathComponent(csPath);
  177. csFileName.GetLastPathComponent(csFile);
  178. csPath.GetFullPathNameW();
  179. csPath.AppendPath(csFile);
  180. csFileName = csPath;
  181. // Check the depth limit
  182. if ( !CheckDepthLimit(csFileName) )
  183. {
  184. return INVALID_HANDLE_VALUE;
  185. }
  186. }
  187. hRet = ORIGINAL_API(FindFirstFileA)(lpFileName, lpFindFileData);
  188. if ( INVALID_HANDLE_VALUE == hRet )
  189. {
  190. return hRet;
  191. }
  192. EnterCriticalSection(&g_MakeThreadSafe);
  193. // Make a new node for this handle
  194. pFFNode = (FFNode *) malloc(sizeof FFNode);
  195. if ( !pFFNode )
  196. {
  197. // Don't close the find, maybe it could still work for the app.
  198. goto exit;
  199. }
  200. pFFNode->hFF = hRet;
  201. pFFNode->dwBranches = 0;
  202. pFFNode->dwFiles = 0;
  203. // Apply our limits until we get a passable find
  204. while( !ApplyLimits(pFFNode, lpFindFileData) )
  205. {
  206. // If there are no more files to find, clean up & exit
  207. // else loop back & ApplyLimits again
  208. if ( !FindNextFileA(hRet, lpFindFileData) )
  209. {
  210. free(pFFNode);
  211. FindClose(hRet);
  212. hRet = INVALID_HANDLE_VALUE;
  213. goto exit;
  214. }
  215. }
  216. // We are clear to add this node to the global list
  217. pFFNode->next = g_FFList;
  218. g_FFList = pFFNode;
  219. LeaveCriticalSection(&g_MakeThreadSafe);
  220. exit:
  221. return hRet;
  222. }
  223. /*++
  224. Abstract:
  225. This function continues a limited search given the search's handle.
  226. History:
  227. 08/24/2000 t-adams Created
  228. --*/
  229. BOOL
  230. FindNextFileAInternal(
  231. HANDLE hFindFile,
  232. LPWIN32_FIND_DATAA lpFindFileData)
  233. {
  234. FFNode *pFFNode = NULL;
  235. BOOL bRet = ORIGINAL_API(FindNextFileA)(hFindFile, lpFindFileData);
  236. if ( !bRet )
  237. {
  238. goto exit;
  239. }
  240. // Find our node in the global list
  241. pFFNode = g_FFList;
  242. while( pFFNode )
  243. {
  244. if ( pFFNode->hFF == hFindFile )
  245. {
  246. break;
  247. }
  248. pFFNode = pFFNode->next;
  249. }
  250. // We don't keep track of relative-path searches if we're not
  251. // limiting such.
  252. if ( pFFNode == NULL )
  253. {
  254. goto exit;
  255. }
  256. // Apply our limits until we get a passable find
  257. while( !ApplyLimits(pFFNode, lpFindFileData) )
  258. {
  259. // If there are no more files to find return FALSE
  260. // else loop back & ApplyLimits again
  261. if ( !FindNextFileAInternal(hFindFile, lpFindFileData) )
  262. {
  263. bRet = FALSE;
  264. goto exit;
  265. }
  266. }
  267. exit:
  268. return bRet;
  269. }
  270. BOOL
  271. APIHOOK(FindNextFileA)(
  272. HANDLE hFindFile,
  273. LPWIN32_FIND_DATAA lpFindFileData)
  274. {
  275. // FindNextFileAInternal is called seperately since it may recurse
  276. EnterCriticalSection(&g_MakeThreadSafe);
  277. BOOL bRet = FindNextFileAInternal(hFindFile, lpFindFileData);
  278. LeaveCriticalSection(&g_MakeThreadSafe);
  279. return bRet;
  280. }
  281. /*++
  282. Abstract:
  283. This function closes a search, cleaning up the structures used
  284. in keeping track of the limits.
  285. History:
  286. 08/24/2000 t-adams Created
  287. --*/
  288. BOOL
  289. APIHOOK(FindClose)(
  290. HANDLE hFindFile)
  291. {
  292. FFNode *pFFNode, *prev;
  293. BOOL bRet = ORIGINAL_API(FindClose)(hFindFile);
  294. EnterCriticalSection(&g_MakeThreadSafe);
  295. // Find the node that matches the handle
  296. pFFNode = g_FFList;
  297. prev = NULL;
  298. while( pFFNode )
  299. {
  300. if ( pFFNode->hFF == hFindFile )
  301. {
  302. // Remove this node from this list
  303. if ( prev )
  304. {
  305. prev->next = pFFNode->next;
  306. }
  307. else
  308. {
  309. g_FFList = pFFNode->next;
  310. }
  311. free(pFFNode);
  312. pFFNode = NULL;
  313. break;
  314. }
  315. prev = pFFNode;
  316. pFFNode = pFFNode->next;
  317. }
  318. LeaveCriticalSection(&g_MakeThreadSafe);
  319. return bRet;
  320. }
  321. /*++
  322. Abstract:
  323. This function parses the command line.
  324. See the top of the file for valid arguments.
  325. History:
  326. 08/24/2000 t-adams Created
  327. --*/
  328. VOID
  329. ParseCommandLine( LPCSTR lpCommandLine )
  330. {
  331. // If there is a command line, reset the default behavior
  332. if (*lpCommandLine != 0)
  333. {
  334. g_bLimitDepth = FALSE;
  335. g_bLimitBranch = FALSE;
  336. g_bLimitFiles = FALSE;
  337. g_bUseShortNames = FALSE;
  338. }
  339. CSTRING_TRY
  340. {
  341. CStringToken csCommandLine(COMMAND_LINE, L" ,\t;:=");
  342. CString csOperator;
  343. // Parse the command line
  344. DWORD *pdwValue = NULL;
  345. while (csCommandLine.GetToken(csOperator))
  346. {
  347. if (csOperator.IsEmpty())
  348. {
  349. goto Exit;
  350. }
  351. // If we're looking for a value
  352. if ( pdwValue )
  353. {
  354. *pdwValue = atol(csOperator.GetAnsi());
  355. pdwValue = NULL;
  356. }
  357. else
  358. { // We're expecting a keyword
  359. if ( csOperator.CompareNoCase(L"DEPTH") == 0 )
  360. {
  361. g_bLimitDepth = TRUE;
  362. pdwValue = &g_dwDepthLimit;
  363. }
  364. else if ( csOperator.CompareNoCase(L"BRANCH") == 0 )
  365. {
  366. g_bLimitBranch = TRUE;
  367. pdwValue = &g_dwBranchLimit;
  368. }
  369. else if ( csOperator.CompareNoCase(L"FILES") == 0 )
  370. {
  371. g_bLimitFiles = TRUE;
  372. pdwValue = &g_dwFileLimit;
  373. }
  374. else if ( csOperator.CompareNoCase(L"SHORTFILENAMES") == 0)
  375. {
  376. g_bUseShortNames = TRUE;
  377. // Don't need a value here
  378. }
  379. else if ( csOperator.CompareNoCase(L"LONGFILENAMES") == 0)
  380. {
  381. g_bUseShortNames = FALSE;
  382. // Don't need a value here
  383. }
  384. else if ( csOperator.CompareNoCase(L"LIMITRELATIVE") == 0)
  385. {
  386. g_bLimitRelative = TRUE;
  387. // Don't need a value here
  388. }
  389. else if ( csOperator.CompareNoCase(L"ALLOWRELATIVE") == 0)
  390. {
  391. g_bLimitRelative = FALSE;
  392. // Don't need a value here
  393. }
  394. }
  395. }
  396. }
  397. CSTRING_CATCH
  398. {
  399. // Do nothing
  400. }
  401. Exit:
  402. //
  403. // Dump results of command line parse
  404. //
  405. DPFN( eDbgLevelInfo, "===================================\n");
  406. DPFN( eDbgLevelInfo, " Limit FindFile \n");
  407. DPFN( eDbgLevelInfo, "===================================\n");
  408. if ( g_bLimitDepth )
  409. {
  410. DPFN( eDbgLevelInfo, " Depth = %d\n", g_dwDepthLimit);
  411. }
  412. if ( g_bLimitBranch )
  413. {
  414. DPFN( eDbgLevelInfo, " Branch = %d\n", g_dwBranchLimit);
  415. }
  416. if ( g_bLimitFiles )
  417. {
  418. DPFN( eDbgLevelInfo, " Files = %d\n", g_dwFileLimit);
  419. }
  420. if ( g_bLimitRelative )
  421. {
  422. DPFN( eDbgLevelInfo, " Limiting Relative Paths.\n");
  423. }
  424. else
  425. {
  426. DPFN( eDbgLevelInfo, " Not Limiting Relative Paths.\n");
  427. }
  428. if ( g_bUseShortNames )
  429. {
  430. DPFN( eDbgLevelInfo, " Using short file names.\n");
  431. }
  432. DPFN( eDbgLevelInfo, "-----------------------------------\n");
  433. }
  434. BOOL
  435. NOTIFY_FUNCTION(
  436. DWORD fdwReason)
  437. {
  438. if (fdwReason == DLL_PROCESS_ATTACH)
  439. {
  440. ParseCommandLine(COMMAND_LINE);
  441. return InitializeCriticalSectionAndSpinCount(&g_MakeThreadSafe,0x80000000);
  442. }
  443. return TRUE;
  444. }
  445. /*++
  446. Register hooked functions
  447. --*/
  448. HOOK_BEGIN
  449. APIHOOK_ENTRY(KERNEL32.DLL, FindFirstFileA)
  450. APIHOOK_ENTRY(KERNEL32.DLL, FindNextFileA)
  451. APIHOOK_ENTRY(KERNEL32.DLL, FindClose)
  452. CALL_NOTIFY_FUNCTION
  453. HOOK_END
  454. IMPLEMENT_SHIM_END