samotop_core/mail/
logger.rs

1//! Reference implementation of a mail service
2//! simply delivering mail to server console log.
3use crate::{
4    common::*,
5    io::tls::MayBeTls,
6    mail::*,
7    smtp::{SessionService, SmtpContext, SmtpSession},
8};
9use std::fmt;
10
11/// Produce info logs on important e-mail and SMTP events.
12///
13/// The logger will use session service name to mark the logs.
14#[derive(Clone, Debug, Default)]
15pub struct SessionLogger;
16
17pub use SessionLogger as DebugService;
18
19impl<T> MailSetup<T> for SessionLogger
20where
21    T: AcceptsSessionService + AcceptsGuard + AcceptsDispatch,
22{
23    fn setup(self, config: &mut T) {
24        config.add_last_session_service(self.clone());
25        config.add_last_guard(self.clone());
26        config.add_last_dispatch(self);
27    }
28}
29impl SessionService for SessionLogger {
30    fn prepare_session<'a, 'i, 's, 'f>(
31        &'a self,
32        _io: &'i mut Box<dyn MayBeTls>,
33        state: &'s mut SmtpContext,
34    ) -> S1Fut<'f, ()>
35    where
36        'a: 'f,
37        'i: 'f,
38        's: 'f,
39    {
40        info!(
41            "{}: Preparing {}",
42            state.session.service_name, state.session.connection
43        );
44        Box::pin(ready(()))
45    }
46}
47
48impl MailGuard for SessionLogger {
49    fn add_recipient<'a, 's, 'f>(
50        &'a self,
51        session: &'s mut SmtpSession,
52        rcpt: Recipient,
53    ) -> S2Fut<'f, AddRecipientResult>
54    where
55        'a: 'f,
56        's: 'f,
57    {
58        info!(
59            "{}: RCPT {} from {:?} (mailid: {:?}).",
60            session.service_name, rcpt.address, session.transaction.mail, session.transaction.id
61        );
62        Box::pin(ready(AddRecipientResult::Inconclusive(rcpt)))
63    }
64    fn start_mail<'a, 's, 'f>(&'a self, session: &'s mut SmtpSession) -> S2Fut<'f, StartMailResult>
65    where
66        'a: 'f,
67        's: 'f,
68    {
69        info!(
70            "{}: MAIL from {:?} (mailid: {:?}). {}",
71            session.service_name, session.transaction.mail, session.transaction.id, session
72        );
73        Box::pin(ready(StartMailResult::Accepted))
74    }
75}
76
77impl MailDispatch for SessionLogger {
78    fn open_mail_body<'a, 's, 'f>(
79        &'a self,
80        session: &'s mut SmtpSession,
81    ) -> S1Fut<'f, DispatchResult>
82    where
83        'a: 'f,
84        's: 'f,
85    {
86        let Transaction {
87            ref mail,
88            ref id,
89            ref rcpts,
90            ..
91        } = session.transaction;
92        info!(
93            "{}: Mail from {:?} for {} (mailid: {:?}). {}",
94            session.service_name,
95            mail.as_ref()
96                .map(|m| m.sender().to_string())
97                .unwrap_or_else(|| "nobody".to_owned()),
98            rcpts.iter().fold(String::new(), |s, r| s + format!(
99                "{:?}, ",
100                r.address.to_string()
101            )
102            .as_ref()),
103            id,
104            session
105        );
106        session.transaction.sink = session.transaction.sink.take().map(|inner| {
107            Box::pin(DebugSink {
108                id: format!("{}: {}", session.service_name, id.clone()),
109                inner,
110            }) as Pin<Box<dyn MailDataSink>>
111        });
112        Box::pin(ready(Ok(())))
113    }
114}
115
116struct DebugSink {
117    id: String,
118    inner: Pin<Box<dyn MailDataSink>>,
119}
120
121impl io::Write for DebugSink {
122    fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<std::io::Result<()>> {
123        self.inner.as_mut().poll_flush(cx)
124    }
125    fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<std::io::Result<()>> {
126        match self.inner.as_mut().poll_flush(cx) {
127            Poll::Ready(Ok(())) => {
128                info!("{}: Mail complete", self.id);
129                Poll::Ready(Ok(()))
130            }
131            Poll::Ready(Err(e)) => {
132                info!("{}: Mail failed: {:?}", self.id, e);
133                Poll::Ready(Ok(()))
134            }
135            Poll::Pending => Poll::Pending,
136        }
137    }
138    fn poll_write(
139        mut self: Pin<&mut Self>,
140        cx: &mut Context<'_>,
141        buf: &[u8],
142    ) -> Poll<std::io::Result<usize>> {
143        match self.inner.as_mut().poll_write(cx, buf) {
144            Poll::Ready(Ok(len)) => {
145                debug!(
146                    "{}: Mail data written: len {} {:?}",
147                    self.id,
148                    len,
149                    String::from_utf8_lossy(&buf[..len])
150                );
151                Poll::Ready(Ok(len))
152            }
153            Poll::Ready(Err(e)) => {
154                info!("{}: Mail data failed: {:?}", self.id, e);
155                Poll::Ready(Err(e))
156            }
157            Poll::Pending => Poll::Pending,
158        }
159    }
160}
161
162impl fmt::Debug for DebugSink {
163    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
164        f.debug_struct("DebugSink")
165            .field("id", &self.id)
166            .field("inner", &"*")
167            .finish()
168    }
169}
170
171#[cfg(test)]
172mod tests {
173    use super::*;
174
175    #[test]
176    fn test_setup() {
177        async_std::task::block_on(async move {
178            let mut sess = SmtpSession::default();
179            let sut = SessionLogger;
180            let tran = sut.start_mail(&mut sess).await;
181            assert_eq!(tran, StartMailResult::Accepted)
182        })
183    }
184}