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, info, warn};
12use crate::login_param::{ConnectionCandidate, ConnectionSecurity};
13use crate::net::dns::{lookup_host_with_cache, update_connect_timestamp};
14use crate::net::proxy::ProxyConfig;
15use crate::net::session::SessionStream;
16use crate::net::tls::wrap_tls;
17use crate::net::{connect_tcp_inner, run_connection_attempts, update_connection_history};
18use crate::tools::time;
19
20#[derive(Debug)]
21pub(crate) struct Client {
22 inner: ImapClient<Box<dyn SessionStream>>,
23}
24
25impl Deref for Client {
26 type Target = ImapClient<Box<dyn SessionStream>>;
27
28 fn deref(&self) -> &Self::Target {
29 &self.inner
30 }
31}
32
33impl DerefMut for Client {
34 fn deref_mut(&mut self) -> &mut Self::Target {
35 &mut self.inner
36 }
37}
38
39fn alpn(port: u16) -> &'static str {
41 if port == 993 {
42 ""
44 } else {
45 "imap"
46 }
47}
48
49pub(crate) async fn determine_capabilities(
53 session: &mut ImapSession<Box<dyn SessionStream>>,
54) -> Result<Capabilities> {
55 let caps = session
56 .capabilities()
57 .await
58 .context("CAPABILITY command error")?;
59 let server_id = if caps.has_str("ID") {
60 session.id([("name", Some("Delta Chat"))]).await?
61 } else {
62 None
63 };
64 let capabilities = Capabilities {
65 can_idle: caps.has_str("IDLE"),
66 can_move: caps.has_str("MOVE"),
67 can_check_quota: caps.has_str("QUOTA"),
68 can_condstore: caps.has_str("CONDSTORE"),
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 "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 tcp_stream = connect_tcp_inner(addr).await?;
210 let account_id = context.get_id();
211 let events = context.events.clone();
212 let logging_stream = LoggingStream::new(tcp_stream, account_id, events)?;
213 let tls_stream = wrap_tls(
214 strict_tls,
215 hostname,
216 addr.port(),
217 alpn(addr.port()),
218 logging_stream,
219 &context.tls_session_store,
220 )
221 .await?;
222 let buffered_stream = BufWriter::new(tls_stream);
223 let session_stream: Box<dyn SessionStream> = Box::new(buffered_stream);
224 let mut client = Client::new(session_stream);
225 let _greeting = client
226 .read_response()
227 .await?
228 .context("Failed to read greeting")?;
229 Ok(client)
230 }
231
232 async fn connect_insecure(context: &Context, addr: SocketAddr) -> Result<Self> {
233 let tcp_stream = connect_tcp_inner(addr).await?;
234 let account_id = context.get_id();
235 let events = context.events.clone();
236 let logging_stream = LoggingStream::new(tcp_stream, account_id, events)?;
237 let buffered_stream = BufWriter::new(logging_stream);
238 let session_stream: Box<dyn SessionStream> = Box::new(buffered_stream);
239 let mut client = Client::new(session_stream);
240 let _greeting = client
241 .read_response()
242 .await?
243 .context("Failed to read greeting")?;
244 Ok(client)
245 }
246
247 async fn connect_starttls(
248 context: &Context,
249 addr: SocketAddr,
250 host: &str,
251 strict_tls: bool,
252 ) -> Result<Self> {
253 let tcp_stream = connect_tcp_inner(addr).await?;
254
255 let account_id = context.get_id();
256 let events = context.events.clone();
257 let tcp_stream = LoggingStream::new(tcp_stream, account_id, events)?;
258
259 let buffered_tcp_stream = BufWriter::new(tcp_stream);
261 let mut client = async_imap::Client::new(buffered_tcp_stream);
262 let _greeting = client
263 .read_response()
264 .await?
265 .context("Failed to read greeting")?;
266 client
267 .run_command_and_check_ok("STARTTLS", None)
268 .await
269 .context("STARTTLS command failed")?;
270 let buffered_tcp_stream = client.into_inner();
271 let tcp_stream = buffered_tcp_stream.into_inner();
272
273 let tls_stream = wrap_tls(
274 strict_tls,
275 host,
276 addr.port(),
277 "",
278 tcp_stream,
279 &context.tls_session_store,
280 )
281 .await
282 .context("STARTTLS upgrade failed")?;
283 let buffered_stream = BufWriter::new(tls_stream);
284 let session_stream: Box<dyn SessionStream> = Box::new(buffered_stream);
285 let client = Client::new(session_stream);
286 Ok(client)
287 }
288
289 async fn connect_secure_proxy(
290 context: &Context,
291 domain: &str,
292 port: u16,
293 strict_tls: bool,
294 proxy_config: ProxyConfig,
295 ) -> Result<Self> {
296 let proxy_stream = proxy_config
297 .connect(context, domain, port, strict_tls)
298 .await?;
299 let tls_stream = wrap_tls(
300 strict_tls,
301 domain,
302 port,
303 alpn(port),
304 proxy_stream,
305 &context.tls_session_store,
306 )
307 .await?;
308 let buffered_stream = BufWriter::new(tls_stream);
309 let session_stream: Box<dyn SessionStream> = Box::new(buffered_stream);
310 let mut client = Client::new(session_stream);
311 let _greeting = client
312 .read_response()
313 .await?
314 .context("Failed to read greeting")?;
315 Ok(client)
316 }
317
318 async fn connect_insecure_proxy(
319 context: &Context,
320 domain: &str,
321 port: u16,
322 proxy_config: ProxyConfig,
323 ) -> Result<Self> {
324 let proxy_stream = proxy_config.connect(context, domain, port, false).await?;
325 let buffered_stream = BufWriter::new(proxy_stream);
326 let session_stream: Box<dyn SessionStream> = Box::new(buffered_stream);
327 let mut client = Client::new(session_stream);
328 let _greeting = client
329 .read_response()
330 .await?
331 .context("Failed to read greeting")?;
332 Ok(client)
333 }
334
335 async fn connect_starttls_proxy(
336 context: &Context,
337 hostname: &str,
338 port: u16,
339 proxy_config: ProxyConfig,
340 strict_tls: bool,
341 ) -> Result<Self> {
342 let proxy_stream = proxy_config
343 .connect(context, hostname, port, strict_tls)
344 .await?;
345
346 let buffered_proxy_stream = BufWriter::new(proxy_stream);
348 let mut client = ImapClient::new(buffered_proxy_stream);
349 let _greeting = client
350 .read_response()
351 .await?
352 .context("Failed to read greeting")?;
353 client
354 .run_command_and_check_ok("STARTTLS", None)
355 .await
356 .context("STARTTLS command failed")?;
357 let buffered_proxy_stream = client.into_inner();
358 let proxy_stream = buffered_proxy_stream.into_inner();
359
360 let tls_stream = wrap_tls(
361 strict_tls,
362 hostname,
363 port,
364 "",
365 proxy_stream,
366 &context.tls_session_store,
367 )
368 .await
369 .context("STARTTLS upgrade failed")?;
370 let buffered_stream = BufWriter::new(tls_stream);
371 let session_stream: Box<dyn SessionStream> = Box::new(buffered_stream);
372 let client = Client::new(session_stream);
373 Ok(client)
374 }
375}