From 33ff9480c9ced9512df213ec2ed0b3480265eaac Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Tue, 12 Dec 2023 15:47:44 +0100 Subject: [PATCH] PluginNotificationTask::ScriptFunc(): on Linux truncate output and comment not to run into an exec(3) error E2BIG due to a too long argument. This sends a notification with truncated output instead of not sending. --- lib/methods/pluginnotificationtask.cpp | 44 ++++++++++++++- test/CMakeLists.txt | 3 + test/methods-pluginnotificationtask.cpp | 75 +++++++++++++++++++++++++ 3 files changed, 121 insertions(+), 1 deletion(-) create mode 100644 test/methods-pluginnotificationtask.cpp diff --git a/lib/methods/pluginnotificationtask.cpp b/lib/methods/pluginnotificationtask.cpp index a20c971a1f3..3c1c8a39a61 100644 --- a/lib/methods/pluginnotificationtask.cpp +++ b/lib/methods/pluginnotificationtask.cpp @@ -12,6 +12,17 @@ #include "base/process.hpp" #include "base/convert.hpp" +#ifdef __linux__ +# include +# include + +# ifndef PAGE_SIZE +# define PAGE_SIZE getpagesize() +# endif /* PAGE_SIZE */ + +const static auto l_MaxOutLen = MAX_ARG_STRLEN * 3u / 4u; +#endif /* __linux__ */ + using namespace icinga; REGISTER_FUNCTION_NONCONST(Internal, PluginNotification, &PluginNotificationTask::ScriptFunc, "notification:user:cr:itype:author:comment:resolvedMacros:useResolvedMacros"); @@ -33,7 +44,11 @@ void PluginNotificationTask::ScriptFunc(const Notification::Ptr& notification, Dictionary::Ptr notificationExtra = new Dictionary({ { "type", Notification::NotificationTypeToStringCompat(type) }, //TODO: Change that to our types. { "author", author }, +#ifdef __linux__ + { "comment", comment.SubStr(0, l_MaxOutLen) } +#else /* __linux__ */ { "comment", comment } +#endif /* __linux__ */ }); Host::Ptr host; @@ -48,8 +63,35 @@ void PluginNotificationTask::ScriptFunc(const Notification::Ptr& notification, resolvers.emplace_back("user", user); resolvers.emplace_back("notification", notificationExtra); resolvers.emplace_back("notification", notification); - if (service) + + if (service) { +#ifdef __linux__ + auto cr (service->GetLastCheckResult()); + + if (cr) { + auto output (cr->GetOutput()); + + if (output.GetLength() > l_MaxOutLen) { + resolvers.emplace_back("service", new Dictionary({{"output", output.SubStr(0, l_MaxOutLen)}})); + } + } +#endif /* __linux__ */ + resolvers.emplace_back("service", service); + } + +#ifdef __linux__ + auto hcr (host->GetLastCheckResult()); + + if (hcr) { + auto output (hcr->GetOutput()); + + if (output.GetLength() > l_MaxOutLen) { + resolvers.emplace_back("host", new Dictionary({{"output", output.SubStr(0, l_MaxOutLen)}})); + } + } +#endif /* __linux__ */ + resolvers.emplace_back("host", host); resolvers.emplace_back("command", commandObj); diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 8919de304dc..cba82fbd100 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -31,12 +31,14 @@ set(base_test_SOURCES icinga-macros.cpp icinga-notification.cpp icinga-perfdata.cpp + methods-pluginnotificationtask.cpp remote-configpackageutility.cpp remote-url.cpp ${base_OBJS} $ $ $ + $ ) if(ICINGA2_UNITY_BUILD) @@ -156,6 +158,7 @@ add_boost_test(base icinga_perfdata/multi icinga_perfdata/scientificnotation icinga_perfdata/parse_edgecases + methods_pluginnotificationtask/truncate_long_output remote_configpackageutility/ValidateName remote_url/id_and_path remote_url/parameters diff --git a/test/methods-pluginnotificationtask.cpp b/test/methods-pluginnotificationtask.cpp new file mode 100644 index 00000000000..e9224beae1a --- /dev/null +++ b/test/methods-pluginnotificationtask.cpp @@ -0,0 +1,75 @@ +/* Icinga 2 | (c) 2023 Icinga GmbH | GPLv2+ */ + +#include "base/array.hpp" +#include "icinga/checkresult.hpp" +#include "icinga/host.hpp" +#include "icinga/notification.hpp" +#include "icinga/notificationcommand.hpp" +#include "icinga/service.hpp" +#include "icinga/user.hpp" +#include "methods/pluginnotificationtask.hpp" +#include +#include + +using namespace icinga; + +BOOST_AUTO_TEST_SUITE(methods_pluginnotificationtask) + +BOOST_AUTO_TEST_CASE(truncate_long_output) +{ +#ifdef __linux__ + Host::Ptr h = new Host(); + CheckResult::Ptr hcr = new CheckResult(); + CheckResult::Ptr scr = new CheckResult(); + Service::Ptr s = new Service(); + User::Ptr u = new User(); + NotificationCommand::Ptr nc = new NotificationCommand(); + Notification::Ptr n = new Notification(); + String placeHolder (1024 * 1024, 'x'); + std::promise promise; + auto future (promise.get_future()); + + hcr->SetOutput("H" + placeHolder + "h", true); + scr->SetOutput("S" + placeHolder + "s", true); + + h->SetName("example.com", true); + h->SetLastCheckResult(hcr, true); + h->Register(); + + s->SetHostName("example.com", true); + s->SetShortName("disk", true); + s->SetLastCheckResult(scr, true); + ((ConfigObject*)s.get())->OnAllConfigLoaded(); // link Host + + nc->SetName("mail", true); + nc->SetCommandLine(new Array({"echo", "$host.output$", "$service.output$", "$notification.comment$"}), true); + nc->Register(); + + n->SetFieldByName("host_name", "example.com", false, DebugInfo()); + n->SetFieldByName("service_name", "disk", false, DebugInfo()); + n->SetFieldByName("command", "mail", false, DebugInfo()); + ((ConfigObject*)n.get())->OnAllConfigLoaded(); // link Service + + Checkable::ExecuteCommandProcessFinishedHandler = [&promise](const Value& commandline, const ProcessResult&) { + promise.set_value(commandline); + }; + + PluginNotificationTask::ScriptFunc(n, u, nullptr, NotificationCustom, "jdoe", "C" + placeHolder + "c", nullptr, false); + future.wait(); + + Checkable::ExecuteCommandProcessFinishedHandler = nullptr; + h->Unregister(); + nc->Unregister(); + + String commandline = Array::Ptr(future.get())->Join(" "); + + BOOST_CHECK(commandline.Contains("echo Hx")); + BOOST_CHECK(!commandline.Contains("xh")); + BOOST_CHECK(commandline.Contains("x Sx")); + BOOST_CHECK(!commandline.Contains("xs")); + BOOST_CHECK(commandline.Contains("x Cx")); + BOOST_CHECK(!commandline.Contains("xc")); +#endif /* __linux__ */ +} + +BOOST_AUTO_TEST_SUITE_END()