// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. #include "precomp.h" //Adding this because delivery doesn't have the updated specstrings.h that windows build does #ifndef __in_xcount #define __in_xcount(size) #endif // sql queries enum eWebBaseQuery { wbqWeb = 1, wbqId, wbqIP, wbqPort, wbqHeader, wbqSecure, wbqDescription }; // prototypes for private helper functions static SCA_WEB* NewWeb(); static void FreeWeb(SCA_WEB *pswDelete); static SCA_WEB* AddWebToList( __in SCA_WEB* pswList, __in SCA_WEB* psw ); static HRESULT ScaWebFindBase( __in IMSAdminBase* piMetabase, __in SCA_WEB* pswList, __in_z LPCWSTR wzWeb, __in int iSiteId, __in_z LPCWSTR wzIP, __in int iPort, __in_z LPCWSTR wzHeader, __in BOOL fSecure, __in_z LPCWSTR wzDescription, __out_ecount(cchWebBase) LPWSTR wzWebBase, __in DWORD cchWebBase ); static HRESULT ScaWebFindFreeBase( __in IMSAdminBase* piMetabase, __in_xcount(unknown) SCA_WEB* pswList, __in int iSiteId, __in_z LPCWSTR wzDescription, __out_ecount(cchWebBase) LPWSTR wzWebBase, __in DWORD cchWebBase ); static HRESULT ScaWebWrite( __in IMSAdminBase* piMetabase, __in SCA_WEB* psw, __in SCA_APPPOOL * psapList ); static HRESULT ScaWebRemove( __in IMSAdminBase* piMetabase, __in const SCA_WEB* psw); static DWORD SiteIdFromDescription( __in_z LPCWSTR wzDescription ); static void Sort( __in_ecount(cArray) DWORD dwArray[], __in int cArray ); HRESULT ScaWebsRead( __in IMSAdminBase* piMetabase, __in SCA_MIMEMAP** ppsmmList, __in SCA_WEB** ppswList, __in SCA_HTTP_HEADER** ppshhList, __in SCA_WEB_ERROR** ppsweList, __in WCA_WRAPQUERY_HANDLE hUserQuery, __in WCA_WRAPQUERY_HANDLE hWebDirPropQuery, __in WCA_WRAPQUERY_HANDLE hSslCertQuery, __in WCA_WRAPQUERY_HANDLE hWebLogQuery, __in WCA_WRAPQUERY_HANDLE hWebAppQuery, __in WCA_WRAPQUERY_HANDLE hWebAppExtQuery, __inout LPWSTR *ppwzCustomActionData ) { Assert(piMetabase && ppswList); HRESULT hr = S_OK; MSIHANDLE hRec; MSIHANDLE hRecAddresses; SCA_WEB* psw = NULL; LPWSTR pwzData = NULL; int iSiteId; DWORD dwLen = 0; WCA_WRAPQUERY_HANDLE hQueryWebSite = NULL; WCA_WRAPQUERY_HANDLE hQueryWebAddress = NULL; hr = WcaBeginUnwrapQuery(&hQueryWebSite, ppwzCustomActionData); ExitOnFailure(hr, "Failed to unwrap query for ScaWebsRead"); hr = WcaBeginUnwrapQuery(&hQueryWebAddress, ppwzCustomActionData); ExitOnFailure(hr, "Failed to unwrap query for ScaWebsRead"); if (0 == WcaGetQueryRecords(hQueryWebSite)) { WcaLog(LOGMSG_VERBOSE, "Required tables not present"); ExitFunction1(hr = S_FALSE); } // loop through all the webs while (S_OK == (hr = WcaFetchWrappedRecord(hQueryWebSite, &hRec))) { psw = NewWeb(); ExitOnNull(psw, hr, E_OUTOFMEMORY, "Failed to allocate memory for web object in memory"); // get the darwin information hr = WcaGetRecordString(hRec, wqWeb, &pwzData); ExitOnFailure(hr, "Failed to get Web"); hr = ::StringCchCopyW(psw->wzKey, countof(psw->wzKey), pwzData); ExitOnFailure(hr, "Failed to copy key string to web object"); if (*pwzData && *ppsmmList) { hr = ScaGetMimeMap(mmptWeb, pwzData, ppsmmList, &psw->psmm); ExitOnFailure(hr, "Failed to get mimemap for VirtualDir"); } // get component install state hr = WcaGetRecordString(hRec, wqComponent, &pwzData); ExitOnFailure(hr, "Failed to get Component for Web"); hr = ::StringCchCopyW(psw->wzComponent, countof(psw->wzComponent), pwzData); ExitOnFailure(hr, "Failed to copy component string to web object"); if (*(psw->wzComponent)) { psw->fHasComponent = TRUE; hr = WcaGetRecordInteger(hRec, wqInstalled, (int *)&psw->isInstalled); ExitOnFailure(hr, "Failed to get web Component's installed state"); WcaGetRecordInteger(hRec, wqAction, (int *)&psw->isAction); ExitOnFailure(hr, "Failed to get web Component's action state"); if (!WcaIsInstalling(psw->isInstalled, psw->isAction) && !WcaIsUninstalling(psw->isInstalled, psw->isAction) && !WcaIsReInstalling(psw->isInstalled, psw->isAction)) { FreeWeb(psw); psw = NULL; continue; // If we aren't acting on this component, skip it } } hr = WcaGetRecordInteger(hRec, wqId, &iSiteId); ExitOnFailure(hr, "Failed to get SiteId for Web"); // Get the web's key address. hr = WcaGetRecordString(hRec, wqAddress, &pwzData); ExitOnFailure(hr, "Failed to get Address for Web"); hr = ::StringCchCopyW(psw->swaKey.wzKey, countof(psw->swaKey.wzKey), pwzData); ExitOnFailure(hr, "Failed to copy key string to web object"); hr = WcaGetRecordString(hRec, wqIP, &pwzData); ExitOnFailure(hr, "Failed to get IP for Web"); hr = ::StringCchCopyW(psw->swaKey.wzIP, countof(psw->swaKey.wzIP), pwzData); ExitOnFailure(hr, "Failed to copy IP string to web object"); hr = WcaGetRecordString(hRec, wqPort, &pwzData); ExitOnFailure(hr, "Failed to get Web Address port"); psw->swaKey.iPort = wcstol(pwzData, NULL, 10); hr = WcaGetRecordString(hRec, wqHeader, &pwzData); ExitOnFailure(hr, "Failed to get Header for Web"); hr = ::StringCchCopyW(psw->swaKey.wzHeader, countof(psw->swaKey.wzHeader), pwzData); ExitOnFailure(hr, "Failed to copy header string to web object"); hr = WcaGetRecordInteger(hRec, wqSecure, &psw->swaKey.fSecure); ExitOnFailure(hr, "Failed to get if Web is secure"); if (S_FALSE == hr) { psw->swaKey.fSecure = FALSE; } // Get the web's description. hr = WcaGetRecordString(hRec, wqDescription, &pwzData); ExitOnFailure(hr, "Failed to get Description for Web"); hr = ::StringCchCopyW(psw->wzDescription, countof(psw->wzDescription), pwzData); ExitOnFailure(hr, "Failed to copy description string to web object"); // Try to find the web root in case it already exists. dwLen = METADATA_MAX_NAME_LEN; hr = ScaWebFindBase(piMetabase, *ppswList, psw->wzKey, iSiteId, psw->swaKey.wzIP, psw->swaKey.iPort, psw->swaKey.wzHeader, psw->swaKey.fSecure, psw->wzDescription, psw->wzWebBase, dwLen); if (S_OK == hr) { psw->fBaseExists = TRUE; } else if (S_FALSE == hr) // didn't find the web site. { psw->fBaseExists = FALSE; // If we're actually configuring the web site. if (psw->fHasComponent) { if (WcaIsInstalling(psw->isInstalled, psw->isAction)) { hr = ScaWebFindFreeBase(piMetabase, *ppswList, iSiteId, psw->wzDescription, psw->wzWebBase, countof(psw->wzWebBase)); ExitOnFailure(hr, "Failed to find free web root."); } else if (WcaIsUninstalling(psw->isInstalled, psw->isAction)) { WcaLog(LOGMSG_VERBOSE, "Web site: '%ls' was already removed, skipping.", psw->wzKey); hr = S_OK; continue; } } } ExitOnFailure(hr, "Failed to find web root"); // get any extra web addresses WcaFetchWrappedReset(hQueryWebAddress); while (S_OK == (hr = WcaFetchWrappedRecordWhereString(hQueryWebAddress, 2, psw->wzKey, &hRecAddresses))) { if (MAX_ADDRESSES_PER_WEB <= psw->cExtraAddresses) { hr = HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER); ExitOnFailure(hr, "Failure to get more extra web addresses, max exceeded."); } hr = WcaGetRecordString(hRecAddresses, waqAddress, &pwzData); ExitOnFailure(hr, "Failed to get extra web Address"); // if this isn't the key address add it if (0 != lstrcmpW(pwzData, psw->swaKey.wzKey)) { hr = ::StringCchCopyW(psw->swaExtraAddresses[psw->cExtraAddresses].wzKey, countof(psw->swaExtraAddresses[psw->cExtraAddresses].wzKey), pwzData); ExitOnFailure(hr, "Failed to copy extra addresses key string to web object"); hr = WcaGetRecordString(hRecAddresses, waqIP, &pwzData); ExitOnFailure(hr, "Failed to get extra web IP"); hr = ::StringCchCopyW(psw->swaExtraAddresses[psw->cExtraAddresses].wzIP, countof(psw->swaExtraAddresses[psw->cExtraAddresses].wzIP), pwzData); ExitOnFailure(hr, "Failed to copy extra addresses IP string to web object"); hr = WcaGetRecordString(hRecAddresses, waqPort, &pwzData); ExitOnFailure(hr, "Failed to get port for extra web IP"); psw->swaExtraAddresses[psw->cExtraAddresses].iPort= wcstol(pwzData, NULL, 10); hr = WcaGetRecordString(hRecAddresses, waqHeader, &pwzData); ExitOnFailure(hr, "Failed to get header for extra web IP"); hr = ::StringCchCopyW(psw->swaExtraAddresses[psw->cExtraAddresses].wzHeader, countof(psw->swaExtraAddresses[psw->cExtraAddresses].wzHeader), pwzData); ExitOnFailure(hr, "Failed to copy extra addresses header string to web object"); hr = WcaGetRecordInteger(hRecAddresses, waqSecure, &psw->swaExtraAddresses[psw->cExtraAddresses].fSecure); ExitOnFailure(hr, "Failed to get if secure extra web IP"); if (S_FALSE == hr) psw->swaExtraAddresses[psw->cExtraAddresses].fSecure = FALSE; ++psw->cExtraAddresses; } } if (E_NOMOREITEMS == hr) hr = S_OK; ExitOnFailure(hr, "Failure occured while getting extra web addresses"); hr = WcaGetRecordInteger(hRec, wqConnectionTimeout, &psw->iConnectionTimeout); ExitOnFailure(hr, "Failed to get connection timeout for Web"); if (psw->fHasComponent) // If we're installing it, it needs a dir { // get the web's directory if (INSTALLSTATE_SOURCE == psw->isAction) { hr = WcaGetRecordString(hRec, wqSourcePath, &pwzData); } else { hr = WcaGetRecordString(hRec, wqTargetPath, &pwzData); } ExitOnFailure(hr, "Failed to get Source/TargetPath for Directory"); // remove trailing backslashes while (lstrlenW(pwzData) > 0 && pwzData[lstrlenW(pwzData)-1] == L'\\') { pwzData[lstrlenW(pwzData)-1] = 0; } hr = ::StringCchCopyW(psw->wzDirectory, countof(psw->wzDirectory), pwzData); ExitOnFailure(hr, "Failed to copy directory string to web object"); } hr = WcaGetRecordInteger(hRec, wqState, &psw->iState); ExitOnFailure(hr, "Failed to get state for Web"); hr = WcaGetRecordInteger(hRec, wqAttributes, &psw->iAttributes); ExitOnFailure(hr, "Failed to get attributes for Web"); // get the dir properties for this web hr = WcaGetRecordString(hRec, wqProperties, &pwzData); ExitOnFailure(hr, "Failed to get directory property record for Web"); if (*pwzData) { hr = ScaGetWebDirProperties(pwzData, hUserQuery, hWebDirPropQuery, &psw->swp); ExitOnFailure(hr, "Failed to get directory properties for Web"); psw->fHasProperties = TRUE; } // get the application information for this web hr = WcaGetRecordString(hRec, wqApplication, &pwzData); ExitOnFailure(hr, "Failed to get application identifier for Web"); if (*pwzData) { hr = ScaGetWebApplication(NULL, pwzData, hWebAppQuery, hWebAppExtQuery, &psw->swapp); ExitOnFailure(hr, "Failed to get application for Web"); psw->fHasApplication = TRUE; } // get the SSL certificates hr = ScaSslCertificateRead(psw->wzKey, hSslCertQuery, &(psw->pswscList)); ExitOnFailure(hr, "Failed to get SSL Certificates."); // get the custom headers if (*ppshhList) { hr = ScaGetHttpHeader(hhptWeb, psw->wzKey, ppshhList, &(psw->pshhList)); ExitOnFailure(hr, "Failed to get Custom HTTP Headers"); } // get the errors if (*ppsweList) { hr = ScaGetWebError(weptWeb, psw->wzKey, ppsweList, &(psw->psweList)); ExitOnFailure(hr, "Failed to get Custom Errors"); } // get the log information for this web hr = WcaGetRecordString(hRec, wqLog, &pwzData); ExitOnFailure(hr, "Failed to get log identifier for Web"); if (*pwzData) { hr = ScaGetWebLog(piMetabase, pwzData, hWebLogQuery, &psw->swl); ExitOnFailure(hr, "Failed to get Log for Web."); psw->fHasLog = TRUE; } *ppswList = AddWebToList(*ppswList, psw); psw = NULL; // set the web NULL so it doesn't accidentally get freed below } if (E_NOMOREITEMS == hr) { hr = S_OK; } LExit: // if anything was left over after an error clean it all up WcaFinishUnwrapQuery(hQueryWebSite); WcaFinishUnwrapQuery(hQueryWebAddress); if (psw) { ScaWebsFreeList(psw); } ReleaseStr(pwzData); return hr; } HRESULT ScaWebsGetBase( __in IMSAdminBase* piMetabase, __in SCA_WEB* pswList, __in LPCWSTR wzWeb, __out_ecount(cchWebBase) LPWSTR wzWebBase, __in DWORD cchWebBase, __in WCA_WRAPQUERY_HANDLE hWrapQuery ) { HRESULT hr = S_OK; MSIHANDLE hRec; int iSiteId; WCHAR wzIP[MAX_PATH]; int iPort = -1; WCHAR wzHeader[MAX_PATH]; BOOL fSecure = FALSE; LPWSTR pwzData = NULL; // get the web information WcaFetchWrappedReset(hWrapQuery); hr = WcaFetchWrappedRecordWhereString(hWrapQuery, 1, wzWeb, &hRec); if (S_OK == hr) { // get the data to search for hr = WcaGetRecordInteger(hRec, wbqId, &iSiteId); ExitOnFailure(hr, "Failed to get SiteId for Web for VirtualDir"); hr = WcaGetRecordString(hRec, wbqIP, &pwzData); ExitOnFailure(hr, "Failed to get IP for Web for VirtualDir"); hr = ::StringCchCopyW(wzIP, countof(wzIP), pwzData); ExitOnFailure(hr, "Failed to copy IP for Web for VirtualDir"); hr = WcaGetRecordString(hRec, wbqPort, &pwzData); ExitOnFailure(hr, "Failed to get port for extra web IP"); iPort = wcstol(pwzData, NULL, 10); hr = WcaGetRecordString(hRec, wbqHeader, &pwzData); ExitOnFailure(hr, "Failed to get Header for Web for VirtualDir"); hr = ::StringCchCopyW(wzHeader, countof(wzHeader), pwzData); ExitOnFailure(hr, "Failed to copy Header for Web for VirtualDir"); hr = WcaGetRecordInteger(hRec, wbqSecure, &fSecure); if (S_FALSE == hr) fSecure = FALSE; hr = WcaGetRecordString(hRec, wbqDescription, &pwzData); ExitOnFailure(hr, "Failed to get Description for Web"); // find the web or find the next free web location hr = ScaWebFindBase(piMetabase, pswList, wzWeb, iSiteId, wzIP, iPort, wzHeader, fSecure, pwzData, wzWebBase, cchWebBase); if (S_FALSE == hr) hr = HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND); ExitOnFailure(hr, "Failed to find Web base"); } else if (S_FALSE == hr) hr = HRESULT_FROM_WIN32(ERROR_NOT_FOUND); // Let's check that there isn't more than one record found - if there is, throw an assert like WcaFetchSingleRecord() would HRESULT hrTemp = WcaFetchWrappedRecordWhereString(hWrapQuery, 1, wzWeb, &hRec); if (SUCCEEDED(hrTemp)) { AssertSz(E_NOMOREITEMS == hrTemp, "ScaWebsGetBase found more than one record"); } LExit: ReleaseStr(pwzData); return hr; } HRESULT ScaWebsInstall( __in IMSAdminBase* piMetabase, __in SCA_WEB* pswList, __in SCA_APPPOOL * psapList ) { HRESULT hr = S_OK; SCA_WEB* psw = pswList; while (psw) { // if we are installing the web site if (psw->fHasComponent && WcaIsInstalling(psw->isInstalled, psw->isAction)) { hr = ScaWebWrite(piMetabase, psw, psapList); ExitOnFailure(hr, "failed to write web '%ls' to metabase", psw->wzKey); } psw = psw->pswNext; } LExit: return hr; } HRESULT ScaWebsUninstall( __in IMSAdminBase* piMetabase, __in SCA_WEB* pswList ) { HRESULT hr = S_OK; SCA_WEB* psw = pswList; while (psw) { // if we are uninstalling the web site if (psw->fHasComponent && WcaIsUninstalling(psw->isInstalled, psw->isAction)) { hr = ScaWebRemove(piMetabase, psw); ExitOnFailure(hr, "Failed to remove web '%ls' from metabase", psw->wzKey); } psw = psw->pswNext; } LExit: return hr; } void ScaWebsFreeList( __in SCA_WEB* pswList ) { SCA_WEB* pswDelete = pswList; while (pswList) { pswDelete = pswList; pswList = pswList->pswNext; // Free the SSL, headers and errors list first FreeWeb(pswDelete); } } // private helper functions static void FreeWeb(SCA_WEB *pswDelete) { ScaSslCertificateFreeList(pswDelete->pswscList); ScaHttpHeaderFreeList(pswDelete->pshhList); ScaWebErrorFreeList(pswDelete->psweList); MemFree(pswDelete); } static SCA_WEB* NewWeb() { SCA_WEB* psw = static_cast(MemAlloc(sizeof(SCA_WEB), TRUE)); Assert(psw); return psw; } static SCA_WEB* AddWebToList( __in SCA_WEB* pswList, __in SCA_WEB* psw ) { if (pswList) { SCA_WEB* pswTemp = pswList; while (pswTemp->pswNext) { pswTemp = pswTemp->pswNext; } pswTemp->pswNext = psw; } else { pswList = psw; } return pswList; } static HRESULT ScaWebFindBase( __in IMSAdminBase* piMetabase, __in SCA_WEB* pswList, __in_z LPCWSTR wzWeb, __in int iSiteId, __in_z LPCWSTR wzIP, __in int iPort, __in_z LPCWSTR wzHeader, __in BOOL fSecure, __in_z LPCWSTR wzDescription, __out_ecount(cchWebBase) LPWSTR wzWebBase, __in DWORD cchWebBase ) { Assert(piMetabase); HRESULT hr = S_OK; WCHAR wzKey[METADATA_MAX_NAME_LEN]; WCHAR wzSubkey[METADATA_MAX_NAME_LEN]; LPWSTR wzFoundKey = NULL; METADATA_RECORD mr; ::ZeroMemory(&mr, sizeof(mr)); METADATA_RECORD mrCompare; ::ZeroMemory(&mrCompare, sizeof(mrCompare)); // try to find the web in memory first for (SCA_WEB* psw = pswList; psw; psw = psw->pswNext) { if (0 == lstrcmpW(wzWeb, psw->wzKey)) { if ((0 == lstrcmpW(wzIP, psw->swaKey.wzIP) || 0 == lstrcmpW(wzIP, L"*") || 0 == lstrcmpW(psw->swaKey.wzIP, L"*")) && iPort == psw->swaKey.iPort && 0 == lstrcmpW(wzHeader, psw->swaKey.wzHeader) && fSecure == psw->swaKey.fSecure) { if (0 == lstrlenW(psw->wzWebBase)) { WcaLog(LOGMSG_STANDARD, "A matching web object in memory was found, but the web object in memory has no associated base"); ExitFunction1(hr = S_FALSE); } hr = ::StringCchCopyW(wzKey, countof(wzKey), psw->wzWebBase); ExitOnFailure(hr, "Failed to copy web base into key."); wzFoundKey = wzKey; break; } else { WcaLog(LOGMSG_STANDARD, "Found web `%ls` but data did not match.", wzWeb); hr = E_UNEXPECTED; break; } } } ExitOnFailure(hr, "Failure occured while searching for web in memory"); // If we didn't find a web in memory matching look in the metabase. if (!wzFoundKey) { mr.dwMDIdentifier = MD_KEY_TYPE; mr.dwMDAttributes = METADATA_INHERIT; mr.dwMDUserType = IIS_MD_UT_SERVER; mr.dwMDDataType = ALL_METADATA; mr.dwMDDataLen = 0; mr.pbMDData = NULL; // If we're looking based on the old WebAddress detection (NULL) or the new // Web description detection (-1) if (MSI_NULL_INTEGER == iSiteId || -1 == iSiteId) { if (MSI_NULL_INTEGER == iSiteId) { mrCompare.dwMDIdentifier = (fSecure) ? MD_SECURE_BINDINGS : MD_SERVER_BINDINGS; } else { mrCompare.dwMDIdentifier = MD_SERVER_COMMENT; } mrCompare.dwMDAttributes = METADATA_INHERIT; mrCompare.dwMDUserType = IIS_MD_UT_SERVER; mrCompare.dwMDDataType = ALL_METADATA; mrCompare.dwMDDataLen = 0; mrCompare.pbMDData = NULL; // Loop through the "web keys" looking for the "IIsWebServer" key that matches our search criteria. for (DWORD dwIndex = 0; SUCCEEDED(hr); ++dwIndex) { hr = piMetabase->EnumKeys(METADATA_MASTER_ROOT_HANDLE, L"/LM/W3SVC", wzSubkey, dwIndex); if (SUCCEEDED(hr)) { hr = ::StringCchPrintfW(wzKey, countof(wzKey), L"/LM/W3SVC/%s", wzSubkey); ExitOnFailure(hr, "Failed to copy web subkey."); hr = MetaGetValue(piMetabase, METADATA_MASTER_ROOT_HANDLE, wzKey, &mr); if (MD_ERROR_DATA_NOT_FOUND == hr || HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND) == hr) { hr = S_FALSE; // didn't find anything, try next one continue; } ExitOnFailure(hr, "Failed to get key from metabase while searching for web servers"); // If we have an IIsWebServer check to see if this is the site we are searching for. if (0 == lstrcmpW(L"IIsWebServer", (LPCWSTR)mr.pbMDData)) { hr = MetaGetValue(piMetabase, METADATA_MASTER_ROOT_HANDLE, wzKey, &mrCompare); if (MD_ERROR_DATA_NOT_FOUND == hr || NULL == mrCompare.pbMDData) { hr = S_FALSE; continue; } ExitOnFailure(hr, "Failed to get address/description from metabase while searching for web servers"); LPWSTR pwzExists = (LPWSTR)mrCompare.pbMDData; if (MSI_NULL_INTEGER == iSiteId) { // Break down the address into its constituent parts (IP:Port:Header). while (S_OK == hr && *pwzExists) { LPCWSTR pwzIPExists = pwzExists; pwzExists = const_cast(wcsstr(pwzIPExists, L":")); ExitOnNull(pwzExists, hr, E_INVALIDARG, "Invalid web address. IP was not separated by a colon."); *pwzExists = L'\0'; LPCWSTR pwzPortExists = pwzExists + 1; pwzExists = const_cast(wcsstr(pwzPortExists, L":")); ExitOnNull(pwzExists, hr, E_INVALIDARG, "Invalid web address. Port was not separated by a colon."); *pwzExists = L'\0'; int iPortExists = wcstol(pwzPortExists, NULL, 10); LPCWSTR pwzHeaderExists = pwzExists + 1; // compare the passed in address with the address listed for this web if ((0 == lstrcmpW(wzIP, pwzIPExists) || 0 == lstrcmpW(wzIP, L"*")) && iPort == iPortExists && 0 == lstrcmpW(wzHeader, pwzHeaderExists)) { wzFoundKey = wzKey; break; } // move to the next block of data, this may move beyond the available // data and exit the while loop above. pwzExists = const_cast(pwzHeaderExists + lstrlenW(pwzHeaderExists) + 1); } } else { if (0 == lstrcmpW(wzDescription, pwzExists)) { wzFoundKey = wzKey; } } // If we found the key we were looking for, no point in continuing to search. if (wzFoundKey) { break; } } } } if (E_NOMOREITEMS == hr) { Assert(!wzFoundKey); hr = S_FALSE; } } else // we're looking a specific SiteId { hr = ::StringCchPrintfW(wzKey, countof(wzKey), L"/LM/W3SVC/%d", iSiteId); ExitOnFailure(hr, "Failed to allocate metabase key string."); // if we have an IIsWebServer store the key hr = MetaGetValue(piMetabase, METADATA_MASTER_ROOT_HANDLE, wzKey, &mr); if (SUCCEEDED(hr) && NULL != mr.pbMDData && 0 == lstrcmpW(L"IIsWebServer", (LPCWSTR)mr.pbMDData)) { wzFoundKey = wzKey; } else if (MD_ERROR_DATA_NOT_FOUND == hr || NULL == mrCompare.pbMDData || NULL == mr.pbMDData) { hr = S_FALSE; } } } if (wzFoundKey) { Assert(S_OK == hr); hr = ::StringCchCopyW(wzWebBase, cchWebBase, wzFoundKey); ExitOnFailure(hr, "Buffer not larger enough for found base key."); } LExit: MetaFreeValue(&mr); MetaFreeValue(&mrCompare); if (!wzFoundKey && SUCCEEDED(hr)) { hr = S_FALSE; } return hr; } static HRESULT ScaWebFindFreeBase( __in IMSAdminBase* piMetabase, __in_xcount(unknown) SCA_WEB* pswList, __in int iSiteId, __in_z LPCWSTR wzDescription, __out_ecount(cchWebBase) LPWSTR wzWebBase, __in DWORD cchWebBase ) { Assert(piMetabase); HRESULT hr = S_OK; WCHAR wzKey[METADATA_MAX_NAME_LEN]; WCHAR wzSubkey[METADATA_MAX_NAME_LEN]; DWORD* prgdwSubKeys = NULL; DWORD cSubKeys = 128; DWORD cSubKeysFilled = 0; DWORD dwKey; METADATA_RECORD mr; ::ZeroMemory(&mr, sizeof(METADATA_RECORD)); mr.dwMDIdentifier = MD_KEY_TYPE; mr.dwMDAttributes = 0; mr.dwMDUserType = IIS_MD_UT_SERVER; mr.dwMDDataType = STRING_METADATA; mr.dwMDDataLen = 0; mr.pbMDData = NULL; if (MSI_NULL_INTEGER == iSiteId || -1 == iSiteId) { prgdwSubKeys = static_cast(MemAlloc(cSubKeys * sizeof(DWORD), TRUE)); ExitOnNull(prgdwSubKeys, hr, E_OUTOFMEMORY, "failed to allocate space for web site keys"); // loop through the "web keys" looking for the "IIsWebServer" key that matches wzWeb for (DWORD dwIndex = 0; SUCCEEDED(hr); ++dwIndex) { hr = piMetabase->EnumKeys(METADATA_MASTER_ROOT_HANDLE, L"/LM/W3SVC", wzSubkey, dwIndex); if (SUCCEEDED(hr)) { hr = ::StringCchPrintfW(wzKey, countof(wzKey), L"/LM/W3SVC/%s", wzSubkey); ExitOnFailure(hr, "Failed remember key."); hr = MetaGetValue(piMetabase, METADATA_MASTER_ROOT_HANDLE, wzKey, &mr); if (MD_ERROR_DATA_NOT_FOUND == hr || HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND) == hr) { hr = S_FALSE; // didn't find anything, try next one continue; } ExitOnFailure(hr, "Failed to get key from metabase while searching for free web root"); // if we have a IIsWebServer get the address information if (0 == lstrcmpW(L"IIsWebServer", (LPCWSTR)mr.pbMDData)) { if (cSubKeysFilled >= cSubKeys) { cSubKeys = cSubKeys * 2; prgdwSubKeys = static_cast(MemReAlloc(prgdwSubKeys, cSubKeys * sizeof(DWORD), FALSE)); ExitOnNull(prgdwSubKeys, hr, E_OUTOFMEMORY, "failed to allocate space for web site keys"); } prgdwSubKeys[cSubKeysFilled] = wcstol(wzSubkey, NULL, 10); ++cSubKeysFilled; Sort(prgdwSubKeys, cSubKeysFilled); } } } if (E_NOMOREITEMS == hr) { hr = S_OK; } ExitOnFailure(hr, "Failed to find free web root"); // Add all the webs created in memory. CONST WCHAR *pcchSlash; for (SCA_WEB* psw = pswList; psw; psw = psw->pswNext) { // Don't process webs that don't have a base if (!*psw->wzWebBase) { continue; } // find the last slash in the web root because the root # is after it pcchSlash = NULL; for (CONST WCHAR *pcch = psw->wzWebBase; pcch && *pcch; ++pcch) { if (L'/' == *pcch) { pcchSlash = pcch; } } // In case we don't find a slash, error out ExitOnNull(pcchSlash, hr, E_INVALIDARG, "Failed to find a slash in the web root: %ls", psw->wzWebBase); prgdwSubKeys[cSubKeysFilled] = wcstol(pcchSlash + 1, NULL, 10); ++cSubKeysFilled; Sort(prgdwSubKeys, cSubKeysFilled); if (cSubKeysFilled >= cSubKeys) { cSubKeys = cSubKeys * 2; prgdwSubKeys = static_cast(MemReAlloc(prgdwSubKeys, cSubKeys * sizeof(DWORD), FALSE)); ExitOnNull(prgdwSubKeys, hr, E_OUTOFMEMORY, "failed to allocate space for web site keys"); } } // Find the lowest free web root. dwKey = (-1 == iSiteId) ? SiteIdFromDescription(wzDescription) : 1; for (DWORD i = 0; i < cSubKeysFilled; ++i) { if (dwKey == prgdwSubKeys[i]) { ++dwKey; } else if (dwKey < prgdwSubKeys[i]) { break; } } } else { dwKey = iSiteId; } hr = ::StringCchPrintfW(wzWebBase, cchWebBase, L"/LM/W3SVC/%u", dwKey); ExitOnFailure(hr, "failed to format web base with key: %u", dwKey); LExit: MetaFreeValue(&mr); if (prgdwSubKeys) { MemFree(prgdwSubKeys); } return hr; } static HRESULT ScaWebWrite( __in IMSAdminBase* piMetabase, __in SCA_WEB* psw, __in SCA_APPPOOL * psapList) { HRESULT hr = S_OK; UINT ui = 0; WCHAR wzIP[64]; WCHAR wzBindings[1024]; WCHAR wzSecureBindings[1024]; WCHAR* pcchNext; // used to properly create the MULTI_SZ DWORD cchPcchNext; WCHAR* pcchSecureNext ; // used to properly create the MULTI_SZ DWORD cchPcchSecureNext; // if the web root doesn't exist create it if (!psw->fBaseExists) { hr = ScaCreateWeb(piMetabase, psw->wzKey, psw->wzWebBase); ExitOnFailure(hr, "Failed to create web"); } else if (psw->iAttributes & SWATTRIB_NOCONFIGUREIFEXISTS) // if we're not supposed to configure existing webs, bail { Assert(psw->fBaseExists); hr = S_FALSE; WcaLog(LOGMSG_VERBOSE, "Skipping configuration of existing web: %ls", psw->wzKey); ExitFunction(); } // put the secure and non-secure bindings together as MULTI_SZs ::ZeroMemory(wzBindings, sizeof(wzBindings)); pcchNext = wzBindings; cchPcchNext = countof(wzBindings); ::ZeroMemory(wzSecureBindings, sizeof(wzSecureBindings)); pcchSecureNext = wzSecureBindings; cchPcchSecureNext = countof(wzSecureBindings); // set the IP address appropriately if (0 == lstrcmpW(psw->swaKey.wzIP, L"*")) { ::ZeroMemory(wzIP, sizeof(wzIP)); } else { hr = ::StringCchCopyW(wzIP, countof(wzIP), psw->swaKey.wzIP); ExitOnFailure(hr, "Failed to copy IP string"); } WCHAR wzBinding[256]; hr = ::StringCchPrintfW(wzBinding, countof(wzBinding), L"%s:%d:%s", wzIP, psw->swaKey.iPort, psw->swaKey.wzHeader); ExitOnFailure(hr, "Failed to format IP:Port:Header binding string"); if (psw->swaKey.fSecure) { hr = ::StringCchCopyW(pcchSecureNext, cchPcchSecureNext, wzBinding); ExitOnFailure(hr, "Failed to copy binding string to securenext string"); pcchSecureNext += lstrlenW(wzBinding) + 1; cchPcchSecureNext -= lstrlenW(wzBinding) + 1; } else { hr = ::StringCchCopyW(pcchNext, cchPcchNext, wzBinding); ExitOnFailure(hr, "Failed to copy binding string to next string"); pcchNext += lstrlenW(wzBinding) + 1; cchPcchNext -= lstrlenW(wzBinding) + 1; } for (ui = 0; ui < psw->cExtraAddresses; ++ui) { // set the IP address appropriately if (0 == lstrcmpW(psw->swaExtraAddresses[ui].wzIP, L"*")) { ::ZeroMemory(wzIP, sizeof(wzIP)); } else { hr = ::StringCchCopyW(wzIP, countof(wzIP), psw->swaExtraAddresses[ui].wzIP); ExitOnFailure(hr, "Failed to copy extra addresses IP to IP string"); } hr = ::StringCchPrintfW(wzBinding, countof(wzBinding), L"%s:%d:%s", wzIP, psw->swaExtraAddresses[ui].iPort, psw->swaExtraAddresses[ui].wzHeader); ExitOnFailure(hr, "Failed to format IP:Port:Header binding string for extra address"); if (psw->swaExtraAddresses[ui].fSecure) { hr = ::StringCchCopyW(pcchSecureNext, cchPcchSecureNext, wzBinding); ExitOnFailure(hr, "Failed to copy binding string to securenext string for extra address"); pcchSecureNext += lstrlenW(wzBinding) + 1; cchPcchSecureNext -= lstrlenW(wzBinding) + 1; } else { hr = ::StringCchCopyW(pcchNext, cchPcchNext, wzBinding); ExitOnFailure(hr, "Failed to copy binding string to next string for extra address"); pcchNext += lstrlenW(wzBinding) + 1; cchPcchNext -= lstrlenW(wzBinding) + 1; } } // Delete the existing secure bindings metabase value, as having one while SSLCertHash and SSLStoreName aren't both set correctly can result in // 0x80070520 (ERROR_NO_SUCH_LOGON_SESSION) errors in some situations on IIS7. Clearing this value first and then setting it after the install has completed // allows the two aforementioned properties to exist in an intermediate state without errors hr = ScaDeleteMetabaseValue(piMetabase, psw->wzWebBase, L"", MD_SECURE_BINDINGS, MULTISZ_METADATA); ExitOnFailure(hr, "Failed to temporarily delete secure bindings for Web"); // now write the bindings to the metabase hr = ScaWriteMetabaseValue(piMetabase, psw->wzWebBase, L"", MD_SERVER_BINDINGS, METADATA_NO_ATTRIBUTES, IIS_MD_UT_SERVER, MULTISZ_METADATA, wzBindings); ExitOnFailure(hr, "Failed to write server bindings for Web"); // write the target path for the web's directory to the metabase hr = ScaWriteMetabaseValue(piMetabase, psw->wzWebBase, L"/Root", MD_VR_PATH, METADATA_INHERIT, IIS_MD_UT_FILE, STRING_METADATA, psw->wzDirectory); ExitOnFailure(hr, "Failed to write virtual root path for Web"); // write the description for the web to the metabase hr = ScaWriteMetabaseValue(piMetabase, psw->wzWebBase, L"", MD_SERVER_COMMENT, METADATA_INHERIT, IIS_MD_UT_SERVER, STRING_METADATA, psw->wzDescription); ExitOnFailure(hr, "Failed to write description for Web"); ui = psw->iConnectionTimeout; if (MSI_NULL_INTEGER != ui) { hr = ScaWriteMetabaseValue(piMetabase, psw->wzWebBase, L"", MD_CONNECTION_TIMEOUT, METADATA_NO_ATTRIBUTES, IIS_MD_UT_SERVER, DWORD_METADATA, (LPVOID)((DWORD_PTR)ui)); ExitOnFailure(hr, "Failed to write connection timeout for Web"); } ui = psw->iState; if (MSI_NULL_INTEGER != ui) { if (2 == ui) { ui = 1; hr = ScaWriteMetabaseValue(piMetabase, psw->wzWebBase, L"", MD_SERVER_AUTOSTART, METADATA_INHERIT, IIS_MD_UT_SERVER, DWORD_METADATA, (LPVOID)((DWORD_PTR)ui)); ExitOnFailure(hr, "Failed to write auto start flag for Web"); ui = 2; } if (1 == ui || 2 == ui) { ui = 1; // start command hr = ScaWriteMetabaseValue(piMetabase, psw->wzWebBase, L"", MD_SERVER_COMMAND, METADATA_NO_ATTRIBUTES, IIS_MD_UT_SERVER, DWORD_METADATA, (LPVOID)((DWORD_PTR)ui)); ExitOnFailure(hr, "Failed to start Web"); } else if (0 == ui) { ui = 2; // stop command hr = ScaWriteMetabaseValue(piMetabase, psw->wzWebBase, L"", MD_SERVER_COMMAND, METADATA_NO_ATTRIBUTES, IIS_MD_UT_SERVER, DWORD_METADATA, (LPVOID)((DWORD_PTR)ui)); ExitOnFailure(hr, "Failed to stop Web"); } else { hr = E_UNEXPECTED; ExitOnFailure(hr, "Unexpected value for Web State"); } } WCHAR wzRootOfWeb[METADATA_MAX_NAME_LEN]; hr = ::StringCchPrintfW(wzRootOfWeb, countof(wzRootOfWeb), L"%s/Root", psw->wzWebBase); ExitOnFailure(hr, "Failed to allocate WebBase/Root string for root of web"); // write the web dirproperties information if (psw->fHasProperties) { hr = ScaWriteWebDirProperties(piMetabase, wzRootOfWeb, &psw->swp); ExitOnFailure(hr, "Failed to write web security information to metabase"); } // write the application information if (psw->fHasApplication) { // On reinstall, we have to uninstall the old application, otherwise a duplicate will be created if (WcaIsReInstalling(psw->isInstalled, psw->isAction)) { hr = ScaDeleteApp(piMetabase, wzRootOfWeb); ExitOnFailure(hr, "Failed to remove application for WebDir as part of a reinstall"); } hr = ScaWriteWebApplication(piMetabase, wzRootOfWeb, &psw->swapp, psapList); ExitOnFailure(hr, "Failed to write web application information to metabase"); } // write the SSL certificate information if (psw->pswscList) { hr = ScaSslCertificateWriteMetabase(piMetabase, psw->wzWebBase, psw->pswscList); ExitOnFailure(hr, "Failed to write SSL certificates for Web site: %ls", psw->wzKey); } hr = ScaWriteMetabaseValue(piMetabase, psw->wzWebBase, L"", MD_SECURE_BINDINGS, METADATA_NO_ATTRIBUTES, IIS_MD_UT_SERVER, MULTISZ_METADATA, wzSecureBindings); ExitOnFailure(hr, "Failed to write secure bindings for Web"); // write the headers if (psw->pshhList) { hr = ScaWriteHttpHeader(piMetabase, wzRootOfWeb, psw->pshhList); ExitOnFailure(hr, "Failed to write custom HTTP headers for Web site: %ls", psw->wzKey); } // write the errors if (psw->psweList) { hr = ScaWriteWebError(piMetabase, weptWeb, psw->wzWebBase, psw->psweList); ExitOnFailure(hr, "Failed to write custom web errors for Web site: %ls", psw->wzKey); } // write the mimetypes if (psw->psmm) { hr = ScaWriteMimeMap(piMetabase, wzRootOfWeb, psw->psmm); ExitOnFailure(hr, "Failed to write mimemap for Web site: %ls", psw->wzKey); } // write the log information to the metabase if (psw->fHasLog) { hr = ScaWriteWebLog(piMetabase, psw->wzWebBase, &psw->swl); ExitOnFailure(hr, "Failed to write web log information to metabase"); } LExit: return hr; } static HRESULT ScaWebRemove( __in IMSAdminBase* piMetabase, __in const SCA_WEB* psw ) { HRESULT hr = S_OK; // simply remove the root key and everything else is pulled at the same time hr = ScaDeleteMetabaseKey(piMetabase, psw->wzWebBase, L""); ExitOnFailure(hr, "Failed to remove web '%ls' from metabase", psw->wzKey); LExit: return hr; } static DWORD SiteIdFromDescription( __in_z LPCWSTR wzDescription ) { LPCWSTR pwz = wzDescription; DWORD dwSiteId = 0; while (pwz && *pwz) { WCHAR ch = *pwz & 0xdf; dwSiteId = (dwSiteId * 101) + ch; ++pwz; } return (dwSiteId % INT_MAX) + 1; } // insertion sort static void Sort( __in_ecount(cArray) DWORD dwArray[], __in int cArray ) { int i, j; DWORD dwData; for (i = 1; i < cArray; ++i) { dwData = dwArray[i]; j = i - 1; while (0 <= j && dwArray[j] > dwData) { dwArray[j + 1] = dwArray[j]; j--; } dwArray[j + 1] = dwData; } }