]> granicus.if.org Git - icinga2/blob - lib/remote/apiclient.cpp
Make sure we don't include zones.d directories for zones which were removed
[icinga2] / lib / remote / apiclient.cpp
1 /******************************************************************************
2  * Icinga 2                                                                   *
3  * Copyright (C) 2012-2014 Icinga Development Team (http://www.icinga.org)    *
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/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"
29
30 using namespace icinga;
31
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);
36
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()),
39           m_NextHeartbeat(0)
40 {
41         if (authenticated)
42                 m_Endpoint = Endpoint::GetByName(identity);
43 }
44
45 void ApiClient::Start(void)
46 {
47         boost::thread thread(boost::bind(&ApiClient::MessageThreadProc, ApiClient::Ptr(this)));
48         thread.detach();
49 }
50
51 String ApiClient::GetIdentity(void) const
52 {
53         return m_Identity;
54 }
55
56 bool ApiClient::IsAuthenticated(void) const
57 {
58         return m_Authenticated;
59 }
60
61 Endpoint::Ptr ApiClient::GetEndpoint(void) const
62 {
63         return m_Endpoint;
64 }
65
66 TlsStream::Ptr ApiClient::GetStream(void) const
67 {
68         return m_Stream;
69 }
70
71 ConnectionRole ApiClient::GetRole(void) const
72 {
73         return m_Role;
74 }
75
76 void ApiClient::SendMessage(const Dictionary::Ptr& message)
77 {
78         if (m_WriteQueue.GetLength() > 20000) {
79                 Log(LogWarning, "remote")
80                     << "Closing connection for API identity '" << m_Identity << "': Too many queued messages.";
81                 Disconnect();
82                 return;
83         }
84
85         m_WriteQueue.Enqueue(boost::bind(&ApiClient::SendMessageSync, ApiClient::Ptr(this), message));
86 }
87
88 void ApiClient::SendMessageSync(const Dictionary::Ptr& message)
89 {
90         try {
91                 ObjectLock olock(m_Stream);
92                 if (m_Stream->IsEof())
93                         return;
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")
101                     << info.str();
102                 Log(LogDebug, "ApiClient")
103                     << info.str() << "\n" << DiagnosticInformation(ex);
104
105                 Disconnect();
106         }
107 }
108
109 void ApiClient::Disconnect(void)
110 {
111         Utility::QueueAsyncCallback(boost::bind(&ApiClient::DisconnectSync, ApiClient::Ptr(this)));
112 }
113
114 void ApiClient::DisconnectSync(void)
115 {
116         Log(LogWarning, "ApiClient")
117             << "API client disconnected for identity '" << m_Identity << "'";
118
119         if (m_Endpoint)
120                 m_Endpoint->RemoveClient(this);
121         else {
122                 ApiListener::Ptr listener = ApiListener::GetInstance();
123                 listener->RemoveAnonymousClient(this);
124         }
125
126         m_Stream->Close();
127 }
128
129 bool ApiClient::ProcessMessage(void)
130 {
131         Dictionary::Ptr message;
132
133         if (m_Stream->IsEof())
134                 return false;
135
136         try {
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);
140
141                 if (pe && *pe == 0)
142                         return false; /* Connection was closed cleanly */
143
144                 throw;
145         }
146
147         if (!message)
148                 return false;
149
150         if (message->Get("method") != "log::SetLogPosition")
151                 m_Seen = Utility::GetTime();
152
153         if (m_Endpoint && message->Contains("ts")) {
154                 double ts = message->Get("ts");
155
156                 /* ignore old messages */
157                 if (ts < m_Endpoint->GetRemoteLogPosition())
158                         return true;
159
160                 m_Endpoint->SetRemoteLogPosition(ts);
161         }
162
163         MessageOrigin origin;
164         origin.FromClient = this;
165
166         if (m_Endpoint) {
167                 if (m_Endpoint->GetZone() != Zone::GetLocalZone())
168                         origin.FromZone = m_Endpoint->GetZone();
169                 else
170                         origin.FromZone = Zone::GetByName(message->Get("originZone"));
171         }
172
173         String method = message->Get("method");
174
175         Log(LogNotice, "ApiClient")
176             << "Received '" << method << "' message from '" << m_Identity << "'";
177
178         Dictionary::Ptr resultMessage = new Dictionary();
179
180         try {
181                 ApiFunction::Ptr afunc = ApiFunction::GetByName(method);
182
183                 if (!afunc)
184                         BOOST_THROW_EXCEPTION(std::invalid_argument("Function '" + method + "' does not exist."));
185
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")
193                     << info.str();
194                 Log(LogDebug, "ApiClient")
195                     << info.str() << "\n" << DiagnosticInformation(ex);
196         }
197
198         if (message->Contains("id")) {
199                 resultMessage->Set("jsonrpc", "2.0");
200                 resultMessage->Set("id", message->Get("id"));
201                 JsonRpc::SendMessage(m_Stream, resultMessage);
202         }
203
204         return true;
205 }
206
207 void ApiClient::MessageThreadProc(void)
208 {
209         Utility::SetThreadName("API Client");
210
211         try {
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);
217         }
218
219         Disconnect();
220 }
221
222 Value SetLogPositionHandler(const MessageOrigin& origin, const Dictionary::Ptr& params)
223 {
224         if (!params)
225                 return Empty;
226
227         double log_position = params->Get("log_position");
228         Endpoint::Ptr endpoint = origin.FromClient->GetEndpoint();
229
230         if (!endpoint)
231                 return Empty;
232
233         if (log_position > endpoint->GetLocalLogPosition())
234                 endpoint->SetLocalLogPosition(log_position);
235
236         return Empty;
237 }
238
239 Value RequestCertificateHandler(const MessageOrigin& origin, const Dictionary::Ptr& params)
240 {
241         if (!params)
242                 return Empty;
243
244         ApiListener::Ptr listener = ApiListener::GetInstance();
245         String salt = listener->GetTicketSalt();
246
247         Dictionary::Ptr result = new Dictionary();
248
249         if (salt.IsEmpty()) {
250                 result->Set("error", "Ticket salt is not configured.");
251                 return result;
252         }
253
254         String ticket = params->Get("ticket");
255         String realTicket = PBKDF2_SHA1(origin.FromClient->GetIdentity(), salt, 50000);
256
257         if (ticket != realTicket) {
258                 result->Set("error", "Invalid ticket.");
259                 return result;
260         }
261
262         boost::shared_ptr<X509> cert = origin.FromClient->GetStream()->GetPeerCertificate();
263
264         EVP_PKEY *pubkey = X509_get_pubkey(cert.get());
265         X509_NAME *subject = X509_get_subject_name(cert.get());
266
267         boost::shared_ptr<X509> newcert = CreateCertIcingaCA(pubkey, subject);
268         result->Set("cert", CertificateToString(newcert));
269
270         String cacertfile = GetIcingaCADir() + "/ca.crt";
271         boost::shared_ptr<X509> cacert = GetX509Certificate(cacertfile);
272         result->Set("ca", CertificateToString(cacert));
273
274         return result;
275 }