From cd95dec00a9c9b4554084d2ec8c56370e0cc188b Mon Sep 17 00:00:00 2001 From: Stephen Griffin Date: Mon, 27 Mar 2023 08:32:00 -0400 Subject: [PATCH 1/8] Add interesting prop filters to dumpstore --- MrMapi/MMContents.cpp | 27 ++++-- MrMapi/MMPropTag.cpp | 92 +----------------- MrMapi/MrMAPI.cpp | 3 +- MrMapi/mmcli.cpp | 22 +++++ MrMapi/mmcli.h | 2 + core/mapi/cache/namedProps.cpp | 110 ++++++++++++++++++++++ core/mapi/cache/namedProps.h | 5 + core/mapi/mapiOutput.cpp | 1 + core/mapi/processor/dumpStore.cpp | 151 ++++++++++++++++++++++++++++++ core/mapi/processor/dumpStore.h | 9 ++ core/utility/cli.h | 1 + core/utility/output.h | 1 + 12 files changed, 326 insertions(+), 98 deletions(-) diff --git a/MrMapi/MMContents.cpp b/MrMapi/MMContents.cpp index 883809e84..ecce3c84c 100644 --- a/MrMapi/MMContents.cpp +++ b/MrMapi/MMContents.cpp @@ -16,24 +16,24 @@ void DumpContentsTable( _In_opt_ LPSRestriction lpRes) { output::DebugPrint( - output::dbgLevel::Generic, + output::dbgLevel::Console, L"DumpContentsTable: Outputting folder %ws from profile %ws to %ws\n", lpszFolder, lpszProfile, lpszDir); if (cli::switchContents.isSet()) - output::DebugPrint(output::dbgLevel::Generic, L"DumpContentsTable: Outputting Contents\n"); + output::DebugPrint(output::dbgLevel::Console, L"DumpContentsTable: Outputting Contents\n"); if (cli::switchAssociatedContents.isSet()) - output::DebugPrint(output::dbgLevel::Generic, L"DumpContentsTable: Outputting Associated Contents\n"); + output::DebugPrint(output::dbgLevel::Console, L"DumpContentsTable: Outputting Associated Contents\n"); if (cli::switchMSG.isSet()) - output::DebugPrint(output::dbgLevel::Generic, L"DumpContentsTable: Outputting as MSG\n"); + output::DebugPrint(output::dbgLevel::Console, L"DumpContentsTable: Outputting as MSG\n"); if (cli::switchMoreProperties.isSet()) - output::DebugPrint(output::dbgLevel::Generic, L"DumpContentsTable: Will retry stream properties\n"); + output::DebugPrint(output::dbgLevel::Console, L"DumpContentsTable: Will retry stream properties\n"); if (cli::switchSkip.isSet()) - output::DebugPrint(output::dbgLevel::Generic, L"DumpContentsTable: Will skip attachments\n"); - if (cli::switchList.isSet()) output::DebugPrint(output::dbgLevel::Generic, L"DumpContentsTable: List only mode\n"); + output::DebugPrint(output::dbgLevel::Console, L"DumpContentsTable: Will skip attachments\n"); + if (cli::switchList.isSet()) output::DebugPrint(output::dbgLevel::Console, L"DumpContentsTable: List only mode\n"); if (ulCount) - output::DebugPrint(output::dbgLevel::Generic, L"DumpContentsTable: Limiting output to %u messages.\n", ulCount); + output::DebugPrint(output::dbgLevel::Console, L"DumpContentsTable: Limiting output to %u messages.\n", ulCount); if (lpFolder) { @@ -43,6 +43,17 @@ void DumpContentsTable( MyDumpStore.InitFolder(lpFolder); MyDumpStore.InitFolderPathRoot(lpszDir); MyDumpStore.InitFolderContentsRestriction(lpRes); + // If any properties passed in, pass them along. + if (cli::switchFindProperty.size() != 0) + { + MyDumpStore.InitProperties(cli::switchFindProperty); + } + + if (cli::switchFindNamedProperty.size() != 0) + { + MyDumpStore.InitNamedProperties(cli::switchFindNamedProperty); + } + if (cli::switchMSG.isSet()) MyDumpStore.EnableMSG(); if (cli::switchList.isSet()) MyDumpStore.EnableList(); if (ulCount) diff --git a/MrMapi/MMPropTag.cpp b/MrMapi/MMPropTag.cpp index 373ecae3a..28accdd87 100644 --- a/MrMapi/MMPropTag.cpp +++ b/MrMapi/MMPropTag.cpp @@ -4,91 +4,11 @@ #include #include #include -#include #include #include #include #include - -// Searches a NAMEID_ARRAY_ENTRY array for a target dispid. -// Exact matches are those that match -// If no hits, then ulNoMatch should be returned for lpulFirstExact -void FindNameIDArrayMatches( - _In_ LONG lTarget, - _In_count_(ulMyArray) NAMEID_ARRAY_ENTRY* MyArray, - _In_ ULONG ulMyArray, - _Out_ ULONG* lpulNumExacts, - _Out_ ULONG* lpulFirstExact) noexcept -{ - ULONG ulLowerBound = 0; - auto ulUpperBound = ulMyArray - 1; // ulMyArray-1 is the last entry - auto ulMidPoint = (ulUpperBound + ulLowerBound) / 2; - ULONG ulFirstMatch = cache::ulNoMatch; - ULONG ulLastMatch = cache::ulNoMatch; - - if (lpulNumExacts) *lpulNumExacts = 0; - if (lpulFirstExact) *lpulFirstExact = cache::ulNoMatch; - - // find A match - while (ulUpperBound - ulLowerBound > 1) - { - if (lTarget == MyArray[ulMidPoint].lValue) - { - ulFirstMatch = ulMidPoint; - break; - } - - if (lTarget < MyArray[ulMidPoint].lValue) - { - ulUpperBound = ulMidPoint; - } - else if (lTarget > MyArray[ulMidPoint].lValue) - { - ulLowerBound = ulMidPoint; - } - ulMidPoint = (ulUpperBound + ulLowerBound) / 2; - } - - // When we get down to two points, we may have only checked one of them - // Make sure we've checked the other - if (lTarget == MyArray[ulUpperBound].lValue) - { - ulFirstMatch = ulUpperBound; - } - else if (lTarget == MyArray[ulLowerBound].lValue) - { - ulFirstMatch = ulLowerBound; - } - - // Check that we got a match - if (cache::ulNoMatch != ulFirstMatch) - { - ulLastMatch = ulFirstMatch; // Remember the last match we've found so far - - // Scan backwards to find the first match - while (ulFirstMatch > 0 && lTarget == MyArray[ulFirstMatch - 1].lValue) - { - ulFirstMatch = ulFirstMatch - 1; - } - - // Scan forwards to find the real last match - // Last entry in the array is ulPropTagArray-1 - while (ulLastMatch + 1 < ulMyArray && lTarget == MyArray[ulLastMatch + 1].lValue) - { - ulLastMatch = ulLastMatch + 1; - } - - ULONG ulNumMatches = 0; - - if (cache::ulNoMatch != ulFirstMatch) - { - ulNumMatches = ulLastMatch - ulFirstMatch + 1; - } - - if (lpulNumExacts) *lpulNumExacts = ulNumMatches; - if (lpulFirstExact) *lpulFirstExact = ulFirstMatch; - } -} +#include // prints the type of a prop tag // no pretty stuff or \n - calling function gets to do that @@ -382,8 +302,7 @@ void PrintDispIDFromNum(_In_ ULONG ulDispID) noexcept wprintf(L"Dispid tag 0x%04lX:\n", ulDispID); - FindNameIDArrayMatches( - ulDispID, NameIDArray.data(), static_cast(NameIDArray.size()), &ulNumExacts, &ulFirstExactMatch); + cache::FindNameIDArrayMatches(ulDispID, &ulNumExacts, &ulFirstExactMatch); if (ulNumExacts > 0 && cache::ulNoMatch != ulFirstExactMatch) { @@ -412,12 +331,7 @@ void PrintDispIDFromName(_In_opt_z_ LPCWSTR lpszDispIDName) noexcept ULONG ulNumExacts = NULL; ULONG ulFirstExactMatch = cache::ulNoMatch; - FindNameIDArrayMatches( - NameIDArray[ulExactMatch].lValue, - NameIDArray.data(), - static_cast(NameIDArray.size()), - &ulNumExacts, - &ulFirstExactMatch); + cache::FindNameIDArrayMatches(NameIDArray[ulExactMatch].lValue, &ulNumExacts, &ulFirstExactMatch); // We're gonna skip at least one, so only print if we have more than one if (ulNumExacts > 1) diff --git a/MrMapi/MrMAPI.cpp b/MrMapi/MrMAPI.cpp index f76977d6f..ab5567de9 100644 --- a/MrMapi/MrMAPI.cpp +++ b/MrMapi/MrMAPI.cpp @@ -143,7 +143,8 @@ int wmain(_In_ int argc, _In_count_(argc) wchar_t* argv[]) registry::useGetPropList = true; registry::parseNamedProps = true; registry::cacheNamedProps = true; - registry::debugTag = 0; + registry::debugTag = static_cast(output::dbgLevel::Console); // Any debug logging with Console will print to the console now + output::initStubCallbacks(); SetDllDirectory(_T("")); diff --git a/MrMapi/mmcli.cpp b/MrMapi/mmcli.cpp index bf02c19d7..21e7e8b34 100644 --- a/MrMapi/mmcli.cpp +++ b/MrMapi/mmcli.cpp @@ -70,6 +70,8 @@ namespace cli option switchAccounts{L"Accounts", cmdmodeEnumAccounts, 0, 0, OPT_INITALL | OPT_PROFILE}; option switchIterate{L"Iterate", cmdmodeEnumAccounts, 0, 0, OPT_NOOPT}; option switchWizard{L"Wizard", cmdmodeEnumAccounts, 0, 0, OPT_NOOPT}; + option switchFindProperty{L"FindProperty", cmdmodeContents, 1, USHRT_MAX, OPT_INITALL}; + option switchFindNamedProperty{L"FindNamedProperty", cmdmodeContents, 1, USHRT_MAX, OPT_INITALL}; // If we want to add aliases for any switches, add them here option switchHelpAlias{L"Help", cmdmodeHelpFull, 0, 0, OPT_INITMFC}; @@ -129,6 +131,8 @@ namespace cli &switchAccounts, &switchIterate, &switchWizard, + &switchFindProperty, + &switchFindNamedProperty, // If we want to add aliases for any switches, add them here &switchHelpAlias, }; @@ -280,6 +284,18 @@ namespace cli switchWizard.name(), switchFlag.name(), switchProfile.name()); + wprintf( + L" MrMAPI -%ws [-%ws ] [-%ws ] [-%ws ]\n", + switchContents.name(), + switchFindProperty.name(), + switchFolder.name(), + switchOutput.name()); + wprintf( + L" MrMAPI -%ws [-%ws ] [-%ws ] [-%ws ]\n", + switchContents.name(), + switchFindNamedProperty.name(), + switchFolder.name(), + switchOutput.name()); if (bFull) { @@ -336,6 +352,12 @@ namespace cli wprintf(L" -Ms (or -%ws) Output as .MSG instead of XML.\n", switchMSG.name()); wprintf(L" -L (or -%ws) List details to screen and do not output files.\n", switchList.name()); wprintf(L" -Re (or -%ws) Restrict output to the 'count' most recent messages.\n", switchRecent.name()); + wprintf( + L" -FindP (or -%ws) Restrict output to messages which contain given properties.\n", + switchFindProperty.name()); + wprintf( + L" -FindN (or -%ws) Restrict output to messages which contain given named properties.\n", + switchFindNamedProperty.name()); wprintf(L"\n"); wprintf(L" Child Folders:\n"); wprintf(L" -Chi (or -%ws) Display child folders of selected folder.\n", switchChildFolders.name()); diff --git a/MrMapi/mmcli.h b/MrMapi/mmcli.h index 9cff84597..2beb75a65 100644 --- a/MrMapi/mmcli.h +++ b/MrMapi/mmcli.h @@ -54,6 +54,8 @@ namespace cli extern option switchAccounts; extern option switchIterate; extern option switchWizard; + extern option switchFindProperty; + extern option switchFindNamedProperty; extern std::vector g_options; diff --git a/core/mapi/cache/namedProps.cpp b/core/mapi/cache/namedProps.cpp index 99baffebb..00552e05e 100644 --- a/core/mapi/cache/namedProps.cpp +++ b/core/mapi/cache/namedProps.cpp @@ -243,6 +243,12 @@ namespace cache MAPIFreeBuffer(lpProp); + // If we don't have a signature, bypass the cache + if (sig.empty()) + { + return directMapi::GetIDsFromNames(lpMAPIProp, nameIDs, ulFlags); + } + return namedPropCache::GetIDsFromNames(lpMAPIProp, sig, nameIDs, ulFlags); } @@ -503,4 +509,108 @@ namespace cache return namedPropCacheEntry::empty(); } } + + // Searches a NAMEID_ARRAY_ENTRY array for a target dispid. + // Exact matches are those that match + // If no hits, then ulNoMatch should be returned for lpulFirstExact + void FindNameIDArrayMatches( + _In_ LONG lTarget, + _In_count_(ulMyArray) NAMEID_ARRAY_ENTRY* MyArray, + _In_ ULONG ulMyArray, + _Out_ ULONG* lpulNumExacts, + _Out_ ULONG* lpulFirstExact) noexcept + { + ULONG ulLowerBound = 0; + auto ulUpperBound = ulMyArray - 1; // ulMyArray-1 is the last entry + auto ulMidPoint = (ulUpperBound + ulLowerBound) / 2; + ULONG ulFirstMatch = cache::ulNoMatch; + ULONG ulLastMatch = cache::ulNoMatch; + + if (lpulNumExacts) *lpulNumExacts = 0; + if (lpulFirstExact) *lpulFirstExact = cache::ulNoMatch; + + // find A match + while (ulUpperBound - ulLowerBound > 1) + { + if (lTarget == MyArray[ulMidPoint].lValue) + { + ulFirstMatch = ulMidPoint; + break; + } + + if (lTarget < MyArray[ulMidPoint].lValue) + { + ulUpperBound = ulMidPoint; + } + else if (lTarget > MyArray[ulMidPoint].lValue) + { + ulLowerBound = ulMidPoint; + } + ulMidPoint = (ulUpperBound + ulLowerBound) / 2; + } + + // When we get down to two points, we may have only checked one of them + // Make sure we've checked the other + if (lTarget == MyArray[ulUpperBound].lValue) + { + ulFirstMatch = ulUpperBound; + } + else if (lTarget == MyArray[ulLowerBound].lValue) + { + ulFirstMatch = ulLowerBound; + } + + // Check that we got a match + if (cache::ulNoMatch != ulFirstMatch) + { + ulLastMatch = ulFirstMatch; // Remember the last match we've found so far + + // Scan backwards to find the first match + while (ulFirstMatch > 0 && lTarget == MyArray[ulFirstMatch - 1].lValue) + { + ulFirstMatch = ulFirstMatch - 1; + } + + // Scan forwards to find the real last match + // Last entry in the array is ulPropTagArray-1 + while (ulLastMatch + 1 < ulMyArray && lTarget == MyArray[ulLastMatch + 1].lValue) + { + ulLastMatch = ulLastMatch + 1; + } + + ULONG ulNumMatches = 0; + + if (cache::ulNoMatch != ulFirstMatch) + { + ulNumMatches = ulLastMatch - ulFirstMatch + 1; + } + + if (lpulNumExacts) *lpulNumExacts = ulNumMatches; + if (lpulFirstExact) *lpulFirstExact = ulFirstMatch; + } + } + + void FindNameIDArrayMatches(_In_ LONG lTarget, _Out_ ULONG* lpulNumExacts, _Out_ ULONG* lpulFirstExact) noexcept + { + FindNameIDArrayMatches( + lTarget, NameIDArray.data(), static_cast(NameIDArray.size()), lpulNumExacts, lpulFirstExact); + } + + // Search for properties matching lpszDispIDName on a substring + _Check_return_ LPNAMEID_ARRAY_ENTRY GetDispIDFromName(_In_z_ LPCWSTR lpszDispIDName) + { + if (!lpszDispIDName) return nullptr; + + const auto entry = find_if(begin(NameIDArray), end(NameIDArray), [&](NAMEID_ARRAY_ENTRY& nameID) noexcept { + if (0 == wcscmp(nameID.lpszName, lpszDispIDName)) + { + // PSUNKNOWN is used as a placeholder in NameIDArray - don't return matching entries + if (!IsEqualGUID(*nameID.lpGuid, guid::PSUNKNOWN)) return true; + } + + return false; + }); + + return entry != end(NameIDArray) ? &(*entry) : nullptr; + } } // namespace cache \ No newline at end of file diff --git a/core/mapi/cache/namedProps.h b/core/mapi/cache/namedProps.h index a3c3a6ba9..319f5ee83 100644 --- a/core/mapi/cache/namedProps.h +++ b/core/mapi/cache/namedProps.h @@ -1,5 +1,7 @@ #pragma once // Named Property Cache +#include +#include namespace cache { @@ -108,4 +110,7 @@ namespace cache _Check_return_ std::shared_ptr find( const std::vector>& list, const std::function&)>& compare); + + void FindNameIDArrayMatches(_In_ LONG lTarget, _Out_ ULONG* lpulNumExacts, _Out_ ULONG* lpulFirstExact) noexcept; + _Check_return_ LPNAMEID_ARRAY_ENTRY GetDispIDFromName(_In_z_ LPCWSTR lpszDispIDName); } // namespace cache \ No newline at end of file diff --git a/core/mapi/mapiOutput.cpp b/core/mapi/mapiOutput.cpp index 740de427f..256bb9698 100644 --- a/core/mapi/mapiOutput.cpp +++ b/core/mapi/mapiOutput.cpp @@ -697,5 +697,6 @@ namespace output } Output(ulDbgLvl, fFile, true, strings::StripCarriage(property::RestrictionToString(lpRes, lpObj))); + Output(ulDbgLvl, fFile, true, L"\n"); } } // namespace output diff --git a/core/mapi/processor/dumpStore.cpp b/core/mapi/processor/dumpStore.cpp index d054358d9..adaebe5ec 100644 --- a/core/mapi/processor/dumpStore.cpp +++ b/core/mapi/processor/dumpStore.cpp @@ -12,7 +12,10 @@ #include #include #include +#include #include +#include +#include namespace mapi::processor { @@ -194,6 +197,8 @@ namespace mapi::processor } output::OutputToFile(m_fFolderProps, L"\n"); + + InitializeInterestingTagArray(); } void dumpStore::DoFolderPerHierarchyTableRowWork(_In_ const _SRow* lpSRow) @@ -207,6 +212,7 @@ namespace mapi::processor void dumpStore::EndFolderWork() { + MAPIFreeBuffer(m_lpInterestingPropTags); if (m_bOutputList) return; if (m_fFolderProps) { @@ -409,6 +415,149 @@ namespace mapi::processor if (lpStream) lpStream->Release(); } + void dumpStore::InitProperties(const std::vector& properties) { m_properties = properties; } + + void dumpStore::InitNamedProperties(const std::vector& namedProperties) + { + m_namedProperties = namedProperties; + } + + void dumpStore::InitializeInterestingTagArray() + { + auto count = static_cast(m_properties.size() + m_namedProperties.size()); + if (!m_lpFolder || count == 0) return; + + wprintf(L"Filtering for one of %lu interesting properties\n", count); + count += 2; // We're going to at PR_SUBJECT and PR_ENTRYID but they're not "interesting" + + auto lpTag = mapi::allocate(CbNewSPropTagArray(count)); + if (lpTag) + { + // Populate the array + lpTag->cValues = count; + auto i = ULONG{0}; + mapi::setTag(lpTag, i++) = PR_SUBJECT_W; + mapi::setTag(lpTag, i++) = PR_ENTRYID; + + // Add regular properties to tag array + for (auto& property : m_properties) + { + auto ulPropTag = proptags::LookupPropName(property.c_str()); + if (ulPropTag != 0) + { + if (i < count) mapi::setTag(lpTag, i++) = ulPropTag; + } + + wprintf(L"Looking for %ws = 0x%08X\n", property.c_str(), ulPropTag); + } + + // Add named properties to tag array + for (auto& namedProperty : m_namedProperties) + { + MAPINAMEID NamedID = {}; + auto ulPropType = PT_UNSPECIFIED; + // Check if that string is a known dispid + const auto lpNameIDEntry = cache::GetDispIDFromName(namedProperty.c_str()); + + // If we matched on a dispid name, use that for our lookup + if (lpNameIDEntry) + { + NamedID.ulKind = MNID_ID; + NamedID.Kind.lID = lpNameIDEntry->lValue; + NamedID.lpguid = const_cast(lpNameIDEntry->lpGuid); + ulPropType = lpNameIDEntry->ulType; + } + else + { + NamedID.ulKind = MNID_STRING; + NamedID.Kind.lID = strings::wstringToUlong(namedProperty, 16); + } + + if (NamedID.lpguid && (MNID_ID == NamedID.ulKind && NamedID.Kind.lID || + MNID_STRING == NamedID.ulKind && NamedID.Kind.lpwstrName)) + { + const auto lpNamedPropTags = cache::GetIDsFromNames(m_lpFolder, {NamedID}, 0); + if (lpNamedPropTags && lpNamedPropTags->cValues == 1) + { + auto ulPropTag = CHANGE_PROP_TYPE(mapi::getTag(lpNamedPropTags, 0), ulPropType); + if (ulPropTag != 0) + { + if (i < count) mapi::setTag(lpTag, i++) = ulPropTag; + switch (NamedID.ulKind) + { + case MNID_ID: + wprintf( + L"Looking for %ws = 0x%04X @ 0x%08X\n", + namedProperty.c_str(), + NamedID.Kind.lID, + ulPropTag); + break; + case MNID_STRING: + default: + wprintf(L"Looking for %ws @ 0x%08X\n", namedProperty.c_str(), ulPropTag); + } + } + } + + MAPIFreeBuffer(lpNamedPropTags); + } + } + + m_lpInterestingPropTags = lpTag; + } + } + + bool dumpStore::MessageHasInterestingProperties(_In_ LPMESSAGE lpMessage) + { + if (!lpMessage || !m_lpInterestingPropTags) return true; + + output::DebugPrint( + output::dbgLevel::Generic, + L"MessageHasInterestingProperties: Looking for any 1 of %d properties\n", + m_lpInterestingPropTags->cValues); + auto fInteresting = false; + + LPSPropValue lpProps = nullptr; + ULONG cVals = 0; + WC_H_GETPROPS_S(lpMessage->GetProps(m_lpInterestingPropTags, fMapiUnicode, &cVals, &lpProps)); + if (lpProps) + { + for (ULONG i = 0; i < cVals; i++) + { + if (lpProps[i].ulPropTag == PR_SUBJECT_W) + { + if (lpProps[i].Value.lpszW) + output::DebugPrint(output::dbgLevel::Generic, L"Examining %ws\n", lpProps[i].Value.lpszW); + continue; + } + + if (lpProps[i].ulPropTag == PR_ENTRYID) continue; + if (PROP_TYPE(lpProps[i].ulPropTag) == PT_ERROR) continue; + if (PROP_TYPE(lpProps[i].ulPropTag) == PT_UNSPECIFIED) continue; + output::DebugPrint( + output::dbgLevel::Generic, + L"MessageHasInterestingProperties: Found interesting property 0x%08X\n", + lpProps[i].ulPropTag); + fInteresting = true; + break; + } + + if (fInteresting) + { + output::DebugPrint(output::dbgLevel::Console, L"Found interesting message:\n"); + output::outputProperties(output::dbgLevel::Console, nullptr, cVals, lpProps, lpMessage, false); + } + + MAPIFreeBuffer(lpProps); + } + + output::DebugPrint( + output::dbgLevel::Generic, + L"MessageHasInterestingProperties: message %ws interesting\n", + (fInteresting ? L"was" : L"was not")); + return fInteresting; + } + void OutputMessageXML( _In_ LPMESSAGE lpMessage, _In_ LPVOID lpParentMessageData, @@ -634,6 +783,8 @@ namespace mapi::processor return false; // no more work necessary } + if (!MessageHasInterestingProperties(lpMessage)) return false; + OutputMessageXML( lpMessage, lpParentMessageData, m_szMessageFileName, m_szFolderPath, m_bRetryStreamProps, lpData); return true; diff --git a/core/mapi/processor/dumpStore.h b/core/mapi/processor/dumpStore.h index 961b8f4f4..689ff14f4 100644 --- a/core/mapi/processor/dumpStore.h +++ b/core/mapi/processor/dumpStore.h @@ -29,6 +29,8 @@ namespace mapi::processor void InitMessagePath(_In_ const std::wstring& szMessageFileName); void InitFolderPathRoot(_In_ const std::wstring& szFolderPathRoot); void InitMailboxTablePathRoot(_In_ const std::wstring& szMailboxTablePathRoot); + void InitProperties(const std::vector& properties); + void InitNamedProperties(const std::vector& namedProperties); void EnableMSG() noexcept; void EnableList() noexcept; void DisableStreamRetry() noexcept; @@ -70,6 +72,9 @@ namespace mapi::processor void EndAttachmentWork(_In_ LPMESSAGE lpMessage, _In_ LPVOID lpData) override; void EndMessageWork(_In_ LPMESSAGE lpMessage, _In_ LPVOID lpData) override; + void InitializeInterestingTagArray(); + bool MessageHasInterestingProperties(_In_ LPMESSAGE lpMessage); + std::wstring m_szMailboxTablePathRoot; std::wstring m_szFolderPathRoot; std::wstring m_szMessageFileName; @@ -83,5 +88,9 @@ namespace mapi::processor bool m_bOutputList; bool m_bRetryStreamProps; bool m_bOutputAttachments; + + std::vector m_properties; + std::vector m_namedProperties; + LPSPropTagArray m_lpInterestingPropTags = nullptr; }; } // namespace mapi::processor \ No newline at end of file diff --git a/core/utility/cli.h b/core/utility/cli.h index 8b7d71589..cfd32b824 100644 --- a/core/utility/cli.h +++ b/core/utility/cli.h @@ -46,6 +46,7 @@ namespace cli } std::wstring at(const size_t _Pos) const noexcept { return size() > _Pos ? args[_Pos] : std::wstring{}; } std::wstring operator[](const size_t _Pos) const noexcept { return at(_Pos); } + operator std::vector&() noexcept { return args; } ULONG atULONG(const size_t _Pos, const int radix = 10) const noexcept { diff --git a/core/utility/output.h b/core/utility/output.h index 3ee4e1077..3fee8c49a 100644 --- a/core/utility/output.h +++ b/core/utility/output.h @@ -33,6 +33,7 @@ namespace output LoadMAPI = 0x00080000, Hierarchy = 0x00100000, NamedPropCache = 0x00200000, + Console = 0x00400000, // used in MrMAPI to print to console Draw = 0x10000000, UI = 0x20000000, MAPIFunctions = 0x40000000, From dd1cf039f083666e97d06b3df276caeb2bebd0a4 Mon Sep 17 00:00:00 2001 From: Stephen Griffin Date: Mon, 27 Mar 2023 09:14:52 -0400 Subject: [PATCH 2/8] add parent folder to our base set of boring properties --- core/mapi/processor/dumpStore.cpp | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/core/mapi/processor/dumpStore.cpp b/core/mapi/processor/dumpStore.cpp index adaebe5ec..6f18430a0 100644 --- a/core/mapi/processor/dumpStore.cpp +++ b/core/mapi/processor/dumpStore.cpp @@ -422,13 +422,28 @@ namespace mapi::processor m_namedProperties = namedProperties; } + static const SizedSPropTagArray(3, boringProps) = {3, {PR_SUBJECT_W, PR_ENTRYID, PR_PARENT_DISPLAY_W}}; + + bool PropIsBoring(ULONG ulPropTag) noexcept + { + for (ULONG iBoring = 0; iBoring < boringProps.cValues; iBoring++) + { + if (ulPropTag == boringProps.aulPropTag[iBoring]) + { + return true; + } + } + + return false; + } + void dumpStore::InitializeInterestingTagArray() { auto count = static_cast(m_properties.size() + m_namedProperties.size()); if (!m_lpFolder || count == 0) return; wprintf(L"Filtering for one of %lu interesting properties\n", count); - count += 2; // We're going to at PR_SUBJECT and PR_ENTRYID but they're not "interesting" + count += boringProps.cValues; // We're going to add some boring properties we'll use later for display auto lpTag = mapi::allocate(CbNewSPropTagArray(count)); if (lpTag) @@ -436,8 +451,10 @@ namespace mapi::processor // Populate the array lpTag->cValues = count; auto i = ULONG{0}; - mapi::setTag(lpTag, i++) = PR_SUBJECT_W; - mapi::setTag(lpTag, i++) = PR_ENTRYID; + for (ULONG iBoring = 0; iBoring < boringProps.cValues; iBoring++) + { + mapi::setTag(lpTag, i++) = boringProps.aulPropTag[iBoring]; + } // Add regular properties to tag array for (auto& property : m_properties) @@ -531,7 +548,8 @@ namespace mapi::processor continue; } - if (lpProps[i].ulPropTag == PR_ENTRYID) continue; + if (PropIsBoring(lpProps[i].ulPropTag)) continue; + if (PROP_TYPE(lpProps[i].ulPropTag) == PT_ERROR) continue; if (PROP_TYPE(lpProps[i].ulPropTag) == PT_UNSPECIFIED) continue; output::DebugPrint( From d99b5637122bbd1fb712a6a6aa772f47389df776 Mon Sep 17 00:00:00 2001 From: Stephen Griffin Date: Mon, 27 Mar 2023 10:53:06 -0400 Subject: [PATCH 3/8] augment interesting message scanning to include attachments like calendar exceptions --- core/mapi/processor/dumpStore.cpp | 118 ++++++++++++++++++++---------- 1 file changed, 81 insertions(+), 37 deletions(-) diff --git a/core/mapi/processor/dumpStore.cpp b/core/mapi/processor/dumpStore.cpp index 6f18430a0..945322e5d 100644 --- a/core/mapi/processor/dumpStore.cpp +++ b/core/mapi/processor/dumpStore.cpp @@ -576,12 +576,11 @@ namespace mapi::processor return fInteresting; } - void OutputMessageXML( + void InitMessageData( _In_ LPMESSAGE lpMessage, _In_ LPVOID lpParentMessageData, _In_ const std::wstring& szMessageFileName, _In_ const std::wstring& szFolderPath, - bool bRetryStreamProps, _Deref_out_opt_ LPVOID* lpData) { if (!lpMessage || !lpData) return; @@ -591,17 +590,6 @@ namespace mapi::processor auto lpMsgData = static_cast(*lpData); - LPSPropValue lpAllProps = nullptr; - ULONG cValues = 0L; - - // Get all props, asking for UNICODE string properties - const auto hRes = WC_H_GETPROPS(mapi::GetPropsNULL(lpMessage, MAPI_UNICODE, &cValues, &lpAllProps)); - if (hRes == MAPI_E_BAD_CHARWIDTH) - { - // Didn't like MAPI_UNICODE - fall back - WC_H_GETPROPS_S(mapi::GetPropsNULL(lpMessage, NULL, &cValues, &lpAllProps)); - } - // If we've got a parent message, we're an attachment - use attachment filename logic if (lpParentMessageData) { @@ -632,28 +620,55 @@ namespace mapi::processor std::wstring szSubj; // BuildFileNameAndPath will substitute a subject if we don't find one SBinary recordKey = {}; - auto lpTemp = PpropFindProp(lpAllProps, cValues, PR_SUBJECT_W); - if (lpTemp && strings::CheckStringProp(lpTemp, PT_UNICODE)) + auto lpSubjectW = LPSPropValue{}; + WC_MAPI_S(mapi::HrGetOnePropEx(lpMessage, PR_SUBJECT_W, fMapiUnicode, &lpSubjectW)); + if (lpSubjectW && strings::CheckStringProp(lpSubjectW, PT_UNICODE)) { - szSubj = lpTemp->Value.lpszW; + szSubj = lpSubjectW->Value.lpszW; } else { - lpTemp = PpropFindProp(lpAllProps, cValues, PR_SUBJECT_A); - if (lpTemp && strings::CheckStringProp(lpTemp, PT_STRING8)) + auto lpSubjectA = LPSPropValue{}; + WC_MAPI_S(mapi::HrGetOnePropEx(lpMessage, PR_SUBJECT_A, fMapiUnicode, &lpSubjectA)); + if (lpSubjectA && strings::CheckStringProp(lpSubjectA, PT_STRING8)) { - szSubj = strings::stringTowstring(lpTemp->Value.lpszA); + szSubj = strings::stringTowstring(lpSubjectA->Value.lpszA); } + + MAPIFreeBuffer(lpSubjectA); } - lpTemp = PpropFindProp(lpAllProps, cValues, PR_RECORD_KEY); - if (lpTemp && PR_RECORD_KEY == lpTemp->ulPropTag) + MAPIFreeBuffer(lpSubjectW); + + auto lpRecordKey = LPSPropValue{}; + WC_MAPI_S(mapi::HrGetOnePropEx(lpMessage, PR_RECORD_KEY, fMapiUnicode, &lpRecordKey)); + if (lpRecordKey && PR_RECORD_KEY == lpRecordKey->ulPropTag) { - recordKey = mapi::getBin(lpTemp); + recordKey = mapi::getBin(lpRecordKey); } + MAPIFreeBuffer(lpRecordKey); + lpMsgData->szFilePath = file::BuildFileNameAndPath(L".xml", szSubj, szFolderPath, &recordKey); // STRING_OK } + } + + void OutputMessageXML(_In_ LPMESSAGE lpMessage, bool bRetryStreamProps, _Deref_out_opt_ LPVOID* lpData) + { + if (!lpMessage || !lpData) return; + + auto lpMsgData = static_cast(*lpData); + + LPSPropValue lpAllProps = nullptr; + ULONG cValues = 0L; + + // Get all props, asking for UNICODE string properties + const auto hRes = WC_H_GETPROPS(mapi::GetPropsNULL(lpMessage, MAPI_UNICODE, &cValues, &lpAllProps)); + if (hRes == MAPI_E_BAD_CHARWIDTH) + { + // Didn't like MAPI_UNICODE - fall back + WC_H_GETPROPS_S(mapi::GetPropsNULL(lpMessage, NULL, &cValues, &lpAllProps)); + } if (!lpMsgData->szFilePath.empty()) { @@ -801,10 +816,11 @@ namespace mapi::processor return false; // no more work necessary } - if (!MessageHasInterestingProperties(lpMessage)) return false; + InitMessageData(lpMessage, lpParentMessageData, m_szMessageFileName, m_szFolderPath, lpData); - OutputMessageXML( - lpMessage, lpParentMessageData, m_szMessageFileName, m_szFolderPath, m_bRetryStreamProps, lpData); + if (!MessageHasInterestingProperties(lpMessage)) return true; + + OutputMessageXML(lpMessage, m_bRetryStreamProps, lpData); return true; } @@ -813,7 +829,13 @@ namespace mapi::processor if (!lpData) return false; if (m_bOutputMSG) return false; // When outputting message files, no recipient work is needed if (m_bOutputList) return false; - output::OutputToFile(static_cast(lpData)->fMessageProps, L"\n"); + + const auto lpMsgData = static_cast(lpData); + if (lpMsgData && lpMsgData->fMessageProps) + { + output::OutputToFile(lpMsgData->fMessageProps, L"\n"); + } + return true; } @@ -829,11 +851,14 @@ namespace mapi::processor const auto lpMsgData = static_cast(lpData); - output::OutputToFilef(lpMsgData->fMessageProps, L"\n", ulCurRow); + if (lpMsgData && lpMsgData->fMessageProps) + { + output::OutputToFilef(lpMsgData->fMessageProps, L"\n", ulCurRow); - output::outputSRow(output::dbgLevel::NoDebug, lpMsgData->fMessageProps, lpSRow, lpMessage); + output::outputSRow(output::dbgLevel::NoDebug, lpMsgData->fMessageProps, lpSRow, lpMessage); - output::OutputToFile(lpMsgData->fMessageProps, L"\n"); + output::OutputToFile(lpMsgData->fMessageProps, L"\n"); + } } void dumpStore::EndRecipientWork(_In_ LPMESSAGE /*lpMessage*/, _In_ LPVOID lpData) @@ -841,15 +866,25 @@ namespace mapi::processor if (!lpData) return; if (m_bOutputMSG) return; // When outputting message files, no recipient work is needed if (m_bOutputList) return; - output::OutputToFile(static_cast(lpData)->fMessageProps, L"\n"); + const auto lpMsgData = static_cast(lpData); + + if (lpMsgData && lpMsgData->fMessageProps) + { + output::OutputToFile(lpMsgData->fMessageProps, L"\n"); + } } bool dumpStore::BeginAttachmentWork(_In_ LPMESSAGE /*lpMessage*/, _In_ LPVOID lpData) { - if (!lpData) return false; if (m_bOutputMSG) return false; // When outputting message files, no attachment work is needed if (m_bOutputList) return false; - output::OutputToFile(static_cast(lpData)->fMessageProps, L"\n"); + const auto lpMsgData = static_cast(lpData); + + if (lpMsgData && lpMsgData->fMessageProps) + { + output::OutputToFile(lpMsgData->fMessageProps, L"\n"); + } + return true; } @@ -867,6 +902,7 @@ namespace mapi::processor const auto lpMsgData = static_cast(lpData); lpMsgData->ulCurAttNum = ulCurRow; // set this so we can pull it if this is an embedded message + if (!lpMsgData->fMessageProps) return; output::OutputToFilef(lpMsgData->fMessageProps, L"(lpData)->fMessageProps, L"\n"); + const auto lpMsgData = static_cast(lpData); + if (lpMsgData && lpMsgData->fMessageProps) + { + output::OutputToFile(lpMsgData->fMessageProps, L"\n"); + } } void dumpStore::EndMessageWork(_In_ LPMESSAGE /*lpMessage*/, _In_ LPVOID lpData) @@ -929,11 +969,15 @@ namespace mapi::processor if (m_bOutputList) return; const auto lpMsgData = static_cast(lpData); - if (lpMsgData->fMessageProps) + if (lpMsgData) { - output::OutputToFile(lpMsgData->fMessageProps, L"\n"); - output::CloseFile(lpMsgData->fMessageProps); + if (lpMsgData->fMessageProps) + { + output::OutputToFile(lpMsgData->fMessageProps, L"\n"); + output::CloseFile(lpMsgData->fMessageProps); + } + + delete lpMsgData; } - delete lpMsgData; } } // namespace mapi::processor \ No newline at end of file From 99fb1f409686fbb06d4bf32205f0376aa9b0da3c Mon Sep 17 00:00:00 2001 From: Stephen Griffin Date: Mon, 27 Mar 2023 10:56:33 -0400 Subject: [PATCH 4/8] fix help --- MrMapi/mmcli.cpp | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/MrMapi/mmcli.cpp b/MrMapi/mmcli.cpp index 21e7e8b34..d390c35d0 100644 --- a/MrMapi/mmcli.cpp +++ b/MrMapi/mmcli.cpp @@ -285,14 +285,9 @@ namespace cli switchFlag.name(), switchProfile.name()); wprintf( - L" MrMAPI -%ws [-%ws ] [-%ws ] [-%ws ]\n", + L" MrMAPI -%ws [-%ws ] [-%ws ] [-%ws ] [-%ws ]\n", switchContents.name(), switchFindProperty.name(), - switchFolder.name(), - switchOutput.name()); - wprintf( - L" MrMAPI -%ws [-%ws ] [-%ws ] [-%ws ]\n", - switchContents.name(), switchFindNamedProperty.name(), switchFolder.name(), switchOutput.name()); From c306fab6e24ef66b1fd1d07b4da400f65478590a Mon Sep 17 00:00:00 2001 From: Stephen Griffin Date: Mon, 27 Mar 2023 11:03:21 -0400 Subject: [PATCH 5/8] Fix help further (combine contents entries) --- MrMapi/mmcli.cpp | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/MrMapi/mmcli.cpp b/MrMapi/mmcli.cpp index d390c35d0..7e249ed0b 100644 --- a/MrMapi/mmcli.cpp +++ b/MrMapi/mmcli.cpp @@ -195,11 +195,13 @@ namespace cli switchProfile.name(), switchFolder.name()); wprintf( - L" MrMAPI -%ws | -%ws [-%ws ] [-%ws ] [-%ws ]\n", + L" MrMAPI -%ws | -%ws [-%ws ] [-%ws ] [-%ws ] [-%ws ] [-%ws ]\n", switchContents.name(), switchAssociatedContents.name(), switchProfile.name(), switchFolder.name(), + switchFindProperty.name(), + switchFindNamedProperty.name(), switchOutput.name()); wprintf( L" [-%ws ] [-%ws ] [-%ws] [-%ws] [-%ws ] [-%ws]\n", @@ -284,13 +286,6 @@ namespace cli switchWizard.name(), switchFlag.name(), switchProfile.name()); - wprintf( - L" MrMAPI -%ws [-%ws ] [-%ws ] [-%ws ] [-%ws ]\n", - switchContents.name(), - switchFindProperty.name(), - switchFindNamedProperty.name(), - switchFolder.name(), - switchOutput.name()); if (bFull) { From db2ce59910a019951b69f9e86b6917a5b69195cc Mon Sep 17 00:00:00 2001 From: Stephen Griffin Date: Mon, 27 Mar 2023 11:21:00 -0400 Subject: [PATCH 6/8] fix record key --- core/mapi/processor/dumpStore.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/core/mapi/processor/dumpStore.cpp b/core/mapi/processor/dumpStore.cpp index 945322e5d..e1cf309d7 100644 --- a/core/mapi/processor/dumpStore.cpp +++ b/core/mapi/processor/dumpStore.cpp @@ -647,9 +647,8 @@ namespace mapi::processor recordKey = mapi::getBin(lpRecordKey); } - MAPIFreeBuffer(lpRecordKey); - lpMsgData->szFilePath = file::BuildFileNameAndPath(L".xml", szSubj, szFolderPath, &recordKey); // STRING_OK + MAPIFreeBuffer(lpRecordKey); } } From aa16ec36602d0ef309c18c88dbb33f1cfc97590f Mon Sep 17 00:00:00 2001 From: Stephen Griffin Date: Mon, 27 Mar 2023 11:23:59 -0400 Subject: [PATCH 7/8] fix formatting --- MrMapi/mmcli.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/MrMapi/mmcli.cpp b/MrMapi/mmcli.cpp index 7e249ed0b..93af1b3db 100644 --- a/MrMapi/mmcli.cpp +++ b/MrMapi/mmcli.cpp @@ -195,7 +195,8 @@ namespace cli switchProfile.name(), switchFolder.name()); wprintf( - L" MrMAPI -%ws | -%ws [-%ws ] [-%ws ] [-%ws ] [-%ws ] [-%ws ]\n", + L" MrMAPI -%ws | -%ws [-%ws ] [-%ws ] [-%ws ] [-%ws ] " + L"[-%ws ]\n", switchContents.name(), switchAssociatedContents.name(), switchProfile.name(), From e8a4135e79aefc792e746a13482e238009c7f4dd Mon Sep 17 00:00:00 2001 From: Stephen Griffin Date: Mon, 27 Mar 2023 11:26:20 -0400 Subject: [PATCH 8/8] fix formatting --- MrMapi/MrMAPI.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/MrMapi/MrMAPI.cpp b/MrMapi/MrMAPI.cpp index ab5567de9..85114414d 100644 --- a/MrMapi/MrMAPI.cpp +++ b/MrMapi/MrMAPI.cpp @@ -143,7 +143,8 @@ int wmain(_In_ int argc, _In_count_(argc) wchar_t* argv[]) registry::useGetPropList = true; registry::parseNamedProps = true; registry::cacheNamedProps = true; - registry::debugTag = static_cast(output::dbgLevel::Console); // Any debug logging with Console will print to the console now + registry::debugTag = + static_cast(output::dbgLevel::Console); // Any debug logging with Console will print to the console now output::initStubCallbacks();