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.

1140 lines
33 KiB

  1. /*
  2. ****************************************************************************
  3. | Copyright (C) 2002 Microsoft Corporation
  4. |
  5. | Component / Subcomponent
  6. | IIS 6.0 / IIS Migration Wizard
  7. |
  8. | Based on:
  9. | http://iis6/Specs/IIS%20Migration6.0_Final.doc
  10. |
  11. | Abstract:
  12. | IIS Metabase access classes implementation
  13. |
  14. | Author:
  15. | ivelinj
  16. |
  17. | Revision History:
  18. | V1.00 March 2002
  19. |
  20. ****************************************************************************
  21. */
  22. #include "StdAfx.h"
  23. #include "IISHelpers.h"
  24. #include "Utils.h"
  25. #include "resource.h"
  26. // CIISSite implementation
  27. //////////////////////////////////////////////////////////////////////////////////
  28. CIISSite::CIISSite( ULONG nSiteID, bool bReadOnly /*= true */ )
  29. {
  30. IMSAdminBasePtr spIABO;
  31. METADATA_HANDLE hSite = NULL;
  32. // Create the ABO
  33. IF_FAILED_HR_THROW( spIABO.CreateInstance( CLSID_MSAdminBase ),
  34. CObjectException( IDS_E_CREATEINSTANCE, L"CLSID_MSAdminBase" ) );
  35. // Open the site
  36. WCHAR wszSitePath[ MAX_PATH + 1 ];
  37. ::swprintf( wszSitePath, L"LM/W3SVC/%u", nSiteID );
  38. HRESULT hr = spIABO->OpenKey( METADATA_MASTER_ROOT_HANDLE,
  39. wszSitePath,
  40. bReadOnly ? METADATA_PERMISSION_READ : ( METADATA_PERMISSION_WRITE | METADATA_PERMISSION_READ ),
  41. KeyAccessTimeout,
  42. &hSite );
  43. if ( HRESULT_FROM_WIN32( ERROR_PATH_NOT_FOUND ) == hr )
  44. {
  45. throw CBaseException( IDS_E_WEBNOTFOUND, ERROR_SUCCESS );
  46. }
  47. else if ( FAILED( hr ) )
  48. {
  49. throw CBaseException( IDS_E_SITEOPEN, hr );
  50. }
  51. m_spIABO.Attach( spIABO.Detach() );
  52. m_hSiteHandle = hSite;
  53. }
  54. CIISSite::~CIISSite()
  55. {
  56. Close();
  57. }
  58. /*
  59. Creates a new empty web site entry in the metabase
  60. dwHint is the first ID to try.
  61. Returns the new SiteID
  62. */
  63. DWORD CIISSite::CreateNew( DWORD dwHint /*=1*/ )
  64. {
  65. // Creating new sites is available only on Server platforms
  66. _ASSERT( CTools::GetOSVer() & 1 );
  67. DWORD dwCurrentID = dwHint;
  68. WCHAR wszSitePath[ 64 ]; // Should be large enough for max DWORD value
  69. IMSAdminBasePtr spIABO;
  70. METADATA_HANDLE hData = NULL;
  71. // Create the ABO
  72. IF_FAILED_HR_THROW( spIABO.CreateInstance( CLSID_MSAdminBase ),
  73. CObjectException( IDS_E_CREATEINSTANCE, L"CLSID_MSAdminBase" ) );
  74. // Open the W3SVC path
  75. IF_FAILED_HR_THROW( spIABO->OpenKey( METADATA_MASTER_ROOT_HANDLE,
  76. L"LM/W3SVC",
  77. METADATA_PERMISSION_WRITE,
  78. KeyAccessTimeout,
  79. &hData ),
  80. CObjectException( IDS_E_METABASE, L"LM/W3SVC" ) );
  81. HRESULT hr = E_FAIL;
  82. do
  83. {
  84. ::swprintf( wszSitePath, L"%u", dwCurrentID++ );
  85. // Try to create this site id
  86. hr = spIABO->AddKey( hData, wszSitePath );
  87. }while( HRESULT_FROM_WIN32( ERROR_ALREADY_EXISTS ) == hr );
  88. VERIFY( SUCCEEDED( spIABO->CloseKey( hData ) ) );
  89. IF_FAILED_HR_THROW( hr, CObjectException( IDS_E_MD_ADDKEY, L"[NewSiteID]" ) );
  90. return dwCurrentID - 1;
  91. }
  92. void CIISSite::DeleteSite( DWORD dwSiteID )
  93. {
  94. // Do not try to delete the default web
  95. _ASSERT( dwSiteID > 1 );
  96. WCHAR wszSitePath[ 64 ];
  97. IMSAdminBasePtr spIABO;
  98. METADATA_HANDLE hData = NULL;
  99. // Create the ABO
  100. IF_FAILED_HR_THROW( spIABO.CreateInstance( CLSID_MSAdminBase ),
  101. CObjectException( IDS_E_CREATEINSTANCE, L"CLSID_MSAdminBase" ) );
  102. // Open the W3SVC path
  103. IF_FAILED_HR_THROW( spIABO->OpenKey( METADATA_MASTER_ROOT_HANDLE,
  104. L"LM/W3SVC",
  105. METADATA_PERMISSION_WRITE,
  106. KeyAccessTimeout,
  107. &hData ),
  108. CObjectException( IDS_E_METABASE, L"LM/W3SVC" ) );
  109. ::swprintf( wszSitePath, L"%u", dwSiteID );
  110. HRESULT hr = spIABO->DeleteKey( hData, wszSitePath );
  111. VERIFY( SUCCEEDED( spIABO->CloseKey( hData ) ) );
  112. if ( FAILED( hr ) && ( hr != HRESULT_FROM_WIN32( ERROR_PATH_NOT_FOUND ) ) )
  113. {
  114. throw CBaseException( IDS_E_METABASE_IO, hr );
  115. }
  116. }
  117. /*
  118. This methods writes the metabase data to the XML doc. spRoot is the XML element under which
  119. the data should be written. hEncryptKey is used to encrypt all secure properties.
  120. The crypt key may be null in which case the properties are exported in clear text ( that happens when
  121. the whole package will be encrypted )
  122. */
  123. void CIISSite::ExportConfig( const IXMLDOMDocumentPtr& spXMLDoc,
  124. const IXMLDOMElementPtr& spRoot,
  125. HCRYPTKEY hEncryptKey )const
  126. {
  127. _ASSERT( spRoot != NULL );
  128. _ASSERT( m_hSiteHandle != NULL ); // Site must be opened first
  129. _ASSERT( m_spIABO != NULL );
  130. IXMLDOMElementPtr spMDRoot;
  131. IXMLDOMElementPtr spInheritRoot;
  132. // We will put the metadata under the XML tag <Metadata>
  133. // Under this node we will have <IISConfigObject> for each metakey under the site's root key
  134. // We will have one tag <MD_Inherit> bellow <Metadata>, where all the inheritable properties will be written
  135. // ( using the <Custom> tag )
  136. // Create our root node for metadata and the root node for inheritable data
  137. spMDRoot = CXMLTools::CreateSubNode( spXMLDoc, spRoot, L"Metadata" );
  138. spInheritRoot = CXMLTools::CreateSubNode( spXMLDoc, spMDRoot, L"MD_Inherit" );
  139. // We need a buffer for reading the metadata for each key
  140. // As there might be a lot of keys instead of allocating the buffer for each key
  141. // we will allocate it here and the ExportKey method will use it
  142. // ( ExportKey may modify the buffer. i.e. nake it bigger )
  143. DWORD dwDefaultBufferSize = 4 * 1024;
  144. TByteAutoPtr spBuffer( new BYTE[ dwDefaultBufferSize ] );
  145. // Export the current site config. This will export any subnodes as well
  146. // NOTE: this will not export inherited data
  147. ExportKey( spXMLDoc, spMDRoot, hEncryptKey, L"", /*r*/spBuffer, /*r*/dwDefaultBufferSize );
  148. // Export ONLY the inheritable data
  149. // This will export all the data that the Site metadata key ( LM/W3SVC/### ) inherits from it's
  150. // parent ( LM/W3SVC )
  151. ExportInheritData( spXMLDoc, spInheritRoot, hEncryptKey, /*r*/spBuffer, /*r*/dwDefaultBufferSize );
  152. // Remove any non-exportable data
  153. RemoveLocalMetadata( spRoot );
  154. }
  155. /*
  156. Exports the site's SSL certificate into the provided smart pointer
  157. Caller must check first if the site has SSL certificate with HaveCertificate method
  158. */
  159. void CIISSite::ExportCert( const IXMLDOMDocumentPtr& spDoc,
  160. const IXMLDOMElementPtr& spRoot,
  161. LPCWSTR wszPassword )const
  162. {
  163. _ASSERT( m_spIABO != NULL );
  164. _ASSERT( m_hSiteHandle != NULL );
  165. _ASSERT( spRoot != NULL );
  166. _ASSERT( spDoc != NULL );
  167. _ASSERT( wszPassword != NULL );
  168. _ASSERT( HaveCertificate() );
  169. TCertContextHandle shCert( GetCert() );
  170. // Create a certificate store in memory. We will put the certificate in this mem store
  171. // and then export this mem store which will export the certificate as well
  172. TCertStoreHandle shMemStore( ::CertOpenStore( CERT_STORE_PROV_MEMORY,
  173. 0,
  174. 0,
  175. CERT_STORE_READONLY_FLAG,
  176. NULL ) );
  177. IF_FAILED_BOOL_THROW( shMemStore.IsValid(),
  178. CBaseException( IDS_E_OPEN_CERT_STORE ) );
  179. // Add our certificate to the mem store
  180. IF_FAILED_BOOL_THROW( ::CertAddCertificateContextToStore( shMemStore.get(),
  181. shCert.get(),
  182. CERT_STORE_ADD_REPLACE_EXISTING,
  183. NULL ),
  184. CBaseException( IDS_E_ADD_CERT_STORE ) );
  185. // Add the certificate chain to the store
  186. // ( the certificate chain is all the certificates starting from this certificate up to a trusted,
  187. // self-signed certificate. We need to do this, as the root certificate may not be trusted on the target
  188. // machine and this will render the SSL certificate invalid ( not trusted )
  189. ChainCertificate( shCert.get(), shMemStore.get() );
  190. CRYPT_DATA_BLOB Data = { 0 };
  191. // Get the size of the encrypted data
  192. ::PFXExportCertStoreEx( shMemStore.get(),
  193. &Data,
  194. wszPassword,
  195. NULL,
  196. EXPORT_PRIVATE_KEYS );
  197. _ASSERT( Data.cbData > 0 );
  198. // Alloc the space end get the data
  199. TByteAutoPtr spData = TByteAutoPtr( new BYTE[ Data.cbData ] );
  200. Data.pbData = spData.get();
  201. IF_FAILED_BOOL_THROW( ::PFXExportCertStoreEx( shMemStore.get(),
  202. &Data,
  203. wszPassword,
  204. NULL,
  205. EXPORT_PRIVATE_KEYS | REPORT_NOT_ABLE_TO_EXPORT_PRIVATE_KEY ),
  206. CBaseException( IDS_E_EXPORT_CERT ) );
  207. // Create the XML element to hold the certificate data
  208. CXMLTools::AddTextNode( spDoc,
  209. spRoot,
  210. L"Certificate",
  211. Convert::ToString( spData.get(), Data.cbData ).c_str() );
  212. }
  213. void CIISSite::ImportConfig( const IXMLDOMNodePtr& spSite, HCRYPTKEY hDecryptKey, bool bImportInherited )const
  214. {
  215. _ASSERT( spSite != NULL );
  216. IXMLDOMNodeListPtr spPaths;
  217. IXMLDOMNodePtr spConfig;
  218. IXMLDOMNodeListPtr spValueList;
  219. IXMLDOMNodePtr spValue;
  220. IF_FAILED_HR_THROW( spSite->selectNodes( _bstr_t( L"Metadata/IISConfigObject" ), &spPaths ),
  221. CBaseException( IDS_E_XML_PARSE ) );
  222. while( S_OK == spPaths->nextNode( &spConfig ) )
  223. {
  224. std::wstring strLocation = CXMLTools::GetAttrib( spConfig, L"Location" );
  225. if ( !strLocation.empty() )
  226. {
  227. AddKey( strLocation.c_str() );
  228. }
  229. // Import every <Custom> tag in this Config object
  230. IF_FAILED_HR_THROW( spConfig->selectNodes( _bstr_t( L"Custom" ), &spValueList ),
  231. CBaseException( IDS_E_XML_PARSE ) );
  232. while( S_OK == spValueList->nextNode( &spValue ) )
  233. {
  234. ImportMetaValue( spValue, strLocation.c_str(), hDecryptKey );
  235. }
  236. }
  237. // Import the inherited values
  238. if ( bImportInherited )
  239. {
  240. IF_FAILED_HR_THROW( spSite->selectNodes( _bstr_t( L"Metadata/MD_Inherit" ), &spValueList ),
  241. CBaseException( IDS_E_XML_PARSE ) );
  242. while( S_OK == spValueList->nextNode( &spValue ) )
  243. {
  244. ImportMetaValue( spValue, NULL, hDecryptKey );
  245. };
  246. }
  247. }
  248. /*
  249. Copies currrent site name into wszName
  250. wszName must be at least METADATA_MAX_NAME_LEN + 1
  251. */
  252. const std::wstring CIISSite::GetDisplayName()const
  253. {
  254. _ASSERT( m_hSiteHandle != NULL );
  255. _ASSERT( m_spIABO != NULL );
  256. DWORD dwUnsued = 0;
  257. WCHAR wszBuffer[ METADATA_MAX_NAME_LEN + 1 ];
  258. METADATA_RECORD md = { 0 };
  259. md.dwMDAttributes = 0;
  260. md.dwMDIdentifier = MD_SERVER_COMMENT;
  261. md.dwMDDataType = ALL_METADATA;
  262. md.dwMDDataLen = ( METADATA_MAX_NAME_LEN + 1 ) * sizeof( WCHAR );
  263. md.pbMDData = reinterpret_cast<BYTE*>( wszBuffer );
  264. IF_FAILED_HR_THROW( m_spIABO->GetData( m_hSiteHandle, NULL, &md, &dwUnsued ),
  265. CBaseException( IDS_E_METABASE_IO ) );
  266. return std::wstring( wszBuffer );
  267. }
  268. const std::wstring CIISSite::GetAnonUser()const
  269. {
  270. _ASSERT( m_spIABO != NULL );
  271. _ASSERT( m_hSiteHandle != NULL );
  272. DWORD dwSize = 0;
  273. METADATA_RECORD md = { 0 };
  274. md.dwMDAttributes = METADATA_INHERIT;
  275. md.dwMDIdentifier = MD_ANONYMOUS_USER_NAME;
  276. md.dwMDDataType = ALL_METADATA;
  277. md.dwMDDataLen = 0;
  278. md.pbMDData = NULL;
  279. VERIFY( FAILED( m_spIABO->GetData( m_hSiteHandle, NULL, &md, &dwSize ) ) );
  280. _ASSERT( dwSize > 0 );
  281. std::auto_ptr<WCHAR> spBuffer( new WCHAR[ dwSize / sizeof( WCHAR ) ] );
  282. md.dwMDDataLen = dwSize;
  283. md.pbMDData = reinterpret_cast<BYTE*>( spBuffer.get() );
  284. IF_FAILED_HR_THROW( m_spIABO->GetData( m_hSiteHandle, NULL, &md, &dwSize ),
  285. CBaseException( IDS_E_METABASE_IO ) );
  286. return std::wstring( spBuffer.get() );
  287. }
  288. /*
  289. Checks if the current IIS Site has a SSL certificate
  290. */
  291. bool CIISSite::HaveCertificate()const
  292. {
  293. _ASSERT( m_spIABO != NULL );
  294. _ASSERT( m_hSiteHandle != NULL );
  295. // Get the cert hash value from the metabase
  296. METADATA_RECORD md = { 0 };
  297. DWORD dwHashSize = 0;
  298. md.dwMDDataType = ALL_METADATA;
  299. md.dwMDIdentifier = MD_SSL_CERT_HASH;
  300. // Do not get the data - just check if it is there
  301. HRESULT hr = m_spIABO->GetData( m_hSiteHandle,
  302. NULL,
  303. &md,
  304. &dwHashSize );
  305. // If the data is not found - no SSL cert
  306. if ( FAILED( hr ) )
  307. {
  308. if ( MD_ERROR_DATA_NOT_FOUND == hr )
  309. {
  310. return false;
  311. }
  312. else if( hr != HRESULT_FROM_WIN32( ERROR_INSUFFICIENT_BUFFER ) )
  313. {
  314. // Unexpected error
  315. throw CBaseException( IDS_E_METABASE_IO, hr );
  316. }
  317. else
  318. {
  319. return true;
  320. }
  321. }
  322. // We can't get here - the above call should always fail
  323. _ASSERT( false );
  324. return false;
  325. }
  326. void CIISSite::BackupMetabase( LPCWSTR wszLocation /*=NULL */ )
  327. {
  328. IMSAdminBasePtr spIABO;
  329. IF_FAILED_HR_THROW( spIABO.CreateInstance( CLSID_MSAdminBase ),
  330. CObjectException( IDS_E_CREATEINSTANCE, L"CLSID_MSAdminBase" ) );
  331. IF_FAILED_HR_THROW( spIABO->Backup( wszLocation,
  332. MD_BACKUP_NEXT_VERSION,
  333. MD_BACKUP_SAVE_FIRST ),
  334. CBaseException( IDS_E_MDBACKUP ) );
  335. }
  336. void CIISSite::SetKeyData( LPCWSTR wszPath, DWORD dwID, DWORD dwUserType, LPCWSTR wszData )const
  337. {
  338. _ASSERT( wszData != NULL );
  339. _ASSERT( m_hSiteHandle != NULL );
  340. _ASSERT( m_spIABO != NULL );
  341. METADATA_RECORD md = { 0 };
  342. md.dwMDIdentifier = dwID;
  343. md.dwMDDataType = STRING_METADATA;
  344. md.dwMDUserType = dwUserType;
  345. md.dwMDDataLen = static_cast<DWORD>( ( ::wcslen( wszData ) + 1 ) * sizeof( WCHAR ) );
  346. md.pbMDData = ( BYTE* )( wszData );
  347. IF_FAILED_HR_THROW( m_spIABO->SetData( m_hSiteHandle, wszPath, &md ),
  348. CBaseException( IDS_E_METABASE_IO ) );
  349. }
  350. const std::wstring CIISSite::GetDefaultAnonUser()
  351. {
  352. DWORD dwSize = 0;
  353. METADATA_RECORD md = { 0 };
  354. md.dwMDAttributes = METADATA_INHERIT;
  355. md.dwMDIdentifier = MD_ANONYMOUS_USER_NAME;
  356. md.dwMDDataType = ALL_METADATA;
  357. md.dwMDDataLen = 0;
  358. md.pbMDData = NULL;
  359. IMSAdminBasePtr spIABO;
  360. METADATA_HANDLE hKey = NULL;
  361. // Create the ABO
  362. IF_FAILED_HR_THROW( spIABO.CreateInstance( CLSID_MSAdminBase ),
  363. CObjectException( IDS_E_CREATEINSTANCE, L"CLSID_MSAdminBase" ) );
  364. IF_FAILED_HR_THROW( spIABO->OpenKey( METADATA_MASTER_ROOT_HANDLE,
  365. _bstr_t( L"LM/W3SVC" ),
  366. METADATA_PERMISSION_READ,
  367. KeyAccessTimeout,
  368. &hKey ),
  369. CBaseException( IDS_E_METABASE_IO ) );
  370. VERIFY( FAILED( spIABO->GetData( hKey, NULL, &md, &dwSize ) ) );
  371. _ASSERT( dwSize > 0 );
  372. std::auto_ptr<WCHAR> spBuffer( new WCHAR[ dwSize / sizeof( WCHAR ) ] );
  373. md.dwMDDataLen = dwSize;
  374. md.pbMDData = reinterpret_cast<BYTE*>( spBuffer.get() );
  375. HRESULT hr = spIABO->GetData( hKey, NULL, &md, &dwSize );
  376. VERIFY( SUCCEEDED( spIABO->CloseKey( hKey ) ) );
  377. IF_FAILED_HR_THROW( hr, CBaseException( IDS_E_METABASE_IO ) );
  378. return std::wstring( spBuffer.get() );
  379. }
  380. // Implementation
  381. /////////////////////////////////////////////////////////////////////////////////////////
  382. void CIISSite::ExportKey( const IXMLDOMDocumentPtr& spDoc,
  383. const IXMLDOMElementPtr& spRoot,
  384. HCRYPTKEY hCryptKey,
  385. LPCWSTR wszNodePath,
  386. TByteAutoPtr& rspBuffer,
  387. DWORD& rdwBufferSize )const
  388. {
  389. _ASSERT( m_hSiteHandle != NULL );
  390. _ASSERT( m_spIABO != NULL );
  391. _ASSERT( spDoc != NULL );
  392. _ASSERT( wszNodePath != NULL );
  393. _ASSERT( rdwBufferSize > 0 );
  394. _ASSERT( rspBuffer.get() != NULL );
  395. // Insert this key entry into the XML file
  396. IXMLDOMElementPtr spCurrentKey = CXMLTools::CreateSubNode( spDoc, spRoot, L"IISConfigObject" );
  397. CXMLTools::SetAttrib( spCurrentKey, L"Location", wszNodePath );
  398. WCHAR wszSubKey[ METADATA_MAX_NAME_LEN + 1 ];
  399. // Write this node data to the XML
  400. ExportKeyData( spDoc, spCurrentKey, hCryptKey, wszNodePath, /*r*/rspBuffer, /*r*/rdwBufferSize );
  401. // Enum any subkeys
  402. // They are not nested in the XML
  403. DWORD iKey = 0;
  404. while( true )
  405. {
  406. // Get the next keyname
  407. HRESULT hr = m_spIABO->EnumKeys( m_hSiteHandle, wszNodePath, wszSubKey, iKey++ );
  408. if ( FAILED( hr ) )
  409. {
  410. if ( HRESULT_FROM_WIN32( ERROR_NO_MORE_ITEMS ) == hr ) break;
  411. else throw CBaseException( IDS_E_METABASE_IO, hr );
  412. }
  413. WCHAR wszSubkeyPath[ METADATA_MAX_NAME_LEN + 1 ];
  414. ::swprintf( wszSubkeyPath, L"%s/%s", wszNodePath, wszSubKey );
  415. // Export subkeys of the current subkey
  416. ExportKey( spDoc, spRoot, hCryptKey, wszSubkeyPath, /*r*/rspBuffer, /*r*/rdwBufferSize );
  417. }
  418. }
  419. void CIISSite::ExportInheritData( const IXMLDOMDocumentPtr& spDoc,
  420. const IXMLDOMElementPtr& spInheritRoot,
  421. HCRYPTKEY hEncryptKey,
  422. TByteAutoPtr& rspBuffer,
  423. DWORD& rdwBufferSize )const
  424. {
  425. _ASSERT( spDoc != NULL );
  426. _ASSERT( spInheritRoot != NULL );
  427. _ASSERT( m_spIABO != NULL );
  428. _ASSERT( rspBuffer.get() != NULL );
  429. _ASSERT( rdwBufferSize > 0 );
  430. DWORD dwEntries = 0;
  431. DWORD dwUnused = 0; // DataSetNumber - we don't care
  432. while( true )
  433. {
  434. DWORD dwRequiredSize = rdwBufferSize;
  435. HRESULT hr = m_spIABO->GetAllData( m_hSiteHandle,
  436. NULL,
  437. METADATA_SECURE | METADATA_INSERT_PATH | METADATA_INHERIT | METADATA_ISINHERITED,
  438. ALL_METADATA,
  439. ALL_METADATA,
  440. &dwEntries,
  441. &dwUnused,
  442. rdwBufferSize,
  443. rspBuffer.get(),
  444. &dwRequiredSize );
  445. // Increase the buffer if we need to
  446. if ( HRESULT_FROM_WIN32( ERROR_INSUFFICIENT_BUFFER ) == hr )
  447. {
  448. _ASSERT( dwRequiredSize > rdwBufferSize );
  449. rdwBufferSize = dwRequiredSize;
  450. rspBuffer = TByteAutoPtr( new BYTE[ rdwBufferSize ] );
  451. continue;
  452. }
  453. else if ( FAILED( hr ) )
  454. {
  455. throw CBaseException( IDS_E_METABASE_IO, hr );
  456. }
  457. else
  458. {
  459. // SUCCEEDED
  460. break;
  461. }
  462. };
  463. METADATA_GETALL_RECORD* aRecords = reinterpret_cast<METADATA_GETALL_RECORD*>( rspBuffer.get() );
  464. for ( DWORD i = 0; i < dwEntries; ++i )
  465. {
  466. // Store the record in the XML file only if this metadata is inherited from the parent
  467. if ( aRecords[ i ].dwMDAttributes & METADATA_ISINHERITED )
  468. {
  469. // Remove the inheritance attribs - we don't need them and ExportMetaRecord will
  470. // not recognize them
  471. aRecords[ i ].dwMDAttributes &= ~METADATA_ISINHERITED;
  472. // Set the inherit attrib as this is an inheritable data
  473. // ( event it is not now - it should be. At import time this data will be applied
  474. // to the WebSite root node as not-inherited but inheritable data
  475. aRecords[ i ].dwMDAttributes |= METADATA_INHERIT;
  476. ExportMetaRecord( spDoc,
  477. spInheritRoot,
  478. hEncryptKey,
  479. aRecords[ i ],
  480. rspBuffer.get() + aRecords[ i ].dwMDDataOffset );
  481. }
  482. }
  483. }
  484. void CIISSite::ExportKeyData( const IXMLDOMDocumentPtr& spDoc,
  485. const IXMLDOMElementPtr& spKey,
  486. HCRYPTKEY hCryptKey,
  487. LPCWSTR wszNodePath,
  488. TByteAutoPtr& rspBuffer,
  489. DWORD& rdwBufferSize )const
  490. {
  491. _ASSERT( wszNodePath != NULL );
  492. _ASSERT( spDoc != NULL );
  493. _ASSERT( spKey != NULL );
  494. _ASSERT( m_spIABO != NULL );
  495. _ASSERT( rspBuffer.get() != NULL );
  496. _ASSERT( rdwBufferSize > 0 );
  497. DWORD dwEntries = 0;
  498. DWORD dwUnused = 0; // DataSetNumber - we don't care
  499. do
  500. {
  501. DWORD dwRequiredSize = rdwBufferSize;
  502. HRESULT hr = m_spIABO->GetAllData( m_hSiteHandle,
  503. wszNodePath,
  504. METADATA_SECURE | METADATA_INSERT_PATH,
  505. ALL_METADATA,
  506. ALL_METADATA,
  507. &dwEntries,
  508. &dwUnused,
  509. rdwBufferSize,
  510. rspBuffer.get(),
  511. &dwRequiredSize );
  512. // Increase the buffer if we need to
  513. if ( HRESULT_FROM_WIN32( ERROR_INSUFFICIENT_BUFFER ) == hr )
  514. {
  515. _ASSERT( dwRequiredSize > rdwBufferSize );
  516. rdwBufferSize = dwRequiredSize;
  517. rspBuffer = TByteAutoPtr( new BYTE[ rdwBufferSize ] );
  518. continue;
  519. }
  520. else if ( FAILED( hr ) )
  521. {
  522. throw CBaseException( IDS_E_METABASE_IO, hr );
  523. }
  524. }while( false );
  525. METADATA_GETALL_RECORD* aRecords = reinterpret_cast<METADATA_GETALL_RECORD*>( rspBuffer.get() );
  526. for ( DWORD i = 0; i < dwEntries; ++i )
  527. {
  528. // Store the record in the XML file
  529. ExportMetaRecord( spDoc, spKey, hCryptKey, aRecords[ i ], rspBuffer.get() + aRecords[ i ].dwMDDataOffset );
  530. }
  531. }
  532. void CIISSite::DecryptData( HCRYPTKEY hDecryptKey, LPWSTR wszData )const
  533. {
  534. _ASSERT( hDecryptKey != NULL );
  535. _ASSERT( wszData != NULL );
  536. TByteAutoPtr spData;
  537. DWORD dwSize = 0;
  538. Convert::ToBLOB( wszData, /*r*/spData, /*r*/dwSize );
  539. // Decrypt data "in-place"
  540. // We are using stream cypher, so the length of the encrypted and decrypted string is the same
  541. IF_FAILED_BOOL_THROW( ::CryptDecrypt( hDecryptKey,
  542. NULL,
  543. TRUE,
  544. 0,
  545. spData.get(),
  546. &dwSize ),
  547. CBaseException( IDS_E_CRYPT_ENCRYPT ) );
  548. _ASSERT( ::wcslen( wszData ) * sizeof( WCHAR ) == dwSize );
  549. ::CopyMemory( wszData, spData.get(), dwSize );
  550. }
  551. /*
  552. Returns the Site's SSL certificate context hanlde
  553. */
  554. const TCertContextHandle CIISSite::GetCert()const
  555. {
  556. _ASSERT( m_spIABO != NULL );
  557. _ASSERT( m_hSiteHandle != NULL );
  558. _ASSERT( HaveCertificate() );
  559. // Get the cert hash value from the metabase
  560. METADATA_RECORD md = { 0 };
  561. DWORD dwHashSize = 0;
  562. TByteAutoPtr spHash;
  563. md.dwMDDataType = ALL_METADATA;
  564. md.dwMDIdentifier = MD_SSL_CERT_HASH;
  565. // Do not get the data - just check if it is there
  566. HRESULT hr = m_spIABO->GetData( m_hSiteHandle,
  567. NULL,
  568. &md,
  569. &dwHashSize );
  570. // We should find the cert - HaveCertificate() is expected to be called prior this method
  571. if ( FAILED( hr ) )
  572. {
  573. if( hr != HRESULT_FROM_WIN32( ERROR_INSUFFICIENT_BUFFER ) )
  574. {
  575. // Unexpected error
  576. throw CBaseException( IDS_E_METABASE_IO, hr );
  577. }
  578. else
  579. {
  580. // Alloc space for the hash
  581. _ASSERT( dwHashSize > 0 );
  582. spHash = TByteAutoPtr( new BYTE[ dwHashSize ] );
  583. md.dwMDDataLen = dwHashSize;
  584. md.pbMDData = spHash.get();
  585. }
  586. }
  587. IF_FAILED_HR_THROW( m_spIABO->GetData( m_hSiteHandle,
  588. NULL,
  589. &md,
  590. &dwHashSize ),
  591. CBaseException( IDS_E_METABASE_IO ) );
  592. // Get the certificate from the store
  593. // The store that keeps the certificates that have assosiated private keys
  594. // is the system store named "MY"
  595. TCertStoreHandle shStore( ::CertOpenStore( CERT_STORE_PROV_SYSTEM,
  596. 0, // Unused for the current store type
  597. NULL, // Deafult crypt provider
  598. CERT_SYSTEM_STORE_LOCAL_MACHINE,
  599. L"MY" ) );
  600. IF_FAILED_BOOL_THROW( shStore.IsValid(),
  601. CBaseException( IDS_E_OPEN_CERT_STORE ) );
  602. // Find the certificate in the store
  603. CRYPT_HASH_BLOB Hash;
  604. Hash.cbData = md.dwMDDataLen;
  605. Hash.pbData = spHash.get();
  606. TCertContextHandle shCert( ::CertFindCertificateInStore( shStore.get(),
  607. X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
  608. 0,
  609. CERT_FIND_HASH,
  610. &Hash,
  611. NULL ) );
  612. IF_FAILED_BOOL_THROW( shCert.IsValid(),
  613. CBaseException( IDS_E_FIND_SSL_CERT ) );
  614. return shCert;
  615. }
  616. /*
  617. Adds hCert certificate chain to hStore
  618. */
  619. void CIISSite::ChainCertificate( PCCERT_CONTEXT hCert, HCERTSTORE hStore )const
  620. {
  621. _ASSERT( hCert != NULL );
  622. _ASSERT( hStore != NULL );
  623. TCertChainHandle shCertChain;
  624. // Use default chain parameters
  625. CERT_CHAIN_PARA CertChainPara = { sizeof( CERT_CHAIN_PARA ) };
  626. IF_FAILED_BOOL_THROW( ::CertGetCertificateChain( HCCE_LOCAL_MACHINE,
  627. hCert,
  628. NULL,
  629. NULL,
  630. &CertChainPara,
  631. 0,
  632. NULL,
  633. &shCertChain ),
  634. CBaseException( IDS_E_CERT_CHAIN ) );
  635. // There must be at least on simple chain
  636. _ASSERT( shCertChain.get()->cChain != 0 );
  637. unsigned i = 0;
  638. while( i < shCertChain.get()->rgpChain[ 0 ]->cElement )
  639. {
  640. PCCERT_CONTEXT hCurrentCert = shCertChain.get()->rgpChain[ 0 ]->rgpElement[ i ]->pCertContext;
  641. TCertContextHandle shTempCert;
  642. // Add it to the store
  643. IF_FAILED_BOOL_THROW( ::CertAddCertificateContextToStore( hStore,
  644. hCurrentCert,
  645. CERT_STORE_ADD_REPLACE_EXISTING,
  646. &shTempCert ),
  647. CBaseException( IDS_E_ADD_CERT_STORE ) );
  648. // As this code is used for the SSL certificate ( the hCert )
  649. // we don't need any root certificates' private keys
  650. VERIFY( ::CertSetCertificateContextProperty( shTempCert.get(), CERT_KEY_PROV_INFO_PROP_ID, 0, NULL ) );
  651. ++i;
  652. };
  653. }
  654. void CIISSite::Close()
  655. {
  656. if ( m_hSiteHandle != NULL )
  657. {
  658. _ASSERT( m_spIABO != NULL );
  659. VERIFY( SUCCEEDED( m_spIABO->CloseKey( m_hSiteHandle ) ) );
  660. m_spIABO = NULL;
  661. m_hSiteHandle = NULL;
  662. }
  663. }
  664. void CIISSite::AddKey( LPCWSTR wszKey )const
  665. {
  666. _ASSERT( ( m_spIABO != NULL ) && ( m_hSiteHandle != NULL ) );
  667. _ASSERT( ( wszKey != NULL ) && ( ::wcslen( wszKey ) > 0 ) );
  668. HRESULT hr = m_spIABO->AddKey( m_hSiteHandle, wszKey );
  669. if ( FAILED( hr ) && ( hr != HRESULT_FROM_WIN32( ERROR_ALREADY_EXISTS ) ))
  670. {
  671. throw CObjectException( IDS_E_MD_ADDKEY, wszKey, hr );
  672. }
  673. }
  674. /*
  675. Writes a MD record to the XML
  676. */
  677. void CIISSite::ExportMetaRecord( const IXMLDOMDocumentPtr& spDoc,
  678. const IXMLDOMElementPtr& spKey,
  679. HCRYPTKEY hCryptKey,
  680. const METADATA_GETALL_RECORD& Data,
  681. void* pvData )const
  682. {
  683. _ASSERT( spDoc != NULL );
  684. _ASSERT( spKey != NULL );
  685. // Skip this types of metadata:
  686. // 1. Volitile data
  687. if ( Data.dwMDAttributes & METADATA_VOLATILE ) return;
  688. // We handle only these types of attributes - METADATA_SECURE, METADATA_INHERIT
  689. // All other should not exist
  690. _ASSERT( ( Data.dwMDAttributes & METADATA_SECURE ) ||
  691. ( Data.dwMDAttributes & METADATA_INHERIT ) ||
  692. ( Data.dwMDAttributes == METADATA_NO_ATTRIBUTES ) );
  693. // Encrypt secure data if we need to
  694. if ( ( hCryptKey != NULL ) && ( Data.dwMDAttributes & METADATA_SECURE ) )
  695. {
  696. DWORD dwSize = Data.dwMDDataLen;
  697. IF_FAILED_BOOL_THROW( ::CryptEncrypt( hCryptKey,
  698. NULL,
  699. TRUE,
  700. 0,
  701. reinterpret_cast<BYTE*>( pvData ),
  702. &dwSize,
  703. Data.dwMDDataLen ),
  704. CBaseException( IDS_E_CRYPT_ENCRYPT ) );
  705. _ASSERT( dwSize == Data.dwMDDataLen );
  706. }
  707. IXMLDOMElementPtr spEl;
  708. // Create the node
  709. // 1. Empty data
  710. if ( ( NULL == pvData ) || ( 0 == Data.dwMDDataLen ) )
  711. {
  712. spEl = CXMLTools::AddTextNode( spDoc, spKey, L"Custom", L"" );
  713. }
  714. // 2. Secure and binary data ( secure data is written in binary format )
  715. else if ( ( BINARY_METADATA == Data.dwMDDataType ) || ( Data.dwMDAttributes & METADATA_SECURE ) )
  716. {
  717. spEl = CXMLTools::AddTextNode( spDoc,
  718. spKey,
  719. L"Custom",
  720. Convert::ToString( reinterpret_cast<BYTE*>( pvData ), Data.dwMDDataLen ).c_str() );
  721. }
  722. else if ( DWORD_METADATA == Data.dwMDDataType )
  723. {
  724. _ASSERT( sizeof( DWORD ) == Data.dwMDDataLen );
  725. spEl = CXMLTools::AddTextNode( spDoc,
  726. spKey,
  727. L"Custom",
  728. Convert::ToString( *( reinterpret_cast<DWORD*>( pvData ) ) ).c_str() );
  729. }
  730. else if ( STRING_METADATA == Data.dwMDDataType )
  731. {
  732. spEl = CXMLTools::AddTextNode( spDoc, spKey, L"Custom", reinterpret_cast<LPCWSTR>( pvData ) );
  733. }
  734. else if ( ( MULTISZ_METADATA == Data.dwMDDataType ) || ( EXPANDSZ_METADATA == Data.dwMDDataType ) )
  735. {
  736. // Convert to data to single string with spaces instead of string terminators
  737. LPWSTR wszData = reinterpret_cast<LPWSTR>( pvData );
  738. MultiStrToString( /*r*/wszData );
  739. spEl = CXMLTools::AddTextNode( spDoc, spKey, L"Custom", wszData );
  740. }
  741. else
  742. {
  743. // Unexpected MD type
  744. _ASSERT( false );
  745. }
  746. // Set the properties
  747. CXMLTools::SetAttrib( spEl, L"ID", Convert::ToString( Data.dwMDIdentifier ).c_str() );
  748. CXMLTools::SetAttrib( spEl, L"UserType", Convert::ToString( Data.dwMDUserType ).c_str() );
  749. CXMLTools::SetAttrib( spEl, L"Type", Convert::ToString( Data.dwMDDataType ).c_str() );
  750. CXMLTools::SetAttrib( spEl, L"Attributes", Convert::ToString( Data.dwMDAttributes ).c_str() );
  751. }
  752. /*
  753. Remove from the config XML all data that should not be imported
  754. spRoot is expected to be the <WebSite> node
  755. */
  756. void CIISSite::RemoveLocalMetadata( const IXMLDOMElementPtr& spRoot )const
  757. {
  758. struct _Helper
  759. {
  760. LPCWSTR wszPath;
  761. DWORD dwID;
  762. };
  763. // First param is the Path ( the meta key, exactly as it will be written in the 'Location' attribute
  764. // of the IISConfigObject tag ).
  765. // Second param = the ID of the property to be removed ( ID attribute of the Custom tag )
  766. _Helper aData[] = { { L"", MD_SSL_CERT_HASH }, // Cert hash
  767. { L"", MD_SSL_CERT_STORE_NAME } // Cert store name
  768. };
  769. for ( DWORD i = 0; i < ARRAY_SIZE( aData ); ++i )
  770. {
  771. WCHAR wszXPath[ 1024 ];
  772. ::swprintf( wszXPath,
  773. L"Metadata/IISConfigObject[@Location=\"%s\"]/Custom[@ID=\"%u\"]",
  774. aData[ i ].wszPath,
  775. aData[ i ].dwID );
  776. CXMLTools::RemoveNodes( spRoot, wszXPath );
  777. }
  778. }
  779. void CIISSite::ImportMetaValue( const IXMLDOMNodePtr& spValue,
  780. LPCWSTR wszLocation,
  781. HCRYPTKEY hDecryptKey )const
  782. {
  783. // Location and Decrypt key are valid to be NULL
  784. _ASSERT( spValue != NULL );
  785. METADATA_RECORD md = { 0 };
  786. md.dwMDIdentifier = Convert::ToDWORD( CXMLTools::GetAttrib( spValue, L"ID" ).c_str() );
  787. md.dwMDAttributes = Convert::ToDWORD( CXMLTools::GetAttrib( spValue, L"Attributes" ).c_str() );
  788. md.dwMDDataType = Convert::ToDWORD( CXMLTools::GetAttrib( spValue, L"Type" ).c_str() );
  789. md.dwMDUserType = Convert::ToDWORD( CXMLTools::GetAttrib( spValue, L"UserType" ).c_str() );
  790. CComBSTR bstrData;
  791. IF_FAILED_HR_THROW( spValue->get_text( &bstrData ),
  792. CBaseException( IDS_E_XML_PARSE ) );
  793. // If the data is secure and we have DecryptKey - then it was encrypted and we should decrypt it
  794. if ( ( md.dwMDAttributes & METADATA_SECURE ) && ( hDecryptKey != NULL ) )
  795. {
  796. // This will decrypt the data in-place
  797. DecryptData( hDecryptKey, /*r*/bstrData.m_str );
  798. }
  799. TByteAutoPtr spBinData;
  800. DWORD dwDWORDData = 0;
  801. // There may not be any data. Just the key
  802. if ( bstrData.Length() > 0 )
  803. {
  804. DWORD dwMultiSzLen = 0;
  805. switch( md.dwMDDataType )
  806. {
  807. case BINARY_METADATA:
  808. Convert::ToBLOB( bstrData.m_str, /*r*/spBinData, /*r*/md.dwMDDataLen );
  809. md.pbMDData = spBinData.get();
  810. break;
  811. case DWORD_METADATA:
  812. dwDWORDData = Convert::ToDWORD( bstrData.m_str );
  813. md.pbMDData = reinterpret_cast<BYTE*>( &dwDWORDData );
  814. md.dwMDDataLen = sizeof( DWORD );
  815. break;
  816. case STRING_METADATA:
  817. md.pbMDData = reinterpret_cast<BYTE*>( bstrData.m_str );
  818. md.dwMDDataLen = static_cast<DWORD>( ( ::wcslen( bstrData ) + 1 ) * sizeof( WCHAR ) );
  819. break;
  820. case MULTISZ_METADATA:
  821. // Multistrings are stored in the XML separated with space
  822. // Convert this to strings, separated with '\0' and the whole sequence must
  823. // be terminated with double '\0'
  824. XMLToMultiSz( /*r*/bstrData, dwMultiSzLen );
  825. md.pbMDData = reinterpret_cast<BYTE*>( bstrData.m_str );
  826. md.dwMDDataLen = dwMultiSzLen * sizeof( WCHAR );
  827. break;
  828. default:
  829. _ASSERT( false );
  830. };
  831. }//if ( bstrData.Length() > 0 )
  832. else
  833. {
  834. // Empty data. However we need a valid pointer
  835. // Use the DWORD var
  836. md.pbMDData = reinterpret_cast<BYTE*>( &dwDWORDData );
  837. md.dwMDDataLen = 0;
  838. }
  839. // Set the data in the metabase
  840. IF_FAILED_HR_THROW( m_spIABO->SetData( m_hSiteHandle,
  841. wszLocation,
  842. &md ),
  843. CObjectException( IDS_E_METABASE, wszLocation ) );
  844. }
  845. void CIISSite::MultiStrToString( LPWSTR wszData )const
  846. {
  847. _ASSERT( wszData != NULL );
  848. LPWSTR wszString = wszData;
  849. // Replace each '\0' with space. leave only the final one
  850. bool bExit = false;
  851. do
  852. {
  853. if ( L'\0' == *wszString )
  854. {
  855. *wszString = L' ';
  856. bExit = *( wszString + 1 ) == L'\0';
  857. }
  858. ++wszString;
  859. }while( !bExit );
  860. }
  861. void CIISSite::XMLToMultiSz( CComBSTR& rbstrData, DWORD& rdwSize )const
  862. {
  863. _ASSERT( rbstrData != NULL );
  864. // We need one more '\0' at the end of the string, 'cause the sequence must
  865. // be double '\0' terminated
  866. // This will reallocate the buffer ( the buffer will be one more symbol wider )
  867. // and will add one more '\0' at the end
  868. DWORD dwSize = static_cast<DWORD>( ::wcslen( rbstrData ) + 2 );
  869. BSTR bstrNew = ::SysAllocStringLen( rbstrData, dwSize - 1 ); // This fun adds one smore char. see docs for details
  870. if ( NULL == bstrNew ) throw CBaseException( IDS_E_OUTOFMEM, ERROR_SUCCESS );
  871. // Convert all spaces ( ' ' ) to '\0'
  872. DWORD iChar = 0;
  873. while( bstrNew[ iChar ] != L'\0' )
  874. {
  875. if ( L' ' == bstrNew[ iChar ] )
  876. {
  877. bstrNew[ iChar ] = L'\0';
  878. }
  879. ++iChar;
  880. };
  881. // Assign the new value to the result
  882. // Operator = cannot be used as it will do a SysAllocString and thus, the final '\0' will be lost
  883. rbstrData.Empty();
  884. rbstrData.Attach( bstrNew );
  885. rdwSize = dwSize;
  886. }