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.

1721 lines
39 KiB

  1. /*++
  2. Copyright (c) 2000 Microsoft Corporation
  3. Module Name :
  4. iisdigestprovider.cxx
  5. Abstract:
  6. IIS Digest authentication provider
  7. - version of Digest auth as implemented by IIS5 and IIS5.1
  8. Author:
  9. Jaroslad - based on code from md5filt 10-Nov-2000
  10. Environment:
  11. Win32 - User Mode
  12. Project:
  13. ULW3.DLL
  14. --*/
  15. #include "precomp.hxx"
  16. #include "iisdigestprovider.hxx"
  17. #include "uuencode.hxx"
  18. # include <mbstring.h>
  19. #include <lm.h>
  20. #include <lmcons.h>
  21. #include <lmjoin.h>
  22. #include <time.h>
  23. //
  24. // lonsint.dll related heade files
  25. //
  26. #include <lonsi.hxx>
  27. #include <tslogon.hxx>
  28. #define DIGEST_AUTH "Digest"
  29. //
  30. // value names used by MD5 authentication.
  31. // must be in sync with MD5_AUTH_NAMES
  32. //
  33. enum MD5_AUTH_NAME
  34. {
  35. MD5_AUTH_USERNAME,
  36. MD5_AUTH_URI,
  37. MD5_AUTH_REALM,
  38. MD5_AUTH_NONCE,
  39. MD5_AUTH_RESPONSE,
  40. MD5_AUTH_ALGORITHM,
  41. MD5_AUTH_DIGEST,
  42. MD5_AUTH_OPAQUE,
  43. MD5_AUTH_QOP,
  44. MD5_AUTH_CNONCE,
  45. MD5_AUTH_NC,
  46. MD5_AUTH_LAST,
  47. };
  48. //
  49. // Value names used by MD5 authentication.
  50. // must be in sync with MD5_AUTH_NAME
  51. //
  52. PSTR MD5_AUTH_NAMES[] = {
  53. "username",
  54. "uri",
  55. "realm",
  56. "nonce",
  57. "response",
  58. "algorithm",
  59. "digest",
  60. "opaque",
  61. "qop",
  62. "cnonce",
  63. "nc"
  64. };
  65. //
  66. // Local function implementation
  67. //
  68. static
  69. LPSTR
  70. SkipWhite(
  71. IN OUT LPSTR p
  72. )
  73. /*++
  74. Routine Description:
  75. Skip white space and ','
  76. Arguments:
  77. p - ptr to string
  78. Return Value:
  79. updated ptr after skiping white space
  80. --*/
  81. {
  82. while ( SAFEIsSpace((UCHAR)(*p) ) || *p == ',' )
  83. {
  84. ++p;
  85. }
  86. return p;
  87. }
  88. //
  89. // class IIS_DIGEST_AUTH_PROVIDER implementation
  90. //
  91. //static
  92. STRA * IIS_DIGEST_AUTH_PROVIDER::_pstraComputerDomain = NULL;
  93. //static
  94. HRESULT
  95. IIS_DIGEST_AUTH_PROVIDER::Initialize(
  96. DWORD dwInternalId
  97. )
  98. /*++
  99. Routine Description:
  100. Initialize IIS Digest SSPI provider
  101. Arguments:
  102. None
  103. Return Value:
  104. HRESULT
  105. --*/
  106. {
  107. HRESULT hr;
  108. SetInternalId( dwInternalId );
  109. _pstraComputerDomain = new STRA;
  110. if( _pstraComputerDomain == NULL )
  111. {
  112. return HRESULT_FROM_WIN32( ERROR_OUTOFMEMORY);
  113. }
  114. //
  115. // Ignore errors that may occur while retrieving domain name
  116. // it is important but not critical information
  117. // client can always explicitly specify domain
  118. //
  119. GetLanGroupDomainName( *_pstraComputerDomain );
  120. hr = IIS_DIGEST_CONN_CONTEXT::Initialize();
  121. if ( FAILED( hr ) )
  122. {
  123. DBGPRINTF(( DBG_CONTEXT,
  124. "Error initializing Digest Auth Prov. hr = %x\n",
  125. hr ));
  126. return hr;
  127. }
  128. return NO_ERROR;
  129. }
  130. //static
  131. VOID
  132. IIS_DIGEST_AUTH_PROVIDER::Terminate(
  133. VOID
  134. )
  135. /*++
  136. Routine Description:
  137. Terminate IIS SSPI Digest provider
  138. Arguments:
  139. None
  140. Return Value:
  141. None
  142. --*/
  143. {
  144. if( _pstraComputerDomain != NULL )
  145. {
  146. delete _pstraComputerDomain;
  147. }
  148. IIS_DIGEST_CONN_CONTEXT::Terminate();
  149. }
  150. HRESULT
  151. IIS_DIGEST_AUTH_PROVIDER::DoesApply(
  152. IN W3_MAIN_CONTEXT * pMainContext,
  153. OUT BOOL * pfApplies
  154. )
  155. /*++
  156. Routine Description:
  157. Does the given request have credentials applicable to the Digest
  158. provider
  159. Arguments:
  160. pMainContext - Main context representing request
  161. pfApplies - Set to true if Digest is applicable
  162. Return Value:
  163. HRESULT
  164. --*/
  165. {
  166. CHAR * pszAuthHeader = NULL;
  167. W3_METADATA * pMetaData = NULL;
  168. HRESULT hr = E_FAIL;
  169. STACK_STRA( strPackage, 64 );
  170. DBG_ASSERT( pMainContext != NULL );
  171. DBG_ASSERT( pfApplies != NULL );
  172. *pfApplies = FALSE;
  173. //
  174. // Is using of Digest SSP enabled?
  175. //
  176. if ( g_pW3Server->QueryUseDigestSSP() )
  177. {
  178. //
  179. // Digest SSP is enabled => IIS Digest cannot be used
  180. //
  181. return NO_ERROR;
  182. }
  183. //
  184. // Get the auth type
  185. //
  186. if ( FAILED( hr = pMainContext->QueryRequest()->GetAuthType( &strPackage ) ) )
  187. {
  188. return hr;
  189. }
  190. //
  191. // No package, no auth
  192. //
  193. if ( strPackage.IsEmpty() )
  194. {
  195. return NO_ERROR;
  196. }
  197. //
  198. // Is it Digest?
  199. //
  200. if ( strPackage.EqualsNoCase( DIGEST_AUTH ) )
  201. {
  202. *pfApplies = TRUE;
  203. }
  204. return NO_ERROR;
  205. }
  206. HRESULT
  207. IIS_DIGEST_AUTH_PROVIDER::DoAuthenticate(
  208. IN W3_MAIN_CONTEXT * pMainContext
  209. )
  210. /*++
  211. Description:
  212. Do authentication work (we will be called if we apply)
  213. Arguments:
  214. pMainContext - Main context
  215. Return Value:
  216. HRESULT
  217. --*/
  218. {
  219. HRESULT hr = E_FAIL;
  220. IIS_DIGEST_CONN_CONTEXT * pDigestConnContext = NULL;
  221. PCHAR pszAuthHeader = NULL;
  222. BOOL fQOPAuth = FALSE;
  223. BOOL fSt = FALSE;
  224. HANDLE hAccessTokenImpersonation = NULL;
  225. IIS_DIGEST_USER_CONTEXT * pUserContext = NULL;
  226. W3_METADATA * pMetaData = NULL;
  227. BOOL fSendAccessDenied = FALSE;
  228. STACK_STRA( straVerb, 10 );
  229. STACK_STRU( strDigestUri, MAX_URL_SIZE + 1 );
  230. STACK_STRU( strUrl, MAX_URL_SIZE + 1 );
  231. STACK_STRA( straCurrentNonce, NONCE_SIZE + 1 );
  232. LPSTR aValueTable[ MD5_AUTH_LAST ];
  233. DIGEST_LOGON_INFO DigestLogonInfo;
  234. CHAR achDomain[ IIS_DNLEN + 1 ];
  235. CHAR achNtUser[ 64 ];
  236. STACK_STRA( straUserName, UNLEN + 1 );
  237. STACK_STRA( straDomainName, IIS_DNLEN + 1 );
  238. STACK_STRA( straMetabaseDomainName, IIS_DNLEN + 1 );
  239. ULONG cbBytesCopied;
  240. DBG_ASSERT( pMainContext != NULL );
  241. //
  242. // Get the part after the auth type
  243. //
  244. pszAuthHeader = pMainContext->QueryRequest()->GetHeader( HttpHeaderAuthorization );
  245. DBG_ASSERT( pszAuthHeader != NULL );
  246. DBG_ASSERT( _strnicmp( pszAuthHeader, DIGEST_AUTH, sizeof(DIGEST_AUTH) - 1 ) == 0 );
  247. //
  248. // Skip the name of Authentication scheme
  249. //
  250. if ( pszAuthHeader[ sizeof(DIGEST_AUTH) ] == '\0' )
  251. {
  252. DBG_ASSERT( pszAuthHeader[ sizeof(DIGEST_AUTH) - 1 ] != '\0' );
  253. hr = HRESULT_FROM_WIN32( ERROR_INVALID_DATA );
  254. goto ExitPoint;
  255. }
  256. pszAuthHeader = pszAuthHeader + sizeof(DIGEST_AUTH) - 1;
  257. if ( !IIS_DIGEST_CONN_CONTEXT::ParseForName( pszAuthHeader,
  258. MD5_AUTH_NAMES,
  259. MD5_AUTH_LAST,
  260. aValueTable ) )
  261. {
  262. hr = HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER );
  263. goto ExitPoint;
  264. }
  265. //
  266. // Simple validation of received arguments
  267. //
  268. if ( aValueTable[ MD5_AUTH_USERNAME ] == NULL ||
  269. aValueTable[ MD5_AUTH_REALM ] == NULL ||
  270. aValueTable[ MD5_AUTH_URI ] == NULL ||
  271. aValueTable[ MD5_AUTH_NONCE ] == NULL ||
  272. aValueTable[ MD5_AUTH_RESPONSE ] == NULL )
  273. {
  274. hr = HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER );
  275. goto ExitPoint;
  276. }
  277. //
  278. // Verify quality of protection (qop) required by client
  279. // We only support "auth" type. If anything else is sent by client it will be ignored
  280. //
  281. if ( aValueTable[ MD5_AUTH_QOP ] != NULL )
  282. {
  283. if ( _stricmp( aValueTable[ MD5_AUTH_QOP ], "auth" ) )
  284. {
  285. hr = HRESULT_FROM_WIN32( ERROR_NOT_SUPPORTED );
  286. goto ExitPoint;
  287. }
  288. //
  289. // qop="auth" has mandatory arguments CNONCE and NC
  290. //
  291. if ( aValueTable[ MD5_AUTH_CNONCE ] == NULL ||
  292. aValueTable[ MD5_AUTH_NC ] == NULL )
  293. {
  294. hr = HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER );
  295. goto ExitPoint;
  296. }
  297. fQOPAuth = TRUE;
  298. }
  299. else
  300. {
  301. aValueTable[ MD5_AUTH_QOP ] = "none";
  302. aValueTable[ MD5_AUTH_CNONCE ] = "none";
  303. aValueTable[ MD5_AUTH_NC ] = "none";
  304. }
  305. if ( FAILED( hr = straCurrentNonce.Copy( aValueTable[ MD5_AUTH_NONCE ] ) ) )
  306. {
  307. goto ExitPoint;
  308. }
  309. //
  310. // Verify that the nonce is well-formed
  311. //
  312. if ( !IIS_DIGEST_CONN_CONTEXT::IsWellFormedNonce( straCurrentNonce ) )
  313. {
  314. fSendAccessDenied = TRUE;
  315. hr = HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER );
  316. goto ExitPoint;
  317. }
  318. //
  319. // What is the request verb?
  320. //
  321. if ( FAILED( hr = pMainContext->QueryRequest()->GetVerbString( &straVerb ) ) )
  322. {
  323. goto ExitPoint;
  324. }
  325. //
  326. // Check URI field match URL
  327. //
  328. if ( strlen(aValueTable[MD5_AUTH_URI]) > MAX_URL_SIZE )
  329. {
  330. hr = HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER );
  331. goto ExitPoint;
  332. }
  333. //
  334. // Normalize DigestUri
  335. //
  336. hr = UlCleanAndCopyUrl( (PUCHAR)aValueTable[MD5_AUTH_URI],
  337. strlen( aValueTable[MD5_AUTH_URI] ),
  338. &cbBytesCopied,
  339. strDigestUri.QueryStr(),
  340. NULL );
  341. if ( FAILED( hr ) )
  342. {
  343. goto ExitPoint;
  344. }
  345. if ( FAILED( hr = pMainContext->QueryRequest()->GetUrl( &strUrl ) ) )
  346. {
  347. goto ExitPoint;
  348. }
  349. if ( !strUrl.Equals( strDigestUri.QueryStr() ) )
  350. {
  351. //
  352. // Note: RFC says that BAD REQUEST should be returned
  353. // but for now to be backward compatible with IIS5.1
  354. // we will return ACCESS_DENIED
  355. //
  356. fSendAccessDenied = TRUE;
  357. hr = HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER );
  358. goto ExitPoint;
  359. }
  360. pDigestConnContext = (IIS_DIGEST_CONN_CONTEXT *)
  361. QueryConnectionAuthContext( pMainContext );
  362. if ( pDigestConnContext == NULL )
  363. {
  364. //
  365. // Create new Authentication context
  366. //
  367. pDigestConnContext = new IIS_DIGEST_CONN_CONTEXT();
  368. if ( pDigestConnContext == NULL )
  369. {
  370. hr = HRESULT_FROM_WIN32( ERROR_NOT_ENOUGH_MEMORY );
  371. goto ExitPoint;
  372. }
  373. hr = SetConnectionAuthContext( pMainContext,
  374. pDigestConnContext );
  375. if ( FAILED( hr ) )
  376. {
  377. goto ExitPoint;
  378. }
  379. }
  380. DBG_ASSERT( pDigestConnContext != NULL );
  381. if ( FAILED( hr = pDigestConnContext->GenerateNonce( ) ) )
  382. {
  383. goto ExitPoint;
  384. }
  385. pMetaData = pMainContext->QueryUrlContext()->QueryMetaData();
  386. DBG_ASSERT( pMetaData != NULL );
  387. if ( FAILED( hr = straMetabaseDomainName.CopyW( pMetaData->QueryDomainName() ) ) )
  388. {
  389. goto ExitPoint;
  390. }
  391. if ( FAILED( hr = BreakUserAndDomain( aValueTable[ MD5_AUTH_USERNAME ],
  392. straMetabaseDomainName,
  393. straDomainName,
  394. straUserName ) ) )
  395. {
  396. goto ExitPoint;
  397. }
  398. DigestLogonInfo.pszNtUser = straUserName.QueryStr();
  399. DigestLogonInfo.pszDomain = straDomainName.QueryStr();
  400. DigestLogonInfo.pszUser = aValueTable[ MD5_AUTH_USERNAME ];
  401. DigestLogonInfo.pszRealm = aValueTable[ MD5_AUTH_REALM ];
  402. DigestLogonInfo.pszURI = aValueTable[ MD5_AUTH_URI ];
  403. DigestLogonInfo.pszMethod = straVerb.QueryStr();
  404. DigestLogonInfo.pszNonce = straCurrentNonce.QueryStr();
  405. DigestLogonInfo.pszCurrentNonce = pDigestConnContext->QueryNonce().QueryStr();
  406. DigestLogonInfo.pszCNonce = aValueTable[ MD5_AUTH_CNONCE ];
  407. DigestLogonInfo.pszQOP = aValueTable[ MD5_AUTH_QOP ];
  408. DigestLogonInfo.pszNC = aValueTable[ MD5_AUTH_NC ];
  409. DigestLogonInfo.pszResponse = aValueTable[ MD5_AUTH_RESPONSE ];
  410. fSt = IISLogonDigestUserA( &DigestLogonInfo,
  411. IISSUBA_DIGEST ,
  412. &hAccessTokenImpersonation );
  413. if ( fSt == FALSE )
  414. {
  415. DWORD dwRet = GetLastError();
  416. if ( dwRet == ERROR_PASSWORD_MUST_CHANGE ||
  417. dwRet == ERROR_PASSWORD_EXPIRED )
  418. {
  419. return HRESULT_FROM_WIN32( dwRet );
  420. }
  421. fSendAccessDenied = TRUE;
  422. hr = HRESULT_FROM_WIN32( dwRet );
  423. goto ExitPoint;
  424. }
  425. //
  426. // Response from the client was correct but the nonce has expired,
  427. //
  428. if ( pDigestConnContext->IsExpiredNonce( straCurrentNonce,
  429. pDigestConnContext->QueryNonce() ) )
  430. {
  431. //
  432. // User knows password but nonce that was used for
  433. // response calculation already expired
  434. // Respond to client with stale=TRUE
  435. // Only Digest header will be sent to client
  436. // ( it will prevent state information needed to be passed
  437. // from DoAuthenticate() to OnAccessDenied() )
  438. //
  439. pDigestConnContext->SetStale( TRUE );
  440. hr = SetDigestHeader( pMainContext, pDigestConnContext );
  441. if ( FAILED( hr ) )
  442. {
  443. goto ExitPoint;
  444. }
  445. //
  446. // Don't let anyone else send back authentication headers when
  447. // the 401 is sent
  448. //
  449. pMainContext->SetProviderHandled( TRUE );
  450. //
  451. // We need to send a 401 response to continue the handshake.
  452. // We have already setup the WWW-Authenticate header
  453. //
  454. pMainContext->QueryResponse()->SetStatus( HttpStatusUnauthorized,
  455. Http401BadLogon );
  456. pMainContext->SetFinishedResponse();
  457. pMainContext->SetErrorStatus( SEC_E_CONTEXT_EXPIRED );
  458. hr = NO_ERROR;
  459. goto ExitPoint;
  460. }
  461. //
  462. // We successfully authenticated.
  463. // Create a user context and setup it up
  464. //
  465. DBG_ASSERT( hAccessTokenImpersonation != NULL );
  466. pUserContext = new IIS_DIGEST_USER_CONTEXT( this );
  467. if ( pUserContext == NULL )
  468. {
  469. hr = HRESULT_FROM_WIN32( ERROR_NOT_ENOUGH_MEMORY );
  470. goto ExitPoint;
  471. }
  472. hr = pUserContext->Create( hAccessTokenImpersonation,
  473. aValueTable[MD5_AUTH_USERNAME] );
  474. if ( FAILED( hr ) )
  475. {
  476. pUserContext->DereferenceUserContext();
  477. pUserContext = NULL;
  478. goto ExitPoint;
  479. }
  480. pMainContext->SetUserContext( pUserContext );
  481. hr = NO_ERROR;
  482. ExitPoint:
  483. if ( FAILED( hr ) )
  484. {
  485. if ( fSendAccessDenied )
  486. {
  487. //
  488. // if ACCESS_DENIED then inform server to send 401 response
  489. // if SetStatus is not called then server will respond
  490. // with 500 Server Error
  491. //
  492. pMainContext->QueryResponse()->SetStatus( HttpStatusUnauthorized,
  493. Http401BadLogon );
  494. //
  495. // SetErrorStatus() and reset value of hr
  496. //
  497. pMainContext->SetErrorStatus( hr );
  498. hr = NO_ERROR;
  499. }
  500. if ( hAccessTokenImpersonation != NULL )
  501. {
  502. CloseHandle( hAccessTokenImpersonation );
  503. hAccessTokenImpersonation = NULL;
  504. }
  505. }
  506. return hr;
  507. }
  508. HRESULT
  509. IIS_DIGEST_AUTH_PROVIDER::OnAccessDenied(
  510. IN W3_MAIN_CONTEXT * pMainContext
  511. )
  512. /*++
  513. Description:
  514. Add WWW-Authenticate Digest headers
  515. Arguments:
  516. pMainContext - main context
  517. Return Value:
  518. HRESULT
  519. --*/
  520. {
  521. HRESULT hr = E_FAIL;
  522. W3_METADATA * pMetaData = NULL;
  523. IIS_DIGEST_CONN_CONTEXT * pDigestConnContext = NULL;
  524. DBG_ASSERT( pMainContext != NULL );
  525. //
  526. // 2 providers implement Digest but they are mutually exclusive
  527. // If DigestSSP is enabled then IIS-DIGEST cannot be used
  528. //
  529. if ( g_pW3Server->QueryUseDigestSSP() )
  530. {
  531. //
  532. // Digest SSP is enabled => IIS Digest cannot be used
  533. //
  534. return NO_ERROR;
  535. }
  536. if( !W3_STATE_AUTHENTICATION::QueryIsDomainMember() )
  537. {
  538. //
  539. // We are not a domain member, so do nothing
  540. //
  541. return NO_ERROR;
  542. }
  543. pDigestConnContext = (IIS_DIGEST_CONN_CONTEXT *)
  544. QueryConnectionAuthContext( pMainContext );
  545. if ( pDigestConnContext == NULL )
  546. {
  547. //
  548. // Create new Authentication context
  549. // it may get reused for next request
  550. // if connection is reused
  551. //
  552. pDigestConnContext = new IIS_DIGEST_CONN_CONTEXT();
  553. if ( pDigestConnContext == NULL )
  554. {
  555. return HRESULT_FROM_WIN32( ERROR_NOT_ENOUGH_MEMORY );
  556. }
  557. hr = SetConnectionAuthContext( pMainContext,
  558. pDigestConnContext );
  559. if ( FAILED( hr ) )
  560. {
  561. return hr;
  562. }
  563. }
  564. return SetDigestHeader( pMainContext, pDigestConnContext );
  565. }
  566. HRESULT
  567. IIS_DIGEST_AUTH_PROVIDER::SetDigestHeader(
  568. IN W3_MAIN_CONTEXT * pMainContext,
  569. IN IIS_DIGEST_CONN_CONTEXT * pDigestConnContext
  570. )
  571. /*++
  572. Description:
  573. Add WWW-Authenticate Digest headers
  574. Arguments:
  575. pMainContext - main context
  576. Return Value:
  577. HRESULT
  578. --*/
  579. {
  580. HRESULT hr = E_FAIL;
  581. BOOL fStale = FALSE;
  582. W3_METADATA * pMetaData = NULL;
  583. STACK_STRA( strOutputHeader, MAX_PATH + 1);
  584. STACK_STRA( strNonce, NONCE_SIZE + 1 );
  585. DBG_ASSERT( pMainContext != NULL );
  586. pMetaData = pMainContext->QueryUrlContext()->QueryMetaData();
  587. DBG_ASSERT( pMetaData != NULL );
  588. fStale = pDigestConnContext->QueryStale( );
  589. //
  590. // Reset Stale so that it will not be used for next request
  591. //
  592. pDigestConnContext->SetStale( FALSE );
  593. if ( FAILED( hr = pDigestConnContext->GenerateNonce() ) )
  594. {
  595. return hr;
  596. }
  597. //
  598. // If a realm is configured, use it. Otherwise use host address of
  599. // request
  600. //
  601. STACK_STRA( straRealm, IIS_DNLEN + 1 );
  602. STACK_STRU( strHostAddr, 256 );
  603. if ( pMetaData->QueryRealm() != NULL )
  604. {
  605. hr = straRealm.CopyW( pMetaData->QueryRealm() );
  606. }
  607. else
  608. {
  609. hr = pMainContext->QueryRequest()->GetHostAddr( &strHostAddr );
  610. if ( FAILED( hr ) )
  611. {
  612. return hr;
  613. }
  614. hr = straRealm.CopyW( strHostAddr.QueryStr() );
  615. }
  616. if ( FAILED( hr ) )
  617. {
  618. return hr;
  619. }
  620. //
  621. // build WWW-Authenticate header
  622. //
  623. if ( FAILED( hr = strOutputHeader.Copy( "Digest qop=\"auth\", realm=\"" ) ) )
  624. {
  625. return hr;
  626. }
  627. if ( FAILED( hr = strOutputHeader.Append( straRealm ) ) )
  628. {
  629. return hr;
  630. }
  631. if ( FAILED( hr = strOutputHeader.Append( "\", nonce=\"" ) ) )
  632. {
  633. return hr;
  634. }
  635. if ( FAILED( hr = strOutputHeader.Append( pDigestConnContext->QueryNonce() ) ) )
  636. {
  637. return hr;
  638. }
  639. if ( FAILED( hr = strOutputHeader.Append( fStale ? "\", stale=true" : "\"" ) ) )
  640. {
  641. return hr;
  642. }
  643. //
  644. // Add the header WWW-Authenticate to the response
  645. //
  646. hr = pMainContext->QueryResponse()->SetHeader(
  647. "WWW-Authenticate",
  648. 16,
  649. strOutputHeader.QueryStr(),
  650. strOutputHeader.QueryCCH()
  651. );
  652. return hr;
  653. }
  654. //static
  655. HRESULT
  656. IIS_DIGEST_AUTH_PROVIDER::GetLanGroupDomainName(
  657. OUT STRA& straDomain
  658. )
  659. /*++
  660. Routine Description:
  661. Tries to retrieve the "LAN group"/domain this machine is a member of.
  662. Arguments:
  663. straDomain - receives current domain name
  664. Returns:
  665. HRESULT
  666. --*/
  667. {
  668. //
  669. // NET_API_STATUS is equivalent to WIN32 errors
  670. //
  671. NET_API_STATUS dwStatus = 0;
  672. NETSETUP_JOIN_STATUS JoinStatus;
  673. LPWSTR pwszDomainInfo = NULL;
  674. HRESULT hr = E_FAIL;
  675. dwStatus = NetGetJoinInformation( NULL,
  676. &pwszDomainInfo,
  677. &JoinStatus );
  678. if( dwStatus == NERR_Success)
  679. {
  680. if ( JoinStatus == NetSetupDomainName )
  681. {
  682. //
  683. // we got a domain
  684. //
  685. DBG_ASSERT( pwszDomainInfo != NULL );
  686. if ( FAILED( hr = straDomain.CopyW( pwszDomainInfo ) ) )
  687. {
  688. goto ExitPoint;
  689. }
  690. }
  691. else
  692. {
  693. //
  694. // Domain information is not available
  695. // (maybe server is member of workgroup)
  696. //
  697. straDomain.Reset();
  698. }
  699. }
  700. else
  701. {
  702. hr = HRESULT_FROM_WIN32( dwStatus );
  703. goto ExitPoint;
  704. }
  705. hr = NO_ERROR;
  706. ExitPoint:
  707. if ( pwszDomainInfo != NULL )
  708. {
  709. NetApiBufferFree( (LPVOID) pwszDomainInfo );
  710. }
  711. return hr;
  712. }
  713. //static
  714. HRESULT
  715. IIS_DIGEST_AUTH_PROVIDER::BreakUserAndDomain(
  716. IN PCHAR pszFullName,
  717. IN STRA& straMetabaseConfiguredDomain,
  718. OUT STRA& straDomainName,
  719. OUT STRA& straUserName
  720. )
  721. /*++
  722. Routine Description:
  723. Breaks up the supplied account into a domain and username; if no domain
  724. is specified
  725. in the account, tries to use either domain configured in metabase or
  726. domain the computer
  727. is a part of.
  728. Arguments:
  729. straFullName - account, of the form domain\username or just username
  730. straMetabaseConfiguredDomain - auth domain configured in metabase
  731. straDomainName - filled in with domain to use for authentication
  732. straUserName - filled in with username on success
  733. Return Value:
  734. HRESULT
  735. --*/
  736. {
  737. PCHAR pszSeparator = NULL;
  738. HRESULT hr = E_FAIL;
  739. if( pszFullName == NULL && pszFullName[0] == '\0' )
  740. {
  741. return HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER );
  742. }
  743. pszSeparator = (PCHAR) _mbschr( (PUCHAR) pszFullName, '\\' );
  744. if ( pszSeparator != NULL )
  745. {
  746. if ( FAILED( hr = straDomainName.Copy ( pszFullName,
  747. DIFF( pszSeparator - pszFullName ) ) ) )
  748. {
  749. return hr;
  750. }
  751. pszFullName = pszSeparator + 1;
  752. }
  753. else
  754. {
  755. straDomainName.Reset();
  756. }
  757. if ( FAILED( hr = straUserName.Copy ( pszFullName ) ) )
  758. {
  759. return hr;
  760. }
  761. //
  762. // If no domain name was specified, try using the metabase-configured domain name; if that
  763. // is non-existent, try getting the name of the domain the computer is a part of
  764. //
  765. if ( straDomainName.IsEmpty() )
  766. {
  767. if ( straMetabaseConfiguredDomain.IsEmpty() )
  768. {
  769. if ( FAILED( hr = straDomainName.Copy ( QueryComputerDomain() ) ) )
  770. {
  771. return hr;
  772. }
  773. }
  774. else
  775. {
  776. if ( FAILED( hr = straDomainName.Copy ( straMetabaseConfiguredDomain ) ) )
  777. {
  778. return hr;
  779. }
  780. }
  781. }
  782. return NO_ERROR;
  783. }
  784. //
  785. // class IIS_DIGEST_USER_CONTEXT implementation
  786. //
  787. HANDLE
  788. IIS_DIGEST_USER_CONTEXT::QueryPrimaryToken(
  789. VOID
  790. )
  791. /*++
  792. Routine Description:
  793. Get primary token for this user
  794. Arguments:
  795. None
  796. Return Value:
  797. Token handle
  798. --*/
  799. {
  800. DBG_ASSERT( _hImpersonationToken != NULL );
  801. if ( _hPrimaryToken == NULL )
  802. {
  803. if ( DuplicateTokenEx( _hImpersonationToken,
  804. TOKEN_ALL_ACCESS,
  805. NULL,
  806. SecurityImpersonation,
  807. TokenPrimary,
  808. &_hPrimaryToken ) )
  809. {
  810. DBG_ASSERT( _hPrimaryToken != NULL );
  811. }
  812. }
  813. return _hPrimaryToken;
  814. }
  815. HRESULT
  816. IIS_DIGEST_USER_CONTEXT::Create(
  817. IN HANDLE hImpersonationToken,
  818. IN PSTR pszUserName
  819. )
  820. /*++
  821. Routine Description:
  822. Create an user context
  823. Arguments:
  824. Return Value:
  825. HRESULT
  826. --*/
  827. {
  828. HRESULT hr = E_FAIL;
  829. DBG_ASSERT( pszUserName != NULL );
  830. DBG_ASSERT( hImpersonationToken != NULL );
  831. if ( hImpersonationToken == NULL ||
  832. pszUserName == NULL )
  833. {
  834. return HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER );
  835. }
  836. _hImpersonationToken = hImpersonationToken;
  837. if ( FAILED( hr = _strUserName.CopyA(pszUserName) ) )
  838. {
  839. return hr;
  840. }
  841. return NO_ERROR;
  842. }
  843. //
  844. // Class IIS_DIGEST_CONN_CONTEXT implementation
  845. //
  846. // Initialize static variables
  847. //static
  848. ALLOC_CACHE_HANDLER * IIS_DIGEST_CONN_CONTEXT::sm_pachIISDIGESTConnContext = NULL;
  849. //static
  850. const PCHAR IIS_DIGEST_CONN_CONTEXT::_pszSecret = "IISMD5";
  851. //static
  852. const DWORD IIS_DIGEST_CONN_CONTEXT::_cchSecret = 6;
  853. //static
  854. HCRYPTPROV IIS_DIGEST_CONN_CONTEXT::s_hCryptProv = NULL;
  855. //static
  856. HRESULT
  857. IIS_DIGEST_CONN_CONTEXT::Initialize(
  858. VOID
  859. )
  860. /*++
  861. Description:
  862. Global IIS_DIGEST_CONN_CONTEXT initialization
  863. Arguments:
  864. None
  865. Return Value:
  866. HRESULT
  867. --*/
  868. {
  869. ALLOC_CACHE_CONFIGURATION acConfig;
  870. //
  871. // Initialize allocation lookaside
  872. //
  873. acConfig.nConcurrency = 1;
  874. acConfig.nThreshold = 100;
  875. acConfig.cbSize = sizeof( IIS_DIGEST_CONN_CONTEXT );
  876. DBG_ASSERT( sm_pachIISDIGESTConnContext == NULL );
  877. sm_pachIISDIGESTConnContext = new ALLOC_CACHE_HANDLER(
  878. "IIS_DIGEST_CONTEXT",
  879. &acConfig );
  880. if ( sm_pachIISDIGESTConnContext == NULL )
  881. {
  882. HRESULT hr = HRESULT_FROM_WIN32( ERROR_NOT_ENOUGH_MEMORY );
  883. DBGPRINTF(( DBG_CONTEXT,
  884. "Error initializing sm_pachIISDIGESTSecContext. hr = 0x%x\n",
  885. hr ));
  886. return hr;
  887. }
  888. //
  889. // Get a handle to the CSP we'll use for all our hash functions etc
  890. //
  891. if ( !CryptAcquireContext( &s_hCryptProv,
  892. NULL,
  893. NULL,
  894. PROV_RSA_FULL,
  895. CRYPT_VERIFYCONTEXT ) )
  896. {
  897. HRESULT hr = HRESULT_FROM_WIN32( GetLastError() );
  898. DBGPRINTF((DBG_CONTEXT,
  899. "CryptAcquireContext() failed : 0x%x\n", GetLastError()));
  900. return hr;
  901. }
  902. return S_OK;
  903. }
  904. //static
  905. VOID
  906. IIS_DIGEST_CONN_CONTEXT::Terminate(
  907. VOID
  908. )
  909. /*++
  910. Routine Description:
  911. Destroy globals
  912. Arguments:
  913. None
  914. Return Value:
  915. None
  916. --*/
  917. {
  918. DBG_ASSERT( sm_pachIISDIGESTConnContext != NULL );
  919. delete sm_pachIISDIGESTConnContext;
  920. sm_pachIISDIGESTConnContext = NULL;
  921. if ( s_hCryptProv != NULL )
  922. {
  923. CryptReleaseContext( s_hCryptProv,
  924. 0 );
  925. s_hCryptProv = NULL;
  926. }
  927. }
  928. //static
  929. HRESULT
  930. IIS_DIGEST_CONN_CONTEXT::HashData(
  931. IN BUFFER& buffData,
  932. OUT BUFFER& buffHash )
  933. /*++
  934. Routine Description:
  935. Creates MD5 hash of input buffer
  936. Arguments:
  937. buffData - data to hash
  938. buffHash - buffer that receives hash; is assumed to be big enough to
  939. contain MD5 hash
  940. Return Value:
  941. HRESULT
  942. --*/
  943. {
  944. HCRYPTHASH hHash = NULL;
  945. HRESULT hr = E_FAIL;
  946. DWORD cbHash = 0;
  947. DBG_ASSERT( buffHash.QuerySize() >= MD5_HASH_SIZE );
  948. if ( !CryptCreateHash( s_hCryptProv,
  949. CALG_MD5,
  950. 0,
  951. 0,
  952. &hHash ) )
  953. {
  954. //DBGPRINTF((DBG_CONTEXT,
  955. // "CryptCreateHash() failed : 0x%x\n", GetLastError()));
  956. hr = HRESULT_FROM_WIN32( GetLastError() );
  957. goto ExitPoint;
  958. }
  959. if ( !CryptHashData( hHash,
  960. (PBYTE) buffData.QueryPtr(),
  961. buffData.QuerySize(),
  962. 0 ) )
  963. {
  964. hr = HRESULT_FROM_WIN32( GetLastError() );
  965. goto ExitPoint;
  966. }
  967. cbHash = buffHash.QuerySize();
  968. if ( !CryptGetHashParam( hHash,
  969. HP_HASHVAL,
  970. (PBYTE) buffHash.QueryPtr(),
  971. &cbHash,
  972. 0 ) )
  973. {
  974. hr = HRESULT_FROM_WIN32( GetLastError() );
  975. goto ExitPoint;
  976. }
  977. hr = NO_ERROR;
  978. ExitPoint:
  979. if ( hHash != NULL )
  980. {
  981. CryptDestroyHash( hHash );
  982. }
  983. return hr;
  984. }
  985. //static
  986. BOOL
  987. IIS_DIGEST_CONN_CONTEXT::IsExpiredNonce(
  988. IN STRA& strRequestNonce,
  989. IN STRA& strPresentNonce
  990. )
  991. /*++
  992. Routine Description:
  993. Checks whether nonce is expired or not by looking at the timestamp on the
  994. nonce
  995. that came in with the request and comparing it with the timestamp on the
  996. latest nonce
  997. Arguments:
  998. strRequestNonce - nonce that came in with request
  999. strPresentNonce - latest nonce
  1000. Return Value:
  1001. TRUE if expired, FALSE if not
  1002. --*/
  1003. {
  1004. //
  1005. // Timestamp is after first 2*RANDOM_SIZE bytes of nonce; also, note that
  1006. // timestamp is time() mod NONCE_GRANULARITY, so all we have to do is simply
  1007. // compare for equality to check that the request nonce hasn't expired
  1008. //
  1009. DBG_ASSERT( strRequestNonce.QueryCCH() >= 2*RANDOM_SIZE + TIMESTAMP_SIZE );
  1010. DBG_ASSERT( strPresentNonce.QueryCCH() >= 2*RANDOM_SIZE + TIMESTAMP_SIZE );
  1011. if ( memcmp( strRequestNonce.QueryStr() + 2*RANDOM_SIZE,
  1012. strPresentNonce.QueryStr() + 2*RANDOM_SIZE,
  1013. TIMESTAMP_SIZE ) != 0 )
  1014. {
  1015. return TRUE;
  1016. }
  1017. return FALSE;
  1018. }
  1019. //static
  1020. BOOL
  1021. IIS_DIGEST_CONN_CONTEXT::IsWellFormedNonce(
  1022. IN STRA& strNonce
  1023. )
  1024. /*++
  1025. Routine Description:
  1026. Checks whether a nonce is "well-formed" by checking hash value, length etc
  1027. Arguments:
  1028. pszNonce - nonce to be checked
  1029. Return Value:
  1030. TRUE if nonce is well-formed, FALSE if not
  1031. --*/
  1032. {
  1033. if ( strNonce.QueryCCH()!= NONCE_SIZE )
  1034. {
  1035. return FALSE;
  1036. }
  1037. //
  1038. // Format of nonce : <random bytes><time stamp><hash of (secret,random bytes,time stamp)>
  1039. //
  1040. STACK_BUFFER( buffBuffer, 2*RANDOM_SIZE + TIMESTAMP_SIZE + _cchSecret );
  1041. STACK_BUFFER( buffHash, MD5_HASH_SIZE );
  1042. STACK_STRA( strAsciiHash, 2*MD5_HASH_SIZE + 1 );
  1043. memcpy( buffBuffer.QueryPtr(),
  1044. _pszSecret,
  1045. _cchSecret );
  1046. memcpy( (PBYTE) buffBuffer.QueryPtr() + _cchSecret,
  1047. strNonce.QueryStr(),
  1048. 2*RANDOM_SIZE + TIMESTAMP_SIZE );
  1049. if ( FAILED( HashData( buffBuffer,
  1050. buffHash ) ) )
  1051. {
  1052. return FALSE;
  1053. }
  1054. ToHex( buffHash,
  1055. strAsciiHash );
  1056. if ( memcmp( strAsciiHash.QueryStr(),
  1057. strNonce.QueryStr() + 2*RANDOM_SIZE + TIMESTAMP_SIZE,
  1058. 2*MD5_HASH_SIZE ) != 0)
  1059. {
  1060. return FALSE;
  1061. }
  1062. return TRUE;
  1063. }
  1064. HRESULT
  1065. IIS_DIGEST_CONN_CONTEXT::GenerateNonce(
  1066. VOID
  1067. )
  1068. /*++
  1069. Routine Description:
  1070. Generate nonce to be stored in user filter context. Nonce is
  1071. <ASCII rep of Random><Time><ASCII of MD5(Secret:Random:Time)>
  1072. Random = <8 random bytes>
  1073. Time = <16 bytes, reverse string rep of result of time() call>
  1074. Secret = 'IISMD5'
  1075. Arguments:
  1076. none
  1077. Return Value:
  1078. HRESULT
  1079. --*/
  1080. {
  1081. HRESULT hr = E_FAIL;
  1082. DWORD tNow = (DWORD) ( time( NULL ) / NONCE_GRANULARITY );
  1083. //
  1084. // If nonce has timed out, generate a new one
  1085. //
  1086. if ( _tLastNonce < tNow )
  1087. {
  1088. STACK_BUFFER( buffTempBuffer, 2*RANDOM_SIZE + TIMESTAMP_SIZE + _cchSecret );
  1089. STACK_BUFFER( buffDigest, MD5_HASH_SIZE );
  1090. STACK_BUFFER( buffRandom, RANDOM_SIZE );
  1091. STACK_STRA( strTimeStamp, TIMESTAMP_SIZE + 1 );
  1092. STACK_STRA( strAsciiDigest, 2*MD5_HASH_SIZE + 1 );
  1093. STACK_STRA( strAsciiRandom, 2*RANDOM_SIZE + 1);
  1094. DWORD cbTimeStamp = 0;
  1095. PSTR pszTimeStamp = NULL;
  1096. _tLastNonce = tNow;
  1097. //
  1098. // First, random bytes
  1099. //
  1100. if ( !CryptGenRandom( s_hCryptProv,
  1101. RANDOM_SIZE,
  1102. (PBYTE) buffRandom.QueryPtr() ) )
  1103. {
  1104. hr = HRESULT_FROM_WIN32( GetLastError() );
  1105. goto ExitPoint;
  1106. }
  1107. //
  1108. // Convert to ASCII, doubling the length, and add to nonce
  1109. //
  1110. ToHex( buffRandom,
  1111. strAsciiRandom );
  1112. if ( FAILED( hr = _straNonce.Copy( strAsciiRandom ) ) )
  1113. {
  1114. goto ExitPoint;
  1115. }
  1116. //
  1117. // Next, reverse string representation of current time; pad with zeros if necessary
  1118. //
  1119. pszTimeStamp = strTimeStamp.QueryStr();
  1120. DBG_ASSERT( pszTimeStamp != NULL );
  1121. while ( tNow != 0 )
  1122. {
  1123. *(pszTimeStamp++) = (BYTE)( '0' + tNow % 10 );
  1124. cbTimeStamp++;
  1125. tNow /= 10;
  1126. }
  1127. DBG_ASSERT( cbTimeStamp <= TIMESTAMP_SIZE );
  1128. //
  1129. // pad with zeros if necessary
  1130. //
  1131. while ( cbTimeStamp < TIMESTAMP_SIZE )
  1132. {
  1133. *(pszTimeStamp++) = '0';
  1134. cbTimeStamp++;
  1135. }
  1136. //
  1137. // terminate the timestamp
  1138. //
  1139. *(pszTimeStamp) = '\0';
  1140. DBG_REQUIRE( strTimeStamp.SetLen( cbTimeStamp ) );
  1141. //
  1142. // Append TimeStamp to Nonce
  1143. //
  1144. if ( FAILED( hr = _straNonce.Append( strTimeStamp ) ) )
  1145. {
  1146. goto ExitPoint;
  1147. }
  1148. //
  1149. // Now hash everything, together with a private key ( IISMD5 )
  1150. //
  1151. memcpy( buffTempBuffer.QueryPtr(),
  1152. _pszSecret,
  1153. _cchSecret );
  1154. memcpy( (PBYTE) buffTempBuffer.QueryPtr() + _cchSecret,
  1155. _straNonce.QueryStr(),
  1156. 2*RANDOM_SIZE + TIMESTAMP_SIZE );
  1157. DBG_ASSERT( buffTempBuffer.QuerySize() == 2*RANDOM_SIZE + TIMESTAMP_SIZE + _cchSecret );
  1158. if ( FAILED( hr = HashData( buffTempBuffer,
  1159. buffDigest ) ) )
  1160. {
  1161. goto ExitPoint;
  1162. }
  1163. //
  1164. // Convert to ASCII, doubling the length
  1165. //
  1166. DBG_ASSERT( buffDigest.QuerySize() == MD5_HASH_SIZE );
  1167. ToHex( buffDigest,
  1168. strAsciiDigest );
  1169. //
  1170. // Add hash to nonce
  1171. //
  1172. if ( FAILED( hr = _straNonce.Append( strAsciiDigest ) ) )
  1173. {
  1174. goto ExitPoint;
  1175. }
  1176. }
  1177. hr = NO_ERROR;
  1178. ExitPoint:
  1179. return hr;
  1180. }
  1181. //static
  1182. BOOL
  1183. IIS_DIGEST_CONN_CONTEXT::ParseForName(
  1184. IN PSTR pszStr,
  1185. IN PSTR * pNameTable,
  1186. IN UINT cNameTable,
  1187. OUT PSTR * pValueTable
  1188. )
  1189. /*++
  1190. Routine Description:
  1191. Parse list of name=value pairs for known names
  1192. Arguments:
  1193. pszStr - line to parse ( '\0' delimited )
  1194. pNameTable - table of known names
  1195. cNameTable - number of known names
  1196. pValueTable - updated with ptr to parsed value for corresponding name
  1197. Return Value:
  1198. TRUE if success, FALSE if error
  1199. --*/
  1200. {
  1201. BOOL fSt = TRUE;
  1202. PSTR pszBeginName;
  1203. PSTR pszEndName;
  1204. PSTR pszBeginVal;
  1205. PSTR pszEndVal;
  1206. UINT iN;
  1207. int ch;
  1208. DBG_ASSERT( pszStr!= NULL );
  1209. for ( iN = 0 ; iN < cNameTable ; ++iN )
  1210. {
  1211. pValueTable[iN] = NULL;
  1212. }
  1213. for ( ; *pszStr && fSt ; )
  1214. {
  1215. pszStr = SkipWhite( pszStr );
  1216. pszBeginName = pszStr;
  1217. for ( pszEndName = pszStr ; (ch=*pszEndName) && ch != '=' && ch != ' ' ; ++pszEndName )
  1218. {
  1219. }
  1220. if ( *pszEndName )
  1221. {
  1222. *pszEndName = '\0';
  1223. pszEndVal = NULL;
  1224. if ( !_stricmp( pszBeginName, "NC" ) )
  1225. {
  1226. for ( pszBeginVal = ++pszEndName ; (ch=*pszBeginVal) && !SAFEIsXDigit((UCHAR)ch) ; ++pszBeginVal )
  1227. {
  1228. }
  1229. if ( SAFEIsXDigit((UCHAR)(*pszBeginVal)) )
  1230. {
  1231. if ( strlen( pszBeginVal ) >= 8 )
  1232. {
  1233. pszEndVal = pszBeginVal + 8;
  1234. }
  1235. }
  1236. }
  1237. else
  1238. {
  1239. //
  1240. // Actually this routine is not compatible with rfc2617 at all It treats all
  1241. // values as quoted string which is not right. To fix the whole parsing problem,
  1242. // we will need to rewrite the routine. As for now, the following is a simple
  1243. // fix for whistler bug 95886.
  1244. //
  1245. if ( !_stricmp( pszBeginName, "qop" ) )
  1246. {
  1247. BOOL fQuotedQop = FALSE;
  1248. for( pszBeginVal = ++pszEndName; ( ch=*pszBeginVal ) && ( ch == '=' || ch == ' ' ); ++pszBeginVal )
  1249. {
  1250. }
  1251. if ( *pszBeginVal == '"' )
  1252. {
  1253. ++pszBeginVal;
  1254. fQuotedQop = TRUE;
  1255. }
  1256. for ( pszEndVal = pszBeginVal; ( ch = *pszEndVal ); ++pszEndVal )
  1257. {
  1258. if ( ch == '"' || ch == ' ' || ch == ',' || ch == '\0' )
  1259. {
  1260. break;
  1261. }
  1262. }
  1263. if ( *pszEndVal != '"' && fQuotedQop )
  1264. {
  1265. pszEndVal = NULL;
  1266. }
  1267. }
  1268. else
  1269. {
  1270. for ( pszBeginVal = ++pszEndName ; (ch=*pszBeginVal) && ch != '"' ; ++pszBeginVal )
  1271. {
  1272. }
  1273. if ( *pszBeginVal == '"' )
  1274. {
  1275. ++pszBeginVal;
  1276. for ( pszEndVal = pszBeginVal ; (ch=*pszEndVal) ; ++pszEndVal )
  1277. {
  1278. if ( ch == '"' )
  1279. {
  1280. break;
  1281. }
  1282. }
  1283. if ( *pszEndVal != '"' )
  1284. {
  1285. pszEndVal = NULL;
  1286. }
  1287. }
  1288. }
  1289. }
  1290. if ( pszEndVal != NULL )
  1291. {
  1292. //
  1293. // Find name in table
  1294. //
  1295. for ( iN = 0 ; iN < cNameTable ; ++iN )
  1296. {
  1297. if ( !_stricmp( pNameTable[iN], pszBeginName ) )
  1298. {
  1299. break;
  1300. }
  1301. }
  1302. if ( iN < cNameTable )
  1303. {
  1304. pValueTable[iN] = pszBeginVal;
  1305. }
  1306. pszStr = pszEndVal;
  1307. if ( *pszEndVal != '\0' )
  1308. {
  1309. *pszEndVal = '\0';
  1310. pszStr++;
  1311. }
  1312. continue;
  1313. }
  1314. }
  1315. fSt = FALSE;
  1316. }
  1317. return fSt;
  1318. }