1
173
/*
2
 * This file is part of mailpot
3
 *
4
 * Copyright 2020 - Manos Pitsidianakis
5
 *
6
 * This program is free software: you can redistribute it and/or modify
7
 * it under the terms of the GNU Affero General Public License as
8
 * published by the Free Software Foundation, either version 3 of the
9
 * License, or (at your option) any later version.
10
 *
11
 * This program is distributed in the hope that it will be useful,
12
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
 * GNU Affero General Public License for more details.
15
 *
16
 * You should have received a copy of the GNU Affero General Public License
17
 * along with this program. If not, see <https://www.gnu.org/licenses/>.
18
 */
19

            
20
#![allow(clippy::new_without_default)]
21

            
22
use std::net::IpAddr; //, Ipv4Addr, Ipv6Addr};
23
use std::{
24
    borrow::Cow,
25
    net::ToSocketAddrs,
26
    sync::{Arc, Mutex, Once},
27
    thread,
28
};
29

            
30
pub use assert_cmd;
31
pub use log::{trace, warn};
32
use mailin_embedded::{
33
    response::{INTERNAL_ERROR, OK},
34
    Handler, Response, Server,
35
};
36
pub use predicates;
37
pub use tempfile::{self, TempDir};
38

            
39
static INIT_STDERR_LOGGING: Once = Once::new();
40

            
41
23
pub fn init_stderr_logging() {
42
45
    INIT_STDERR_LOGGING.call_once(|| {
43
66
        stderrlog::new()
44
            .quiet(false)
45
22
            .verbosity(log::LevelFilter::Trace)
46
            .show_module_names(true)
47
22
            .timestamp(stderrlog::Timestamp::Millisecond)
48
            .init()
49
22
            .unwrap();
50
22
    });
51
23
}
52
pub const ADDRESS: &str = "127.0.0.1:8825";
53

            
54
#[derive(Debug, Clone)]
55
pub enum Message {
56
    Helo,
57
    Mail {
58
        from: String,
59
    },
60
    Rcpt {
61
        from: String,
62
        to: Vec<String>,
63
    },
64
    DataStart {
65
        from: String,
66
        to: Vec<String>,
67
    },
68
    Data {
69
        #[allow(dead_code)]
70
        from: String,
71
        to: Vec<String>,
72
        buf: Vec<u8>,
73
    },
74
}
75

            
76
#[allow(clippy::type_complexity)]
77
24
#[derive(Debug, Clone)]
78
pub struct TestSmtpHandler {
79
12
    address: Cow<'static, str>,
80
12
    ssl: SslConfig,
81
12
    envelope_from: Cow<'static, str>,
82
12
    auth: mailpot::melib::smtp::SmtpAuth,
83
12
    pub messages: Arc<Mutex<Vec<((IpAddr, String), Message)>>>,
84
12
    pub stored: Arc<Mutex<Vec<(String, mailpot::melib::Envelope)>>>,
85
}
86

            
87
impl Handler for TestSmtpHandler {
88
6
    fn helo(&mut self, ip: IpAddr, domain: &str) -> Response {
89
        //eprintln!("helo ip {:?} domain {:?}", ip, domain);
90
12
        self.messages
91
            .lock()
92
            .unwrap()
93
12
            .push(((ip, domain.to_string()), Message::Helo));
94
6
        OK
95
6
    }
96

            
97
8
    fn mail(&mut self, ip: IpAddr, domain: &str, from: &str) -> Response {
98
        //eprintln!("mail() ip {:?} domain {:?} from {:?}", ip, domain, from);
99
16
        if let Some((_, message)) = self
100
            .messages
101
            .lock()
102
            .unwrap()
103
            .iter_mut()
104
            .rev()
105
16
            .find(|((i, d), _)| (i, d.as_str()) == (&ip, domain))
106
        {
107
8
            if let Message::Helo = &message {
108
8
                *message = Message::Mail {
109
8
                    from: from.to_string(),
110
                };
111
8
                return OK;
112
            }
113
8
        }
114
        INTERNAL_ERROR
115
8
    }
116

            
117
8
    fn rcpt(&mut self, _to: &str) -> Response {
118
        //eprintln!("rcpt() to {:?}", _to);
119
8
        if let Some((_, message)) = self.messages.lock().unwrap().last_mut() {
120
8
            if let Message::Mail { from } = message {
121
8
                *message = Message::Rcpt {
122
8
                    from: from.clone(),
123
8
                    to: vec![_to.to_string()],
124
                };
125
8
                return OK;
126
            } else if let Message::Rcpt { to, .. } = message {
127
                to.push(_to.to_string());
128
                return OK;
129
            }
130
8
        }
131
        INTERNAL_ERROR
132
8
    }
133

            
134
8
    fn data_start(
135
        &mut self,
136
        _domain: &str,
137
        _from: &str,
138
        _is8bit: bool,
139
        _to: &[String],
140
    ) -> Response {
141
        // eprintln!( "data_start() domain {:?} from {:?} is8bit {:?} to {:?}", _domain,
142
        // _from, _is8bit, _to);
143
8
        if let Some(((_, d), ref mut message)) = self.messages.lock().unwrap().last_mut() {
144
8
            if d != _domain {
145
                return INTERNAL_ERROR;
146
            }
147
8
            if let Message::Rcpt { from, to } = message {
148
8
                *message = Message::DataStart {
149
8
                    from: from.to_string(),
150
8
                    to: to.to_vec(),
151
                };
152
8
                return OK;
153
            }
154
8
        }
155
        INTERNAL_ERROR
156
8
    }
157

            
158
156
    fn data(&mut self, _buf: &[u8]) -> Result<(), std::io::Error> {
159
156
        if let Some(((_, _), ref mut message)) = self.messages.lock().unwrap().last_mut() {
160
156
            if let Message::DataStart { from, to } = message {
161
8
                *message = Message::Data {
162
8
                    from: from.to_string(),
163
8
                    to: to.clone(),
164
8
                    buf: _buf.to_vec(),
165
                };
166
8
                return Ok(());
167
148
            } else if let Message::Data { buf, .. } = message {
168
148
                buf.extend(_buf.iter());
169
148
                return Ok(());
170
            }
171
156
        }
172
        Ok(())
173
156
    }
174

            
175
8
    fn data_end(&mut self) -> Response {
176
8
        let last = self.messages.lock().unwrap().pop();
177
8
        if let Some(((ip, domain), Message::Data { from: _, to, buf })) = last {
178
16
            for to in to {
179
8
                match mailpot::melib::Envelope::from_bytes(&buf, None) {
180
8
                    Ok(env) => {
181
8
                        self.stored.lock().unwrap().push((to.clone(), env));
182
                    }
183
                    Err(err) => {
184
                        panic!("envelope parse error {}", err);
185
                    }
186
                }
187
8
            }
188
16
            self.messages
189
                .lock()
190
                .unwrap()
191
16
                .push(((ip, domain), Message::Helo));
192
8
            return OK;
193
8
        }
194
        panic!("last self.messages item was not Message::Data: {last:?}"); //INTERNAL_ERROR
195
8
    }
196
}
197

            
198
impl TestSmtpHandler {
199
    #[inline]
200
    pub fn smtp_conf(&self) -> mailpot::melib::smtp::SmtpServerConf {
201
        use mailpot::melib::smtp::*;
202
        let sockaddr = self
203
            .address
204
            .as_ref()
205
            .to_socket_addrs()
206
            .unwrap()
207
            .next()
208
            .unwrap();
209
        let ip = sockaddr.ip();
210
        let port = sockaddr.port();
211

            
212
        SmtpServerConf {
213
            hostname: ip.to_string(),
214
            port,
215
            envelope_from: self.envelope_from.to_string(),
216
            auth: self.auth.clone(),
217
            security: SmtpSecurity::None,
218
            extensions: Default::default(),
219
        }
220
    }
221
}
222

            
223
impl TestSmtpHandler {
224
3
    pub fn builder() -> TestSmtpHandlerBuilder {
225
3
        TestSmtpHandlerBuilder::new()
226
3
    }
227
}
228

            
229
pub struct TestSmtpHandlerBuilder {
230
    address: Cow<'static, str>,
231
    ssl: SslConfig,
232
    auth: mailpot::melib::smtp::SmtpAuth,
233
    envelope_from: Cow<'static, str>,
234
}
235

            
236
impl TestSmtpHandlerBuilder {
237
3
    pub fn new() -> Self {
238
3
        Self {
239
3
            address: ADDRESS.into(),
240
3
            ssl: SslConfig::None,
241
3
            auth: mailpot::melib::smtp::SmtpAuth::None,
242
3
            envelope_from: "foo-chat@example.com".into(),
243
        }
244
3
    }
245

            
246
    pub fn address(self, address: impl Into<Cow<'static, str>>) -> Self {
247
        Self {
248
            address: address.into(),
249
            ..self
250
        }
251
    }
252

            
253
    pub fn ssl(self, ssl: SslConfig) -> Self {
254
        Self { ssl, ..self }
255
    }
256

            
257
3
    pub fn build(self) -> TestSmtpHandler {
258
        let Self {
259
3
            address,
260
3
            ssl,
261
3
            auth,
262
3
            envelope_from,
263
        } = self;
264
3
        let handler = TestSmtpHandler {
265
            address,
266
            ssl,
267
            auth,
268
            envelope_from,
269
6
            messages: Arc::new(Mutex::new(vec![])),
270
3
            stored: Arc::new(Mutex::new(vec![])),
271
        };
272
3
        crate::init_stderr_logging();
273
3
        let handler2 = handler.clone();
274
6
        let _smtp_handle = thread::spawn(move || {
275
3
            let address = handler2.address.clone();
276
3
            let ssl = handler2.ssl.clone();
277

            
278
3
            let mut server = Server::new(handler2.clone());
279
3
            let sockaddr = address.as_ref().to_socket_addrs().unwrap().next().unwrap();
280
3
            let ip = sockaddr.ip();
281
3
            let port = sockaddr.port();
282
3
            let addr = std::net::SocketAddr::new(ip, port);
283
3
            eprintln!("Running smtp server at {}", addr);
284
9
            server
285
                .with_name("example.com")
286
3
                .with_ssl((&ssl).into())
287
                .unwrap()
288
3
                .with_addr(addr)
289
                .unwrap();
290
3
            server.serve().expect("Could not run server");
291
3
        });
292
3
        handler
293
3
    }
294
}
295

            
296
/// Mirror struct for [`mailin_embedded::SslConfig`] because it does not
297
/// implement Debug or Clone.
298
15
#[derive(Clone, Debug)]
299
pub enum SslConfig {
300
    /// Do not support STARTTLS
301
    None,
302
    /// Use a self-signed certificate for STARTTLS
303
    SelfSigned {
304
        /// Certificate path
305
        cert_path: String,
306
        /// Path to key file
307
        key_path: String,
308
    },
309
    /// Use a certificate from an authority
310
    Trusted {
311
        /// Certificate path
312
        cert_path: String,
313
        /// Key file path
314
        key_path: String,
315
        /// Path to CA bundle
316
        chain_path: String,
317
    },
318
}
319

            
320
impl From<&SslConfig> for mailin_embedded::SslConfig {
321
3
    fn from(val: &SslConfig) -> Self {
322
3
        match val {
323
3
            SslConfig::None => Self::None,
324
            SslConfig::SelfSigned {
325
                ref cert_path,
326
                ref key_path,
327
            } => Self::SelfSigned {
328
                cert_path: cert_path.to_string(),
329
                key_path: key_path.to_string(),
330
            },
331
            SslConfig::Trusted {
332
                ref cert_path,
333
                ref key_path,
334
                ref chain_path,
335
            } => Self::Trusted {
336
                cert_path: cert_path.to_string(),
337
                key_path: key_path.to_string(),
338
                chain_path: chain_path.to_string(),
339
            },
340
        }
341
3
    }
342
}