1 /* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
2
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>
15
16 using namespace icinga;
17
18 thread_local Dictionary::Ptr MacroResolver::OverrideMacros;
19
ResolveMacros(const Value & str,const ResolverList & resolvers,const CheckResult::Ptr & cr,String * missingMacro,const MacroProcessor::EscapeCallback & escapeFn,const Dictionary::Ptr & resolvedMacros,bool useResolvedMacros,int recursionLevel)20 Value MacroProcessor::ResolveMacros(const Value& str, const ResolverList& resolvers,
21 const CheckResult::Ptr& cr, String *missingMacro,
22 const MacroProcessor::EscapeCallback& escapeFn, const Dictionary::Ptr& resolvedMacros,
23 bool useResolvedMacros, int recursionLevel)
24 {
25 if (useResolvedMacros)
26 REQUIRE_NOT_NULL(resolvedMacros);
27
28 Value result;
29
30 if (str.IsEmpty())
31 return Empty;
32
33 if (str.IsScalar()) {
34 result = InternalResolveMacros(str, resolvers, cr, missingMacro, escapeFn,
35 resolvedMacros, useResolvedMacros, recursionLevel + 1);
36 } else if (str.IsObjectType<Array>()) {
37 ArrayData resultArr;
38 Array::Ptr arr = str;
39
40 ObjectLock olock(arr);
41
42 for (const Value& arg : arr) {
43 /* Note: don't escape macros here. */
44 Value value = InternalResolveMacros(arg, resolvers, cr, missingMacro,
45 EscapeCallback(), resolvedMacros, useResolvedMacros, recursionLevel + 1);
46
47 if (value.IsObjectType<Array>())
48 resultArr.push_back(Utility::Join(value, ';'));
49 else
50 resultArr.push_back(value);
51 }
52
53 result = new Array(std::move(resultArr));
54 } else if (str.IsObjectType<Dictionary>()) {
55 Dictionary::Ptr resultDict = new Dictionary();
56 Dictionary::Ptr dict = str;
57
58 ObjectLock olock(dict);
59
60 for (const Dictionary::Pair& kv : dict) {
61 /* Note: don't escape macros here. */
62 resultDict->Set(kv.first, InternalResolveMacros(kv.second, resolvers, cr, missingMacro,
63 EscapeCallback(), resolvedMacros, useResolvedMacros, recursionLevel + 1));
64 }
65
66 result = resultDict;
67 } else if (str.IsObjectType<Function>()) {
68 result = EvaluateFunction(str, resolvers, cr, escapeFn, resolvedMacros, useResolvedMacros, 0);
69 } else {
70 BOOST_THROW_EXCEPTION(std::invalid_argument("Macro is not a string or array."));
71 }
72
73 return result;
74 }
75
ResolveMacro(const String & macro,const ResolverList & resolvers,const CheckResult::Ptr & cr,Value * result,bool * recursive_macro)76 bool MacroProcessor::ResolveMacro(const String& macro, const ResolverList& resolvers,
77 const CheckResult::Ptr& cr, Value *result, bool *recursive_macro)
78 {
79 CONTEXT("Resolving macro '" + macro + "'");
80
81 *recursive_macro = false;
82
83 std::vector<String> tokens = macro.Split(".");
84
85 String objName;
86 if (tokens.size() > 1) {
87 objName = tokens[0];
88 tokens.erase(tokens.begin());
89 }
90
91 for (const ResolverSpec& resolver : resolvers) {
92 if (!objName.IsEmpty() && objName != resolver.first)
93 continue;
94
95 if (objName.IsEmpty()) {
96 CustomVarObject::Ptr dobj = dynamic_pointer_cast<CustomVarObject>(resolver.second);
97
98 if (dobj) {
99 Dictionary::Ptr vars = dobj->GetVars();
100
101 if (vars && vars->Contains(macro)) {
102 *result = vars->Get(macro);
103 *recursive_macro = true;
104 return true;
105 }
106 }
107 }
108
109 auto *mresolver = dynamic_cast<MacroResolver *>(resolver.second.get());
110
111 if (mresolver && mresolver->ResolveMacro(boost::algorithm::join(tokens, "."), cr, result))
112 return true;
113
114 Value ref = resolver.second;
115 bool valid = true;
116
117 for (const String& token : tokens) {
118 if (ref.IsObjectType<Dictionary>()) {
119 Dictionary::Ptr dict = ref;
120 if (dict->Contains(token)) {
121 ref = dict->Get(token);
122 continue;
123 } else {
124 valid = false;
125 break;
126 }
127 } else if (ref.IsObject()) {
128 Object::Ptr object = ref;
129
130 Type::Ptr type = object->GetReflectionType();
131
132 if (!type) {
133 valid = false;
134 break;
135 }
136
137 int field = type->GetFieldId(token);
138
139 if (field == -1) {
140 valid = false;
141 break;
142 }
143
144 ref = object->GetField(field);
145
146 Field fieldInfo = type->GetFieldInfo(field);
147
148 if (strcmp(fieldInfo.TypeName, "Timestamp") == 0)
149 ref = static_cast<long>(ref);
150 }
151 }
152
153 if (valid) {
154 if (tokens[0] == "vars" ||
155 tokens[0] == "action_url" ||
156 tokens[0] == "notes_url" ||
157 tokens[0] == "notes")
158 *recursive_macro = true;
159
160 *result = ref;
161 return true;
162 }
163 }
164
165 return false;
166 }
167
EvaluateFunction(const Function::Ptr & func,const ResolverList & resolvers,const CheckResult::Ptr & cr,const MacroProcessor::EscapeCallback & escapeFn,const Dictionary::Ptr & resolvedMacros,bool useResolvedMacros,int recursionLevel)168 Value MacroProcessor::EvaluateFunction(const Function::Ptr& func, const ResolverList& resolvers,
169 const CheckResult::Ptr& cr, const MacroProcessor::EscapeCallback& escapeFn,
170 const Dictionary::Ptr& resolvedMacros, bool useResolvedMacros, int recursionLevel)
171 {
172 Dictionary::Ptr resolvers_this = new Dictionary();
173
174 for (const ResolverSpec& resolver : resolvers) {
175 resolvers_this->Set(resolver.first, resolver.second);
176 }
177
178 auto internalResolveMacrosShim = [resolvers, cr, resolvedMacros, useResolvedMacros, recursionLevel](const std::vector<Value>& args) {
179 if (args.size() < 1)
180 BOOST_THROW_EXCEPTION(std::invalid_argument("Too few arguments for function"));
181
182 String missingMacro;
183
184 return MacroProcessor::InternalResolveMacros(args[0], resolvers, cr, &missingMacro, MacroProcessor::EscapeCallback(),
185 resolvedMacros, useResolvedMacros, recursionLevel);
186 };
187
188 resolvers_this->Set("macro", new Function("macro (temporary)", internalResolveMacrosShim, { "str" }));
189
190 auto internalResolveArgumentsShim = [resolvers, cr, resolvedMacros, useResolvedMacros, recursionLevel](const std::vector<Value>& args) {
191 if (args.size() < 2)
192 BOOST_THROW_EXCEPTION(std::invalid_argument("Too few arguments for function"));
193
194 return MacroProcessor::ResolveArguments(args[0], args[1], resolvers, cr,
195 resolvedMacros, useResolvedMacros, recursionLevel + 1);
196 };
197
198 resolvers_this->Set("resolve_arguments", new Function("resolve_arguments (temporary)", internalResolveArgumentsShim, { "command", "args" }));
199
200 return func->InvokeThis(resolvers_this);
201 }
202
InternalResolveMacros(const String & str,const ResolverList & resolvers,const CheckResult::Ptr & cr,String * missingMacro,const MacroProcessor::EscapeCallback & escapeFn,const Dictionary::Ptr & resolvedMacros,bool useResolvedMacros,int recursionLevel)203 Value MacroProcessor::InternalResolveMacros(const String& str, const ResolverList& resolvers,
204 const CheckResult::Ptr& cr, String *missingMacro,
205 const MacroProcessor::EscapeCallback& escapeFn, const Dictionary::Ptr& resolvedMacros,
206 bool useResolvedMacros, int recursionLevel)
207 {
208 CONTEXT("Resolving macros for string '" + str + "'");
209
210 if (recursionLevel > 15)
211 BOOST_THROW_EXCEPTION(std::runtime_error("Infinite recursion detected while resolving macros"));
212
213 size_t offset, pos_first, pos_second;
214 offset = 0;
215
216 Dictionary::Ptr resolvers_this;
217
218 String result = str;
219 while ((pos_first = result.FindFirstOf("$", offset)) != String::NPos) {
220 pos_second = result.FindFirstOf("$", pos_first + 1);
221
222 if (pos_second == String::NPos)
223 BOOST_THROW_EXCEPTION(std::runtime_error("Closing $ not found in macro format string."));
224
225 String name = result.SubStr(pos_first + 1, pos_second - pos_first - 1);
226
227 Value resolved_macro;
228 bool recursive_macro;
229 bool found;
230
231 if (useResolvedMacros) {
232 recursive_macro = false;
233 found = resolvedMacros->Contains(name);
234
235 if (found)
236 resolved_macro = resolvedMacros->Get(name);
237 } else
238 found = ResolveMacro(name, resolvers, cr, &resolved_macro, &recursive_macro);
239
240 /* $$ is an escape sequence for $. */
241 if (name.IsEmpty()) {
242 resolved_macro = "$";
243 found = true;
244 }
245
246 if (resolved_macro.IsObjectType<Function>()) {
247 resolved_macro = EvaluateFunction(resolved_macro, resolvers, cr, escapeFn,
248 resolvedMacros, useResolvedMacros, recursionLevel + 1);
249 }
250
251 if (!found) {
252 if (!missingMacro)
253 Log(LogWarning, "MacroProcessor")
254 << "Macro '" << name << "' is not defined.";
255 else
256 *missingMacro = name;
257 }
258
259 /* recursively resolve macros in the macro if it was a user macro */
260 if (recursive_macro) {
261 if (resolved_macro.IsObjectType<Array>()) {
262 Array::Ptr arr = resolved_macro;
263 ArrayData resolved_arr;
264
265 ObjectLock olock(arr);
266 for (const Value& value : arr) {
267 if (value.IsScalar()) {
268 resolved_arr.push_back(InternalResolveMacros(value,
269 resolvers, cr, missingMacro, EscapeCallback(), nullptr,
270 false, recursionLevel + 1));
271 } else
272 resolved_arr.push_back(value);
273 }
274
275 resolved_macro = new Array(std::move(resolved_arr));
276 } else if (resolved_macro.IsString()) {
277 resolved_macro = InternalResolveMacros(resolved_macro,
278 resolvers, cr, missingMacro, EscapeCallback(), nullptr,
279 false, recursionLevel + 1);
280 }
281 }
282
283 if (!useResolvedMacros && found && resolvedMacros)
284 resolvedMacros->Set(name, resolved_macro);
285
286 if (escapeFn)
287 resolved_macro = escapeFn(resolved_macro);
288
289 /* we're done if this is the only macro and there are no other non-macro parts in the string */
290 if (pos_first == 0 && pos_second == str.GetLength() - 1)
291 return resolved_macro;
292 else if (resolved_macro.IsObjectType<Array>())
293 BOOST_THROW_EXCEPTION(std::invalid_argument("Mixing both strings and non-strings in macros is not allowed."));
294
295 if (resolved_macro.IsObjectType<Array>()) {
296 /* don't allow mixing strings and arrays in macro strings */
297 if (pos_first != 0 || pos_second != str.GetLength() - 1)
298 BOOST_THROW_EXCEPTION(std::invalid_argument("Mixing both strings and non-strings in macros is not allowed."));
299
300 return resolved_macro;
301 }
302
303 String resolved_macro_str = resolved_macro;
304
305 result.Replace(pos_first, pos_second - pos_first + 1, resolved_macro_str);
306 offset = pos_first + resolved_macro_str.GetLength();
307 }
308
309 return result;
310 }
311
312
ValidateMacroString(const String & macro)313 bool MacroProcessor::ValidateMacroString(const String& macro)
314 {
315 if (macro.IsEmpty())
316 return true;
317
318 size_t pos_first, pos_second, offset;
319 offset = 0;
320
321 while ((pos_first = macro.FindFirstOf("$", offset)) != String::NPos) {
322 pos_second = macro.FindFirstOf("$", pos_first + 1);
323
324 if (pos_second == String::NPos)
325 return false;
326
327 offset = pos_second + 1;
328 }
329
330 return true;
331 }
332
ValidateCustomVars(const ConfigObject::Ptr & object,const Dictionary::Ptr & value)333 void MacroProcessor::ValidateCustomVars(const ConfigObject::Ptr& object, const Dictionary::Ptr& value)
334 {
335 if (!value)
336 return;
337
338 /* string, array, dictionary */
339 ObjectLock olock(value);
340 for (const Dictionary::Pair& kv : value) {
341 const Value& varval = kv.second;
342
343 if (varval.IsObjectType<Dictionary>()) {
344 /* only one dictonary level */
345 Dictionary::Ptr varval_dict = varval;
346
347 ObjectLock xlock(varval_dict);
348 for (const Dictionary::Pair& kv_var : varval_dict) {
349 if (!kv_var.second.IsString())
350 continue;
351
352 if (!ValidateMacroString(kv_var.second))
353 BOOST_THROW_EXCEPTION(ValidationError(object.get(), { "vars", kv.first, kv_var.first }, "Closing $ not found in macro format string '" + kv_var.second + "'."));
354 }
355 } else if (varval.IsObjectType<Array>()) {
356 /* check all array entries */
357 Array::Ptr varval_arr = varval;
358
359 ObjectLock ylock (varval_arr);
360 for (const Value& arrval : varval_arr) {
361 if (!arrval.IsString())
362 continue;
363
364 if (!ValidateMacroString(arrval)) {
365 BOOST_THROW_EXCEPTION(ValidationError(object.get(), { "vars", kv.first }, "Closing $ not found in macro format string '" + arrval + "'."));
366 }
367 }
368 } else {
369 if (!varval.IsString())
370 continue;
371
372 if (!ValidateMacroString(varval))
373 BOOST_THROW_EXCEPTION(ValidationError(object.get(), { "vars", kv.first }, "Closing $ not found in macro format string '" + varval + "'."));
374 }
375 }
376 }
377
AddArgumentHelper(const Array::Ptr & args,const String & key,const String & value,bool add_key,bool add_value)378 void MacroProcessor::AddArgumentHelper(const Array::Ptr& args, const String& key, const String& value,
379 bool add_key, bool add_value)
380 {
381 if (add_key)
382 args->Add(key);
383
384 if (add_value)
385 args->Add(value);
386 }
387
EscapeMacroShellArg(const Value & value)388 Value MacroProcessor::EscapeMacroShellArg(const Value& value)
389 {
390 String result;
391
392 if (value.IsObjectType<Array>()) {
393 Array::Ptr arr = value;
394
395 ObjectLock olock(arr);
396 for (const Value& arg : arr) {
397 if (result.GetLength() > 0)
398 result += " ";
399
400 result += Utility::EscapeShellArg(arg);
401 }
402 } else
403 result = Utility::EscapeShellArg(value);
404
405 return result;
406 }
407
408 struct CommandArgument
409 {
410 int Order{0};
411 bool SkipKey{false};
412 bool RepeatKey{true};
413 bool SkipValue{false};
414 String Key;
415 Value AValue;
416
operator <CommandArgument417 bool operator<(const CommandArgument& rhs) const
418 {
419 return Order < rhs.Order;
420 }
421 };
422
ResolveArguments(const Value & command,const Dictionary::Ptr & arguments,const MacroProcessor::ResolverList & resolvers,const CheckResult::Ptr & cr,const Dictionary::Ptr & resolvedMacros,bool useResolvedMacros,int recursionLevel)423 Value MacroProcessor::ResolveArguments(const Value& command, const Dictionary::Ptr& arguments,
424 const MacroProcessor::ResolverList& resolvers, const CheckResult::Ptr& cr,
425 const Dictionary::Ptr& resolvedMacros, bool useResolvedMacros, int recursionLevel)
426 {
427 if (useResolvedMacros)
428 REQUIRE_NOT_NULL(resolvedMacros);
429
430 Value resolvedCommand;
431 if (!arguments || command.IsObjectType<Array>() || command.IsObjectType<Function>())
432 resolvedCommand = MacroProcessor::ResolveMacros(command, resolvers, cr, nullptr,
433 EscapeMacroShellArg, resolvedMacros, useResolvedMacros, recursionLevel + 1);
434 else {
435 resolvedCommand = new Array({ command });
436 }
437
438 if (arguments) {
439 std::vector<CommandArgument> args;
440
441 ObjectLock olock(arguments);
442 for (const Dictionary::Pair& kv : arguments) {
443 const Value& arginfo = kv.second;
444
445 CommandArgument arg;
446 arg.Key = kv.first;
447
448 bool required = false;
449 Value argval;
450
451 if (arginfo.IsObjectType<Dictionary>()) {
452 Dictionary::Ptr argdict = arginfo;
453 if (argdict->Contains("key"))
454 arg.Key = argdict->Get("key");
455 argval = argdict->Get("value");
456 if (argdict->Contains("required"))
457 required = argdict->Get("required");
458 arg.SkipKey = argdict->Get("skip_key");
459 if (argdict->Contains("repeat_key"))
460 arg.RepeatKey = argdict->Get("repeat_key");
461 arg.Order = argdict->Get("order");
462
463 Value set_if = argdict->Get("set_if");
464
465 if (!set_if.IsEmpty()) {
466 String missingMacro;
467 Value set_if_resolved = MacroProcessor::ResolveMacros(set_if, resolvers,
468 cr, &missingMacro, MacroProcessor::EscapeCallback(), resolvedMacros,
469 useResolvedMacros, recursionLevel + 1);
470
471 if (!missingMacro.IsEmpty())
472 continue;
473
474 int value;
475
476 if (set_if_resolved == "true")
477 value = 1;
478 else if (set_if_resolved == "false")
479 value = 0;
480 else {
481 try {
482 value = Convert::ToLong(set_if_resolved);
483 } catch (const std::exception& ex) {
484 /* tried to convert a string */
485 Log(LogWarning, "PluginUtility")
486 << "Error evaluating set_if value '" << set_if_resolved
487 << "' used in argument '" << arg.Key << "': " << ex.what();
488 continue;
489 }
490 }
491
492 if (!value)
493 continue;
494 }
495 }
496 else
497 argval = arginfo;
498
499 if (argval.IsEmpty())
500 arg.SkipValue = true;
501
502 String missingMacro;
503 arg.AValue = MacroProcessor::ResolveMacros(argval, resolvers,
504 cr, &missingMacro, MacroProcessor::EscapeCallback(), resolvedMacros,
505 useResolvedMacros, recursionLevel + 1);
506
507 if (!missingMacro.IsEmpty()) {
508 if (required) {
509 BOOST_THROW_EXCEPTION(ScriptError("Non-optional macro '" + missingMacro + "' used in argument '" +
510 arg.Key + "' is missing."));
511 }
512
513 continue;
514 }
515
516 args.emplace_back(std::move(arg));
517 }
518
519 std::sort(args.begin(), args.end());
520
521 Array::Ptr command_arr = resolvedCommand;
522 for (const CommandArgument& arg : args) {
523
524 if (arg.AValue.IsObjectType<Dictionary>()) {
525 Log(LogWarning, "PluginUtility")
526 << "Tried to use dictionary in argument '" << arg.Key << "'.";
527 continue;
528 } else if (arg.AValue.IsObjectType<Array>()) {
529 bool first = true;
530 Array::Ptr arr = static_cast<Array::Ptr>(arg.AValue);
531
532 ObjectLock olock(arr);
533 for (const Value& value : arr) {
534 bool add_key;
535
536 if (first) {
537 first = false;
538 add_key = !arg.SkipKey;
539 } else
540 add_key = !arg.SkipKey && arg.RepeatKey;
541
542 AddArgumentHelper(command_arr, arg.Key, value, add_key, !arg.SkipValue);
543 }
544 } else
545 AddArgumentHelper(command_arr, arg.Key, arg.AValue, !arg.SkipKey, !arg.SkipValue);
546 }
547 }
548
549 return resolvedCommand;
550 }
551