deltachat/imap/
client.rs

1use std::net::SocketAddr;
2use std::ops::{Deref, DerefMut};
3
4use anyhow::{Context as _, Result};
5use async_imap::Client as ImapClient;
6use async_imap::Session as ImapSession;
7use tokio::io::BufWriter;
8
9use super::capabilities::Capabilities;
10use crate::context::Context;
11use crate::log::{LoggingStream, warn};
12use crate::net::dns::{lookup_host_with_cache, update_connect_timestamp};
13use crate::net::proxy::ProxyConfig;
14use crate::net::session::SessionStream;
15use crate::net::tls::wrap_tls;
16use crate::net::{connect_tcp_inner, run_connection_attempts, update_connection_history};
17use crate::tools::time;
18use crate::transport::ConnectionCandidate;
19use crate::transport::ConnectionSecurity;
20
21#[derive(Debug)]
22pub(crate) struct Client {
23    inner: ImapClient<Box<dyn SessionStream>>,
24}
25
26impl Deref for Client {
27    type Target = ImapClient<Box<dyn SessionStream>>;
28
29    fn deref(&self) -> &Self::Target {
30        &self.inner
31    }
32}
33
34impl DerefMut for Client {
35    fn deref_mut(&mut self) -> &mut Self::Target {
36        &mut self.inner
37    }
38}
39
40/// Converts port number to ALPN list.
41fn alpn(port: u16) -> &'static str {
42    if port == 993 {
43        // Do not request ALPN on standard port.
44        ""
45    } else {
46        "imap"
47    }
48}
49
50/// Determine server capabilities.
51///
52/// If server supports ID capability, send our client ID.
53pub(crate) async fn determine_capabilities(
54    session: &mut ImapSession<Box<dyn SessionStream>>,
55) -> Result<Capabilities> {
56    let caps = session
57        .capabilities()
58        .await
59        .context("CAPABILITY command error")?;
60    let server_id = if caps.has_str("ID") {
61        session.id([("name", Some("Delta Chat"))]).await?
62    } else {
63        None
64    };
65    let capabilities = Capabilities {
66        can_idle: caps.has_str("IDLE"),
67        can_move: caps.has_str("MOVE"),
68        can_check_quota: caps.has_str("QUOTA"),
69        can_condstore: caps.has_str("CONDSTORE"),
70        can_metadata: caps.has_str("METADATA"),
71        can_compress: caps.has_str("COMPRESS=DEFLATE"),
72        can_push: caps.has_str("XDELTAPUSH"),
73        is_chatmail: caps.has_str("XCHATMAIL"),
74        server_id,
75    };
76    Ok(capabilities)
77}
78
79impl Client {
80    fn new(stream: Box<dyn SessionStream>) -> Self {
81        Self {
82            inner: ImapClient::new(stream),
83        }
84    }
85
86    pub(crate) async fn login(
87        self,
88        username: &str,
89        password: &str,
90    ) -> Result<ImapSession<Box<dyn SessionStream>>> {
91        let Client { inner, .. } = self;
92
93        let session = inner
94            .login(username, password)
95            .await
96            .map_err(|(err, _client)| err)?;
97        Ok(session)
98    }
99
100    pub(crate) async fn authenticate(
101        self,
102        auth_type: &str,
103        authenticator: impl async_imap::Authenticator,
104    ) -> Result<ImapSession<Box<dyn SessionStream>>> {
105        let Client { inner, .. } = self;
106        let session = inner
107            .authenticate(auth_type, authenticator)
108            .await
109            .map_err(|(err, _client)| err)?;
110        Ok(session)
111    }
112
113    async fn connection_attempt(
114        context: Context,
115        host: String,
116        security: ConnectionSecurity,
117        resolved_addr: SocketAddr,
118        strict_tls: bool,
119    ) -> Result<Self> {
120        let context = &context;
121        let host = &host;
122        info!(
123            context,
124            "Attempting IMAP connection to {host} ({resolved_addr})."
125        );
126        let res = match security {
127            ConnectionSecurity::Tls => {
128                Client::connect_secure(context, resolved_addr, host, strict_tls).await
129            }
130            ConnectionSecurity::Starttls => {
131                Client::connect_starttls(context, resolved_addr, host, strict_tls).await
132            }
133            ConnectionSecurity::Plain => Client::connect_insecure(context, resolved_addr).await,
134        };
135        match res {
136            Ok(client) => {
137                let ip_addr = resolved_addr.ip().to_string();
138                let port = resolved_addr.port();
139
140                let save_cache = match security {
141                    ConnectionSecurity::Tls | ConnectionSecurity::Starttls => strict_tls,
142                    ConnectionSecurity::Plain => false,
143                };
144                if save_cache {
145                    update_connect_timestamp(context, host, &ip_addr).await?;
146                }
147                update_connection_history(context, "imap", host, port, &ip_addr, time()).await?;
148                Ok(client)
149            }
150            Err(err) => {
151                warn!(
152                    context,
153                    "Failed to connect to {host} ({resolved_addr}): {err:#}."
154                );
155                Err(err)
156            }
157        }
158    }
159
160    pub async fn connect(
161        context: &Context,
162        proxy_config: Option<ProxyConfig>,
163        strict_tls: bool,
164        candidate: ConnectionCandidate,
165    ) -> Result<Self> {
166        let host = &candidate.host;
167        let port = candidate.port;
168        let security = candidate.security;
169        if let Some(proxy_config) = proxy_config {
170            let client = match security {
171                ConnectionSecurity::Tls => {
172                    Client::connect_secure_proxy(context, host, port, strict_tls, proxy_config)
173                        .await?
174                }
175                ConnectionSecurity::Starttls => {
176                    Client::connect_starttls_proxy(context, host, port, proxy_config, strict_tls)
177                        .await?
178                }
179                ConnectionSecurity::Plain => {
180                    Client::connect_insecure_proxy(context, host, port, proxy_config).await?
181                }
182            };
183            update_connection_history(context, "imap", host, port, host, time()).await?;
184            Ok(client)
185        } else {
186            let load_cache = match security {
187                ConnectionSecurity::Tls | ConnectionSecurity::Starttls => strict_tls,
188                ConnectionSecurity::Plain => false,
189            };
190
191            let connection_futures =
192                lookup_host_with_cache(context, host, port, "imap", load_cache)
193                    .await?
194                    .into_iter()
195                    .map(|resolved_addr| {
196                        let context = context.clone();
197                        let host = host.to_string();
198                        Self::connection_attempt(context, host, security, resolved_addr, strict_tls)
199                    });
200            run_connection_attempts(connection_futures).await
201        }
202    }
203
204    async fn connect_secure(
205        context: &Context,
206        addr: SocketAddr,
207        hostname: &str,
208        strict_tls: bool,
209    ) -> Result<Self> {
210        let use_sni = true;
211        let tcp_stream = connect_tcp_inner(addr).await?;
212        let account_id = context.get_id();
213        let events = context.events.clone();
214        let logging_stream = LoggingStream::new(tcp_stream, account_id, events)?;
215        let tls_stream = wrap_tls(
216            strict_tls,
217            hostname,
218            addr.port(),
219            use_sni,
220            alpn(addr.port()),
221            logging_stream,
222            &context.tls_session_store,
223        )
224        .await?;
225        let buffered_stream = BufWriter::new(tls_stream);
226        let session_stream: Box<dyn SessionStream> = Box::new(buffered_stream);
227        let mut client = Client::new(session_stream);
228        let _greeting = client
229            .read_response()
230            .await?
231            .context("Failed to read greeting")?;
232        Ok(client)
233    }
234
235    async fn connect_insecure(context: &Context, addr: SocketAddr) -> Result<Self> {
236        let tcp_stream = connect_tcp_inner(addr).await?;
237        let account_id = context.get_id();
238        let events = context.events.clone();
239        let logging_stream = LoggingStream::new(tcp_stream, account_id, events)?;
240        let buffered_stream = BufWriter::new(logging_stream);
241        let session_stream: Box<dyn SessionStream> = Box::new(buffered_stream);
242        let mut client = Client::new(session_stream);
243        let _greeting = client
244            .read_response()
245            .await?
246            .context("Failed to read greeting")?;
247        Ok(client)
248    }
249
250    async fn connect_starttls(
251        context: &Context,
252        addr: SocketAddr,
253        host: &str,
254        strict_tls: bool,
255    ) -> Result<Self> {
256        let use_sni = false;
257        let tcp_stream = connect_tcp_inner(addr).await?;
258
259        let account_id = context.get_id();
260        let events = context.events.clone();
261        let tcp_stream = LoggingStream::new(tcp_stream, account_id, events)?;
262
263        // Run STARTTLS command and convert the client back into a stream.
264        let buffered_tcp_stream = BufWriter::new(tcp_stream);
265        let mut client = async_imap::Client::new(buffered_tcp_stream);
266        let _greeting = client
267            .read_response()
268            .await?
269            .context("Failed to read greeting")?;
270        client
271            .run_command_and_check_ok("STARTTLS", None)
272            .await
273            .context("STARTTLS command failed")?;
274        let buffered_tcp_stream = client.into_inner();
275        let tcp_stream = buffered_tcp_stream.into_inner();
276
277        let tls_stream = wrap_tls(
278            strict_tls,
279            host,
280            addr.port(),
281            use_sni,
282            "",
283            tcp_stream,
284            &context.tls_session_store,
285        )
286        .await
287        .context("STARTTLS upgrade failed")?;
288        let buffered_stream = BufWriter::new(tls_stream);
289        let session_stream: Box<dyn SessionStream> = Box::new(buffered_stream);
290        let client = Client::new(session_stream);
291        Ok(client)
292    }
293
294    async fn connect_secure_proxy(
295        context: &Context,
296        domain: &str,
297        port: u16,
298        strict_tls: bool,
299        proxy_config: ProxyConfig,
300    ) -> Result<Self> {
301        let use_sni = true;
302        let proxy_stream = proxy_config
303            .connect(context, domain, port, strict_tls)
304            .await?;
305        let tls_stream = wrap_tls(
306            strict_tls,
307            domain,
308            port,
309            use_sni,
310            alpn(port),
311            proxy_stream,
312            &context.tls_session_store,
313        )
314        .await?;
315        let buffered_stream = BufWriter::new(tls_stream);
316        let session_stream: Box<dyn SessionStream> = Box::new(buffered_stream);
317        let mut client = Client::new(session_stream);
318        let _greeting = client
319            .read_response()
320            .await?
321            .context("Failed to read greeting")?;
322        Ok(client)
323    }
324
325    async fn connect_insecure_proxy(
326        context: &Context,
327        domain: &str,
328        port: u16,
329        proxy_config: ProxyConfig,
330    ) -> Result<Self> {
331        let proxy_stream = proxy_config.connect(context, domain, port, false).await?;
332        let buffered_stream = BufWriter::new(proxy_stream);
333        let session_stream: Box<dyn SessionStream> = Box::new(buffered_stream);
334        let mut client = Client::new(session_stream);
335        let _greeting = client
336            .read_response()
337            .await?
338            .context("Failed to read greeting")?;
339        Ok(client)
340    }
341
342    async fn connect_starttls_proxy(
343        context: &Context,
344        hostname: &str,
345        port: u16,
346        proxy_config: ProxyConfig,
347        strict_tls: bool,
348    ) -> Result<Self> {
349        let use_sni = false;
350        let proxy_stream = proxy_config
351            .connect(context, hostname, port, strict_tls)
352            .await?;
353
354        // Run STARTTLS command and convert the client back into a stream.
355        let buffered_proxy_stream = BufWriter::new(proxy_stream);
356        let mut client = ImapClient::new(buffered_proxy_stream);
357        let _greeting = client
358            .read_response()
359            .await?
360            .context("Failed to read greeting")?;
361        client
362            .run_command_and_check_ok("STARTTLS", None)
363            .await
364            .context("STARTTLS command failed")?;
365        let buffered_proxy_stream = client.into_inner();
366        let proxy_stream = buffered_proxy_stream.into_inner();
367
368        let tls_stream = wrap_tls(
369            strict_tls,
370            hostname,
371            port,
372            use_sni,
373            "",
374            proxy_stream,
375            &context.tls_session_store,
376        )
377        .await
378        .context("STARTTLS upgrade failed")?;
379        let buffered_stream = BufWriter::new(tls_stream);
380        let session_stream: Box<dyn SessionStream> = Box::new(buffered_stream);
381        let client = Client::new(session_stream);
382        Ok(client)
383    }
384}