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