diff --git a/src/imap.rs b/src/imap.rs index 0d91da8f7..5eaf0fd6a 100644 --- a/src/imap.rs +++ b/src/imap.rs @@ -308,6 +308,7 @@ impl Imap { if let Some(socks5_config) = &config.socks5_config { if config.lp.security == Socket::Starttls { Client::connect_starttls_socks5( + context, imap_server, imap_port, socks5_config.clone(), @@ -315,13 +316,18 @@ impl Imap { ) .await } else { - Client::connect_insecure_socks5(imap_server, imap_port, socks5_config.clone()) - .await + Client::connect_insecure_socks5( + context, + imap_server, + imap_port, + socks5_config.clone(), + ) + .await } } else if config.lp.security == Socket::Starttls { - Client::connect_starttls(imap_server, imap_port, config.strict_tls).await + Client::connect_starttls(context, imap_server, imap_port, config.strict_tls).await } else { - Client::connect_insecure((imap_server, imap_port)).await + Client::connect_insecure(context, imap_server, imap_port).await } } else { let config = &self.config; @@ -330,6 +336,7 @@ impl Imap { if let Some(socks5_config) = &config.socks5_config { Client::connect_secure_socks5( + context, imap_server, imap_port, config.strict_tls, @@ -337,7 +344,7 @@ impl Imap { ) .await } else { - Client::connect_secure(imap_server, imap_port, config.strict_tls).await + Client::connect_secure(context, imap_server, imap_port, config.strict_tls).await } }; diff --git a/src/imap/client.rs b/src/imap/client.rs index aaada0f90..b6b106c45 100644 --- a/src/imap/client.rs +++ b/src/imap/client.rs @@ -7,11 +7,11 @@ use anyhow::{Context as _, Result}; use async_imap::Client as ImapClient; use async_imap::Session as ImapSession; use tokio::io::BufWriter; -use tokio::net::ToSocketAddrs; use super::capabilities::Capabilities; use super::session::Session; use super::session::SessionStream; +use crate::context::Context; use crate::login_param::build_tls; use crate::net::connect_tcp; use crate::socks::Socks5Config; @@ -88,8 +88,13 @@ impl Client { Ok(Session::new(session, capabilities)) } - pub async fn connect_secure(hostname: &str, port: u16, strict_tls: bool) -> Result { - let tcp_stream = connect_tcp((hostname, port), IMAP_TIMEOUT).await?; + pub async fn connect_secure( + context: &Context, + hostname: &str, + port: u16, + strict_tls: bool, + ) -> Result { + let tcp_stream = connect_tcp(context, hostname, port, IMAP_TIMEOUT).await?; let tls = build_tls(strict_tls); let tls_stream = tls.connect(hostname, tcp_stream).await?; let buffered_stream = BufWriter::new(tls_stream); @@ -104,8 +109,8 @@ impl Client { Ok(Client { inner: client }) } - pub async fn connect_insecure(addr: impl ToSocketAddrs) -> Result { - let tcp_stream = connect_tcp(addr, IMAP_TIMEOUT).await?; + pub async fn connect_insecure(context: &Context, hostname: &str, port: u16) -> Result { + let tcp_stream = connect_tcp(context, hostname, port, IMAP_TIMEOUT).await?; let buffered_stream = BufWriter::new(tcp_stream); let session_stream: Box = Box::new(buffered_stream); let mut client = ImapClient::new(session_stream); @@ -117,8 +122,13 @@ impl Client { Ok(Client { inner: client }) } - pub async fn connect_starttls(hostname: &str, port: u16, strict_tls: bool) -> Result { - let tcp_stream = connect_tcp((hostname, port), IMAP_TIMEOUT).await?; + pub async fn connect_starttls( + context: &Context, + hostname: &str, + port: u16, + strict_tls: bool, + ) -> Result { + let tcp_stream = connect_tcp(context, hostname, port, IMAP_TIMEOUT).await?; // Run STARTTLS command and convert the client back into a stream. let mut client = ImapClient::new(tcp_stream); @@ -146,12 +156,15 @@ impl Client { } pub async fn connect_secure_socks5( + context: &Context, domain: &str, port: u16, strict_tls: bool, socks5_config: Socks5Config, ) -> Result { - let socks5_stream = socks5_config.connect(domain, port, IMAP_TIMEOUT).await?; + let socks5_stream = socks5_config + .connect(context, domain, port, IMAP_TIMEOUT) + .await?; let tls = build_tls(strict_tls); let tls_stream = tls.connect(domain, socks5_stream).await?; let buffered_stream = BufWriter::new(tls_stream); @@ -166,11 +179,14 @@ impl Client { } pub async fn connect_insecure_socks5( + context: &Context, domain: &str, port: u16, socks5_config: Socks5Config, ) -> Result { - let socks5_stream = socks5_config.connect(domain, port, IMAP_TIMEOUT).await?; + let socks5_stream = socks5_config + .connect(context, domain, port, IMAP_TIMEOUT) + .await?; let buffered_stream = BufWriter::new(socks5_stream); let session_stream: Box = Box::new(buffered_stream); let mut client = ImapClient::new(session_stream); @@ -183,12 +199,15 @@ impl Client { } pub async fn connect_starttls_socks5( + context: &Context, hostname: &str, port: u16, socks5_config: Socks5Config, strict_tls: bool, ) -> Result { - let socks5_stream = socks5_config.connect(hostname, port, IMAP_TIMEOUT).await?; + let socks5_stream = socks5_config + .connect(context, hostname, port, IMAP_TIMEOUT) + .await?; // Run STARTTLS command and convert the client back into a stream. let mut client = ImapClient::new(socks5_stream); diff --git a/src/net.rs b/src/net.rs index 6c0c7dd0b..2885c0523 100644 --- a/src/net.rs +++ b/src/net.rs @@ -1,25 +1,55 @@ ///! # Common network utilities. +use std::net::SocketAddr; use std::pin::Pin; use std::time::Duration; use anyhow::{Context as _, Result}; -use tokio::net::{TcpStream, ToSocketAddrs}; +use tokio::net::{lookup_host, TcpStream}; use tokio::time::timeout; use tokio_io_timeout::TimeoutStream; +use crate::context::Context; + +async fn connect_tcp_inner(addr: SocketAddr, timeout_val: Duration) -> Result { + let tcp_stream = timeout(timeout_val, TcpStream::connect(addr)) + .await + .context("connection timeout")? + .context("connection failure")?; + Ok(tcp_stream) +} + /// Returns a TCP connection stream with read/write timeouts set /// and Nagle's algorithm disabled with `TCP_NODELAY`. /// /// `TCP_NODELAY` ensures writing to the stream always results in immediate sending of the packet /// to the network, which is important to reduce the latency of interactive protocols such as IMAP. pub(crate) async fn connect_tcp( - addr: impl ToSocketAddrs, + context: &Context, + host: &str, + port: u16, timeout_val: Duration, ) -> Result>>> { - let tcp_stream = timeout(timeout_val, TcpStream::connect(addr)) - .await - .context("connection timeout")? - .context("connection failure")?; + let mut tcp_stream = None; + for resolved_addr in lookup_host((host, port)).await? { + info!( + context, + "Resolved {}:{} into {}.", host, port, &resolved_addr + ); + match connect_tcp_inner(resolved_addr, timeout_val).await { + Ok(stream) => { + tcp_stream = Some(stream); + break; + } + Err(err) => { + warn!( + context, + "Failed to connect to {}: {:#}.", resolved_addr, err + ); + } + } + } + let tcp_stream = + tcp_stream.with_context(|| format!("failed to connect to {}:{}", host, port))?; // Disable Nagle's algorithm. tcp_stream.set_nodelay(true)?; diff --git a/src/socks.rs b/src/socks.rs index b05c265dc..52eb4fd61 100644 --- a/src/socks.rs +++ b/src/socks.rs @@ -58,11 +58,12 @@ impl Socks5Config { pub async fn connect( &self, + context: &Context, target_host: &str, target_port: u16, timeout_val: Duration, ) -> Result>>>> { - let tcp_stream = connect_tcp((self.host.clone(), self.port), timeout_val).await?; + let tcp_stream = connect_tcp(context, &self.host, self.port, timeout_val).await?; let authentication_method = if let Some((username, password)) = self.user_password.as_ref() {