/****************************************************************************** * Icinga 2 * * Copyright (C) 2012-2017 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(void) { l_HttpServerConnectionTimeoutTimer = new Timer(); l_HttpServerConnectionTimeoutTimer->OnTimerExpired.connect(std::bind(&HttpServerConnection::TimeoutTimerHandler)); l_HttpServerConnectionTimeoutTimer->SetInterval(15); l_HttpServerConnectionTimeoutTimer->Start(); } void HttpServerConnection::Start(void) { /* 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(void) const { return m_ApiUser; } TlsStream::Ptr HttpServerConnection::GetStream(void) const { return m_Stream; } void HttpServerConnection::Disconnect(void) { 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(void) { bool res; try { res = m_CurrentRequest.Parse(m_Context, false); } catch (const std::invalid_argument& ex) { HttpResponse response(m_Stream, m_CurrentRequest); response.SetStatus(400, "Bad request"); String msg = String("

Bad request

") + ex.what() + "

"; response.WriteBody(msg.CStr(), msg.GetLength()); response.Finish(); m_Stream->Shutdown(); return false; } catch (const std::exception& ex) { HttpResponse response(m_Stream, m_CurrentRequest); response.SetStatus(400, "Bad request"); String msg = "

Bad request

" + DiagnosticInformation(ex) + "

"; response.WriteBody(msg.CStr(), msg.GetLength()); response.Finish(); m_Stream->Shutdown(); return false; } if (m_CurrentRequest.Complete) { m_RequestQueue.Enqueue(std::bind(&HttpServerConnection::ProcessMessageAsync, HttpServerConnection::Ptr(this), m_CurrentRequest)); m_Seen = Utility::GetTime(); m_PendingRequests++; m_CurrentRequest.~HttpRequest(); new (&m_CurrentRequest) HttpRequest(m_Stream); return true; } return res; } void HttpServerConnection::ProcessMessageAsync(HttpRequest& request) { String auth_header = request.Headers->Get("authorization"); String::SizeType pos = auth_header.FindFirstOf(" "); String username, password; if (pos != String::NPos && auth_header.SubStr(0, pos) == "Basic") { String credentials_base64 = auth_header.SubStr(pos + 1); String credentials = Base64::Decode(credentials_base64); String::SizeType cpos = credentials.FindFirstOf(":"); if (cpos != String::NPos) { username = credentials.SubStr(0, cpos); password = credentials.SubStr(cpos + 1); } } ApiUser::Ptr user; /* client_cn matched. */ if (m_ApiUser) user = m_ApiUser; else { user = ApiUser::GetByName(username); /* Deny authentication if 1) given password is empty 2) configured password does not match. */ if (password.IsEmpty()) user.reset(); else if (user && user->GetPassword() != password) user.reset(); } String requestUrl = request.RequestUrl->Format(); Log(LogInformation, "HttpServerConnection") << "Request: " << request.RequestMethod << " " << requestUrl << " (from " << m_Stream->GetSocket()->GetPeerAddress() << ", user: " << (user ? user->GetName() : "") << ")"; HttpResponse response(m_Stream, request); ApiListener::Ptr listener = ApiListener::GetInstance(); if (!listener) return; Array::Ptr headerAllowOrigin = listener->GetAccessControlAllowOrigin(); if (headerAllowOrigin->GetLength() != 0) { String origin = request.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 = request.Headers->Get("access-control-request-method"); if (!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(); m_PendingRequests--; return; } } String accept_header = request.Headers->Get("accept"); if (request.RequestMethod != "GET" && accept_header != "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()); } else if (!user) { Log(LogWarning, "HttpServerConnection") << "Unauthorized request: " << request.RequestMethod << " " << requestUrl; response.SetStatus(401, "Unauthorized"); response.AddHeader("WWW-Authenticate", "Basic realm=\"Icinga 2\""); if (request.Headers->Get("accept") == "application/json") { Dictionary::Ptr result = new Dictionary(); result->Set("error", 401); result->Set("status", "Unauthorized. Please check your user credentials."); HttpUtility::SendJsonBody(response, result); } else { response.AddHeader("Content-Type", "text/html"); String msg = "

Unauthorized. Please check your user credentials.

"; response.WriteBody(msg.CStr(), msg.GetLength()); } } else { try { HttpHandler::ProcessRequest(user, request, response); } catch (const std::exception& ex) { Log(LogCritical, "HttpServerConnection") << "Unhandled exception while processing Http request: " << DiagnosticInformation(ex); response.SetStatus(503, "Unhandled exception"); String errorInfo = DiagnosticInformation(ex); if (request.Headers->Get("accept") == "application/json") { Dictionary::Ptr result = new Dictionary(); result->Set("error", 503); result->Set("status", errorInfo); HttpUtility::SendJsonBody(response, result); } else { response.AddHeader("Content-Type", "text/plain"); response.WriteBody(errorInfo.CStr(), errorInfo.GetLength()); } } } response.Finish(); m_PendingRequests--; } void HttpServerConnection::DataAvailableHandler(void) { 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(void) { 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(void) { ApiListener::Ptr listener = ApiListener::GetInstance(); for (const HttpServerConnection::Ptr& client : listener->GetHttpClients()) { client->CheckLiveness(); } }