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