license for libnxdb and libnxsrv changed to LGPL
[public/netxms.git] / src / server / libnxsrv / isc.cpp
CommitLineData
5039dede
AK
1/*
2** NetXMS - Network Management System
0702ed69 3** Copyright (C) 2003-2010 Victor Kirhenshtein
5039dede
AK
4**
5** This program is free software; you can redistribute it and/or modify
0702ed69
VK
6** it under the terms of the GNU Lesser General Public License as published by
7** the Free Software Foundation; either version 3 of the License, or
5039dede
AK
8** (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**
0702ed69 15** You should have received a copy of the GNU Lesser General Public License
5039dede
AK
16** along with this program; if not, write to the Free Software
17** Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18**
19** File: isc.cpp
20**
21**/
22
23#include "libnxsrv.h"
24
25
26//
27// Constants
28//
29
30#define RECEIVER_BUFFER_SIZE 262144
31
32
33//
34// Texts for ISC error codes
35//
36
37const TCHAR LIBNXSRV_EXPORTABLE *ISCErrorCodeToText(DWORD code)
38{
39 static const TCHAR *errorText[] =
40 {
41 _T("Success"),
42 _T("Unknown service"),
43 _T("Request out of state"),
44 _T("Service disabled"),
45 _T("Encryption required"),
46 _T("Connection broken"),
47 _T("Already connected"),
48 _T("Socket error"),
49 _T("Connect failed"),
50 _T("Invalid or incompatible NXCP version"),
51 _T("Request timed out"),
52 _T("Command or function not implemented"),
53 _T("No suitable ciphers found"),
54 _T("Invalid public key"),
55 _T("Invalid session key"),
56 _T("Internal error"),
57 _T("Session setup failed"),
58 _T("Object not found"),
59 _T("Failed to post event")
60 };
61
62 if (code <= ISC_ERR_POST_EVENT_FAILED)
63 return errorText[code];
64 return _T("Unknown error code");
65}
66
67
68//
69// Receiver thread starter
70//
71
72THREAD_RESULT THREAD_CALL ISC::ReceiverThreadStarter(void *arg)
73{
74 ((ISC *)arg)->ReceiverThread();
75 return THREAD_OK;
76}
77
78
79//
80// Default constructor for ISC - normally shouldn't be used
81//
82
83ISC::ISC()
84{
85 m_flags = 0;
86 m_addr = inet_addr("127.0.0.1");
87 m_port = NETXMS_ISC_PORT;
88 m_socket = -1;
89 m_msgWaitQueue = new MsgWaitQueue;
90 m_requestId = 1;
91 m_hReceiverThread = INVALID_THREAD_HANDLE;
92 m_ctx = NULL;
93 m_recvTimeout = 420000; // 7 minutes
94 m_commandTimeout = 10000; // 10 seconds
95 m_mutexDataLock = MutexCreate();
96}
97
98
99//
100// Normal constructor for ISC
101//
102
103ISC::ISC(DWORD addr, WORD port)
104{
105 m_flags = 0;
106 m_addr = addr;
107 m_port = port;
108 m_socket = -1;
109 m_msgWaitQueue = new MsgWaitQueue;
110 m_requestId = 1;
111 m_hReceiverThread = INVALID_THREAD_HANDLE;
112 m_ctx = NULL;
113 m_recvTimeout = 420000; // 7 minutes
114 m_commandTimeout = 10000; // 10 seconds
115 m_mutexDataLock = MutexCreate();
116}
117
118
119//
120// Destructor
121//
122
123ISC::~ISC()
124{
125 // Disconnect from peer
126 Disconnect();
127
128 // Wait for receiver thread termination
129 ThreadJoin(m_hReceiverThread);
130
131 // Close socket if active
132 Lock();
133 if (m_socket != -1)
134 {
135 closesocket(m_socket);
136 m_socket = -1;
137 }
138 Unlock();
139
140 delete m_msgWaitQueue;
141 DestroyEncryptionContext(m_ctx);
142
143 MutexDestroy(m_mutexDataLock);
144}
145
146
147//
148// Print message. This function is virtual and can be overrided in
149// derived classes. Default implementation will print message to stdout.
150//
151
152void ISC::PrintMsg(const TCHAR *format, ...)
153{
154 va_list args;
155
156 va_start(args, format);
157 _vtprintf(format, args);
158 va_end(args);
159 _tprintf(_T("\n"));
160}
161
162
163//
164// Receiver thread
165//
166
167void ISC::ReceiverThread(void)
168{
169 CSCPMessage *pMsg;
170 CSCP_MESSAGE *pRawMsg;
171 CSCP_BUFFER *pMsgBuffer;
172 BYTE *pDecryptionBuffer = NULL;
173 int err;
174 TCHAR szBuffer[128], szIpAddr[16];
175 SOCKET nSocket;
176
177 // Initialize raw message receiving function
178 pMsgBuffer = (CSCP_BUFFER *)malloc(sizeof(CSCP_BUFFER));
179 RecvNXCPMessage(0, NULL, pMsgBuffer, 0, NULL, NULL, 0);
180
181 // Allocate space for raw message
182 pRawMsg = (CSCP_MESSAGE *)malloc(RECEIVER_BUFFER_SIZE);
183#ifdef _WITH_ENCRYPTION
184 pDecryptionBuffer = (BYTE *)malloc(RECEIVER_BUFFER_SIZE);
185#endif
186
187 // Message receiving loop
188 while(1)
189 {
190 // Receive raw message
191 Lock();
192 nSocket = m_socket;
193 Unlock();
194 if ((err = RecvNXCPMessage(nSocket, pRawMsg, pMsgBuffer, RECEIVER_BUFFER_SIZE,
195 &m_ctx, pDecryptionBuffer, m_recvTimeout)) <= 0)
196 {
197 PrintMsg(_T("ISC::ReceiverThread(): RecvNXCPMessage() failed: error=%d, socket_error=%d"), err, WSAGetLastError());
198 break;
199 }
200
201 // Check if we get too large message
202 if (err == 1)
203 {
204 PrintMsg(_T("Received too large message %s (%d bytes)"),
205 NXCPMessageCodeName(ntohs(pRawMsg->wCode), szBuffer),
206 ntohl(pRawMsg->dwSize));
207 continue;
208 }
209
210 // Check if we are unable to decrypt message
211 if (err == 2)
212 {
213 PrintMsg(_T("Unable to decrypt received message"));
214 continue;
215 }
216
217 // Check for timeout
218 if (err == 3)
219 {
220 PrintMsg(_T("Timed out waiting for message"));
221 break;
222 }
223
224 // Check that actual received packet size is equal to encoded in packet
225 if ((int)ntohl(pRawMsg->dwSize) != err)
226 {
227 PrintMsg(_T("RecvMsg: Bad packet length [dwSize=%d ActualSize=%d]"), ntohl(pRawMsg->dwSize), err);
228 continue; // Bad packet, wait for next
229 }
230
231 if (ntohs(pRawMsg->wFlags) & MF_BINARY)
232 {
233 // Convert message header to host format
234 pRawMsg->dwId = ntohl(pRawMsg->dwId);
235 pRawMsg->wCode = ntohs(pRawMsg->wCode);
236 pRawMsg->dwNumVars = ntohl(pRawMsg->dwNumVars);
237 DbgPrintf(6, "ISC: Received raw message %s from peer at %s",
238 NXCPMessageCodeName(pRawMsg->wCode, szBuffer), IpToStr(m_addr, szIpAddr));
239 }
240 else
241 {
242 // Create message object from raw message
243 pMsg = new CSCPMessage(pRawMsg, m_protocolVersion);
244 m_msgWaitQueue->Put(pMsg);
245 }
246 }
247
248 // Close socket and mark connection as disconnected
249 Lock();
250 if (err == 0)
251 shutdown(m_socket, SHUT_RDWR);
252 closesocket(m_socket);
253 m_socket = -1;
254 DestroyEncryptionContext(m_ctx);
255 m_ctx = NULL;
256 m_flags &= ~ISCF_IS_CONNECTED;;
257 Unlock();
258
259 free(pRawMsg);
260 free(pMsgBuffer);
261#ifdef _WITH_ENCRYPTION
262 free(pDecryptionBuffer);
263#endif
264}
265
266
267//
268// Connect to ISC peer
269//
270
271DWORD ISC::Connect(DWORD service, RSA *pServerKey, BOOL requireEncryption)
272{
273 struct sockaddr_in sa;
274 TCHAR szBuffer[256];
275 BOOL bForceEncryption = FALSE, bSecondPass = FALSE;
276 DWORD rcc = ISC_ERR_INTERNAL_ERROR;
277
278 // Check if already connected
279 if (m_flags & ISCF_IS_CONNECTED)
280 return ISC_ERR_ALREADY_CONNECTED;
281
282 if (requireEncryption)
283 m_flags |= ISCF_REQUIRE_ENCRYPTION;
284 else
285 m_flags &= ~ISCF_REQUIRE_ENCRYPTION;
286
287 // Wait for receiver thread from previous connection, if any
288 ThreadJoin(m_hReceiverThread);
289 m_hReceiverThread = INVALID_THREAD_HANDLE;
290
291 // Check if we need to close existing socket
292 if (m_socket != -1)
293 closesocket(m_socket);
294
295 // Create socket
296 m_socket = socket(AF_INET, SOCK_STREAM, 0);
297 if (m_socket == -1)
298 {
299 rcc = ISC_ERR_SOCKET_ERROR;
300 PrintMsg(_T("ISC: Call to socket() failed"));
301 goto connect_cleanup;
302 }
303
304 // Fill in address structure
305 memset(&sa, 0, sizeof(sa));
306 sa.sin_family = AF_INET;
307 sa.sin_addr.s_addr = m_addr;
308 sa.sin_port = htons(m_port);
309
310 // Connect to server
311 if (connect(m_socket, (struct sockaddr *)&sa, sizeof(sa)) == -1)
312 {
313 rcc = ISC_ERR_CONNECT_FAILED;
314 PrintMsg(_T("Cannot establish connection with ISC peer %s"),
315 IpToStr(ntohl(m_addr), szBuffer));
316 goto connect_cleanup;
317 }
318
319 // Set non-blocking mode
320 SetSocketNonBlocking(m_socket);
321
322 if (!NXCPGetPeerProtocolVersion(m_socket, &m_protocolVersion))
323 {
324 rcc = ISC_ERR_INVALID_NXCP_VERSION;
325 PrintMsg(_T("Cannot detect NXCP version for ISC peer %s"),
326 IpToStr(ntohl(m_addr), szBuffer));
327 goto connect_cleanup;
328 }
329
330 if (m_protocolVersion > NXCP_VERSION)
331 {
332 rcc = ISC_ERR_INVALID_NXCP_VERSION;
333 PrintMsg(_T("ISC peer %s uses incompatible NXCP version %d"),
334 IpToStr(ntohl(m_addr), szBuffer), m_protocolVersion);
335 goto connect_cleanup;
336 }
337
338 // Start receiver thread
339 m_hReceiverThread = ThreadCreateEx(ReceiverThreadStarter, 0, this);
340
341 // Setup encryption
342setup_encryption:
343 if (pServerKey != NULL)
344 {
345 rcc = SetupEncryption(pServerKey);
346 if ((rcc != ERR_SUCCESS) &&
347 (m_flags & ISCF_REQUIRE_ENCRYPTION))
348 {
349 PrintMsg(_T("Cannot setup encrypted channel with ISC peer %s"),
350 IpToStr(ntohl(m_addr), szBuffer));
351 goto connect_cleanup;
352 }
353 }
354 else
355 {
356 if (m_flags & ISCF_REQUIRE_ENCRYPTION)
357 {
358 rcc = ISC_ERR_ENCRYPTION_REQUIRED;
359 PrintMsg(_T("Cannot setup encrypted channel with ISC peer %s"),
360 IpToStr(ntohl(m_addr), szBuffer));
361 goto connect_cleanup;
362 }
363 }
364
365 // Test connectivity
366 m_flags |= ISCF_IS_CONNECTED;
367 if ((rcc = Nop()) != ERR_SUCCESS)
368 {
369 if (rcc == ISC_ERR_ENCRYPTION_REQUIRED)
370 {
371 m_flags |= ISCF_REQUIRE_ENCRYPTION;
372 goto setup_encryption;
373 }
374 PrintMsg(_T("Communication with ISC peer %s failed (%s)"), IpToStr(ntohl(m_addr), szBuffer),
375 ISCErrorCodeToText(rcc));
376 goto connect_cleanup;
377 }
378
379 rcc = ConnectToService(service);
380
381connect_cleanup:
382 if (rcc != ISC_ERR_SUCCESS)
383 {
384 Lock();
385 m_flags &= ~ISCF_IS_CONNECTED;
386 if (m_socket != -1)
387 shutdown(m_socket, SHUT_RDWR);
388 Unlock();
389 ThreadJoin(m_hReceiverThread);
390 m_hReceiverThread = INVALID_THREAD_HANDLE;
391
392 Lock();
393 if (m_socket != -1)
394 {
395 closesocket(m_socket);
396 m_socket = -1;
397 }
398
399 DestroyEncryptionContext(m_ctx);
400 m_ctx = NULL;
401
402 Unlock();
403 }
404
405 return rcc;
406}
407
408
409//
410// Disconnect from ISC peer
411//
412
413void ISC::Disconnect()
414{
415 Lock();
416 if (m_socket != -1)
417 {
418 shutdown(m_socket, SHUT_RDWR);
419 m_flags &= ~ISCF_IS_CONNECTED;;
420 }
421 Unlock();
422}
423
424
425//
426// Send message to peer
427//
428
429BOOL ISC::SendMessage(CSCPMessage *pMsg)
430{
431 CSCP_MESSAGE *pRawMsg;
432 CSCP_ENCRYPTED_MESSAGE *pEnMsg;
433 BOOL bResult;
434
435 if (!(m_flags & ISCF_IS_CONNECTED))
436 return FALSE;
437
438 pRawMsg = pMsg->CreateMessage();
439 if (m_ctx != NULL)
440 {
441 pEnMsg = CSCPEncryptMessage(m_ctx, pRawMsg);
442 if (pEnMsg != NULL)
443 {
444 bResult = (SendEx(m_socket, (char *)pEnMsg, ntohl(pEnMsg->dwSize), 0) == (int)ntohl(pEnMsg->dwSize));
445 free(pEnMsg);
446 }
447 else
448 {
449 bResult = FALSE;
450 }
451 }
452 else
453 {
454 bResult = (SendEx(m_socket, (char *)pRawMsg, ntohl(pRawMsg->dwSize), 0) == (int)ntohl(pRawMsg->dwSize));
455 }
456 free(pRawMsg);
457 return bResult;
458}
459
460
461//
462// Wait for request completion code
463//
464
465DWORD ISC::WaitForRCC(DWORD rqId, DWORD timeOut)
466{
467 CSCPMessage *pMsg;
468 DWORD dwRetCode;
469
470 pMsg = m_msgWaitQueue->WaitForMessage(CMD_REQUEST_COMPLETED, rqId, timeOut);
471 if (pMsg != NULL)
472 {
473 dwRetCode = pMsg->GetVariableLong(VID_RCC);
474 delete pMsg;
475 }
476 else
477 {
478 dwRetCode = ISC_ERR_REQUEST_TIMEOUT;
479 }
480 return dwRetCode;
481}
482
483
484//
485// Setup encryption
486//
487
488DWORD ISC::SetupEncryption(RSA *pServerKey)
489{
490#ifdef _WITH_ENCRYPTION
491 CSCPMessage msg(m_protocolVersion), *pResp;
492 DWORD dwRqId, dwError, dwResult;
493
494 dwRqId = m_requestId++;
495
496 PrepareKeyRequestMsg(&msg, pServerKey);
497 msg.SetId(dwRqId);
498 if (SendMessage(&msg))
499 {
500 pResp = WaitForMessage(CMD_SESSION_KEY, dwRqId, m_commandTimeout);
501 if (pResp != NULL)
502 {
503 dwResult = SetupEncryptionContext(pResp, &m_ctx, NULL, pServerKey, m_protocolVersion);
504 switch(dwResult)
505 {
506 case RCC_SUCCESS:
507 dwError = ISC_ERR_SUCCESS;
508 break;
509 case RCC_NO_CIPHERS:
510 dwError = ISC_ERR_NO_CIPHERS;
511 break;
512 case RCC_INVALID_PUBLIC_KEY:
513 dwError = ISC_ERR_INVALID_PUBLIC_KEY;
514 break;
515 case RCC_INVALID_SESSION_KEY:
516 dwError = ISC_ERR_INVALID_SESSION_KEY;
517 break;
518 default:
519 dwError = ISC_ERR_INTERNAL_ERROR;
520 break;
521 }
522 delete pResp;
523 }
524 else
525 {
526 dwError = ISC_ERR_REQUEST_TIMEOUT;
527 }
528 }
529 else
530 {
531 dwError = ISC_ERR_CONNECTION_BROKEN;
532 }
533
534 return dwError;
535#else
536 return ISC_ERR_NOT_IMPLEMENTED;
537#endif
538}
539
540
541//
542// Send dummy command to peer (can be used for keepalive)
543//
544
545DWORD ISC::Nop(void)
546{
547 CSCPMessage msg(m_protocolVersion);
548 DWORD dwRqId;
549
550 dwRqId = m_requestId++;
551 msg.SetCode(CMD_KEEPALIVE);
552 msg.SetId(dwRqId);
553 if (SendMessage(&msg))
554 return WaitForRCC(dwRqId, m_commandTimeout);
555 else
556 return ISC_ERR_CONNECTION_BROKEN;
557}
558
559
560//
561// Connect to requested service
562//
563
564DWORD ISC::ConnectToService(DWORD service)
565{
566 CSCPMessage msg(m_protocolVersion);
567 DWORD dwRqId;
568
569 dwRqId = m_requestId++;
570 msg.SetCode(CMD_ISC_CONNECT_TO_SERVICE);
571 msg.SetId(dwRqId);
572 msg.SetVariable(VID_SERVICE_ID, service);
573 if (SendMessage(&msg))
574 return WaitForRCC(dwRqId, m_commandTimeout);
575 else
576 return ISC_ERR_CONNECTION_BROKEN;
577}