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