Implemented file upload to agents
[public/netxms.git] / src / agent / core / session.cpp
1 /*
2 ** NetXMS multiplatform core agent
3 ** Copyright (C) 2003, 2004 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 ** $module: session.cpp
20 **
21 **/
22
23 #include "nxagentd.h"
24
25
26 //
27 // Externals
28 //
29
30 void UnregisterSession(DWORD dwIndex);
31
32
33 //
34 // Constants
35 //
36
37 #define RAW_MSG_SIZE 262144
38
39
40 //
41 // Client communication read thread
42 //
43
44 THREAD_RESULT THREAD_CALL CommSession::ReadThreadStarter(void *pArg)
45 {
46 ((CommSession *)pArg)->ReadThread();
47
48 // When CommSession::ReadThread exits, all other session
49 // threads are already stopped, so we can safely destroy
50 // session object
51 UnregisterSession(((CommSession *)pArg)->GetIndex());
52 delete (CommSession *)pArg;
53 return THREAD_OK;
54 }
55
56
57 //
58 // Client communication write thread
59 //
60
61 THREAD_RESULT THREAD_CALL CommSession::WriteThreadStarter(void *pArg)
62 {
63 ((CommSession *)pArg)->WriteThread();
64 return THREAD_OK;
65 }
66
67
68 //
69 // Received message processing thread
70 //
71
72 THREAD_RESULT THREAD_CALL CommSession::ProcessingThreadStarter(void *pArg)
73 {
74 ((CommSession *)pArg)->ProcessingThread();
75 return THREAD_OK;
76 }
77
78
79 //
80 // Client session class constructor
81 //
82
83 CommSession::CommSession(SOCKET hSocket, DWORD dwHostAddr, BOOL bInstallationServer)
84 {
85 m_pSendQueue = new Queue;
86 m_pMessageQueue = new Queue;
87 m_hSocket = hSocket;
88 m_dwIndex = INVALID_INDEX;
89 m_pMsgBuffer = (CSCP_BUFFER *)malloc(sizeof(CSCP_BUFFER));
90 m_hWriteThread = INVALID_THREAD_HANDLE;
91 m_hProcessingThread = INVALID_THREAD_HANDLE;
92 m_dwHostAddr = dwHostAddr;
93 m_bIsAuthenticated = (g_dwFlags & AF_REQUIRE_AUTH) ? FALSE : TRUE;
94 m_bInstallationServer = bInstallationServer;
95 m_hCurrFile = -1;
96 }
97
98
99 //
100 // Destructor
101 //
102
103 CommSession::~CommSession()
104 {
105 shutdown(m_hSocket, 2);
106 closesocket(m_hSocket);
107 delete m_pSendQueue;
108 delete m_pMessageQueue;
109 safe_free(m_pMsgBuffer);
110 if (m_hCurrFile != -1)
111 close(m_hCurrFile);
112 }
113
114
115 //
116 // Start all threads
117 //
118
119 void CommSession::Run(void)
120 {
121 m_hWriteThread = ThreadCreateEx(WriteThreadStarter, 0, this);
122 m_hProcessingThread = ThreadCreateEx(ProcessingThreadStarter, 0, this);
123 ThreadCreate(ReadThreadStarter, 0, this);
124 }
125
126
127 //
128 // Reading thread
129 //
130
131 void CommSession::ReadThread(void)
132 {
133 CSCP_MESSAGE *pRawMsg;
134 CSCPMessage *pMsg;
135 int iErr;
136 char szBuffer[256];
137 WORD wFlags;
138
139 // Initialize raw message receiving function
140 RecvCSCPMessage(0, NULL, m_pMsgBuffer, 0);
141
142 pRawMsg = (CSCP_MESSAGE *)malloc(RAW_MSG_SIZE);
143 while(1)
144 {
145 if ((iErr = RecvCSCPMessage(m_hSocket, pRawMsg, m_pMsgBuffer, RAW_MSG_SIZE)) <= 0)
146 break;
147
148 // Check if message is too large
149 if (iErr == 1)
150 continue;
151
152 // Check that actual received packet size is equal to encoded in packet
153 if ((int)ntohl(pRawMsg->dwSize) != iErr)
154 {
155 DebugPrintf("Actual message size doesn't match wSize value (%d,%d)", iErr, ntohl(pRawMsg->dwSize));
156 continue; // Bad packet, wait for next
157 }
158
159 wFlags = ntohs(pRawMsg->wFlags);
160 if (wFlags & MF_BINARY)
161 {
162 // Convert message header to host format
163 pRawMsg->dwId = ntohl(pRawMsg->dwId);
164 pRawMsg->wCode = ntohs(pRawMsg->wCode);
165 pRawMsg->dwNumVars = ntohl(pRawMsg->dwNumVars);
166 DebugPrintf("Received raw message %s", CSCPMessageCodeName(pRawMsg->wCode, szBuffer));
167
168 if (pRawMsg->wCode == CMD_FILE_DATA)
169 {
170 if ((m_hCurrFile != -1) && (m_dwFileRqId == pRawMsg->dwId))
171 {
172 if (write(m_hCurrFile, pRawMsg->df, pRawMsg->dwNumVars) == (int)pRawMsg->dwNumVars)
173 {
174 if (wFlags & MF_EOF)
175 {
176 CSCPMessage msg;
177
178 close(m_hCurrFile);
179 m_hCurrFile = -1;
180
181 msg.SetCode(CMD_REQUEST_COMPLETED);
182 msg.SetId(pRawMsg->dwId);
183 msg.SetVariable(VID_RCC, ERR_SUCCESS);
184 SendMessage(&msg);
185 }
186 }
187 else
188 {
189 // I/O error
190 CSCPMessage msg;
191
192 close(m_hCurrFile);
193 m_hCurrFile = -1;
194
195 msg.SetCode(CMD_REQUEST_COMPLETED);
196 msg.SetId(pRawMsg->dwId);
197 msg.SetVariable(VID_RCC, ERR_IO_FAILURE);
198 SendMessage(&msg);
199 }
200 }
201 }
202 }
203 else
204 {
205 // Create message object from raw message
206 pMsg = new CSCPMessage(pRawMsg);
207 m_pMessageQueue->Put(pMsg);
208 }
209 }
210 if (iErr < 0)
211 WriteLog(MSG_SESSION_BROKEN, EVENTLOG_WARNING_TYPE, "e", WSAGetLastError());
212 free(pRawMsg);
213
214 // Notify other threads to exit
215 m_pSendQueue->Put(INVALID_POINTER_VALUE);
216 m_pMessageQueue->Put(INVALID_POINTER_VALUE);
217
218 // Wait for other threads to finish
219 ThreadJoin(m_hWriteThread);
220 ThreadJoin(m_hProcessingThread);
221
222 DebugPrintf("Session with %s closed", IpToStr(m_dwHostAddr, szBuffer));
223 }
224
225
226 //
227 // Writing thread
228 //
229
230 void CommSession::WriteThread(void)
231 {
232 CSCP_MESSAGE *pMsg;
233 char szBuffer[128];
234
235 while(1)
236 {
237 pMsg = (CSCP_MESSAGE *)m_pSendQueue->GetOrBlock();
238 if (pMsg == INVALID_POINTER_VALUE) // Session termination indicator
239 break;
240
241 DebugPrintf("Sending message %s", CSCPMessageCodeName(ntohs(pMsg->wCode), szBuffer));
242 if (send(m_hSocket, (const char *)pMsg, ntohl(pMsg->dwSize), 0) <= 0)
243 {
244 free(pMsg);
245 break;
246 }
247 free(pMsg);
248 }
249 }
250
251
252 //
253 // Message processing thread
254 //
255
256 void CommSession::ProcessingThread(void)
257 {
258 CSCPMessage *pMsg;
259 char szBuffer[128];
260 CSCPMessage msg;
261
262 while(1)
263 {
264 pMsg = (CSCPMessage *)m_pMessageQueue->GetOrBlock();
265 if (pMsg == INVALID_POINTER_VALUE) // Session termination indicator
266 break;
267 DebugPrintf("Received message %s", CSCPMessageCodeName(pMsg->GetCode(), szBuffer));
268
269 // Prepare responce message
270 msg.SetCode(CMD_REQUEST_COMPLETED);
271 msg.SetId(pMsg->GetId());
272
273 // Check if authentication required
274 if ((!m_bIsAuthenticated) && (pMsg->GetCode() != CMD_AUTHENTICATE))
275 {
276 msg.SetVariable(VID_RCC, ERR_AUTH_REQUIRED);
277 }
278 else
279 {
280 switch(pMsg->GetCode())
281 {
282 case CMD_AUTHENTICATE:
283 Authenticate(pMsg, &msg);
284 break;
285 case CMD_GET_PARAMETER:
286 GetParameter(pMsg, &msg);
287 break;
288 case CMD_GET_LIST:
289 GetList(pMsg, &msg);
290 break;
291 case CMD_KEEPALIVE:
292 msg.SetVariable(VID_RCC, ERR_SUCCESS);
293 break;
294 case CMD_ACTION:
295 Action(pMsg, &msg);
296 break;
297 case CMD_TRANSFER_FILE:
298 RecvFile(pMsg, &msg);
299 break;
300 default:
301 msg.SetVariable(VID_RCC, ERR_UNKNOWN_COMMAND);
302 break;
303 }
304 }
305 delete pMsg;
306
307 // Send responce
308 SendMessage(&msg);
309 msg.DeleteAllVariables();
310 }
311 }
312
313
314 //
315 // Authenticate peer
316 //
317
318 void CommSession::Authenticate(CSCPMessage *pRequest, CSCPMessage *pMsg)
319 {
320 if (m_bIsAuthenticated)
321 {
322 // Already authenticated
323 pMsg->SetVariable(VID_RCC, (g_dwFlags & AF_REQUIRE_AUTH) ? ERR_ALREADY_AUTHENTICATED : ERR_AUTH_NOT_REQUIRED);
324 }
325 else
326 {
327 char szSecret[MAX_SECRET_LENGTH];
328 BYTE hash[32];
329 WORD wAuthMethod;
330
331 wAuthMethod = pRequest->GetVariableShort(VID_AUTH_METHOD);
332 switch(wAuthMethod)
333 {
334 case AUTH_PLAINTEXT:
335 pRequest->GetVariableStr(VID_SHARED_SECRET, szSecret, MAX_SECRET_LENGTH);
336 if (!strcmp(szSecret, g_szSharedSecret))
337 {
338 m_bIsAuthenticated = TRUE;
339 pMsg->SetVariable(VID_RCC, ERR_SUCCESS);
340 }
341 else
342 {
343 WriteLog(MSG_AUTH_FAILED, EVENTLOG_WARNING_TYPE, "as", m_dwHostAddr, "PLAIN");
344 pMsg->SetVariable(VID_RCC, ERR_AUTH_FAILED);
345 }
346 break;
347 case AUTH_MD5_HASH:
348 pRequest->GetVariableBinary(VID_SHARED_SECRET, (BYTE *)szSecret, MD5_DIGEST_SIZE);
349 CalculateMD5Hash((BYTE *)g_szSharedSecret, strlen(g_szSharedSecret), hash);
350 if (!memcmp(szSecret, hash, MD5_DIGEST_SIZE))
351 {
352 m_bIsAuthenticated = TRUE;
353 pMsg->SetVariable(VID_RCC, ERR_SUCCESS);
354 }
355 else
356 {
357 WriteLog(MSG_AUTH_FAILED, EVENTLOG_WARNING_TYPE, "as", m_dwHostAddr, "MD5");
358 pMsg->SetVariable(VID_RCC, ERR_AUTH_FAILED);
359 }
360 break;
361 case AUTH_SHA1_HASH:
362 pRequest->GetVariableBinary(VID_SHARED_SECRET, (BYTE *)szSecret, SHA1_DIGEST_SIZE);
363 CalculateSHA1Hash((BYTE *)g_szSharedSecret, strlen(g_szSharedSecret), hash);
364 if (!memcmp(szSecret, hash, SHA1_DIGEST_SIZE))
365 {
366 m_bIsAuthenticated = TRUE;
367 pMsg->SetVariable(VID_RCC, ERR_SUCCESS);
368 }
369 else
370 {
371 WriteLog(MSG_AUTH_FAILED, EVENTLOG_WARNING_TYPE, "as", m_dwHostAddr, "SHA1");
372 pMsg->SetVariable(VID_RCC, ERR_AUTH_FAILED);
373 }
374 break;
375 default:
376 pMsg->SetVariable(VID_RCC, ERR_NOT_IMPLEMENTED);
377 break;
378 }
379 }
380 }
381
382
383 //
384 // Get parameter's value
385 //
386
387 void CommSession::GetParameter(CSCPMessage *pRequest, CSCPMessage *pMsg)
388 {
389 char szParameter[MAX_PARAM_NAME], szValue[MAX_RESULT_LENGTH];
390 DWORD dwErrorCode;
391
392 pRequest->GetVariableStr(VID_PARAMETER, szParameter, MAX_PARAM_NAME);
393 dwErrorCode = GetParameterValue(szParameter, szValue);
394 pMsg->SetVariable(VID_RCC, dwErrorCode);
395 if (dwErrorCode == ERR_SUCCESS)
396 pMsg->SetVariable(VID_VALUE, szValue);
397 }
398
399
400 //
401 // Get list of values
402 //
403
404 void CommSession::GetList(CSCPMessage *pRequest, CSCPMessage *pMsg)
405 {
406 char szParameter[MAX_PARAM_NAME];
407 DWORD i, dwErrorCode;
408 NETXMS_VALUES_LIST value;
409
410 pRequest->GetVariableStr(VID_PARAMETER, szParameter, MAX_PARAM_NAME);
411 value.dwNumStrings = 0;
412 value.ppStringList = NULL;
413
414 dwErrorCode = GetEnumValue(szParameter, &value);
415 pMsg->SetVariable(VID_RCC, dwErrorCode);
416 if (dwErrorCode == ERR_SUCCESS)
417 {
418 pMsg->SetVariable(VID_NUM_STRINGS, value.dwNumStrings);
419 for(i = 0; i < value.dwNumStrings; i++)
420 pMsg->SetVariable(VID_ENUM_VALUE_BASE + i, value.ppStringList[i]);
421 }
422
423 for(i = 0; i < value.dwNumStrings; i++)
424 safe_free(value.ppStringList[i]);
425 safe_free(value.ppStringList);
426 }
427
428
429 //
430 // Perform action on request
431 //
432
433 void CommSession::Action(CSCPMessage *pRequest, CSCPMessage *pMsg)
434 {
435 char szAction[MAX_PARAM_NAME];
436 NETXMS_VALUES_LIST args;
437 DWORD i, dwRetCode;
438
439 // Get action name and arguments
440 pRequest->GetVariableStr(VID_ACTION_NAME, szAction, MAX_PARAM_NAME);
441 args.dwNumStrings = pRequest->GetVariableLong(VID_NUM_ARGS);
442 args.ppStringList = (char **)malloc(sizeof(char *) * args.dwNumStrings);
443 for(i = 0; i < args.dwNumStrings; i++)
444 args.ppStringList[i] = pRequest->GetVariableStr(VID_ACTION_ARG_BASE + i);
445
446 // Execute action
447 dwRetCode = ExecAction(szAction, &args);
448 pMsg->SetVariable(VID_RCC, dwRetCode);
449
450 // Cleanup
451 for(i = 0; i < args.dwNumStrings; i++)
452 safe_free(args.ppStringList[i]);
453 safe_free(args.ppStringList);
454 }
455
456
457 //
458 // Prepare for receiving file
459 //
460
461 void CommSession::RecvFile(CSCPMessage *pRequest, CSCPMessage *pMsg)
462 {
463 int i;
464 size_t nLen;
465 TCHAR szFileName[MAX_PATH], szFullPath[MAX_PATH];
466
467 if (m_bInstallationServer)
468 {
469 szFileName[0] = 0;
470 pRequest->GetVariableStr(VID_FILE_NAME, szFileName, MAX_PATH);
471 DebugPrintf("Preparing for receiving file \"%s\"", szFileName);
472 for(i = _tcslen(szFileName) - 1;
473 (i >= 0) && (szFileName[i] != '\\') && (szFileName[i] != '/'); i--);
474
475 _tcscpy(szFullPath, g_szFileStore);
476 nLen = _tcslen(szFullPath);
477 if ((szFullPath[nLen - 1] != '\\') &&
478 (szFullPath[nLen - 1] != '/'))
479 {
480 _tcscat(szFullPath, FS_PATH_SEPARATOR);
481 nLen++;
482 }
483 _tcsncpy(&szFullPath[nLen], szFileName, MAX_PATH - nLen);
484
485 // Check if for some reason we have already opened file
486 if (m_hCurrFile != -1)
487 {
488 pMsg->SetVariable(VID_RCC, ERR_RESOURCE_BUSY);
489 }
490 else
491 {
492 DebugPrintf("Writing to local file \"%s\"", szFullPath);
493 m_hCurrFile = _topen(szFullPath, O_CREAT | O_TRUNC | O_WRONLY | O_BINARY, 0600);
494 if (m_hCurrFile == -1)
495 {
496 DebugPrintf("Error opening file for writing: %s", strerror(errno));
497 pMsg->SetVariable(VID_RCC, ERR_IO_FAILURE);
498 }
499 else
500 {
501 m_dwFileRqId = pRequest->GetId();
502 pMsg->SetVariable(VID_RCC, ERR_SUCCESS);
503 }
504 }
505 }
506 else
507 {
508 pMsg->SetVariable(VID_RCC, ERR_ACCESS_DENIED);
509 }
510 }