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 tcp_stream = connect_tcp_inner(addr).await?;
211        let account_id = context.get_id();
212        let events = context.events.clone();
213        let logging_stream = LoggingStream::new(tcp_stream, account_id, events)?;
214        let tls_stream = wrap_tls(
215            strict_tls,
216            hostname,
217            addr.port(),
218            alpn(addr.port()),
219            logging_stream,
220            &context.tls_session_store,
221        )
222        .await?;
223        let buffered_stream = BufWriter::new(tls_stream);
224        let session_stream: Box<dyn SessionStream> = Box::new(buffered_stream);
225        let mut client = Client::new(session_stream);
226        let _greeting = client
227            .read_response()
228            .await?
229            .context("Failed to read greeting")?;
230        Ok(client)
231    }
232
233    async fn connect_insecure(context: &Context, addr: SocketAddr) -> Result<Self> {
234        let tcp_stream = connect_tcp_inner(addr).await?;
235        let account_id = context.get_id();
236        let events = context.events.clone();
237        let logging_stream = LoggingStream::new(tcp_stream, account_id, events)?;
238        let buffered_stream = BufWriter::new(logging_stream);
239        let session_stream: Box<dyn SessionStream> = Box::new(buffered_stream);
240        let mut client = Client::new(session_stream);
241        let _greeting = client
242            .read_response()
243            .await?
244            .context("Failed to read greeting")?;
245        Ok(client)
246    }
247
248    async fn connect_starttls(
249        context: &Context,
250        addr: SocketAddr,
251        host: &str,
252        strict_tls: bool,
253    ) -> Result<Self> {
254        let tcp_stream = connect_tcp_inner(addr).await?;
255
256        let account_id = context.get_id();
257        let events = context.events.clone();
258        let tcp_stream = LoggingStream::new(tcp_stream, account_id, events)?;
259
260        // Run STARTTLS command and convert the client back into a stream.
261        let buffered_tcp_stream = BufWriter::new(tcp_stream);
262        let mut client = async_imap::Client::new(buffered_tcp_stream);
263        let _greeting = client
264            .read_response()
265            .await?
266            .context("Failed to read greeting")?;
267        client
268            .run_command_and_check_ok("STARTTLS", None)
269            .await
270            .context("STARTTLS command failed")?;
271        let buffered_tcp_stream = client.into_inner();
272        let tcp_stream = buffered_tcp_stream.into_inner();
273
274        let tls_stream = wrap_tls(
275            strict_tls,
276            host,
277            addr.port(),
278            "",
279            tcp_stream,
280            &context.tls_session_store,
281        )
282        .await
283        .context("STARTTLS upgrade failed")?;
284        let buffered_stream = BufWriter::new(tls_stream);
285        let session_stream: Box<dyn SessionStream> = Box::new(buffered_stream);
286        let client = Client::new(session_stream);
287        Ok(client)
288    }
289
290    async fn connect_secure_proxy(
291        context: &Context,
292        domain: &str,
293        port: u16,
294        strict_tls: bool,
295        proxy_config: ProxyConfig,
296    ) -> Result<Self> {
297        let proxy_stream = proxy_config
298            .connect(context, domain, port, strict_tls)
299            .await?;
300        let tls_stream = wrap_tls(
301            strict_tls,
302            domain,
303            port,
304            alpn(port),
305            proxy_stream,
306            &context.tls_session_store,
307        )
308        .await?;
309        let buffered_stream = BufWriter::new(tls_stream);
310        let session_stream: Box<dyn SessionStream> = Box::new(buffered_stream);
311        let mut client = Client::new(session_stream);
312        let _greeting = client
313            .read_response()
314            .await?
315            .context("Failed to read greeting")?;
316        Ok(client)
317    }
318
319    async fn connect_insecure_proxy(
320        context: &Context,
321        domain: &str,
322        port: u16,
323        proxy_config: ProxyConfig,
324    ) -> Result<Self> {
325        let proxy_stream = proxy_config.connect(context, domain, port, false).await?;
326        let buffered_stream = BufWriter::new(proxy_stream);
327        let session_stream: Box<dyn SessionStream> = Box::new(buffered_stream);
328        let mut client = Client::new(session_stream);
329        let _greeting = client
330            .read_response()
331            .await?
332            .context("Failed to read greeting")?;
333        Ok(client)
334    }
335
336    async fn connect_starttls_proxy(
337        context: &Context,
338        hostname: &str,
339        port: u16,
340        proxy_config: ProxyConfig,
341        strict_tls: bool,
342    ) -> Result<Self> {
343        let proxy_stream = proxy_config
344            .connect(context, hostname, port, strict_tls)
345            .await?;
346
347        // Run STARTTLS command and convert the client back into a stream.
348        let buffered_proxy_stream = BufWriter::new(proxy_stream);
349        let mut client = ImapClient::new(buffered_proxy_stream);
350        let _greeting = client
351            .read_response()
352            .await?
353            .context("Failed to read greeting")?;
354        client
355            .run_command_and_check_ok("STARTTLS", None)
356            .await
357            .context("STARTTLS command failed")?;
358        let buffered_proxy_stream = client.into_inner();
359        let proxy_stream = buffered_proxy_stream.into_inner();
360
361        let tls_stream = wrap_tls(
362            strict_tls,
363            hostname,
364            port,
365            "",
366            proxy_stream,
367            &context.tls_session_store,
368        )
369        .await
370        .context("STARTTLS upgrade failed")?;
371        let buffered_stream = BufWriter::new(tls_stream);
372        let session_stream: Box<dyn SessionStream> = Box::new(buffered_stream);
373        let client = Client::new(session_stream);
374        Ok(client)
375    }
376}