1 /******************************************************************************
3 * Copyright (C) 2012-2017 Icinga Development Team (https://www.icinga.com/) *
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. *
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. *
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 ******************************************************************************/
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>
35 using namespace icinga;
37 static boost::once_flag l_HttpServerConnectionOnceFlag = BOOST_ONCE_INIT;
38 static Timer::Ptr l_HttpServerConnectionTimeoutTimer;
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)
43 boost::call_once(l_HttpServerConnectionOnceFlag, &HttpServerConnection::StaticInitialize);
45 m_RequestQueue.SetName("HttpServerConnection");
48 m_ApiUser = ApiUser::GetByClientCN(identity);
51 void HttpServerConnection::StaticInitialize(void)
53 l_HttpServerConnectionTimeoutTimer = new Timer();
54 l_HttpServerConnectionTimeoutTimer->OnTimerExpired.connect(boost::bind(&HttpServerConnection::TimeoutTimerHandler));
55 l_HttpServerConnectionTimeoutTimer->SetInterval(5);
56 l_HttpServerConnectionTimeoutTimer->Start();
59 void HttpServerConnection::Start(void)
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();
67 ApiUser::Ptr HttpServerConnection::GetApiUser(void) const
72 TlsStream::Ptr HttpServerConnection::GetStream(void) const
77 void HttpServerConnection::Disconnect(void)
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");
85 Log(LogDebug, "HttpServerConnection", "Http client disconnected");
87 ApiListener::Ptr listener = ApiListener::GetInstance();
88 listener->RemoveHttpClient(this);
90 m_CurrentRequest.~HttpRequest();
91 new (&m_CurrentRequest) HttpRequest(Stream::Ptr());
96 bool HttpServerConnection::ProcessMessage(void)
100 HttpResponse response(m_Stream, m_CurrentRequest);
102 if (!m_CurrentRequest.CompleteHeaders) {
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());
111 m_CurrentRequest.~HttpRequest();
112 new (&m_CurrentRequest) HttpRequest(m_Stream);
114 m_Stream->Shutdown();
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());
123 m_CurrentRequest.~HttpRequest();
124 new (&m_CurrentRequest) HttpRequest(m_Stream);
126 m_Stream->Shutdown();
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);
139 m_Stream->Shutdown();
145 if (!m_CurrentRequest.CompleteBody) {
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());
154 m_CurrentRequest.~HttpRequest();
155 new (&m_CurrentRequest) HttpRequest(m_Stream);
157 m_Stream->Shutdown();
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());
166 m_CurrentRequest.~HttpRequest();
167 new (&m_CurrentRequest) HttpRequest(m_Stream);
169 m_Stream->Shutdown();
176 m_RequestQueue.Enqueue(std::bind(&HttpServerConnection::ProcessMessageAsync,
177 HttpServerConnection::Ptr(this), m_CurrentRequest, response, m_AuthenticatedUser));
179 m_Seen = Utility::GetTime();
182 m_CurrentRequest.~HttpRequest();
183 new (&m_CurrentRequest) HttpRequest(m_Stream);
188 bool HttpServerConnection::ManageHeaders(HttpResponse& response)
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}
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());
205 /* client_cn matched. */
207 m_AuthenticatedUser = m_ApiUser;
209 m_AuthenticatedUser = ApiUser::GetByAuthHeader(m_CurrentRequest.Headers->Get("authorization"));
211 String requestUrl = m_CurrentRequest.RequestUrl->Format();
213 Socket::Ptr socket = m_Stream->GetSocket();
215 Log(LogInformation, "HttpServerConnection")
216 << "Request: " << m_CurrentRequest.RequestMethod << " " << requestUrl
217 << " (from " << (socket ? socket->GetPeerAddress() : "<unkown>")
218 << ", user: " << (m_AuthenticatedUser ? m_AuthenticatedUser->GetName() : "<unauthenticated>") << ")";
220 ApiListener::Ptr listener = ApiListener::GetInstance();
225 Array::Ptr headerAllowOrigin = listener->GetAccessControlAllowOrigin();
227 if (headerAllowOrigin->GetLength() != 0) {
228 String origin = m_CurrentRequest.Headers->Get("origin");
230 ObjectLock olock(headerAllowOrigin);
232 for (const String& allowedOrigin : headerAllowOrigin) {
233 if (allowedOrigin == origin)
234 response.AddHeader("Access-Control-Allow-Origin", origin);
238 if (listener->GetAccessControlAllowCredentials())
239 response.AddHeader("Access-Control-Allow-Credentials", "true");
241 String accessControlRequestMethodHeader = m_CurrentRequest.Headers->Get("access-control-request-method");
243 if (m_CurrentRequest.RequestMethod == "OPTIONS" && !accessControlRequestMethodHeader.IsEmpty()) {
244 response.SetStatus(200, "OK");
246 response.AddHeader("Access-Control-Allow-Methods", listener->GetAccessControlAllowMethods());
247 response.AddHeader("Access-Control-Allow-Headers", listener->GetAccessControlAllowHeaders());
249 String msg = "Preflight OK";
250 response.WriteBody(msg.CStr(), msg.GetLength());
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());
266 if (!m_AuthenticatedUser) {
267 Log(LogWarning, "HttpServerConnection")
268 << "Unauthorized request: " << m_CurrentRequest.RequestMethod << " " << requestUrl;
270 response.SetStatus(401, "Unauthorized");
271 response.AddHeader("WWW-Authenticate", "Basic realm=\"Icinga 2\"");
273 if (m_CurrentRequest.Headers->Get("accept") == "application/json") {
274 Dictionary::Ptr result = new Dictionary();
276 result->Set("error", 401);
277 result->Set("status", "Unauthorized. Please check your user credentials.");
279 HttpUtility::SendJsonBody(response, result);
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());
290 size_t maxSize = defaultContentLengthLimit;
292 Array::Ptr permissions = m_AuthenticatedUser->GetPermissions();
293 ObjectLock olock(permissions);
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;
302 size_t contentLength = m_CurrentRequest.Headers->Get("content-length");
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());
316 void HttpServerConnection::ProcessMessageAsync(HttpRequest& request, HttpResponse& response, const ApiUser::Ptr& user)
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));
330 void HttpServerConnection::DataAvailableHandler(void)
334 if (!m_Stream->IsEof()) {
335 boost::mutex::scoped_lock lock(m_DataHandlerMutex);
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);
353 void HttpServerConnection::CheckLiveness(void)
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.";
362 void HttpServerConnection::TimeoutTimerHandler(void)
364 ApiListener::Ptr listener = ApiListener::GetInstance();
366 for (const HttpServerConnection::Ptr& client : listener->GetHttpClients()) {
367 client->CheckLiveness();