03b9177395e9927c18d86b746ad840b942445621
[public/netxms.git] / src / server / libnxsrv / isc.cpp
1 /*
2 ** NetXMS - Network Management System
3 ** Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008 Victor Kirhenshtein
4 **
5 ** This program is free software; you can redistribute it and/or modify
6 ** it under the terms of the GNU General Public License as published by
7 ** the Free Software Foundation; either version 2 of the License, or
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 **
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
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
37 const 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
72 THREAD_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
83 ISC::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
103 ISC::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
123 ISC::~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
152 void 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
167 void 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
271 DWORD 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
342 setup_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
381 connect_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
413 void 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
429 BOOL 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
465 DWORD 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
488 DWORD 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
545 DWORD 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
564 DWORD 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 }