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
40fn alpn(port: u16) -> &'static str {
42 if port == 993 {
43 ""
45 } else {
46 "imap"
47 }
48}
49
50pub(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_metadata: caps.has_str("METADATA"),
70 can_compress: caps.has_str("COMPRESS=DEFLATE"),
71 can_push: caps.has_str("XDELTAPUSH"),
72 is_chatmail: caps.has_str("XCHATMAIL"),
73 server_id,
74 };
75 Ok(capabilities)
76}
77
78impl Client {
79 fn new(stream: Box<dyn SessionStream>) -> Self {
80 Self {
81 inner: ImapClient::new(stream),
82 }
83 }
84
85 pub(crate) async fn login(
86 self,
87 username: &str,
88 password: &str,
89 ) -> Result<ImapSession<Box<dyn SessionStream>>> {
90 let Client { inner, .. } = self;
91
92 let session = inner
93 .login(username, password)
94 .await
95 .map_err(|(err, _client)| err)?;
96 Ok(session)
97 }
98
99 pub(crate) async fn authenticate(
100 self,
101 auth_type: &str,
102 authenticator: impl async_imap::Authenticator,
103 ) -> Result<ImapSession<Box<dyn SessionStream>>> {
104 let Client { inner, .. } = self;
105 let session = inner
106 .authenticate(auth_type, authenticator)
107 .await
108 .map_err(|(err, _client)| err)?;
109 Ok(session)
110 }
111
112 async fn connection_attempt(
113 context: Context,
114 host: String,
115 security: ConnectionSecurity,
116 resolved_addr: SocketAddr,
117 strict_tls: bool,
118 ) -> Result<Self> {
119 let context = &context;
120 let host = &host;
121 info!(
122 context,
123 "Attempting IMAP connection to {host} ({resolved_addr})."
124 );
125 let res = match security {
126 ConnectionSecurity::Tls => {
127 Client::connect_secure(context, resolved_addr, host, strict_tls).await
128 }
129 ConnectionSecurity::Starttls => {
130 Client::connect_starttls(context, resolved_addr, host, strict_tls).await
131 }
132 ConnectionSecurity::Plain => Client::connect_insecure(context, resolved_addr).await,
133 };
134 match res {
135 Ok(client) => {
136 let ip_addr = resolved_addr.ip().to_string();
137 let port = resolved_addr.port();
138
139 let save_cache = match security {
140 ConnectionSecurity::Tls | ConnectionSecurity::Starttls => strict_tls,
141 ConnectionSecurity::Plain => false,
142 };
143 if save_cache {
144 update_connect_timestamp(context, host, &ip_addr).await?;
145 }
146 update_connection_history(context, "imap", host, port, &ip_addr, time()).await?;
147 Ok(client)
148 }
149 Err(err) => {
150 warn!(
151 context,
152 "IMAP failed to connect to {host} ({resolved_addr}): {err:#}."
153 );
154 Err(err)
155 }
156 }
157 }
158
159 pub async fn connect(
160 context: &Context,
161 proxy_config: Option<ProxyConfig>,
162 strict_tls: bool,
163 candidate: &ConnectionCandidate,
164 ) -> Result<Self> {
165 let host = &candidate.host;
166 let port = candidate.port;
167 let security = candidate.security;
168 if let Some(proxy_config) = proxy_config {
169 let client = match security {
170 ConnectionSecurity::Tls => {
171 Client::connect_secure_proxy(context, host, port, strict_tls, proxy_config)
172 .await?
173 }
174 ConnectionSecurity::Starttls => {
175 Client::connect_starttls_proxy(context, host, port, proxy_config, strict_tls)
176 .await?
177 }
178 ConnectionSecurity::Plain => {
179 Client::connect_insecure_proxy(context, host, port, proxy_config).await?
180 }
181 };
182 update_connection_history(context, "imap", host, port, host, time()).await?;
183 Ok(client)
184 } else {
185 let load_cache = match security {
186 ConnectionSecurity::Tls | ConnectionSecurity::Starttls => strict_tls,
187 ConnectionSecurity::Plain => false,
188 };
189
190 let connection_futures =
191 lookup_host_with_cache(context, host, port, "imap", load_cache)
192 .await?
193 .into_iter()
194 .map(|resolved_addr| {
195 let context = context.clone();
196 let host = host.to_string();
197 Self::connection_attempt(context, host, security, resolved_addr, strict_tls)
198 });
199 run_connection_attempts(connection_futures).await
200 }
201 }
202
203 async fn connect_secure(
204 context: &Context,
205 addr: SocketAddr,
206 hostname: &str,
207 strict_tls: bool,
208 ) -> Result<Self> {
209 let use_sni = true;
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 use_sni,
219 alpn(addr.port()),
220 logging_stream,
221 &context.tls_session_store,
222 &context.spki_hash_store,
223 &context.sql,
224 )
225 .await?;
226 let buffered_stream = BufWriter::new(tls_stream);
227 let session_stream: Box<dyn SessionStream> = Box::new(buffered_stream);
228 let mut client = Client::new(session_stream);
229 let _greeting = client
230 .read_response()
231 .await?
232 .context("Failed to read greeting")?;
233 Ok(client)
234 }
235
236 async fn connect_insecure(context: &Context, addr: SocketAddr) -> Result<Self> {
237 let tcp_stream = connect_tcp_inner(addr).await?;
238 let account_id = context.get_id();
239 let events = context.events.clone();
240 let logging_stream = LoggingStream::new(tcp_stream, account_id, events)?;
241 let buffered_stream = BufWriter::new(logging_stream);
242 let session_stream: Box<dyn SessionStream> = Box::new(buffered_stream);
243 let mut client = Client::new(session_stream);
244 let _greeting = client
245 .read_response()
246 .await?
247 .context("Failed to read greeting")?;
248 Ok(client)
249 }
250
251 async fn connect_starttls(
252 context: &Context,
253 addr: SocketAddr,
254 host: &str,
255 strict_tls: bool,
256 ) -> Result<Self> {
257 let use_sni = false;
258 let tcp_stream = connect_tcp_inner(addr).await?;
259
260 let account_id = context.get_id();
261 let events = context.events.clone();
262 let tcp_stream = LoggingStream::new(tcp_stream, account_id, events)?;
263
264 let buffered_tcp_stream = BufWriter::new(tcp_stream);
266 let mut client = async_imap::Client::new(buffered_tcp_stream);
267 let _greeting = client
268 .read_response()
269 .await?
270 .context("Failed to read greeting")?;
271 client
272 .run_command_and_check_ok("STARTTLS", None)
273 .await
274 .context("STARTTLS command failed")?;
275 let buffered_tcp_stream = client.into_inner();
276 let tcp_stream = buffered_tcp_stream.into_inner();
277
278 let tls_stream = wrap_tls(
279 strict_tls,
280 host,
281 addr.port(),
282 use_sni,
283 "",
284 tcp_stream,
285 &context.tls_session_store,
286 &context.spki_hash_store,
287 &context.sql,
288 )
289 .await
290 .context("STARTTLS upgrade failed")?;
291 let buffered_stream = BufWriter::new(tls_stream);
292 let session_stream: Box<dyn SessionStream> = Box::new(buffered_stream);
293 let client = Client::new(session_stream);
294 Ok(client)
295 }
296
297 async fn connect_secure_proxy(
298 context: &Context,
299 domain: &str,
300 port: u16,
301 strict_tls: bool,
302 proxy_config: ProxyConfig,
303 ) -> Result<Self> {
304 let use_sni = true;
305 let proxy_stream = proxy_config
306 .connect(context, domain, port, strict_tls)
307 .await?;
308 let tls_stream = wrap_tls(
309 strict_tls,
310 domain,
311 port,
312 use_sni,
313 alpn(port),
314 proxy_stream,
315 &context.tls_session_store,
316 &context.spki_hash_store,
317 &context.sql,
318 )
319 .await?;
320 let buffered_stream = BufWriter::new(tls_stream);
321 let session_stream: Box<dyn SessionStream> = Box::new(buffered_stream);
322 let mut client = Client::new(session_stream);
323 let _greeting = client
324 .read_response()
325 .await?
326 .context("Failed to read greeting")?;
327 Ok(client)
328 }
329
330 async fn connect_insecure_proxy(
331 context: &Context,
332 domain: &str,
333 port: u16,
334 proxy_config: ProxyConfig,
335 ) -> Result<Self> {
336 let proxy_stream = proxy_config.connect(context, domain, port, false).await?;
337 let buffered_stream = BufWriter::new(proxy_stream);
338 let session_stream: Box<dyn SessionStream> = Box::new(buffered_stream);
339 let mut client = Client::new(session_stream);
340 let _greeting = client
341 .read_response()
342 .await?
343 .context("Failed to read greeting")?;
344 Ok(client)
345 }
346
347 async fn connect_starttls_proxy(
348 context: &Context,
349 hostname: &str,
350 port: u16,
351 proxy_config: ProxyConfig,
352 strict_tls: bool,
353 ) -> Result<Self> {
354 let use_sni = false;
355 let proxy_stream = proxy_config
356 .connect(context, hostname, port, strict_tls)
357 .await?;
358
359 let buffered_proxy_stream = BufWriter::new(proxy_stream);
361 let mut client = ImapClient::new(buffered_proxy_stream);
362 let _greeting = client
363 .read_response()
364 .await?
365 .context("Failed to read greeting")?;
366 client
367 .run_command_and_check_ok("STARTTLS", None)
368 .await
369 .context("STARTTLS command failed")?;
370 let buffered_proxy_stream = client.into_inner();
371 let proxy_stream = buffered_proxy_stream.into_inner();
372
373 let tls_stream = wrap_tls(
374 strict_tls,
375 hostname,
376 port,
377 use_sni,
378 "",
379 proxy_stream,
380 &context.tls_session_store,
381 &context.spki_hash_store,
382 &context.sql,
383 )
384 .await
385 .context("STARTTLS upgrade failed")?;
386 let buffered_stream = BufWriter::new(tls_stream);
387 let session_stream: Box<dyn SessionStream> = Box::new(buffered_stream);
388 let client = Client::new(session_stream);
389 Ok(client)
390 }
391}