1 /******************************************************************************
3 * Copyright (C) 2012-2015 Icinga Development Team (http://www.icinga.org) *
5 * This program is free software; you can redistribute it and/or *
6 * modify it under the terms of the GNU General Public License *
7 * as published by the Free Software Foundation; either version 2 *
8 * of the License, or (at your option) any later version. *
10 * This program is distributed in the hope that it will be useful, *
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13 * GNU General Public License for more details. *
15 * You should have received a copy of the GNU General Public License *
16 * along with this program; if not, write to the Free Software Foundation *
17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. *
18 ******************************************************************************/
20 #include "icinga/macroprocessor.hpp"
21 #include "icinga/macroresolver.hpp"
22 #include "icinga/customvarobject.hpp"
23 #include "base/array.hpp"
24 #include "base/objectlock.hpp"
25 #include "base/logger.hpp"
26 #include "base/context.hpp"
27 #include "base/configobject.hpp"
28 #include "base/scriptframe.hpp"
29 #include "base/convert.hpp"
30 #include "base/exception.hpp"
31 #include <boost/assign.hpp>
32 #include <boost/foreach.hpp>
33 #include <boost/algorithm/string/split.hpp>
34 #include <boost/algorithm/string/join.hpp>
35 #include <boost/algorithm/string/classification.hpp>
37 using namespace icinga;
39 Value MacroProcessor::ResolveMacros(const Value& str, const ResolverList& resolvers,
40 const CheckResult::Ptr& cr, String *missingMacro,
41 const MacroProcessor::EscapeCallback& escapeFn, const Dictionary::Ptr& resolvedMacros,
42 bool useResolvedMacros, int recursionLevel)
50 result = InternalResolveMacros(str, resolvers, cr, missingMacro, escapeFn,
51 resolvedMacros, useResolvedMacros, recursionLevel + 1);
52 } else if (str.IsObjectType<Array>()) {
53 Array::Ptr resultArr = new Array();
56 ObjectLock olock(arr);
58 BOOST_FOREACH(const Value& arg, arr) {
59 /* Note: don't escape macros here. */
60 Value value = InternalResolveMacros(arg, resolvers, cr, missingMacro,
61 EscapeCallback(), resolvedMacros, useResolvedMacros, recursionLevel + 1);
63 if (value.IsObjectType<Array>())
64 resultArr->Add(Utility::Join(value, ';'));
66 resultArr->Add(value);
70 } else if (str.IsObjectType<Dictionary>()) {
71 Dictionary::Ptr resultDict = new Dictionary();
72 Dictionary::Ptr dict = str;
74 ObjectLock olock(dict);
76 BOOST_FOREACH(const Dictionary::Pair& kv, dict) {
77 /* Note: don't escape macros here. */
78 resultDict->Set(kv.first, InternalResolveMacros(kv.second, resolvers, cr, missingMacro,
79 EscapeCallback(), resolvedMacros, useResolvedMacros, recursionLevel + 1));
83 } else if (str.IsObjectType<Function>()) {
84 result = EvaluateFunction(str, resolvers, cr, escapeFn, resolvedMacros, useResolvedMacros, 0);
86 BOOST_THROW_EXCEPTION(std::invalid_argument("Macro is not a string or array."));
92 bool MacroProcessor::ResolveMacro(const String& macro, const ResolverList& resolvers,
93 const CheckResult::Ptr& cr, Value *result, bool *recursive_macro)
95 CONTEXT("Resolving macro '" + macro + "'");
97 *recursive_macro = false;
99 std::vector<String> tokens;
100 boost::algorithm::split(tokens, macro, boost::is_any_of("."));
103 if (tokens.size() > 1) {
105 tokens.erase(tokens.begin());
108 BOOST_FOREACH(const ResolverSpec& resolver, resolvers) {
109 if (!objName.IsEmpty() && objName != resolver.first)
112 if (objName.IsEmpty()) {
113 CustomVarObject::Ptr dobj = dynamic_pointer_cast<CustomVarObject>(resolver.second);
116 Dictionary::Ptr vars = dobj->GetVars();
118 if (vars && vars->Contains(macro)) {
119 *result = vars->Get(macro);
120 *recursive_macro = true;
126 MacroResolver *mresolver = dynamic_cast<MacroResolver *>(resolver.second.get());
128 if (mresolver && mresolver->ResolveMacro(boost::algorithm::join(tokens, "."), cr, result))
131 Value ref = resolver.second;
134 BOOST_FOREACH(const String& token, tokens) {
135 if (ref.IsObjectType<Dictionary>()) {
136 Dictionary::Ptr dict = ref;
137 if (dict->Contains(token)) {
138 ref = dict->Get(token);
144 } else if (ref.IsObject()) {
145 Object::Ptr object = ref;
147 Type::Ptr type = object->GetReflectionType();
154 int field = type->GetFieldId(token);
161 ref = object->GetField(field);
166 if (tokens[0] == "vars" ||
167 tokens[0] == "action_url" ||
168 tokens[0] == "notes_url" ||
169 tokens[0] == "notes")
170 *recursive_macro = true;
180 Value MacroProcessor::InternalResolveMacrosShim(const std::vector<Value>& args, const ResolverList& resolvers,
181 const CheckResult::Ptr& cr, const MacroProcessor::EscapeCallback& escapeFn, const Dictionary::Ptr& resolvedMacros,
182 bool useResolvedMacros, int recursionLevel)
185 BOOST_THROW_EXCEPTION(std::invalid_argument("Too few arguments for function"));
189 return MacroProcessor::InternalResolveMacros(args[0], resolvers, cr, &missingMacro, escapeFn,
190 resolvedMacros, useResolvedMacros, recursionLevel);
193 Value MacroProcessor::InternalResolveArgumentsShim(const std::vector<Value>& args, const ResolverList& resolvers,
194 const CheckResult::Ptr& cr, const Dictionary::Ptr& resolvedMacros,
195 bool useResolvedMacros, int recursionLevel)
198 BOOST_THROW_EXCEPTION(std::invalid_argument("Too few arguments for function"));
200 return MacroProcessor::ResolveArguments(args[0], args[1], resolvers, cr,
201 resolvedMacros, useResolvedMacros, recursionLevel);
204 Value MacroProcessor::EvaluateFunction(const Function::Ptr& func, const ResolverList& resolvers,
205 const CheckResult::Ptr& cr, const MacroProcessor::EscapeCallback& escapeFn,
206 const Dictionary::Ptr& resolvedMacros, bool useResolvedMacros, int recursionLevel)
208 Dictionary::Ptr resolvers_this = new Dictionary();
210 BOOST_FOREACH(const ResolverSpec& resolver, resolvers) {
211 resolvers_this->Set(resolver.first, resolver.second);
214 resolvers_this->Set("macro", new Function(boost::bind(&MacroProcessor::InternalResolveMacrosShim,
215 _1, boost::cref(resolvers), cr, MacroProcessor::EscapeCallback(), resolvedMacros, useResolvedMacros,
216 recursionLevel + 1)));
217 resolvers_this->Set("resolve_arguments", new Function(boost::bind(&MacroProcessor::InternalResolveArgumentsShim,
218 _1, boost::cref(resolvers), cr, resolvedMacros, useResolvedMacros,
219 recursionLevel + 1)));
221 ScriptFrame frame(resolvers_this);
222 return func->Invoke();
225 Value MacroProcessor::InternalResolveMacros(const String& str, const ResolverList& resolvers,
226 const CheckResult::Ptr& cr, String *missingMacro,
227 const MacroProcessor::EscapeCallback& escapeFn, const Dictionary::Ptr& resolvedMacros,
228 bool useResolvedMacros, int recursionLevel)
230 CONTEXT("Resolving macros for string '" + str + "'");
232 if (recursionLevel > 15)
233 BOOST_THROW_EXCEPTION(std::runtime_error("Infinite recursion detected while resolving macros"));
235 size_t offset, pos_first, pos_second;
238 Dictionary::Ptr resolvers_this;
241 while ((pos_first = result.FindFirstOf("$", offset)) != String::NPos) {
242 pos_second = result.FindFirstOf("$", pos_first + 1);
244 if (pos_second == String::NPos)
245 BOOST_THROW_EXCEPTION(std::runtime_error("Closing $ not found in macro format string."));
247 String name = result.SubStr(pos_first + 1, pos_second - pos_first - 1);
249 Value resolved_macro;
250 bool recursive_macro;
253 if (useResolvedMacros) {
254 recursive_macro = false;
255 found = resolvedMacros->Contains(name);
258 resolved_macro = resolvedMacros->Get(name);
260 found = ResolveMacro(name, resolvers, cr, &resolved_macro, &recursive_macro);
262 /* $$ is an escape sequence for $. */
263 if (name.IsEmpty()) {
264 resolved_macro = "$";
268 if (resolved_macro.IsObjectType<Function>()) {
269 resolved_macro = EvaluateFunction(resolved_macro, resolvers, cr, escapeFn,
270 resolvedMacros, useResolvedMacros, recursionLevel + 1);
275 Log(LogWarning, "MacroProcessor")
276 << "Macro '" << name << "' is not defined.";
278 *missingMacro = name;
281 /* recursively resolve macros in the macro if it was a user macro */
282 if (recursive_macro) {
283 if (resolved_macro.IsObjectType<Array>()) {
284 Array::Ptr arr = resolved_macro;
285 Array::Ptr resolved_arr = new Array();
287 ObjectLock olock(arr);
288 BOOST_FOREACH(const Value& value, arr) {
289 if (value.IsScalar()) {
290 resolved_arr->Add(InternalResolveMacros(value,
291 resolvers, cr, missingMacro, EscapeCallback(), Dictionary::Ptr(),
292 false, recursionLevel + 1));
294 resolved_arr->Add(value);
297 resolved_macro = resolved_arr;
298 } else if (resolved_macro.IsString()) {
299 resolved_macro = InternalResolveMacros(resolved_macro,
300 resolvers, cr, missingMacro, EscapeCallback(), Dictionary::Ptr(),
301 false, recursionLevel + 1);
305 if (!useResolvedMacros && found && resolvedMacros)
306 resolvedMacros->Set(name, resolved_macro);
309 resolved_macro = escapeFn(resolved_macro);
311 /* we're done if this is the only macro and there are no other non-macro parts in the string */
312 if (pos_first == 0 && pos_second == str.GetLength() - 1)
313 return resolved_macro;
314 else if (resolved_macro.IsObjectType<Array>())
315 BOOST_THROW_EXCEPTION(std::invalid_argument("Mixing both strings and non-strings in macros is not allowed."));
317 if (resolved_macro.IsObjectType<Array>()) {
318 /* don't allow mixing strings and arrays in macro strings */
319 if (pos_first != 0 || pos_second != str.GetLength() - 1)
320 BOOST_THROW_EXCEPTION(std::invalid_argument("Mixing both strings and non-strings in macros is not allowed."));
322 return resolved_macro;
325 String resolved_macro_str = resolved_macro;
327 result.Replace(pos_first, pos_second - pos_first + 1, resolved_macro_str);
328 offset = pos_first + resolved_macro_str.GetLength();
335 bool MacroProcessor::ValidateMacroString(const String& macro)
340 size_t pos_first, pos_second, offset;
343 while ((pos_first = macro.FindFirstOf("$", offset)) != String::NPos) {
344 pos_second = macro.FindFirstOf("$", pos_first + 1);
346 if (pos_second == String::NPos)
349 offset = pos_second + 1;
355 void MacroProcessor::ValidateCustomVars(const ConfigObject::Ptr& object, const Dictionary::Ptr& value)
360 /* string, array, dictionary */
361 ObjectLock olock(value);
362 BOOST_FOREACH(const Dictionary::Pair& kv, value) {
363 const Value& varval = kv.second;
365 if (varval.IsObjectType<Dictionary>()) {
366 /* only one dictonary level */
367 Dictionary::Ptr varval_dict = varval;
369 ObjectLock xlock(varval_dict);
370 BOOST_FOREACH(const Dictionary::Pair& kv_var, varval_dict) {
371 if (kv_var.second.IsEmpty())
374 if (!ValidateMacroString(kv_var.second))
375 BOOST_THROW_EXCEPTION(ValidationError(object.get(), boost::assign::list_of<String>("vars")(kv.first)(kv_var.first), "Closing $ not found in macro format string '" + kv_var.second + "'."));
377 } else if (varval.IsObjectType<Array>()) {
378 /* check all array entries */
379 Array::Ptr varval_arr = varval;
381 ObjectLock ylock (varval_arr);
382 BOOST_FOREACH(const Value& arrval, varval_arr) {
383 if (arrval.IsEmpty())
386 if (!ValidateMacroString(arrval)) {
387 BOOST_THROW_EXCEPTION(ValidationError(object.get(), boost::assign::list_of<String>("vars")(kv.first), "Closing $ not found in macro format string '" + arrval + "'."));
391 if (varval.IsEmpty())
394 String varstr = varval;
396 if (!ValidateMacroString(varstr))
397 BOOST_THROW_EXCEPTION(ValidationError(object.get(), boost::assign::list_of<String>("vars")(kv.first), "Closing $ not found in macro format string '" + varstr + "'."));
402 void MacroProcessor::AddArgumentHelper(const Array::Ptr& args, const String& key, const String& value,
403 bool add_key, bool add_value)
412 Value MacroProcessor::EscapeMacroShellArg(const Value& value)
416 if (value.IsObjectType<Array>()) {
417 Array::Ptr arr = value;
419 ObjectLock olock(arr);
420 BOOST_FOREACH(const Value& arg, arr) {
421 if (result.GetLength() > 0)
424 result += Utility::EscapeShellArg(arg);
427 result = Utility::EscapeShellArg(value);
432 struct CommandArgument
441 CommandArgument(void)
442 : Order(0), SkipKey(false), RepeatKey(true), SkipValue(false)
445 bool operator<(const CommandArgument& rhs) const
447 return Order < rhs.Order;
451 Value MacroProcessor::ResolveArguments(const Value& command, const Dictionary::Ptr& arguments,
452 const MacroProcessor::ResolverList& resolvers, const CheckResult::Ptr& cr,
453 const Dictionary::Ptr& resolvedMacros, bool useResolvedMacros, int recursionLevel)
455 Value resolvedCommand;
456 if (!arguments || command.IsObjectType<Array>() || command.IsObjectType<Function>())
457 resolvedCommand = MacroProcessor::ResolveMacros(command, resolvers, cr, NULL,
458 EscapeMacroShellArg, resolvedMacros, useResolvedMacros, recursionLevel + 1);
460 Array::Ptr arr = new Array();
462 resolvedCommand = arr;
466 std::vector<CommandArgument> args;
468 ObjectLock olock(arguments);
469 BOOST_FOREACH(const Dictionary::Pair& kv, arguments) {
470 const Value& arginfo = kv.second;
475 bool required = false;
478 if (arginfo.IsObjectType<Dictionary>()) {
479 Dictionary::Ptr argdict = arginfo;
480 if (argdict->Contains("key"))
481 arg.Key = argdict->Get("key");
482 argval = argdict->Get("value");
483 if (argdict->Contains("required"))
484 required = argdict->Get("required");
485 arg.SkipKey = argdict->Get("skip_key");
486 if (argdict->Contains("repeat_key"))
487 arg.RepeatKey = argdict->Get("repeat_key");
488 arg.Order = argdict->Get("order");
490 Value set_if = argdict->Get("set_if");
492 if (!set_if.IsEmpty()) {
494 Value set_if_resolved = MacroProcessor::ResolveMacros(set_if, resolvers,
495 cr, &missingMacro, MacroProcessor::EscapeCallback(), resolvedMacros,
496 useResolvedMacros, recursionLevel + 1);
498 if (!missingMacro.IsEmpty())
503 if (set_if_resolved == "true")
505 else if (set_if_resolved == "false")
509 value = Convert::ToLong(set_if_resolved);
510 } catch (const std::exception& ex) {
511 /* tried to convert a string */
512 Log(LogWarning, "PluginUtility")
513 << "Error evaluating set_if value '" << set_if_resolved << "': " << ex.what();
525 if (argval.IsEmpty())
526 arg.SkipValue = true;
529 arg.AValue = MacroProcessor::ResolveMacros(argval, resolvers,
530 cr, &missingMacro, MacroProcessor::EscapeCallback(), resolvedMacros,
531 useResolvedMacros, recursionLevel + 1);
533 if (!missingMacro.IsEmpty()) {
535 BOOST_THROW_EXCEPTION(ScriptError("Non-optional macro '" + missingMacro + "' used in argument '" +
536 arg.Key + "' is missing."));
545 std::sort(args.begin(), args.end());
547 Array::Ptr command_arr = resolvedCommand;
548 BOOST_FOREACH(const CommandArgument& arg, args) {
550 if (arg.AValue.IsObjectType<Dictionary>()) {
551 Log(LogWarning, "PluginUtility", "Tried to use dictionary in argument");
553 } else if (arg.AValue.IsObjectType<Array>()) {
555 Array::Ptr arr = static_cast<Array::Ptr>(arg.AValue);
557 ObjectLock olock(arr);
558 BOOST_FOREACH(const Value& value, arr) {
563 add_key = !arg.SkipKey;
565 add_key = !arg.SkipKey && arg.RepeatKey;
567 AddArgumentHelper(command_arr, arg.Key, value, add_key, !arg.SkipValue);
570 AddArgumentHelper(command_arr, arg.Key, arg.AValue, !arg.SkipKey, !arg.SkipValue);
574 return resolvedCommand;