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 | |
32 | namespace Poco { |
33 | namespace Net { |
34 | |
35 | |
36 | // |
37 | // MessageNotification |
38 | // |
39 | |
40 | |
41 | class MessageNotification: public Poco::Notification |
42 | { |
43 | public: |
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 | |
70 | private: |
71 | std::string _message; |
72 | Poco::Net::SocketAddress _sourceAddress; |
73 | }; |
74 | |
75 | |
76 | // |
77 | // RemoteUDPListener |
78 | // |
79 | |
80 | |
81 | class RemoteUDPListener: public Poco::Runnable |
82 | { |
83 | public: |
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 | |
96 | private: |
97 | Poco::NotificationQueue& _queue; |
98 | DatagramSocket _socket; |
99 | bool _stopped; |
100 | }; |
101 | |
102 | |
103 | RemoteUDPListener::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 | |
111 | RemoteUDPListener::~RemoteUDPListener() |
112 | { |
113 | } |
114 | |
115 | |
116 | void 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 | |
142 | void RemoteUDPListener::safeStop() |
143 | { |
144 | _stopped = true; |
145 | } |
146 | |
147 | |
148 | // |
149 | // SyslogParser |
150 | // |
151 | |
152 | |
153 | class SyslogParser: public Poco::Runnable |
154 | { |
155 | public: |
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 | |
172 | private: |
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 | |
187 | private: |
188 | Poco::NotificationQueue& _queue; |
189 | bool _stopped; |
190 | RemoteSyslogListener* _pListener; |
191 | }; |
192 | |
193 | |
194 | const std::string SyslogParser::NILVALUE("-" ); |
195 | |
196 | |
197 | SyslogParser::SyslogParser(Poco::NotificationQueue& queue, RemoteSyslogListener* pListener): |
198 | _queue(queue), |
199 | _stopped(false), |
200 | _pListener(pListener) |
201 | { |
202 | poco_check_ptr (_pListener); |
203 | } |
204 | |
205 | |
206 | SyslogParser::~SyslogParser() |
207 | { |
208 | } |
209 | |
210 | |
211 | void 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 | |
238 | void SyslogParser::safeStop() |
239 | { |
240 | _stopped = true; |
241 | } |
242 | |
243 | |
244 | void 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 | |
268 | void 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 | |
293 | void 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 | |
324 | void 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 | |
393 | std::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 | |
404 | std::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 | |
435 | std::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 | |
465 | Poco::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 | |
495 | const std::string RemoteSyslogListener::PROP_PORT("port" ); |
496 | const std::string RemoteSyslogListener::PROP_THREADS("threads" ); |
497 | |
498 | const std::string RemoteSyslogListener::LOG_PROP_APP("app" ); |
499 | const std::string RemoteSyslogListener::LOG_PROP_HOST("host" ); |
500 | const std::string RemoteSyslogListener::LOG_PROP_STRUCTURED_DATA("structured-data" ); |
501 | |
502 | |
503 | RemoteSyslogListener::RemoteSyslogListener(): |
504 | _pListener(0), |
505 | _pParser(0), |
506 | _port(RemoteSyslogChannel::SYSLOG_PORT), |
507 | _threads(1) |
508 | { |
509 | } |
510 | |
511 | |
512 | RemoteSyslogListener::RemoteSyslogListener(Poco::UInt16 port): |
513 | _pListener(0), |
514 | _pParser(0), |
515 | _port(port), |
516 | _threads(1) |
517 | { |
518 | } |
519 | |
520 | |
521 | RemoteSyslogListener::RemoteSyslogListener(Poco::UInt16 port, int threads): |
522 | _pListener(0), |
523 | _pParser(0), |
524 | _port(port), |
525 | _threads(threads) |
526 | { |
527 | } |
528 | |
529 | |
530 | RemoteSyslogListener::~RemoteSyslogListener() |
531 | { |
532 | } |
533 | |
534 | |
535 | void RemoteSyslogListener::processMessage(const std::string& messageText) |
536 | { |
537 | Poco::Message message; |
538 | _pParser->parse(messageText, message); |
539 | log(message); |
540 | } |
541 | |
542 | |
543 | void RemoteSyslogListener::enqueueMessage(const std::string& messageText, const Poco::Net::SocketAddress& senderAddress) |
544 | { |
545 | _queue.enqueueNotification(new MessageNotification(messageText, senderAddress)); |
546 | } |
547 | |
548 | |
549 | void 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 | |
574 | std::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 | |
585 | void 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 | |
604 | void 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 | |
624 | void RemoteSyslogListener::registerChannel() |
625 | { |
626 | Poco::LoggingFactory::defaultFactory().registerChannelClass("RemoteSyslogListener" , new Poco::Instantiator<RemoteSyslogListener, Poco::Channel>); |
627 | } |
628 | |
629 | |
630 | } } // namespace Poco::Net |
631 | |