Version number unified to mobile console
[public/netxms.git] / src / server / core / syslogd.cpp
CommitLineData
5039dede
AK
1/*
2** NetXMS - Network Management System
e9491562 3** Copyright (C) 2003-2012 Victor Kirhenshtein
5039dede
AK
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: syslogd.cpp
20**
21**/
22
23#include "nxcore.h"
24#include <nxlog.h>
25#include <nxlpapi.h>
26
27
28//
29// Constants
30//
31
32#define MAX_SYSLOG_MSG_LEN 1024
33
34
35//
36// Queued syslog message structure
37//
38
39struct QUEUED_SYSLOG_MESSAGE
40{
41 DWORD dwSourceIP;
42 int nBytes;
43 char *psMsg;
44};
45
46
47//
48// Static data
49//
50
51static QWORD m_qwMsgId = 1;
52static Queue *m_pSyslogQueue = NULL;
53static LogParser *m_parser = NULL;
4d0c32f3 54static MUTEX m_mutexParserAccess = INVALID_MUTEX_HANDLE;
5039dede
AK
55
56
e9491562
VK
57/**
58 * Parse timestamp field
59 */
5039dede
AK
60static BOOL ParseTimeStamp(char **ppStart, int nMsgSize, int *pnPos, time_t *ptmTime)
61{
62 static char psMonth[12][5] = { "Jan ", "Feb ", "Mar ", "Apr ",
63 "May ", "Jun ", "Jul ", "Aug ",
64 "Sep ", "Oct ", "Nov ", "Dec " };
65 struct tm timestamp;
66 time_t t;
67 char szBuffer[16], *pCurr = *ppStart;
68 int i;
69
70 if (nMsgSize - *pnPos < 16)
71 return FALSE; // Timestamp cannot be shorter than 16 bytes
72
73 // Prepare local time structure
74 t = time(NULL);
75 memcpy(&timestamp, localtime(&t), sizeof(struct tm));
76
77 // Month
78 for(i = 0; i < 12; i++)
79 if (!memcmp(pCurr, psMonth[i], 4))
80 {
81 timestamp.tm_mon = i;
82 break;
83 }
84 if (i == 12)
85 return FALSE;
86 pCurr += 4;
87
88 // Day of week
89 if (isdigit(*pCurr))
90 {
91 timestamp.tm_mday = *pCurr - '0';
92 }
93 else
94 {
95 if (*pCurr != ' ')
96 return FALSE; // Invalid day of month
97 timestamp.tm_mday = 0;
98 }
99 pCurr++;
100 if (isdigit(*pCurr))
101 {
102 timestamp.tm_mday = timestamp.tm_mday * 10 + (*pCurr - '0');
103 }
104 else
105 {
106 return FALSE; // Invalid day of month
107 }
108 pCurr++;
109 if (*pCurr != ' ')
110 return FALSE;
111 pCurr++;
112
113 // HH:MM:SS
114 memcpy(szBuffer, pCurr, 8);
115 szBuffer[8] = 0;
116 if (sscanf(szBuffer, "%02d:%02d:%02d", &timestamp.tm_hour,
117 &timestamp.tm_min, &timestamp.tm_sec) != 3)
118 return FALSE; // Invalid time format
119 pCurr += 8;
120
121 // Check for Cisco variant - HH:MM:SS.nnn
122 if (*pCurr == '.')
123 {
124 pCurr++;
125 if (isdigit(*pCurr))
126 pCurr++;
127 if (isdigit(*pCurr))
128 pCurr++;
129 if (isdigit(*pCurr))
130 pCurr++;
131 }
132
133 if (*pCurr != ' ')
134 return FALSE; // Space should follow timestamp
135 pCurr++;
136
137 // Convert to system time
138 *ptmTime = mktime(&timestamp);
139 if (*ptmTime == ((time_t)-1))
140 return FALSE;
141
142 // Adjust current position
143 *pnPos += (int)(pCurr - *ppStart);
144 *ppStart = pCurr;
145 return TRUE;
146}
147
e9491562
VK
148/**
149 * Parse syslog message
150 */
e0f99bf0 151static BOOL ParseSyslogMessage(char *psMsg, int nMsgLen, NX_SYSLOG_RECORD *pRec)
5039dede
AK
152{
153 int i, nLen, nPos = 0;
154 char *pCurr = psMsg;
155
e0f99bf0 156 memset(pRec, 0, sizeof(NX_SYSLOG_RECORD));
5039dede
AK
157
158 // Parse PRI part
159 if (*psMsg == '<')
160 {
161 int nPri = 0, nCount = 0;
162
163 for(pCurr++, nPos++; isdigit(*pCurr) && (nPos < nMsgLen); pCurr++, nPos++, nCount++)
164 nPri = nPri * 10 + (*pCurr - '0');
165 if (nPos >= nMsgLen)
166 return FALSE; // Unexpected end of message
167
168 if ((*pCurr == '>') && (nCount > 0) && (nCount <4))
169 {
170 pRec->nFacility = nPri / 8;
171 pRec->nSeverity = nPri % 8;
172 pCurr++;
173 nPos++;
174 }
175 else
176 {
177 return FALSE; // Invalid message
178 }
179 }
180 else
181 {
182 // Set default PRI of 13
183 pRec->nFacility = 1;
184 pRec->nSeverity = SYSLOG_SEVERITY_NOTICE;
185 }
186
187 // Parse HEADER part
188 if (ParseTimeStamp(&pCurr, nMsgLen, &nPos, &pRec->tmTimeStamp))
189 {
190 // Hostname
5e5461a9 191 for(i = 0; (*pCurr >= 33) && (*pCurr <= 126) && (i < MAX_SYSLOG_HOSTNAME_LEN - 1) && (nPos < nMsgLen); i++, nPos++, pCurr++)
5039dede
AK
192 pRec->szHostName[i] = *pCurr;
193 if ((nPos >= nMsgLen) || (*pCurr != ' '))
194 {
195 // Not a valid hostname, assuming to be a part of message
196 pCurr -= i;
197 nPos -= i;
5e5461a9 198 pRec->szHostName[0] = 0;
5039dede
AK
199 }
200 else
201 {
202 pCurr++;
203 nPos++;
204 }
205 }
206 else
207 {
208 pRec->tmTimeStamp = time(NULL);
209 }
210
211 // Parse MSG part
212 for(i = 0; isalnum(*pCurr) && (i < MAX_SYSLOG_TAG_LEN) && (nPos < nMsgLen); i++, nPos++, pCurr++)
213 pRec->szTag[i] = *pCurr;
214 if ((i == MAX_SYSLOG_TAG_LEN) || (nPos >= nMsgLen))
215 {
216 // Too long tag, assuming that it's a part of message
217 pRec->szTag[0] = 0;
218 }
219 pCurr -= i;
220 nPos -= i;
221 nLen = min(nMsgLen - nPos, MAX_LOG_MSG_LENGTH);
222 memcpy(pRec->szMessage, pCurr, nLen);
223
224 return TRUE;
225}
226
e9491562
VK
227/**
228 * Bind syslog message to NetXMS node object
229 * dwSourceIP is an IP address from which we receive message
230 */
e0f99bf0 231static void BindMsgToNode(NX_SYSLOG_RECORD *pRec, DWORD dwSourceIP)
5039dede 232{
8ebb2e89 233 Node *pNode = NULL;
5039dede
AK
234 DWORD dwIpAddr;
235
236 // Determine IP address of a source
237 if (pRec->szHostName[0] == 0)
238 {
239 // Hostname was not defined in the message
240 dwIpAddr = dwSourceIP;
241 }
242 else
243 {
a749a70a 244 dwIpAddr = ntohl(ResolveHostNameA(pRec->szHostName));
3aa66b65
VK
245 if ((dwIpAddr == INADDR_NONE) || (dwIpAddr == INADDR_ANY))
246 {
e0f99bf0
VK
247#ifdef UNICODE
248 WCHAR wname[MAX_OBJECT_NAME];
249 MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, pRec->szHostName, -1, wname, MAX_OBJECT_NAME);
250 wname[MAX_OBJECT_NAME - 1] = 0;
251 pNode = (Node *)FindObjectByName(wname, OBJECT_NODE);
252#else
3aa66b65 253 pNode = (Node *)FindObjectByName(pRec->szHostName, OBJECT_NODE);
e0f99bf0 254#endif
3aa66b65
VK
255 if (pNode == NULL)
256 dwIpAddr = dwSourceIP;
257 }
5039dede
AK
258 }
259
260 // Match source IP to NetXMS object
3aa66b65 261 if ((dwIpAddr != INADDR_NONE) && (pNode == NULL))
89135050 262 pNode = FindNodeByIP(0, dwIpAddr);
3aa66b65
VK
263
264 if (pNode != NULL)
265 {
266 pRec->dwSourceObject = pNode->Id();
267 if (pRec->szHostName[0] == 0)
e0f99bf0
VK
268 {
269#ifdef UNICODE
270 WideCharToMultiByte(CP_ACP, WC_DEFAULTCHAR | WC_COMPOSITECHECK, pNode->Name(), -1, pRec->szHostName, MAX_SYSLOG_HOSTNAME_LEN, NULL, NULL);
271 pRec->szHostName[MAX_SYSLOG_HOSTNAME_LEN - 1] = 0;
272#else
3aa66b65 273 nx_strncpy(pRec->szHostName, pNode->Name(), MAX_SYSLOG_HOSTNAME_LEN);
e0f99bf0
VK
274#endif
275 }
3aa66b65
VK
276 }
277 else
278 {
279 if (pRec->szHostName[0] == 0)
e0f99bf0 280 IpToStrA(dwSourceIP, pRec->szHostName);
5039dede
AK
281 }
282}
283
e9491562
VK
284/**
285 * Handler for EnumerateSessions()
286 */
5039dede
AK
287static void BroadcastSyslogMessage(ClientSession *pSession, void *pArg)
288{
e05b1945 289 if (pSession->isAuthenticated())
e0f99bf0 290 pSession->onSyslogMessage((NX_SYSLOG_RECORD *)pArg);
5039dede
AK
291}
292
e9491562
VK
293/**
294 * Process syslog message
295 */
5039dede
AK
296static void ProcessSyslogMessage(char *psMsg, int nMsgLen, DWORD dwSourceIP)
297{
e0f99bf0 298 NX_SYSLOG_RECORD record;
ce7565e7 299 TCHAR szQuery[4096];
5039dede
AK
300
301 if (ParseSyslogMessage(psMsg, nMsgLen, &record))
302 {
303 record.qwMsgId = m_qwMsgId++;
304 BindMsgToNode(&record, dwSourceIP);
5039dede
AK
305 _sntprintf(szQuery, 4096,
306 _T("INSERT INTO syslog (msg_id,msg_timestamp,facility,severity,")
307 _T("source_object_id,hostname,msg_tag,msg_text) VALUES ")
fe1d4002 308 _T("(") UINT64_FMT _T(",") TIME_T_FMT _T(",%d,%d,%d,%s,%s,%s)"),
5039dede
AK
309 record.qwMsgId, record.tmTimeStamp, record.nFacility,
310 record.nSeverity, record.dwSourceObject,
fe1d4002
VK
311 (const TCHAR *)DBPrepareStringA(g_hCoreDB, record.szHostName),
312 (const TCHAR *)DBPrepareStringA(g_hCoreDB, record.szTag),
313 (const TCHAR *)DBPrepareStringA(g_hCoreDB, record.szMessage));
5039dede
AK
314 DBQuery(g_hCoreDB, szQuery);
315
316 // Send message to all connected clients
317 EnumerateClientSessions(BroadcastSyslogMessage, &record);
318
c17f6cbc 319 MutexLock(m_mutexParserAccess);
5039dede
AK
320 if ((record.dwSourceObject != 0) && (m_parser != NULL))
321 {
6646dca4
VK
322#ifdef UNICODE
323 WCHAR wtag[MAX_SYSLOG_TAG_LEN];
324 WCHAR wmsg[MAX_LOG_MSG_LENGTH];
325 MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, record.szTag, -1, wtag, MAX_SYSLOG_TAG_LEN);
326 MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, record.szMessage, -1, wmsg, MAX_LOG_MSG_LENGTH);
327 m_parser->matchEvent(wtag, record.nFacility, 1 << record.nSeverity,
328 wmsg, record.dwSourceObject);
329#else
d43aee56
VK
330 m_parser->matchEvent(record.szTag, record.nFacility, 1 << record.nSeverity,
331 record.szMessage, record.dwSourceObject);
6646dca4 332#endif
5039dede 333 }
4d0c32f3 334 MutexUnlock(m_mutexParserAccess);
5039dede
AK
335 }
336}
337
e9491562
VK
338/**
339 * Syslog processing thread
340 */
5039dede
AK
341static THREAD_RESULT THREAD_CALL SyslogProcessingThread(void *pArg)
342{
343 QUEUED_SYSLOG_MESSAGE *pMsg;
344
345 while(1)
346 {
347 pMsg = (QUEUED_SYSLOG_MESSAGE *)m_pSyslogQueue->GetOrBlock();
348 if (pMsg == INVALID_POINTER_VALUE)
349 break;
350
351 ProcessSyslogMessage(pMsg->psMsg, pMsg->nBytes, pMsg->dwSourceIP);
352 free(pMsg->psMsg);
353 free(pMsg);
354 }
355 return THREAD_OK;
356}
357
358
359//
360// Queue syslog message for processing
361//
362
363static void QueueSyslogMessage(char *psMsg, int nMsgLen, DWORD dwSourceIP)
364{
365 QUEUED_SYSLOG_MESSAGE *pMsg;
366
367 pMsg = (QUEUED_SYSLOG_MESSAGE *)malloc(sizeof(QUEUED_SYSLOG_MESSAGE));
368 pMsg->dwSourceIP = dwSourceIP;
369 pMsg->nBytes = nMsgLen;
370 pMsg->psMsg = (char *)nx_memdup(psMsg, nMsgLen);
371 m_pSyslogQueue->Put(pMsg);
372}
373
374
375//
376// Callback for syslog parser
377//
378
6646dca4
VK
379static void SyslogParserCallback(DWORD eventCode, const TCHAR *eventName, const TCHAR *line, int paramCount,
380 TCHAR **params, DWORD objectId, void *userArg)
5039dede
AK
381{
382 char format[] = "ssssssssssssssssssssssssssssssss";
fe1d4002 383 TCHAR *plist[32];
5039dede
AK
384 int i, count;
385
386 count = min(paramCount, 32);
387 format[count] = 0;
388 for(i = 0; i < count; i++)
389 plist[i] = params[i];
2dd24569 390 PostEvent(eventCode, objectId, format,
5039dede
AK
391 plist[0], plist[1], plist[2], plist[3],
392 plist[4], plist[5], plist[6], plist[7],
393 plist[8], plist[9], plist[10], plist[11],
394 plist[12], plist[13], plist[14], plist[15],
395 plist[16], plist[17], plist[18], plist[19],
396 plist[20], plist[21], plist[22], plist[23],
397 plist[24], plist[25], plist[26], plist[27],
398 plist[28], plist[29], plist[30], plist[31]);
399}
400
401
402//
403// Event name resolver
404//
405
6646dca4 406static bool EventNameResolver(const TCHAR *name, DWORD *code)
5039dede
AK
407{
408 EVENT_TEMPLATE *event;
4d0c32f3 409 bool success = false;
5039dede
AK
410
411 event = FindEventTemplateByName(name);
412 if (event != NULL)
413 {
414 *code = event->dwCode;
4d0c32f3 415 success = true;
5039dede
AK
416 }
417 return success;
418}
419
420
4d0c32f3
VK
421//
422// Create syslog parser from config
423//
424
425static void CreateParserFromConfig()
426{
9f24efb3 427 char *xml;
4d0c32f3 428
c17f6cbc 429 MutexLock(m_mutexParserAccess);
4d0c32f3 430 delete_and_null(m_parser);
9f24efb3
VK
431#ifdef UNICODE
432 WCHAR *wxml = ConfigReadCLOB(_T("SyslogParser"), _T("<parser></parser>"));
433 if (wxml != NULL)
434 {
435 xml = UTF8StringFromWideString(wxml);
436 free(wxml);
437 }
438 else
439 {
440 xml = NULL;
441 }
442#else
6646dca4 443 xml = ConfigReadCLOB("SyslogParser", "<parser></parser>");
9f24efb3 444#endif
4d0c32f3
VK
445 if (xml != NULL)
446 {
6646dca4 447 TCHAR parseError[256];
4d0c32f3
VK
448
449 m_parser = new LogParser;
450 m_parser->setEventNameResolver(EventNameResolver);
451 if (m_parser->createFromXml(xml, -1, parseError, 256))
452 {
453 m_parser->setCallback(SyslogParserCallback);
454 DbgPrintf(3, _T("syslogd: parser successfully created from config"));
455 }
456 else
457 {
458 delete_and_null(m_parser);
459 nxlog_write(MSG_SYSLOG_PARSER_INIT_FAILED, EVENTLOG_ERROR_TYPE, "s", parseError);
460 }
461 free(xml);
462 }
463 MutexUnlock(m_mutexParserAccess);
464}
465
466
5039dede
AK
467//
468// Syslog messages receiver thread
469//
470
471THREAD_RESULT THREAD_CALL SyslogDaemon(void *pArg)
472{
473 SOCKET hSocket;
474 struct sockaddr_in addr;
475 int nBytes, nPort, nRet;
476 socklen_t nAddrLen;
477 char sMsg[MAX_SYSLOG_MSG_LEN];
478 DB_RESULT hResult;
479 THREAD hProcessingThread;
480 fd_set rdfs;
481 struct timeval tv;
5039dede
AK
482
483 // Determine first available message id
484 hResult = DBSelect(g_hCoreDB, _T("SELECT max(msg_id) FROM syslog"));
485 if (hResult != NULL)
486 {
487 if (DBGetNumRows(hResult) > 0)
488 {
489 m_qwMsgId = max(DBGetFieldUInt64(hResult, 0, 0) + 1, m_qwMsgId);
490 }
491 DBFreeResult(hResult);
492 }
493
494 hSocket = socket(AF_INET, SOCK_DGRAM, 0);
495 if (hSocket == -1)
496 {
35f836fe 497 nxlog_write(MSG_SOCKET_FAILED, EVENTLOG_ERROR_TYPE, "s", _T("SyslogDaemon"));
5039dede
AK
498 return THREAD_OK;
499 }
500
1ddf3f0c 501 SetSocketExclusiveAddrUse(hSocket);
5039dede
AK
502 SetSocketReuseFlag(hSocket);
503
504 // Get listen port number
505 nPort = ConfigReadInt(_T("SyslogListenPort"), 514);
506 if ((nPort < 1) || (nPort > 65535))
507 nPort = 514;
508
509 // Fill in local address structure
510 memset(&addr, 0, sizeof(struct sockaddr_in));
511 addr.sin_family = AF_INET;
512 addr.sin_addr.s_addr = ResolveHostName(g_szListenAddress);
513 addr.sin_port = htons((WORD)nPort);
514
515 // Bind socket
516 if (bind(hSocket, (struct sockaddr *)&addr, sizeof(struct sockaddr_in)) != 0)
517 {
35f836fe 518 nxlog_write(MSG_BIND_ERROR, EVENTLOG_ERROR_TYPE, "dse", nPort, _T("SyslogDaemon"), WSAGetLastError());
5039dede
AK
519 closesocket(hSocket);
520 return THREAD_OK;
521 }
522 nxlog_write(MSG_LISTENING_FOR_SYSLOG, EVENTLOG_INFORMATION_TYPE, "ad", ntohl(addr.sin_addr.s_addr), nPort);
523
524 // Create message parser
4d0c32f3
VK
525 m_mutexParserAccess = MutexCreate();
526 CreateParserFromConfig();
5039dede
AK
527
528 // Start processing thread
529 m_pSyslogQueue = new Queue(1000, 100);
530 hProcessingThread = ThreadCreateEx(SyslogProcessingThread, 0, NULL);
531
532 DbgPrintf(1, _T("Syslog Daemon started"));
533
534 // Wait for packets
89135050 535 while(!IsShutdownInProgress())
5039dede
AK
536 {
537 FD_ZERO(&rdfs);
538 FD_SET(hSocket, &rdfs);
539 tv.tv_sec = 1;
540 tv.tv_usec = 0;
541 nRet = select((int)hSocket + 1, &rdfs, NULL, NULL, &tv);
542 if (nRet > 0)
543 {
544 nAddrLen = sizeof(struct sockaddr_in);
545 nBytes = recvfrom(hSocket, sMsg, MAX_SYSLOG_MSG_LEN, 0,
546 (struct sockaddr *)&addr, &nAddrLen);
547 if (nBytes > 0)
548 {
549 QueueSyslogMessage(sMsg, nBytes, ntohl(addr.sin_addr.s_addr));
550 }
551 else
552 {
553 // Sleep on error
554 ThreadSleepMs(100);
555 }
556 }
557 else if (nRet == -1)
558 {
559 // Sleep on error
560 ThreadSleepMs(100);
561 }
562 }
563
564 // Stop processing thread
565 m_pSyslogQueue->Put(INVALID_POINTER_VALUE);
566 ThreadJoin(hProcessingThread);
567 delete m_pSyslogQueue;
568 delete m_parser;
569
570 DbgPrintf(1, _T("Syslog Daemon stopped"));
571 return THREAD_OK;
572}
573
574
575//
e0f99bf0 576// Create NXCP message from NX_SYSLOG_RECORD structure
5039dede
AK
577//
578
e0f99bf0 579void CreateMessageFromSyslogMsg(CSCPMessage *pMsg, NX_SYSLOG_RECORD *pRec)
5039dede
AK
580{
581 DWORD dwId = VID_SYSLOG_MSG_BASE;
582
583 pMsg->SetVariable(VID_NUM_RECORDS, (DWORD)1);
584 pMsg->SetVariable(dwId++, pRec->qwMsgId);
585 pMsg->SetVariable(dwId++, (DWORD)pRec->tmTimeStamp);
586 pMsg->SetVariable(dwId++, (WORD)pRec->nFacility);
587 pMsg->SetVariable(dwId++, (WORD)pRec->nSeverity);
588 pMsg->SetVariable(dwId++, pRec->dwSourceObject);
e0f99bf0
VK
589 pMsg->SetVariableFromMBString(dwId++, pRec->szHostName);
590 pMsg->SetVariableFromMBString(dwId++, pRec->szTag);
591 pMsg->SetVariableFromMBString(dwId++, pRec->szMessage);
5039dede 592}
4d0c32f3
VK
593
594
595//
596// Reinitialize parser on configuration change
597//
598
599void ReinitializeSyslogParser()
600{
601 if (m_mutexParserAccess == INVALID_MUTEX_HANDLE)
602 return; // Syslog daemon not initialized
603 CreateParserFromConfig();
604}