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