diff --git a/CHANGELOG.md b/CHANGELOG.md index 9ed2bba6..d063442d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,19 @@ # ChangeLog +## [10.2.1] - 2023-06-20 +### Changed +- Configuration: accept wildcard as exclusion path +- Output filename for shadow copies will be also based on volume identifier +- Limits accept UINT_MAX instead of handling it as "no limits" + +### Fixed +- Location is now overridable with cli like others options +- GetThis: fix option 'ResurrectRecord' always set to 'no' +- USNInfo: fix option 'ResurrectRecord' always set to 'yes' +- USNInfo: fix location resolution on some situations with vss +- Fix log file option + + ## [10.2.0] - 2023-04-20 ### Added - Volume Shadow Copy: add fallback mode when 'vss' service is stopped using directly 'volsnap.sys' diff --git a/src/OrcCommand/Command/FastFind/FastFind.h b/src/OrcCommand/Command/FastFind/FastFind.h index f2888831..f62f18e0 100644 --- a/src/OrcCommand/Command/FastFind/FastFind.h +++ b/src/OrcCommand/Command/FastFind/FastFind.h @@ -260,6 +260,7 @@ class ORCUTILS_API Main : public UtilitiesMain std::wstring YaraSource; std::unique_ptr Yara; + std::vector inputFilesystemLocations; Configuration() : FileSystem() diff --git a/src/OrcCommand/Command/FatInfo/FatInfo.h b/src/OrcCommand/Command/FatInfo/FatInfo.h index 89373235..81f8b21f 100644 --- a/src/OrcCommand/Command/FatInfo/FatInfo.h +++ b/src/OrcCommand/Command/FatInfo/FatInfo.h @@ -59,6 +59,7 @@ class ORCUTILS_API Main : public UtilitiesMain Intentions ColumnIntentions; Intentions DefaultIntentions; std::vector Filters; + std::vector InputLocations; }; static LPCWSTR ToolName() { return L"FatInfo"; } diff --git a/src/OrcCommand/Command/FatInfo/FatInfo_Config.cpp b/src/OrcCommand/Command/FatInfo/FatInfo_Config.cpp index 4e4cb4f4..1beba522 100644 --- a/src/OrcCommand/Command/FatInfo/FatInfo_Config.cpp +++ b/src/OrcCommand/Command/FatInfo/FatInfo_Config.cpp @@ -116,10 +116,15 @@ HRESULT Main::GetConfigurationFromConfig(const ConfigItem& configitem) m_Config.bPopSystemObjects = false; m_Config.locs.SetPopulateSystemObjects((bool)m_Config.bPopSystemObjects); - if (FAILED(hr = m_Config.locs.AddLocationsFromConfigItem(configitem[FATINFO_LOCATIONS]))) + if (configitem[FATINFO_LOCATIONS]) { - Log::Error("Failed to get locations definition from config [{}]", SystemError(hr)); - return hr; + if (FAILED(hr = m_Config.locs.AddLocationsFromConfigItem(configitem[FATINFO_LOCATIONS]))) + { + Log::Error("Failed to get locations definition from config [{}]", SystemError(hr)); + return hr; + } + + LocationSet::ParseLocationsFromConfigItem(configitem[FATINFO_LOCATIONS], m_Config.InputLocations); } if (configitem[FATINFO_LOGGING]) @@ -235,6 +240,8 @@ HRESULT Main::GetConfigurationFromArgcArgv(int argc, LPCWSTR argv[]) m_Config.bPopSystemObjects = false; m_Config.locs.SetPopulateSystemObjects((bool)m_Config.bPopSystemObjects); + LocationSet::ParseLocationsFromArgcArgv(argc, argv, m_Config.InputLocations); + if (FAILED(hr = m_Config.locs.AddLocationsFromArgcArgv(argc, argv))) return hr; @@ -261,6 +268,11 @@ HRESULT Main::CheckConfiguration() SystemDetails::SetOrcComputerName(m_utilitiesConfig.strComputerName); } + if (m_Config.InputLocations.empty()) + { + Log::Critical("Missing location parameter"); + } + m_Config.locs.Consolidate(false, FSVBR::FSType::FAT); if (m_Config.output.Type == OutputSpec::Kind::None) diff --git a/src/OrcCommand/Command/GetSamples/GetSamples_Config.cpp b/src/OrcCommand/Command/GetSamples/GetSamples_Config.cpp index 0c2bc481..b8a8c7af 100644 --- a/src/OrcCommand/Command/GetSamples/GetSamples_Config.cpp +++ b/src/OrcCommand/Command/GetSamples/GetSamples_Config.cpp @@ -304,7 +304,7 @@ HRESULT Main::CheckConfiguration() // TODO: make a function to use also in GetThis_config.cpp if (!config.limits.bIgnoreLimits - && (config.limits.dwlMaxTotalBytes == INFINITE && config.limits.dwMaxSampleCount == INFINITE)) + && (!config.limits.dwlMaxTotalBytes.has_value() && !config.limits.dwMaxSampleCount.has_value())) { Log::Critical( "No global (at samples level, MaxTotalBytes or MaxSampleCount) has been set: set limits in configuration " diff --git a/src/OrcCommand/Command/GetSamples/GetSamples_Run.cpp b/src/OrcCommand/Command/GetSamples/GetSamples_Run.cpp index b03df6ee..e92baa0c 100644 --- a/src/OrcCommand/Command/GetSamples/GetSamples_Run.cpp +++ b/src/OrcCommand/Command/GetSamples/GetSamples_Run.cpp @@ -283,24 +283,24 @@ HRESULT Main::WriteGetThisConfig( getthisconfig[GETTHIS_SAMPLES].Status = ConfigItem::PRESENT; - if (config.limits.dwlMaxTotalBytes != INFINITE) + if (config.limits.dwlMaxTotalBytes.has_value()) { getthisconfig[GETTHIS_SAMPLES].SubItems[CONFIG_MAXTOTALBYTES].strData = - std::to_wstring(config.limits.dwlMaxTotalBytes); + std::to_wstring(config.limits.dwlMaxTotalBytes.value()); getthisconfig[GETTHIS_SAMPLES].SubItems[CONFIG_MAXTOTALBYTES].Status = ConfigItem::PRESENT; } - if (config.limits.dwlMaxBytesPerSample != INFINITE) + if (config.limits.dwlMaxBytesPerSample.has_value()) { getthisconfig[GETTHIS_SAMPLES].SubItems[CONFIG_MAXBYTESPERSAMPLE].strData = - std::to_wstring(config.limits.dwlMaxBytesPerSample); + std::to_wstring(config.limits.dwlMaxBytesPerSample.value()); getthisconfig[GETTHIS_SAMPLES].SubItems[CONFIG_MAXBYTESPERSAMPLE].Status = ConfigItem::PRESENT; } - if (config.limits.dwMaxSampleCount != INFINITE) + if (config.limits.dwMaxSampleCount.has_value()) { getthisconfig[GETTHIS_SAMPLES].SubItems[CONFIG_MAXSAMPLECOUNT].strData = - std::to_wstring(config.limits.dwMaxSampleCount); + std::to_wstring(config.limits.dwMaxSampleCount.value()); getthisconfig[GETTHIS_SAMPLES].SubItems[CONFIG_MAXSAMPLECOUNT].Status = ConfigItem::PRESENT; } diff --git a/src/OrcCommand/Command/GetThis/GetThis.h b/src/OrcCommand/Command/GetThis/GetThis.h index 128badd0..ed42d589 100644 --- a/src/OrcCommand/Command/GetThis/GetThis.h +++ b/src/OrcCommand/Command/GetThis/GetThis.h @@ -153,6 +153,8 @@ class ORCUTILS_API Main : public UtilitiesMain ContentSpec GetContentSpecFromString(const std::wstring& str); + std::vector inputLocations; + private: static std::wregex g_ContentRegEx; }; diff --git a/src/OrcCommand/Command/GetThis/GetThis_Config.cpp b/src/OrcCommand/Command/GetThis/GetThis_Config.cpp index f508826b..fa6f29e0 100644 --- a/src/OrcCommand/Command/GetThis/GetThis_Config.cpp +++ b/src/OrcCommand/Command/GetThis/GetThis_Config.cpp @@ -168,16 +168,26 @@ HRESULT Main::GetConfigurationFromConfig(const ConfigItem& configitem) config.bAddShadows = bAddShadows; } - if (FAILED(hr = config.Locations.AddLocationsFromConfigItem(configitem[GETTHIS_LOCATION]))) + if (configitem[GETTHIS_LOCATION]) { - Log::Error(L"Syntax error in specific locations parsing in config file"); - return hr; + if (FAILED(hr = config.Locations.AddLocationsFromConfigItem(configitem[GETTHIS_LOCATION]))) + { + Log::Error(L"Syntax error in specific locations parsing in config file"); + return hr; + } + + LocationSet::ParseLocationsFromConfigItem(configitem[GETTHIS_LOCATION], config.inputLocations); } - if (FAILED(hr = config.Locations.AddKnownLocations(configitem[GETTHIS_KNOWNLOCATIONS]))) + if (configitem[GETTHIS_KNOWNLOCATIONS]) { - Log::Error(L"Syntax error in known locations parsing in config file"); - return hr; + if (FAILED(hr = config.Locations.AddKnownLocations(configitem[GETTHIS_KNOWNLOCATIONS]))) + { + Log::Error(L"Syntax error in known locations parsing in config file"); + return hr; + } + + LocationSet::ParseLocationsFromConfigItem(configitem[GETTHIS_KNOWNLOCATIONS], config.inputLocations); } if (configitem[GETTHIS_SAMPLES][CONFIG_MAXBYTESPERSAMPLE]) @@ -477,6 +487,8 @@ HRESULT Main::GetConfigurationFromArgcArgv(int argc, LPCWSTR argv[]) } } + LocationSet::ParseLocationsFromArgcArgv(argc, argv, config.inputLocations); + if (FAILED(hr = config.Locations.AddLocationsFromArgcArgv(argc, argv))) { Log::Error("Error in specific locations parsing"); @@ -506,6 +518,11 @@ HRESULT Main::CheckConfiguration() config.bAddShadows = false; } + if (config.inputLocations.empty()) + { + Log::Critical("Missing location parameter"); + } + config.Locations.Consolidate( (bool)config.bAddShadows, config.m_shadows.value_or(LocationSet::ShadowFilters()), @@ -562,7 +579,7 @@ HRESULT Main::CheckConfiguration() // TODO: make a function to use also in GetSamples_config.cpp if (!config.limits.bIgnoreLimits - && (config.limits.dwlMaxTotalBytes == INFINITE && config.limits.dwMaxSampleCount == INFINITE)) + && (!config.limits.dwlMaxTotalBytes.has_value() && !config.limits.dwMaxSampleCount.has_value())) { Log::Critical( L"No global (at samples level, MaxTotalBytes or MaxSampleCount) has been set: set limits in configuration " diff --git a/src/OrcCommand/Command/GetThis/GetThis_Run.cpp b/src/OrcCommand/Command/GetThis/GetThis_Run.cpp index 2e60c35f..f94b2dad 100644 --- a/src/OrcCommand/Command/GetThis/GetThis_Run.cpp +++ b/src/OrcCommand/Command/GetThis/GetThis_Run.cpp @@ -593,49 +593,49 @@ LimitStatus SampleLimitStatus(const Limits& globalLimits, const Limits& localLim return LimitStatus::NoLimits; } - if (globalLimits.dwMaxSampleCount != INFINITE) + if (globalLimits.dwMaxSampleCount.has_value()) { - if (globalLimits.dwAccumulatedSampleCount >= globalLimits.dwMaxSampleCount) + if (globalLimits.dwAccumulatedSampleCount >= globalLimits.dwMaxSampleCount.value()) { return GlobalSampleCountLimitReached; } } - if (localLimits.dwMaxSampleCount != INFINITE) + if (localLimits.dwMaxSampleCount.has_value()) { - if (localLimits.dwAccumulatedSampleCount >= localLimits.dwMaxSampleCount) + if (localLimits.dwAccumulatedSampleCount >= localLimits.dwMaxSampleCount.value()) { return LocalSampleCountLimitReached; } } - if (globalLimits.dwlMaxBytesPerSample != INFINITE) + if (globalLimits.dwlMaxBytesPerSample.has_value()) { - if (dataSize > globalLimits.dwlMaxBytesPerSample) + if (dataSize > globalLimits.dwlMaxBytesPerSample.value()) { return GlobalMaxBytesPerSample; } } - if (globalLimits.dwlMaxTotalBytes != INFINITE) + if (globalLimits.dwlMaxTotalBytes.has_value()) { - if (dataSize + globalLimits.dwlAccumulatedBytesTotal > globalLimits.dwlMaxTotalBytes) + if (dataSize + globalLimits.dwlAccumulatedBytesTotal > globalLimits.dwlMaxTotalBytes.value()) { return GlobalMaxTotalBytes; } } - if (localLimits.dwlMaxBytesPerSample != INFINITE) + if (localLimits.dwlMaxBytesPerSample.has_value()) { - if (dataSize > localLimits.dwlMaxBytesPerSample) + if (dataSize > localLimits.dwlMaxBytesPerSample.value()) { return LocalMaxBytesPerSample; } } - if (localLimits.dwlMaxTotalBytes != INFINITE) + if (localLimits.dwlMaxTotalBytes.has_value()) { - if (dataSize + localLimits.dwlAccumulatedBytesTotal > localLimits.dwlMaxTotalBytes) + if (dataSize + localLimits.dwlAccumulatedBytesTotal > localLimits.dwlMaxTotalBytes.value()) { return LocalMaxTotalBytes; } @@ -1431,7 +1431,7 @@ HRESULT Main::FindMatchingSamples() config.Locations, std::bind(&Main::OnMatchingSample, this, std::placeholders::_1, std::placeholders::_2), false, - ResurrectRecordsMode::kNo); + config.resurrectRecordsMode); if (FAILED(hr)) { diff --git a/src/OrcCommand/Command/NTFSInfo/NTFSInfo.h b/src/OrcCommand/Command/NTFSInfo/NTFSInfo.h index 91b5b400..2d9dd401 100644 --- a/src/OrcCommand/Command/NTFSInfo/NTFSInfo.h +++ b/src/OrcCommand/Command/NTFSInfo/NTFSInfo.h @@ -100,6 +100,7 @@ class ORCUTILS_API Main : public UtilitiesMain Intentions ColumnIntentions; Intentions DefaultIntentions; std::vector Filters; + std::vector InputLocations; }; private: diff --git a/src/OrcCommand/Command/NTFSInfo/NTFSInfo_Config.cpp b/src/OrcCommand/Command/NTFSInfo/NTFSInfo_Config.cpp index 364d4762..981b5004 100644 --- a/src/OrcCommand/Command/NTFSInfo/NTFSInfo_Config.cpp +++ b/src/OrcCommand/Command/NTFSInfo/NTFSInfo_Config.cpp @@ -205,7 +205,9 @@ HRESULT Main::GetConfigurationFromConfig(const ConfigItem& configitem) if (!mode) { Log::Error( - L"Failed to parse 'Resurrect' attribute (value: {}) [{}]", configitem[NTFSINFO_RESURRECT].c_str(), mode.error()); + L"Failed to parse 'Resurrect' attribute (value: {}) [{}]", + configitem[NTFSINFO_RESURRECT].c_str(), + mode.error()); } else { @@ -253,6 +255,8 @@ HRESULT Main::GetConfigurationFromConfig(const ConfigItem& configitem) return hr; } + LocationSet::ParseLocationsFromConfigItem(configitem[NTFSINFO_LOCATIONS], config.InputLocations); + if (FAILED(hr = GetColumnsAndFiltersFromConfig(configitem))) { Log::Error(L"Failed to get column definition from config [{}]", SystemError(hr)); @@ -381,6 +385,8 @@ HRESULT Main::GetConfigurationFromArgcArgv(int argc, LPCWSTR argv[]) } // argc/argv parameters only + LocationSet::ParseLocationsFromArgcArgv(argc, argv, config.InputLocations); + if (boost::logic::indeterminate(config.bAddShadows)) config.bAddShadows = false; if (boost::logic::indeterminate(config.bPopSystemObjects)) @@ -429,6 +435,11 @@ HRESULT Main::CheckConfiguration() config.bAddShadows = false; } + if (config.InputLocations.empty()) + { + Log::Critical("Missing location parameter"); + } + config.locs.Consolidate( static_cast(config.bAddShadows), config.m_shadows.value_or(LocationSet::ShadowFilters()), diff --git a/src/OrcCommand/Command/NTFSInfo/NTFSInfo_Run.cpp b/src/OrcCommand/Command/NTFSInfo/NTFSInfo_Run.cpp index d7164c63..5265fabc 100644 --- a/src/OrcCommand/Command/NTFSInfo/NTFSInfo_Run.cpp +++ b/src/OrcCommand/Command/NTFSInfo/NTFSInfo_Run.cpp @@ -646,6 +646,14 @@ HRESULT Main::RunThroughMFT() if (locations.empty()) { + if (config.m_excludes.has_value() && config.m_excludes->find(L"*") != std::cend(*config.m_excludes)) + { + // TODO: BEWARE: this is not complete, it should handle cases where specific drive is targetted and excluded + // and ShadowFilters. + Log::Info(L"No volume found"); + return S_OK; + } + Log::Critical( L"No NTFS volumes configured for parsing. Use \"*\" to parse all mounted volumes or list the volumes you " L"want parsed"); diff --git a/src/OrcCommand/Command/USNInfo/USNInfo.h b/src/OrcCommand/Command/USNInfo/USNInfo.h index cf718d7c..fe5fa1ef 100644 --- a/src/OrcCommand/Command/USNInfo/USNInfo.h +++ b/src/OrcCommand/Command/USNInfo/USNInfo.h @@ -50,6 +50,7 @@ class ORCUTILS_API Main : public UtilitiesMain std::optional m_shadows; std::optional m_shadowsParser; std::optional m_excludes; + std::vector m_inputLocations; }; private: diff --git a/src/OrcCommand/Command/USNInfo/USNInfo_Config.cpp b/src/OrcCommand/Command/USNInfo/USNInfo_Config.cpp index ab924ac1..863c84f6 100644 --- a/src/OrcCommand/Command/USNInfo/USNInfo_Config.cpp +++ b/src/OrcCommand/Command/USNInfo/USNInfo_Config.cpp @@ -45,6 +45,8 @@ HRESULT Main::GetConfigurationFromConfig(const ConfigItem& configitem) return hr; } + LocationSet::ParseLocationsFromConfigItem(configitem[USNINFO_LOCATIONS], config.m_inputLocations); + boost::logic::tribool bAddShadows; for (auto& item : configitem[USNINFO_LOCATIONS].NodeList) { @@ -141,6 +143,8 @@ HRESULT Main::GetConfigurationFromArgcArgv(int argc, LPCWSTR argv[]) } } + LocationSet::ParseLocationsFromArgcArgv(argc, argv, config.m_inputLocations); + if (FAILED(hr = config.locs.AddLocationsFromArgcArgv(argc, argv))) return hr; @@ -165,6 +169,11 @@ HRESULT Main::CheckConfiguration() config.bAddShadows = false; } + if (config.m_inputLocations.empty()) + { + Log::Critical("Missing location parameter"); + } + config.locs.Consolidate( static_cast(config.bAddShadows), config.m_shadows.value_or(LocationSet::ShadowFilters()), diff --git a/src/OrcCommand/Command/USNInfo/USNInfo_Run.cpp b/src/OrcCommand/Command/USNInfo/USNInfo_Run.cpp index 09bb9b4b..68223bf3 100644 --- a/src/OrcCommand/Command/USNInfo/USNInfo_Run.cpp +++ b/src/OrcCommand/Command/USNInfo/USNInfo_Run.cpp @@ -152,15 +152,24 @@ HRESULT Main::Run() return hr; } - const auto& unique_locs = config.locs.GetAltitudeLocations(); + const auto& locs = config.locs.GetAltitudeLocations(); std::vector> locations; + std::vector> allLocations; - // keep only the locations we're parsing - std::copy_if( - std::cbegin(unique_locs), - std::cend(unique_locs), - std::back_inserter(locations), - [](const std::shared_ptr& item) -> bool { return item->GetParse(); }); + std::copy_if(begin(locs), end(locs), back_inserter(locations), [](const std::shared_ptr& loc) { + if (loc == nullptr) + return false; + + return (loc->GetParse() && loc->IsNTFS()); + }); + + if (locations.empty()) + { + Log::Critical( + L"No NTFS volumes configured for parsing. Use \"*\" to parse all mounted volumes or list the volumes you " + L"want parsed"); + return E_INVALIDARG; + } if (config.output.Type == OutputSpec::Kind::Archive) { @@ -172,8 +181,9 @@ HRESULT Main::Run() } } - BOOST_SCOPE_EXIT(&config, &m_outputs) { m_outputs.CloseAll(config.output); } - BOOST_SCOPE_EXIT_END; + Guard::Scope closeOnExit([&]() { + m_outputs.CloseAll(config.output); + }); hr = m_outputs.GetWriters(config.output, L"USNInfo", locations); if (FAILED(hr)) @@ -182,57 +192,59 @@ HRESULT Main::Run() return hr; } - hr = m_outputs.ForEachOutput( - config.output, [this](const MultipleOutput::OutputPair& dir) -> HRESULT { - m_console.Print(L"Parsing volume '{}'", dir.first.m_pLoc->GetLocation()); - USNJournalWalkerOffline walker; + auto outputIt = std::begin(m_outputs.Outputs()); + for (const auto& loc : locations) + { + Guard::Scope onExit([this, &outputIt]() { + m_outputs.CloseOne(config.output, *outputIt); + outputIt++; + }); - HRESULT hr = walker.Initialize(dir.first.m_pLoc); - if (FAILED(hr)) - { - if (hr == HRESULT_FROM_WIN32(ERROR_FILE_SYSTEM_LIMITATION)) - { - Log::Warn(L"File system not eligible for volume '{}'", dir.first.m_pLoc->GetLocation()); - return S_OK; - } - - Log::Critical( - L"Failed to init walk for volume '{}' [{}]", dir.first.m_pLoc->GetLocation(), SystemError(hr)); - return hr; - } + m_console.Print(L"Parsing: {} [{}]", loc->GetLocation(), boost::join(loc->GetPaths(), L", ")); + USNJournalWalkerOffline walker; - if (!walker.GetUsnJournal()) + HRESULT hr = walker.Initialize(loc); + if (FAILED(hr)) + { + if (hr == HRESULT_FROM_WIN32(ERROR_FILE_SYSTEM_LIMITATION)) { - Log::Warn(L"Did not find a USN journal on following volume '{}'", dir.first.m_pLoc->GetLocation()); - return S_OK; + Log::Warn(L"File system not eligible for volume '{}'", loc->GetLocation()); + continue; } - IUSNJournalWalker::Callbacks callbacks; - callbacks.RecordCallback = - [](const std::shared_ptr& volreader, WCHAR* szFullName, USN_RECORD* pElt) {}; + Log::Critical(L"Failed to init walk for volume '{}' [{}]", loc->GetLocation(), SystemError(hr)); + continue; + } - hr = walker.EnumJournal(callbacks); - if (FAILED(hr)) - { - Log::Error(L"Failed to enum MFT records '{}' [{}]", dir.first.m_pLoc->GetLocation(), SystemError(hr)); - return S_OK; - } + if (!walker.GetUsnJournal()) + { + Log::Warn(L"Did not find a USN journal on following volume '{}'", loc->GetLocation()); + continue; + } - callbacks.RecordCallback = - [this, dir](const std::shared_ptr& volreader, WCHAR* szFullName, USN_RECORD* pElt) { - USNRecordInformation(*dir.second, volreader, szFullName, pElt); - }; + IUSNJournalWalker::Callbacks callbacks; + callbacks.RecordCallback = + [](const std::shared_ptr& volreader, WCHAR* szFullName, USN_RECORD* pElt) {}; - hr = walker.ReadJournal(callbacks); - if (FAILED(hr)) - { - Log::Error(L"Failed to walk volume '{}' [{}]", dir.first.m_pLoc->GetLocation(), SystemError(hr)); - return S_OK; - } + hr = walker.EnumJournal(callbacks); + if (FAILED(hr)) + { + Log::Error(L"Failed to enum MFT records '{}' [{}]", loc->GetLocation(), SystemError(hr)); + continue; + } - Log::Info(L"Done"); - return S_OK; - }); + callbacks.RecordCallback = + [this, &outputIt](const std::shared_ptr& volreader, WCHAR* szFullName, USN_RECORD* pElt) { + USNRecordInformation(*outputIt->second, volreader, szFullName, pElt); + }; + + hr = walker.ReadJournal(callbacks); + if (FAILED(hr)) + { + Log::Error(L"Failed to walk volume '{}' [{}]", loc->GetLocation(), SystemError(hr)); + continue; + } + } if (FAILED(hr)) { diff --git a/src/OrcCommand/Log/UtilitiesLoggerConfiguration.cpp b/src/OrcCommand/Log/UtilitiesLoggerConfiguration.cpp index 987648ca..394b9fca 100644 --- a/src/OrcCommand/Log/UtilitiesLoggerConfiguration.cpp +++ b/src/OrcCommand/Log/UtilitiesLoggerConfiguration.cpp @@ -835,7 +835,7 @@ void UtilitiesLoggerConfiguration::Parse(const ConfigItem& item, UtilitiesLogger configuration.file.level = ::ParseLogLevel(item[CONFIGITEM_LOG_LOGFILE_NODE]); configuration.file.backtraceTrigger = ::ParseBacktraceLevel(item[CONFIGITEM_LOG_LOGFILE_NODE]); - if (item[CONFIGITEM_LOG_LOGFILE_OUTPUT]) + if (item[CONFIGITEM_LOG_LOGFILE_NODE][CONFIGITEM_LOG_LOGFILE_OUTPUT]) { const auto& outputItem = item[CONFIGITEM_LOG_LOGFILE_NODE][CONFIGITEM_LOG_LOGFILE_OUTPUT]; diff --git a/src/OrcCommand/UtilitiesMain.h b/src/OrcCommand/UtilitiesMain.h index a16839c2..61c8c482 100644 --- a/src/OrcCommand/UtilitiesMain.h +++ b/src/OrcCommand/UtilitiesMain.h @@ -41,6 +41,7 @@ #include "Log/LogTerminationHandler.h" #include "Utils/StdStream/StandardOutput.h" #include "Text/Guid.h" +#include "Limit.h" #include "VolumeReader.h" #include "Utils/EnumFlags.h" @@ -557,6 +558,18 @@ class UtilitiesMain return false; } + template + static bool ParameterOption(LPCWSTR szArg, LPCWSTR szOption, std::optional>& parameter) + { + OptionType result; + if (ParameterOption(szArg, szOption, result)) + { + parameter.emplace(std::move(result)); + return true; + } + return false; + } + static bool OptionalParameterOption( LPCWSTR szArg, LPCWSTR szOption, @@ -624,6 +637,18 @@ class UtilitiesMain return false; } + template + static bool FileSizeOption(LPCWSTR szArg, LPCWSTR szOption, std::optional& parameter) + { + OptionType result(0); + if (FileSizeOption(szArg, szOption, result)) + { + parameter.emplace(std::move(result)); + return true; + } + return false; + } + static bool FileSizeOption(LPCWSTR szArg, LPCWSTR szOption, DWORDLONG& dwlFileSize); static bool AltitudeOption(LPCWSTR szArg, LPCWSTR szOption, LocationSet::Altitude& altitude); diff --git a/src/OrcLib/Configuration/ConfigFile_Common.cpp b/src/OrcLib/Configuration/ConfigFile_Common.cpp index 703ac916..ca444cbc 100644 --- a/src/OrcLib/Configuration/ConfigFile_Common.cpp +++ b/src/OrcLib/Configuration/ConfigFile_Common.cpp @@ -399,7 +399,7 @@ HRESULT Orc::Config::Common::location(ConfigItem& parent, DWORD dwIndex, ConfigI // LOCATION item HRESULT Orc::Config::Common::location(ConfigItem& parent, DWORD dwIndex) { - return location(parent, dwIndex, ConfigItem::MANDATORY); + return location(parent, dwIndex, ConfigItem::OPTION); } HRESULT Orc::Config::Common::optional_location(ConfigItem& parent, DWORD dwIndex) diff --git a/src/OrcLib/FileFind.cpp b/src/OrcLib/FileFind.cpp index 9067c1f3..20f60b35 100644 --- a/src/OrcLib/FileFind.cpp +++ b/src/OrcLib/FileFind.cpp @@ -4292,7 +4292,7 @@ HRESULT FileFind::ExcludeMatch(const std::shared_ptr& aMatch) HRESULT FileFind::Find( const LocationSet& locations, - FileFind::FoundMatchCallback aCallback, + FileFind::FoundMatchCallback foundMatchCallback, bool bParseI30Data, ResurrectRecordsMode resurrectRecordsMode) { @@ -4318,112 +4318,142 @@ HRESULT FileFind::Find( bool hasSomeFailure = false; - for (const auto& aLoc : locs) + for (const auto& location : locs) + { + hr = Find(location, foundMatchCallback, bParseI30Data, resurrectRecordsMode); + if (FAILED(hr)) + { + Log::Error(L"Failed FileFind::Find on '{}'", location->GetLocation()); + hasSomeFailure = true; + continue; + } + } + + if (hasSomeFailure) + { + return E_FAIL; + } + + return S_OK; +} + +HRESULT FileFind::Find( + const std::shared_ptr& location, + FileFind::FoundMatchCallback aCallback, + bool bParseI30Data, + ResurrectRecordsMode resurrectRecordsMode) +{ + HRESULT hr = E_FAIL; + + if (m_ExactNameTerms.empty() && m_ExactPathTerms.empty() && m_Terms.empty() && m_SizeTerms.empty() + && m_I30ExactNameTerms.empty() && m_I30ExactPathTerms.empty() && m_I30Terms.empty()) + return S_OK; + + m_NeededHash = GetNeededHashAlgorithms(); + + hr = InitializeYara(); + if (FAILED(hr)) { - HRESULT hr = E_FAIL; - MFTWalker walk; + return hr; + } + + MFTWalker walk; - m_FullNameBuilder = walk.GetFullNameBuilder(); - m_InLocationBuilder = walk.GetInLocationBuilder(); + m_FullNameBuilder = walk.GetFullNameBuilder(); + m_InLocationBuilder = walk.GetInLocationBuilder(); - m_pVolReader = aLoc->GetReader(); + m_pVolReader = location->GetReader(); - if (FAILED(hr = walk.Initialize(aLoc, resurrectRecordsMode))) + if (FAILED(hr = walk.Initialize(location, resurrectRecordsMode))) + { + if (hr == HRESULT_FROM_WIN32(ERROR_FILE_SYSTEM_LIMITATION)) { - if (hr == HRESULT_FROM_WIN32(ERROR_FILE_SYSTEM_LIMITATION)) - { - Log::Debug(L"File system not eligible for volume '{}' [{}]", aLoc->GetLocation(), SystemError(hr)); - } - else - { - Log::Critical(L"Failed to init walk for volume '{}' [{}]", aLoc->GetLocation(), SystemError(hr)); - hasSomeFailure = true; - } + Log::Debug(L"File system not eligible for volume '{}' [{}]", location->GetLocation(), SystemError(hr)); } else { - MFTWalker::Callbacks cbs; - auto pCB = aCallback; + Log::Critical(L"Failed to init walk for volume '{}' [{}]", location->GetLocation(), SystemError(hr)); + return hr; + } + } + else + { + MFTWalker::Callbacks cbs; + auto pCB = aCallback; - bool bStop = false; + bool bStop = false; - cbs.ElementCallback = - [this, aCallback, &bStop, &hr](const std::shared_ptr& volreader, MFTRecord* pElt) { - DBG_UNREFERENCED_PARAMETER(volreader); - try + cbs.ElementCallback = + [this, aCallback, &bStop, &hr](const std::shared_ptr& volreader, MFTRecord* pElt) { + DBG_UNREFERENCED_PARAMETER(volreader); + try + { + if (pElt) { - if (pElt) + if (FAILED(hr = FindMatch(pElt, bStop, aCallback))) { - if (FAILED(hr = FindMatch(pElt, bStop, aCallback))) - { - Log::Error(L"FindMatch failed"); - pElt->CleanCachedData(); - return; - } + Log::Error(L"FindMatch failed"); pElt->CleanCachedData(); + return; } + pElt->CleanCachedData(); } - catch (WCHAR* e) - { - Log::Error(L"Could not parse record: '{}'", e); - } - return; - }; - - cbs.ProgressCallback = [&bStop](ULONG ulProgress) -> HRESULT { - if (bStop) + } + catch (WCHAR* e) { - return HRESULT_FROM_WIN32(ERROR_NO_MORE_FILES); + Log::Error(L"Could not parse record: '{}'", e); } - return S_OK; + return; }; - if (bParseI30Data && (!m_I30ExactNameTerms.empty() || !m_I30ExactPathTerms.empty() || !m_I30Terms.empty())) + cbs.ProgressCallback = [&bStop](ULONG ulProgress) -> HRESULT { + if (bStop) { - cbs.I30Callback = [this, aCallback, &bStop, &hr]( - const std::shared_ptr& volreader, - MFTRecord* pElt, - const PINDEX_ENTRY pEntry, - const PFILE_NAME pFileName, - bool bCarvedEntry) { - DBG_UNREFERENCED_PARAMETER(volreader); - DBG_UNREFERENCED_PARAMETER(bCarvedEntry); - DBG_UNREFERENCED_PARAMETER(pEntry); - DBG_UNREFERENCED_PARAMETER(pElt); - try - { - if (FAILED(hr = FindI30Match(pFileName, bStop, aCallback))) - { - Log::Error(L"FindI30Match failed"); - return; - } - } - catch (WCHAR* e) + return HRESULT_FROM_WIN32(ERROR_NO_MORE_FILES); + } + return S_OK; + }; + + if (bParseI30Data && (!m_I30ExactNameTerms.empty() || !m_I30ExactPathTerms.empty() || !m_I30Terms.empty())) + { + cbs.I30Callback = [this, aCallback, &bStop, &hr]( + const std::shared_ptr& volreader, + MFTRecord* pElt, + const PINDEX_ENTRY pEntry, + const PFILE_NAME pFileName, + bool bCarvedEntry) { + DBG_UNREFERENCED_PARAMETER(volreader); + DBG_UNREFERENCED_PARAMETER(bCarvedEntry); + DBG_UNREFERENCED_PARAMETER(pEntry); + DBG_UNREFERENCED_PARAMETER(pElt); + try + { + if (FAILED(hr = FindI30Match(pFileName, bStop, aCallback))) { - Log::Error(L"Could not parse record: '{}'", e); + Log::Error(L"FindI30Match failed"); + return; } - return; - }; - } - - if (FAILED(hr = walk.Walk(cbs))) - { - Log::Debug(L"Failed to walk volume '{}' [{}]", aLoc->GetLocation(), SystemError(hr)); - } - else - { - Log::Debug("Done"); - walk.Statistics(L"Done"); - } + } + catch (WCHAR* e) + { + Log::Error(L"Could not parse record: '{}'", e); + } + return; + }; } - } - if (hasSomeFailure) - { - return E_FAIL; + if (FAILED(hr = walk.Walk(cbs)) && hr != HRESULT_FROM_WIN32(ERROR_NO_MORE_FILES)) + { + Log::Debug(L"Failed to walk volume '{}' [{}]", location->GetLocation(), SystemError(hr)); + } + else + { + Log::Debug("Done"); + walk.Statistics(L"Done"); + } } - return S_OK; + return hr; } void FileFind::PrintSpecs() const diff --git a/src/OrcLib/FileFind.h b/src/OrcLib/FileFind.h index 168c2340..bbcfd6e6 100644 --- a/src/OrcLib/FileFind.h +++ b/src/OrcLib/FileFind.h @@ -512,7 +512,16 @@ class FileFind bool bParseI30Data, ResurrectRecordsMode resurrectRecordsMode); - const std::vector>& Matches() const { return m_Matches; } + HRESULT Find( + const std::shared_ptr& location, + FileFind::FoundMatchCallback aCallback, + bool bParseI30Data, + ResurrectRecordsMode resurrectRecordsMode); + + const std::vector>& Matches() const + { + return m_Matches; + } void PrintSpecs() const; diff --git a/src/OrcLib/Location.cpp b/src/OrcLib/Location.cpp index 03ec5dd0..f8ee0352 100644 --- a/src/OrcLib/Location.cpp +++ b/src/OrcLib/Location.cpp @@ -394,6 +394,15 @@ void Location::EnumerateShadowCopies( return; } + for (auto& shadow : shadows) + { + shadow.parentIdentifier = GetIdentifier(); + if (shadow.parentVolume->ShortVolumeName()) + { + shadow.VolumeName = shadow.parentVolume->ShortVolumeName(); + } + } + // Most recent snapshot first std::sort(std::begin(shadows), std::end(shadows), [](const auto& lhs, const auto& rhs) { return lhs.CreationTime > rhs.CreationTime; @@ -715,13 +724,21 @@ void Location::MakeIdentifier() } break; case Type::Snapshot: { - wregex r(REGEX_SNAPSHOT, regex_constants::icase); - wsmatch s; - - if (regex_match(m_Location, s, r)) + if (m_Shadow) + { + m_Identifier = fmt::format(L"{}_{}", m_Shadow->parentIdentifier, m_Shadow->guid); + } + else { + Log::Error(L"Location::MakeIdentifier: unexpected code path"); + wregex r(REGEX_SNAPSHOT, regex_constants::icase); + wsmatch s; - m_Identifier = L"Snapshot_" + s[REGEX_SNAPSHOT_NUM].str(); + if (regex_match(m_Location, s, r)) + { + + m_Identifier = L"Snapshot_" + s[REGEX_SNAPSHOT_NUM].str(); + } } } break; diff --git a/src/OrcLib/LocationSet.cpp b/src/OrcLib/LocationSet.cpp index 5b8748dc..b865a493 100644 --- a/src/OrcLib/LocationSet.cpp +++ b/src/OrcLib/LocationSet.cpp @@ -223,9 +223,11 @@ void GetExcludedVolumeLocations( const LocationSet::PathExcludes& excludedPaths, std::vector& excludedLocations) { + const bool excludeAll = excludedPaths.find(L"*") != std::cend(excludedPaths); + for (const auto& path : volume.Paths) { - if (excludedPaths.find(path) != std::cend(excludedPaths)) + if (excludeAll || excludedPaths.find(path) != std::cend(excludedPaths)) { std::copy( std::cbegin(volume.Locations), std::cend(volume.Locations), std::back_inserter(excludedLocations)); @@ -236,7 +238,7 @@ void GetExcludedVolumeLocations( for (const auto& location : volume.Locations) { - if (excludedPaths.find(location->GetLocation()) != std::cend(excludedPaths)) + if (excludeAll || excludedPaths.find(location->GetLocation()) != std::cend(excludedPaths)) { std::copy( std::cbegin(volume.Locations), std::cend(volume.Locations), std::back_inserter(excludedLocations)); @@ -1092,28 +1094,82 @@ HRESULT LocationSet::AddLocationsFromConfigItem(const ConfigItem& config) return hr; } -HRESULT LocationSet::AddLocationsFromArgcArgv(int argc, LPCWSTR argv[]) +HRESULT LocationSet::ParseLocationsFromConfigItem(const ConfigItem& config, std::vector& locations) { - HRESULT hr = E_FAIL; + if (!config) + { + return S_OK; + } - if (FAILED(hr = EnumerateLocations())) - return hr; + if (config.Type != ConfigItem::NODELIST) + { + return E_INVALIDARG; + } + if (config.strName != L"location") + { + return E_INVALIDARG; + } + + HRESULT hr = S_OK; + for (auto& item : config.NodeList) + { + locations.emplace_back(item.c_str()); + } + + return S_OK; +} + +void LocationSet::ParseLocationsFromArgcArgv(int argc, LPCWSTR argv[], std::vector& locations) +{ for (int i = 1; i < argc; i++) { if (argv[i][0] != L'/' && argv[i][0] != L'+' && argv[i][0] != L'-') { if (argv[i][0] == L'*') { - if (FAILED(hr = ParseAllVolumes())) - return hr; - return S_OK; + Log::Debug(L"Enable all locations"); } - else + + locations.emplace_back(argv[i]); + } + } +} + +HRESULT LocationSet::AddLocationsFromArgcArgv(int argc, LPCWSTR argv[]) +{ + HRESULT hr = E_FAIL; + + if (FAILED(hr = EnumerateLocations())) + { + return hr; + } + + std::vector locationsFromCli; + ParseLocationsFromArgcArgv(argc, argv, locationsFromCli); + if (locationsFromCli.empty()) + { + return S_OK; + } + + Log::Debug("Disable location set previously as a cli option overrides the value"); + for (auto& location : m_Locations) + { + location.second->SetParse(false); + } + + for (const auto& location : locationsFromCli) + { + if (location == L"*") + { + return ParseAllVolumes(); + } + else + { + std::vector> addedLocs; + if (FAILED(hr = AddLocations(location.c_str(), addedLocs))) { - std::vector> addedLocs; - if (FAILED(hr = AddLocations(argv[i], addedLocs))) - return hr; + return hr; } } } diff --git a/src/OrcLib/LocationSet.h b/src/OrcLib/LocationSet.h index dafb8db1..26831e83 100644 --- a/src/OrcLib/LocationSet.h +++ b/src/OrcLib/LocationSet.h @@ -165,6 +165,9 @@ class LocationSet HRESULT AddLocations(const WCHAR* szLocation, std::vector>& addedLocs, bool bToParse = true); + static HRESULT ParseLocationsFromConfigItem(const ConfigItem& config, std::vector& locations); + static void ParseLocationsFromArgcArgv(int argc, LPCWSTR argv[], std::vector& locations); + HRESULT AddLocationsFromConfigItem(const ConfigItem& config); HRESULT AddLocationsFromArgcArgv(int argc, LPCWSTR argv[]); HRESULT AddKnownLocations(const ConfigItem& item); diff --git a/src/OrcLib/OrcLimits.h b/src/OrcLib/OrcLimits.h index 175e1e11..51fc2809 100644 --- a/src/OrcLib/OrcLimits.h +++ b/src/OrcLib/OrcLimits.h @@ -23,14 +23,14 @@ class Limits bool bIgnoreLimits = false; - Limit> dwlMaxTotalBytes = INFINITE; + std::optional>> dwlMaxTotalBytes; DWORDLONG dwlAccumulatedBytesTotal = 0LL; bool bMaxTotalBytesReached = false; - Limit> dwlMaxBytesPerSample = INFINITE; + std::optional>> dwlMaxBytesPerSample; bool bMaxBytesPerSampleReached = false; - Limit dwMaxSampleCount = INFINITE; + std::optional> dwMaxSampleCount; DWORD dwAccumulatedSampleCount = 0LL; bool bMaxSampleCountReached = false; }; diff --git a/src/OrcLib/USNJournalWalkerOffline.cpp b/src/OrcLib/USNJournalWalkerOffline.cpp index 7747d92a..66899f54 100644 --- a/src/OrcLib/USNJournalWalkerOffline.cpp +++ b/src/OrcLib/USNJournalWalkerOffline.cpp @@ -24,7 +24,7 @@ static const auto ROOT_USN = 0x0005000000000005LL; DWORD USNJournalWalkerOffline::m_BufferSize = 0x10000; USNJournalWalkerOffline::USNJournalWalkerOffline() - : m_Locations() + : m_location() { m_dwlRootUSN = ROOT_USN; m_cchMaxComponentLength = 255; @@ -61,13 +61,11 @@ HRESULT USNJournalWalkerOffline::Initialize(const std::shared_ptr& loc fileFind.AddTerm(fs); - std::shared_ptr added; - m_Locations.AddLocation(loc, added, true); - m_Locations.Consolidate(true, FSVBR::FSType::NTFS); + m_location = loc; if (FAILED( hr = fileFind.Find( - m_Locations, + m_location, [this, hr](const std::shared_ptr& aFileMatch, bool& bStop) { Log::Info( L"Found USN journal {}: {}", @@ -88,7 +86,7 @@ HRESULT USNJournalWalkerOffline::Initialize(const std::shared_ptr& loc false, ResurrectRecordsMode::kNo))) { - Log::Error("Failed to parse location while searching for USN journal"); + Log::Error("Failed to parse location while searching for USN journal [{}]", SystemError(hr)); } if (FAILED(hr = m_RecordStore.InitializeStore(USN_MAX_NUMBER, m_dwRecordMaxSize))) @@ -103,14 +101,12 @@ HRESULT USNJournalWalkerOffline::EnumJournal(const IUSNJournalWalker::Callbacks& { HRESULT hr = E_FAIL; - const LocationSet::Locations& locations(m_Locations.GetLocations()); - - if (locations.size() == 0) + if (m_location == nullptr) return hr; MFTWalker walk; - if (FAILED(hr = walk.Initialize(locations.begin()->second, ResurrectRecordsMode::kYes))) + if (FAILED(hr = walk.Initialize(m_location, ResurrectRecordsMode::kNo))) { Log::Error(L"Failed during MFT walk initialisation [{}]", SystemError(hr)); return hr; diff --git a/src/OrcLib/USNJournalWalkerOffline.h b/src/OrcLib/USNJournalWalkerOffline.h index 563e9522..7c49d0d6 100644 --- a/src/OrcLib/USNJournalWalkerOffline.h +++ b/src/OrcLib/USNJournalWalkerOffline.h @@ -52,8 +52,7 @@ class USNJournalWalkerOffline static void SetBufferSize(DWORD size); private: - LocationSet m_Locations; - + std::shared_ptr m_location; std::shared_ptr m_USNJournal; static DWORD m_BufferSize; diff --git a/src/OrcLib/VolumeShadowCopies.h b/src/OrcLib/VolumeShadowCopies.h index 579e9e84..3bd82341 100644 --- a/src/OrcLib/VolumeShadowCopies.h +++ b/src/OrcLib/VolumeShadowCopies.h @@ -36,6 +36,7 @@ class VolumeShadowCopies VSS_TIMESTAMP CreationTime; GUID guid; std::shared_ptr parentVolume; + std::wstring parentIdentifier; Shadow( LPCWSTR szVolume,