/****************************************************************************** * Icinga 2 * * Copyright (C) 2012-2018 Icinga Development Team (https://www.icinga.com/) * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License * * as published by the Free Software Foundation; either version 2 * * of the License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software Foundation * * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * ******************************************************************************/ #include "remote/httpserverconnection.hpp" #include "remote/httphandler.hpp" #include "remote/httputility.hpp" #include "remote/apilistener.hpp" #include "remote/apifunction.hpp" #include "remote/jsonrpc.hpp" #include "base/base64.hpp" #include "base/configtype.hpp" #include "base/objectlock.hpp" #include "base/utility.hpp" #include "base/logger.hpp" #include "base/exception.hpp" #include "base/convert.hpp" #include using namespace icinga; static boost::once_flag l_HttpServerConnectionOnceFlag = BOOST_ONCE_INIT; static Timer::Ptr l_HttpServerConnectionTimeoutTimer; HttpServerConnection::HttpServerConnection(const String& identity, bool authenticated, const TlsStream::Ptr& stream) : m_Stream(stream), m_Seen(Utility::GetTime()), m_CurrentRequest(stream), m_PendingRequests(0) { boost::call_once(l_HttpServerConnectionOnceFlag, &HttpServerConnection::StaticInitialize); m_RequestQueue.SetName("HttpServerConnection"); if (authenticated) m_ApiUser = ApiUser::GetByClientCN(identity); } void HttpServerConnection::StaticInitialize() { l_HttpServerConnectionTimeoutTimer = new Timer(); l_HttpServerConnectionTimeoutTimer->OnTimerExpired.connect(std::bind(&HttpServerConnection::TimeoutTimerHandler)); l_HttpServerConnectionTimeoutTimer->SetInterval(5); l_HttpServerConnectionTimeoutTimer->Start(); } void HttpServerConnection::Start() { /* the stream holds an owning reference to this object through the callback we're registering here */ m_Stream->RegisterDataHandler(std::bind(&HttpServerConnection::DataAvailableHandler, HttpServerConnection::Ptr(this))); if (m_Stream->IsDataAvailable()) DataAvailableHandler(); } ApiUser::Ptr HttpServerConnection::GetApiUser() const { return m_ApiUser; } TlsStream::Ptr HttpServerConnection::GetStream() const { return m_Stream; } void HttpServerConnection::Disconnect() { boost::mutex::scoped_try_lock lock(m_DataHandlerMutex); if (!lock.owns_lock()) { Log(LogInformation, "HttpServerConnection", "Unable to disconnect Http client, I/O thread busy"); return; } Log(LogDebug, "HttpServerConnection", "Http client disconnected"); ApiListener::Ptr listener = ApiListener::GetInstance(); listener->RemoveHttpClient(this); m_CurrentRequest.~HttpRequest(); new (&m_CurrentRequest) HttpRequest(nullptr); m_Stream->Close(); } bool HttpServerConnection::ProcessMessage() { bool res; HttpResponse response(m_Stream, m_CurrentRequest); if (!m_CurrentRequest.CompleteHeaders) { try { res = m_CurrentRequest.ParseHeader(m_Context, false); } catch (const std::invalid_argument& ex) { response.SetStatus(400, "Bad Request"); String msg = String("

Bad Request

") + ex.what() + "

"; response.WriteBody(msg.CStr(), msg.GetLength()); response.Finish(); m_CurrentRequest.~HttpRequest(); new (&m_CurrentRequest) HttpRequest(m_Stream); m_Stream->Shutdown(); return false; } catch (const std::exception& ex) { response.SetStatus(500, "Internal Server Error"); String msg = "

Internal Server Error

" + DiagnosticInformation(ex) + "

"; response.WriteBody(msg.CStr(), msg.GetLength()); response.Finish(); m_CurrentRequest.~HttpRequest(); new (&m_CurrentRequest) HttpRequest(m_Stream); m_Stream->Shutdown(); return false; } return res; } if (!m_CurrentRequest.CompleteHeaderCheck) { m_CurrentRequest.CompleteHeaderCheck = true; if (!ManageHeaders(response)) { m_CurrentRequest.~HttpRequest(); new (&m_CurrentRequest) HttpRequest(m_Stream); m_Stream->Shutdown(); return false; } } if (!m_CurrentRequest.CompleteBody) { try { res = m_CurrentRequest.ParseBody(m_Context, false); } catch (const std::invalid_argument& ex) { response.SetStatus(400, "Bad Request"); String msg = String("

Bad Request

") + ex.what() + "

"; response.WriteBody(msg.CStr(), msg.GetLength()); response.Finish(); m_CurrentRequest.~HttpRequest(); new (&m_CurrentRequest) HttpRequest(m_Stream); m_Stream->Shutdown(); return false; } catch (const std::exception& ex) { response.SetStatus(500, "Internal Server Error"); String msg = "

Internal Server Error

" + DiagnosticInformation(ex) + "

