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 [&'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(strict_tls, hostname, alpn(addr.port()), logging_stream).await?;
214 let buffered_stream = BufWriter::new(tls_stream);
215 let session_stream: Box<dyn SessionStream> = Box::new(buffered_stream);
216 let mut client = Client::new(session_stream);
217 let _greeting = client
218 .read_response()
219 .await?
220 .context("Failed to read greeting")?;
221 Ok(client)
222 }
223
224 async fn connect_insecure(context: &Context, addr: SocketAddr) -> Result<Self> {
225 let tcp_stream = connect_tcp_inner(addr).await?;
226 let account_id = context.get_id();
227 let events = context.events.clone();
228 let logging_stream = LoggingStream::new(tcp_stream, account_id, events)?;
229 let buffered_stream = BufWriter::new(logging_stream);
230 let session_stream: Box<dyn SessionStream> = Box::new(buffered_stream);
231 let mut client = Client::new(session_stream);
232 let _greeting = client
233 .read_response()
234 .await?
235 .context("Failed to read greeting")?;
236 Ok(client)
237 }
238
239 async fn connect_starttls(
240 context: &Context,
241 addr: SocketAddr,
242 host: &str,
243 strict_tls: bool,
244 ) -> Result<Self> {
245 let tcp_stream = connect_tcp_inner(addr).await?;
246
247 let account_id = context.get_id();
248 let events = context.events.clone();
249 let tcp_stream = LoggingStream::new(tcp_stream, account_id, events)?;
250
251 let buffered_tcp_stream = BufWriter::new(tcp_stream);
253 let mut client = async_imap::Client::new(buffered_tcp_stream);
254 let _greeting = client
255 .read_response()
256 .await?
257 .context("Failed to read greeting")?;
258 client
259 .run_command_and_check_ok("STARTTLS", None)
260 .await
261 .context("STARTTLS command failed")?;
262 let buffered_tcp_stream = client.into_inner();
263 let tcp_stream = buffered_tcp_stream.into_inner();
264
265 let tls_stream = wrap_tls(strict_tls, host, &[], tcp_stream)
266 .await
267 .context("STARTTLS upgrade failed")?;
268 let buffered_stream = BufWriter::new(tls_stream);
269 let session_stream: Box<dyn SessionStream> = Box::new(buffered_stream);
270 let client = Client::new(session_stream);
271 Ok(client)
272 }
273
274 async fn connect_secure_proxy(
275 context: &Context,
276 domain: &str,
277 port: u16,
278 strict_tls: bool,
279 proxy_config: ProxyConfig,
280 ) -> Result<Self> {
281 let proxy_stream = proxy_config
282 .connect(context, domain, port, strict_tls)
283 .await?;
284 let tls_stream = wrap_tls(strict_tls, domain, alpn(port), proxy_stream).await?;
285 let buffered_stream = BufWriter::new(tls_stream);
286 let session_stream: Box<dyn SessionStream> = Box::new(buffered_stream);
287 let mut client = Client::new(session_stream);
288 let _greeting = client
289 .read_response()
290 .await?
291 .context("Failed to read greeting")?;
292 Ok(client)
293 }
294
295 async fn connect_insecure_proxy(
296 context: &Context,
297 domain: &str,
298 port: u16,
299 proxy_config: ProxyConfig,
300 ) -> Result<Self> {
301 let proxy_stream = proxy_config.connect(context, domain, port, false).await?;
302 let buffered_stream = BufWriter::new(proxy_stream);
303 let session_stream: Box<dyn SessionStream> = Box::new(buffered_stream);
304 let mut client = Client::new(session_stream);
305 let _greeting = client
306 .read_response()
307 .await?
308 .context("Failed to read greeting")?;
309 Ok(client)
310 }
311
312 async fn connect_starttls_proxy(
313 context: &Context,
314 hostname: &str,
315 port: u16,
316 proxy_config: ProxyConfig,
317 strict_tls: bool,
318 ) -> Result<Self> {
319 let proxy_stream = proxy_config
320 .connect(context, hostname, port, strict_tls)
321 .await?;
322
323 let buffered_proxy_stream = BufWriter::new(proxy_stream);
325 let mut client = ImapClient::new(buffered_proxy_stream);
326 let _greeting = client
327 .read_response()
328 .await?
329 .context("Failed to read greeting")?;
330 client
331 .run_command_and_check_ok("STARTTLS", None)
332 .await
333 .context("STARTTLS command failed")?;
334 let buffered_proxy_stream = client.into_inner();
335 let proxy_stream = buffered_proxy_stream.into_inner();
336
337 let tls_stream = wrap_tls(strict_tls, hostname, &[], proxy_stream)
338 .await
339 .context("STARTTLS upgrade failed")?;
340 let buffered_stream = BufWriter::new(tls_stream);
341 let session_stream: Box<dyn SessionStream> = Box::new(buffered_stream);
342 let client = Client::new(session_stream);
343 Ok(client)
344 }
345}