1//
2// RemoteSyslogListener.cpp
3//
4// Library: Net
5// Package: Logging
6// Module: RemoteSyslogListener
7//
8// Copyright (c) 2007, Applied Informatics Software Engineering GmbH.
9// and Contributors.
10//
11// SPDX-License-Identifier: BSL-1.0
12//
13
14
15#include "Poco/Net/RemoteSyslogListener.h"
16#include "Poco/Net/RemoteSyslogChannel.h"
17#include "Poco/Net/DatagramSocket.h"
18#include "Poco/Net/SocketAddress.h"
19#include "Poco/Runnable.h"
20#include "Poco/Notification.h"
21#include "Poco/AutoPtr.h"
22#include "Poco/NumberParser.h"
23#include "Poco/NumberFormatter.h"
24#include "Poco/DateTimeParser.h"
25#include "Poco/Message.h"
26#include "Poco/LoggingFactory.h"
27#include "Poco/Buffer.h"
28#include "Poco/Ascii.h"
29#include <cstddef>
30
31
32namespace Poco {
33namespace Net {
34
35
36//
37// MessageNotification
38//
39
40
41class MessageNotification: public Poco::Notification
42{
43public:
44 MessageNotification(const char* buffer, std::size_t length, const Poco::Net::SocketAddress& sourceAddress):
45 _message(buffer, length),
46 _sourceAddress(sourceAddress)
47 {
48 }
49
50 MessageNotification(const std::string& message, const Poco::Net::SocketAddress& sourceAddress):
51 _message(message),
52 _sourceAddress(sourceAddress)
53 {
54 }
55
56 ~MessageNotification()
57 {
58 }
59
60 const std::string& message() const
61 {
62 return _message;
63 }
64
65 const Poco::Net::SocketAddress& sourceAddress() const
66 {
67 return _sourceAddress;
68 }
69
70private:
71 std::string _message;
72 Poco::Net::SocketAddress _sourceAddress;
73};
74
75
76//
77// RemoteUDPListener
78//
79
80
81class RemoteUDPListener: public Poco::Runnable
82{
83public:
84 enum
85 {
86 WAITTIME_MILLISEC = 1000,
87 BUFFER_SIZE = 65536
88 };
89
90 RemoteUDPListener(Poco::NotificationQueue& queue, Poco::UInt16 port);
91 ~RemoteUDPListener();
92
93 void run();
94 void safeStop();
95
96private:
97 Poco::NotificationQueue& _queue;
98 DatagramSocket _socket;
99 bool _stopped;
100};
101
102
103RemoteUDPListener::RemoteUDPListener(Poco::NotificationQueue& queue, Poco::UInt16 port):
104 _queue(queue),
105 _socket(Poco::Net::SocketAddress(Poco::Net::IPAddress(), port)),
106 _stopped(false)
107{
108}
109
110
111RemoteUDPListener::~RemoteUDPListener()
112{
113}
114
115
116void RemoteUDPListener::run()
117{
118 Poco::Buffer<char> buffer(BUFFER_SIZE);
119 Poco::Timespan waitTime(WAITTIME_MILLISEC* 1000);
120 while (!_stopped)
121 {
122 try
123 {
124 if (_socket.poll(waitTime, Socket::SELECT_READ))
125 {
126 Poco::Net::SocketAddress sourceAddress;
127 int n = _socket.receiveFrom(buffer.begin(), BUFFER_SIZE, sourceAddress);
128 if (n > 0)
129 {
130 _queue.enqueueNotification(new MessageNotification(buffer.begin(), n, sourceAddress));
131 }
132 }
133 }
134 catch (...)
135 {
136 // lazy exception catching
137 }
138 }
139}
140
141
142void RemoteUDPListener::safeStop()
143{
144 _stopped = true;
145}
146
147
148//
149// SyslogParser
150//
151
152
153class SyslogParser: public Poco::Runnable
154{
155public:
156 static const std::string NILVALUE;
157
158 enum
159 {
160 WAITTIME_MILLISEC = 1000
161 };
162
163 SyslogParser(Poco::NotificationQueue& queue, RemoteSyslogListener* pListener);
164 ~SyslogParser();
165
166 void parse(const std::string& line, Poco::Message& message);
167 void run();
168 void safeStop();
169
170 static Poco::Message::Priority convert(RemoteSyslogChannel::Severity severity);
171
172private:
173 void parsePrio(const std::string& line, std::size_t& pos, RemoteSyslogChannel::Severity& severity, RemoteSyslogChannel::Facility& fac);
174 void parseNew(const std::string& line, RemoteSyslogChannel::Severity severity, RemoteSyslogChannel::Facility fac, std::size_t& pos, Poco::Message& message);
175 void parseBSD(const std::string& line, RemoteSyslogChannel::Severity severity, RemoteSyslogChannel::Facility fac, std::size_t& pos, Poco::Message& message);
176
177 static std::string parseUntilSpace(const std::string& line, std::size_t& pos);
178 /// Parses until it encounters the next space char, returns the string from pos, excluding space
179 /// pos will point past the space char
180
181 static std::string parseStructuredData(const std::string& line, std::size_t& pos);
182 /// Parses the structured data field.
183
184 static std::string parseStructuredDataToken(const std::string& line, std::size_t& pos);
185 /// Parses a token from the structured data field.
186
187private:
188 Poco::NotificationQueue& _queue;
189 bool _stopped;
190 RemoteSyslogListener* _pListener;
191};
192
193
194const std::string SyslogParser::NILVALUE("-");
195
196
197SyslogParser::SyslogParser(Poco::NotificationQueue& queue, RemoteSyslogListener* pListener):
198 _queue(queue),
199 _stopped(false),
200 _pListener(pListener)
201{
202 poco_check_ptr (_pListener);
203}
204
205
206SyslogParser::~SyslogParser()
207{
208}
209
210
211void SyslogParser::run()
212{
213 while (!_stopped)
214 {
215 try
216 {
217 Poco::AutoPtr<Poco::Notification> pNf(_queue.waitDequeueNotification(WAITTIME_MILLISEC));
218 if (pNf)
219 {
220 Poco::AutoPtr<MessageNotification> pMsgNf = pNf.cast<MessageNotification>();
221 Poco::Message message;
222 parse(pMsgNf->message(), message);
223 message["addr"] =pMsgNf->sourceAddress().host().toString();
224 _pListener->log(message);
225 }
226 }
227 catch (Poco::Exception&)
228 {
229 // parsing exception, what should we do?
230 }
231 catch (...)
232 {
233 }
234 }
235}
236
237
238void SyslogParser::safeStop()
239{
240 _stopped = true;
241}
242
243
244void SyslogParser::parse(const std::string& line, Poco::Message& message)
245{
246 // <int> -> int: lower 3 bits severity, upper bits: facility
247 std::size_t pos = 0;
248 RemoteSyslogChannel::Severity severity;
249 RemoteSyslogChannel::Facility fac;
250 parsePrio(line, pos, severity, fac);
251
252 // the next field decide if we parse an old BSD message or a new syslog message
253 // BSD: expects a month value in string form: Jan, Feb...
254 // SYSLOG expects a version number: 1
255
256 if (Poco::Ascii::isDigit(line[pos]))
257 {
258 parseNew(line, severity, fac, pos, message);
259 }
260 else
261 {
262 parseBSD(line, severity, fac, pos, message);
263 }
264 poco_assert (pos == line.size());
265}
266
267
268void SyslogParser::parsePrio(const std::string& line, std::size_t& pos, RemoteSyslogChannel::Severity& severity, RemoteSyslogChannel::Facility& fac)
269{
270 poco_assert (pos < line.size());
271 poco_assert (line[pos] == '<');
272 ++pos;
273 std::size_t start = pos;
274
275 while (pos < line.size() && Poco::Ascii::isDigit(line[pos]))
276 ++pos;
277
278 poco_assert (line[pos] == '>');
279 poco_assert (pos - start > 0);
280 std::string valStr = line.substr(start, pos - start);
281 ++pos; // skip the >
282
283 int val = Poco::NumberParser::parse(valStr);
284 poco_assert (val >= 0 && val <= (RemoteSyslogChannel::SYSLOG_LOCAL7 + RemoteSyslogChannel::SYSLOG_DEBUG));
285
286 Poco::UInt16 pri = static_cast<Poco::UInt16>(val);
287 // now get the lowest 3 bits
288 severity = static_cast<RemoteSyslogChannel::Severity>(pri & 0x0007u);
289 fac = static_cast<RemoteSyslogChannel::Facility>(pri & 0xfff8u);
290}
291
292
293void SyslogParser::parseNew(const std::string& line, RemoteSyslogChannel::Severity severity, RemoteSyslogChannel::Facility /*fac*/, std::size_t& pos, Poco::Message& message)
294{
295 Poco::Message::Priority prio = convert(severity);
296 // rest of the unparsed header is:
297 // VERSION SP TIMESTAMP SP HOSTNAME SP APP-NAME SP PROCID SP MSGID
298 std::string versionStr(parseUntilSpace(line, pos));
299 std::string timeStr(parseUntilSpace(line, pos)); // can be the nilvalue!
300 std::string hostName(parseUntilSpace(line, pos));
301 std::string appName(parseUntilSpace(line, pos));
302 std::string procId(parseUntilSpace(line, pos));
303 std::string msgId(parseUntilSpace(line, pos));
304 std::string sd(parseStructuredData(line, pos));
305 std::string messageText(line.substr(pos));
306 pos = line.size();
307 Poco::DateTime date;
308 int tzd = 0;
309 bool hasDate = Poco::DateTimeParser::tryParse(RemoteSyslogChannel::SYSLOG_TIMEFORMAT, timeStr, date, tzd);
310 Poco::Message logEntry(msgId, messageText, prio);
311 logEntry[RemoteSyslogListener::LOG_PROP_HOST] = hostName;
312 logEntry[RemoteSyslogListener::LOG_PROP_APP] = appName;
313 logEntry[RemoteSyslogListener::LOG_PROP_STRUCTURED_DATA] = sd;
314
315 if (hasDate)
316 logEntry.setTime(date.timestamp());
317 int lval(0);
318 Poco::NumberParser::tryParse(procId, lval);
319 logEntry.setPid(lval);
320 message.swap(logEntry);
321}
322
323
324void SyslogParser::parseBSD(const std::string& line, RemoteSyslogChannel::Severity severity, RemoteSyslogChannel::Facility /*fac*/, std::size_t& pos, Poco::Message& message)
325{
326 Poco::Message::Priority prio = convert(severity);
327 // rest of the unparsed header is:
328 // "%b %f %H:%M:%S" SP hostname|ipaddress
329 // detect three spaces
330 int spaceCnt = 0;
331 std::size_t start = pos;
332 while (spaceCnt < 3 && pos < line.size())
333 {
334 if (line[pos] == ' ')
335 {
336 spaceCnt++;
337 if (spaceCnt == 1)
338 {
339 // size must be 3 chars for month
340 if (pos - start != 3)
341 {
342 // probably a shortened time value, or the hostname
343 // assume hostName
344 Poco::Message logEntry(line.substr(start, pos-start), line.substr(pos+1), prio);
345 message.swap(logEntry);
346 return;
347 }
348 }
349 else if (spaceCnt == 2)
350 {
351 // a day value!
352 if (!(Poco::Ascii::isDigit(line[pos-1]) && (Poco::Ascii::isDigit(line[pos-2]) || Poco::Ascii::isSpace(line[pos-2]))))
353 {
354 // assume the next field is a hostname
355 spaceCnt = 3;
356 }
357 }
358 if (pos + 1 < line.size() && line[pos+1] == ' ')
359 {
360 // we have two spaces when the day value is smaller than 10!
361 ++pos; // skip one
362 }
363 }
364 ++pos;
365 }
366 std::string timeStr(line.substr(start, pos-start-1));
367 int tzd(0);
368 Poco::DateTime date;
369 int year = date.year(); // year is not included, use the current one
370 bool hasDate = Poco::DateTimeParser::tryParse(RemoteSyslogChannel::BSD_TIMEFORMAT, timeStr, date, tzd);
371 if (hasDate)
372 {
373 int m = date.month();
374 int d = date.day();
375 int h = date.hour();
376 int min = date.minute();
377 int sec = date.second();
378 date = Poco::DateTime(year, m, d, h, min, sec);
379 }
380 // next entry is host SP
381 std::string hostName(parseUntilSpace(line, pos));
382
383 // TAG: at most 32 alphanumeric chars, ANY non alphannumeric indicates start of message content
384 // ignore: treat everything as content
385 std::string messageText(line.substr(pos));
386 pos = line.size();
387 Poco::Message logEntry(hostName, messageText, prio);
388 logEntry.setTime(date.timestamp());
389 message.swap(logEntry);
390}
391
392
393std::string SyslogParser::parseUntilSpace(const std::string& line, std::size_t& pos)
394{
395 std::size_t start = pos;
396 while (pos < line.size() && !Poco::Ascii::isSpace(line[pos]))
397 ++pos;
398 // skip space
399 ++pos;
400 return line.substr(start, pos-start-1);
401}
402
403
404std::string SyslogParser::parseStructuredData(const std::string& line, std::size_t& pos)
405{
406 std::string sd;
407 if (pos < line.size())
408 {
409 if (line[pos] == '-')
410 {
411 ++pos;
412 }
413 else if (line[pos] == '[')
414 {
415 std::string tok = parseStructuredDataToken(line, pos);
416 while (tok == "[")
417 {
418 sd += tok;
419 tok = parseStructuredDataToken(line, pos);
420 while (tok != "]" && !tok.empty())
421 {
422 sd += tok;
423 tok = parseStructuredDataToken(line, pos);
424 }
425 sd += tok;
426 if (pos < line.size() && line[pos] == '[') tok = parseStructuredDataToken(line, pos);
427 }
428 }
429 if (pos < line.size() && Poco::Ascii::isSpace(line[pos])) ++pos;
430 }
431 return sd;
432}
433
434
435std::string SyslogParser::parseStructuredDataToken(const std::string& line, std::size_t& pos)
436{
437 std::string tok;
438 if (pos < line.size())
439 {
440 if (Poco::Ascii::isSpace(line[pos]) || line[pos] == '=' || line[pos] == '[' || line[pos] == ']')
441 {
442 tok += line[pos++];
443 }
444 else if (line[pos] == '"')
445 {
446 tok += line[pos++];
447 while (pos < line.size() && line[pos] != '"')
448 {
449 tok += line[pos++];
450 }
451 tok += '"';
452 if (pos < line.size()) pos++;
453 }
454 else
455 {
456 while (pos < line.size() && !Poco::Ascii::isSpace(line[pos]) && line[pos] != '=')
457 {
458 tok += line[pos++];
459 }
460 }
461 }
462 return tok;
463}
464
465Poco::Message::Priority SyslogParser::convert(RemoteSyslogChannel::Severity severity)
466{
467 switch (severity)
468 {
469 case RemoteSyslogChannel::SYSLOG_EMERGENCY:
470 return Poco::Message::PRIO_FATAL;
471 case RemoteSyslogChannel::SYSLOG_ALERT:
472 return Poco::Message::PRIO_FATAL;
473 case RemoteSyslogChannel::SYSLOG_CRITICAL:
474 return Poco::Message::PRIO_CRITICAL;
475 case RemoteSyslogChannel::SYSLOG_ERROR:
476 return Poco::Message::PRIO_ERROR;
477 case RemoteSyslogChannel::SYSLOG_WARNING:
478 return Poco::Message::PRIO_WARNING;
479 case RemoteSyslogChannel::SYSLOG_NOTICE:
480 return Poco::Message::PRIO_NOTICE;
481 case RemoteSyslogChannel::SYSLOG_INFORMATIONAL:
482 return Poco::Message::PRIO_INFORMATION;
483 case RemoteSyslogChannel::SYSLOG_DEBUG:
484 return Poco::Message::PRIO_DEBUG;
485 }
486 throw Poco::LogicException("Illegal severity value in message");
487}
488
489
490//
491// RemoteSyslogListener
492//
493
494
495const std::string RemoteSyslogListener::PROP_PORT("port");
496const std::string RemoteSyslogListener::PROP_THREADS("threads");
497
498const std::string RemoteSyslogListener::LOG_PROP_APP("app");
499const std::string RemoteSyslogListener::LOG_PROP_HOST("host");
500const std::string RemoteSyslogListener::LOG_PROP_STRUCTURED_DATA("structured-data");
501
502
503RemoteSyslogListener::RemoteSyslogListener():
504 _pListener(0),
505 _pParser(0),
506 _port(RemoteSyslogChannel::SYSLOG_PORT),
507 _threads(1)
508{
509}
510
511
512RemoteSyslogListener::RemoteSyslogListener(Poco::UInt16 port):
513 _pListener(0),
514 _pParser(0),
515 _port(port),
516 _threads(1)
517{
518}
519
520
521RemoteSyslogListener::RemoteSyslogListener(Poco::UInt16 port, int threads):
522 _pListener(0),
523 _pParser(0),
524 _port(port),
525 _threads(threads)
526{
527}
528
529
530RemoteSyslogListener::~RemoteSyslogListener()
531{
532}
533
534
535void RemoteSyslogListener::processMessage(const std::string& messageText)
536{
537 Poco::Message message;
538 _pParser->parse(messageText, message);
539 log(message);
540}
541
542
543void RemoteSyslogListener::enqueueMessage(const std::string& messageText, const Poco::Net::SocketAddress& senderAddress)
544{
545 _queue.enqueueNotification(new MessageNotification(messageText, senderAddress));
546}
547
548
549void RemoteSyslogListener::setProperty(const std::string& name, const std::string& value)
550{
551 if (name == PROP_PORT)
552 {
553 int val = Poco::NumberParser::parse(value);
554 if (val >= 0 && val < 65536)
555 _port = static_cast<Poco::UInt16>(val);
556 else
557 throw Poco::InvalidArgumentException("Not a valid port number", value);
558 }
559 else if (name == PROP_THREADS)
560 {
561 int val = Poco::NumberParser::parse(value);
562 if (val > 0 && val < 16)
563 _threads = val;
564 else
565 throw Poco::InvalidArgumentException("Invalid number of threads", value);
566 }
567 else
568 {
569 SplitterChannel::setProperty(name, value);
570 }
571}
572
573
574std::string RemoteSyslogListener::getProperty(const std::string& name) const
575{
576 if (name == PROP_PORT)
577 return Poco::NumberFormatter::format(_port);
578 else if (name == PROP_THREADS)
579 return Poco::NumberFormatter::format(_threads);
580 else
581 return SplitterChannel::getProperty(name);
582}
583
584
585void RemoteSyslogListener::open()
586{
587 SplitterChannel::open();
588 _pParser = new SyslogParser(_queue, this);
589 if (_port > 0)
590 {
591 _pListener = new RemoteUDPListener(_queue, _port);
592 }
593 for (int i = 0; i < _threads; i++)
594 {
595 _threadPool.start(*_pParser);
596 }
597 if (_pListener)
598 {
599 _threadPool.start(*_pListener);
600 }
601}
602
603
604void RemoteSyslogListener::close()
605{
606 if (_pListener)
607 {
608 _pListener->safeStop();
609 }
610 if (_pParser)
611 {
612 _pParser->safeStop();
613 }
614 _queue.clear();
615 _threadPool.joinAll();
616 delete _pListener;
617 delete _pParser;
618 _pListener = 0;
619 _pParser = 0;
620 SplitterChannel::close();
621}
622
623
624void RemoteSyslogListener::registerChannel()
625{
626 Poco::LoggingFactory::defaultFactory().registerChannelClass("RemoteSyslogListener", new Poco::Instantiator<RemoteSyslogListener, Poco::Channel>);
627}
628
629
630} } // namespace Poco::Net
631