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 "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 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 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}