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::{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::{
18 connect_tcp_inner, connect_tls_inner, run_connection_attempts, update_connection_history,
19};
20use crate::tools::time;
21
22#[derive(Debug)]
23pub(crate) struct Client {
24 inner: ImapClient<Box<dyn SessionStream>>,
25}
26
27impl Deref for Client {
28 type Target = ImapClient<Box<dyn SessionStream>>;
29
30 fn deref(&self) -> &Self::Target {
31 &self.inner
32 }
33}
34
35impl DerefMut for Client {
36 fn deref_mut(&mut self) -> &mut Self::Target {
37 &mut self.inner
38 }
39}
40
41fn alpn(port: u16) -> &'static [&'static str] {
43 if port == 993 {
44 &[]
46 } else {
47 &["imap"]
48 }
49}
50
51pub(crate) async fn determine_capabilities(
55 session: &mut ImapSession<Box<dyn SessionStream>>,
56) -> Result<Capabilities> {
57 let caps = session
58 .capabilities()
59 .await
60 .context("CAPABILITY command error")?;
61 let server_id = if caps.has_str("ID") {
62 session.id([("name", Some("Delta Chat"))]).await?
63 } else {
64 None
65 };
66 let capabilities = Capabilities {
67 can_idle: caps.has_str("IDLE"),
68 can_move: caps.has_str("MOVE"),
69 can_check_quota: caps.has_str("QUOTA"),
70 can_condstore: caps.has_str("CONDSTORE"),
71 can_metadata: caps.has_str("METADATA"),
72 can_compress: caps.has_str("COMPRESS=DEFLATE"),
73 can_push: caps.has_str("XDELTAPUSH"),
74 is_chatmail: caps.has_str("XCHATMAIL"),
75 server_id,
76 };
77 Ok(capabilities)
78}
79
80impl Client {
81 fn new(stream: Box<dyn SessionStream>) -> Self {
82 Self {
83 inner: ImapClient::new(stream),
84 }
85 }
86
87 pub(crate) async fn login(
88 self,
89 username: &str,
90 password: &str,
91 ) -> Result<ImapSession<Box<dyn SessionStream>>> {
92 let Client { inner, .. } = self;
93
94 let session = inner
95 .login(username, password)
96 .await
97 .map_err(|(err, _client)| err)?;
98 Ok(session)
99 }
100
101 pub(crate) async fn authenticate(
102 self,
103 auth_type: &str,
104 authenticator: impl async_imap::Authenticator,
105 ) -> Result<ImapSession<Box<dyn SessionStream>>> {
106 let Client { inner, .. } = self;
107 let session = inner
108 .authenticate(auth_type, authenticator)
109 .await
110 .map_err(|(err, _client)| err)?;
111 Ok(session)
112 }
113
114 async fn connection_attempt(
115 context: Context,
116 host: String,
117 security: ConnectionSecurity,
118 resolved_addr: SocketAddr,
119 strict_tls: bool,
120 ) -> Result<Self> {
121 let context = &context;
122 let host = &host;
123 info!(
124 context,
125 "Attempting IMAP connection to {host} ({resolved_addr})."
126 );
127 let res = match security {
128 ConnectionSecurity::Tls => {
129 Client::connect_secure(resolved_addr, host, strict_tls).await
130 }
131 ConnectionSecurity::Starttls => {
132 Client::connect_starttls(resolved_addr, host, strict_tls).await
133 }
134 ConnectionSecurity::Plain => Client::connect_insecure(resolved_addr).await,
135 };
136 match res {
137 Ok(client) => {
138 let ip_addr = resolved_addr.ip().to_string();
139 let port = resolved_addr.port();
140
141 let save_cache = match security {
142 ConnectionSecurity::Tls | ConnectionSecurity::Starttls => strict_tls,
143 ConnectionSecurity::Plain => false,
144 };
145 if save_cache {
146 update_connect_timestamp(context, host, &ip_addr).await?;
147 }
148 update_connection_history(context, "imap", host, port, &ip_addr, time()).await?;
149 Ok(client)
150 }
151 Err(err) => {
152 warn!(
153 context,
154 "Failed to connect to {host} ({resolved_addr}): {err:#}."
155 );
156 Err(err)
157 }
158 }
159 }
160
161 pub async fn connect(
162 context: &Context,
163 proxy_config: Option<ProxyConfig>,
164 strict_tls: bool,
165 candidate: ConnectionCandidate,
166 ) -> Result<Self> {
167 let host = &candidate.host;
168 let port = candidate.port;
169 let security = candidate.security;
170 if let Some(proxy_config) = proxy_config {
171 let client = match security {
172 ConnectionSecurity::Tls => {
173 Client::connect_secure_proxy(context, host, port, strict_tls, proxy_config)
174 .await?
175 }
176 ConnectionSecurity::Starttls => {
177 Client::connect_starttls_proxy(context, host, port, proxy_config, strict_tls)
178 .await?
179 }
180 ConnectionSecurity::Plain => {
181 Client::connect_insecure_proxy(context, host, port, proxy_config).await?
182 }
183 };
184 update_connection_history(context, "imap", host, port, host, time()).await?;
185 Ok(client)
186 } else {
187 let load_cache = match security {
188 ConnectionSecurity::Tls | ConnectionSecurity::Starttls => strict_tls,
189 ConnectionSecurity::Plain => false,
190 };
191
192 let connection_futures =
193 lookup_host_with_cache(context, host, port, "imap", load_cache)
194 .await?
195 .into_iter()
196 .map(|resolved_addr| {
197 let context = context.clone();
198 let host = host.to_string();
199 Self::connection_attempt(context, host, security, resolved_addr, strict_tls)
200 });
201 run_connection_attempts(connection_futures).await
202 }
203 }
204
205 async fn connect_secure(addr: SocketAddr, hostname: &str, strict_tls: bool) -> Result<Self> {
206 let tls_stream = connect_tls_inner(addr, hostname, strict_tls, alpn(addr.port())).await?;
207 let buffered_stream = BufWriter::new(tls_stream);
208 let session_stream: Box<dyn SessionStream> = Box::new(buffered_stream);
209 let mut client = Client::new(session_stream);
210 let _greeting = client
211 .read_response()
212 .await
213 .context("failed to read greeting")??;
214 Ok(client)
215 }
216
217 async fn connect_insecure(addr: SocketAddr) -> Result<Self> {
218 let tcp_stream = connect_tcp_inner(addr).await?;
219 let buffered_stream = BufWriter::new(tcp_stream);
220 let session_stream: Box<dyn SessionStream> = Box::new(buffered_stream);
221 let mut client = Client::new(session_stream);
222 let _greeting = client
223 .read_response()
224 .await
225 .context("failed to read greeting")??;
226 Ok(client)
227 }
228
229 async fn connect_starttls(addr: SocketAddr, host: &str, strict_tls: bool) -> Result<Self> {
230 let tcp_stream = connect_tcp_inner(addr).await?;
231
232 let buffered_tcp_stream = BufWriter::new(tcp_stream);
234 let mut client = async_imap::Client::new(buffered_tcp_stream);
235 let _greeting = client
236 .read_response()
237 .await
238 .context("failed to read greeting")??;
239 client
240 .run_command_and_check_ok("STARTTLS", None)
241 .await
242 .context("STARTTLS command failed")?;
243 let buffered_tcp_stream = client.into_inner();
244 let tcp_stream = buffered_tcp_stream.into_inner();
245
246 let tls_stream = wrap_tls(strict_tls, host, &[], tcp_stream)
247 .await
248 .context("STARTTLS upgrade failed")?;
249
250 let buffered_stream = BufWriter::new(tls_stream);
251 let session_stream: Box<dyn SessionStream> = Box::new(buffered_stream);
252 let client = Client::new(session_stream);
253 Ok(client)
254 }
255
256 async fn connect_secure_proxy(
257 context: &Context,
258 domain: &str,
259 port: u16,
260 strict_tls: bool,
261 proxy_config: ProxyConfig,
262 ) -> Result<Self> {
263 let proxy_stream = proxy_config
264 .connect(context, domain, port, strict_tls)
265 .await?;
266 let tls_stream = wrap_tls(strict_tls, domain, alpn(port), proxy_stream).await?;
267 let buffered_stream = BufWriter::new(tls_stream);
268 let session_stream: Box<dyn SessionStream> = Box::new(buffered_stream);
269 let mut client = Client::new(session_stream);
270 let _greeting = client
271 .read_response()
272 .await
273 .context("failed to read greeting")??;
274 Ok(client)
275 }
276
277 async fn connect_insecure_proxy(
278 context: &Context,
279 domain: &str,
280 port: u16,
281 proxy_config: ProxyConfig,
282 ) -> Result<Self> {
283 let proxy_stream = proxy_config.connect(context, domain, port, false).await?;
284 let buffered_stream = BufWriter::new(proxy_stream);
285 let session_stream: Box<dyn SessionStream> = Box::new(buffered_stream);
286 let mut client = Client::new(session_stream);
287 let _greeting = client
288 .read_response()
289 .await
290 .context("failed to read greeting")??;
291 Ok(client)
292 }
293
294 async fn connect_starttls_proxy(
295 context: &Context,
296 hostname: &str,
297 port: u16,
298 proxy_config: ProxyConfig,
299 strict_tls: bool,
300 ) -> Result<Self> {
301 let proxy_stream = proxy_config
302 .connect(context, hostname, port, strict_tls)
303 .await?;
304
305 let buffered_proxy_stream = BufWriter::new(proxy_stream);
307 let mut client = ImapClient::new(buffered_proxy_stream);
308 let _greeting = client
309 .read_response()
310 .await
311 .context("failed to read greeting")??;
312 client
313 .run_command_and_check_ok("STARTTLS", None)
314 .await
315 .context("STARTTLS command failed")?;
316 let buffered_proxy_stream = client.into_inner();
317 let proxy_stream = buffered_proxy_stream.into_inner();
318
319 let tls_stream = wrap_tls(strict_tls, hostname, &[], proxy_stream)
320 .await
321 .context("STARTTLS upgrade failed")?;
322 let buffered_stream = BufWriter::new(tls_stream);
323 let session_stream: Box<dyn SessionStream> = Box::new(buffered_stream);
324 let client = Client::new(session_stream);
325 Ok(client)
326 }
327}