1 /* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
2 
3 #include "icingadb/icingadb.hpp"
4 #include "base/configtype.hpp"
5 #include "base/object-packer.hpp"
6 #include "base/logger.hpp"
7 #include "base/serializer.hpp"
8 #include "base/tlsutility.hpp"
9 #include "base/initialize.hpp"
10 #include "base/objectlock.hpp"
11 #include "base/array.hpp"
12 #include "base/scriptglobal.hpp"
13 #include "base/convert.hpp"
14 #include "base/json.hpp"
15 #include "icinga/customvarobject.hpp"
16 #include "icinga/checkcommand.hpp"
17 #include "icinga/notificationcommand.hpp"
18 #include "icinga/eventcommand.hpp"
19 #include "icinga/host.hpp"
20 #include <boost/algorithm/string.hpp>
21 #include <map>
22 #include <utility>
23 #include <vector>
24 
25 using namespace icinga;
26 
FormatCheckSumBinary(const String & str)27 String IcingaDB::FormatCheckSumBinary(const String& str)
28 {
29 	char output[20*2+1];
30 	for (int i = 0; i < 20; i++)
31 		sprintf(output + 2 * i, "%02x", str[i]);
32 
33 	return output;
34 }
35 
FormatCommandLine(const Value & commandLine)36 String IcingaDB::FormatCommandLine(const Value& commandLine)
37 {
38 	String result;
39 	if (commandLine.IsObjectType<Array>()) {
40 		Array::Ptr args = commandLine;
41 		bool first = true;
42 
43 		ObjectLock olock(args);
44 		for (const Value& arg : args) {
45 			String token = "'" + Convert::ToString(arg) + "'";
46 
47 			if (first)
48 				first = false;
49 			else
50 				result += String(1, ' ');
51 
52 			result += token;
53 		}
54 	} else if (!commandLine.IsEmpty()) {
55 		result = commandLine;
56 		boost::algorithm::replace_all(result, "\'", "\\'");
57 		result = "'" + result + "'";
58 	}
59 
60 	return result;
61 }
62 
GetObjectIdentifier(const ConfigObject::Ptr & object)63 String IcingaDB::GetObjectIdentifier(const ConfigObject::Ptr& object)
64 {
65 	return HashValue(new Array({m_EnvironmentId, object->GetName()}));
66 }
67 
68 /**
69  * Calculates a deterministic history event ID like SHA1(env, eventType, x...[, nt][, eventTime])
70  *
71  * Where SHA1(env, x...) = GetObjectIdentifier(object)
72  */
CalcEventID(const char * eventType,const ConfigObject::Ptr & object,double eventTime,NotificationType nt)73 String IcingaDB::CalcEventID(const char* eventType, const ConfigObject::Ptr& object, double eventTime, NotificationType nt)
74 {
75 	Array::Ptr rawId = new Array({object->GetName()});
76 	rawId->Insert(0, m_EnvironmentId);
77 	rawId->Insert(1, eventType);
78 
79 	if (nt) {
80 		rawId->Add(GetNotificationTypeByEnum(nt));
81 	}
82 
83 	if (eventTime) {
84 		rawId->Add(TimestampToMilliseconds(eventTime));
85 	}
86 
87 	return HashValue(std::move(rawId));
88 }
89 
90 static const std::set<String> metadataWhitelist ({"package", "source_location", "templates"});
91 
92 /**
93  * Prepare custom vars for being written to Redis
94  *
95  * object.vars = {
96  *   "disks": {
97  *     "disk": {},
98  *     "disk /": {
99  *       "disk_partitions": "/"
100  *     }
101  *   }
102  * }
103  *
104  * return {
105  *   SHA1(PackObject([
106  *     EnvironmentId,
107  *     "disks",
108  *     {
109  *       "disk": {},
110  *       "disk /": {
111  *         "disk_partitions": "/"
112  *       }
113  *     }
114  *   ])): {
115  *     "environment_id": EnvironmentId,
116  *     "name_checksum": SHA1("disks"),
117  *     "name": "disks",
118  *     "value": {
119  *       "disk": {},
120  *       "disk /": {
121  *         "disk_partitions": "/"
122  *       }
123  *     }
124  *   }
125  * }
126  *
127  * @param	Dictionary	Config object with custom vars
128  *
129  * @return 				JSON-like data structure for Redis
130  */
SerializeVars(const Dictionary::Ptr & vars)131 Dictionary::Ptr IcingaDB::SerializeVars(const Dictionary::Ptr& vars)
132 {
133 	if (!vars)
134 		return nullptr;
135 
136 	Dictionary::Ptr res = new Dictionary();
137 
138 	ObjectLock olock(vars);
139 
140 	for (auto& kv : vars) {
141 		res->Set(
142 			SHA1(PackObject((Array::Ptr)new Array({m_EnvironmentId, kv.first, kv.second}))),
143 			(Dictionary::Ptr)new Dictionary({
144 				{"environment_id", m_EnvironmentId},
145 				{"name_checksum", SHA1(kv.first)},
146 				{"name", kv.first},
147 				{"value", JsonEncode(kv.second)},
148 			})
149 		);
150 	}
151 
152 	return res;
153 }
154 
GetNotificationTypeByEnum(NotificationType type)155 const char* IcingaDB::GetNotificationTypeByEnum(NotificationType type)
156 {
157 	switch (type) {
158 		case NotificationDowntimeStart:
159 			return "downtime_start";
160 		case NotificationDowntimeEnd:
161 			return "downtime_end";
162 		case NotificationDowntimeRemoved:
163 			return "downtime_removed";
164 		case NotificationCustom:
165 			return "custom";
166 		case NotificationAcknowledgement:
167 			return "acknowledgement";
168 		case NotificationProblem:
169 			return "problem";
170 		case NotificationRecovery:
171 			return "recovery";
172 		case NotificationFlappingStart:
173 			return "flapping_start";
174 		case NotificationFlappingEnd:
175 			return "flapping_end";
176 	}
177 
178 	VERIFY(!"Invalid notification type.");
179 }
180 
181 static const std::set<String> propertiesBlacklistEmpty;
182 
HashValue(const Value & value)183 String IcingaDB::HashValue(const Value& value)
184 {
185 	return HashValue(value, propertiesBlacklistEmpty);
186 }
187 
HashValue(const Value & value,const std::set<String> & propertiesBlacklist,bool propertiesWhitelist)188 String IcingaDB::HashValue(const Value& value, const std::set<String>& propertiesBlacklist, bool propertiesWhitelist)
189 {
190 	Value temp;
191 	bool mutabl;
192 
193 	Type::Ptr type = value.GetReflectionType();
194 
195 	if (ConfigObject::TypeInstance->IsAssignableFrom(type)) {
196 		temp = Serialize(value, FAConfig);
197 		mutabl = true;
198 	} else {
199 		temp = value;
200 		mutabl = false;
201 	}
202 
203 	if (propertiesBlacklist.size() && temp.IsObject()) {
204 		Dictionary::Ptr dict = dynamic_pointer_cast<Dictionary>((Object::Ptr)temp);
205 
206 		if (dict) {
207 			if (!mutabl)
208 				dict = dict->ShallowClone();
209 
210 			ObjectLock olock(dict);
211 
212 			if (propertiesWhitelist) {
213 				auto current = dict->Begin();
214 				auto propertiesBlacklistEnd = propertiesBlacklist.end();
215 
216 				while (current != dict->End()) {
217 					if (propertiesBlacklist.find(current->first) == propertiesBlacklistEnd) {
218 						dict->Remove(current++);
219 					} else {
220 						++current;
221 					}
222 				}
223 			} else {
224 				for (auto& property : propertiesBlacklist)
225 					dict->Remove(property);
226 			}
227 
228 			if (!mutabl)
229 				temp = dict;
230 		}
231 	}
232 
233 	return SHA1(PackObject(temp));
234 }
235 
GetLowerCaseTypeNameDB(const ConfigObject::Ptr & obj)236 String IcingaDB::GetLowerCaseTypeNameDB(const ConfigObject::Ptr& obj)
237 {
238 	return obj->GetReflectionType()->GetName().ToLower();
239 }
240 
TimestampToMilliseconds(double timestamp)241 long long IcingaDB::TimestampToMilliseconds(double timestamp) {
242 	return static_cast<long long>(timestamp * 1000);
243 }
244 
IcingaToStreamValue(const Value & value)245 String IcingaDB::IcingaToStreamValue(const Value& value)
246 {
247 	switch (value.GetType()) {
248 		case ValueBoolean:
249 			return Convert::ToString(int(value));
250 		case ValueString:
251 			return Utility::ValidateUTF8(value);
252 		case ValueNumber:
253 		case ValueEmpty:
254 			return Convert::ToString(value);
255 		default:
256 			return JsonEncode(value);
257 	}
258 }
259 
260 // Returns the items that exist in "arrayOld" but not in "arrayNew"
GetArrayDeletedValues(const Array::Ptr & arrayOld,const Array::Ptr & arrayNew)261 std::vector<Value> IcingaDB::GetArrayDeletedValues(const Array::Ptr& arrayOld, const Array::Ptr& arrayNew) {
262 	std::vector<Value> deletedValues;
263 
264 	if (!arrayOld) {
265 		return deletedValues;
266 	}
267 
268 	if (!arrayNew) {
269 		return std::vector<Value>(arrayOld->Begin(), arrayOld->End());
270 	}
271 
272 	std::vector<Value> vectorOld(arrayOld->Begin(), arrayOld->End());
273 	std::sort(vectorOld.begin(), vectorOld.end());
274 	vectorOld.erase(std::unique(vectorOld.begin(), vectorOld.end()), vectorOld.end());
275 
276 	std::vector<Value> vectorNew(arrayNew->Begin(), arrayNew->End());
277 	std::sort(vectorNew.begin(), vectorNew.end());
278 	vectorNew.erase(std::unique(vectorNew.begin(), vectorNew.end()), vectorNew.end());
279 
280 	std::set_difference(vectorOld.begin(), vectorOld.end(), vectorNew.begin(), vectorNew.end(), std::back_inserter(deletedValues));
281 
282 	return deletedValues;
283 }
284 
285 // Returns the keys that exist in "dictOld" but not in "dictNew"
GetDictionaryDeletedKeys(const Dictionary::Ptr & dictOld,const Dictionary::Ptr & dictNew)286 std::vector<String> IcingaDB::GetDictionaryDeletedKeys(const Dictionary::Ptr& dictOld, const Dictionary::Ptr& dictNew) {
287 	std::vector<String> deletedKeys;
288 
289 	if (!dictOld) {
290 		return deletedKeys;
291 	}
292 
293 	std::vector<String> oldKeys = dictOld->GetKeys();
294 
295 	if (!dictNew) {
296 		return oldKeys;
297 	}
298 
299 	std::vector<String> newKeys = dictNew->GetKeys();
300 
301 	std::set_difference(oldKeys.begin(), oldKeys.end(), newKeys.begin(), newKeys.end(), std::back_inserter(deletedKeys));
302 
303 	return deletedKeys;
304 }
305