1
177
/*
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 mailpot::{
37
    melib::{self, smol, smtp::SmtpServerConf},
38
    models::{changesets::ListSubscriptionChangeset, *},
39
    Configuration, Connection, Queue, SendMail,
40
};
41
pub use predicates;
42
pub use tempfile::{self, TempDir};
43

            
44
static INIT_STDERR_LOGGING: Once = Once::new();
45

            
46
13
pub fn init_stderr_logging() {
47
25
    INIT_STDERR_LOGGING.call_once(|| {
48
24
        stderrlog::new()
49
            .quiet(false)
50
            .verbosity(15)
51
            .show_module_names(true)
52
12
            .timestamp(stderrlog::Timestamp::Millisecond)
53
            .init()
54
12
            .unwrap();
55
12
    });
56
13
}
57
pub const ADDRESS: &str = "127.0.0.1:8825";
58

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

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

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

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

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

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

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

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

            
203
impl TestSmtpHandler {
204
    #[inline]
205
    pub fn smtp_conf(&self) -> melib::smtp::SmtpServerConf {
206
        use melib::smtp::*;
207
        let sockaddr = self
208
            .address
209
            .as_ref()
210
            .to_socket_addrs()
211
            .unwrap()
212
            .next()
213
            .unwrap();
214
        let ip = sockaddr.ip();
215
        let port = sockaddr.port();
216

            
217
        SmtpServerConf {
218
            hostname: ip.to_string(),
219
            port,
220
            envelope_from: self.envelope_from.to_string(),
221
            auth: self.auth.clone(),
222
            security: SmtpSecurity::None,
223
            extensions: Default::default(),
224
        }
225
    }
226
}
227

            
228
impl TestSmtpHandler {
229
3
    pub fn builder() -> TestSmtpHandlerBuilder {
230
3
        TestSmtpHandlerBuilder::new()
231
3
    }
232
}
233

            
234
pub struct TestSmtpHandlerBuilder {
235
    address: Cow<'static, str>,
236
    ssl: SslConfig,
237
    auth: melib::smtp::SmtpAuth,
238
    envelope_from: Cow<'static, str>,
239
}
240

            
241
impl TestSmtpHandlerBuilder {
242
3
    pub fn new() -> Self {
243
3
        Self {
244
3
            address: ADDRESS.into(),
245
3
            ssl: SslConfig::None,
246
3
            auth: melib::smtp::SmtpAuth::None,
247
3
            envelope_from: "foo-chat@example.com".into(),
248
        }
249
3
    }
250

            
251
    pub fn address(self, address: impl Into<Cow<'static, str>>) -> Self {
252
        Self {
253
            address: address.into(),
254
            ..self
255
        }
256
    }
257

            
258
    pub fn ssl(self, ssl: SslConfig) -> Self {
259
        Self { ssl, ..self }
260
    }
261

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

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

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

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