]> granicus.if.org Git - icinga2/blob - lib/remote/consolehandler.cpp
79962cb50b51583f286ce50df66b811ff08901d4
[icinga2] / lib / remote / consolehandler.cpp
1 /******************************************************************************
2  * Icinga 2                                                                   *
3  * Copyright (C) 2012-2018 Icinga Development Team (https://www.icinga.com/)  *
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 "remote/consolehandler.hpp"
21 #include "remote/httputility.hpp"
22 #include "remote/filterutility.hpp"
23 #include "config/configcompiler.hpp"
24 #include "base/configtype.hpp"
25 #include "base/configwriter.hpp"
26 #include "base/scriptglobal.hpp"
27 #include "base/logger.hpp"
28 #include "base/serializer.hpp"
29 #include "base/timer.hpp"
30 #include "base/initialize.hpp"
31 #include <boost/thread/once.hpp>
32 #include <set>
33
34 using namespace icinga;
35
36 REGISTER_URLHANDLER("/v1/console", ConsoleHandler);
37
38 static boost::mutex l_QueryMutex;
39 static std::map<String, ApiScriptFrame> l_ApiScriptFrames;
40 static Timer::Ptr l_FrameCleanupTimer;
41 static boost::mutex l_ApiScriptMutex;
42
43 static void ScriptFrameCleanupHandler()
44 {
45         boost::mutex::scoped_lock lock(l_ApiScriptMutex);
46
47         std::vector<String> cleanup_keys;
48
49         typedef std::pair<String, ApiScriptFrame> KVPair;
50
51         for (const KVPair& kv : l_ApiScriptFrames) {
52                 if (kv.second.Seen < Utility::GetTime() - 1800)
53                         cleanup_keys.push_back(kv.first);
54         }
55
56         for (const String& key : cleanup_keys)
57                 l_ApiScriptFrames.erase(key);
58 }
59
60 static void EnsureFrameCleanupTimer()
61 {
62         static boost::once_flag once = BOOST_ONCE_INIT;
63
64         boost::call_once(once, []() {
65                 l_FrameCleanupTimer = new Timer();
66                 l_FrameCleanupTimer->OnTimerExpired.connect(std::bind(ScriptFrameCleanupHandler));
67                 l_FrameCleanupTimer->SetInterval(30);
68                 l_FrameCleanupTimer->Start();
69         });
70 }
71
72 bool ConsoleHandler::HandleRequest(const ApiUser::Ptr& user, HttpRequest& request, HttpResponse& response, const Dictionary::Ptr& params)
73 {
74         if (request.RequestUrl->GetPath().size() > 3)
75                 return false;
76
77         if (request.RequestMethod != "POST")
78                 return false;
79
80         QueryDescription qd;
81
82         String methodName = request.RequestUrl->GetPath()[2];
83
84         FilterUtility::CheckPermission(user, "console");
85
86         String session = HttpUtility::GetLastParameter(params, "session");
87
88         if (session.IsEmpty())
89                 session = Utility::NewUniqueID();
90
91         String command = HttpUtility::GetLastParameter(params, "command");
92
93         bool sandboxed = HttpUtility::GetLastParameter(params, "sandboxed");
94
95         if (methodName == "execute-script")
96                 return ExecuteScriptHelper(request, response, params, command, session, sandboxed);
97         else if (methodName == "auto-complete-script")
98                 return AutocompleteScriptHelper(request, response, params, command, session, sandboxed);
99
100         HttpUtility::SendJsonError(response, params, 400, "Invalid method specified: " + methodName);
101         return true;
102 }
103
104 bool ConsoleHandler::ExecuteScriptHelper(HttpRequest& request, HttpResponse& response,
105         const Dictionary::Ptr& params, const String& command, const String& session, bool sandboxed)
106 {
107         Log(LogNotice, "Console")
108                 << "Executing expression: " << command;
109
110         EnsureFrameCleanupTimer();
111
112         ApiScriptFrame& lsf = l_ApiScriptFrames[session];
113         lsf.Seen = Utility::GetTime();
114
115         if (!lsf.Locals)
116                 lsf.Locals = new Dictionary();
117
118         String fileName = "<" + Convert::ToString(lsf.NextLine) + ">";
119         lsf.NextLine++;
120
121         lsf.Lines[fileName] = command;
122
123         Dictionary::Ptr resultInfo;
124         std::unique_ptr<Expression> expr;
125         Value exprResult;
126
127         try {
128                 expr = ConfigCompiler::CompileText(fileName, command);
129
130                 ScriptFrame frame(true);
131                 frame.Locals = lsf.Locals;
132                 frame.Self = lsf.Locals;
133                 frame.Sandboxed = sandboxed;
134
135                 exprResult = expr->Evaluate(frame);
136
137                 resultInfo = new Dictionary({
138                         { "code", 200 },
139                         { "status", "Executed successfully." },
140                         { "result", Serialize(exprResult, 0) }
141                 });
142         } catch (const ScriptError& ex) {
143                 DebugInfo di = ex.GetDebugInfo();
144
145                 std::ostringstream msgbuf;
146
147                 msgbuf << di.Path << ": " << lsf.Lines[di.Path] << "\n"
148                         << String(di.Path.GetLength() + 2, ' ')
149                         << String(di.FirstColumn, ' ') << String(di.LastColumn - di.FirstColumn + 1, '^') << "\n"
150                         << ex.what() << "\n";
151
152                 resultInfo = new Dictionary({
153                         { "code", 500 },
154                         { "status", String(msgbuf.str()) },
155                         { "incomplete_expression", ex.IsIncompleteExpression() },
156                         { "debug_info", new Dictionary({
157                                 { "path", di.Path },
158                                 { "first_line", di.FirstLine },
159                                 { "first_column", di.FirstColumn },
160                                 { "last_line", di.LastLine },
161                                 { "last_column", di.LastColumn }
162                         }) }
163                 });
164         }
165
166         Dictionary::Ptr result = new Dictionary({
167                 { "results", new Array({ resultInfo }) }
168         });
169
170         response.SetStatus(200, "OK");
171         HttpUtility::SendJsonBody(response, params, result);
172
173         return true;
174 }
175
176 bool ConsoleHandler::AutocompleteScriptHelper(HttpRequest& request, HttpResponse& response,
177         const Dictionary::Ptr& params, const String& command, const String& session, bool sandboxed)
178 {
179         Log(LogInformation, "Console")
180                 << "Auto-completing expression: " << command;
181
182         EnsureFrameCleanupTimer();
183
184         ApiScriptFrame& lsf = l_ApiScriptFrames[session];
185         lsf.Seen = Utility::GetTime();
186
187         if (!lsf.Locals)
188                 lsf.Locals = new Dictionary();
189
190
191         ScriptFrame frame(true);
192         frame.Locals = lsf.Locals;
193         frame.Self = lsf.Locals;
194         frame.Sandboxed = sandboxed;
195
196         Dictionary::Ptr result1 = new Dictionary({
197                 { "code", 200 },
198                 { "status", "Auto-completed successfully." },
199                 { "suggestions", Array::FromVector(GetAutocompletionSuggestions(command, frame)) }
200         });
201
202         Dictionary::Ptr result = new Dictionary({
203                 { "results", new Array({ result1 }) }
204         });
205
206         response.SetStatus(200, "OK");
207         HttpUtility::SendJsonBody(response, params, result);
208
209         return true;
210 }
211
212 static void AddSuggestion(std::vector<String>& matches, const String& word, const String& suggestion)
213 {
214         if (suggestion.Find(word) != 0)
215                 return;
216
217         matches.push_back(suggestion);
218 }
219
220 static void AddSuggestions(std::vector<String>& matches, const String& word, const String& pword, bool withFields, const Value& value)
221 {
222         String prefix;
223
224         if (!pword.IsEmpty())
225                 prefix = pword + ".";
226
227         if (value.IsObjectType<Dictionary>()) {
228                 Dictionary::Ptr dict = value;
229
230                 ObjectLock olock(dict);
231                 for (const Dictionary::Pair& kv : dict) {
232                         AddSuggestion(matches, word, prefix + kv.first);
233                 }
234         }
235
236         if (withFields) {
237                 Type::Ptr type = value.GetReflectionType();
238
239                 for (int i = 0; i < type->GetFieldCount(); i++) {
240                         Field field = type->GetFieldInfo(i);
241
242                         AddSuggestion(matches, word, prefix + field.Name);
243                 }
244
245                 while (type) {
246                         Object::Ptr prototype = type->GetPrototype();
247                         Dictionary::Ptr dict = dynamic_pointer_cast<Dictionary>(prototype);
248
249                         if (dict) {
250                                 ObjectLock olock(dict);
251                                 for (const Dictionary::Pair& kv : dict) {
252                                         AddSuggestion(matches, word, prefix + kv.first);
253                                 }
254                         }
255
256                         type = type->GetBaseType();
257                 }
258         }
259 }
260
261 std::vector<String> ConsoleHandler::GetAutocompletionSuggestions(const String& word, ScriptFrame& frame)
262 {
263         std::vector<String> matches;
264
265         for (const String& keyword : ConfigWriter::GetKeywords()) {
266                 AddSuggestion(matches, word, keyword);
267         }
268
269         {
270                 ObjectLock olock(frame.Locals);
271                 for (const Dictionary::Pair& kv : frame.Locals) {
272                         AddSuggestion(matches, word, kv.first);
273                 }
274         }
275
276         {
277                 ObjectLock olock(ScriptGlobal::GetGlobals());
278                 for (const Dictionary::Pair& kv : ScriptGlobal::GetGlobals()) {
279                         AddSuggestion(matches, word, kv.first);
280                 }
281         }
282
283         {
284                 Array::Ptr imports = ScriptFrame::GetImports();
285                 ObjectLock olock(imports);
286                 for (const Value& import : imports) {
287                         AddSuggestions(matches, word, "", false, import);
288                 }
289         }
290
291         String::SizeType cperiod = word.RFind(".");
292
293         if (cperiod != String::NPos) {
294                 String pword = word.SubStr(0, cperiod);
295
296                 Value value;
297
298                 try {
299                         std::unique_ptr<Expression> expr = ConfigCompiler::CompileText("temp", pword);
300
301                         if (expr)
302                                 value = expr->Evaluate(frame);
303
304                         AddSuggestions(matches, word, pword, true, value);
305                 } catch (...) { /* Ignore the exception */ }
306         }
307
308         return matches;
309 }