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::login_param::{ConnectionCandidate, ConnectionSecurity};
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::{
17 connect_tcp_inner, connect_tls_inner, run_connection_attempts, update_connection_history,
18};
19use crate::tools::time;
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 [&'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(resolved_addr, host, strict_tls).await
129 }
130 ConnectionSecurity::Starttls => {
131 Client::connect_starttls(resolved_addr, host, strict_tls).await
132 }
133 ConnectionSecurity::Plain => Client::connect_insecure(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(addr: SocketAddr, hostname: &str, strict_tls: bool) -> Result<Self> {
205 let tls_stream = connect_tls_inner(addr, hostname, strict_tls, alpn(addr.port())).await?;
206 let buffered_stream = BufWriter::new(tls_stream);
207 let session_stream: Box<dyn SessionStream> = Box::new(buffered_stream);
208 let mut client = Client::new(session_stream);
209 let _greeting = client
210 .read_response()
211 .await
212 .context("failed to read greeting")??;
213 Ok(client)
214 }
215
216 async fn connect_insecure(addr: SocketAddr) -> Result<Self> {
217 let tcp_stream = connect_tcp_inner(addr).await?;
218 let buffered_stream = BufWriter::new(tcp_stream);
219 let session_stream: Box<dyn SessionStream> = Box::new(buffered_stream);
220 let mut client = Client::new(session_stream);
221 let _greeting = client
222 .read_response()
223 .await
224 .context("failed to read greeting")??;
225 Ok(client)
226 }
227
228 async fn connect_starttls(addr: SocketAddr, host: &str, strict_tls: bool) -> Result<Self> {
229 let tcp_stream = connect_tcp_inner(addr).await?;
230
231 let buffered_tcp_stream = BufWriter::new(tcp_stream);
233 let mut client = async_imap::Client::new(buffered_tcp_stream);
234 let _greeting = client
235 .read_response()
236 .await
237 .context("failed to read greeting")??;
238 client
239 .run_command_and_check_ok("STARTTLS", None)
240 .await
241 .context("STARTTLS command failed")?;
242 let buffered_tcp_stream = client.into_inner();
243 let tcp_stream = buffered_tcp_stream.into_inner();
244
245 let tls_stream = wrap_tls(strict_tls, host, &[], tcp_stream)
246 .await
247 .context("STARTTLS upgrade failed")?;
248
249 let buffered_stream = BufWriter::new(tls_stream);
250 let session_stream: Box<dyn SessionStream> = Box::new(buffered_stream);
251 let client = Client::new(session_stream);
252 Ok(client)
253 }
254
255 async fn connect_secure_proxy(
256 context: &Context,
257 domain: &str,
258 port: u16,
259 strict_tls: bool,
260 proxy_config: ProxyConfig,
261 ) -> Result<Self> {
262 let proxy_stream = proxy_config
263 .connect(context, domain, port, strict_tls)
264 .await?;
265 let tls_stream = wrap_tls(strict_tls, domain, alpn(port), proxy_stream).await?;
266 let buffered_stream = BufWriter::new(tls_stream);
267 let session_stream: Box<dyn SessionStream> = Box::new(buffered_stream);
268 let mut client = Client::new(session_stream);
269 let _greeting = client
270 .read_response()
271 .await
272 .context("failed to read greeting")??;
273 Ok(client)
274 }
275
276 async fn connect_insecure_proxy(
277 context: &Context,
278 domain: &str,
279 port: u16,
280 proxy_config: ProxyConfig,
281 ) -> Result<Self> {
282 let proxy_stream = proxy_config.connect(context, domain, port, false).await?;
283 let buffered_stream = BufWriter::new(proxy_stream);
284 let session_stream: Box<dyn SessionStream> = Box::new(buffered_stream);
285 let mut client = Client::new(session_stream);
286 let _greeting = client
287 .read_response()
288 .await
289 .context("failed to read greeting")??;
290 Ok(client)
291 }
292
293 async fn connect_starttls_proxy(
294 context: &Context,
295 hostname: &str,
296 port: u16,
297 proxy_config: ProxyConfig,
298 strict_tls: bool,
299 ) -> Result<Self> {
300 let proxy_stream = proxy_config
301 .connect(context, hostname, port, strict_tls)
302 .await?;
303
304 let buffered_proxy_stream = BufWriter::new(proxy_stream);
306 let mut client = ImapClient::new(buffered_proxy_stream);
307 let _greeting = client
308 .read_response()
309 .await
310 .context("failed to read greeting")??;
311 client
312 .run_command_and_check_ok("STARTTLS", None)
313 .await
314 .context("STARTTLS command failed")?;
315 let buffered_proxy_stream = client.into_inner();
316 let proxy_stream = buffered_proxy_stream.into_inner();
317
318 let tls_stream = wrap_tls(strict_tls, hostname, &[], proxy_stream)
319 .await
320 .context("STARTTLS upgrade failed")?;
321 let buffered_stream = BufWriter::new(tls_stream);
322 let session_stream: Box<dyn SessionStream> = Box::new(buffered_stream);
323 let client = Client::new(session_stream);
324 Ok(client)
325 }
326}