From c63684a72f6c2ad20109a1d9cfd97a325b34b019 Mon Sep 17 00:00:00 2001 From: Gunnar Beutner Date: Fri, 22 Mar 2013 10:58:47 +0100 Subject: [PATCH] Shell-escape macros. --- lib/base/qstring.cpp | 11 ++++++ lib/base/qstring.h | 2 ++ lib/base/utility.cpp | 46 ++++++++++++++++++++++++ lib/base/utility.h | 2 ++ lib/icinga/macroprocessor.cpp | 11 +++--- lib/icinga/macroprocessor.h | 9 +++-- lib/icinga/pluginchecktask.cpp | 2 +- lib/icinga/pluginnotificationtask.cpp | 2 +- test/Makefile.am | 3 +- test/base-shellescape.cpp | 51 +++++++++++++++++++++++++++ 10 files changed, 130 insertions(+), 9 deletions(-) create mode 100644 test/base-shellescape.cpp diff --git a/lib/base/qstring.cpp b/lib/base/qstring.cpp index 8ee5c0e40..1ae6cff0d 100644 --- a/lib/base/qstring.cpp +++ b/lib/base/qstring.cpp @@ -86,6 +86,12 @@ String& String::operator+=(const char *rhs) return *this; } +String& String::operator+=(char rhs) +{ + m_Data += rhs; + return *this; +} + bool String::IsEmpty(void) const { return m_Data.empty(); @@ -121,6 +127,11 @@ size_t String::FindFirstOf(const char *s, size_t pos) const return m_Data.find_first_of(s, pos); } +size_t String::FindFirstOf(char ch, size_t pos) const +{ + return m_Data.find_first_of(ch, pos); +} + String String::SubStr(size_t first, size_t len) const { return m_Data.substr(first, len); diff --git a/lib/base/qstring.h b/lib/base/qstring.h index 38d289dce..5c7628e9d 100644 --- a/lib/base/qstring.h +++ b/lib/base/qstring.h @@ -63,6 +63,7 @@ public: String& operator+=(const String& rhs); String& operator+=(const char *rhs); + String& operator+=(char rhs); bool IsEmpty(void) const; @@ -75,6 +76,7 @@ public: size_t GetLength(void) const; size_t FindFirstOf(const char *s, size_t pos = 0) const; + size_t FindFirstOf(char ch, size_t pos = 0) const; String SubStr(size_t first, size_t len = NPos) const; void Replace(size_t first, size_t second, const String& str); diff --git a/lib/base/utility.cpp b/lib/base/utility.cpp index b12367fbf..d970380bd 100644 --- a/lib/base/utility.cpp +++ b/lib/base/utility.cpp @@ -431,3 +431,49 @@ String Utility::FormatDateTime(const char *format, double ts) return timestamp; } + +String Utility::EscapeShellCmd(const String& s) +{ + String result; + int prev_quote = String::NPos; + ssize_t index = -1; + + BOOST_FOREACH(char ch, s) { + bool escape = false; + + index++; + +#ifdef _WIN32 + if (ch == '%' || ch == '"' || ch == '\'') + escape = true; +#else /* _WIN32 */ + if (ch == '"' || ch == '\'') { + /* Find a matching closing quotation character. */ + if (prev_quote == String::NPos && (prev_quote = s.FindFirstOf(ch, index + 1)) != String::NPos) + ; /* Empty statement. */ + else if (prev_quote != String::NPos && s[prev_quote] == ch) + prev_quote = String::NPos; + else + escape = true; + } +#endif /* _WIN32 */ + + if (ch == '#' || ch == '&' || ch == ';' || ch == '`' || ch == '|' || + ch == '*' || ch == '?' || ch == '~' || ch == '<' || ch == '>' || + ch == '^' || ch == '(' || ch == ')' || ch == '[' || ch == ']' || + ch == '{' || ch == '}' || ch == '$' || ch == '\\' || ch == '\x0A' || + ch == '\xFF') + escape = true; + + if (escape) +#ifdef _WIN32 + result += '%'; +#else /* _WIN32 */ + result += '\\'; +#endif /* _WIN32 */ + + result += ch; + } + + return result; +} diff --git a/lib/base/utility.h b/lib/base/utility.h index 826863fd6..4a5e86938 100644 --- a/lib/base/utility.h +++ b/lib/base/utility.h @@ -75,6 +75,8 @@ public: static void SetNonBlockingSocket(SOCKET s); + static String EscapeShellCmd(const String& s); + private: Utility(void); }; diff --git a/lib/icinga/macroprocessor.cpp b/lib/icinga/macroprocessor.cpp index 434580366..53b310eff 100644 --- a/lib/icinga/macroprocessor.cpp +++ b/lib/icinga/macroprocessor.cpp @@ -30,14 +30,15 @@ using namespace icinga; /** * @threadsafety Always. */ -Value MacroProcessor::ResolveMacros(const Value& cmd, const Dictionary::Ptr& macros) +Value MacroProcessor::ResolveMacros(const Value& cmd, const Dictionary::Ptr& macros, + const MacroProcessor::EscapeCallback& escapeFn) { Value result; ASSERT(macros->IsSealed()); if (cmd.IsScalar()) { - result = InternalResolveMacros(cmd, macros); + result = InternalResolveMacros(cmd, macros, escapeFn); } else if (cmd.IsObjectType()) { Array::Ptr resultArr = boost::make_shared(); Array::Ptr arr = cmd; @@ -45,7 +46,8 @@ Value MacroProcessor::ResolveMacros(const Value& cmd, const Dictionary::Ptr& mac ObjectLock olock(arr); BOOST_FOREACH(const Value& arg, arr) { - resultArr->Add(InternalResolveMacros(arg, macros)); + /* Note: don't escape macros here. */ + resultArr->Add(InternalResolveMacros(arg, macros, EscapeCallback())); } result = resultArr; @@ -59,7 +61,8 @@ Value MacroProcessor::ResolveMacros(const Value& cmd, const Dictionary::Ptr& mac /** * @threadsafety Always. */ -String MacroProcessor::InternalResolveMacros(const String& str, const Dictionary::Ptr& macros) +String MacroProcessor::InternalResolveMacros(const String& str, const Dictionary::Ptr& macros, + const MacroProcessor::EscapeCallback& escapeFn) { size_t offset, pos_first, pos_second; offset = 0; diff --git a/lib/icinga/macroprocessor.h b/lib/icinga/macroprocessor.h index acbd278fc..7860c8f93 100644 --- a/lib/icinga/macroprocessor.h +++ b/lib/icinga/macroprocessor.h @@ -22,6 +22,7 @@ #include "icinga/i2-icinga.h" #include "base/dictionary.h" +#include #include namespace icinga @@ -35,13 +36,17 @@ namespace icinga class I2_ICINGA_API MacroProcessor { public: - static Value ResolveMacros(const Value& str, const Dictionary::Ptr& macros); + typedef boost::function EscapeCallback; + + static Value ResolveMacros(const Value& str, const Dictionary::Ptr& macros, + const EscapeCallback& escapeFn = EscapeCallback()); static Dictionary::Ptr MergeMacroDicts(const std::vector& macroDicts); private: MacroProcessor(void); - static String InternalResolveMacros(const String& str, const Dictionary::Ptr& macros); + static String InternalResolveMacros(const String& str, + const Dictionary::Ptr& macros, const EscapeCallback& escapeFn); }; } diff --git a/lib/icinga/pluginchecktask.cpp b/lib/icinga/pluginchecktask.cpp index 6d1458f39..89405d1d9 100644 --- a/lib/icinga/pluginchecktask.cpp +++ b/lib/icinga/pluginchecktask.cpp @@ -48,7 +48,7 @@ void PluginCheckTask::ScriptFunc(const ScriptTask::Ptr& task, const std::vector< Dictionary::Ptr macros = arguments[1]; Value raw_command = service->GetCheckCommand(); - Value command = MacroProcessor::ResolveMacros(raw_command, macros); + Value command = MacroProcessor::ResolveMacros(raw_command, macros, Utility::EscapeShellCmd); Process::Ptr process = boost::make_shared(Process::SplitCommand(command), macros); diff --git a/lib/icinga/pluginnotificationtask.cpp b/lib/icinga/pluginnotificationtask.cpp index 217df959a..c15328eb2 100644 --- a/lib/icinga/pluginnotificationtask.cpp +++ b/lib/icinga/pluginnotificationtask.cpp @@ -69,7 +69,7 @@ void PluginNotificationTask::ScriptFunc(const ScriptTask::Ptr& task, const std:: Dictionary::Ptr allMacros = MacroProcessor::MergeMacroDicts(macroDicts); - Value command = MacroProcessor::ResolveMacros(raw_command, allMacros); + Value command = MacroProcessor::ResolveMacros(raw_command, allMacros, Utility::EscapeShellCmd); Process::Ptr process = boost::make_shared(Process::SplitCommand(command), macros); diff --git a/test/Makefile.am b/test/Makefile.am index 6e3a78f0f..48e8a8eeb 100644 --- a/test/Makefile.am +++ b/test/Makefile.am @@ -8,7 +8,8 @@ check_PROGRAMS = \ icinga2_test_SOURCES = \ test.cpp \ - base-dictionary.cpp + base-dictionary.cpp \ + base-shellescape.cpp icinga2_test_CPPFLAGS = \ $(BOOST_CPPFLAGS) \ diff --git a/test/base-shellescape.cpp b/test/base-shellescape.cpp new file mode 100644 index 000000000..ee147f24b --- /dev/null +++ b/test/base-shellescape.cpp @@ -0,0 +1,51 @@ +/****************************************************************************** + * Icinga 2 * + * Copyright (C) 2012 Icinga Development Team (http://www.icinga.org/) * + * * + * This program is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License * + * as published by the Free Software Foundation; either version 2 * + * of the License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the Free Software Foundation * + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * + ******************************************************************************/ + +#include "base/utility.h" +#include +#include +#include + +using namespace icinga; + +BOOST_AUTO_TEST_SUITE(base_shellescape) + +BOOST_AUTO_TEST_CASE(escape_basic) +{ +#ifdef _WIN32 + BOOST_CHECK(Utility::EscapeShellCmd("%PATH%") == "^%PATH^%"); +#endif /* _WIN32 */ + + BOOST_CHECK(Utility::EscapeShellCmd("$PATH") == "\\$PATH"); + BOOST_CHECK(Utility::EscapeShellCmd("\\$PATH") == "\\\\\\$PATH"); + +} + +BOOST_AUTO_TEST_CASE(escape_quoted) +{ +#ifdef _WIN32 + BOOST_CHECK(Utility::EscapeShellCmd("'hello'") == "\\'hello\\'"); + BOOST_CHECK(Utility::EscapeShellCmd("\"hello\"") == "\\\"hello\\\""); +#else /* _WIN32 */ + BOOST_CHECK(Utility::EscapeShellCmd("'hello'") == "'hello'"); + BOOST_CHECK(Utility::EscapeShellCmd("'hello") == "\\'hello"); +#endif /* _WIN32 */ +} + +BOOST_AUTO_TEST_SUITE_END()