Leaked source code of windows server 2003
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

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