From 95a5a8f9efef02ddcec5b3f69be99a00d71a802a Mon Sep 17 00:00:00 2001 From: Sean Hall Date: Sun, 16 Dec 2018 21:19:24 -0600 Subject: Import implementation of IisCA from old repo's scasched/scaexec. --- src/ca/scaweberr.cpp | 371 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 371 insertions(+) create mode 100644 src/ca/scaweberr.cpp (limited to 'src/ca/scaweberr.cpp') diff --git a/src/ca/scaweberr.cpp b/src/ca/scaweberr.cpp new file mode 100644 index 00000000..2441f006 --- /dev/null +++ b/src/ca/scaweberr.cpp @@ -0,0 +1,371 @@ +// 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" + +enum eWebErrorQuery { weqErrorCode = 1, weqSubCode, weqParentType, weqParentValue, weqFile, weqURL }; + +static HRESULT AddWebErrorToList(SCA_WEB_ERROR** ppsweList); + +void ScaWebErrorFreeList(SCA_WEB_ERROR *psweList) +{ + SCA_WEB_ERROR *psweDelete = psweList; + while (psweList) + { + psweDelete = psweList; + psweList = psweList->psweNext; + + MemFree(psweDelete); + } +} + +HRESULT ScaWebErrorRead( + SCA_WEB_ERROR **ppsweList, + __inout LPWSTR *ppwzCustomActionData + ) +{ +// AssertSz(0, "Debug ScaWebErrorRead here"); + HRESULT hr = S_OK; + MSIHANDLE hRec; + LPWSTR pwzData = NULL; + SCA_WEB_ERROR* pswe; + WCA_WRAPQUERY_HANDLE hWrapQuery = NULL; + + ExitOnNull(ppsweList, hr, E_INVALIDARG, "Failed to read web error, because no web error was provided to read"); + + hr = WcaBeginUnwrapQuery(&hWrapQuery, ppwzCustomActionData); + ExitOnFailure(hr, "Failed to unwrap query for ScaAppPoolRead"); + + if (0 == WcaGetQueryRecords(hWrapQuery)) + { + WcaLog(LOGMSG_VERBOSE, "Skipping ScaWebErrorRead() - required tables not present."); + ExitFunction1(hr = S_FALSE); + } + + // loop through all the web errors + while (S_OK == (hr = WcaFetchWrappedRecord(hWrapQuery, &hRec))) + { + hr = AddWebErrorToList(ppsweList); + ExitOnFailure(hr, "failed to add web error to list"); + + pswe = *ppsweList; + + hr = WcaGetRecordInteger(hRec, weqErrorCode, &(pswe->iErrorCode)); + ExitOnFailure(hr, "failed to get IIsWebError.ErrorCode"); + + hr = WcaGetRecordInteger(hRec, weqSubCode, &(pswe->iSubCode)); + ExitOnFailure(hr, "failed to get IIsWebError.SubCode"); + + hr = WcaGetRecordInteger(hRec, weqParentType, &(pswe->iParentType)); + ExitOnFailure(hr, "failed to get IIsWebError.ParentType"); + + hr = WcaGetRecordString(hRec, weqParentValue, &pwzData); + ExitOnFailure(hr, "Failed to get IIsWebError.ParentValue"); + hr = ::StringCchCopyW(pswe->wzParentValue, countof(pswe->wzParentValue), pwzData); + ExitOnFailure(hr, "Failed to copy IIsWebError.ParentValue"); + + hr = WcaGetRecordString(hRec, weqFile, &pwzData); + ExitOnFailure(hr, "Failed to get IIsWebError.File"); + hr = ::StringCchCopyW(pswe->wzFile, countof(pswe->wzFile), pwzData); + ExitOnFailure(hr, "Failed to copy IIsWebError.File"); + + hr = WcaGetRecordString(hRec, weqURL, &pwzData); + ExitOnFailure(hr, "Failed to get IIsWebError.URL"); + hr = ::StringCchCopyW(pswe->wzURL, countof(pswe->wzURL), pwzData); + ExitOnFailure(hr, "Failed to copy IIsWebError.URL"); + + // If they've specified both a file and a URL, that's invalid + if (*(pswe->wzFile) && *(pswe->wzURL)) + ExitOnFailure(hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA), "Both File and URL specified for web error. File: %ls, URL: %ls", pswe->wzFile, pswe->wzURL); + } + + if (E_NOMOREITEMS == hr) + { + hr = S_OK; + } + ExitOnFailure(hr, "Failure while processing web errors"); + +LExit: + WcaFinishUnwrapQuery(hWrapQuery); + + ReleaseStr(pwzData); + + return hr; +} + +HRESULT ScaGetWebError(int iParentType, LPCWSTR wzParentValue, SCA_WEB_ERROR **ppsweList, SCA_WEB_ERROR **ppsweOut) +{ + HRESULT hr = S_OK; + SCA_WEB_ERROR* psweAdd = NULL; + SCA_WEB_ERROR* psweLast = NULL; + + *ppsweOut = NULL; + + if (!*ppsweList) + return hr; + + SCA_WEB_ERROR* pswe = *ppsweList; + while (pswe) + { + if (iParentType == pswe->iParentType && 0 == lstrcmpW(wzParentValue, pswe->wzParentValue)) + { + // Found a match, take this one out of the list and add it to the matched out list + psweAdd = pswe; + + if (psweLast) + { + // If we're not at the beginning of the list tell the last node about it's new next (since we're taking away it's current next) + psweLast->psweNext = psweAdd->psweNext; + } + else + { + // If we are at the beginning (no psweLast) update the beginning (since we're taking it) + *ppsweList = pswe->psweNext; + } + pswe = pswe->psweNext; // move on + + // Add the one we've removed to the beginning of the out list + psweAdd->psweNext = *ppsweOut; + *ppsweOut = psweAdd; + } + else + { + psweLast = pswe; // remember the last we that didn't match + pswe = pswe->psweNext; // move on + } + } + + return hr; +} + +HRESULT ScaWriteWebError(IMSAdminBase* piMetabase, int iParentType, LPCWSTR wzRoot, SCA_WEB_ERROR* psweList) +{ +// AssertSz(0, "Debug ScaWriteWebError here"); + Assert(*wzRoot && psweList); + + HRESULT hr = S_OK; + + DWORD cchData = 0; + LPWSTR pwzSearchKey = NULL; + LPWSTR pwz = NULL; + LPWSTR pwzErrors = NULL; + + LPWSTR pwzCodeSubCode = NULL; + LPWSTR pwzAcceptableCodeSubCode = NULL; + LPCWSTR wzFoundCodeSubCode = NULL; + DWORD_PTR dwFoundCodeSubCodeIndex = 0xFFFFFFFF; + BOOL fOldValueFound = FALSE; + LPWSTR pwzAcceptableErrors = NULL; + + LPWSTR pwzNewError = NULL; + + METADATA_RECORD mr; + ::ZeroMemory(&mr, sizeof(mr)); + + ExitOnNull(piMetabase, hr, E_INVALIDARG, "Failed to write web error, because no metabase was provided"); + ExitOnNull(wzRoot, hr, E_INVALIDARG, "Failed to write web error, because no root was provided"); + + // get the set of all valid custom errors from the metabase + mr.dwMDIdentifier = MD_CUSTOM_ERROR_DESC; + mr.dwMDAttributes = METADATA_INHERIT; + mr.dwMDUserType = IIS_MD_UT_SERVER; + mr.dwMDDataType = ALL_METADATA; + mr.dwMDDataLen = cchData = 0; + mr.pbMDData = NULL; + + hr = MetaGetValue(piMetabase, METADATA_MASTER_ROOT_HANDLE, L"/LM/W3SVC/Info", &mr); + ExitOnFailure(hr, "Unable to get set of acceptable error codes for this server."); + + pwzAcceptableErrors = reinterpret_cast(mr.pbMDData); + + // Check if web errors already exist here + mr.dwMDIdentifier = MD_CUSTOM_ERROR; + mr.dwMDAttributes = METADATA_INHERIT; + mr.dwMDUserType = IIS_MD_UT_SERVER; + mr.dwMDDataType = ALL_METADATA; + mr.dwMDDataLen = cchData = 0; + mr.pbMDData = NULL; + + hr = MetaGetValue(piMetabase, METADATA_MASTER_ROOT_HANDLE, wzRoot, &mr); + if (HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND) == hr || MD_ERROR_DATA_NOT_FOUND == hr) + { + // + // If we don't have one already, find an appropriate one to start with + // + + // we can walk up key by key and look for custom errors to inherit + + hr = StrAllocConcat(&pwzSearchKey, wzRoot, 0); + ExitOnFailure(hr, "Failed to copy root string: %ls", wzRoot); + + pwz = pwzSearchKey + lstrlenW(pwzSearchKey); + + while (NULL == pwzErrors) + { + // find the last slash + while (*pwz != '/' && pwz != pwzSearchKey) + pwz --; + + if (pwz == pwzSearchKey) + break; + + *pwz = L'\0'; + + // Try here. If it's not found, keep walking up the path + hr = MetaGetValue(piMetabase, METADATA_MASTER_ROOT_HANDLE, pwzSearchKey, &mr); + if (HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND) == hr || MD_ERROR_DATA_NOT_FOUND == hr) + hr = S_FALSE; + ExitOnFailure(hr, "failed to discover default error values to start with for web root: %ls while walking up the tree", wzRoot); + + if (S_OK == hr) + { + pwzErrors = reinterpret_cast(mr.pbMDData); + break; + } + + // Don't keep going if we're at the root + if (0 == lstrcmpW(pwz + 1, L"W3SVC")) + break; + } + } + else + { + pwzErrors = reinterpret_cast(mr.pbMDData); + } + ExitOnFailure(hr, "failed to discover default error values to start with for web root: %ls", wzRoot); + + // The above code should have come up with some value to start pwzErrors off with. Make sure it did. + if (NULL == pwzErrors) + { + ExitOnFailure(hr = E_UNEXPECTED, "failed to discover default error values to start with for web root: %ls", wzRoot); + } + + // Loop through the web errors + for (SCA_WEB_ERROR* pswe = psweList; pswe; pswe = pswe->psweNext) + { + // Assume that we will have to replace + fOldValueFound = TRUE; + + // If the subcode is 0, that means "*" in MD_CUSTOM_ERROR (thus the special formatting logic) + if (0 == pswe->iSubCode) + { + hr = StrAllocFormatted(&pwzCodeSubCode, L"%d,*", pswe->iErrorCode); + ExitOnFailure(hr, "failed to create error code string while installing web error"); + } + else + { + hr = StrAllocFormatted(&pwzCodeSubCode, L"%d,%d", pswe->iErrorCode, pswe->iSubCode); + ExitOnFailure(hr, "failed to create error code,subcode string while installing web error"); + } + + hr = MultiSzFindSubstring(pwzErrors, pwzCodeSubCode, &dwFoundCodeSubCodeIndex, &wzFoundCodeSubCode); + ExitOnFailure(hr, "failed to find existing error code,subcode: %ls", pwzCodeSubCode); + + // If we didn't find this error code/sub code pair in the list already, make sure it's acceptable to add + if (S_FALSE == hr) + { + // + // Make sure this error code/sub code pair is in the "acceptable" list + // + + // If the subcode is 0, that means "0" in MD_CUSTOM_ERROR_DESC (no special formatting logic needed) + hr = StrAllocFormatted(&pwzAcceptableCodeSubCode, L"%d,%d", pswe->iErrorCode, pswe->iSubCode); + ExitOnFailure(hr, "failed to create error code,subcode string while installing web error"); + + // We don't care where it is, just whether it's there or not + hr = MultiSzFindSubstring(pwzAcceptableErrors, pwzAcceptableCodeSubCode, NULL, NULL); + ExitOnFailure(hr, "failed to find whether or not error code, subcode: %ls is supported", pwzCodeSubCode); + + if (S_FALSE == hr) + { + WcaLog(LOGMSG_VERBOSE, "Skipping error code, subcode: %ls because it is not supported by the server.", pwzCodeSubCode); + continue; + } + + // If we didn't find it (and its an acceptable error) then we have nothing to replace + fOldValueFound = FALSE; + } + + // Set up the new error string if needed + if (*(pswe->wzFile)) + { + hr = StrAllocFormatted(&pwzNewError, L"%s,FILE,%s", pwzCodeSubCode, pswe->wzFile); + ExitOnFailure(hr, "failed to create new error code string with code,subcode: %ls, file: %ls", pwzCodeSubCode, pswe->wzFile); + } + else if (*(pswe->wzURL)) + { + hr = StrAllocFormatted(&pwzNewError, L"%s,URL,%s", pwzCodeSubCode, pswe->wzURL); + ExitOnFailure(hr, "failed to create new error code string with code,subcode: %ls, file: %ls", pwzCodeSubCode, pswe->wzFile); + } + else if (fOldValueFound) + { + // If no File or URL was specified, they want a default error so remove the old value from the MULTISZ and move on + hr = MultiSzRemoveString(&pwzErrors, dwFoundCodeSubCodeIndex); + ExitOnFailure(hr, "failed to remove string for error code sub code: %ls in order to make it 'default'", pwzCodeSubCode); + continue; + } + + // If we have something to replace, replace it, otherwise, put it at the beginning (order shouldn't matter) + if (fOldValueFound) + { + hr = MultiSzReplaceString(&pwzErrors, dwFoundCodeSubCodeIndex, pwzNewError); + ExitOnFailure(hr, "failed to replace old error string with new error string for error code,subcode: %ls", pwzCodeSubCode); + } + else + { + hr = MultiSzPrepend(&pwzErrors, NULL, pwzNewError); + ExitOnFailure(hr, "failed to prepend new error string for error code,subcode: %ls", pwzCodeSubCode); + } + } + + // now write the CustomErrors to the metabase + if (weptWeb == iParentType) + { + hr = ScaWriteMetabaseValue(piMetabase, wzRoot, L"/Root", MD_CUSTOM_ERROR, METADATA_INHERIT, IIS_MD_UT_FILE, MULTISZ_METADATA, pwzErrors); + ExitOnFailure(hr, "Failed to write Web Error to /Root"); + } + else + { + hr = ScaWriteMetabaseValue(piMetabase, wzRoot, NULL, MD_CUSTOM_ERROR, METADATA_INHERIT, IIS_MD_UT_FILE, MULTISZ_METADATA, pwzErrors); + ExitOnFailure(hr, "Failed to write Web Error"); + } + +LExit: + ReleaseStr(pwzErrors); + ReleaseStr(pwzSearchKey); + ReleaseStr(pwzCodeSubCode); + ReleaseStr(pwzAcceptableCodeSubCode); + ReleaseStr(pwzAcceptableErrors); + + return hr; +} + +static HRESULT AddWebErrorToList(SCA_WEB_ERROR** ppsweList) +{ + HRESULT hr = S_OK; + + SCA_WEB_ERROR* pswe = static_cast(MemAlloc(sizeof(SCA_WEB_ERROR), TRUE)); + ExitOnNull(pswe, hr, E_OUTOFMEMORY, "failed to allocate memory for new web error list element"); + + pswe->psweNext = *ppsweList; + *ppsweList = pswe; + +LExit: + return hr; +} + +HRESULT ScaWebErrorCheckList(SCA_WEB_ERROR* psweList) +{ + if (!psweList) + { + return S_OK; + } + + while (psweList) + { + WcaLog(LOGMSG_STANDARD, "WebError code: %d subcode: %d for parent: %ls not used!", psweList->iErrorCode, psweList->iSubCode, psweList->wzParentValue); + psweList = psweList->psweNext; + } + + return E_FAIL; +} + -- cgit v1.2.3-55-g6feb