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.

2943 lines
79 KiB

  1. /**********************************************************************/
  2. /** Microsoft Windows NT **/
  3. /** Copyright(c) Microsoft Corp., 1994 **/
  4. /**********************************************************************/
  5. /*
  6. cgi.cxx
  7. This module contains the gateway interface code for an HTTP request
  8. FILE HISTORY:
  9. Johnl 22-Sep-1994 Created
  10. MuraliK 22-Jan-1996 Use UNC Impersonation/Revert
  11. */
  12. #include "w3p.hxx"
  13. #include "issched.hxx"
  14. //
  15. // This is the exit code given to processes that we terminate
  16. //
  17. #define CGI_PREMATURE_DEATH_CODE 0xf1256323
  18. //
  19. // Beyond around this number, CreateProcessXXX return errors
  20. //
  21. #define CGI_COMMAND_LINE_MAXCB 30000
  22. #define ISUNC(a) ((a)[0]=='\\' && (a)[1]=='\\')
  23. //
  24. // The default minimum number of CGI threads we'll allow to
  25. // run at once. This is for compatibility with IIS 3.0 and
  26. // is based on the default number of ATQ threads in 3.0 (4.0
  27. // defaults to four). Note this is only effective if
  28. // UsePoolThreadForCGI is TRUE (default).
  29. //
  30. #define IIS3_MIN_CGI 10
  31. //
  32. // Prototypes
  33. //
  34. BOOL
  35. IsCmdExe(
  36. const CHAR * pchPath
  37. );
  38. //
  39. // Private globals.
  40. //
  41. BOOL fCGIInitialized = FALSE;
  42. LONG g_cMinCGIs = 0;
  43. //
  44. // Controls whether special command characters are allowed in cmd.exe
  45. // requests
  46. //
  47. BOOL fAllowSpecialCharsInShell = FALSE;
  48. typedef struct CgiEnvTableEntry_ {
  49. TCHAR* m_pszName;
  50. BOOL m_fIsProcessEnv;
  51. UINT m_cchNameLen;
  52. UINT m_cchToCopy; // will be non zero for var to copy from
  53. // process environment. In this case m_pszName
  54. // points to the environment entry to copy
  55. // ( name + '=' + value + '\0' )
  56. // otherwise this entry is to be accessed
  57. // using GetInfo()
  58. } CgiEnvTableEntry;
  59. //
  60. // Environment variable block used for CGI
  61. //
  62. // best if in alphabetical order ( the env list is easier to read )
  63. // but not mandatory.
  64. // Note that the "" ( accessed as HTTP_ALL ) will be expanded to a list
  65. // of unsorted entries, but this list as a whole will be considered to be
  66. // named "HTTP_ALL" for sorting order.
  67. //
  68. CgiEnvTableEntry CGIEnvTable[] =
  69. {
  70. {TEXT("AUTH_TYPE"),FALSE},
  71. {TEXT("AUTH_PASSWORD"),FALSE},
  72. {TEXT("AUTH_USER"),FALSE},
  73. {TEXT("ComSpec"),TRUE},
  74. {TEXT("CERT_COOKIE"), FALSE},
  75. {TEXT("CERT_FLAGS"), FALSE},
  76. {TEXT("CERT_ISSUER"), FALSE},
  77. {TEXT("CERT_SERIALNUMBER"), FALSE},
  78. {TEXT("CERT_SUBJECT"), FALSE},
  79. {TEXT("CONTENT_LENGTH"),FALSE},
  80. {TEXT("CONTENT_TYPE"),FALSE},
  81. {TEXT("GATEWAY_INTERFACE"),FALSE},
  82. {TEXT(""),FALSE}, // Means insert all HTTP_ headers here
  83. {TEXT("HTTPS"),FALSE},
  84. {TEXT("HTTPS_KEYSIZE"),FALSE},
  85. {TEXT("HTTPS_SECRETKEYSIZE"),FALSE},
  86. {TEXT("HTTPS_SERVER_ISSUER"),FALSE},
  87. {TEXT("HTTPS_SERVER_SUBJECT"),FALSE},
  88. {TEXT("INSTANCE_ID"),FALSE},
  89. {TEXT("LOCAL_ADDR"),FALSE},
  90. {TEXT("LOGON_USER"),FALSE},
  91. {TEXT("PATH"),TRUE},
  92. {TEXT("PATH_INFO"),FALSE},
  93. {TEXT("PATH_TRANSLATED"),FALSE},
  94. {TEXT("QUERY_STRING"),FALSE},
  95. {TEXT("REMOTE_ADDR"),FALSE},
  96. {TEXT("REMOTE_HOST"),FALSE},
  97. {TEXT("REMOTE_USER"),FALSE},
  98. {TEXT("REQUEST_METHOD"),FALSE},
  99. {TEXT("SCRIPT_NAME"),FALSE},
  100. {TEXT("SERVER_NAME"),FALSE},
  101. {TEXT("SERVER_PORT"),FALSE},
  102. {TEXT("SERVER_PORT_SECURE"),FALSE},
  103. {TEXT("SERVER_PROTOCOL"),FALSE},
  104. {TEXT("SERVER_SOFTWARE"),FALSE},
  105. {TEXT("SystemRoot"),TRUE},
  106. {TEXT("UNMAPPED_REMOTE_USER"),FALSE},
  107. {TEXT("windir"),TRUE},
  108. {NULL,FALSE}
  109. };
  110. BOOL fForwardServerEnvironmentBlock = TRUE;
  111. //
  112. // Store environment block for IIS process
  113. //
  114. LPSTR g_pszIisEnv = NULL;
  115. CgiEnvTableEntry *g_pEnvEntries = NULL;
  116. VOID
  117. WINAPI
  118. CGITerminateProcess(
  119. PVOID pContext
  120. );
  121. extern "C" int __cdecl
  122. QsortEnvCmp(
  123. const void *pA,
  124. const void *pB )
  125. /*++
  126. Routine Description:
  127. Compare CgiEnvTableEntry using their name entry
  128. Arguments:
  129. pA - pointer to 1st entry
  130. pB - pointer to 2nd entry
  131. Returns:
  132. -1 if 1st entry comes first in sort order,
  133. 0 if identical
  134. 1 if 2nd entry comes first
  135. --*/
  136. {
  137. LPSTR p1 = ((CgiEnvTableEntry*)pA)->m_pszName;
  138. LPSTR p2 = ((CgiEnvTableEntry*)pB)->m_pszName;
  139. if ( ! p1[0] )
  140. {
  141. p1 = "HTTP_ALL";
  142. }
  143. if ( ! p2[0] )
  144. {
  145. p2 = "HTTP_ALL";
  146. }
  147. return _stricmp( p1, p2 );
  148. }
  149. /*******************************************************************
  150. NAME: CGI_INFO
  151. SYNOPSIS: Simple storage class passed to thread
  152. HISTORY:
  153. Johnl 22-Sep-1994 Created
  154. ********************************************************************/
  155. class CGI_INFO
  156. {
  157. public:
  158. CGI_INFO( HTTP_REQUEST * pRequest )
  159. : _pRequest ( pRequest ),
  160. _cbData ( 0 ),
  161. _hStdOut ( NULL ),
  162. _hStdIn ( NULL ),
  163. _hProcess ( NULL ),
  164. _dwSchedCookie ( 0 ),
  165. _fServerPoolThread( FALSE ),
  166. _pExec ( NULL )
  167. {
  168. }
  169. ~CGI_INFO( VOID )
  170. {
  171. if ( _hStdOut )
  172. {
  173. if ( !::CloseHandle( _hStdOut ))
  174. {
  175. DBGPRINTF((DBG_CONTEXT,
  176. "[~CGI_INFO] CloseHandle failed on StdOut, %d\n",
  177. GetLastError()));
  178. }
  179. }
  180. if ( _hStdIn )
  181. {
  182. if ( !::CloseHandle( _hStdIn ))
  183. {
  184. DBGPRINTF((DBG_CONTEXT,
  185. "[~CGI_INFO] CloseHandle failed on StdIn, %d\n",
  186. GetLastError()));
  187. }
  188. }
  189. if ( _hProcess )
  190. {
  191. if ( !::CloseHandle( _hProcess ))
  192. {
  193. DBGPRINTF((DBG_CONTEXT,
  194. "[~CGI_INFO] CloseHandle failed on Process, %d\n",
  195. GetLastError()));
  196. }
  197. }
  198. DWORD dwCookie = (DWORD) InterlockedExchange( (LPLONG) &_dwSchedCookie,
  199. 0 );
  200. if ( dwCookie != 0 )
  201. {
  202. RemoveWorkItem( dwCookie );
  203. }
  204. }
  205. HTTP_REQUEST * _pRequest;
  206. DWORD _dwSchedCookie; // Scheduled callback cookie
  207. BOOL _fServerPoolThread; // Are we running in a server pool
  208. // thread?
  209. //
  210. // Child process
  211. //
  212. HANDLE _hProcess;
  213. //
  214. // Parent's input and output handles and child's process handle
  215. //
  216. HANDLE _hStdOut;
  217. HANDLE _hStdIn;
  218. //
  219. // Handles input from CGI (headers and additional data)
  220. //
  221. BUFFER _Buff;
  222. UINT _cbData;
  223. LIST_ENTRY _CgiListEntry;
  224. static LIST_ENTRY _CgiListHead;
  225. static CRITICAL_SECTION _csCgiList;
  226. //
  227. // Execution Descriptor block
  228. //
  229. EXEC_DESCRIPTOR * _pExec;
  230. };
  231. //
  232. // Globals
  233. //
  234. CRITICAL_SECTION CGI_INFO::_csCgiList;
  235. LIST_ENTRY CGI_INFO::_CgiListHead;
  236. //
  237. // Private prototypes.
  238. //
  239. BOOL ProcessCGIInput( CGI_INFO * pCGIInfo );
  240. BOOL SetupChildEnv( EXEC_DESCRIPTOR * pExec,
  241. BUFFER * pBuff );
  242. BOOL SetupChildPipes( STARTUPINFO * pstartupinfo,
  243. HANDLE * phParentIn,
  244. HANDLE * phParentOut );
  245. BOOL SetupCmdLine( STR * pstrCmdLine,
  246. const STR & strParams );
  247. DWORD CGIThread( PVOID Param );
  248. BOOL ProcessCGIInput( CGI_INFO * pCGIInfo,
  249. BYTE * buff,
  250. DWORD cbRead,
  251. BOOL * pfReadHeaders,
  252. BOOL * pfDone,
  253. BOOL * pfSkipDisconnect,
  254. DWORD * pdwHttpStatus );
  255. /*******************************************************************/
  256. APIERR
  257. InitializeCGI(
  258. VOID
  259. )
  260. /*++
  261. Routine Description:
  262. Initialize CGI
  263. Arguments:
  264. None
  265. Return Value:
  266. TRUE if successful, FALSE on error
  267. --*/
  268. {
  269. LPVOID pvEnv;
  270. UINT cchStr;
  271. UINT cchIisEnv;
  272. UINT cEnv;
  273. INT chScanEndOfName;
  274. CgiEnvTableEntry * pCgiEnv;
  275. HKEY hkeyParam;
  276. INITIALIZE_CRITICAL_SECTION( &CGI_INFO::_csCgiList );
  277. InitializeListHead( &CGI_INFO::_CgiListHead );
  278. if ( RegOpenKeyEx( HKEY_LOCAL_MACHINE,
  279. W3_PARAMETERS_KEY,
  280. 0,
  281. KEY_READ,
  282. &hkeyParam ) == NO_ERROR )
  283. {
  284. fAllowSpecialCharsInShell = !!ReadRegistryDword( hkeyParam,
  285. "AllowSpecialCharsInShell",
  286. FALSE );
  287. fForwardServerEnvironmentBlock = !!ReadRegistryDword(
  288. hkeyParam,
  289. "ForwardServerEnvironmentBlock",
  290. TRUE );
  291. RegCloseKey( hkeyParam );
  292. }
  293. if ( fForwardServerEnvironmentBlock
  294. && (pvEnv = GetEnvironmentStrings()) )
  295. {
  296. //
  297. // Compute length of environment block and # of variables
  298. // ( excluding block delimiter )
  299. //
  300. cchIisEnv = 0;
  301. cEnv = 0;
  302. while ( cchStr = strlen( ((PSTR)pvEnv) + cchIisEnv ) )
  303. {
  304. cchIisEnv += cchStr + 1;
  305. ++cEnv;
  306. }
  307. ++cchIisEnv;
  308. //
  309. // store it
  310. //
  311. if ( (g_pszIisEnv = (LPSTR)LocalAlloc(
  312. LMEM_FIXED, cchIisEnv * sizeof(TCHAR))) == NULL )
  313. {
  314. return ERROR_NOT_ENOUGH_MEMORY;
  315. }
  316. CopyMemory(
  317. g_pszIisEnv,
  318. pvEnv,
  319. cchIisEnv * sizeof(TCHAR) );
  320. FreeEnvironmentStrings( (LPTSTR)pvEnv );
  321. pvEnv = (PVOID)g_pszIisEnv;
  322. if ( g_pEnvEntries = new CgiEnvTableEntry [
  323. cEnv + sizeof(CGIEnvTable)/sizeof(CgiEnvTableEntry) ] )
  324. {
  325. cchIisEnv = 0;
  326. cEnv = 0;
  327. //
  328. // add process environment to table
  329. //
  330. while ( cchStr = strlen( ((PSTR)pvEnv) + cchIisEnv ) )
  331. {
  332. g_pEnvEntries[ cEnv ].m_pszName = ((PSTR)pvEnv) + cchIisEnv;
  333. g_pEnvEntries[ cEnv ].m_fIsProcessEnv = TRUE;
  334. // compute length of name : up to '=' char
  335. for ( g_pEnvEntries[ cEnv ].m_cchNameLen = 0 ;
  336. ( chScanEndOfName = g_pEnvEntries[ cEnv ].m_pszName
  337. [ g_pEnvEntries[ cEnv ].m_cchNameLen ] )
  338. && chScanEndOfName != '=' ; )
  339. {
  340. ++g_pEnvEntries[ cEnv ].m_cchNameLen;
  341. }
  342. g_pEnvEntries[ cEnv ].m_cchToCopy = cchStr + 1;
  343. cchIisEnv += cchStr + 1;
  344. ++cEnv;
  345. }
  346. //
  347. // add CGI environment variables to table
  348. //
  349. for ( pCgiEnv = CGIEnvTable ; pCgiEnv->m_pszName ; ++pCgiEnv )
  350. {
  351. if ( !pCgiEnv->m_fIsProcessEnv )
  352. {
  353. memcpy( g_pEnvEntries + cEnv, pCgiEnv,
  354. sizeof(CgiEnvTableEntry) );
  355. g_pEnvEntries[ cEnv ].m_cchNameLen
  356. = strlen( pCgiEnv->m_pszName );
  357. g_pEnvEntries[ cEnv ].m_cchToCopy = 0;
  358. ++cEnv;
  359. }
  360. }
  361. //
  362. // add delimiter entry
  363. //
  364. g_pEnvEntries[ cEnv ].m_pszName = NULL;
  365. qsort( g_pEnvEntries,
  366. cEnv,
  367. sizeof(CgiEnvTableEntry),
  368. QsortEnvCmp );
  369. }
  370. else
  371. {
  372. return ERROR_NOT_ENOUGH_MEMORY;
  373. }
  374. }
  375. else
  376. {
  377. g_pEnvEntries = CGIEnvTable;
  378. }
  379. fCGIInitialized = TRUE;
  380. return NO_ERROR;
  381. } // InitializeCGI
  382. VOID
  383. TerminateCGI(
  384. VOID
  385. )
  386. /*++
  387. Routine Description:
  388. Terminate CGI
  389. Arguments:
  390. None
  391. Return Value:
  392. Nothing
  393. --*/
  394. {
  395. if ( fCGIInitialized )
  396. {
  397. if (g_pszIisEnv != NULL )
  398. {
  399. LocalFree( g_pszIisEnv );
  400. g_pszIisEnv = NULL;
  401. }
  402. if ( g_pEnvEntries && (g_pEnvEntries != CGIEnvTable) )
  403. {
  404. delete [] g_pEnvEntries;
  405. }
  406. DeleteCriticalSection( &CGI_INFO::_csCgiList );
  407. }
  408. } // TerminateCGI
  409. VOID
  410. KillCGIProcess(
  411. VOID
  412. )
  413. /*++
  414. Routine Description:
  415. Kill all CGI process
  416. Arguments:
  417. None
  418. Return Value:
  419. Nothing
  420. --*/
  421. {
  422. CGI_INFO* pCgi;
  423. if ( fCGIInitialized )
  424. {
  425. //
  426. // Kill all outstanding process
  427. //
  428. BOOL bListEmpty = TRUE;
  429. EnterCriticalSection( &CGI_INFO::_csCgiList );
  430. LIST_ENTRY* pEntry;
  431. for ( pEntry = CGI_INFO::_CgiListHead.Flink;
  432. pEntry != &CGI_INFO::_CgiListHead ;
  433. pEntry = pEntry->Flink )
  434. {
  435. pCgi = CONTAINING_RECORD( pEntry,
  436. CGI_INFO,
  437. CGI_INFO::_CgiListEntry );
  438. bListEmpty = FALSE;
  439. if ( pCgi->_hProcess )
  440. {
  441. CGITerminateProcess( pCgi->_hProcess );
  442. }
  443. }
  444. LeaveCriticalSection( &CGI_INFO::_csCgiList );
  445. for (int i = 0; !bListEmpty && (i < 5); i++)
  446. {
  447. //
  448. // Wait for all threads to complete to avoid
  449. // shutdown timeout problem.
  450. //
  451. Sleep(1000);
  452. EnterCriticalSection( &CGI_INFO::_csCgiList );
  453. if (IsListEmpty(&CGI_INFO::_CgiListHead))
  454. {
  455. bListEmpty = TRUE;
  456. }
  457. LeaveCriticalSection( &CGI_INFO::_csCgiList );
  458. }
  459. }
  460. } // KillCGIProcess
  461. VOID
  462. KillCGIInstanceProcs(
  463. W3_SERVER_INSTANCE *pw3siInstance
  464. )
  465. /*++
  466. Routine Description:
  467. Kill all CGI process
  468. Arguments:
  469. None
  470. Return Value:
  471. Nothing
  472. --*/
  473. {
  474. CGI_INFO* pCgi;
  475. if ( fCGIInitialized )
  476. {
  477. //
  478. // Kill all outstanding process
  479. //
  480. EnterCriticalSection( &CGI_INFO::_csCgiList );
  481. LIST_ENTRY* pEntry;
  482. for ( pEntry = CGI_INFO::_CgiListHead.Flink;
  483. pEntry != &CGI_INFO::_CgiListHead ;
  484. pEntry = pEntry->Flink )
  485. {
  486. pCgi = CONTAINING_RECORD( pEntry,
  487. CGI_INFO,
  488. CGI_INFO::_CgiListEntry );
  489. DBG_ASSERT(pCgi->_pExec);
  490. DBG_ASSERT(pCgi->_pExec->_pRequest);
  491. if ( (pw3siInstance == pCgi->_pExec->_pRequest->QueryW3Instance()) &&
  492. pCgi->_hProcess )
  493. {
  494. CGITerminateProcess( pCgi->_hProcess );
  495. }
  496. }
  497. LeaveCriticalSection( &CGI_INFO::_csCgiList );
  498. }
  499. } // KillCGIProcess
  500. BOOL
  501. HTTP_REQUEST::ProcessGateway(
  502. EXEC_DESCRIPTOR * pExec,
  503. BOOL * pfHandled,
  504. BOOL * pfFinished,
  505. BOOL fTrusted
  506. )
  507. /*++
  508. Routine Description:
  509. Prepares for either a CGI call
  510. If the .exe or .dll isn't found, then *pfHandled will be set
  511. to FALSE and the request will be processed again with the
  512. assumption we just happenned to find a directory with a
  513. trailing .exe or .dll.
  514. Arguments:
  515. pExec - Execution descriptor block
  516. pfHandled - Indicates if the request was a gateway request
  517. pfFinished - Indicates no further processing is required
  518. fTrusted - Can this app be trusted to process things on a read only
  519. vroot
  520. Return Value:
  521. TRUE if successful, FALSE on error
  522. --*/
  523. {
  524. BOOL fRet = TRUE;
  525. TCHAR * pch;
  526. CHAR tmpStr[MAX_PATH];
  527. STR strWorkingDir(tmpStr,MAX_PATH);
  528. DWORD dwMask;
  529. INT cStr;
  530. BOOL fChild = pExec->IsChild();
  531. DWORD dwAttr;
  532. DWORD err;
  533. *pfHandled = FALSE;
  534. *pfFinished = FALSE; // ProcessBGI may reset this
  535. if ( !VrootAccessCheck( pExec->_pMetaData, FILE_GENERIC_EXECUTE ) )
  536. {
  537. DBGPRINTF(( DBG_CONTEXT, "ACESS_DENIED: User \"%s\" doesn't have EXECUTE permissions for CGI %s\n",
  538. _strUserName.QueryStr(), pExec->_pstrPhysicalPath->QueryStr()));
  539. SetDeniedFlags( SF_DENIED_RESOURCE );
  540. return FALSE;
  541. }
  542. if ( !pExec->_pstrPathInfo->Unescape() )
  543. {
  544. return FALSE;
  545. }
  546. //
  547. // If this isn't a trusted app, make sure the user has execute on
  548. // this virtual root
  549. //
  550. if ( !(fTrusted && IS_ACCESS_ALLOWED2(pExec, SCRIPT))
  551. &&
  552. !IS_ACCESS_ALLOWED2(pExec, EXECUTE) )
  553. {
  554. *pfHandled = TRUE;
  555. if ( fChild )
  556. {
  557. SetLastError( ERROR_INVALID_FLAGS );
  558. return FALSE;
  559. }
  560. else
  561. {
  562. DBGPRINTF(( DBG_CONTEXT, "ACCESS_DENIED: No EXECUTE permissions on Vroot \"%s\" for CGI %s\n",
  563. pExec->_pMetaData->QueryVrPath()->QueryStr(), pExec->_pstrPhysicalPath->QueryStr()));
  564. Disconnect( HT_FORBIDDEN, IDS_EXECUTE_ACCESS_DENIED, FALSE, pfFinished );
  565. return TRUE;
  566. }
  567. }
  568. //
  569. // We need to calculate the number of characters that comprise the
  570. // working directory for the script path (which is the web root)
  571. //
  572. if ( !LookupVirtualRoot( pExec->_pstrPhysicalPath,
  573. pExec->_pstrURL->QueryStr(),
  574. pExec->_pstrURL->QueryCCH(),
  575. NULL,
  576. NULL,
  577. &dwMask ))
  578. {
  579. return FALSE;
  580. }
  581. LPSTR pszWorkingDir = strrchr( pExec->_pstrPhysicalPath->QueryStr(), '\\' );
  582. if ( pszWorkingDir == NULL )
  583. {
  584. return FALSE;
  585. }
  586. if ( !strWorkingDir.Copy( pExec->_pstrPhysicalPath->QueryStr(),
  587. DIFF( pszWorkingDir -
  588. pExec->_pstrPhysicalPath->QueryStr() ) + 1))
  589. {
  590. return FALSE;
  591. }
  592. //
  593. // Keep-alive not supported for CGI
  594. //
  595. if ( !fChild )
  596. {
  597. SetKeepConn( FALSE );
  598. }
  599. //
  600. // If this was a mapped script extension expand any parameters
  601. //
  602. if ( !pExec->_pstrGatewayImage->IsEmpty() )
  603. {
  604. CHAR tmpStr[MAX_PATH];
  605. CHAR tmpStr2[1024];
  606. STR strDecodedParams(tmpStr,MAX_PATH);
  607. STR strCmdLine(tmpStr2,1024);
  608. if ( !SetupCmdLine( &strDecodedParams,
  609. *(pExec->_pstrURLParams) ) ||
  610. !strCmdLine.Resize( pExec->_pstrGatewayImage->QueryCB() +
  611. pExec->_pstrPhysicalPath->QueryCB() +
  612. strDecodedParams.QueryCB()))
  613. {
  614. return FALSE;
  615. }
  616. if ( IsCmdExe( pExec->_pstrGatewayImage->QueryStr() ))
  617. {
  618. //
  619. // Make sure the path to the file exists if we're running
  620. // the command interpreter
  621. //
  622. if ( !pExec->ImpersonateUser() )
  623. {
  624. return FALSE;
  625. }
  626. dwAttr = GetFileAttributes( pExec->_pstrPhysicalPath->QueryStr() );
  627. err = GetLastError();
  628. pExec->RevertUser();
  629. if ( dwAttr == 0xffffffff )
  630. {
  631. DBGPRINTF(( DBG_CONTEXT,
  632. "[ProcessGateway] Error %d openning batch file %s\n",
  633. err,
  634. pExec->_pstrPhysicalPath->QueryStr() ) );
  635. if ( !fChild &&
  636. ( (err == ERROR_FILE_NOT_FOUND) ||
  637. (err == ERROR_PATH_NOT_FOUND) ||
  638. (err == ERROR_INVALID_NAME) ) )
  639. {
  640. SetState( HTR_DONE, HT_NOT_FOUND, GetLastError() );
  641. Disconnect( HT_NOT_FOUND, NO_ERROR, FALSE, pfFinished );
  642. *pfHandled = TRUE;
  643. return TRUE;
  644. }
  645. return FALSE;
  646. }
  647. }
  648. cStr = wsprintf( strCmdLine.QueryStr(),
  649. pExec->_pstrGatewayImage->QueryStr(),
  650. pExec->_pstrPhysicalPath->QueryStr(),
  651. strDecodedParams.QueryStr() );
  652. strCmdLine.SetLen( cStr );
  653. fRet = ProcessCGI( pExec,
  654. NULL,
  655. &strWorkingDir,
  656. pfHandled,
  657. pfFinished,
  658. &strCmdLine );
  659. }
  660. else
  661. {
  662. fRet = ProcessCGI( pExec,
  663. pExec->_pstrPhysicalPath,
  664. &strWorkingDir,
  665. pfHandled,
  666. pfFinished );
  667. }
  668. return fRet;
  669. }
  670. /********************************************************************/
  671. BOOL
  672. HTTP_REQUEST::ProcessCGI(
  673. EXEC_DESCRIPTOR * pExec,
  674. const STR * pstrPath,
  675. const STR * pstrWorkingDir,
  676. BOOL * pfHandled,
  677. BOOL * pfFinished,
  678. STR * pstrCmdLine
  679. )
  680. /*++
  681. Routine Description:
  682. Processes a CGI client request
  683. Arguments:
  684. pExec - Execution descriptor block
  685. pstrPath - Fully qualified path to executable (or NULL if the module
  686. is contained in pstrCmdLine)
  687. strWorkingDir - Working directory for spawned process
  688. (generally the web root)
  689. pfHandled - Set to TRUE if no further processing is needed
  690. pfFinished - Set to TRUE if no further I/O operation for this request
  691. pstrCmdLine - Optional command line to use instead of the default
  692. Return Value:
  693. TRUE if successful, FALSE on error
  694. --*/
  695. {
  696. STARTUPINFO startupinfo;
  697. PROCESS_INFORMATION processinfo;
  698. UCHAR tmpBuffer[1500];
  699. BUFFER buffEnv(tmpBuffer,1500);
  700. BOOL fRet = FALSE;
  701. CGI_INFO * pCGIInfo = NULL;
  702. DWORD dwThreadId;
  703. HANDLE hThread;
  704. CHAR tmpStr[MAX_PATH];
  705. STR strCmdLine(tmpStr,MAX_PATH);
  706. DWORD dwFlags = DETACHED_PROCESS;
  707. BOOL fChild = pExec->IsChild();
  708. BOOL fIsCmdExe;
  709. *pfHandled = TRUE;
  710. //
  711. // Note we move the module name to the command line so argv
  712. // comes out correctly
  713. //
  714. if ( pstrPath != NULL )
  715. {
  716. if ( !strCmdLine.Copy( "\"", sizeof("\"")-1 ) ||
  717. !strCmdLine.Append( pstrPath->QueryStr() ) ||
  718. !strCmdLine.Append( "\" ", sizeof("\" ")-1 ))
  719. {
  720. return FALSE;
  721. }
  722. }
  723. if ( !SetupChildEnv( pExec,
  724. &buffEnv ) ||
  725. !SetupCmdLine( &strCmdLine,
  726. *(pExec->_pstrURLParams) ))
  727. {
  728. return FALSE;
  729. }
  730. pstrPath = NULL;
  731. //
  732. // If a command line wasn't supplied, then use the default command line
  733. //
  734. if ( !pstrCmdLine )
  735. pstrCmdLine = &strCmdLine;
  736. //
  737. // Check to see if we're spawning cmd.exe, if so, refuse the request if
  738. // there are any special shell characters. Note we do the check here
  739. // so that the command line has been fully unescaped
  740. //
  741. if ( !fAllowSpecialCharsInShell ||
  742. ISUNC(pstrCmdLine->QueryStr()) )
  743. {
  744. if ( fIsCmdExe = IsCmdExe( pstrCmdLine->QueryStr() ) )
  745. {
  746. //
  747. // if invoking cmd.exe for a UNC script then don't set the working directory.
  748. // otherwise cmd.exe will complains about working dir on UNC being not
  749. // supported, which will destroy HTTP headers.
  750. //
  751. if ( ISUNC(pstrCmdLine->QueryStr()) )
  752. {
  753. pstrWorkingDir = NULL;
  754. }
  755. }
  756. }
  757. if ( !fAllowSpecialCharsInShell )
  758. {
  759. DWORD i;
  760. if ( fIsCmdExe )
  761. {
  762. //
  763. // We'll either match one of the characters or the '\0'
  764. //
  765. i = strcspn( pstrCmdLine->QueryStr(), "&|(,;%<>" );
  766. if ( pstrCmdLine->QueryStr()[i] )
  767. {
  768. DBGPRINTF(( DBG_CONTEXT,
  769. "[ProcessCGI] Refusing request for command shell due "
  770. " to special characters\n" ));
  771. SetLastError( ERROR_INVALID_PARAMETER );
  772. return FALSE;
  773. }
  774. }
  775. }
  776. //
  777. // See if CPU Throttling has this CPU stopped.
  778. //
  779. if (_pW3Instance->AreProcsCPUStopped() && pExec->_pMetaData->QueryJobCGIEnabled()) {
  780. SetLastError(ERROR_NOT_ENOUGH_QUOTA);
  781. fRet = FALSE;
  782. goto Exit;
  783. }
  784. //
  785. // Setup the pipes information
  786. //
  787. pCGIInfo = new CGI_INFO( this );
  788. if ( !pCGIInfo )
  789. {
  790. SetLastError( ERROR_NOT_ENOUGH_MEMORY );
  791. goto Exit;
  792. }
  793. pCGIInfo->_pExec = pExec;
  794. ZeroMemory( &startupinfo, sizeof(startupinfo) );
  795. startupinfo.cb = sizeof(startupinfo);
  796. EnterCriticalSection( &CGI_INFO::_csCgiList );
  797. InsertHeadList( &CGI_INFO::_CgiListHead,
  798. &pCGIInfo->_CgiListEntry );
  799. LeaveCriticalSection( &CGI_INFO::_csCgiList );
  800. //
  801. // We specify an unnamed desktop so a new windowstation will be created
  802. // in the context of the calling user
  803. //
  804. startupinfo.lpDesktop = "";
  805. if ( !SetupChildPipes( &startupinfo,
  806. &pCGIInfo->_hStdIn,
  807. &pCGIInfo->_hStdOut ) )
  808. {
  809. IF_DEBUG( CGI )
  810. {
  811. DBGPRINTF((DBG_CONTEXT,
  812. "[ProcessCGI] Failed to create child pipes, error %d",
  813. GetLastError() ));
  814. }
  815. goto Exit;
  816. }
  817. if ( pExec->_pMetaData->QueryCreateProcessNewConsole() )
  818. {
  819. dwFlags = CREATE_NEW_CONSOLE;
  820. }
  821. //////////////////////////////////////////////////////////////
  822. //
  823. // Allow control over whether CreateProcess is called instead of
  824. // CreateProcessAsUser. Running the services as a windows app
  825. // works around the security problem spawning executables
  826. //
  827. // Note this code block is outside the impersonation block
  828. //
  829. //////////////////////////////////////////////////////////////
  830. //////////////////////////////////////////////////////////////
  831. //
  832. // Spawn the process and close the handles since we don't need them
  833. //
  834. IF_DEBUG( CGI )
  835. {
  836. DBGPRINTF(( DBG_CONTEXT,
  837. "[ProcessCGI] Creating process, path = %s, cmdline = %s\n",
  838. (pstrPath ? pstrPath->QueryStr() : "NULL"),
  839. pstrCmdLine->QueryStr() ));
  840. }
  841. if (pExec->_pMetaData->QueryJobCGIEnabled())
  842. {
  843. dwFlags |= CREATE_SUSPENDED;
  844. }
  845. if ( !pExec->_pMetaData->QueryCreateProcessAsUser() ||
  846. QuerySingleAccessToken() )
  847. {
  848. if ( !pExec->ImpersonateUser() )
  849. {
  850. goto Exit;
  851. }
  852. fRet = CreateProcess( (pstrPath ? pstrPath->QueryStr() : NULL),
  853. pstrCmdLine->QueryStr(),
  854. NULL, // Process security
  855. NULL, // Thread security
  856. TRUE, // Inherit handles
  857. dwFlags,
  858. buffEnv.QueryPtr(),
  859. pstrWorkingDir ? pstrWorkingDir->QueryStr() :
  860. NULL,
  861. &startupinfo,
  862. &processinfo );
  863. pExec->RevertUser();
  864. }
  865. else
  866. {
  867. HANDLE hDelete = NULL;
  868. HANDLE hToken = pExec->QueryPrimaryHandle( &hDelete );
  869. if ( !ImpersonateLoggedOnUser( hToken ) )
  870. {
  871. DBGPRINTF(( DBG_CONTEXT,
  872. "[ProcessCGI] ImpersonateLoggedOnUser failed, error %lx\n",
  873. GetLastError() ));
  874. if ( hDelete )
  875. {
  876. TCP_REQUIRE( CloseHandle( hDelete ));
  877. }
  878. goto Exit;
  879. }
  880. fRet = CreateProcessAsUser( hToken,
  881. (pstrPath ? pstrPath->QueryStr() : NULL),
  882. pstrCmdLine->QueryStr(),
  883. NULL, // Process security
  884. NULL, // Thread security
  885. TRUE, // Inherit handles
  886. dwFlags,
  887. buffEnv.QueryPtr(),
  888. pstrWorkingDir ? pstrWorkingDir->QueryStr() :
  889. NULL,
  890. &startupinfo,
  891. &processinfo );
  892. TCP_REQUIRE( RevertToSelf() );
  893. if ( hDelete )
  894. {
  895. TCP_REQUIRE( CloseHandle( hDelete ) );
  896. }
  897. }
  898. if ((fRet) && (pExec->_pMetaData->QueryJobCGIEnabled()))
  899. {
  900. DBG_ASSERT(_pW3Instance != NULL);
  901. DBG_REQUIRE(_pW3Instance->AddProcessToJob(processinfo.hProcess, FALSE) == ERROR_SUCCESS);
  902. if (ResumeThread(processinfo.hThread) == 0xFFFFFFFF)
  903. {
  904. fRet = FALSE;
  905. TerminateProcess(processinfo.hThread,
  906. GetLastError());
  907. TCP_REQUIRE( CloseHandle( processinfo.hProcess ));
  908. }
  909. }
  910. TCP_REQUIRE( CloseHandle( startupinfo.hStdOutput ));
  911. TCP_REQUIRE( CloseHandle( startupinfo.hStdInput ));
  912. if ( !fRet )
  913. {
  914. DBGPRINTF((DBG_CONTEXT,
  915. "[ProcessCGI] Create process failed, error %d, exe = %s, cmd line = %s\n",
  916. GetLastError(),
  917. (pstrPath ? pstrPath->QueryStr() : "null"),
  918. (pstrCmdLine ? pstrCmdLine->QueryStr() : "null") ));
  919. goto Exit;
  920. }
  921. QueryW3StatsObj()->IncrTotalCGIRequests();
  922. TCP_REQUIRE( CloseHandle( processinfo.hThread ));
  923. DBG_ASSERT( startupinfo.hStdError == startupinfo.hStdOutput);
  924. //
  925. // Save the process handle in case we need to terminate it later on
  926. //
  927. pCGIInfo->_hProcess = processinfo.hProcess;
  928. //
  929. // Before we start the CGI thread, set our new state
  930. //
  931. SetState( HTR_CGI );
  932. if ( QueryW3Instance()->IsUsePoolThreadForCGI() )
  933. {
  934. BOOL fIncPoolThread = FALSE;
  935. //
  936. // To maintain IIS 3.0 compatible behavior, allow 10 CGI threads
  937. // on a single proc machine, since IIS 3.0 by default had 10 ATQ
  938. // threads per processor.
  939. // Note that on multi-proc machines, you will end up with more
  940. // than 10 threads (specifically, you get a boost in the max thread
  941. // count of IIS3_MIN_CGI - ATQ_REG_DEF_PER_PROCESSOR_ATQ_THREADS).
  942. //
  943. if ( InterlockedIncrement( &g_cMinCGIs ) <=
  944. ( IIS3_MIN_CGI - ATQ_REG_DEF_PER_PROCESSOR_ATQ_THREADS ) )
  945. {
  946. fIncPoolThread = TRUE;
  947. AtqSetInfo( AtqIncMaxPoolThreads, 0 );
  948. }
  949. //
  950. // Call the CGI processor directly.
  951. //
  952. pCGIInfo->_fServerPoolThread = TRUE;
  953. CGIThread( pCGIInfo );
  954. if ( fIncPoolThread )
  955. {
  956. AtqSetInfo( AtqDecMaxPoolThreads, 0 );
  957. }
  958. InterlockedDecrement( &g_cMinCGIs );
  959. }
  960. else
  961. {
  962. //
  963. // Create a thread to handle IO with the child process
  964. //
  965. // Child execs don't need referencing since they are executed
  966. // synchronously
  967. if ( !fChild )
  968. {
  969. Reference();
  970. }
  971. if ( !(hThread = CreateThread( NULL,
  972. 0,
  973. (LPTHREAD_START_ROUTINE) CGIThread,
  974. pCGIInfo,
  975. 0,
  976. &dwThreadId )))
  977. {
  978. if ( !fChild )
  979. {
  980. Dereference();
  981. }
  982. goto Exit;
  983. }
  984. //
  985. // If this is a child CGI, we must wait for completion.
  986. // Therefore, wait indefinitely on the CGIThread
  987. //
  988. if ( fChild )
  989. {
  990. TCP_REQUIRE( WaitForSingleObject( hThread, INFINITE ) );
  991. }
  992. //
  993. // We don't use the thread handle so free the resource
  994. //
  995. TCP_REQUIRE( CloseHandle( hThread ));
  996. }
  997. fRet = TRUE;
  998. Exit:
  999. if ( !fRet )
  1000. {
  1001. DWORD err = GetLastError();
  1002. if (pCGIInfo != NULL)
  1003. {
  1004. EnterCriticalSection( &CGI_INFO::_csCgiList );
  1005. RemoveEntryList( &pCGIInfo->_CgiListEntry );
  1006. LeaveCriticalSection( &CGI_INFO::_csCgiList );
  1007. delete pCGIInfo;
  1008. }
  1009. if ( !fChild )
  1010. {
  1011. if ( err == ERROR_ACCESS_DENIED )
  1012. {
  1013. SetDeniedFlags( SF_DENIED_RESOURCE );
  1014. }
  1015. else if ( err == ERROR_FILE_NOT_FOUND ||
  1016. err == ERROR_PATH_NOT_FOUND )
  1017. {
  1018. SetState( HTR_DONE, HT_NOT_FOUND, err );
  1019. Disconnect( HT_NOT_FOUND, NO_ERROR, FALSE, pfFinished );
  1020. fRet = TRUE;
  1021. }
  1022. else if ( err == ERROR_MORE_DATA ||
  1023. ( err == ERROR_INVALID_PARAMETER &&
  1024. pstrCmdLine->QueryCB() > CGI_COMMAND_LINE_MAXCB ) )
  1025. {
  1026. // These errors are returned by CreateProcess[AsUser] if
  1027. // the query string is too long
  1028. SetState( HTR_DONE, HT_URL_TOO_LONG, err );
  1029. Disconnect( HT_URL_TOO_LONG, NO_ERROR, FALSE, pfFinished );
  1030. fRet = TRUE;
  1031. }
  1032. else if (err == ERROR_NOT_ENOUGH_QUOTA) {
  1033. SetState( HTR_DONE, HT_SVC_UNAVAILABLE, ERROR_NOT_ENOUGH_QUOTA );
  1034. Disconnect( HT_SVC_UNAVAILABLE, IDS_SITE_RESOURCE_BLOCKED, FALSE, pfFinished );
  1035. fRet = TRUE;
  1036. }
  1037. }
  1038. }
  1039. return fRet;
  1040. }
  1041. /*******************************************************************
  1042. NAME: SetupChildPipes
  1043. SYNOPSIS: Creates/duplicates pipes for redirecting stdin and
  1044. stdout to a child process
  1045. ENTRY: pstartupinfo - pointer to startup info structure, receives
  1046. child stdin and stdout handles
  1047. phParentIn - Pipe to use for parent reading
  1048. phParenOut - Pipe to use for parent writing
  1049. RETURNS: TRUE if successful, FALSE on failure
  1050. HISTORY:
  1051. Johnl 22-Sep-1994 Created
  1052. ********************************************************************/
  1053. BOOL SetupChildPipes( STARTUPINFO * pstartupinfo,
  1054. HANDLE * phParentIn,
  1055. HANDLE * phParentOut )
  1056. {
  1057. SECURITY_ATTRIBUTES sa;
  1058. *phParentIn = NULL;
  1059. *phParentOut = NULL;
  1060. sa.nLength = sizeof(sa);
  1061. sa.lpSecurityDescriptor = NULL;
  1062. sa.bInheritHandle = TRUE;
  1063. pstartupinfo->dwFlags = STARTF_USESTDHANDLES;
  1064. //
  1065. // Create the pipes then mark them as not inheritted in the
  1066. // DuplicateHandle to prevent handle leaks
  1067. //
  1068. if ( !CreatePipe( phParentIn,
  1069. &pstartupinfo->hStdOutput,
  1070. &sa,
  1071. 0 ) ||
  1072. !DuplicateHandle( GetCurrentProcess(),
  1073. *phParentIn,
  1074. GetCurrentProcess(),
  1075. phParentIn,
  1076. 0,
  1077. FALSE,
  1078. DUPLICATE_SAME_ACCESS |
  1079. DUPLICATE_CLOSE_SOURCE) ||
  1080. !CreatePipe( &pstartupinfo->hStdInput,
  1081. phParentOut,
  1082. &sa,
  1083. 0 ) ||
  1084. !DuplicateHandle( GetCurrentProcess(),
  1085. *phParentOut,
  1086. GetCurrentProcess(),
  1087. phParentOut,
  1088. 0,
  1089. FALSE,
  1090. DUPLICATE_SAME_ACCESS |
  1091. DUPLICATE_CLOSE_SOURCE ))
  1092. {
  1093. goto ErrorExit;
  1094. }
  1095. //
  1096. // Stdout and Stderror will use the same pipe. If clients tend
  1097. // to close stderr, then we'll have to duplicate the handle
  1098. //
  1099. pstartupinfo->hStdError = pstartupinfo->hStdOutput;
  1100. IF_DEBUG ( CGI )
  1101. {
  1102. DBGPRINTF((DBG_CONTEXT,
  1103. "[SetupChildPipes] Parent In = %x, Parent Out = %x, Child In = %x, Child Out = %x\n",
  1104. *phParentIn,
  1105. *phParentOut,
  1106. pstartupinfo->hStdInput,
  1107. pstartupinfo->hStdOutput));
  1108. }
  1109. return TRUE;
  1110. ErrorExit:
  1111. IF_DEBUG( CGI )
  1112. {
  1113. DBGPRINTF((DBG_CONTEXT,
  1114. "[SetupChildPipes] Failed with error %d\n",
  1115. GetLastError()));
  1116. }
  1117. if ( *phParentIn )
  1118. {
  1119. TCP_REQUIRE( CloseHandle( *phParentIn ));
  1120. *phParentIn = NULL;
  1121. }
  1122. if ( *phParentOut )
  1123. {
  1124. TCP_REQUIRE( CloseHandle( *phParentOut ));
  1125. *phParentOut = NULL;
  1126. }
  1127. return FALSE;
  1128. }
  1129. /*******************************************************************
  1130. NAME: SetupChildEnv
  1131. SYNOPSIS: Based on the passed pRequest, builds a CGI environment block
  1132. ENTRY: pExec - Execution Descriptor Block
  1133. pBuff - Buffer to receive environment block
  1134. RETURNS: TRUE if successful, FALSE on failure
  1135. HISTORY:
  1136. Johnl 22-Sep-1994 Created
  1137. ********************************************************************/
  1138. BOOL SetupChildEnv( EXEC_DESCRIPTOR * pExec,
  1139. BUFFER * pBuff )
  1140. {
  1141. TCHAR * pch;
  1142. TCHAR * pchtmp;
  1143. CHAR tmpStr[1024];
  1144. STR strVal(tmpStr,1024);
  1145. UINT cchCurrentPos = 0; // Points to '\0' in buffer
  1146. UINT cchName, cchValue;
  1147. UINT cbNeeded;
  1148. BOOL fChild = pExec->IsChild();
  1149. HTTP_REQUEST * pRequest = pExec->_pRequest;
  1150. int i = 0;
  1151. //
  1152. // Build the environment block for CGI
  1153. //
  1154. while ( g_pEnvEntries[i].m_pszName != NULL )
  1155. {
  1156. //
  1157. // Check if this is a copy entry from process environment
  1158. //
  1159. if ( g_pEnvEntries[i].m_cchToCopy )
  1160. {
  1161. if ( !pBuff->Resize( (cchCurrentPos + g_pEnvEntries[i].m_cchToCopy)
  1162. * sizeof(TCHAR) ) )
  1163. {
  1164. return FALSE;
  1165. }
  1166. pch = (TCHAR *) pBuff->QueryPtr();
  1167. memcpy( pch + cchCurrentPos,
  1168. g_pEnvEntries[i].m_pszName,
  1169. g_pEnvEntries[i].m_cchToCopy );
  1170. cchCurrentPos += g_pEnvEntries[i].m_cchToCopy;
  1171. ++i;
  1172. continue;
  1173. }
  1174. //
  1175. // The NULL string means we're adding all of
  1176. // the HTTP header fields which requires a little
  1177. // bit of special processing
  1178. //
  1179. if ( !*g_pEnvEntries[i].m_pszName )
  1180. {
  1181. pch = "ALL_HTTP";
  1182. }
  1183. else
  1184. {
  1185. pch = g_pEnvEntries[i].m_pszName;
  1186. }
  1187. if ( !strcmp( pch, "PATH_INFO" ) )
  1188. {
  1189. if ( !strVal.Copy( pExec->_pstrPathInfo->QueryStr() ) )
  1190. {
  1191. return FALSE;
  1192. }
  1193. }
  1194. else if ( !strcmp( pch, "PATH_TRANSLATED" ) )
  1195. {
  1196. if ( !pRequest->LookupVirtualRoot( &strVal,
  1197. pExec->_pstrPathInfo->QueryStr(),
  1198. pExec->_pstrPathInfo->QueryCCH(),
  1199. NULL,
  1200. NULL,
  1201. NULL,
  1202. NULL,
  1203. FALSE,
  1204. pExec->_pstrPathInfo->QueryCCH() ?
  1205. &pExec->_pPathInfoMetaData : NULL,
  1206. pExec->_pstrPathInfo->QueryCCH() ?
  1207. &pExec->_pPathInfoURIBlob : NULL ) )
  1208. {
  1209. return FALSE;
  1210. }
  1211. }
  1212. else if ( fChild && !strcmp( pch, "QUERY_STRING" ) )
  1213. {
  1214. if ( !strVal.Copy( pExec->_pstrURLParams->QueryStr() ) )
  1215. {
  1216. return FALSE;
  1217. }
  1218. }
  1219. else
  1220. {
  1221. if ( !pRequest->GetInfo( pch, &strVal ) )
  1222. {
  1223. return FALSE;
  1224. }
  1225. }
  1226. cchName = _tcslen( g_pEnvEntries[i].m_pszName );
  1227. cchValue = strVal.QueryCCH();
  1228. //
  1229. // We need space for the terminating '\0' and the '='
  1230. //
  1231. cbNeeded = ( cchName + cchValue + 1 + 1) * sizeof(TCHAR);
  1232. if ( !pBuff->Resize( cchCurrentPos * sizeof(TCHAR) + cbNeeded,
  1233. 512 ))
  1234. {
  1235. return FALSE;
  1236. }
  1237. //
  1238. // Replace the '\n' with a '\0' as needed
  1239. // for the HTTP headers
  1240. //
  1241. if ( !*g_pEnvEntries[i].m_pszName )
  1242. {
  1243. pchtmp = strVal.QueryStr();
  1244. //
  1245. // Convert the first ':' of each header to to an '=' for the
  1246. // environment table
  1247. //
  1248. while ( pchtmp = strchr( pchtmp, ':' ))
  1249. {
  1250. *pchtmp = '=';
  1251. if ( !(pchtmp = strchr( pchtmp, '\n' )))
  1252. {
  1253. break;
  1254. }
  1255. }
  1256. pchtmp = strVal.QueryStr();
  1257. while ( pchtmp = strchr( pchtmp+1, '\n' ))
  1258. {
  1259. *pchtmp = '\0';
  1260. }
  1261. }
  1262. pch = (TCHAR *) pBuff->QueryPtr();
  1263. if ( *g_pEnvEntries[i].m_pszName )
  1264. {
  1265. if ( strVal.QueryStr()[0] )
  1266. {
  1267. memcpy( pch + cchCurrentPos,
  1268. g_pEnvEntries[i].m_pszName,
  1269. cchName * sizeof(TCHAR));
  1270. *(pch + cchCurrentPos + cchName) = '=';
  1271. memcpy( pch + cchCurrentPos + cchName + 1,
  1272. strVal.QueryStr(),
  1273. (cchValue + 1) * sizeof(TCHAR));
  1274. cchCurrentPos += cchName + cchValue + 1 + 1;
  1275. }
  1276. }
  1277. else
  1278. {
  1279. CopyMemory(
  1280. pch + cchCurrentPos + cchName,
  1281. strVal.QueryStr(),
  1282. (cchValue + 1) * sizeof(TCHAR));
  1283. cchCurrentPos += cchName + cchValue;
  1284. }
  1285. i++;
  1286. }
  1287. //
  1288. // Add a '\0' terminator to the environment list
  1289. //
  1290. if ( !pBuff->Resize( (cchCurrentPos + 1) * sizeof(TCHAR)))
  1291. {
  1292. return FALSE;
  1293. }
  1294. *((TCHAR *) pBuff->QueryPtr() + cchCurrentPos) = TEXT('\0');
  1295. return TRUE;
  1296. }
  1297. /*******************************************************************
  1298. NAME: SetupCmdLine
  1299. SYNOPSIS: Sets up a CGI command line
  1300. ENTRY: pstrCmdLine - Receives command line
  1301. strParams - Parameters following "?" in URL
  1302. HISTORY:
  1303. Johnl 04-Oct-1994 Created
  1304. ********************************************************************/
  1305. BOOL
  1306. SetupCmdLine(
  1307. STR * pstrCmdLine,
  1308. const STR & strParams
  1309. )
  1310. {
  1311. TCHAR * pch;
  1312. //
  1313. // If an unencoded "=" is found, don't use the command line
  1314. // (some weird CGI rule)
  1315. //
  1316. if ( _tcschr( strParams.QueryStr(),
  1317. TEXT('=') ))
  1318. {
  1319. return TRUE;
  1320. }
  1321. STACK_STR (strDecodedParams, 256);
  1322. //
  1323. // Replace "+" with spaces and decode any hex escapes
  1324. //
  1325. if ( !strDecodedParams.Copy( strParams ) )
  1326. {
  1327. return FALSE;
  1328. }
  1329. while ( pch = _tcschr( strDecodedParams.QueryStr(),
  1330. TEXT('+') ))
  1331. {
  1332. *pch = TEXT(' ');
  1333. pch++;
  1334. }
  1335. if (!strDecodedParams.Unescape() ||
  1336. !pstrCmdLine->Append(strDecodedParams))
  1337. {
  1338. return FALSE;
  1339. }
  1340. return TRUE;
  1341. } // SetupCmdLine
  1342. /*******************************************************************
  1343. NAME: CGIThread
  1344. SYNOPSIS: Sends any gateway data to the scripts stdin and forwards
  1345. the script's stdout to the client's socket
  1346. ENTRY: Param - Pointer to CGI_INFO structure
  1347. HISTORY:
  1348. Johnl 22-Sep-1994 Created
  1349. ********************************************************************/
  1350. DWORD CGIThread( PVOID Param )
  1351. {
  1352. CGI_INFO * pCGIInfo = (CGI_INFO *) Param;
  1353. HTTP_REQUEST * pRequest = pCGIInfo->_pRequest;
  1354. EXEC_DESCRIPTOR * pExec = pCGIInfo->_pExec;
  1355. BYTE buff[2048];
  1356. DWORD cbWritten;
  1357. DWORD cbRead;
  1358. DWORD cbSent;
  1359. DWORD dwExitCode;
  1360. DWORD err;
  1361. BOOL fReadHeaders = FALSE;
  1362. BOOL fChild = pExec->IsChild();
  1363. BOOL fNoHeaders = pExec->NoHeaders();
  1364. BOOL fRedirectOnly = pExec->RedirectOnly();
  1365. BOOL fDone = FALSE;
  1366. BOOL fSkipDisconnect;
  1367. BOOL fRet = TRUE;
  1368. DWORD dwHttpStatus = HT_OK;
  1369. DWORD msScriptTimeout = pRequest->QueryMetaData()->QueryScriptTimeout()
  1370. * 1000;
  1371. STACK_STR(strTemp, 128);
  1372. STACK_STR(strResponse, 512);
  1373. CHAR ach[17];
  1374. CHAR *pszCRLF;
  1375. DWORD dwCRLFSize;
  1376. BOOL fExitCodeProcess;
  1377. DWORD dwCookie = 0;
  1378. IF_DEBUG( CGI )
  1379. {
  1380. DBGPRINTF((DBG_CONTEXT,
  1381. "[CGIThread] Entered, hstdin %x, hstdout = %x\n",
  1382. pCGIInfo->_hStdIn,
  1383. pCGIInfo->_hStdOut));
  1384. }
  1385. pRequest->SetLogStatus( HT_OK, NO_ERROR );
  1386. //
  1387. // Update the statistics counters
  1388. //
  1389. pRequest->QueryW3StatsObj()->IncrCGIRequests();
  1390. //
  1391. // Schedule a callback to kill the process if he doesn't die
  1392. // in a timely manner
  1393. //
  1394. pCGIInfo->_dwSchedCookie = ScheduleWorkItem( CGITerminateProcess,
  1395. pCGIInfo->_hProcess,
  1396. msScriptTimeout );
  1397. if ( !pCGIInfo->_dwSchedCookie )
  1398. {
  1399. DBGPRINTF(( DBG_CONTEXT,
  1400. "[CGI_THREAD] ScheduleWorkItem failed, error %d\n",
  1401. GetLastError() ));
  1402. }
  1403. //
  1404. // First we have to write any additional data to the program's stdin
  1405. //
  1406. if ( pRequest->QueryEntityBodyCB() )
  1407. {
  1408. DWORD cbNextRead;
  1409. DWORD cbLeft = 0;
  1410. IF_DEBUG( CGI )
  1411. {
  1412. DBGPRINTF((DBG_CONTEXT,
  1413. "[CGIThread] Writing %d bytes to child's stdin\n",
  1414. pRequest->QueryEntityBodyCB()));
  1415. }
  1416. if ( !::WriteFile( pCGIInfo->_hStdOut,
  1417. pRequest->QueryEntityBody(),
  1418. pRequest->QueryEntityBodyCB(),
  1419. &cbWritten,
  1420. NULL ))
  1421. {
  1422. DBGPRINTF((DBG_CONTEXT,
  1423. "[CGI_THREAD] WriteFile failed, error %d\n",
  1424. GetLastError()));
  1425. }
  1426. if ( cbWritten != pRequest->QueryEntityBodyCB() )
  1427. {
  1428. DBGPRINTF((DBG_CONTEXT,
  1429. "[CGI_THREAD] %d bytes written of %d bytes\n",
  1430. cbWritten,
  1431. pRequest->QueryEntityBodyCB()));
  1432. }
  1433. //
  1434. // Now stream any unread data to the CGI application but
  1435. // watch out for the case where we get more data then is
  1436. // indicated by the Content-Length
  1437. //
  1438. if ( pRequest->QueryEntityBodyCB() < pRequest->QueryClientContentLength())
  1439. {
  1440. cbLeft = pRequest->QueryClientContentLength() -
  1441. pRequest->QueryEntityBodyCB();
  1442. }
  1443. while ( cbLeft )
  1444. {
  1445. cbNextRead = min( cbLeft,
  1446. pRequest->QueryClientReqBuff()->QuerySize() );
  1447. if ( !pRequest->ReadFile( pRequest->QueryClientRequest(),
  1448. cbNextRead,
  1449. &cbRead,
  1450. IO_FLAG_SYNC ) ||
  1451. !cbRead )
  1452. {
  1453. DBGPRINTF(( DBG_CONTEXT,
  1454. "[CGI_THREAD] Error reading gateway data (%d)\n",
  1455. GetLastError() ));
  1456. fRet = FALSE;
  1457. break;
  1458. }
  1459. cbLeft -= cbRead;
  1460. pRequest->AddTotalEntityBodyCB( cbRead );
  1461. if ( !::WriteFile( pCGIInfo->_hStdOut,
  1462. pRequest->QueryClientRequest(),
  1463. cbRead,
  1464. &cbWritten,
  1465. NULL ))
  1466. {
  1467. fRet = FALSE;
  1468. break;
  1469. }
  1470. }
  1471. }
  1472. if ( !fRet )
  1473. {
  1474. //
  1475. // If an error occurred during the client read or write, we let the CGI
  1476. // application continue
  1477. //
  1478. DBGPRINTF((DBG_CONTEXT,
  1479. "[CGI_THREAD] Gateway ReadFile or CGI WriteFile failed, error %d\n",
  1480. GetLastError()));
  1481. }
  1482. //
  1483. // Now wait for any data the child sends to its stdout or for the
  1484. // process to exit
  1485. //
  1486. //
  1487. // Handle input from child
  1488. //
  1489. while (TRUE)
  1490. {
  1491. fRet = ::ReadFile( pCGIInfo->_hStdIn,
  1492. buff,
  1493. sizeof(buff),
  1494. &cbRead,
  1495. NULL );
  1496. if ( !fRet )
  1497. {
  1498. err = GetLastError();
  1499. if ( err == ERROR_BROKEN_PIPE )
  1500. {
  1501. break;
  1502. }
  1503. IF_DEBUG( CGI )
  1504. {
  1505. DBGPRINTF((DBG_CONTEXT,
  1506. "[CGI_THREAD] ReadFile from child stdout failed, error %d, _hStdIn = %x\n",
  1507. GetLastError(),
  1508. pCGIInfo->_hStdIn));
  1509. }
  1510. pRequest->SetLogStatus( 500, err );
  1511. break;
  1512. }
  1513. IF_DEBUG( CGI )
  1514. {
  1515. DBGPRINTF((DBG_CONTEXT,
  1516. "[CGI_THREAD] ReadFile read %d bytes\n",
  1517. cbRead));
  1518. }
  1519. //
  1520. // If no bytes were read, assume the file has been closed so
  1521. // get out
  1522. //
  1523. if ( !cbRead )
  1524. {
  1525. break;
  1526. }
  1527. //
  1528. // The CGI script can specify headers to include in the
  1529. // response. Wait till we receive all of the headers.
  1530. //
  1531. if ( !fReadHeaders && !pExec->IsNPH() )
  1532. {
  1533. if ( !fNoHeaders)
  1534. {
  1535. if ( !ProcessCGIInput( pCGIInfo,
  1536. buff,
  1537. cbRead,
  1538. &fReadHeaders,
  1539. &fDone,
  1540. &fSkipDisconnect,
  1541. &dwHttpStatus ))
  1542. {
  1543. DBGPRINTF((DBG_CONTEXT,
  1544. "[CGIThread] ProcessCGIInput failed with error %d\n",
  1545. GetLastError()));
  1546. if ( fChild )
  1547. {
  1548. goto SkipDisconnect;
  1549. }
  1550. else
  1551. {
  1552. goto Disconnect;
  1553. }
  1554. }
  1555. if ( fSkipDisconnect )
  1556. {
  1557. goto SkipDisconnect;
  1558. }
  1559. if ( fDone )
  1560. {
  1561. if ( fChild )
  1562. {
  1563. goto SkipDisconnect;
  1564. }
  1565. else
  1566. {
  1567. goto Disconnect;
  1568. }
  1569. }
  1570. }
  1571. else
  1572. {
  1573. BYTE * pbExtraData;
  1574. DWORD cbRemainingData;
  1575. pbExtraData = ScanForTerminator( (CHAR*) buff );
  1576. if ( pbExtraData != NULL )
  1577. {
  1578. fReadHeaders = TRUE;
  1579. cbRemainingData = cbRead - DIFF( pbExtraData - buff );
  1580. if ( HTV_HEAD != pRequest->QueryVerb() &&
  1581. !pRequest->WriteFile( pbExtraData,
  1582. cbRemainingData,
  1583. &cbSent,
  1584. IO_FLAG_SYNC ) )
  1585. {
  1586. pRequest->SetLogStatus( HT_SERVER_ERROR,
  1587. GetLastError() );
  1588. break;
  1589. }
  1590. }
  1591. }
  1592. //
  1593. // Either we are waiting for the rest of the header or
  1594. // we've sent the header and any residual data so wait
  1595. // for more data
  1596. //
  1597. continue;
  1598. }
  1599. if ( (HTV_HEAD != pRequest->QueryVerb()) &&
  1600. !pRequest->WriteFile( buff,
  1601. cbRead,
  1602. &cbSent,
  1603. IO_FLAG_SYNC ))
  1604. {
  1605. DBGPRINTF((DBG_CONTEXT,
  1606. "[CGI_THREAD] WriteFile to socket failed, error %d\n",
  1607. GetLastError()));
  1608. pRequest->SetLogStatus( 500,
  1609. GetLastError() );
  1610. break;
  1611. }
  1612. }
  1613. //
  1614. // Remove the scheduled timeout callback
  1615. //
  1616. dwCookie = (DWORD) InterlockedExchange( (LPLONG) &(pCGIInfo->_dwSchedCookie),
  1617. 0 );
  1618. if ( dwCookie != 0 )
  1619. {
  1620. if ( !RemoveWorkItem( dwCookie ) )
  1621. {
  1622. DBGPRINTF(( DBG_CONTEXT,
  1623. "[CGI_THREAD] Failed to remove scheduled item\n" ));
  1624. }
  1625. }
  1626. //
  1627. // If we had to kill the process, or the Job Object killed the process,
  1628. // log an event
  1629. //
  1630. fExitCodeProcess = GetExitCodeProcess( pCGIInfo->_hProcess,
  1631. &dwExitCode );
  1632. if ( (fExitCodeProcess) &&
  1633. (( dwExitCode == CGI_PREMATURE_DEATH_CODE ) ||
  1634. ( dwExitCode == ERROR_NOT_ENOUGH_QUOTA )))
  1635. {
  1636. const CHAR * apsz[2];
  1637. //
  1638. // Log an event and terminate the process
  1639. //
  1640. DBGPRINTF((DBG_CONTEXT,
  1641. "[CGI_THREAD] - CGI Script %s, params %s was killed\n",
  1642. pExec->_pstrURL->QueryStr(),
  1643. pExec->_pstrURLParams->QueryStr()));
  1644. apsz[0] = pExec->_pstrURL->QueryStr();
  1645. apsz[1] = pExec->_pstrURLParams->QueryStr();
  1646. //
  1647. // As it happens, this message is appropriate for Job Object
  1648. // CGI Timeout as well as the original timeout
  1649. //
  1650. g_pInetSvc->LogEvent( W3_EVENT_KILLING_SCRIPT,
  1651. 2,
  1652. apsz,
  1653. 0 );
  1654. dwHttpStatus = HT_BAD_GATEWAY;
  1655. //
  1656. // If we haven't sent the headers, build up a full response, otherwise
  1657. // tack on the message to the end of the current output
  1658. //
  1659. if ( !fNoHeaders && !fRedirectOnly )
  1660. {
  1661. DWORD dwTemp;
  1662. pRequest->SetLogStatus( HT_BAD_GATEWAY,
  1663. ERROR_SERVICE_REQUEST_TIMEOUT );
  1664. if ( HTTP_REQ_BASE::BuildStatusLine( &strTemp,
  1665. HT_BAD_GATEWAY,
  1666. 0,
  1667. pRequest->QueryURL(),
  1668. NULL))
  1669. {
  1670. if ( pRequest->BuildBaseResponseHeader( &strResponse,
  1671. &fDone,
  1672. &strTemp,
  1673. HTTPH_NO_CUSTOM))
  1674. {
  1675. strResponse.SetLen( strlen(strResponse.QueryStr()) );
  1676. strTemp.SetLen(0);
  1677. if (pRequest->CheckCustomError(&strTemp, HT_BAD_GATEWAY,
  1678. MD_ERROR_SUB502_TIMEOUT,
  1679. &fDone,
  1680. &dwTemp))
  1681. {
  1682. if (fDone)
  1683. {
  1684. goto SkipDisconnect;
  1685. }
  1686. pszCRLF = "\r\n";
  1687. dwCRLFSize = CRLF_SIZE;
  1688. DBG_REQUIRE(strTemp.SetLen(strlen(strTemp.QueryStr())) );
  1689. }
  1690. else
  1691. {
  1692. if ( !g_pInetSvc->LoadStr( strTemp,
  1693. IDS_CGI_APP_TIMEOUT ))
  1694. {
  1695. goto Disconnect;
  1696. }
  1697. dwTemp = strTemp.QueryCB();
  1698. pszCRLF = "\r\nContent-Type: text/html\r\n\r\n";
  1699. dwCRLFSize = sizeof("\r\nContent-Type: text/html\r\n\r\n") - 1;
  1700. }
  1701. _itoa( dwTemp, ach, 10 );
  1702. DBG_REQUIRE(strResponse.Append("Content-Length: ",
  1703. sizeof("Content-Length: ") - 1));
  1704. DBG_REQUIRE(strResponse.Append(ach));
  1705. DBG_REQUIRE(strResponse.Append(pszCRLF, dwCRLFSize));
  1706. if (HTV_HEAD != pRequest->QueryVerb())
  1707. {
  1708. DBG_REQUIRE( strResponse.Append( strTemp));
  1709. }
  1710. pRequest->SendHeader( strResponse.QueryStr(),
  1711. strResponse.QueryCB(),
  1712. IO_FLAG_SYNC,
  1713. &fDone );
  1714. }
  1715. }
  1716. }
  1717. else
  1718. {
  1719. if ( g_pInetSvc->LoadStr( strResponse, IDS_CGI_APP_TIMEOUT ))
  1720. {
  1721. pRequest->WriteFile( strResponse.QueryStr(),
  1722. strResponse.QueryCB(),
  1723. &cbSent,
  1724. IO_FLAG_SYNC );
  1725. }
  1726. }
  1727. }
  1728. else if ( !fReadHeaders )
  1729. {
  1730. //
  1731. // If we never finished reading the headers, send a nice message
  1732. // to the client
  1733. //
  1734. if ( !fNoHeaders && !fRedirectOnly && !pExec->IsNPH() )
  1735. {
  1736. DWORD dwTemp;
  1737. CHAR *pszTemp;
  1738. dwHttpStatus = HT_BAD_GATEWAY;
  1739. pRequest->SetLogStatus( HT_BAD_GATEWAY,
  1740. ERROR_SERVICE_REQUEST_TIMEOUT );
  1741. if ( HTTP_REQ_BASE::BuildStatusLine( &strTemp,
  1742. HT_BAD_GATEWAY,
  1743. 0,
  1744. pRequest->QueryURL(),
  1745. NULL))
  1746. {
  1747. CHAR ach[17];
  1748. CHAR *pszCRLF;
  1749. DWORD dwCRLFSize;
  1750. DWORD dwExtraSize;
  1751. if ( pRequest->BuildBaseResponseHeader( &strResponse,
  1752. &fDone,
  1753. &strTemp,
  1754. HTTPH_NO_CUSTOM))
  1755. {
  1756. DWORD dwCGIHeaderLength;
  1757. strResponse.SetLen( strlen(strResponse.QueryStr()) );
  1758. strTemp.SetLen(0);
  1759. if (pRequest->CheckCustomError(&strTemp, HT_BAD_GATEWAY,
  1760. MD_ERROR_SUB502_PREMATURE_EXIT,
  1761. &fDone,
  1762. &dwTemp))
  1763. {
  1764. if (fDone)
  1765. {
  1766. goto SkipDisconnect;
  1767. }
  1768. pszCRLF = "\r\n";
  1769. dwCRLFSize = CRLF_SIZE;
  1770. DBG_REQUIRE(strTemp.SetLen(strlen(strTemp.QueryStr())) );
  1771. dwExtraSize = strTemp.QueryCB() - dwTemp;
  1772. }
  1773. else
  1774. {
  1775. if ( !g_pInetSvc->LoadStr( strTemp, IDS_BAD_CGI_APP ))
  1776. {
  1777. goto Disconnect;
  1778. }
  1779. dwTemp = strTemp.QueryCB();
  1780. dwExtraSize = 0;
  1781. pszCRLF = "\r\nContent-Type: text/html\r\n\r\n";
  1782. dwCRLFSize = sizeof("\r\nContent-Type: text/html\r\n\r\n") - 1;
  1783. }
  1784. dwCGIHeaderLength = strlen((CHAR *)pCGIInfo->_Buff.QueryPtr() );
  1785. if (strstr(strTemp.QueryStr(), "%s") != NULL)
  1786. {
  1787. dwTemp += dwCGIHeaderLength;
  1788. dwTemp -= (sizeof("%s") - 1);
  1789. }
  1790. // Truncate the buffer to 1024, since wsprintf won't do more than that.
  1791. if (dwTemp > 1024)
  1792. {
  1793. dwTemp = 1024;
  1794. }
  1795. _itoa( dwTemp, ach, 10 );
  1796. DBG_REQUIRE(strResponse.Append("Content-Length: ",
  1797. sizeof("Content-Length: ") - 1));
  1798. DBG_REQUIRE(strResponse.Append(ach));
  1799. DBG_REQUIRE(strResponse.Append(pszCRLF, dwCRLFSize));
  1800. if (HTV_HEAD != pRequest->QueryVerb())
  1801. {
  1802. //
  1803. // There might be a string substitute pattern in the error
  1804. // message. Resize the buffer to handle it, and substitue
  1805. // the headers.
  1806. //
  1807. if (!strResponse.Resize(strResponse.QueryCB() +
  1808. dwTemp + dwExtraSize + 1))
  1809. {
  1810. goto Disconnect;
  1811. }
  1812. if (!pCGIInfo->_Buff.Resize(dwCGIHeaderLength + 1))
  1813. {
  1814. goto Disconnect;
  1815. }
  1816. pszTemp = (CHAR *)pCGIInfo->_Buff.QueryPtr();
  1817. pszTemp[dwCGIHeaderLength] = '\0';
  1818. wsprintf(strResponse.QueryStr() + strResponse.QueryCB(),
  1819. strTemp.QueryStr(), pszTemp);
  1820. DBG_REQUIRE(strResponse.SetLen(strResponse.QueryCB() +
  1821. dwTemp + dwExtraSize));
  1822. }
  1823. pRequest->SendHeader( strResponse.QueryStr(),
  1824. strResponse.QueryCB(),
  1825. IO_FLAG_SYNC,
  1826. &fDone );
  1827. }
  1828. }
  1829. }
  1830. else
  1831. {
  1832. //
  1833. // We don't want headers and none were found.
  1834. // Send whatever CGI output was 'as is'.
  1835. //
  1836. if ( pCGIInfo->_cbData )
  1837. {
  1838. pRequest->WriteFile( pCGIInfo->_Buff.QueryPtr(),
  1839. pCGIInfo->_cbData,
  1840. &cbSent,
  1841. IO_FLAG_SYNC );
  1842. }
  1843. }
  1844. }
  1845. if ( fChild )
  1846. {
  1847. goto SkipDisconnect;
  1848. }
  1849. Disconnect:
  1850. DBG_ASSERT( !fChild );
  1851. IF_DEBUG ( CGI )
  1852. {
  1853. DBGPRINTF((DBG_CONTEXT,
  1854. "[CGIThread] Exiting thread, Current State = %d, Ref = %d\n",
  1855. pRequest->QueryState(),
  1856. pRequest->QueryRefCount()));
  1857. }
  1858. //
  1859. // Make sure this request gets logged
  1860. //
  1861. pRequest->SetState( pRequest->QueryState(),
  1862. dwHttpStatus,
  1863. NO_ERROR ); // Don't have a good Win32 mapping here
  1864. pRequest->WriteLogRecord();
  1865. //
  1866. // Force a shutdown here so the CGI process exit doesn't cause a
  1867. // reset on this socket
  1868. //
  1869. pRequest->Disconnect( 0, NO_ERROR, TRUE );
  1870. SkipDisconnect: // The only time the disconnect is skipped is when
  1871. // a CGI app sends a redirect
  1872. if ( !pCGIInfo->_fServerPoolThread )
  1873. {
  1874. //
  1875. // Indicate that this Atq context should not be used because this thread
  1876. // is about to go away which will cause the AcceptEx IO to be cancelled
  1877. //
  1878. pRequest->QueryClientConn()->SetAtqReuseContextFlag( FALSE );
  1879. //
  1880. // Reference is only done if we've created a new thread
  1881. //
  1882. if ( !fChild )
  1883. {
  1884. DereferenceConn( pRequest->QueryClientConn() );
  1885. }
  1886. }
  1887. EnterCriticalSection( &CGI_INFO::_csCgiList );
  1888. RemoveEntryList( &pCGIInfo->_CgiListEntry );
  1889. LeaveCriticalSection( &CGI_INFO::_csCgiList );
  1890. delete pCGIInfo;
  1891. pRequest->QueryW3StatsObj()->DecrCurrentCGIRequests();
  1892. return 0;
  1893. }
  1894. /*******************************************************************
  1895. NAME: ProcessCGIInput
  1896. SYNOPSIS: Handles headers the CGI program hands back to the server
  1897. ENTRY: pCGIInfo - Pointer to CGI structure
  1898. buff - Pointer to data just read
  1899. cbRead - Number of bytes read into buff
  1900. pfReadHeaders - Set to TRUE after we've finished processing
  1901. all of the HTTP headers the CGI script gave us
  1902. pfDone - Set to TRUE to indicate no further processing is
  1903. needed
  1904. pfSkipDisconnect - Set to TRUE to indicate no further
  1905. processing is needed and the caller should not call
  1906. disconnect
  1907. HISTORY:
  1908. Johnl 22-Sep-1994 Created
  1909. ********************************************************************/
  1910. BOOL
  1911. ProcessCGIInput(
  1912. CGI_INFO * pCGIInfo,
  1913. BYTE * buff,
  1914. DWORD cbRead,
  1915. BOOL * pfReadHeaders,
  1916. BOOL * pfDone,
  1917. BOOL * pfSkipDisconnect,
  1918. DWORD * pdwHttpStatus
  1919. )
  1920. {
  1921. PCHAR pchValue;
  1922. PCHAR pchField;
  1923. BYTE * pbData;
  1924. PCHAR pszTail;
  1925. DWORD cbData;
  1926. DWORD cbSent;
  1927. STACK_STR( strContentType, 64 );
  1928. STACK_STR( strStatus, 32 );
  1929. STACK_STR( strCGIResp, MAX_PATH ); // Contains CGI client headers
  1930. BOOL fFoundContentType = FALSE;
  1931. BOOL fFoundStatus = FALSE;
  1932. HTTP_REQUEST * pRequest = pCGIInfo->_pRequest;
  1933. DWORD cbNeeded, cbBaseResp;
  1934. BOOL fNoHeaders = pCGIInfo->_pExec->NoHeaders();
  1935. BOOL fRedirectOnly = pCGIInfo->_pExec->RedirectOnly();
  1936. STACK_STR( strError, 80);
  1937. DWORD dwContentLength;
  1938. CHAR ach[17];
  1939. DWORD dwCLLength;
  1940. DBG_ASSERT( cbRead > 0 );
  1941. *pfDone = FALSE;
  1942. *pfSkipDisconnect = FALSE;
  1943. if ( !pCGIInfo->_Buff.Resize( pCGIInfo->_cbData + cbRead,
  1944. 256 ))
  1945. {
  1946. return FALSE;
  1947. }
  1948. CopyMemory( (BYTE *)pCGIInfo->_Buff.QueryPtr() + pCGIInfo->_cbData,
  1949. buff,
  1950. cbRead );
  1951. pCGIInfo->_cbData += cbRead;
  1952. //
  1953. // The end of CGI headers are marked by a blank line, check to see if
  1954. // we've hit that line
  1955. //
  1956. if ( !CheckForTermination( pfReadHeaders,
  1957. &pCGIInfo->_Buff,
  1958. pCGIInfo->_cbData,
  1959. &pbData,
  1960. &cbData,
  1961. 256 ))
  1962. {
  1963. return FALSE;
  1964. }
  1965. if ( !*pfReadHeaders )
  1966. {
  1967. return TRUE;
  1968. }
  1969. //
  1970. // We've found the end of the headers, process them
  1971. //
  1972. // if request header contains:
  1973. //
  1974. // Content-Type: xxxx - Send as the content type
  1975. // Location: xxxx - if starts with /, send doc, otherwise send redirect message
  1976. // URI: preferred synonym to Location:
  1977. // Status: nnn xxx - Send as status code (HTTP/1.0 nnn xxx)
  1978. //
  1979. // Send other request headers (server, message date, mime version)
  1980. //
  1981. CHAR pszOutputString[512];
  1982. BOOL fDidRedirect = FALSE;
  1983. INET_PARSER Parser( (CHAR *) pCGIInfo->_Buff.QueryPtr() );
  1984. while ( *(pchField = Parser.QueryToken()) )
  1985. {
  1986. Parser.SkipTo( ':' );
  1987. Parser += 1;
  1988. pchValue = Parser.QueryToken();
  1989. if ( !fRedirectOnly &&
  1990. !::_strnicmp( "Status", pchField, 6 ) )
  1991. {
  1992. DWORD cbLine = strlen( Parser.QueryLine());
  1993. fFoundStatus = TRUE;
  1994. *pdwHttpStatus = atoi( pchValue );
  1995. if ( !strStatus.Resize( LEN_PSZ_HTTP_VERSION_STR +
  1996. cbLine + 4) ||
  1997. !strStatus.Copy( !g_ReplyWith11 ? PSZ_HTTP_VERSION_STR :
  1998. PSZ_HTTP_VERSION_STR11,
  1999. LEN_PSZ_HTTP_VERSION_STR ) ||
  2000. !strStatus.Append( Parser.QueryLine(), cbLine ) )
  2001. {
  2002. return FALSE;
  2003. }
  2004. // I am safe to assume space is there, because of resize
  2005. DBG_ASSERT( strStatus.QueryCB() < (strStatus.QuerySize() - 2));
  2006. strStatus.AppendCRLF();
  2007. }
  2008. else if ( !_strnicmp( "Location", pchField, 8 ) ||
  2009. !_strnicmp( "URI", pchField, 3 ))
  2010. {
  2011. //
  2012. // The CGI script is redirecting us to another URL.
  2013. // If it begins with a '/', then send it, otherwise
  2014. // send a redirect message
  2015. //
  2016. if ( *pchValue == TEXT('/') && !fRedirectOnly )
  2017. {
  2018. if ( !pRequest->ReprocessURL( pchValue,
  2019. HTV_GET ))
  2020. {
  2021. return FALSE;
  2022. }
  2023. *pfSkipDisconnect = TRUE;
  2024. *pfDone = TRUE;
  2025. return TRUE;
  2026. }
  2027. DWORD cbLen;
  2028. CHAR pszMessageString[256];
  2029. cbLen = LoadString( GetModuleHandle( W3_MODULE_NAME ),
  2030. IDS_URL_MOVED,
  2031. pszMessageString,
  2032. 256 );
  2033. if ( !cbLen )
  2034. {
  2035. return FALSE;
  2036. }
  2037. wsprintf( pszOutputString,
  2038. pszMessageString,
  2039. pchValue );
  2040. if ( fRedirectOnly )
  2041. {
  2042. if ( !pRequest->WriteFile( pszOutputString,
  2043. strlen(pszOutputString),
  2044. &cbLen,
  2045. IO_FLAG_SYNC ) )
  2046. {
  2047. return FALSE;
  2048. }
  2049. }
  2050. else if ( !strCGIResp.Append( "Location: ", 10 ) ||
  2051. !strCGIResp.Append( pchValue ) ||
  2052. !strCGIResp.Append( "\r\n", 2 ) ||
  2053. !strStatus.Copy( !g_ReplyWith11 ? PSZ_HTTP_VERSION_STR :
  2054. PSZ_HTTP_VERSION_STR11,
  2055. LEN_PSZ_HTTP_VERSION_STR ) ||
  2056. !strStatus.Append( "302 Object Moved\r\n", 18 ) )
  2057. {
  2058. return FALSE;
  2059. }
  2060. fDidRedirect = TRUE;
  2061. }
  2062. else if ( !fRedirectOnly )
  2063. {
  2064. //
  2065. // Copy any other fields the script specified
  2066. //
  2067. Parser.QueryLine();
  2068. if ( !::_strnicmp( "Content-Type", pchField, 12 ))
  2069. {
  2070. fFoundContentType = TRUE;
  2071. if ( !strContentType.Append( pchField ) ||
  2072. !strContentType.Append( "\r\n", 2 ))
  2073. {
  2074. return FALSE;
  2075. }
  2076. }
  2077. else
  2078. {
  2079. //
  2080. // Terminate line
  2081. //
  2082. if ( !strCGIResp.Append( pchField ) ||
  2083. !strCGIResp.Append( "\r\n", 2 ))
  2084. {
  2085. return FALSE;
  2086. }
  2087. }
  2088. }
  2089. Parser.NextLine();
  2090. }
  2091. //
  2092. // If we're ignoring all but redirects, then simply sent the data
  2093. // past the headers and we're done.
  2094. //
  2095. if ( fRedirectOnly )
  2096. {
  2097. goto SendRemainder;
  2098. }
  2099. //
  2100. // If the CGI script didn't specify a content type, then use
  2101. // the default
  2102. //
  2103. if ( fDidRedirect )
  2104. {
  2105. if ( !strContentType.Copy( "Content-Type: text/html\r\n", 25 ) )
  2106. {
  2107. return FALSE;
  2108. }
  2109. }
  2110. else if ( !fFoundContentType )
  2111. {
  2112. STR str;
  2113. // NYI: SelectMimeMapping will yield a string with allocation
  2114. // this is a temp string - try to avoid allocs
  2115. if ( !strContentType.Append( PSZ_KWD_CONTENT_TYPE,
  2116. LEN_PSZ_KWD_CONTENT_TYPE )||
  2117. !SelectMimeMapping( &str,
  2118. NULL,
  2119. pCGIInfo->_pExec->_pMetaData )||
  2120. !strContentType.Append( str ) ||
  2121. !strContentType.Append( "\r\n", 2 ))
  2122. {
  2123. return FALSE;
  2124. }
  2125. }
  2126. //
  2127. // Combine the CGI specified headers with the regular headers
  2128. // the server would send (message date, server ver. etc)
  2129. //
  2130. if ( !*pdwHttpStatus && !fDidRedirect )
  2131. {
  2132. *pdwHttpStatus = HT_OK;
  2133. }
  2134. else
  2135. {
  2136. if ( *pdwHttpStatus == HT_DENIED )
  2137. {
  2138. pRequest->SetDeniedFlags( SF_DENIED_APPLICATION );
  2139. pRequest->SetAuthenticationRequested( TRUE );
  2140. }
  2141. }
  2142. if ( !pRequest->BuildBaseResponseHeader( pRequest->QueryRespBuf(),
  2143. pfDone,
  2144. (fFoundStatus || fDidRedirect) ?
  2145. &strStatus : NULL,
  2146. (*pdwHttpStatus == HT_OK) ?
  2147. 0 : HTTPH_NO_CUSTOM
  2148. ))
  2149. {
  2150. return FALSE;
  2151. }
  2152. if ( *pfDone )
  2153. {
  2154. return TRUE;
  2155. }
  2156. if ( *pdwHttpStatus != HT_OK && !fDidRedirect )
  2157. {
  2158. DWORD dwSubStatus;
  2159. BOOL fErrorDone;
  2160. // Some sort of error status, check for a custom error.
  2161. fErrorDone = FALSE;
  2162. dwSubStatus = pRequest->IsAuthenticationRequested() ?
  2163. MD_ERROR_SUB401_APPLICATION : 0;
  2164. if (pRequest->CheckCustomError(&strError, *pdwHttpStatus,
  2165. dwSubStatus, &fErrorDone,
  2166. &dwContentLength,
  2167. dwSubStatus == 0 ? TRUE : FALSE))
  2168. {
  2169. // Had some sort of a custom error. If it's being completely
  2170. // handled, bail out now.
  2171. if (fErrorDone)
  2172. {
  2173. *pfSkipDisconnect = TRUE;
  2174. return TRUE;
  2175. }
  2176. // We had a custom error, but it wasn't completely handled. This
  2177. // overrides a content-type and any content sent by the CGI script
  2178. // itself.
  2179. strError.SetLen(strlen(strError.QueryStr()));
  2180. cbData = 0;
  2181. }
  2182. }
  2183. cbBaseResp = pRequest->QueryRespBufCB();
  2184. if (strError.QueryCB() != 0)
  2185. {
  2186. _itoa( dwContentLength, ach, 10 );
  2187. dwCLLength = strlen(ach);
  2188. cbNeeded = cbBaseResp +
  2189. strError.QueryCB() +
  2190. sizeof("Content-Length: \r\n") - 1 +
  2191. dwCLLength +
  2192. 1; // For trailing NULL.
  2193. if ( !pRequest->QueryRespBuf()->Resize( cbNeeded ))
  2194. {
  2195. return FALSE;
  2196. }
  2197. pszTail = pRequest->QueryRespBufPtr() + cbBaseResp;
  2198. memcpy(pszTail, "Content-Length: ", sizeof("Content-Length: ") - 1);
  2199. pszTail += sizeof("Content-Length: ") - 1;
  2200. memcpy(pszTail, ach, dwCLLength);
  2201. pszTail += dwCLLength;
  2202. memcpy(pszTail, "\r\n", CRLF_SIZE);
  2203. pszTail += CRLF_SIZE;
  2204. memcpy(pszTail, strError.QueryStr(), strError.QueryCB() + 1);
  2205. pszTail += strError.QueryCB();
  2206. }
  2207. else
  2208. {
  2209. cbNeeded = cbBaseResp +
  2210. strContentType.QueryCB() +
  2211. strCGIResp.QueryCB() +
  2212. sizeof( "\r\n" ); // Include the '\0' in the count
  2213. if ( fDidRedirect )
  2214. {
  2215. cbNeeded += strlen(pszOutputString);
  2216. }
  2217. if ( !pRequest->QueryRespBuf()->Resize( cbNeeded ))
  2218. {
  2219. return FALSE;
  2220. }
  2221. pszTail = pRequest->QueryRespBufPtr() + cbBaseResp;
  2222. memcpy( pszTail, strContentType.QueryStr(), strContentType.QueryCB() );
  2223. pszTail += strContentType.QueryCB();
  2224. memcpy(pszTail, strCGIResp.QueryStr(), strCGIResp.QueryCB());
  2225. pszTail += strCGIResp.QueryCB();
  2226. memcpy(pszTail, "\r\n", CRLF_SIZE+1);
  2227. pszTail += CRLF_SIZE;
  2228. if ( fDidRedirect )
  2229. {
  2230. memcpy(pszTail, pszOutputString, strlen(pszOutputString));
  2231. pszTail += strlen(pszOutputString);
  2232. }
  2233. }
  2234. if ( !pRequest->SendHeader( pRequest->QueryRespBufPtr(),
  2235. DIFF(pszTail - pRequest->QueryRespBufPtr()),
  2236. IO_FLAG_SYNC,
  2237. pfDone ))
  2238. {
  2239. return FALSE;
  2240. }
  2241. //
  2242. // If we had a custom error message, make sure we set the done flag now.
  2243. //
  2244. if (strError.QueryCB() != 0)
  2245. {
  2246. *pfDone = TRUE;
  2247. }
  2248. if ( fDidRedirect )
  2249. {
  2250. *pfDone = TRUE;
  2251. return TRUE;
  2252. }
  2253. //
  2254. // If there was additional data in the buffer, send that out now
  2255. //
  2256. SendRemainder:
  2257. if ( cbData )
  2258. {
  2259. if ( !pRequest->WriteFile( pbData,
  2260. cbData,
  2261. &cbSent,
  2262. IO_FLAG_SYNC ))
  2263. {
  2264. return FALSE;
  2265. }
  2266. }
  2267. return TRUE;
  2268. }
  2269. VOID
  2270. WINAPI
  2271. CGITerminateProcess(
  2272. PVOID pContext
  2273. )
  2274. /*++
  2275. Routine Description:
  2276. This function is the callback called by the scheduler thread after the
  2277. specified timeout period has elapsed.
  2278. Arguments:
  2279. pContext - Handle of process to kill
  2280. --*/
  2281. {
  2282. IF_DEBUG( CGI )
  2283. {
  2284. DBGPRINTF(( DBG_CONTEXT,
  2285. "[CGITerminateProcess] - Terminating process handle %x\n",
  2286. pContext ));
  2287. }
  2288. if ( !TerminateProcess( (HANDLE) pContext, CGI_PREMATURE_DEATH_CODE ))
  2289. {
  2290. DBGPRINTF((DBG_CONTEXT,
  2291. "[CGITerminateProcess] - TerminateProcess returned %d\n",
  2292. GetLastError()));
  2293. }
  2294. } // CGITerminateProcess
  2295. BOOL
  2296. IsCmdExe(
  2297. const CHAR * pchPath
  2298. )
  2299. {
  2300. while ( *pchPath )
  2301. {
  2302. if ( (*pchPath == 'c') || (*pchPath == 'C') )
  2303. {
  2304. if ( !_strnicmp(pchPath,"cmd.exe",sizeof("cmd.exe") - 1)
  2305. || !_strnicmp(pchPath,"command.com",sizeof("command.com") - 1)
  2306. )
  2307. {
  2308. return TRUE;
  2309. }
  2310. }
  2311. pchPath++;
  2312. }
  2313. return FALSE;
  2314. } // IsCmdExe