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