1use std::{
8 net::{IpAddr, SocketAddr},
9 time::Duration,
10};
11
12use smtp_proto::{Response, response::parser::ResponseReceiver};
13use tokio::{
14 io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt},
15 net::{TcpSocket, TcpStream},
16};
17
18use crate::SmtpClient;
19
20impl<T: AsyncRead + AsyncWrite + Unpin> SmtpClient<T> {
21 pub async fn read(&mut self) -> crate::Result<Response<String>> {
22 let mut buf = vec![0u8; 1024];
23 let mut parser = ResponseReceiver::default();
24
25 loop {
26 let br = self.stream.read(&mut buf).await?;
27
28 if br > 0 {
29 match parser.parse(&mut buf[..br].iter()) {
30 Ok(reply) => return Ok(reply),
31 Err(err) => match err {
32 smtp_proto::Error::NeedsMoreData { .. } => (),
33 _ => {
34 return Err(crate::Error::UnparseableReply);
35 }
36 },
37 }
38 } else {
39 return Err(crate::Error::UnparseableReply);
40 }
41 }
42 }
43
44 pub async fn read_many(&mut self, num: usize) -> crate::Result<Vec<Response<String>>> {
45 let mut buf = vec![0u8; 1024];
46 let mut response = Vec::with_capacity(num);
47 let mut parser = ResponseReceiver::default();
48
49 'outer: loop {
50 let br = self.stream.read(&mut buf).await?;
51
52 if br > 0 {
53 let mut iter = buf[..br].iter();
54
55 loop {
56 match parser.parse(&mut iter) {
57 Ok(reply) => {
58 response.push(reply);
59 if response.len() != num {
60 parser.reset();
61 } else {
62 break 'outer;
63 }
64 }
65 Err(err) => match err {
66 smtp_proto::Error::NeedsMoreData { .. } => break,
67 _ => {
68 return Err(crate::Error::UnparseableReply);
69 }
70 },
71 }
72 }
73 } else {
74 return Err(crate::Error::UnparseableReply);
75 }
76 }
77
78 Ok(response)
79 }
80
81 pub async fn cmd(&mut self, cmd: impl AsRef<[u8]>) -> crate::Result<Response<String>> {
83 tokio::time::timeout(self.timeout, async {
84 self.stream.write_all(cmd.as_ref()).await?;
85 self.stream.flush().await?;
86 self.read().await
87 })
88 .await
89 .map_err(|_| crate::Error::Timeout)?
90 }
91
92 pub async fn cmds(
94 &mut self,
95 cmds: impl IntoIterator<Item = impl AsRef<[u8]>>,
96 ) -> crate::Result<Vec<Response<String>>> {
97 tokio::time::timeout(self.timeout, async {
98 let mut num_replies = 0;
99 for cmd in cmds {
100 self.stream.write_all(cmd.as_ref()).await?;
101 num_replies += 1;
102 }
103 self.stream.flush().await?;
104 self.read_many(num_replies).await
105 })
106 .await
107 .map_err(|_| crate::Error::Timeout)?
108 }
109}
110
111impl SmtpClient<TcpStream> {
112 pub async fn connect(remote_addr: SocketAddr, timeout: Duration) -> crate::Result<Self> {
114 tokio::time::timeout(timeout, async {
115 Ok(SmtpClient {
116 stream: TcpStream::connect(remote_addr).await?,
117 timeout,
118 })
119 })
120 .await
121 .map_err(|_| crate::Error::Timeout)?
122 }
123
124 pub async fn connect_using(
126 local_ip: IpAddr,
127 remote_addr: SocketAddr,
128 timeout: Duration,
129 ) -> crate::Result<Self> {
130 tokio::time::timeout(timeout, async {
131 let socket = if local_ip.is_ipv4() {
132 TcpSocket::new_v4()?
133 } else {
134 TcpSocket::new_v6()?
135 };
136 socket.bind(SocketAddr::new(local_ip, 0))?;
137
138 Ok(SmtpClient {
139 stream: socket.connect(remote_addr).await?,
140 timeout,
141 })
142 })
143 .await
144 .map_err(|_| crate::Error::Timeout)?
145 }
146}
147
148#[cfg(test)]
149mod test {
150 use std::time::Duration;
151
152 use tokio::io::{AsyncRead, AsyncWrite};
153
154 use crate::{SmtpClient, SmtpClientBuilder};
155
156 #[tokio::test]
157 async fn smtp_basic() {
158 env_logger::init();
160 let client = SmtpClientBuilder::new("mail.smtp2go.com", 2525)
161 .unwrap()
162 .implicit_tls(false)
163 .connect()
164 .await
165 .unwrap();
166 client.quit().await.unwrap();
167 let client = SmtpClientBuilder::new("mail.smtp2go.com", 2525)
168 .unwrap()
169 .allow_invalid_certs()
170 .implicit_tls(false)
171 .connect()
172 .await
173 .unwrap();
174 client.quit().await.unwrap();
175
176 let client = SmtpClientBuilder::new("smtp.gmail.com", 465)
178 .unwrap()
179 .connect()
180 .await
181 .unwrap();
182 client.quit().await.unwrap();
183
184 let client = SmtpClientBuilder::new("smtp.gmail.com", 465)
186 .unwrap()
187 .allow_invalid_certs()
188 .connect()
189 .await
190 .unwrap();
191 client.quit().await.unwrap();
192 }
193
194 #[derive(Default)]
195 struct AsyncBufWriter {
196 buf: Vec<u8>,
197 }
198
199 impl AsyncRead for AsyncBufWriter {
200 fn poll_read(
201 self: std::pin::Pin<&mut Self>,
202 _cx: &mut std::task::Context<'_>,
203 _buf: &mut tokio::io::ReadBuf<'_>,
204 ) -> std::task::Poll<std::io::Result<()>> {
205 unreachable!()
206 }
207 }
208
209 impl AsyncWrite for AsyncBufWriter {
210 fn poll_write(
211 mut self: std::pin::Pin<&mut Self>,
212 _cx: &mut std::task::Context<'_>,
213 buf: &[u8],
214 ) -> std::task::Poll<Result<usize, std::io::Error>> {
215 self.buf.extend_from_slice(buf);
216 std::task::Poll::Ready(Ok(buf.len()))
217 }
218
219 fn poll_flush(
220 self: std::pin::Pin<&mut Self>,
221 _cx: &mut std::task::Context<'_>,
222 ) -> std::task::Poll<Result<(), std::io::Error>> {
223 std::task::Poll::Ready(Ok(()))
224 }
225
226 fn poll_shutdown(
227 self: std::pin::Pin<&mut Self>,
228 _cx: &mut std::task::Context<'_>,
229 ) -> std::task::Poll<Result<(), std::io::Error>> {
230 std::task::Poll::Ready(Ok(()))
231 }
232 }
233
234 #[tokio::test]
235 async fn transparency_procedure() {
236 const SMUGGLER: &str = r#"From: Joe SixPack <john@foobar.net>
237To: Suzie Q <suzie@foobar.org>
238Subject: Is dinner ready?
239
240Hi.
241
242We lost the game. Are you hungry yet?
243
244Joe.
245
246<SEP>.
247MAIL FROM:<admin@foobar.net>
248RCPT TO:<ok@foobar.org>
249DATA
250From: Joe SixPack <admin@foobar.net>
251To: Suzie Q <suzie@foobar.org>
252Subject: smuggled message
253
254This is a smuggled message
255"#;
256
257 for (test, result) in [
258 (
259 "A: b\r\n.\r\n".to_string(),
260 "A: b\r\n..\r\n\r\n.\r\n".to_string(),
261 ),
262 ("A: b\r\n.".to_string(), "A: b\r\n..\r\n.\r\n".to_string()),
263 (
264 "A: b\r\n..\r\n".to_string(),
265 "A: b\r\n...\r\n\r\n.\r\n".to_string(),
266 ),
267 ("A: ...b".to_string(), "A: ...b\r\n.\r\n".to_string()),
268 (
269 "A: \n.\r\nMAIL FROM:<>".to_string(),
270 "A: \n..\r\nMAIL FROM:<>\r\n.\r\n".to_string(),
271 ),
272 (
273 "A: \r.\r\nMAIL FROM:<>".to_string(),
274 "A: \r..\r\nMAIL FROM:<>\r\n.\r\n".to_string(),
275 ),
276 (
277 SMUGGLER
278 .replace('\r', "")
279 .replace('\n', "\r\n")
280 .replace("<SEP>", "\r"),
281 SMUGGLER
282 .replace('\r', "")
283 .replace('\n', "\r\n")
284 .replace("<SEP>", "\r.")
285 + "\r\n.\r\n",
286 ),
287 (
288 SMUGGLER
289 .replace('\r', "")
290 .replace('\n', "\r\n")
291 .replace("<SEP>", "\n"),
292 SMUGGLER
293 .replace('\r', "")
294 .replace('\n', "\r\n")
295 .replace("<SEP>", "\n.")
296 + "\r\n.\r\n",
297 ),
298 ] {
299 let mut client = SmtpClient {
300 stream: AsyncBufWriter::default(),
301 timeout: Duration::from_secs(30),
302 };
303 client.write_message(test.as_bytes()).await.unwrap();
304 assert_eq!(String::from_utf8(client.stream.buf).unwrap(), result);
305 }
306 }
307}