"; response.WriteBody(msg.CStr(), msg.GetLength()); response.Finish(); m_CurrentRequest.~HttpRequest(); new (&m_CurrentRequest) HttpRequest(m_Stream); m_Stream->Shutdown(); return false; } return res; } m_RequestQueue.Enqueue(std::bind(&HttpServerConnection::ProcessMessageAsync, HttpServerConnection::Ptr(this), m_CurrentRequest, response, m_AuthenticatedUser)); m_Seen = Utility::GetTime(); m_PendingRequests++; m_CurrentRequest.~HttpRequest(); new (&m_CurrentRequest) HttpRequest(m_Stream); return false; } bool HttpServerConnection::ManageHeaders(HttpResponse& response) { if (m_CurrentRequest.Headers->Get("expect") == "100-continue") { String continueResponse = "HTTP/1.1 100 Continue\r\n\r\n"; m_Stream->Write(continueResponse.CStr(), continueResponse.GetLength()); } /* client_cn matched. */ if (m_ApiUser) m_AuthenticatedUser = m_ApiUser; else m_AuthenticatedUser = ApiUser::GetByAuthHeader(m_CurrentRequest.Headers->Get("authorization")); String requestUrl = m_CurrentRequest.RequestUrl->Format(); Socket::Ptr socket = m_Stream->GetSocket(); Log(LogInformation, "HttpServerConnection") << "Request: " << m_CurrentRequest.RequestMethod << " " << requestUrl << " (from " << (socket ? socket->GetPeerAddress() : "") << ", user: " << (m_AuthenticatedUser ? m_AuthenticatedUser->GetName() : "") << ")"; ApiListener::Ptr listener = ApiListener::GetInstance(); if (!listener) return false; Array::Ptr headerAllowOrigin = listener->GetAccessControlAllowOrigin(); if (headerAllowOrigin->GetLength() != 0) { String origin = m_CurrentRequest.Headers->Get("origin"); { ObjectLock olock(headerAllowOrigin); for (const String& allowedOrigin : headerAllowOrigin) { if (allowedOrigin == origin) response.AddHeader("Access-Control-Allow-Origin", origin); } } if (listener->GetAccessControlAllowCredentials()) response.AddHeader("Access-Control-Allow-Credentials", "true"); String accessControlRequestMethodHeader = m_CurrentRequest.Headers->Get("access-control-request-method"); if (m_CurrentRequest.RequestMethod == "OPTIONS" && !accessControlRequestMethodHeader.IsEmpty()) { response.SetStatus(200, "OK"); response.AddHeader("Access-Control-Allow-Methods", listener->GetAccessControlAllowMethods()); response.AddHeader("Access-Control-Allow-Headers", listener->GetAccessControlAllowHeaders()); String msg = "Preflight OK"; response.WriteBody(msg.CStr(), msg.GetLength()); response.Finish(); return false; } } if (m_CurrentRequest.RequestMethod != "GET" && m_CurrentRequest.Headers->Get("accept") != "application/json") { response.SetStatus(400, "Wrong Accept header"); response.AddHeader("Content-Type", "text/html"); String msg = "

Accept header is missing or not set to 'application/json'.

"; response.WriteBody(msg.CStr(), msg.GetLength()); response.Finish(); return false; } if (!m_AuthenticatedUser) { Log(LogWarning, "HttpServerConnection") << "Unauthorized request: " << m_CurrentRequest.RequestMethod << " " << requestUrl; response.SetStatus(401, "Unauthorized"); response.AddHeader("WWW-Authenticate", "Basic realm=\"Icinga 2\""); if (m_CurrentRequest.Headers->Get("accept") == "application/json") { Dictionary::Ptr result = new Dictionary({ { "error", 401 }, { "status", "Unauthorized. Please check your user credentials." } }); HttpUtility::SendJsonBody(response, nullptr, result); } else { response.AddHeader("Content-Type", "text/html"); String msg = "

Unauthorized. Please check your user credentials.

"; response.WriteBody(msg.CStr(), msg.GetLength()); } response.Finish(); return false; } return true; } void HttpServerConnection::ProcessMessageAsync(HttpRequest& request, HttpResponse& response, const ApiUser::Ptr& user) { try { HttpHandler::ProcessRequest(user, request, response); } catch (const std::exception& ex) { Log(LogCritical, "HttpServerConnection") << "Unhandled exception while processing Http request: " << DiagnosticInformation(ex); HttpUtility::SendJsonError(response, nullptr, 503, "Unhandled exception" , DiagnosticInformation(ex)); } response.Finish(); m_PendingRequests--; } void HttpServerConnection::DataAvailableHandler() { bool close = false; if (!m_Stream->IsEof()) { boost::mutex::scoped_lock lock(m_DataHandlerMutex); try { while (ProcessMessage()) ; /* empty loop body */ } catch (const std::exception& ex) { Log(LogWarning, "HttpServerConnection") << "Error while reading Http request: " << DiagnosticInformation(ex); close = true; } } else close = true; if (close) Disconnect(); } void HttpServerConnection::CheckLiveness() { if (m_Seen < Utility::GetTime() - 10 && m_PendingRequests == 0) { Log(LogInformation, "HttpServerConnection") << "No messages for Http connection have been received in the last 10 seconds."; Disconnect(); } } void HttpServerConnection::TimeoutTimerHandler() { ApiListener::Ptr listener = ApiListener::GetInstance(); for (const HttpServerConnection::Ptr& client : listener->GetHttpClients()) { client->CheckLiveness(); } }