From 175153ce6adacc92e584225934e86b9a3d7ca434 Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Tue, 19 Dec 2023 12:21:03 +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 | 47 ++++++++++++- test/CMakeLists.txt | 3 + test/methods-pluginnotificationtask.cpp | 88 +++++++++++++++++++++++++ 3 files changed, 137 insertions(+), 1 deletion(-) create mode 100644 test/methods-pluginnotificationtask.cpp diff --git a/lib/methods/pluginnotificationtask.cpp b/lib/methods/pluginnotificationtask.cpp index a20c971a1f3..95911fae92c 100644 --- a/lib/methods/pluginnotificationtask.cpp +++ b/lib/methods/pluginnotificationtask.cpp @@ -12,6 +12,20 @@ #include "base/process.hpp" #include "base/convert.hpp" +#ifdef __linux__ +# include +# include + +# ifndef PAGE_SIZE +// MAX_ARG_STRLEN is a multiple of PAGE_SIZE which is missing +# define PAGE_SIZE getpagesize() +# endif /* PAGE_SIZE */ + +// Make e.g. the $host.output$ itself even 10% shorter to leave enough room +// for e.g. --host-output= as in --host-output=$host.output$, but without int overflow +const static auto l_MaxOutLen = MAX_ARG_STRLEN - MAX_ARG_STRLEN / 10u; +#endif /* __linux__ */ + using namespace icinga; REGISTER_FUNCTION_NONCONST(Internal, PluginNotification, &PluginNotificationTask::ScriptFunc, "notification:user:cr:itype:author:comment:resolvedMacros:useResolvedMacros"); @@ -33,7 +47,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 +66,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 24eb2198cf0..dd7a04df7df 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) @@ -161,6 +163,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..ec582dc8a0e --- /dev/null +++ b/test/methods-pluginnotificationtask.cpp @@ -0,0 +1,88 @@ +/* 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); + s->OnAllConfigLoaded(); // link Host + + nc->SetCommandLine( + new Array({ + "echo", + "host_output=$host.output$", + "service_output=$service.output$", + "notification_comment=$notification.comment$", + "output=$output$", + "comment=$comment$" + }), + true + ); + + nc->SetName("mail", true); + nc->Register(); + + n->SetFieldByName("host_name", "example.com", false, DebugInfo()); + n->SetFieldByName("service_name", "disk", false, DebugInfo()); + n->SetFieldByName("command", "mail", false, DebugInfo()); + n->OnAllConfigLoaded(); // link Service + + Checkable::ExecuteCommandProcessFinishedHandler = [&promise](const Value&, const ProcessResult& pr) { + promise.set_value(pr.Output); + }; + + PluginNotificationTask::ScriptFunc(n, u, nullptr, NotificationCustom, "jdoe", "C" + placeHolder + "c", nullptr, false); + future.wait(); + + Checkable::ExecuteCommandProcessFinishedHandler = nullptr; + h->Unregister(); + nc->Unregister(); + + auto output (future.get()); + + BOOST_CHECK(output.Contains("host_output=Hx")); + BOOST_CHECK(!output.Contains("xh")); + BOOST_CHECK(output.Contains("x service_output=Sx")); + BOOST_CHECK(!output.Contains("xs")); + BOOST_CHECK(output.Contains("x notification_comment=Cx")); + BOOST_CHECK(!output.Contains("xc")); + BOOST_CHECK(output.Contains("x output=Sx")); + BOOST_CHECK(output.Contains("x comment=Cx")); +#endif /* __linux__ */ +} + +BOOST_AUTO_TEST_SUITE_END()