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