syslog_rs/
syslog_provider.rs

1/*-
2 * syslog-rs - a syslog client translated from libc to rust
3 * 
4 * Copyright 2025 Aleksandr Morozov
5 * 
6 * The syslog-rs crate can be redistributed and/or modified
7 * under the terms of either of the following licenses:
8 *
9 *   1. the Mozilla Public License Version 2.0 (the “MPL”) OR
10 *                     
11 *   2. EUROPEAN UNION PUBLIC LICENCE v. 1.2 EUPL © the European Union 2007, 2016
12 */
13
14use std::{fmt, os::unix::net::UnixDatagram, path::{Path, PathBuf}};
15
16use crate::{common, error::SyRes, throw_error};
17
18#[cfg(feature = "build_sync")]
19pub(super) use crate::sync::socket::{SyslogTap, Tap};
20
21#[cfg(feature = "async_enabled")]
22pub use crate::a_sync::{AsyncTap, AsyncSyslogTap};
23
24#[derive(Debug, Clone, Copy, PartialEq, Eq)]
25pub enum DestinationType
26{
27    Local, Network, File
28}
29
30impl DestinationType
31{
32    #[inline]
33    pub 
34    fn is_network(&self) -> bool
35    {
36        return self == &Self::Network;
37    }
38}
39
40/// This trait is a common interface for the syslog realization which holds
41/// necessary info for compile time initialization and initialization during
42/// runtime.
43#[cfg(feature = "build_sync")]
44pub trait SyslogDestination: fmt::Debug + fmt::Display + Send + Clone + SyslogDestMsg + 'static
45{
46    /// A provider of the connection to syslog for sync code.
47    type SocketTap: SyslogTap<Self>;
48
49    const DEST_TYPE: DestinationType;
50}
51
52/// This trait is a common interface for the syslog realization which holds
53/// necessary info for compile time initialization and initialization during
54/// runtime. For async portion.
55#[cfg(feature = "async_enabled")]
56pub trait AsyncSyslogDestination: fmt::Debug + fmt::Display + Send + Clone + SyslogDestMsg + 'static
57{
58    /// A provider of the connection to syslog for async code.
59    type SocketTap: AsyncSyslogTap<Self>;
60}
61
62/// A commin trait for both sync and async which implements methods that 
63/// returns some specific values for each type of the `destination`.
64pub trait SyslogDestMsg: fmt::Debug + fmt::Display + Send + Clone + 'static
65{
66    /// Should return the max message length for specific syslog server type.
67    fn get_max_msg_len() -> usize;
68}
69
70
71/// A local syslog which is working over datagram channel. If no path is specified,
72/// it will try to find the syslog server by the hardcoded links. The same will be
73/// performed if `use_alternative` is enabled when path is provided.
74#[derive(Debug, Clone)]
75pub struct SyslogLocal
76{
77    /// A path to custom `UnixDatagram` socket.
78    custom_remote_path: Option<PathBuf>,
79
80    /// usefull only if `custom_path` is [Some]
81    use_alternative: bool,
82}
83
84
85impl fmt::Display for SyslogLocal
86{
87    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result 
88    {
89        write!(f, "custom_path: {:?}, alt: {}", self.custom_remote_path, self.use_alternative)
90    }
91}
92
93impl SyslogLocal
94{
95    fn new_internal(custom_path: Option<PathBuf>, use_alt_path: bool) -> SyRes<Self>
96    {
97        let remote_path: Option<PathBuf> = 
98            if let Some(path) = custom_path
99            {
100                if use_alt_path == false
101                {
102                    if path.exists() == false || path.is_file() == false
103                    {
104                        throw_error!("path either does not exists or not a file: '{}'", path.display());
105                    }
106                }
107
108                Some(path.to_path_buf())
109            }
110            else
111            {
112                None
113            };
114
115        return Ok(
116            Self
117            { 
118                custom_remote_path: remote_path, 
119                use_alternative: use_alt_path, 
120            }
121        );
122    }
123
124    /// Creates a default connection to local syslog server. The correct path will be picked
125    /// automatically.
126    pub 
127    fn new() -> Self
128    {
129        return
130            Self::new_internal(None, false).unwrap();
131    }
132
133    /// Creates a special connection to specific local socket. If `use_alt_path` is 
134    /// set to true and `custom_path` is not available, other pre-programmed paths
135    /// will be probed and selected the first available.
136    pub 
137    fn new_custom_path<P: Into<PathBuf>>(custom_path: P, use_alt_path: bool) -> SyRes<Self>
138    {
139        return
140            Self::new_internal(Some(custom_path.into()), use_alt_path);
141    }
142
143    #[inline]
144    pub 
145    fn get_custom_remote_path(&self) -> Option<&Path>
146    {
147        return self.custom_remote_path.as_ref().map(|f| f.as_path());
148    }
149
150    #[inline]
151    pub 
152    fn get_use_alternative(&self) -> bool
153    {
154        return self.use_alternative;
155    }
156}
157
158impl SyslogDestMsg for SyslogLocal
159{
160    fn get_max_msg_len() -> usize 
161    {
162        if *common::RFC5424_MAX_DGRAM >= common::MAXLINE
163        {
164            return common::MAXLINE;
165        }
166        else
167        {
168            return *common::RFC5424_MAX_DGRAM;
169        };
170    }
171}
172
173#[cfg(feature = "build_async_tokio")]
174impl AsyncSyslogDestination for SyslogLocal
175{
176    type SocketTap = AsyncTap::<tokio::net::UnixDatagram, Self>;
177
178}
179
180#[cfg(feature = "build_async_smol")]
181impl AsyncSyslogDestination for SyslogLocal
182{
183    type SocketTap = AsyncTap::<smol::net::unix::UnixDatagram, Self>;
184}
185
186#[cfg(feature = "build_sync")]
187impl SyslogDestination for SyslogLocal
188{
189    type SocketTap = Tap::<UnixDatagram, Self>;
190    
191    const DEST_TYPE: DestinationType = DestinationType::Local;
192}
193
194#[cfg(feature = "build_ext_net")]
195pub mod imp_syslog_net
196{
197    use std::{fmt, net::{IpAddr, Ipv4Addr, SocketAddr, TcpStream, UdpSocket}, time::Duration};
198
199    use crate::{common, error::SyRes, map_error, SyslogDestMsg};
200
201    use super::*;
202
203    /// A remote syslog server which works over plain TCP.
204    #[derive(Debug, Clone)]
205    pub struct SyslogNetTcp
206    {
207        /// Remote address in format host:port
208        remote_addr: SocketAddr,
209
210        /// Local bind address in format host:port.
211        bind_addr: SocketAddr,
212
213        /// A optional connection timeout.
214        conn_timeout: Option<Duration>
215    }
216
217    impl fmt::Display for SyslogNetTcp
218    {
219        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result 
220        {
221            write!(f, "remote_addr: {}, bind_addr: {}, conn_timeout: {:?}", self.remote_addr, 
222                self.bind_addr, self.conn_timeout)
223        }
224    }
225
226    impl SyslogNetTcp
227    {
228        /// Creates a new TCP connection to syslog server.
229        /// 
230        /// # Arguments
231        /// 
232        /// * `dest_addr` - a server's address in form "ip:port"
233        /// 
234        /// * `local` - a local bind address in format "ip:port". Optional.
235        ///     If not set, then will bind to 0.0.0.0 i.e OS specific.
236        /// 
237        /// * `conn_timeout` - an optional connection timeout. If not specified, then
238        ///     TCP connection timeout (OS specific).
239        pub 
240        fn new<P>(remote: P, local: Option<P>, conn_timeout: Option<Duration>) -> SyRes<Self>
241        where P: AsRef<str>
242        {
243            let remote_addr: SocketAddr = 
244                remote
245                    .as_ref()
246                    .parse()
247                    .map_err(|e| 
248                        map_error!("failed parsing remote addr '{}', error: '{}'", remote.as_ref(), e)
249                    )?;
250
251            let bind_addr: SocketAddr = 
252                if let Some(bind) = local
253                {
254                    bind
255                        .as_ref()
256                        .parse()
257                        .map_err(|e| 
258                            map_error!("failed parsing bind addr '{}', error: '{}'", remote.as_ref(), e)
259                        )?
260                }
261                else
262                {
263                    SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 0)
264                };
265
266            return Ok(
267                Self
268                {
269                    remote_addr: remote_addr,
270                    bind_addr: bind_addr,
271                    conn_timeout: conn_timeout
272                }
273            );
274        }
275
276        #[inline]
277        pub 
278        fn get_remote_addr(&self) -> &SocketAddr
279        {
280            return &self.remote_addr;
281        }
282
283        #[inline]
284        pub 
285        fn get_bind_addr(&self) -> &SocketAddr
286        {
287            return &self.bind_addr;
288        }
289
290        #[inline]
291        pub 
292        fn get_conn_timeout(&self) -> Option<Duration>
293        {
294            return self.conn_timeout;
295        }
296    }
297
298    impl SyslogDestMsg for SyslogNetTcp
299    {
300        fn get_max_msg_len() -> usize 
301        {
302            return common::RFC5424_TCP_MAX_PKT_LEN;
303        }
304    }
305
306    #[cfg(feature = "build_async_tokio")]
307    impl AsyncSyslogDestination for SyslogNetTcp
308    {
309        type SocketTap = AsyncTap::<tokio::net::TcpStream, Self>;
310    }
311
312    #[cfg(feature = "build_async_smol")]
313    impl AsyncSyslogDestination for SyslogNetTcp
314    {
315        type SocketTap =  AsyncTap::<smol::net::TcpStream, Self>;
316    }
317
318    #[cfg(feature = "build_sync")]
319    impl SyslogDestination for SyslogNetTcp
320    {
321        type SocketTap = Tap::<TcpStream, Self>;
322
323        const DEST_TYPE: DestinationType = DestinationType::Network;
324    }
325
326
327    /// A remote syslog server which works over plain UDP.
328    #[derive(Debug, Clone)]
329    pub struct SyslogNetUdp
330    {
331        remote_addr: SocketAddr,
332        bind_addr: SocketAddr
333    }
334
335    impl fmt::Display for SyslogNetUdp
336    {
337        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result 
338        {
339            write!(f, "remote_addr: {}, bind_addr: {}", self.remote_addr, 
340                self.bind_addr)
341        }
342    }
343
344    impl SyslogNetUdp
345    {
346        /// Creates a new UDP connection to syslog server.
347        /// 
348        /// # Arguments
349        /// 
350        /// * `dest_addr` - a server's address in form "ip:port"
351        /// 
352        /// * `local` - a local bind address in format "ip:port". Optional.
353        ///     If not set, then will bind to 0.0.0.0 i.e OS specific.
354        pub 
355        fn new<P>(dest_addr: P, local: Option<P>) -> SyRes<Self>
356        where P: AsRef<str>
357        {
358            let remote_addr: SocketAddr = 
359                dest_addr
360                    .as_ref()
361                    .parse()
362                    .map_err(|e| 
363                        map_error!("failed parsing remote addr '{}', error: '{}'", dest_addr.as_ref(), e)
364                    )?;
365
366            let bind_addr: SocketAddr = 
367                if let Some(bind) = local
368                {
369                    bind
370                        .as_ref()
371                        .parse()
372                        .map_err(|e| 
373                            map_error!("failed parsing bind addr '{}', error: '{}'", dest_addr.as_ref(), e)
374                        )?
375                }
376                else
377                {
378                    SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 0)
379                };
380
381            return Ok(
382                Self
383                {
384                    remote_addr: remote_addr,
385                    bind_addr: bind_addr
386                }
387            );
388        }
389
390        #[inline]
391        pub 
392        fn get_remote_addr(&self) -> &SocketAddr
393        {
394            return &self.remote_addr;
395        }
396
397        #[inline]
398        pub 
399        fn get_bind_addr(&self) -> &SocketAddr
400        {
401            return &self.bind_addr;
402        }
403    }
404
405    impl SyslogDestMsg for SyslogNetUdp
406    {
407        fn get_max_msg_len() -> usize 
408        {
409            return common::RFC5424_UDP_MAX_PKT_LEN;
410        }
411    }
412
413    #[cfg(feature = "build_async_tokio")]
414    impl AsyncSyslogDestination for SyslogNetUdp
415    {
416        type SocketTap = AsyncTap::<tokio::net::UdpSocket, Self>;
417    }
418
419    #[cfg(feature = "build_async_smol")]
420    impl AsyncSyslogDestination for SyslogNetUdp
421    {
422        type SocketTap = AsyncTap::<smol::net::UdpSocket, Self>;
423    }
424
425    #[cfg(feature = "build_sync")]
426    impl SyslogDestination for SyslogNetUdp
427    {
428        type SocketTap = Tap::<UdpSocket, Self>;
429
430        const DEST_TYPE: DestinationType = DestinationType::Network;
431    }
432}
433
434#[cfg(feature = "build_ext_net")]
435pub use self::imp_syslog_net::SyslogNetTcp;
436
437#[cfg(feature = "build_ext_net")]
438pub use self::imp_syslog_net::SyslogNetUdp;
439
440#[cfg(feature = "build_ext_file")]
441pub mod imp_syslog_file
442{
443    use std::{fmt, fs::File, path::{Path, PathBuf}};
444
445    use crate::{common};
446
447    use super::*;
448
449    /// A writer to local file. NO syslog server is required.
450    #[derive(Debug, Clone)]
451    pub struct SyslogFile
452    {
453        /// A path to text file on the file system.
454        file_path: PathBuf
455    }
456
457    impl fmt::Display for SyslogFile
458    {
459        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result 
460        {
461            write!(f, "<file> path: {}", self.file_path.display())
462        }
463    }
464
465    impl SyslogFile
466    {
467        /// Sets where to create or open file and write there.
468        pub 
469        fn new<P: Into<PathBuf>>(dest_path: P) -> Self
470        {
471            return
472                Self
473                {
474                    file_path: dest_path.into() 
475                };
476        }
477
478        #[inline]
479        pub 
480        fn get_path(&self) -> &Path
481        {
482            return self.file_path.as_path();
483        }
484    }
485
486    impl SyslogDestMsg for SyslogFile
487    {
488        fn get_max_msg_len() -> usize 
489        {
490            if *common::RFC5424_MAX_DGRAM >= common::MAXLINE
491            {
492                return common::MAXLINE;
493            }
494            else
495            {
496                return *common::RFC5424_MAX_DGRAM;
497            };
498        }
499    }
500
501    #[cfg(feature = "build_async_tokio")]
502    impl AsyncSyslogDestination for SyslogFile
503    {
504        type SocketTap = AsyncTap::<tokio::fs::File, Self>;
505    }
506
507    #[cfg(feature = "build_async_smol")]
508    impl AsyncSyslogDestination for SyslogFile
509    {
510        type SocketTap = AsyncTap::<smol::fs::File, Self>;
511    }
512
513    #[cfg(feature = "build_sync")]
514    impl SyslogDestination for SyslogFile
515    {
516        type SocketTap =  Tap::<File, Self>;
517
518        const DEST_TYPE: DestinationType = DestinationType::File;
519    }
520}
521
522#[cfg(feature = "build_ext_file")]
523pub use self::imp_syslog_file::SyslogFile;
524
525#[cfg(feature = "build_ext_tls")]
526pub mod imp_syslog_tls
527{
528    use std::fmt;
529    use std::{net::{IpAddr, Ipv4Addr, SocketAddr, TcpStream}, sync::Arc, time::Duration};
530
531    #[cfg(feature = "build_sync")]
532use rustls::lock::Mutex;
533    use rustls::{pki_types::{CertificateDer, ServerName}, ClientConfig, ClientConnection, RootCertStore, StreamOwned};
534
535    use crate::{common, error::SyRes, map_error};
536
537    use super::*;
538
539    /// A remote syslog server with encrypted (TLS) over TCP connection.
540    #[derive(Debug, Clone)]
541    pub struct SyslogTls
542    {
543        /// A remote address in format host:port.
544        remote_addr: SocketAddr,
545
546        /// A local address to bind in format host:port.
547        bind_addr: SocketAddr,
548
549        /// A remote server name.
550        serv_name: ServerName<'static>,
551
552        /// A pre-configuret config.
553        client_config: Arc<ClientConfig>,
554
555        /// An optional connection time out. If not specified, OS (TCP) default.
556        conn_timeout: Option<Duration>,
557    }
558
559    impl fmt::Display for SyslogTls
560    {
561        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result 
562        {
563            write!(f, "remote: {}, bind: {}, serv_name: {:?}, client: {:?}, conn_timeout: {:?}", self.remote_addr, 
564                self.bind_addr, self.serv_name, self.client_config, self.conn_timeout)
565        }
566    }
567
568    impl SyslogTls
569    {
570        /// Creates a new TLS over TCP connection to syslog server. 
571        /// 
572        /// # Arguments
573        /// 
574        /// * `remote` - an (ip:port) to specific server.
575        /// 
576        /// * `local` - an (ip:port) to local bind address. Optional. If not set,
577        ///     OS specific behaviour.
578        /// 
579        /// * `serv_name` - a FQDN of server (should match with `root_cert`)
580        /// 
581        /// * `root_cert` - root certificates able to provide a root-of-trust for connection authentication.
582        /// 
583        /// * `conn_timeout` - an optional connection timeout. If not specified, will be OS (TCP) connection
584        ///     timeout default.
585        pub 
586        fn new<P>(remote: P, local: Option<P>, serv_name: impl Into<String>, 
587            root_cert: Vec<u8>, conn_timeout: Option<Duration>) -> SyRes<Self>
588        where P: AsRef<str>
589        {
590            use rustls::pki_types::pem::PemObject;
591
592            let server_name = serv_name.into();
593
594            let remote_addr: SocketAddr = 
595                remote
596                    .as_ref()
597                    .parse()
598                    .map_err(|e| 
599                        map_error!("failed parsing remote addr '{}', error: '{}'", remote.as_ref(), e)
600                    )?;
601
602            let cert = 
603                CertificateDer::from_pem_slice(root_cert.as_slice())
604                    .map_err(|e|
605                        map_error!("certificate parse error: '{}'", e)
606                    )?;
607
608            let mut root_store = RootCertStore::empty();
609            root_store
610                .add(cert)
611                .map_err(|e|
612                    map_error!("can not add root ceritficate, error: '{}'", e)
613                )?;
614
615            let serv_name = 
616                server_name
617                    .try_into()
618                    .map_err(|e|
619                        map_error!("server name error: '{}'", e)
620                    )?;
621
622            let client_config = 
623                rustls::ClientConfig::builder()
624                    .with_root_certificates(root_store)
625                    .with_no_client_auth();
626
627            let bind_addr: SocketAddr = 
628                if let Some(bind) = local
629                {
630                    bind
631                        .as_ref()
632                        .parse()
633                        .map_err(|e| 
634                            map_error!("failed parsing bind addr '{}', error: '{}'", remote.as_ref(), e)
635                        )?
636                }
637                else
638                {
639                    SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 0)
640                };
641
642            return Ok(
643                Self
644                {
645                    remote_addr: remote_addr, 
646                    bind_addr: bind_addr,
647                    serv_name: serv_name,
648                    client_config: Arc::new(client_config),
649                    conn_timeout: conn_timeout,
650                }
651            );
652        }
653
654        #[inline]
655        pub
656        fn get_remote_addr(&self) -> &SocketAddr
657        {
658            return &self.remote_addr;
659        }
660
661        #[inline]
662        pub 
663        fn get_bind_addr(&self) -> &SocketAddr
664        {
665            return &self.bind_addr;
666        }
667
668        #[inline]
669        pub 
670        fn get_serv_name(&self) -> ServerName<'static>
671        {
672            return self.serv_name.clone();
673        }
674
675        #[inline]
676        pub 
677        fn get_client_config(&self) -> Arc<ClientConfig>
678        {
679            return self.client_config.clone();
680        }
681
682        #[inline]
683        pub 
684        fn get_get_conn_timeout(&self) -> Option<Duration>
685        {
686            return self.conn_timeout.clone();
687        }
688    }
689
690    impl SyslogDestMsg for SyslogTls
691    {
692        fn get_max_msg_len() -> usize 
693        {
694             return common::RFC5424_TCP_MAX_PKT_LEN;
695        }
696    }
697
698    #[cfg(feature = "build_async_tokio")]
699    impl AsyncSyslogDestination for SyslogTls
700    {
701        type SocketTap = AsyncTap::<tokio_rustls::client::TlsStream<tokio::net::TcpStream>, Self>;
702    }
703
704    #[cfg(feature = "build_async_smol")]
705    impl AsyncSyslogDestination for SyslogTls
706    {
707        type SocketTap = AsyncTap::<futures_rustls::client::TlsStream<smol::net::TcpStream>, Self>;
708    }
709
710    #[cfg(feature = "build_sync")]
711    impl SyslogDestination for SyslogTls
712    {
713        type SocketTap = Tap::<Mutex<StreamOwned<ClientConnection, TcpStream>>, Self>;
714
715        const DEST_TYPE: DestinationType = DestinationType::Network;
716    }
717}
718
719#[cfg(feature = "build_ext_tls")]
720pub use self::imp_syslog_tls::SyslogTls;