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