1 /* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
3 #include "icinga/macroprocessor.hpp"
4 #include "icinga/macroresolver.hpp"
5 #include "icinga/customvarobject.hpp"
6 #include "base/array.hpp"
7 #include "base/objectlock.hpp"
8 #include "base/logger.hpp"
9 #include "base/context.hpp"
10 #include "base/configobject.hpp"
11 #include "base/scriptframe.hpp"
12 #include "base/convert.hpp"
13 #include "base/exception.hpp"
14 #include <boost/algorithm/string/join.hpp>
16 using namespace icinga;
18 Value MacroProcessor::ResolveMacros(const Value& str, const ResolverList& resolvers,
19 const CheckResult::Ptr& cr, String *missingMacro,
20 const MacroProcessor::EscapeCallback& escapeFn, const Dictionary::Ptr& resolvedMacros,
21 bool useResolvedMacros, int recursionLevel)
23 if (useResolvedMacros)
24 REQUIRE_NOT_NULL(resolvedMacros);
32 result = InternalResolveMacros(str, resolvers, cr, missingMacro, escapeFn,
33 resolvedMacros, useResolvedMacros, recursionLevel + 1);
34 } else if (str.IsObjectType<Array>()) {
38 ObjectLock olock(arr);
40 for (const Value& arg : arr) {
41 /* Note: don't escape macros here. */
42 Value value = InternalResolveMacros(arg, resolvers, cr, missingMacro,
43 EscapeCallback(), resolvedMacros, useResolvedMacros, recursionLevel + 1);
45 if (value.IsObjectType<Array>())
46 resultArr.push_back(Utility::Join(value, ';'));
48 resultArr.push_back(value);
51 result = new Array(std::move(resultArr));
52 } else if (str.IsObjectType<Dictionary>()) {
53 Dictionary::Ptr resultDict = new Dictionary();
54 Dictionary::Ptr dict = str;
56 ObjectLock olock(dict);
58 for (const Dictionary::Pair& kv : dict) {
59 /* Note: don't escape macros here. */
60 resultDict->Set(kv.first, InternalResolveMacros(kv.second, resolvers, cr, missingMacro,
61 EscapeCallback(), resolvedMacros, useResolvedMacros, recursionLevel + 1));
65 } else if (str.IsObjectType<Function>()) {
66 result = EvaluateFunction(str, resolvers, cr, escapeFn, resolvedMacros, useResolvedMacros, 0);
68 BOOST_THROW_EXCEPTION(std::invalid_argument("Macro is not a string or array."));
74 bool MacroProcessor::ResolveMacro(const String& macro, const ResolverList& resolvers,
75 const CheckResult::Ptr& cr, Value *result, bool *recursive_macro)
77 CONTEXT("Resolving macro '" + macro + "'");
79 *recursive_macro = false;
81 std::vector<String> tokens = macro.Split(".");
84 if (tokens.size() > 1) {
86 tokens.erase(tokens.begin());
89 for (const ResolverSpec& resolver : resolvers) {
90 if (!objName.IsEmpty() && objName != resolver.first)
93 if (objName.IsEmpty()) {
94 CustomVarObject::Ptr dobj = dynamic_pointer_cast<CustomVarObject>(resolver.second);
97 Dictionary::Ptr vars = dobj->GetVars();
99 if (vars && vars->Contains(macro)) {
100 *result = vars->Get(macro);
101 *recursive_macro = true;
107 auto *mresolver = dynamic_cast<MacroResolver *>(resolver.second.get());
109 if (mresolver && mresolver->ResolveMacro(boost::algorithm::join(tokens, "."), cr, result))
112 Value ref = resolver.second;
115 for (const String& token : tokens) {
116 if (ref.IsObjectType<Dictionary>()) {
117 Dictionary::Ptr dict = ref;
118 if (dict->Contains(token)) {
119 ref = dict->Get(token);
125 } else if (ref.IsObject()) {
126 Object::Ptr object = ref;
128 Type::Ptr type = object->GetReflectionType();
135 int field = type->GetFieldId(token);
142 ref = object->GetField(field);
144 Field fieldInfo = type->GetFieldInfo(field);
146 if (strcmp(fieldInfo.TypeName, "Timestamp") == 0)
147 ref = static_cast<long>(ref);
152 if (tokens[0] == "vars" ||
153 tokens[0] == "action_url" ||
154 tokens[0] == "notes_url" ||
155 tokens[0] == "notes")
156 *recursive_macro = true;
166 Value MacroProcessor::EvaluateFunction(const Function::Ptr& func, const ResolverList& resolvers,
167 const CheckResult::Ptr& cr, const MacroProcessor::EscapeCallback& escapeFn,
168 const Dictionary::Ptr& resolvedMacros, bool useResolvedMacros, int recursionLevel)
170 Dictionary::Ptr resolvers_this = new Dictionary();
172 for (const ResolverSpec& resolver : resolvers) {
173 resolvers_this->Set(resolver.first, resolver.second);
176 auto internalResolveMacrosShim = [resolvers, cr, resolvedMacros, useResolvedMacros, recursionLevel](const std::vector<Value>& args) {
178 BOOST_THROW_EXCEPTION(std::invalid_argument("Too few arguments for function"));
182 return MacroProcessor::InternalResolveMacros(args[0], resolvers, cr, &missingMacro, MacroProcessor::EscapeCallback(),
183 resolvedMacros, useResolvedMacros, recursionLevel);
186 resolvers_this->Set("macro", new Function("macro (temporary)", internalResolveMacrosShim, { "str" }));
188 auto internalResolveArgumentsShim = [resolvers, cr, resolvedMacros, useResolvedMacros, recursionLevel](const std::vector<Value>& args) {
190 BOOST_THROW_EXCEPTION(std::invalid_argument("Too few arguments for function"));
192 return MacroProcessor::ResolveArguments(args[0], args[1], resolvers, cr,
193 resolvedMacros, useResolvedMacros, recursionLevel + 1);
196 resolvers_this->Set("resolve_arguments", new Function("resolve_arguments (temporary)", internalResolveArgumentsShim, { "command", "args" }));
198 return func->InvokeThis(resolvers_this);
201 Value MacroProcessor::InternalResolveMacros(const String& str, const ResolverList& resolvers,
202 const CheckResult::Ptr& cr, String *missingMacro,
203 const MacroProcessor::EscapeCallback& escapeFn, const Dictionary::Ptr& resolvedMacros,
204 bool useResolvedMacros, int recursionLevel)
206 CONTEXT("Resolving macros for string '" + str + "'");
208 if (recursionLevel > 15)
209 BOOST_THROW_EXCEPTION(std::runtime_error("Infinite recursion detected while resolving macros"));
211 size_t offset, pos_first, pos_second;
214 Dictionary::Ptr resolvers_this;
217 while ((pos_first = result.FindFirstOf("$", offset)) != String::NPos) {
218 pos_second = result.FindFirstOf("$", pos_first + 1);
220 if (pos_second == String::NPos)
221 BOOST_THROW_EXCEPTION(std::runtime_error("Closing $ not found in macro format string."));
223 String name = result.SubStr(pos_first + 1, pos_second - pos_first - 1);
225 Value resolved_macro;
226 bool recursive_macro;
229 if (useResolvedMacros) {
230 recursive_macro = false;
231 found = resolvedMacros->Contains(name);
234 resolved_macro = resolvedMacros->Get(name);
236 found = ResolveMacro(name, resolvers, cr, &resolved_macro, &recursive_macro);
238 /* $$ is an escape sequence for $. */
239 if (name.IsEmpty()) {
240 resolved_macro = "$";
244 if (resolved_macro.IsObjectType<Function>()) {
245 resolved_macro = EvaluateFunction(resolved_macro, resolvers, cr, escapeFn,
246 resolvedMacros, useResolvedMacros, recursionLevel + 1);
251 Log(LogWarning, "MacroProcessor")
252 << "Macro '" << name << "' is not defined.";
254 *missingMacro = name;
257 /* recursively resolve macros in the macro if it was a user macro */
258 if (recursive_macro) {
259 if (resolved_macro.IsObjectType<Array>()) {
260 Array::Ptr arr = resolved_macro;
261 ArrayData resolved_arr;
263 ObjectLock olock(arr);
264 for (const Value& value : arr) {
265 if (value.IsScalar()) {
266 resolved_arr.push_back(InternalResolveMacros(value,
267 resolvers, cr, missingMacro, EscapeCallback(), nullptr,
268 false, recursionLevel + 1));
270 resolved_arr.push_back(value);
273 resolved_macro = new Array(std::move(resolved_arr));
274 } else if (resolved_macro.IsString()) {
275 resolved_macro = InternalResolveMacros(resolved_macro,
276 resolvers, cr, missingMacro, EscapeCallback(), nullptr,
277 false, recursionLevel + 1);
281 if (!useResolvedMacros && found && resolvedMacros)
282 resolvedMacros->Set(name, resolved_macro);
285 resolved_macro = escapeFn(resolved_macro);
287 /* we're done if this is the only macro and there are no other non-macro parts in the string */
288 if (pos_first == 0 && pos_second == str.GetLength() - 1)
289 return resolved_macro;
290 else if (resolved_macro.IsObjectType<Array>())
291 BOOST_THROW_EXCEPTION(std::invalid_argument("Mixing both strings and non-strings in macros is not allowed."));
293 if (resolved_macro.IsObjectType<Array>()) {
294 /* don't allow mixing strings and arrays in macro strings */
295 if (pos_first != 0 || pos_second != str.GetLength() - 1)
296 BOOST_THROW_EXCEPTION(std::invalid_argument("Mixing both strings and non-strings in macros is not allowed."));
298 return resolved_macro;
301 String resolved_macro_str = resolved_macro;
303 result.Replace(pos_first, pos_second - pos_first + 1, resolved_macro_str);
304 offset = pos_first + resolved_macro_str.GetLength();
311 bool MacroProcessor::ValidateMacroString(const String& macro)
316 size_t pos_first, pos_second, offset;
319 while ((pos_first = macro.FindFirstOf("$", offset)) != String::NPos) {
320 pos_second = macro.FindFirstOf("$", pos_first + 1);
322 if (pos_second == String::NPos)
325 offset = pos_second + 1;
331 void MacroProcessor::ValidateCustomVars(const ConfigObject::Ptr& object, const Dictionary::Ptr& value)
336 /* string, array, dictionary */
337 ObjectLock olock(value);
338 for (const Dictionary::Pair& kv : value) {
339 const Value& varval = kv.second;
341 if (varval.IsObjectType<Dictionary>()) {
342 /* only one dictonary level */
343 Dictionary::Ptr varval_dict = varval;
345 ObjectLock xlock(varval_dict);
346 for (const Dictionary::Pair& kv_var : varval_dict) {
347 if (!kv_var.second.IsString())
350 if (!ValidateMacroString(kv_var.second))
351 BOOST_THROW_EXCEPTION(ValidationError(object.get(), { "vars", kv.first, kv_var.first }, "Closing $ not found in macro format string '" + kv_var.second + "'."));
353 } else if (varval.IsObjectType<Array>()) {
354 /* check all array entries */
355 Array::Ptr varval_arr = varval;
357 ObjectLock ylock (varval_arr);
358 for (const Value& arrval : varval_arr) {
359 if (!arrval.IsString())
362 if (!ValidateMacroString(arrval)) {
363 BOOST_THROW_EXCEPTION(ValidationError(object.get(), { "vars", kv.first }, "Closing $ not found in macro format string '" + arrval + "'."));
367 if (!varval.IsString())
370 if (!ValidateMacroString(varval))
371 BOOST_THROW_EXCEPTION(ValidationError(object.get(), { "vars", kv.first }, "Closing $ not found in macro format string '" + varval + "'."));
376 void MacroProcessor::AddArgumentHelper(const Array::Ptr& args, const String& key, const String& value,
377 bool add_key, bool add_value)
386 Value MacroProcessor::EscapeMacroShellArg(const Value& value)
390 if (value.IsObjectType<Array>()) {
391 Array::Ptr arr = value;
393 ObjectLock olock(arr);
394 for (const Value& arg : arr) {
395 if (result.GetLength() > 0)
398 result += Utility::EscapeShellArg(arg);
401 result = Utility::EscapeShellArg(value);
406 struct CommandArgument
410 bool RepeatKey{true};
411 bool SkipValue{false};
415 bool operator<(const CommandArgument& rhs) const
417 return Order < rhs.Order;
421 Value MacroProcessor::ResolveArguments(const Value& command, const Dictionary::Ptr& arguments,
422 const MacroProcessor::ResolverList& resolvers, const CheckResult::Ptr& cr,
423 const Dictionary::Ptr& resolvedMacros, bool useResolvedMacros, int recursionLevel)
425 if (useResolvedMacros)
426 REQUIRE_NOT_NULL(resolvedMacros);
428 Value resolvedCommand;
429 if (!arguments || command.IsObjectType<Array>() || command.IsObjectType<Function>())
430 resolvedCommand = MacroProcessor::ResolveMacros(command, resolvers, cr, nullptr,
431 EscapeMacroShellArg, resolvedMacros, useResolvedMacros, recursionLevel + 1);
433 resolvedCommand = new Array({ command });
437 std::vector<CommandArgument> args;
439 ObjectLock olock(arguments);
440 for (const Dictionary::Pair& kv : arguments) {
441 const Value& arginfo = kv.second;
446 bool required = false;
449 if (arginfo.IsObjectType<Dictionary>()) {
450 Dictionary::Ptr argdict = arginfo;
451 if (argdict->Contains("key"))
452 arg.Key = argdict->Get("key");
453 argval = argdict->Get("value");
454 if (argdict->Contains("required"))
455 required = argdict->Get("required");
456 arg.SkipKey = argdict->Get("skip_key");
457 if (argdict->Contains("repeat_key"))
458 arg.RepeatKey = argdict->Get("repeat_key");
459 arg.Order = argdict->Get("order");
461 Value set_if = argdict->Get("set_if");
463 if (!set_if.IsEmpty()) {
465 Value set_if_resolved = MacroProcessor::ResolveMacros(set_if, resolvers,
466 cr, &missingMacro, MacroProcessor::EscapeCallback(), resolvedMacros,
467 useResolvedMacros, recursionLevel + 1);
469 if (!missingMacro.IsEmpty())
474 if (set_if_resolved == "true")
476 else if (set_if_resolved == "false")
480 value = Convert::ToLong(set_if_resolved);
481 } catch (const std::exception& ex) {
482 /* tried to convert a string */
483 Log(LogWarning, "PluginUtility")
484 << "Error evaluating set_if value '" << set_if_resolved
485 << "' used in argument '" << arg.Key << "': " << ex.what();
497 if (argval.IsEmpty())
498 arg.SkipValue = true;
501 arg.AValue = MacroProcessor::ResolveMacros(argval, resolvers,
502 cr, &missingMacro, MacroProcessor::EscapeCallback(), resolvedMacros,
503 useResolvedMacros, recursionLevel + 1);
505 if (!missingMacro.IsEmpty()) {
507 BOOST_THROW_EXCEPTION(ScriptError("Non-optional macro '" + missingMacro + "' used in argument '" +
508 arg.Key + "' is missing."));
514 args.emplace_back(std::move(arg));
517 std::sort(args.begin(), args.end());
519 Array::Ptr command_arr = resolvedCommand;
520 for (const CommandArgument& arg : args) {
522 if (arg.AValue.IsObjectType<Dictionary>()) {
523 Log(LogWarning, "PluginUtility")
524 << "Tried to use dictionary in argument '" << arg.Key << "'.";
526 } else if (arg.AValue.IsObjectType<Array>()) {
528 Array::Ptr arr = static_cast<Array::Ptr>(arg.AValue);
530 ObjectLock olock(arr);
531 for (const Value& value : arr) {
536 add_key = !arg.SkipKey;
538 add_key = !arg.SkipKey && arg.RepeatKey;
540 AddArgumentHelper(command_arr, arg.Key, value, add_key, !arg.SkipValue);
543 AddArgumentHelper(command_arr, arg.Key, arg.AValue, !arg.SkipKey, !arg.SkipValue);
547 return resolvedCommand;