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
181private:
182 Poco::NotificationQueue& _queue;
183 bool _stopped;
184 RemoteSyslogListener* _pListener;
185};
186
187
188const std::string SyslogParser::NILVALUE("-");
189
190
191SyslogParser::SyslogParser(Poco::NotificationQueue& queue, RemoteSyslogListener* pListener):
192 _queue(queue),
193 _stopped(false),
194 _pListener(pListener)
195{
196 poco_check_ptr (_pListener);
197}
198
199
200SyslogParser::~SyslogParser()
201{
202}
203
204
205void SyslogParser::run()
206{
207 while (!_stopped)
208 {
209 try
210 {
211 Poco::AutoPtr<Poco::Notification> pNf(_queue.waitDequeueNotification(WAITTIME_MILLISEC));
212 if (pNf)
213 {
214 Poco::AutoPtr<MessageNotification> pMsgNf = pNf.cast<MessageNotification>();
215 Poco::Message message;
216 parse(pMsgNf->message(), message);
217 message["addr"] =pMsgNf->sourceAddress().host().toString();
218 _pListener->log(message);
219 }
220 }
221 catch (Poco::Exception&)
222 {
223 // parsing exception, what should we do?
224 }
225 catch (...)
226 {
227 }
228 }
229}
230
231
232void SyslogParser::safeStop()
233{
234 _stopped = true;
235}
236
237
238void SyslogParser::parse(const std::string& line, Poco::Message& message)
239{
240 // <int> -> int: lower 3 bits severity, upper bits: facility
241 std::size_t pos = 0;
242 RemoteSyslogChannel::Severity severity;
243 RemoteSyslogChannel::Facility fac;
244 parsePrio(line, pos, severity, fac);
245
246 // the next field decide if we parse an old BSD message or a new syslog message
247 // BSD: expects a month value in string form: Jan, Feb...
248 // SYSLOG expects a version number: 1
249
250 if (Poco::Ascii::isDigit(line[pos]))
251 {
252 parseNew(line, severity, fac, pos, message);
253 }
254 else
255 {
256 parseBSD(line, severity, fac, pos, message);
257 }
258 poco_assert (pos == line.size());
259}
260
261
262void SyslogParser::parsePrio(const std::string& line, std::size_t& pos, RemoteSyslogChannel::Severity& severity, RemoteSyslogChannel::Facility& fac)
263{
264 poco_assert (pos < line.size());
265 poco_assert (line[pos] == '<');
266 ++pos;
267 std::size_t start = pos;
268
269 while (pos < line.size() && Poco::Ascii::isDigit(line[pos]))
270 ++pos;
271
272 poco_assert (line[pos] == '>');
273 poco_assert (pos - start > 0);
274 std::string valStr = line.substr(start, pos - start);
275 ++pos; // skip the >
276
277 int val = Poco::NumberParser::parse(valStr);
278 poco_assert (val >= 0 && val <= (RemoteSyslogChannel::SYSLOG_LOCAL7 + RemoteSyslogChannel::SYSLOG_DEBUG));
279
280 Poco::UInt16 pri = static_cast<Poco::UInt16>(val);
281 // now get the lowest 3 bits
282 severity = static_cast<RemoteSyslogChannel::Severity>(pri & 0x0007u);
283 fac = static_cast<RemoteSyslogChannel::Facility>(pri & 0xfff8u);
284}
285
286
287void SyslogParser::parseNew(const std::string& line, RemoteSyslogChannel::Severity severity, RemoteSyslogChannel::Facility fac, std::size_t& pos, Poco::Message& message)
288{
289 Poco::Message::Priority prio = convert(severity);
290 // rest of the unparsed header is:
291 // VERSION SP TIMESTAMP SP HOSTNAME SP APP-NAME SP PROCID SP MSGID
292 std::string versionStr(parseUntilSpace(line, pos));
293 std::string timeStr(parseUntilSpace(line, pos)); // can be the nilvalue!
294 std::string hostName(parseUntilSpace(line, pos));
295 std::string appName(parseUntilSpace(line, pos));
296 std::string procId(parseUntilSpace(line, pos));
297 std::string msgId(parseUntilSpace(line, pos));
298 std::string messageText(line.substr(pos));
299 pos = line.size();
300 Poco::DateTime date;
301 int tzd = 0;
302 bool hasDate = Poco::DateTimeParser::tryParse(RemoteSyslogChannel::SYSLOG_TIMEFORMAT, timeStr, date, tzd);
303 Poco::Message logEntry(msgId, messageText, prio);
304 logEntry[RemoteSyslogListener::LOG_PROP_HOST] = hostName;
305 logEntry[RemoteSyslogListener::LOG_PROP_APP] = appName;
306
307 if (hasDate)
308 logEntry.setTime(date.timestamp());
309 int lval(0);
310 Poco::NumberParser::tryParse(procId, lval);
311 logEntry.setPid(lval);
312 message.swap(logEntry);
313}
314
315
316void SyslogParser::parseBSD(const std::string& line, RemoteSyslogChannel::Severity severity, RemoteSyslogChannel::Facility fac, std::size_t& pos, Poco::Message& message)
317{
318 Poco::Message::Priority prio = convert(severity);
319 // rest of the unparsed header is:
320 // "%b %f %H:%M:%S" SP hostname|ipaddress
321 // detect three spaces
322 int spaceCnt = 0;
323 std::size_t start = pos;
324 while (spaceCnt < 3 && pos < line.size())
325 {
326 if (line[pos] == ' ')
327 {
328 spaceCnt++;
329 if (spaceCnt == 1)
330 {
331 // size must be 3 chars for month
332 if (pos - start != 3)
333 {
334 // probably a shortened time value, or the hostname
335 // assume hostName
336 Poco::Message logEntry(line.substr(start, pos-start), line.substr(pos+1), prio);
337 message.swap(logEntry);
338 return;
339 }
340 }
341 else if (spaceCnt == 2)
342 {
343 // a day value!
344 if (!(Poco::Ascii::isDigit(line[pos-1]) && (Poco::Ascii::isDigit(line[pos-2]) || Poco::Ascii::isSpace(line[pos-2]))))
345 {
346 // assume the next field is a hostname
347 spaceCnt = 3;
348 }
349 }
350 if (pos + 1 < line.size() && line[pos+1] == ' ')
351 {
352 // we have two spaces when the day value is smaller than 10!
353 ++pos; // skip one
354 }
355 }
356 ++pos;
357 }
358 std::string timeStr(line.substr(start, pos-start-1));
359 int tzd(0);
360 Poco::DateTime date;
361 int year = date.year(); // year is not included, use the current one
362 bool hasDate = Poco::DateTimeParser::tryParse(RemoteSyslogChannel::BSD_TIMEFORMAT, timeStr, date, tzd);
363 if (hasDate)
364 {
365 int m = date.month();
366 int d = date.day();
367 int h = date.hour();
368 int min = date.minute();
369 int sec = date.second();
370 date = Poco::DateTime(year, m, d, h, min, sec);
371 }
372 // next entry is host SP
373 std::string hostName(parseUntilSpace(line, pos));
374
375 // TAG: at most 32 alphanumeric chars, ANY non alphannumeric indicates start of message content
376 // ignore: treat everything as content
377 std::string messageText(line.substr(pos));
378 pos = line.size();
379 Poco::Message logEntry(hostName, messageText, prio);
380 logEntry.setTime(date.timestamp());
381 message.swap(logEntry);
382}
383
384
385std::string SyslogParser::parseUntilSpace(const std::string& line, std::size_t& pos)
386{
387 std::size_t start = pos;
388 while (pos < line.size() && !Poco::Ascii::isSpace(line[pos]))
389 ++pos;
390 // skip space
391 ++pos;
392 return line.substr(start, pos-start-1);
393}
394
395
396Poco::Message::Priority SyslogParser::convert(RemoteSyslogChannel::Severity severity)
397{
398 switch (severity)
399 {
400 case RemoteSyslogChannel::SYSLOG_EMERGENCY:
401 return Poco::Message::PRIO_FATAL;
402 case RemoteSyslogChannel::SYSLOG_ALERT:
403 return Poco::Message::PRIO_FATAL;
404 case RemoteSyslogChannel::SYSLOG_CRITICAL:
405 return Poco::Message::PRIO_CRITICAL;
406 case RemoteSyslogChannel::SYSLOG_ERROR:
407 return Poco::Message::PRIO_ERROR;
408 case RemoteSyslogChannel::SYSLOG_WARNING:
409 return Poco::Message::PRIO_WARNING;
410 case RemoteSyslogChannel::SYSLOG_NOTICE:
411 return Poco::Message::PRIO_NOTICE;
412 case RemoteSyslogChannel::SYSLOG_INFORMATIONAL:
413 return Poco::Message::PRIO_INFORMATION;
414 case RemoteSyslogChannel::SYSLOG_DEBUG:
415 return Poco::Message::PRIO_DEBUG;
416 }
417 throw Poco::LogicException("Illegal severity value in message");
418}
419
420
421//
422// RemoteSyslogListener
423//
424
425
426const std::string RemoteSyslogListener::PROP_PORT("port");
427const std::string RemoteSyslogListener::PROP_THREADS("threads");
428
429const std::string RemoteSyslogListener::LOG_PROP_APP("app");
430const std::string RemoteSyslogListener::LOG_PROP_HOST("host");
431
432
433RemoteSyslogListener::RemoteSyslogListener():
434 _pListener(0),
435 _pParser(0),
436 _port(RemoteSyslogChannel::SYSLOG_PORT),
437 _threads(1)
438{
439}
440
441
442RemoteSyslogListener::RemoteSyslogListener(Poco::UInt16 port):
443 _pListener(0),
444 _pParser(0),
445 _port(port),
446 _threads(1)
447{
448}
449
450
451RemoteSyslogListener::RemoteSyslogListener(Poco::UInt16 port, int threads):
452 _pListener(0),
453 _pParser(0),
454 _port(port),
455 _threads(threads)
456{
457}
458
459
460RemoteSyslogListener::~RemoteSyslogListener()
461{
462}
463
464
465void RemoteSyslogListener::processMessage(const std::string& messageText)
466{
467 Poco::Message message;
468 _pParser->parse(messageText, message);
469 log(message);
470}
471
472
473void RemoteSyslogListener::enqueueMessage(const std::string& messageText, const Poco::Net::SocketAddress& senderAddress)
474{
475 _queue.enqueueNotification(new MessageNotification(messageText, senderAddress));
476}
477
478
479void RemoteSyslogListener::setProperty(const std::string& name, const std::string& value)
480{
481 if (name == PROP_PORT)
482 {
483 int val = Poco::NumberParser::parse(value);
484 if (val >= 0 && val < 65536)
485 _port = static_cast<Poco::UInt16>(val);
486 else
487 throw Poco::InvalidArgumentException("Not a valid port number", value);
488 }
489 else if (name == PROP_THREADS)
490 {
491 int val = Poco::NumberParser::parse(value);
492 if (val > 0 && val < 16)
493 _threads = val;
494 else
495 throw Poco::InvalidArgumentException("Invalid number of threads", value);
496 }
497 else
498 {
499 SplitterChannel::setProperty(name, value);
500 }
501}
502
503
504std::string RemoteSyslogListener::getProperty(const std::string& name) const
505{
506 if (name == PROP_PORT)
507 return Poco::NumberFormatter::format(_port);
508 else if (name == PROP_THREADS)
509 return Poco::NumberFormatter::format(_threads);
510 else
511 return SplitterChannel::getProperty(name);
512}
513
514
515void RemoteSyslogListener::open()
516{
517 SplitterChannel::open();
518 _pParser = new SyslogParser(_queue, this);
519 if (_port > 0)
520 {
521 _pListener = new RemoteUDPListener(_queue, _port);
522 }
523 for (int i = 0; i < _threads; i++)
524 {
525 _threadPool.start(*_pParser);
526 }
527 if (_pListener)
528 {
529 _threadPool.start(*_pListener);
530 }
531}
532
533
534void RemoteSyslogListener::close()
535{
536 if (_pListener)
537 {
538 _pListener->safeStop();
539 }
540 if (_pParser)
541 {
542 _pParser->safeStop();
543 }
544 _queue.clear();
545 _threadPool.joinAll();
546 delete _pListener;
547 delete _pParser;
548 _pListener = 0;
549 _pParser = 0;
550 SplitterChannel::close();
551}
552
553
554void RemoteSyslogListener::registerChannel()
555{
556 Poco::LoggingFactory::defaultFactory().registerChannelClass("RemoteSyslogListener", new Poco::Instantiator<RemoteSyslogListener, Poco::Channel>);
557}
558
559
560} } // namespace Poco::Net
561