]> granicus.if.org Git - icinga2/blob - lib/icinga/apiactions.cpp
Merge pull request #7164 from Icinga/bugfix/notification-times-validate
[icinga2] / lib / icinga / apiactions.cpp
1 /* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
2
3 #include "icinga/apiactions.hpp"
4 #include "icinga/service.hpp"
5 #include "icinga/servicegroup.hpp"
6 #include "icinga/hostgroup.hpp"
7 #include "icinga/pluginutility.hpp"
8 #include "icinga/checkcommand.hpp"
9 #include "icinga/eventcommand.hpp"
10 #include "icinga/notificationcommand.hpp"
11 #include "remote/apiaction.hpp"
12 #include "remote/apilistener.hpp"
13 #include "remote/pkiutility.hpp"
14 #include "remote/httputility.hpp"
15 #include "base/utility.hpp"
16 #include "base/convert.hpp"
17 #include <fstream>
18
19 using namespace icinga;
20
21 REGISTER_APIACTION(process_check_result, "Service;Host", &ApiActions::ProcessCheckResult);
22 REGISTER_APIACTION(reschedule_check, "Service;Host", &ApiActions::RescheduleCheck);
23 REGISTER_APIACTION(send_custom_notification, "Service;Host", &ApiActions::SendCustomNotification);
24 REGISTER_APIACTION(delay_notification, "Service;Host", &ApiActions::DelayNotification);
25 REGISTER_APIACTION(acknowledge_problem, "Service;Host", &ApiActions::AcknowledgeProblem);
26 REGISTER_APIACTION(remove_acknowledgement, "Service;Host", &ApiActions::RemoveAcknowledgement);
27 REGISTER_APIACTION(add_comment, "Service;Host", &ApiActions::AddComment);
28 REGISTER_APIACTION(remove_comment, "Service;Host;Comment", &ApiActions::RemoveComment);
29 REGISTER_APIACTION(schedule_downtime, "Service;Host", &ApiActions::ScheduleDowntime);
30 REGISTER_APIACTION(remove_downtime, "Service;Host;Downtime", &ApiActions::RemoveDowntime);
31 REGISTER_APIACTION(shutdown_process, "", &ApiActions::ShutdownProcess);
32 REGISTER_APIACTION(restart_process, "", &ApiActions::RestartProcess);
33 REGISTER_APIACTION(generate_ticket, "", &ApiActions::GenerateTicket);
34
35 Dictionary::Ptr ApiActions::CreateResult(int code, const String& status,
36         const Dictionary::Ptr& additional)
37 {
38         Dictionary::Ptr result = new Dictionary({
39                 { "code", code },
40                 { "status", status }
41         });
42
43         if (additional)
44                 additional->CopyTo(result);
45
46         return result;
47 }
48
49 Dictionary::Ptr ApiActions::ProcessCheckResult(const ConfigObject::Ptr& object,
50         const Dictionary::Ptr& params)
51 {
52         Checkable::Ptr checkable = static_pointer_cast<Checkable>(object);
53
54         if (!checkable)
55                 return ApiActions::CreateResult(404,
56                         "Cannot process passive check result for non-existent object.");
57
58         if (!checkable->GetEnablePassiveChecks())
59                 return ApiActions::CreateResult(403, "Passive checks are disabled for object '" + checkable->GetName() + "'.");
60
61         Host::Ptr host;
62         Service::Ptr service;
63         tie(host, service) = GetHostService(checkable);
64
65         if (!params->Contains("exit_status"))
66                 return ApiActions::CreateResult(400, "Parameter 'exit_status' is required.");
67
68         int exitStatus = HttpUtility::GetLastParameter(params, "exit_status");
69
70         ServiceState state;
71
72         if (!service) {
73                 if (exitStatus == 0)
74                         state = ServiceOK;
75                 else if (exitStatus == 1)
76                         state = ServiceCritical;
77                 else
78                         return ApiActions::CreateResult(400, "Invalid 'exit_status' for Host "
79                                 + checkable->GetName() + ".");
80         } else {
81                 state = PluginUtility::ExitStatusToState(exitStatus);
82         }
83
84         if (!params->Contains("plugin_output"))
85                 return ApiActions::CreateResult(400, "Parameter 'plugin_output' is required");
86
87         CheckResult::Ptr cr = new CheckResult();
88         cr->SetOutput(HttpUtility::GetLastParameter(params, "plugin_output"));
89         cr->SetState(state);
90
91         if (params->Contains("execution_start"))
92                 cr->SetExecutionStart(HttpUtility::GetLastParameter(params, "execution_start"));
93
94         if (params->Contains("execution_end"))
95                 cr->SetExecutionEnd(HttpUtility::GetLastParameter(params, "execution_end"));
96
97         cr->SetCheckSource(HttpUtility::GetLastParameter(params, "check_source"));
98
99         Value perfData = params->Get("performance_data");
100
101         /* Allow to pass a performance data string from Icinga Web 2 next to the new Array notation. */
102         if (perfData.IsString())
103                 cr->SetPerformanceData(PluginUtility::SplitPerfdata(perfData));
104         else
105                 cr->SetPerformanceData(perfData);
106
107         cr->SetCommand(params->Get("check_command"));
108
109         /* Mark this check result as passive. */
110         cr->SetActive(false);
111
112         /* Result TTL allows to overrule the next expected freshness check. */
113         if (params->Contains("ttl"))
114                 cr->SetTtl(HttpUtility::GetLastParameter(params, "ttl"));
115
116         checkable->ProcessCheckResult(cr);
117
118         return ApiActions::CreateResult(200, "Successfully processed check result for object '" + checkable->GetName() + "'.");
119 }
120
121 Dictionary::Ptr ApiActions::RescheduleCheck(const ConfigObject::Ptr& object,
122         const Dictionary::Ptr& params)
123 {
124         Checkable::Ptr checkable = static_pointer_cast<Checkable>(object);
125
126         if (!checkable)
127                 return ApiActions::CreateResult(404, "Cannot reschedule check for non-existent object.");
128
129         if (Convert::ToBool(HttpUtility::GetLastParameter(params, "force")))
130                 checkable->SetForceNextCheck(true);
131
132         double nextCheck;
133         if (params->Contains("next_check"))
134                 nextCheck = HttpUtility::GetLastParameter(params, "next_check");
135         else
136                 nextCheck = Utility::GetTime();
137
138         checkable->SetNextCheck(nextCheck);
139
140         /* trigger update event for DB IDO */
141         Checkable::OnNextCheckUpdated(checkable);
142
143         return ApiActions::CreateResult(200, "Successfully rescheduled check for object '" + checkable->GetName() + "'.");
144 }
145
146 Dictionary::Ptr ApiActions::SendCustomNotification(const ConfigObject::Ptr& object,
147         const Dictionary::Ptr& params)
148 {
149         Checkable::Ptr checkable = static_pointer_cast<Checkable>(object);
150
151         if (!checkable)
152                 return ApiActions::CreateResult(404, "Cannot send notification for non-existent object.");
153
154         if (!params->Contains("author"))
155                 return ApiActions::CreateResult(400, "Parameter 'author' is required.");
156
157         if (!params->Contains("comment"))
158                 return ApiActions::CreateResult(400, "Parameter 'comment' is required.");
159
160         if (Convert::ToBool(HttpUtility::GetLastParameter(params, "force")))
161                 checkable->SetForceNextNotification(true);
162
163         Checkable::OnNotificationsRequested(checkable, NotificationCustom, checkable->GetLastCheckResult(),
164                 HttpUtility::GetLastParameter(params, "author"), HttpUtility::GetLastParameter(params, "comment"), nullptr);
165
166         return ApiActions::CreateResult(200, "Successfully sent custom notification for object '" + checkable->GetName() + "'.");
167 }
168
169 Dictionary::Ptr ApiActions::DelayNotification(const ConfigObject::Ptr& object,
170         const Dictionary::Ptr& params)
171 {
172         Checkable::Ptr checkable = static_pointer_cast<Checkable>(object);
173
174         if (!checkable)
175                 return ApiActions::CreateResult(404, "Cannot delay notifications for non-existent object");
176
177         if (!params->Contains("timestamp"))
178                 return ApiActions::CreateResult(400, "A timestamp is required to delay notifications");
179
180         for (const Notification::Ptr& notification : checkable->GetNotifications()) {
181                 notification->SetNextNotification(HttpUtility::GetLastParameter(params, "timestamp"));
182         }
183
184         return ApiActions::CreateResult(200, "Successfully delayed notifications for object '" + checkable->GetName() + "'.");
185 }
186
187 Dictionary::Ptr ApiActions::AcknowledgeProblem(const ConfigObject::Ptr& object,
188         const Dictionary::Ptr& params)
189 {
190         Checkable::Ptr checkable = static_pointer_cast<Checkable>(object);
191
192         if (!checkable)
193                 return ApiActions::CreateResult(404, "Cannot acknowledge problem for non-existent object.");
194
195         if (!params->Contains("author") || !params->Contains("comment"))
196                 return ApiActions::CreateResult(400, "Acknowledgements require author and comment.");
197
198         AcknowledgementType sticky = AcknowledgementNormal;
199         bool notify = false;
200         bool persistent = false;
201         double timestamp = 0.0;
202
203         if (params->Contains("sticky") && HttpUtility::GetLastParameter(params, "sticky"))
204                 sticky = AcknowledgementSticky;
205         if (params->Contains("notify"))
206                 notify = HttpUtility::GetLastParameter(params, "notify");
207         if (params->Contains("persistent"))
208                 persistent = HttpUtility::GetLastParameter(params, "persistent");
209         if (params->Contains("expiry")) {
210                 timestamp = HttpUtility::GetLastParameter(params, "expiry");
211
212                 if (timestamp <= Utility::GetTime())
213                         return ApiActions::CreateResult(409, "Acknowledgement 'expiry' timestamp must be in the future for object " + checkable->GetName());
214         } else
215                 timestamp = 0;
216
217         Host::Ptr host;
218         Service::Ptr service;
219         tie(host, service) = GetHostService(checkable);
220
221         if (!service) {
222                 if (host->GetState() == HostUp)
223                         return ApiActions::CreateResult(409, "Host " + checkable->GetName() + " is UP.");
224         } else {
225                 if (service->GetState() == ServiceOK)
226                         return ApiActions::CreateResult(409, "Service " + checkable->GetName() + " is OK.");
227         }
228
229         Comment::AddComment(checkable, CommentAcknowledgement, HttpUtility::GetLastParameter(params, "author"),
230                 HttpUtility::GetLastParameter(params, "comment"), persistent, timestamp);
231         checkable->AcknowledgeProblem(HttpUtility::GetLastParameter(params, "author"),
232                 HttpUtility::GetLastParameter(params, "comment"), sticky, notify, persistent, timestamp);
233
234         return ApiActions::CreateResult(200, "Successfully acknowledged problem for object '" + checkable->GetName() + "'.");
235 }
236
237 Dictionary::Ptr ApiActions::RemoveAcknowledgement(const ConfigObject::Ptr& object,
238         const Dictionary::Ptr& params)
239 {
240         Checkable::Ptr checkable = static_pointer_cast<Checkable>(object);
241
242         if (!checkable)
243                 return ApiActions::CreateResult(404,
244                         "Cannot remove acknowledgement for non-existent checkable object "
245                         + object->GetName() + ".");
246
247         checkable->ClearAcknowledgement();
248         checkable->RemoveCommentsByType(CommentAcknowledgement);
249
250         return ApiActions::CreateResult(200, "Successfully removed acknowledgement for object '" + checkable->GetName() + "'.");
251 }
252
253 Dictionary::Ptr ApiActions::AddComment(const ConfigObject::Ptr& object,
254         const Dictionary::Ptr& params)
255 {
256         Checkable::Ptr checkable = static_pointer_cast<Checkable>(object);
257
258         if (!checkable)
259                 return ApiActions::CreateResult(404, "Cannot add comment for non-existent object");
260
261         if (!params->Contains("author") || !params->Contains("comment"))
262                 return ApiActions::CreateResult(400, "Comments require author and comment.");
263
264         String commentName = Comment::AddComment(checkable, CommentUser,
265                 HttpUtility::GetLastParameter(params, "author"),
266                 HttpUtility::GetLastParameter(params, "comment"), false, 0);
267
268         Comment::Ptr comment = Comment::GetByName(commentName);
269
270         Dictionary::Ptr additional = new Dictionary({
271                 { "name", commentName },
272                 { "legacy_id", comment->GetLegacyId() }
273         });
274
275         return ApiActions::CreateResult(200, "Successfully added comment '"
276                 + commentName + "' for object '" + checkable->GetName()
277                 + "'.", additional);
278 }
279
280 Dictionary::Ptr ApiActions::RemoveComment(const ConfigObject::Ptr& object,
281         const Dictionary::Ptr& params)
282 {
283         Checkable::Ptr checkable = dynamic_pointer_cast<Checkable>(object);
284
285         if (checkable) {
286                 std::set<Comment::Ptr> comments = checkable->GetComments();
287
288                 for (const Comment::Ptr& comment : comments) {
289                         Comment::RemoveComment(comment->GetName());
290                 }
291
292                 return ApiActions::CreateResult(200, "Successfully removed all comments for object '" + checkable->GetName() + "'.");
293         }
294
295         Comment::Ptr comment = static_pointer_cast<Comment>(object);
296
297         if (!comment)
298                 return ApiActions::CreateResult(404, "Cannot remove non-existent comment object.");
299
300         String commentName = comment->GetName();
301
302         Comment::RemoveComment(commentName);
303
304         return ApiActions::CreateResult(200, "Successfully removed comment '" + commentName + "'.");
305 }
306
307 Dictionary::Ptr ApiActions::ScheduleDowntime(const ConfigObject::Ptr& object,
308         const Dictionary::Ptr& params)
309 {
310         Checkable::Ptr checkable = static_pointer_cast<Checkable>(object);
311
312         if (!checkable)
313                 return ApiActions::CreateResult(404, "Can't schedule downtime for non-existent object.");
314
315         if (!params->Contains("start_time") || !params->Contains("end_time") ||
316                 !params->Contains("author") || !params->Contains("comment")) {
317
318                 return ApiActions::CreateResult(400, "Options 'start_time', 'end_time', 'author' and 'comment' are required");
319         }
320
321         bool fixed = true;
322         if (params->Contains("fixed"))
323                 fixed = HttpUtility::GetLastParameter(params, "fixed");
324
325         if (!fixed && !params->Contains("duration"))
326                 return ApiActions::CreateResult(400, "Option 'duration' is required for flexible downtime");
327
328         double duration = 0.0;
329         if (params->Contains("duration"))
330                 duration = HttpUtility::GetLastParameter(params, "duration");
331
332         String triggerName;
333         if (params->Contains("trigger_name"))
334                 triggerName = HttpUtility::GetLastParameter(params, "trigger_name");
335
336         String author = HttpUtility::GetLastParameter(params, "author");
337         String comment = HttpUtility::GetLastParameter(params, "comment");
338         double startTime = HttpUtility::GetLastParameter(params, "start_time");
339         double endTime = HttpUtility::GetLastParameter(params, "end_time");
340
341         DowntimeChildOptions childOptions = DowntimeNoChildren;
342         if (params->Contains("child_options")) {
343                 try {
344                         childOptions = Downtime::ChildOptionsFromValue(HttpUtility::GetLastParameter(params, "child_options"));
345                 } catch (const std::exception&) {
346                         return ApiActions::CreateResult(400, "Option 'child_options' provided an invalid value.");
347                 }
348         }
349
350         String downtimeName = Downtime::AddDowntime(checkable, author, comment, startTime, endTime,
351                 fixed, triggerName, duration);
352
353         Downtime::Ptr downtime = Downtime::GetByName(downtimeName);
354
355         Dictionary::Ptr additional = new Dictionary({
356                 { "name", downtimeName },
357                 { "legacy_id", downtime->GetLegacyId() }
358         });
359
360         /* Schedule downtime for all child objects. */
361         if (childOptions != DowntimeNoChildren) {
362                 /* 'DowntimeTriggeredChildren' schedules child downtimes triggered by the parent downtime.
363                  * 'DowntimeNonTriggeredChildren' schedules non-triggered downtimes for all children.
364                  */
365                 if (childOptions == DowntimeTriggeredChildren)
366                         triggerName = downtimeName;
367
368                 Log(LogNotice, "ApiActions")
369                         << "Processing child options " << childOptions << " for downtime " << downtimeName;
370
371                 ArrayData childDowntimes;
372
373                 for (const Checkable::Ptr& child : checkable->GetAllChildren()) {
374                         Log(LogNotice, "ApiActions")
375                                 << "Scheduling downtime for child object " << child->GetName();
376
377                         String childDowntimeName = Downtime::AddDowntime(child, author, comment, startTime, endTime,
378                                 fixed, triggerName, duration);
379
380                         Log(LogNotice, "ApiActions")
381                                 << "Add child downtime '" << childDowntimeName << "'.";
382
383                         Downtime::Ptr childDowntime = Downtime::GetByName(childDowntimeName);
384
385                         childDowntimes.push_back(new Dictionary({
386                                 { "name", childDowntimeName },
387                                 { "legacy_id", childDowntime->GetLegacyId() }
388                         }));
389                 }
390
391                 additional->Set("child_downtimes", new Array(std::move(childDowntimes)));
392         }
393
394         return ApiActions::CreateResult(200, "Successfully scheduled downtime '" +
395                 downtimeName + "' for object '" + checkable->GetName() + "'.", additional);
396 }
397
398 Dictionary::Ptr ApiActions::RemoveDowntime(const ConfigObject::Ptr& object,
399         const Dictionary::Ptr& params)
400 {
401         Checkable::Ptr checkable = dynamic_pointer_cast<Checkable>(object);
402
403         if (checkable) {
404                 std::set<Downtime::Ptr> downtimes = checkable->GetDowntimes();
405
406                 for (const Downtime::Ptr& downtime : downtimes) {
407                         Downtime::RemoveDowntime(downtime->GetName(), true);
408                 }
409
410                 return ApiActions::CreateResult(200, "Successfully removed all downtimes for object '" + checkable->GetName() + "'.");
411         }
412
413         Downtime::Ptr downtime = static_pointer_cast<Downtime>(object);
414
415         if (!downtime)
416                 return ApiActions::CreateResult(404, "Cannot remove non-existent downtime object.");
417
418         String downtimeName = downtime->GetName();
419
420         Downtime::RemoveDowntime(downtimeName, true);
421
422         return ApiActions::CreateResult(200, "Successfully removed downtime '" + downtimeName + "'.");
423 }
424
425 Dictionary::Ptr ApiActions::ShutdownProcess(const ConfigObject::Ptr& object,
426         const Dictionary::Ptr& params)
427 {
428         Application::RequestShutdown();
429
430         return ApiActions::CreateResult(200, "Shutting down Icinga 2.");
431 }
432
433 Dictionary::Ptr ApiActions::RestartProcess(const ConfigObject::Ptr& object,
434         const Dictionary::Ptr& params)
435 {
436         Application::RequestRestart();
437
438         return ApiActions::CreateResult(200, "Restarting Icinga 2.");
439 }
440
441 Dictionary::Ptr ApiActions::GenerateTicket(const ConfigObject::Ptr&,
442         const Dictionary::Ptr& params)
443 {
444         if (!params->Contains("cn"))
445                 return ApiActions::CreateResult(400, "Option 'cn' is required");
446
447         String cn = HttpUtility::GetLastParameter(params, "cn");
448
449         ApiListener::Ptr listener = ApiListener::GetInstance();
450         String salt = listener->GetTicketSalt();
451
452         if (salt.IsEmpty())
453                 return ApiActions::CreateResult(500, "Ticket salt is not configured in ApiListener object");
454
455         String ticket = PBKDF2_SHA1(cn, salt, 50000);
456
457         Dictionary::Ptr additional = new Dictionary({
458                 { "ticket", ticket }
459         });
460
461         return ApiActions::CreateResult(200, "Generated PKI ticket '" + ticket + "' for common name '"
462                 + cn + "'.", additional);
463 }