1 /*
2  *          Copyright Andrey Semashev 2007 - 2018.
3  * Distributed under the Boost Software License, Version 1.0.
4  *    (See accompanying file LICENSE_1_0.txt or copy at
5  *          http://www.boost.org/LICENSE_1_0.txt)
6  */
7 /*!
8  * \file   syslog_backend.cpp
9  * \author Andrey Semashev
10  * \date   08.01.2008
11  *
12  * \brief  This header is the Boost.Log library implementation, see the library documentation
13  *         at http://www.boost.org/doc/libs/release/libs/log/doc/html/index.html.
14  */
15 
16 #ifndef BOOST_LOG_WITHOUT_SYSLOG
17 
18 #include <boost/log/detail/config.hpp>
19 #include <ctime>
20 #include <algorithm>
21 #include <stdexcept>
22 #include <boost/limits.hpp>
23 #include <boost/assert.hpp>
24 #include <boost/smart_ptr/weak_ptr.hpp>
25 #include <boost/smart_ptr/shared_ptr.hpp>
26 #include <boost/smart_ptr/make_shared_object.hpp>
27 #include <boost/throw_exception.hpp>
28 #if !defined(BOOST_LOG_NO_ASIO)
29 #include <boost/asio/buffer.hpp>
30 #include <boost/asio/socket_base.hpp>
31 #include <boost/asio/io_context.hpp>
32 #include <boost/asio/ip/udp.hpp>
33 #include <boost/asio/ip/address.hpp>
34 #include <boost/asio/ip/host_name.hpp>
35 #include <boost/asio/ip/resolver_base.hpp>
36 #endif
37 #include <boost/system/error_code.hpp>
38 #include <boost/date_time/c_time.hpp>
39 #include <boost/log/sinks/syslog_backend.hpp>
40 #include <boost/log/sinks/syslog_constants.hpp>
41 #include <boost/log/detail/singleton.hpp>
42 #include <boost/log/detail/snprintf.hpp>
43 #include <boost/log/exceptions.hpp>
44 #if !defined(BOOST_LOG_NO_THREADS)
45 #include <boost/thread/locks.hpp>
46 #include <boost/thread/mutex.hpp>
47 #endif
48 #include "unique_ptr.hpp"
49 
50 #ifdef BOOST_LOG_USE_NATIVE_SYSLOG
51 #include <syslog.h>
52 #endif // BOOST_LOG_USE_NATIVE_SYSLOG
53 
54 #include <boost/log/detail/header.hpp>
55 
56 namespace boost {
57 
58 BOOST_LOG_OPEN_NAMESPACE
59 
60 namespace sinks {
61 
62 namespace syslog {
63 
64     //! The function constructs log record level from an integer
make_level(int lev)65     BOOST_LOG_API level make_level(int lev)
66     {
67         if (BOOST_UNLIKELY(static_cast< unsigned int >(lev) >= 8u))
68             BOOST_THROW_EXCEPTION(std::out_of_range("syslog level value is out of range"));
69         return static_cast< level >(lev);
70     }
71 
72     //! The function constructs log source facility from an integer
make_facility(int fac)73     BOOST_LOG_API facility make_facility(int fac)
74     {
75         if (BOOST_UNLIKELY((static_cast< unsigned int >(fac) & 7u) != 0u
76             || static_cast< unsigned int >(fac) > (23u * 8u)))
77         {
78             BOOST_THROW_EXCEPTION(std::out_of_range("syslog facility code value is out of range"));
79         }
80         return static_cast< facility >(fac);
81     }
82 
83 } // namespace syslog
84 
85 ////////////////////////////////////////////////////////////////////////////////
86 //! Syslog sink backend implementation
87 ////////////////////////////////////////////////////////////////////////////////
88 struct syslog_backend::implementation
89 {
90 #ifdef BOOST_LOG_USE_NATIVE_SYSLOG
91     struct native;
92 #endif // BOOST_LOG_USE_NATIVE_SYSLOG
93 #if !defined(BOOST_LOG_NO_ASIO)
94     struct udp_socket_based;
95 #endif
96 
97     //! Level mapper
98     severity_mapper_type m_LevelMapper;
99 
100     //! Logging facility (portable or native, depending on the backend implementation)
101     const int m_Facility;
102 
103     //! Constructor
implementationboost::sinks::syslog_backend::implementation104     explicit implementation(int facility) :
105         m_Facility(facility)
106     {
107     }
108     //! Virtual destructor
~implementationboost::sinks::syslog_backend::implementation109     virtual ~implementation() {}
110 
111     //! The method sends the formatted message to the syslog host
112     virtual void send(syslog::level lev, string_type const& formatted_message) = 0;
113 };
114 
115 
116 ////////////////////////////////////////////////////////////////////////////////
117 //  Native syslog API support
118 ////////////////////////////////////////////////////////////////////////////////
119 
120 #ifdef BOOST_LOG_USE_NATIVE_SYSLOG
121 
122 BOOST_LOG_ANONYMOUS_NAMESPACE {
123 
124     //! Syslog service initializer (implemented as a weak singleton)
125 #if !defined(BOOST_LOG_NO_THREADS)
126     class native_syslog_initializer :
127         private log::aux::lazy_singleton< native_syslog_initializer, mutex >
128 #else
129     class native_syslog_initializer
130 #endif
131     {
132 #if !defined(BOOST_LOG_NO_THREADS)
133         friend class log::aux::lazy_singleton< native_syslog_initializer, mutex >;
134         typedef log::aux::lazy_singleton< native_syslog_initializer, mutex > mutex_holder;
135 #endif
136 
137     public:
138         native_syslog_initializer(std::string const& ident, int facility)
139         {
140             ::openlog((ident.empty() ? static_cast< const char* >(NULL) : ident.c_str()), 0, facility);
141         }
142         ~native_syslog_initializer()
143         {
144             ::closelog();
145         }
146 
147         static shared_ptr< native_syslog_initializer > get_instance(std::string const& ident, int facility)
148         {
149 #if !defined(BOOST_LOG_NO_THREADS)
150             lock_guard< mutex > lock(mutex_holder::get());
151 #endif
152             static weak_ptr< native_syslog_initializer > instance;
153             shared_ptr< native_syslog_initializer > p(instance.lock());
154             if (!p)
155             {
156                 p = boost::make_shared< native_syslog_initializer >(ident, facility);
157                 instance = p;
158             }
159             return p;
160         }
161     };
162 
163 } // namespace
164 
165 struct syslog_backend::implementation::native :
166     public implementation
167 {
168     //! Reference to the syslog service initializer
169     const shared_ptr< native_syslog_initializer > m_pSyslogInitializer;
170 
171     //! Constructor
nativeboost::sinks::syslog_backend::implementation::native172     native(syslog::facility const& fac, std::string const& ident) :
173         implementation(convert_facility(fac)),
174         m_pSyslogInitializer(native_syslog_initializer::get_instance(ident, this->m_Facility))
175     {
176     }
177 
178     //! The method sends the formatted message to the syslog host
sendboost::sinks::syslog_backend::implementation::native179     void send(syslog::level lev, string_type const& formatted_message)
180     {
181         int native_level;
182         switch (lev)
183         {
184         case syslog::emergency:
185             native_level = LOG_EMERG; break;
186         case syslog::alert:
187             native_level = LOG_ALERT; break;
188         case syslog::critical:
189             native_level = LOG_CRIT; break;
190         case syslog::error:
191             native_level = LOG_ERR; break;
192         case syslog::warning:
193             native_level = LOG_WARNING; break;
194         case syslog::notice:
195             native_level = LOG_NOTICE; break;
196         case syslog::debug:
197             native_level = LOG_DEBUG; break;
198         default:
199             native_level = LOG_INFO; break;
200         }
201 
202         ::syslog(this->m_Facility | native_level, "%s", formatted_message.c_str());
203     }
204 
205 private:
206     //! The function converts portable facility codes to the native codes
convert_facilityboost::sinks::syslog_backend::implementation::native207     static int convert_facility(syslog::facility const& fac)
208     {
209         // POSIX does not specify anything except for LOG_USER and LOG_LOCAL*
210         #ifndef LOG_KERN
211         #define LOG_KERN LOG_USER
212         #endif
213         #ifndef LOG_DAEMON
214         #define LOG_DAEMON LOG_KERN
215         #endif
216         #ifndef LOG_MAIL
217         #define LOG_MAIL LOG_USER
218         #endif
219         #ifndef LOG_AUTH
220         #define LOG_AUTH LOG_DAEMON
221         #endif
222         #ifndef LOG_SYSLOG
223         #define LOG_SYSLOG LOG_DAEMON
224         #endif
225         #ifndef LOG_LPR
226         #define LOG_LPR LOG_DAEMON
227         #endif
228         #ifndef LOG_NEWS
229         #define LOG_NEWS LOG_USER
230         #endif
231         #ifndef LOG_UUCP
232         #define LOG_UUCP LOG_USER
233         #endif
234         #ifndef LOG_CRON
235         #define LOG_CRON LOG_DAEMON
236         #endif
237         #ifndef LOG_AUTHPRIV
238         #define LOG_AUTHPRIV LOG_AUTH
239         #endif
240         #ifndef LOG_FTP
241         #define LOG_FTP LOG_DAEMON
242         #endif
243 
244         static const int native_facilities[24] =
245         {
246             LOG_KERN,
247             LOG_USER,
248             LOG_MAIL,
249             LOG_DAEMON,
250             LOG_AUTH,
251             LOG_SYSLOG,
252             LOG_LPR,
253             LOG_NEWS,
254             LOG_UUCP,
255             LOG_CRON,
256             LOG_AUTHPRIV,
257             LOG_FTP,
258 
259             // reserved values
260             LOG_USER,
261             LOG_USER,
262             LOG_USER,
263             LOG_USER,
264 
265             LOG_LOCAL0,
266             LOG_LOCAL1,
267             LOG_LOCAL2,
268             LOG_LOCAL3,
269             LOG_LOCAL4,
270             LOG_LOCAL5,
271             LOG_LOCAL6,
272             LOG_LOCAL7
273         };
274 
275         std::size_t n = static_cast< unsigned int >(fac) / 8u;
276         BOOST_ASSERT(n < sizeof(native_facilities) / sizeof(*native_facilities));
277         return native_facilities[n];
278     }
279 };
280 
281 #endif // BOOST_LOG_USE_NATIVE_SYSLOG
282 
283 
284 ////////////////////////////////////////////////////////////////////////////////
285 //  Socket-based implementation
286 ////////////////////////////////////////////////////////////////////////////////
287 
288 #if !defined(BOOST_LOG_NO_ASIO)
289 
290 BOOST_LOG_ANONYMOUS_NAMESPACE {
291 
292     //! The shared UDP socket
293     struct syslog_udp_socket
294     {
295     private:
296         //! The socket primitive
297         asio::ip::udp::socket m_Socket;
298 
299     public:
300         //! The constructor creates a socket bound to the specified local address and port
301         explicit syslog_udp_socket(asio::io_context& io_ctx, asio::ip::udp const& protocol, asio::ip::udp::endpoint const& local_address) :
302             m_Socket(io_ctx)
303         {
304             m_Socket.open(protocol);
305             m_Socket.set_option(asio::socket_base::reuse_address(true));
306             m_Socket.bind(local_address);
307         }
308         //! The destructor closes the socket
309         ~syslog_udp_socket()
310         {
311             boost::system::error_code ec;
312             m_Socket.shutdown(asio::socket_base::shutdown_both, ec);
313             m_Socket.close(ec);
314         }
315 
316         //! The method sends the syslog message to the specified endpoint
317         void send_message(int pri, const char* local_host_name, asio::ip::udp::endpoint const& target, const char* message);
318 
319         BOOST_DELETED_FUNCTION(syslog_udp_socket(syslog_udp_socket const&))
320         BOOST_DELETED_FUNCTION(syslog_udp_socket& operator= (syslog_udp_socket const&))
321     };
322 
323     //! The class contains the UDP service for syslog sockets to function
324     class syslog_udp_service :
325         public log::aux::lazy_singleton< syslog_udp_service, shared_ptr< syslog_udp_service > >
326     {
327         friend class log::aux::lazy_singleton< syslog_udp_service, shared_ptr< syslog_udp_service > >;
328         typedef log::aux::lazy_singleton< syslog_udp_service, shared_ptr< syslog_udp_service > > base_type;
329 
330     public:
331         //! The IO context instance
332         asio::io_context m_IOContext;
333         //! The local host name to put into log message
334         std::string m_LocalHostName;
335 
336 #if !defined(BOOST_LOG_NO_THREADS)
337         //! A synchronization primitive to protect the host name resolver
338         mutex m_Mutex;
339         //! The resolver is used to acquire connection endpoints
340         asio::ip::udp::resolver m_HostNameResolver;
341 #endif // !defined(BOOST_LOG_NO_THREADS)
342 
343     private:
344         //! Default constructor
345         syslog_udp_service()
346 #if !defined(BOOST_LOG_NO_THREADS)
347             : m_HostNameResolver(m_IOContext)
348 #endif // !defined(BOOST_LOG_NO_THREADS)
349         {
350             boost::system::error_code err;
351             m_LocalHostName = asio::ip::host_name(err);
352         }
353         //! Initializes the singleton instance
354         static void init_instance()
355         {
356             base_type::get_instance().reset(new syslog_udp_service());
357         }
358     };
359 
360     //! The method sends the syslog message to the specified endpoint
361     void syslog_udp_socket::send_message(
362         int pri, const char* local_host_name, asio::ip::udp::endpoint const& target, const char* message)
363     {
364         std::time_t t = std::time(NULL);
365         std::tm ts;
366         std::tm* time_stamp = boost::date_time::c_time::localtime(&t, &ts);
367 
368         // Month will have to be injected separately, as involving locale won't do here
369         static const char months[12][4] =
370         {
371             "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
372         };
373 
374         // The packet size is mandated in RFC3164, plus one for the terminating zero
375         char packet[1025];
376         int n = boost::log::aux::snprintf
377         (
378             packet,
379             sizeof(packet),
380             "<%d>%s %2d %02d:%02d:%02d %s %s",
381             pri,
382             months[time_stamp->tm_mon],
383             time_stamp->tm_mday,
384             time_stamp->tm_hour,
385             time_stamp->tm_min,
386             time_stamp->tm_sec,
387             local_host_name,
388             message
389         );
390         if (BOOST_LIKELY(n > 0))
391         {
392             std::size_t packet_size = static_cast< std::size_t >(n) >= sizeof(packet) ? sizeof(packet) - 1u : static_cast< std::size_t >(n);
393             m_Socket.send_to(asio::buffer(packet, packet_size), target);
394         }
395     }
396 
397 } // namespace
398 
399 struct syslog_backend::implementation::udp_socket_based :
400     public implementation
401 {
402     //! Protocol to be used
403     asio::ip::udp m_Protocol;
404     //! Pointer to the list of sockets
405     shared_ptr< syslog_udp_service > m_pService;
406     //! Pointer to the socket being used
407     log::aux::unique_ptr< syslog_udp_socket > m_pSocket;
408     //! The target host to send packets to
409     asio::ip::udp::endpoint m_TargetHost;
410 
411     //! Constructor
udp_socket_basedboost::sinks::syslog_backend::implementation::udp_socket_based412     explicit udp_socket_based(syslog::facility const& fac, asio::ip::udp const& protocol) :
413         implementation(fac),
414         m_Protocol(protocol),
415         m_pService(syslog_udp_service::get())
416     {
417         if (m_Protocol == asio::ip::udp::v4())
418         {
419             m_TargetHost = asio::ip::udp::endpoint(asio::ip::address_v4(0x7F000001), 514); // 127.0.0.1:514
420         }
421         else
422         {
423             // ::1, port 514
424             asio::ip::address_v6::bytes_type addr;
425             std::fill_n(addr.data(), addr.size() - 1u, static_cast< unsigned char >(0u));
426             addr[addr.size() - 1u] = 1u;
427             m_TargetHost = asio::ip::udp::endpoint(asio::ip::address_v6(addr), 514);
428         }
429     }
430 
431     //! The method sends the formatted message to the syslog host
sendboost::sinks::syslog_backend::implementation::udp_socket_based432     void send(syslog::level lev, string_type const& formatted_message)
433     {
434         if (!m_pSocket.get())
435         {
436             asio::ip::udp::endpoint any_local_address;
437             m_pSocket.reset(new syslog_udp_socket(m_pService->m_IOContext, m_Protocol, any_local_address));
438         }
439 
440         m_pSocket->send_message(
441             this->m_Facility | static_cast< int >(lev),
442             m_pService->m_LocalHostName.c_str(),
443             m_TargetHost,
444             formatted_message.c_str());
445     }
446 };
447 
448 #endif // !defined(BOOST_LOG_NO_ASIO)
449 
450 ////////////////////////////////////////////////////////////////////////////////
451 //  Sink backend implementation
452 ////////////////////////////////////////////////////////////////////////////////
syslog_backend()453 BOOST_LOG_API syslog_backend::syslog_backend()
454 {
455     construct(log::aux::empty_arg_list());
456 }
457 
458 //! Destructor
~syslog_backend()459 BOOST_LOG_API syslog_backend::~syslog_backend()
460 {
461     delete m_pImpl;
462 }
463 
464 //! The method installs the function object that maps application severity levels to Syslog levels
set_severity_mapper(severity_mapper_type const & mapper)465 BOOST_LOG_API void syslog_backend::set_severity_mapper(severity_mapper_type const& mapper)
466 {
467     m_pImpl->m_LevelMapper = mapper;
468 }
469 
470 //! The method writes the message to the sink
consume(record_view const & rec,string_type const & formatted_message)471 BOOST_LOG_API void syslog_backend::consume(record_view const& rec, string_type const& formatted_message)
472 {
473     m_pImpl->send(
474         m_pImpl->m_LevelMapper.empty() ? syslog::info : m_pImpl->m_LevelMapper(rec),
475         formatted_message);
476 }
477 
478 
479 //! The method creates the backend implementation
construct(syslog::facility fac,syslog::impl_types use_impl,ip_versions ip_version,std::string const & ident)480 BOOST_LOG_API void syslog_backend::construct(syslog::facility fac, syslog::impl_types use_impl, ip_versions ip_version, std::string const& ident)
481 {
482 #ifdef BOOST_LOG_USE_NATIVE_SYSLOG
483     if (use_impl == syslog::native)
484     {
485         typedef implementation::native native_impl;
486         m_pImpl = new native_impl(fac, ident);
487         return;
488     }
489 #endif // BOOST_LOG_USE_NATIVE_SYSLOG
490 
491 #if !defined(BOOST_LOG_NO_ASIO)
492     typedef implementation::udp_socket_based udp_socket_based_impl;
493     switch (ip_version)
494     {
495     case v4:
496         m_pImpl = new udp_socket_based_impl(fac, asio::ip::udp::v4());
497         break;
498     case v6:
499         m_pImpl = new udp_socket_based_impl(fac, asio::ip::udp::v6());
500         break;
501     default:
502         BOOST_LOG_THROW_DESCR(setup_error, "Incorrect IP version specified");
503     }
504 #endif
505 }
506 
507 #if !defined(BOOST_LOG_NO_ASIO)
508 
509 //! The method sets the local address which log records will be sent from.
set_local_address(std::string const & addr,unsigned short port)510 BOOST_LOG_API void syslog_backend::set_local_address(std::string const& addr, unsigned short port)
511 {
512 #if !defined(BOOST_LOG_NO_THREADS)
513     typedef implementation::udp_socket_based udp_socket_based_impl;
514     if (udp_socket_based_impl* impl = dynamic_cast< udp_socket_based_impl* >(m_pImpl))
515     {
516         char service_name[std::numeric_limits< int >::digits10 + 3];
517         boost::log::aux::snprintf(service_name, sizeof(service_name), "%d", static_cast< int >(port));
518 
519         asio::ip::udp::endpoint local_address;
520         {
521             lock_guard< mutex > lock(impl->m_pService->m_Mutex);
522             asio::ip::udp::resolver::results_type results = impl->m_pService->m_HostNameResolver.resolve
523             (
524                 addr,
525                 service_name,
526                 asio::ip::resolver_base::address_configured | asio::ip::resolver_base::passive
527             );
528 
529             local_address = *results.cbegin();
530         }
531 
532         impl->m_pSocket.reset(new syslog_udp_socket(impl->m_pService->m_IOContext, impl->m_Protocol, local_address));
533     }
534 #else
535     // Boost.ASIO requires threads for the host name resolver,
536     // so without threads we simply assume the string already contains IP address
537     set_local_address(boost::asio::ip::address::from_string(addr), port);
538 #endif // !defined(BOOST_LOG_NO_THREADS)
539 }
540 //! The method sets the local address which log records will be sent from.
set_local_address(boost::asio::ip::address const & addr,unsigned short port)541 BOOST_LOG_API void syslog_backend::set_local_address(boost::asio::ip::address const& addr, unsigned short port)
542 {
543     typedef implementation::udp_socket_based udp_socket_based_impl;
544     if (udp_socket_based_impl* impl = dynamic_cast< udp_socket_based_impl* >(m_pImpl))
545     {
546         impl->m_pSocket.reset(new syslog_udp_socket(
547             impl->m_pService->m_IOContext, impl->m_Protocol, asio::ip::udp::endpoint(addr, port)));
548     }
549 }
550 
551 //! The method sets the address of the remote host where log records will be sent to.
set_target_address(std::string const & addr,unsigned short port)552 BOOST_LOG_API void syslog_backend::set_target_address(std::string const& addr, unsigned short port)
553 {
554 #if !defined(BOOST_LOG_NO_THREADS)
555     typedef implementation::udp_socket_based udp_socket_based_impl;
556     if (udp_socket_based_impl* impl = dynamic_cast< udp_socket_based_impl* >(m_pImpl))
557     {
558         char service_name[std::numeric_limits< int >::digits10 + 3];
559         boost::log::aux::snprintf(service_name, sizeof(service_name), "%d", static_cast< int >(port));
560 
561         asio::ip::udp::endpoint remote_address;
562         {
563             lock_guard< mutex > lock(impl->m_pService->m_Mutex);
564             asio::ip::udp::resolver::results_type results = impl->m_pService->m_HostNameResolver.resolve
565             (
566                 addr,
567                 service_name,
568                 asio::ip::resolver_query_base::address_configured
569             );
570 
571             remote_address = *results.cbegin();
572         }
573 
574         impl->m_TargetHost = remote_address;
575     }
576 #else
577     // Boost.ASIO requires threads for the host name resolver,
578     // so without threads we simply assume the string already contains IP address
579     set_target_address(boost::asio::ip::address::from_string(addr), port);
580 #endif // !defined(BOOST_LOG_NO_THREADS)
581 }
582 //! The method sets the address of the remote host where log records will be sent to.
set_target_address(boost::asio::ip::address const & addr,unsigned short port)583 BOOST_LOG_API void syslog_backend::set_target_address(boost::asio::ip::address const& addr, unsigned short port)
584 {
585     typedef implementation::udp_socket_based udp_socket_based_impl;
586     if (udp_socket_based_impl* impl = dynamic_cast< udp_socket_based_impl* >(m_pImpl))
587     {
588         impl->m_TargetHost = asio::ip::udp::endpoint(addr, port);
589     }
590 }
591 
592 #endif // !defined(BOOST_LOG_NO_ASIO)
593 
594 } // namespace sinks
595 
596 BOOST_LOG_CLOSE_NAMESPACE // namespace log
597 
598 } // namespace boost
599 
600 #include <boost/log/detail/footer.hpp>
601 
602 #endif // !defined(BOOST_LOG_WITHOUT_SYSLOG)
603