]> granicus.if.org Git - icinga2/blob - lib/remote/httpserverconnection.cpp
Limit HTTP body size
[icinga2] / lib / remote / httpserverconnection.cpp
1 /******************************************************************************
2  * Icinga 2                                                                   *
3  * Copyright (C) 2012-2017 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/httpserverconnection.hpp"
21 #include "remote/httphandler.hpp"
22 #include "remote/httputility.hpp"
23 #include "remote/apilistener.hpp"
24 #include "remote/apifunction.hpp"
25 #include "remote/jsonrpc.hpp"
26 #include "base/base64.hpp"
27 #include "base/configtype.hpp"
28 #include "base/objectlock.hpp"
29 #include "base/utility.hpp"
30 #include "base/logger.hpp"
31 #include "base/exception.hpp"
32 #include "base/convert.hpp"
33 #include <boost/thread/once.hpp>
34
35 using namespace icinga;
36
37 static boost::once_flag l_HttpServerConnectionOnceFlag = BOOST_ONCE_INIT;
38 static Timer::Ptr l_HttpServerConnectionTimeoutTimer;
39
40 HttpServerConnection::HttpServerConnection(const String& identity, bool authenticated, const TlsStream::Ptr& stream)
41         : m_Stream(stream), m_Seen(Utility::GetTime()), m_CurrentRequest(stream), m_PendingRequests(0)
42 {
43         boost::call_once(l_HttpServerConnectionOnceFlag, &HttpServerConnection::StaticInitialize);
44
45         m_RequestQueue.SetName("HttpServerConnection");
46
47         if (authenticated)
48                 m_ApiUser = ApiUser::GetByClientCN(identity);
49 }
50
51 void HttpServerConnection::StaticInitialize(void)
52 {
53         l_HttpServerConnectionTimeoutTimer = new Timer();
54         l_HttpServerConnectionTimeoutTimer->OnTimerExpired.connect(boost::bind(&HttpServerConnection::TimeoutTimerHandler));
55         l_HttpServerConnectionTimeoutTimer->SetInterval(5);
56         l_HttpServerConnectionTimeoutTimer->Start();
57 }
58
59 void HttpServerConnection::Start(void)
60 {
61         /* the stream holds an owning reference to this object through the callback we're registering here */
62         m_Stream->RegisterDataHandler(boost::bind(&HttpServerConnection::DataAvailableHandler, HttpServerConnection::Ptr(this)));
63         if (m_Stream->IsDataAvailable())
64                 DataAvailableHandler();
65 }
66
67 ApiUser::Ptr HttpServerConnection::GetApiUser(void) const
68 {
69         return m_ApiUser;
70 }
71
72 TlsStream::Ptr HttpServerConnection::GetStream(void) const
73 {
74         return m_Stream;
75 }
76
77 void HttpServerConnection::Disconnect(void)
78 {
79         boost::mutex::scoped_try_lock lock(m_DataHandlerMutex);
80         if (!lock.owns_lock()) {
81                 Log(LogInformation, "HttpServerConnection", "Unable to disconnect Http client, I/O thread busy");
82                 return;
83         }
84
85         Log(LogDebug, "HttpServerConnection", "Http client disconnected");
86
87         ApiListener::Ptr listener = ApiListener::GetInstance();
88         listener->RemoveHttpClient(this);
89
90         m_CurrentRequest.~HttpRequest();
91         new (&m_CurrentRequest) HttpRequest(Stream::Ptr());
92
93         m_Stream->Close();
94 }
95
96 bool HttpServerConnection::ProcessMessage(void)
97 {
98
99         bool res;
100         HttpResponse response(m_Stream, m_CurrentRequest);
101
102         if (!m_CurrentRequest.CompleteHeaders) {
103                 try {
104                         res = m_CurrentRequest.ParseHeader(m_Context, false);
105                 } catch (const std::invalid_argument& ex) {
106                         response.SetStatus(400, "Bad Request");
107                         String msg = String("<h1>Bad Request</h1><p><pre>") + ex.what() + "</pre></p>";
108                         response.WriteBody(msg.CStr(), msg.GetLength());
109                         response.Finish();
110
111                         m_CurrentRequest.~HttpRequest();
112                         new (&m_CurrentRequest) HttpRequest(m_Stream);
113
114                         m_Stream->Shutdown();
115
116                         return false;
117                 } catch (const std::exception& ex) {
118                         response.SetStatus(500, "Internal Server Error");
119                         String msg = "<h1>Internal Server Error</h1><p><pre>" + DiagnosticInformation(ex) + "</pre></p>";
120                         response.WriteBody(msg.CStr(), msg.GetLength());
121                         response.Finish();
122
123                         m_CurrentRequest.~HttpRequest();
124                         new (&m_CurrentRequest) HttpRequest(m_Stream);
125
126                         m_Stream->Shutdown();
127
128                         return false;
129                 }
130                 return res;
131         }
132
133         if (!m_CurrentRequest.CompleteHeaderCheck) {
134                 m_CurrentRequest.CompleteHeaderCheck = true;
135                 if (!ManageHeaders(response)) {
136                         m_CurrentRequest.~HttpRequest();
137                         new (&m_CurrentRequest) HttpRequest(m_Stream);
138
139                         m_Stream->Shutdown();
140
141                         return false;
142                 }
143         }
144
145         if (!m_CurrentRequest.CompleteBody) {
146                 try {
147                         res = m_CurrentRequest.ParseBody(m_Context, false);
148                 } catch (const std::invalid_argument& ex) {
149                         response.SetStatus(400, "Bad Request");
150                         String msg = String("<h1>Bad Request</h1><p><pre>") + ex.what() + "</pre></p>";
151                         response.WriteBody(msg.CStr(), msg.GetLength());
152                         response.Finish();
153
154                         m_CurrentRequest.~HttpRequest();
155                         new (&m_CurrentRequest) HttpRequest(m_Stream);
156
157                         m_Stream->Shutdown();
158
159                         return false;
160                 } catch (const std::exception& ex) {
161                         response.SetStatus(500, "Internal Server Error");
162                         String msg = "<h1>Internal Server Error</h1><p><pre>" + DiagnosticInformation(ex) + "</pre></p>";
163                         response.WriteBody(msg.CStr(), msg.GetLength());
164                         response.Finish();
165
166                         m_CurrentRequest.~HttpRequest();
167                         new (&m_CurrentRequest) HttpRequest(m_Stream);
168
169                         m_Stream->Shutdown();
170
171                         return false;
172                 }
173                 return res;
174         }
175
176         m_RequestQueue.Enqueue(std::bind(&HttpServerConnection::ProcessMessageAsync,
177                 HttpServerConnection::Ptr(this), m_CurrentRequest, response, m_AuthenticatedUser));
178
179         m_Seen = Utility::GetTime();
180         m_PendingRequests++;
181
182         m_CurrentRequest.~HttpRequest();
183         new (&m_CurrentRequest) HttpRequest(m_Stream);
184
185         return false;
186 }
187
188 bool HttpServerConnection::ManageHeaders(HttpResponse& response)
189 {
190         static const size_t defaultContentLengthLimit = 1 * 1028 * 1028;
191         static const Dictionary::Ptr specialContentLengthLimits = new Dictionary({
192                   {"*", 512 * 1028 * 1028},
193                   {"config/modify", 512 * 1028 * 1028},
194                   {"console", 512 * 1028 * 1028},
195                   {"objects/create", 512 * 1028 * 1028},
196                   {"objects/modify", 512 * 1028 * 1028},
197                   {"objects/delete", 512 * 1028 * 1028}
198         });
199
200         if (m_CurrentRequest.Headers->Get("expect") == "100-continue") {
201                 String continueResponse = "HTTP/1.1 100 Continue\r\n\r\n";
202                 m_Stream->Write(continueResponse.CStr(), continueResponse.GetLength());
203         }
204
205         /* client_cn matched. */
206         if (m_ApiUser)
207                 m_AuthenticatedUser = m_ApiUser;
208         else
209                 m_AuthenticatedUser = ApiUser::GetByAuthHeader(m_CurrentRequest.Headers->Get("authorization"));
210
211         String requestUrl = m_CurrentRequest.RequestUrl->Format();
212
213         Socket::Ptr socket = m_Stream->GetSocket();
214
215         Log(LogInformation, "HttpServerConnection")
216                 << "Request: " << m_CurrentRequest.RequestMethod << " " << requestUrl
217                 << " (from " << (socket ? socket->GetPeerAddress() : "<unkown>")
218                 << ", user: " << (m_AuthenticatedUser ? m_AuthenticatedUser->GetName() : "<unauthenticated>") << ")";
219
220         ApiListener::Ptr listener = ApiListener::GetInstance();
221
222         if (!listener)
223                 return false;
224
225         Array::Ptr headerAllowOrigin = listener->GetAccessControlAllowOrigin();
226
227         if (headerAllowOrigin->GetLength() != 0) {
228                 String origin = m_CurrentRequest.Headers->Get("origin");
229                 {
230                         ObjectLock olock(headerAllowOrigin);
231
232                         for (const String& allowedOrigin : headerAllowOrigin) {
233                                 if (allowedOrigin == origin)
234                                         response.AddHeader("Access-Control-Allow-Origin", origin);
235                         }
236                 }
237
238                 if (listener->GetAccessControlAllowCredentials())
239                         response.AddHeader("Access-Control-Allow-Credentials", "true");
240
241                 String accessControlRequestMethodHeader = m_CurrentRequest.Headers->Get("access-control-request-method");
242
243                 if (m_CurrentRequest.RequestMethod == "OPTIONS" && !accessControlRequestMethodHeader.IsEmpty()) {
244                         response.SetStatus(200, "OK");
245
246                         response.AddHeader("Access-Control-Allow-Methods", listener->GetAccessControlAllowMethods());
247                         response.AddHeader("Access-Control-Allow-Headers", listener->GetAccessControlAllowHeaders());
248
249                         String msg = "Preflight OK";
250                         response.WriteBody(msg.CStr(), msg.GetLength());
251
252                         response.Finish();
253                         return false;
254                 }
255         }
256
257         if (m_CurrentRequest.RequestMethod != "GET" && m_CurrentRequest.Headers->Get("accept") != "application/json") {
258                 response.SetStatus(400, "Wrong Accept header");
259                 response.AddHeader("Content-Type", "text/html");
260                 String msg = "<h1>Accept header is missing or not set to 'application/json'.</h1>";
261                 response.WriteBody(msg.CStr(), msg.GetLength());
262                 response.Finish();
263                 return false;
264         }
265
266         if (!m_AuthenticatedUser) {
267                 Log(LogWarning, "HttpServerConnection")
268                         << "Unauthorized request: " << m_CurrentRequest.RequestMethod << " " << requestUrl;
269
270                 response.SetStatus(401, "Unauthorized");
271                 response.AddHeader("WWW-Authenticate", "Basic realm=\"Icinga 2\"");
272
273                 if (m_CurrentRequest.Headers->Get("accept") == "application/json") {
274                         Dictionary::Ptr result = new Dictionary();
275
276                         result->Set("error", 401);
277                         result->Set("status", "Unauthorized. Please check your user credentials.");
278
279                         HttpUtility::SendJsonBody(response, result);
280                 } else {
281                         response.AddHeader("Content-Type", "text/html");
282                         String msg = "<h1>Unauthorized. Please check your user credentials.</h1>";
283                         response.WriteBody(msg.CStr(), msg.GetLength());
284                 }
285
286                 response.Finish();
287                 return false;
288         }
289
290         size_t maxSize = defaultContentLengthLimit;
291
292         Array::Ptr permissions = m_AuthenticatedUser->GetPermissions();
293         ObjectLock olock(permissions);
294
295         for (const Value& permission : permissions) {
296                 std::vector<String> permissionParts = String(permission).Split("/");
297                 String permissionPath = permissionParts[0] + (permissionParts.size() > 1 ? "/" + permissionParts[1] : "");
298                 int size = specialContentLengthLimits->Get(permissionPath);
299                 maxSize = size > maxSize ? size : maxSize;
300         }
301
302         size_t contentLength = m_CurrentRequest.Headers->Get("content-length");
303
304         if (contentLength > maxSize) {
305                 response.SetStatus(400, "Bad Request");
306                 String msg = String("<h1>Content length exceeded maximum</h1>");
307                 response.WriteBody(msg.CStr(), msg.GetLength());
308                 response.Finish();
309
310                 return false;
311         }
312
313         return true;
314 }
315
316 void HttpServerConnection::ProcessMessageAsync(HttpRequest& request, HttpResponse& response, const ApiUser::Ptr& user)
317 {
318         try {
319                 HttpHandler::ProcessRequest(user, request, response);
320         } catch (const std::exception& ex) {
321                 Log(LogCritical, "HttpServerConnection")
322                         << "Unhandled exception while processing Http request: " << DiagnosticInformation(ex);
323                 HttpUtility::SendJsonError(response, 503, "Unhandled exception" , DiagnosticInformation(ex));
324         }
325
326         response.Finish();
327         m_PendingRequests--;
328 }
329
330 void HttpServerConnection::DataAvailableHandler(void)
331 {
332         bool close = false;
333
334         if (!m_Stream->IsEof()) {
335                 boost::mutex::scoped_lock lock(m_DataHandlerMutex);
336
337                 try {
338                         while (ProcessMessage())
339                                 ; /* empty loop body */
340                 } catch (const std::exception& ex) {
341                         Log(LogWarning, "HttpServerConnection")
342                             << "Error while reading Http request: " << DiagnosticInformation(ex);
343
344                         close = true;
345                 }
346         } else
347                 close = true;
348
349         if (close)
350                 Disconnect();
351 }
352
353 void HttpServerConnection::CheckLiveness(void)
354 {
355         if (m_Seen < Utility::GetTime() - 10 && m_PendingRequests == 0) {
356                 Log(LogInformation, "HttpServerConnection")
357                     <<  "No messages for Http connection have been received in the last 10 seconds.";
358                 Disconnect();
359         }
360 }
361
362 void HttpServerConnection::TimeoutTimerHandler(void)
363 {
364         ApiListener::Ptr listener = ApiListener::GetInstance();
365
366         for (const HttpServerConnection::Ptr& client : listener->GetHttpClients()) {
367                 client->CheckLiveness();
368         }
369 }
370