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