mail_send/smtp/
ehlo.rs

1/*
2 * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
3 *
4 * SPDX-License-Identifier: Apache-2.0 OR MIT
5 */
6
7use smtp_proto::{
8    response::parser::{ResponseReceiver, MAX_RESPONSE_LENGTH},
9    EhloResponse,
10};
11use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
12
13use crate::SmtpClient;
14
15impl<T: AsyncRead + AsyncWrite + Unpin> SmtpClient<T> {
16    /// Sends a EHLO command to the server.
17    pub async fn ehlo(&mut self, hostname: &str) -> crate::Result<EhloResponse<String>> {
18        tokio::time::timeout(self.timeout, async {
19            self.stream
20                .write_all(format!("EHLO {hostname}\r\n").as_bytes())
21                .await?;
22            self.stream.flush().await?;
23            self.read_ehlo().await
24        })
25        .await
26        .map_err(|_| crate::Error::Timeout)?
27    }
28
29    /// Sends a LHLO command to the server.
30    pub async fn lhlo(&mut self, hostname: &str) -> crate::Result<EhloResponse<String>> {
31        tokio::time::timeout(self.timeout, async {
32            self.stream
33                .write_all(format!("LHLO {hostname}\r\n").as_bytes())
34                .await?;
35            self.stream.flush().await?;
36            self.read_ehlo().await
37        })
38        .await
39        .map_err(|_| crate::Error::Timeout)?
40    }
41
42    pub async fn read_ehlo(&mut self) -> crate::Result<EhloResponse<String>> {
43        let mut buf = vec![0u8; 1024];
44        let mut buf_concat = Vec::with_capacity(0);
45
46        loop {
47            let br = self.stream.read(&mut buf).await?;
48
49            if br == 0 {
50                return Err(crate::Error::UnparseableReply);
51            }
52            let mut iter = if buf_concat.is_empty() {
53                buf[..br].iter()
54            } else if br + buf_concat.len() < MAX_RESPONSE_LENGTH {
55                buf_concat.extend_from_slice(&buf[..br]);
56                buf_concat.iter()
57            } else {
58                return Err(crate::Error::UnparseableReply);
59            };
60
61            match EhloResponse::parse(&mut iter) {
62                Ok(reply) => return Ok(reply),
63                Err(err) => match err {
64                    smtp_proto::Error::NeedsMoreData { .. } => {
65                        if buf_concat.is_empty() {
66                            buf_concat = buf[..br].to_vec();
67                        }
68                    }
69                    smtp_proto::Error::InvalidResponse { code } => {
70                        match ResponseReceiver::from_code(code).parse(&mut iter) {
71                            Ok(response) => {
72                                return Err(crate::Error::UnexpectedReply(response));
73                            }
74                            Err(smtp_proto::Error::NeedsMoreData { .. }) => {
75                                if buf_concat.is_empty() {
76                                    buf_concat = buf[..br].to_vec();
77                                }
78                            }
79                            Err(_) => return Err(crate::Error::UnparseableReply),
80                        }
81                    }
82                    _ => {
83                        return Err(crate::Error::UnparseableReply);
84                    }
85                },
86            }
87        }
88    }
89}