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