1 /******************************************************************************
3 * Copyright (C) 2012-2018 Icinga Development Team (https://www.icinga.com/) *
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/algorithm/string/split.hpp>
32 #include <boost/algorithm/string/join.hpp>
33 #include <boost/algorithm/string/classification.hpp>
35 using namespace icinga;
37 Value MacroProcessor::ResolveMacros(const Value& str, const ResolverList& resolvers,
38 const CheckResult::Ptr& cr, String *missingMacro,
39 const MacroProcessor::EscapeCallback& escapeFn, const Dictionary::Ptr& resolvedMacros,
40 bool useResolvedMacros, int recursionLevel)
48 result = InternalResolveMacros(str, resolvers, cr, missingMacro, escapeFn,
49 resolvedMacros, useResolvedMacros, recursionLevel + 1);
50 } else if (str.IsObjectType<Array>()) {
51 Array::Ptr resultArr = new Array();
54 ObjectLock olock(arr);
56 for (const Value& arg : arr) {
57 /* Note: don't escape macros here. */
58 Value value = InternalResolveMacros(arg, resolvers, cr, missingMacro,
59 EscapeCallback(), resolvedMacros, useResolvedMacros, recursionLevel + 1);
61 if (value.IsObjectType<Array>())
62 resultArr->Add(Utility::Join(value, ';'));
64 resultArr->Add(value);
68 } else if (str.IsObjectType<Dictionary>()) {
69 Dictionary::Ptr resultDict = new Dictionary();
70 Dictionary::Ptr dict = str;
72 ObjectLock olock(dict);
74 for (const Dictionary::Pair& kv : dict) {
75 /* Note: don't escape macros here. */
76 resultDict->Set(kv.first, InternalResolveMacros(kv.second, resolvers, cr, missingMacro,
77 EscapeCallback(), resolvedMacros, useResolvedMacros, recursionLevel + 1));
81 } else if (str.IsObjectType<Function>()) {
82 result = EvaluateFunction(str, resolvers, cr, escapeFn, resolvedMacros, useResolvedMacros, 0);
84 BOOST_THROW_EXCEPTION(std::invalid_argument("Macro is not a string or array."));
90 bool MacroProcessor::ResolveMacro(const String& macro, const ResolverList& resolvers,
91 const CheckResult::Ptr& cr, Value *result, bool *recursive_macro)
93 CONTEXT("Resolving macro '" + macro + "'");
95 *recursive_macro = false;
97 std::vector<String> tokens;
98 boost::algorithm::split(tokens, macro, boost::is_any_of("."));
101 if (tokens.size() > 1) {
103 tokens.erase(tokens.begin());
106 for (const ResolverSpec& resolver : resolvers) {
107 if (!objName.IsEmpty() && objName != resolver.first)
110 if (objName.IsEmpty()) {
111 CustomVarObject::Ptr dobj = dynamic_pointer_cast<CustomVarObject>(resolver.second);
114 Dictionary::Ptr vars = dobj->GetVars();
116 if (vars && vars->Contains(macro)) {
117 *result = vars->Get(macro);
118 *recursive_macro = true;
124 auto *mresolver = dynamic_cast<MacroResolver *>(resolver.second.get());
126 if (mresolver && mresolver->ResolveMacro(boost::algorithm::join(tokens, "."), cr, result))
129 Value ref = resolver.second;
132 for (const String& token : tokens) {
133 if (ref.IsObjectType<Dictionary>()) {
134 Dictionary::Ptr dict = ref;
135 if (dict->Contains(token)) {
136 ref = dict->Get(token);
142 } else if (ref.IsObject()) {
143 Object::Ptr object = ref;
145 Type::Ptr type = object->GetReflectionType();
152 int field = type->GetFieldId(token);
159 ref = object->GetField(field);
161 Field fieldInfo = type->GetFieldInfo(field);
163 if (strcmp(fieldInfo.TypeName, "Timestamp") == 0)
164 ref = static_cast<long>(ref);
169 if (tokens[0] == "vars" ||
170 tokens[0] == "action_url" ||
171 tokens[0] == "notes_url" ||
172 tokens[0] == "notes")
173 *recursive_macro = true;
183 Value MacroProcessor::EvaluateFunction(const Function::Ptr& func, const ResolverList& resolvers,
184 const CheckResult::Ptr& cr, const MacroProcessor::EscapeCallback& escapeFn,
185 const Dictionary::Ptr& resolvedMacros, bool useResolvedMacros, int recursionLevel)
187 Dictionary::Ptr resolvers_this = new Dictionary();
189 for (const ResolverSpec& resolver : resolvers) {
190 resolvers_this->Set(resolver.first, resolver.second);
193 auto internalResolveMacrosShim = [resolvers, cr, resolvedMacros, useResolvedMacros, recursionLevel](const std::vector<Value>& args) {
195 BOOST_THROW_EXCEPTION(std::invalid_argument("Too few arguments for function"));
199 return MacroProcessor::InternalResolveMacros(args[0], resolvers, cr, &missingMacro, MacroProcessor::EscapeCallback(),
200 resolvedMacros, useResolvedMacros, recursionLevel);
203 resolvers_this->Set("macro", new Function("macro (temporary)", internalResolveMacrosShim, { "str" }));
205 auto internalResolveArgumentsShim = [resolvers, cr, resolvedMacros, useResolvedMacros, recursionLevel](const std::vector<Value>& args) {
207 BOOST_THROW_EXCEPTION(std::invalid_argument("Too few arguments for function"));
209 return MacroProcessor::ResolveArguments(args[0], args[1], resolvers, cr,
210 resolvedMacros, useResolvedMacros, recursionLevel + 1);
213 resolvers_this->Set("resolve_arguments", new Function("resolve_arguments (temporary)", internalResolveArgumentsShim, { "command", "args" }));
215 return func->InvokeThis(resolvers_this);
218 Value MacroProcessor::InternalResolveMacros(const String& str, const ResolverList& resolvers,
219 const CheckResult::Ptr& cr, String *missingMacro,
220 const MacroProcessor::EscapeCallback& escapeFn, const Dictionary::Ptr& resolvedMacros,
221 bool useResolvedMacros, int recursionLevel)
223 CONTEXT("Resolving macros for string '" + str + "'");
225 if (recursionLevel > 15)
226 BOOST_THROW_EXCEPTION(std::runtime_error("Infinite recursion detected while resolving macros"));
228 size_t offset, pos_first, pos_second;
231 Dictionary::Ptr resolvers_this;
234 while ((pos_first = result.FindFirstOf("$", offset)) != String::NPos) {
235 pos_second = result.FindFirstOf("$", pos_first + 1);
237 if (pos_second == String::NPos)
238 BOOST_THROW_EXCEPTION(std::runtime_error("Closing $ not found in macro format string."));
240 String name = result.SubStr(pos_first + 1, pos_second - pos_first - 1);
242 Value resolved_macro;
243 bool recursive_macro;
246 if (useResolvedMacros) {
247 recursive_macro = false;
248 found = resolvedMacros->Contains(name);
251 resolved_macro = resolvedMacros->Get(name);
253 found = ResolveMacro(name, resolvers, cr, &resolved_macro, &recursive_macro);
255 /* $$ is an escape sequence for $. */
256 if (name.IsEmpty()) {
257 resolved_macro = "$";
261 if (resolved_macro.IsObjectType<Function>()) {
262 resolved_macro = EvaluateFunction(resolved_macro, resolvers, cr, escapeFn,
263 resolvedMacros, useResolvedMacros, recursionLevel + 1);
268 Log(LogWarning, "MacroProcessor")
269 << "Macro '" << name << "' is not defined.";
271 *missingMacro = name;
274 /* recursively resolve macros in the macro if it was a user macro */
275 if (recursive_macro) {
276 if (resolved_macro.IsObjectType<Array>()) {
277 Array::Ptr arr = resolved_macro;
278 Array::Ptr resolved_arr = new Array();
280 ObjectLock olock(arr);
281 for (const Value& value : arr) {
282 if (value.IsScalar()) {
283 resolved_arr->Add(InternalResolveMacros(value,
284 resolvers, cr, missingMacro, EscapeCallback(), nullptr,
285 false, recursionLevel + 1));
287 resolved_arr->Add(value);
290 resolved_macro = resolved_arr;
291 } else if (resolved_macro.IsString()) {
292 resolved_macro = InternalResolveMacros(resolved_macro,
293 resolvers, cr, missingMacro, EscapeCallback(), nullptr,
294 false, recursionLevel + 1);
298 if (!useResolvedMacros && found && resolvedMacros)
299 resolvedMacros->Set(name, resolved_macro);
302 resolved_macro = escapeFn(resolved_macro);
304 /* we're done if this is the only macro and there are no other non-macro parts in the string */
305 if (pos_first == 0 && pos_second == str.GetLength() - 1)
306 return resolved_macro;
307 else if (resolved_macro.IsObjectType<Array>())
308 BOOST_THROW_EXCEPTION(std::invalid_argument("Mixing both strings and non-strings in macros is not allowed."));
310 if (resolved_macro.IsObjectType<Array>()) {
311 /* don't allow mixing strings and arrays in macro strings */
312 if (pos_first != 0 || pos_second != str.GetLength() - 1)
313 BOOST_THROW_EXCEPTION(std::invalid_argument("Mixing both strings and non-strings in macros is not allowed."));
315 return resolved_macro;
318 String resolved_macro_str = resolved_macro;
320 result.Replace(pos_first, pos_second - pos_first + 1, resolved_macro_str);
321 offset = pos_first + resolved_macro_str.GetLength();
328 bool MacroProcessor::ValidateMacroString(const String& macro)
333 size_t pos_first, pos_second, offset;
336 while ((pos_first = macro.FindFirstOf("$", offset)) != String::NPos) {
337 pos_second = macro.FindFirstOf("$", pos_first + 1);
339 if (pos_second == String::NPos)
342 offset = pos_second + 1;
348 void MacroProcessor::ValidateCustomVars(const ConfigObject::Ptr& object, const Dictionary::Ptr& value)
353 /* string, array, dictionary */
354 ObjectLock olock(value);
355 for (const Dictionary::Pair& kv : value) {
356 const Value& varval = kv.second;
358 if (varval.IsObjectType<Dictionary>()) {
359 /* only one dictonary level */
360 Dictionary::Ptr varval_dict = varval;
362 ObjectLock xlock(varval_dict);
363 for (const Dictionary::Pair& kv_var : varval_dict) {
364 if (!kv_var.second.IsString())
367 if (!ValidateMacroString(kv_var.second))
368 BOOST_THROW_EXCEPTION(ValidationError(object.get(), { "vars", kv.first, kv_var.first }, "Closing $ not found in macro format string '" + kv_var.second + "'."));
370 } else if (varval.IsObjectType<Array>()) {
371 /* check all array entries */
372 Array::Ptr varval_arr = varval;
374 ObjectLock ylock (varval_arr);
375 for (const Value& arrval : varval_arr) {
376 if (!arrval.IsString())
379 if (!ValidateMacroString(arrval)) {
380 BOOST_THROW_EXCEPTION(ValidationError(object.get(), { "vars", kv.first }, "Closing $ not found in macro format string '" + arrval + "'."));
384 if (!varval.IsString())
387 if (!ValidateMacroString(varval))
388 BOOST_THROW_EXCEPTION(ValidationError(object.get(), { "vars", kv.first }, "Closing $ not found in macro format string '" + varval + "'."));
393 void MacroProcessor::AddArgumentHelper(const Array::Ptr& args, const String& key, const String& value,
394 bool add_key, bool add_value)
403 Value MacroProcessor::EscapeMacroShellArg(const Value& value)
407 if (value.IsObjectType<Array>()) {
408 Array::Ptr arr = value;
410 ObjectLock olock(arr);
411 for (const Value& arg : arr) {
412 if (result.GetLength() > 0)
415 result += Utility::EscapeShellArg(arg);
418 result = Utility::EscapeShellArg(value);
423 struct CommandArgument
427 bool RepeatKey{true};
428 bool SkipValue{false};
432 bool operator<(const CommandArgument& rhs) const
434 return Order < rhs.Order;
438 Value MacroProcessor::ResolveArguments(const Value& command, const Dictionary::Ptr& arguments,
439 const MacroProcessor::ResolverList& resolvers, const CheckResult::Ptr& cr,
440 const Dictionary::Ptr& resolvedMacros, bool useResolvedMacros, int recursionLevel)
442 Value resolvedCommand;
443 if (!arguments || command.IsObjectType<Array>() || command.IsObjectType<Function>())
444 resolvedCommand = MacroProcessor::ResolveMacros(command, resolvers, cr, nullptr,
445 EscapeMacroShellArg, resolvedMacros, useResolvedMacros, recursionLevel + 1);
447 Array::Ptr arr = new Array();
449 resolvedCommand = arr;
453 std::vector<CommandArgument> args;
455 ObjectLock olock(arguments);
456 for (const Dictionary::Pair& kv : arguments) {
457 const Value& arginfo = kv.second;
462 bool required = false;
465 if (arginfo.IsObjectType<Dictionary>()) {
466 Dictionary::Ptr argdict = arginfo;
467 if (argdict->Contains("key"))
468 arg.Key = argdict->Get("key");
469 argval = argdict->Get("value");
470 if (argdict->Contains("required"))
471 required = argdict->Get("required");
472 arg.SkipKey = argdict->Get("skip_key");
473 if (argdict->Contains("repeat_key"))
474 arg.RepeatKey = argdict->Get("repeat_key");
475 arg.Order = argdict->Get("order");
477 Value set_if = argdict->Get("set_if");
479 if (!set_if.IsEmpty()) {
481 Value set_if_resolved = MacroProcessor::ResolveMacros(set_if, resolvers,
482 cr, &missingMacro, MacroProcessor::EscapeCallback(), resolvedMacros,
483 useResolvedMacros, recursionLevel + 1);
485 if (!missingMacro.IsEmpty())
490 if (set_if_resolved == "true")
492 else if (set_if_resolved == "false")
496 value = Convert::ToLong(set_if_resolved);
497 } catch (const std::exception& ex) {
498 /* tried to convert a string */
499 Log(LogWarning, "PluginUtility")
500 << "Error evaluating set_if value '" << set_if_resolved
501 << "' used in argument '" << arg.Key << "': " << ex.what();
513 if (argval.IsEmpty())
514 arg.SkipValue = true;
517 arg.AValue = MacroProcessor::ResolveMacros(argval, resolvers,
518 cr, &missingMacro, MacroProcessor::EscapeCallback(), resolvedMacros,
519 useResolvedMacros, recursionLevel + 1);
521 if (!missingMacro.IsEmpty()) {
523 BOOST_THROW_EXCEPTION(ScriptError("Non-optional macro '" + missingMacro + "' used in argument '" +
524 arg.Key + "' is missing."));
530 args.emplace_back(std::move(arg));
533 std::sort(args.begin(), args.end());
535 Array::Ptr command_arr = resolvedCommand;
536 for (const CommandArgument& arg : args) {
538 if (arg.AValue.IsObjectType<Dictionary>()) {
539 Log(LogWarning, "PluginUtility")
540 << "Tried to use dictionary in argument '" << arg.Key << "'.";
542 } else if (arg.AValue.IsObjectType<Array>()) {
544 Array::Ptr arr = static_cast<Array::Ptr>(arg.AValue);
546 ObjectLock olock(arr);
547 for (const Value& value : arr) {
552 add_key = !arg.SkipKey;
554 add_key = !arg.SkipKey && arg.RepeatKey;
556 AddArgumentHelper(command_arr, arg.Key, value, add_key, !arg.SkipValue);
559 AddArgumentHelper(command_arr, arg.Key, arg.AValue, !arg.SkipKey, !arg.SkipValue);
563 return resolvedCommand;