[icinga-checkins] icinga.org: icinga2/master: Shell-escape macros.

git at icinga.org git at icinga.org
Fri Mar 22 10:58:55 CET 2013


Module: icinga2
Branch: master
Commit: c63684a72f6c2ad20109a1d9cfd97a325b34b019
URL:    https://git.icinga.org/?p=icinga2.git;a=commit;h=c63684a72f6c2ad20109a1d9cfd97a325b34b019

Author: Gunnar Beutner <gunnar.beutner at netways.de>
Date:   Fri Mar 22 10:58:47 2013 +0100

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(-)

diff --git a/lib/base/qstring.cpp b/lib/base/qstring.cpp
index 8ee5c0e..1ae6cff 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 38d289d..5c7628e 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 b12367f..d970380 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 826863f..4a5e869 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 4345803..53b310e 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>()) {
 		Array::Ptr resultArr = boost::make_shared<Array>();
 		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 acbd278..7860c8f 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 <boost/function.hpp>
 #include <vector>
 
 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<String (const String&)> EscapeCallback;
+
+	static Value ResolveMacros(const Value& str, const Dictionary::Ptr& macros,
+	    const EscapeCallback& escapeFn = EscapeCallback());
 	static Dictionary::Ptr MergeMacroDicts(const std::vector<Dictionary::Ptr>& 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 6d1458f..89405d1 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>(Process::SplitCommand(command), macros);
 
diff --git a/lib/icinga/pluginnotificationtask.cpp b/lib/icinga/pluginnotificationtask.cpp
index 217df95..c15328e 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>(Process::SplitCommand(command), macros);
 
diff --git a/test/Makefile.am b/test/Makefile.am
index 6e3a78f..48e8a8e 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 0000000..ee147f2
--- /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 <boost/test/unit_test.hpp>
+#include <boost/smart_ptr/make_shared.hpp>
+#include <iostream>
+
+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()





More information about the icinga-checkins mailing list