1 /******************************************************************************
3 * Copyright (C) 2012-2014 Icinga Development Team (http://www.icinga.org) *
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/apiclient.hpp"
21 #include "remote/apilistener.hpp"
22 #include "remote/apifunction.hpp"
23 #include "remote/jsonrpc.hpp"
24 #include "base/dynamictype.hpp"
25 #include "base/objectlock.hpp"
26 #include "base/utility.hpp"
27 #include "base/logger.hpp"
28 #include "base/exception.hpp"
30 using namespace icinga;
32 static Value SetLogPositionHandler(const MessageOrigin& origin, const Dictionary::Ptr& params);
33 REGISTER_APIFUNCTION(SetLogPosition, log, &SetLogPositionHandler);
34 static Value RequestCertificateHandler(const MessageOrigin& origin, const Dictionary::Ptr& params);
35 REGISTER_APIFUNCTION(RequestCertificate, pki, &RequestCertificateHandler);
37 ApiClient::ApiClient(const String& identity, bool authenticated, const TlsStream::Ptr& stream, ConnectionRole role)
38 : m_Identity(identity), m_Authenticated(authenticated), m_Stream(stream), m_Role(role), m_Seen(Utility::GetTime()),
42 m_Endpoint = Endpoint::GetByName(identity);
45 void ApiClient::Start(void)
47 boost::thread thread(boost::bind(&ApiClient::MessageThreadProc, ApiClient::Ptr(this)));
51 String ApiClient::GetIdentity(void) const
56 bool ApiClient::IsAuthenticated(void) const
58 return m_Authenticated;
61 Endpoint::Ptr ApiClient::GetEndpoint(void) const
66 TlsStream::Ptr ApiClient::GetStream(void) const
71 ConnectionRole ApiClient::GetRole(void) const
76 void ApiClient::SendMessage(const Dictionary::Ptr& message)
78 if (m_WriteQueue.GetLength() > 20000) {
79 Log(LogWarning, "remote")
80 << "Closing connection for API identity '" << m_Identity << "': Too many queued messages.";
85 m_WriteQueue.Enqueue(boost::bind(&ApiClient::SendMessageSync, ApiClient::Ptr(this), message));
88 void ApiClient::SendMessageSync(const Dictionary::Ptr& message)
91 ObjectLock olock(m_Stream);
92 if (m_Stream->IsEof())
94 JsonRpc::SendMessage(m_Stream, message);
95 if (message->Get("method") != "log::SetLogPosition")
96 m_Seen = Utility::GetTime();
97 } catch (const std::exception& ex) {
98 std::ostringstream info;
99 info << "Error while sending JSON-RPC message for identity '" << m_Identity << "'";
100 Log(LogWarning, "ApiClient")
102 Log(LogDebug, "ApiClient")
103 << info.str() << "\n" << DiagnosticInformation(ex);
109 void ApiClient::Disconnect(void)
111 Utility::QueueAsyncCallback(boost::bind(&ApiClient::DisconnectSync, ApiClient::Ptr(this)));
114 void ApiClient::DisconnectSync(void)
116 Log(LogWarning, "ApiClient")
117 << "API client disconnected for identity '" << m_Identity << "'";
120 m_Endpoint->RemoveClient(this);
122 ApiListener::Ptr listener = ApiListener::GetInstance();
123 listener->RemoveAnonymousClient(this);
129 bool ApiClient::ProcessMessage(void)
131 Dictionary::Ptr message;
133 if (m_Stream->IsEof())
137 message = JsonRpc::ReadMessage(m_Stream);
138 } catch (const openssl_error& ex) {
139 const unsigned long *pe = boost::get_error_info<errinfo_openssl_error>(ex);
142 return false; /* Connection was closed cleanly */
150 if (message->Get("method") != "log::SetLogPosition")
151 m_Seen = Utility::GetTime();
153 if (m_Endpoint && message->Contains("ts")) {
154 double ts = message->Get("ts");
156 /* ignore old messages */
157 if (ts < m_Endpoint->GetRemoteLogPosition())
160 m_Endpoint->SetRemoteLogPosition(ts);
163 MessageOrigin origin;
164 origin.FromClient = this;
167 if (m_Endpoint->GetZone() != Zone::GetLocalZone())
168 origin.FromZone = m_Endpoint->GetZone();
170 origin.FromZone = Zone::GetByName(message->Get("originZone"));
173 String method = message->Get("method");
175 Log(LogNotice, "ApiClient")
176 << "Received '" << method << "' message from '" << m_Identity << "'";
178 Dictionary::Ptr resultMessage = new Dictionary();
181 ApiFunction::Ptr afunc = ApiFunction::GetByName(method);
184 BOOST_THROW_EXCEPTION(std::invalid_argument("Function '" + method + "' does not exist."));
186 resultMessage->Set("result", afunc->Invoke(origin, message->Get("params")));
187 } catch (const std::exception& ex) {
188 //TODO: Add a user readable error message for the remote caller
189 resultMessage->Set("error", DiagnosticInformation(ex));
190 std::ostringstream info;
191 info << "Error while processing message for identity '" << m_Identity << "'";
192 Log(LogWarning, "ApiClient")
194 Log(LogDebug, "ApiClient")
195 << info.str() << "\n" << DiagnosticInformation(ex);
198 if (message->Contains("id")) {
199 resultMessage->Set("jsonrpc", "2.0");
200 resultMessage->Set("id", message->Get("id"));
201 JsonRpc::SendMessage(m_Stream, resultMessage);
207 void ApiClient::MessageThreadProc(void)
209 Utility::SetThreadName("API Client");
212 while (ProcessMessage())
213 ; /* empty loop body */
214 } catch (const std::exception& ex) {
215 Log(LogWarning, "ApiClient")
216 << "Error while reading JSON-RPC message for identity '" << m_Identity << "': " << DiagnosticInformation(ex);
222 Value SetLogPositionHandler(const MessageOrigin& origin, const Dictionary::Ptr& params)
227 double log_position = params->Get("log_position");
228 Endpoint::Ptr endpoint = origin.FromClient->GetEndpoint();
233 if (log_position > endpoint->GetLocalLogPosition())
234 endpoint->SetLocalLogPosition(log_position);
239 Value RequestCertificateHandler(const MessageOrigin& origin, const Dictionary::Ptr& params)
244 ApiListener::Ptr listener = ApiListener::GetInstance();
245 String salt = listener->GetTicketSalt();
247 Dictionary::Ptr result = new Dictionary();
249 if (salt.IsEmpty()) {
250 result->Set("error", "Ticket salt is not configured.");
254 String ticket = params->Get("ticket");
255 String realTicket = PBKDF2_SHA1(origin.FromClient->GetIdentity(), salt, 50000);
257 if (ticket != realTicket) {
258 result->Set("error", "Invalid ticket.");
262 boost::shared_ptr<X509> cert = origin.FromClient->GetStream()->GetPeerCertificate();
264 EVP_PKEY *pubkey = X509_get_pubkey(cert.get());
265 X509_NAME *subject = X509_get_subject_name(cert.get());
267 boost::shared_ptr<X509> newcert = CreateCertIcingaCA(pubkey, subject);
268 result->Set("cert", CertificateToString(newcert));
270 String cacertfile = GetIcingaCADir() + "/ca.crt";
271 boost::shared_ptr<X509> cacert = GetX509Certificate(cacertfile);
272 result->Set("ca", CertificateToString(cacert));