diff --git a/Cargo.lock b/Cargo.lock index c662c12bd..cdac87796 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -325,13 +325,14 @@ dependencies = [ [[package]] name = "async-smtp" version = "0.4.0" -source = "git+https://github.com/async-email/async-smtp?rev=c8800625f7cf29f437143ac7e720ac2730a0962f#c8800625f7cf29f437143ac7e720ac2730a0962f" +source = "git+https://github.com/async-email/async-smtp?branch=master#2c21f5fb643e9a24c1097f13db4dfcd7818ada06" dependencies = [ "async-native-tls", "async-std", "async-trait", "base64 0.13.0", "bufstream", + "fast-socks5", "hostname 0.1.5", "log", "nom 5.1.2", @@ -1142,6 +1143,7 @@ dependencies = [ "email", "encoded-words", "escaper", + "fast-socks5", "futures", "futures-lite", "hex", @@ -1515,6 +1517,19 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" +[[package]] +name = "fast-socks5" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c1955b65d95243f547eb1d1ee6e5ce75ecf6daaeb72b08cd6c66e549d6d88e1" +dependencies = [ + "anyhow", + "async-std", + "futures", + "log", + "thiserror", +] + [[package]] name = "fast_chemail" version = "0.9.6" diff --git a/Cargo.toml b/Cargo.toml index 9c848cf0f..6f6809cf7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,7 +18,7 @@ ansi_term = { version = "0.12.1", optional = true } anyhow = "1.0.42" async-imap = { git = "https://github.com/async-email/async-imap" } async-native-tls = { version = "0.3.3" } -async-smtp = { git = "https://github.com/async-email/async-smtp", rev="c8800625f7cf29f437143ac7e720ac2730a0962f" } +async-smtp = { git = "https://github.com/async-email/async-smtp", branch="master", features = ["socks5"] } async-std-resolver = "0.20.3" async-std = { version = "~1.9.0", features = ["unstable"] } async-tar = "0.3.0" @@ -72,6 +72,7 @@ thiserror = "1.0.26" toml = "0.5.6" url = "2.2.2" uuid = { version = "0.8", features = ["serde", "v4"] } +fast-socks5 = "0.4.2" humansize = "1.1.1" [dev-dependencies] diff --git a/deltachat-ffi/deltachat.h b/deltachat-ffi/deltachat.h index 8255d8b5c..669ee3e45 100644 --- a/deltachat-ffi/deltachat.h +++ b/deltachat-ffi/deltachat.h @@ -271,6 +271,11 @@ char* dc_get_blobdir (const dc_context_t* context); * - `send_port` = SMTP-port, guessed if left out * - `send_security`= SMTP-socket, one of @ref DC_SOCKET, defaults to #DC_SOCKET_AUTO * - `server_flags` = IMAP-/SMTP-flags as a combination of @ref DC_LP flags, guessed if left out + * - `socks5_enabled` = SOCKS5 enabled + * - `socks5_host` = SOCKS5 proxy server host + * - `socks5_port` = SOCKS5 proxy server port + * - `socks5_user` = SOCKS5 proxy username + * - `socks5_password` = SOCKS5 proxy password * - `imap_certificate_checks` = how to check IMAP certificates, one of the @ref DC_CERTCK flags, defaults to #DC_CERTCK_AUTO (0) * - `smtp_certificate_checks` = how to check SMTP certificates, one of the @ref DC_CERTCK flags, defaults to #DC_CERTCK_AUTO (0) * - `displayname` = Own name to use when sending messages. MUAs are allowed to spread this way e.g. using CC, defaults to empty diff --git a/deltachat-ffi/src/lib.rs b/deltachat-ffi/src/lib.rs index 474afa451..89d11ec99 100644 --- a/deltachat-ffi/src/lib.rs +++ b/deltachat-ffi/src/lib.rs @@ -3614,7 +3614,16 @@ pub unsafe extern "C" fn dc_provider_new_from_email( return ptr::null(); } let addr = to_string_lossy(addr); - match block_on(provider::get_provider_info(addr.as_str())) { + + let ctx = &*context; + let socks5_enabled = block_on(async move { + ctx.get_config_bool(config::Config::Socks5Enabled) + .await + .log_err(ctx, "Can't get config") + .unwrap_or_default() + }); + + match block_on(provider::get_provider_info(addr.as_str(), socks5_enabled)) { Some(provider) => provider, None => ptr::null_mut(), } diff --git a/examples/repl/cmdline.rs b/examples/repl/cmdline.rs index 12a59823b..381b4fe24 100644 --- a/examples/repl/cmdline.rs +++ b/examples/repl/cmdline.rs @@ -1168,7 +1168,11 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu } "providerinfo" => { ensure!(!arg1.is_empty(), "Argument missing."); - match provider::get_provider_info(arg1).await { + let socks5_enabled = context + .get_config_bool(config::Config::Socks5Enabled) + .await + .unwrap_or_default(); + match provider::get_provider_info(arg1, socks5_enabled).await { Some(info) => { println!("Information for provider belonging to {}:", arg1); println!("status: {}", info.status as u32); diff --git a/src/config.rs b/src/config.rs index ae6333f23..197ac049c 100644 --- a/src/config.rs +++ b/src/config.rs @@ -48,6 +48,12 @@ pub enum Config { SmtpCertificateChecks, ServerFlags, + Socks5Enabled, + Socks5Host, + Socks5Port, + Socks5User, + Socks5Password, + Displayname, Selfstatus, Selfavatar, diff --git a/src/configure.rs b/src/configure.rs index 3829a7a5b..97469ae0f 100644 --- a/src/configure.rs +++ b/src/configure.rs @@ -14,6 +14,7 @@ use percent_encoding::{utf8_percent_encode, NON_ALPHANUMERIC}; use crate::dc_tools::EmailAddress; use crate::imap::Imap; +use crate::login_param::Socks5Config; use crate::login_param::{LoginParam, ServerLoginParam}; use crate::message::Message; use crate::oauth2::dc_get_oauth2_addr; @@ -170,12 +171,17 @@ async fn configure(ctx: &Context, param: &mut LoginParam) -> Result<()> { DC_LP_AUTH_NORMAL as i32 }; + let socks5_config = param.socks5_config.clone(); + let socks5_enabled = socks5_config.is_some(); + let ctx2 = ctx.clone(); let update_device_chats_handle = task::spawn(async move { ctx2.update_device_chats().await }); // Step 1: Load the parameters and check email-address and password - if oauth2 { + // Do oauth2 only if socks5 is disabled. As soon as we have a http library that can do + // socks5 requests, this can work with socks5 too + if oauth2 && !socks5_enabled { // the used oauth2 addr may differ, check this. // if dc_get_oauth2_addr() is not available in the oauth2 implementation, just use the given one. progress!(ctx, 10); @@ -217,7 +223,7 @@ async fn configure(ctx: &Context, param: &mut LoginParam) -> Result<()> { "checking internal provider-info for offline autoconfig" ); - if let Some(provider) = provider::get_provider_info(¶m_domain).await { + if let Some(provider) = provider::get_provider_info(¶m_domain, socks5_enabled).await { param.provider = Some(provider); match provider.status { provider::Status::Ok | provider::Status::Preparation => { @@ -256,9 +262,16 @@ async fn configure(ctx: &Context, param: &mut LoginParam) -> Result<()> { } } } else { + // Try receiving autoconfig info!(ctx, "no offline autoconfig found"); - param_autoconfig = - get_autoconfig(ctx, param, ¶m_domain, ¶m_addr_urlencoded).await; + param_autoconfig = if socks5_enabled { + // Currently we can't do http requests through socks5, to not leak + // the ip, just don't do online autoconfig + info!(ctx, "socks5 enabled, skipping autoconfig"); + None + } else { + get_autoconfig(ctx, param, ¶m_domain, ¶m_addr_urlencoded).await + } } } else { param_autoconfig = None; @@ -320,6 +333,7 @@ async fn configure(ctx: &Context, param: &mut LoginParam) -> Result<()> { match try_smtp_one_param( &context_smtp, &smtp_param, + &socks5_config, &smtp_addr, oauth2, provider_strict_tls, @@ -359,7 +373,16 @@ async fn configure(ctx: &Context, param: &mut LoginParam) -> Result<()> { param.imap.port = imap_server.port; param.imap.security = imap_server.socket; - match try_imap_one_param(ctx, ¶m.imap, ¶m.addr, oauth2, provider_strict_tls).await { + match try_imap_one_param( + ctx, + ¶m.imap, + ¶m.socks5_config, + ¶m.addr, + oauth2, + provider_strict_tls, + ) + .await + { Ok(configured_imap) => { imap = Some(configured_imap); break; @@ -507,6 +530,7 @@ async fn get_autoconfig( async fn try_imap_one_param( context: &Context, param: &ServerLoginParam, + socks5_config: &Option, addr: &str, oauth2: bool, provider_strict_tls: bool, @@ -519,7 +543,16 @@ async fn try_imap_one_param( let (_s, r) = async_std::channel::bounded(1); - let mut imap = match Imap::new(param, addr, oauth2, provider_strict_tls, r).await { + let mut imap = match Imap::new( + param, + socks5_config.clone(), + addr, + oauth2, + provider_strict_tls, + r, + ) + .await + { Err(err) => { info!(context, "failure: {}", err); return Err(ConfigurationError { @@ -548,19 +581,37 @@ async fn try_imap_one_param( async fn try_smtp_one_param( context: &Context, param: &ServerLoginParam, + socks5_config: &Option, addr: &str, oauth2: bool, provider_strict_tls: bool, smtp: &mut Smtp, ) -> Result<(), ConfigurationError> { let inf = format!( - "smtp: {}@{}:{} security={} certificate_checks={} oauth2={}", - param.user, param.server, param.port, param.security, param.certificate_checks, oauth2 + "smtp: {}@{}:{} security={} certificate_checks={} oauth2={} socks5_config={}", + param.user, + param.server, + param.port, + param.security, + param.certificate_checks, + oauth2, + if let Some(socks5_config) = socks5_config { + socks5_config.to_string() + } else { + "None".to_string() + } ); info!(context, "Trying: {}", inf); if let Err(err) = smtp - .connect(context, param, addr, oauth2, provider_strict_tls) + .connect( + context, + param, + socks5_config, + addr, + oauth2, + provider_strict_tls, + ) .await { info!(context, "failure: {}", err); diff --git a/src/context.rs b/src/context.rs index 152a3a114..705658cc1 100644 --- a/src/context.rs +++ b/src/context.rs @@ -289,6 +289,7 @@ impl Context { let request_msgs = message::get_request_msg_cnt(self).await as usize; let contacts = Contact::get_real_cnt(self).await? as usize; let is_configured = self.get_config_int(Config::Configured).await?; + let socks5_enabled = self.get_config_int(Config::Socks5Enabled).await?; let dbversion = self .sql .get_raw_config_int("dbversion") @@ -357,6 +358,7 @@ impl Context { .unwrap_or_else(|| "".to_string()), ); res.insert("is_configured", is_configured.to_string()); + res.insert("socks5_enabled", socks5_enabled.to_string()); res.insert("entered_account_settings", l.to_string()); res.insert("used_account_settings", l2.to_string()); res.insert( @@ -878,6 +880,10 @@ mod tests { "send_security", "server_flags", "smtp_certificate_checks", + "socks5_host", + "socks5_port", + "socks5_user", + "socks5_password", ]; let t = TestContext::new().await; let info = t.get_info().await.unwrap(); diff --git a/src/imap.rs b/src/imap.rs index a3a9d306e..c8486e374 100644 --- a/src/imap.rs +++ b/src/imap.rs @@ -27,6 +27,7 @@ use crate::events::EventType; use crate::headerdef::{HeaderDef, HeaderDefMap}; use crate::job::{self, Action}; use crate::login_param::{CertificateChecks, LoginParam, ServerLoginParam}; +use crate::login_param::{ServerAddress, Socks5Config}; use crate::message::{self, update_server_uid, MessageState}; use crate::mimeparser; use crate::oauth2::dc_get_oauth2_access_token; @@ -142,6 +143,7 @@ impl FolderMeaning { struct ImapConfig { pub addr: String, pub lp: ServerLoginParam, + pub socks5_config: Option, pub strict_tls: bool, pub oauth2: bool, pub selected_folder: Option, @@ -165,6 +167,7 @@ impl Imap { /// `addr` is used to renew token if OAuth2 authentication is used. pub async fn new( lp: &ServerLoginParam, + socks5_config: Option, addr: &str, oauth2: bool, provider_strict_tls: bool, @@ -183,6 +186,7 @@ impl Imap { let config = ImapConfig { addr: addr.to_string(), lp: lp.clone(), + socks5_config, strict_tls, oauth2, selected_folder: None, @@ -222,6 +226,7 @@ impl Imap { let imap = Self::new( ¶m.imap, + param.socks5_config.clone(), ¶m.addr, param.server_flags & DC_LP_AUTH_OAUTH2 != 0, param.provider.map_or(false, |provider| provider.strict_tls), @@ -261,7 +266,20 @@ impl Imap { let imap_server: &str = config.lp.server.as_ref(); let imap_port = config.lp.port; - match Client::connect_insecure((imap_server, imap_port)).await { + let connection = if let Some(socks5_config) = &config.socks5_config { + Client::connect_insecure_socks5( + &ServerAddress { + host: imap_server.to_string(), + port: imap_port, + }, + socks5_config.clone(), + ) + .await + } else { + Client::connect_insecure((imap_server, imap_port)).await + }; + + match connection { Ok(client) => { if config.lp.security == Socket::Starttls { client.secure(imap_server, config.strict_tls).await @@ -276,7 +294,20 @@ impl Imap { let imap_server: &str = config.lp.server.as_ref(); let imap_port = config.lp.port; - Client::connect_secure((imap_server, imap_port), imap_server, config.strict_tls).await + if let Some(socks5_config) = &config.socks5_config { + Client::connect_secure_socks5( + &ServerAddress { + host: imap_server.to_string(), + port: imap_port, + }, + config.strict_tls, + socks5_config.clone(), + ) + .await + } else { + Client::connect_secure((imap_server, imap_port), imap_server, config.strict_tls) + .await + } }; let login_res = match connection_res { diff --git a/src/imap/client.rs b/src/imap/client.rs index 0c6964922..fb76516e0 100644 --- a/src/imap/client.rs +++ b/src/imap/client.rs @@ -1,16 +1,24 @@ -use std::ops::{Deref, DerefMut}; +use std::{ + ops::{Deref, DerefMut}, + time::Duration, +}; use async_imap::{ error::{Error as ImapError, Result as ImapResult}, Client as ImapClient, }; + +use async_smtp::ServerAddress; use async_std::net::{self, TcpStream}; use super::session::Session; -use crate::login_param::dc_build_tls; +use crate::login_param::{dc_build_tls, Socks5Config}; use super::session::SessionStream; +/// IMAP write and read timeout in seconds. +const IMAP_TIMEOUT: u64 = 30; + #[derive(Debug)] pub(crate) struct Client { is_secure: bool, @@ -111,6 +119,63 @@ impl Client { }) } + pub async fn connect_secure_socks5( + target_addr: &ServerAddress, + strict_tls: bool, + socks5_config: Socks5Config, + ) -> ImapResult { + let socks5_stream: Box = Box::new( + match socks5_config + .connect(target_addr, Some(Duration::from_secs(IMAP_TIMEOUT))) + .await + { + Ok(s) => s, + Err(e) => return ImapResult::Err(async_imap::error::Error::Bad(e.to_string())), + }, + ); + + let tls = dc_build_tls(strict_tls); + let tls_stream: Box = + Box::new(tls.connect(target_addr.host.clone(), socks5_stream).await?); + let mut client = ImapClient::new(tls_stream); + + let _greeting = client + .read_response() + .await + .ok_or_else(|| ImapError::Bad("failed to read greeting".to_string()))?; + + Ok(Client { + is_secure: true, + inner: client, + }) + } + + pub async fn connect_insecure_socks5( + target_addr: &ServerAddress, + socks5_config: Socks5Config, + ) -> ImapResult { + let socks5_stream: Box = Box::new( + match socks5_config + .connect(target_addr, Some(Duration::from_secs(IMAP_TIMEOUT))) + .await + { + Ok(s) => s, + Err(e) => return ImapResult::Err(async_imap::error::Error::Bad(e.to_string())), + }, + ); + + let mut client = ImapClient::new(socks5_stream); + let _greeting = client + .read_response() + .await + .ok_or_else(|| ImapError::Bad("failed to read greeting".to_string()))?; + + Ok(Client { + is_secure: false, + inner: client, + }) + } + pub async fn secure(self, domain: &str, strict_tls: bool) -> ImapResult { if self.is_secure { Ok(self) diff --git a/src/imap/session.rs b/src/imap/session.rs index e533cc22e..4b7c7c001 100644 --- a/src/imap/session.rs +++ b/src/imap/session.rs @@ -3,6 +3,7 @@ use std::ops::{Deref, DerefMut}; use async_imap::Session as ImapSession; use async_native_tls::TlsStream; use async_std::net::TcpStream; +use fast_socks5::client::Socks5Stream; #[derive(Debug)] pub(crate) struct Session { @@ -17,6 +18,7 @@ pub(crate) trait SessionStream: impl SessionStream for TlsStream> {} impl SessionStream for TlsStream {} impl SessionStream for TcpStream {} +impl SessionStream for Socks5Stream {} impl Deref for Session { type Target = ImapSession>; diff --git a/src/login_param.rs b/src/login_param.rs index cc377bb41..bc03d42fb 100644 --- a/src/login_param.rs +++ b/src/login_param.rs @@ -2,11 +2,18 @@ use std::borrow::Cow; use std::fmt; +use std::time::Duration; use crate::provider::{get_provider_by_id, Provider}; use crate::{context::Context, provider::Socket}; use anyhow::Result; +use async_std::io; +use async_std::net::TcpStream; + +pub use async_smtp::ServerAddress; +use fast_socks5::client::Socks5Stream; + #[derive(Copy, Clone, Debug, Display, FromPrimitive, PartialEq, Eq)] #[repr(u32)] #[strum(serialize_all = "snake_case")] @@ -44,6 +51,84 @@ pub struct ServerLoginParam { pub certificate_checks: CertificateChecks, } +#[derive(Default, Debug, Clone, PartialEq)] +pub struct Socks5Config { + pub host: String, + pub port: u16, + pub user_password: Option<(String, String)>, +} + +impl Socks5Config { + /// Reads SOCKS5 configuration from the database. + pub async fn from_database(context: &Context) -> Result> { + let sql = &context.sql; + + let enabled = sql + .get_raw_config_bool("socks5_enabled") + .await + .unwrap_or(false); + if enabled { + let host = sql.get_raw_config("socks5_host").await?.unwrap_or_default(); + let port: u16 = sql + .get_raw_config_int("socks5_port") + .await? + .unwrap_or_default() as u16; + let user = sql.get_raw_config("socks5_user").await?.unwrap_or_default(); + let password = sql + .get_raw_config("socks5_password") + .await? + .unwrap_or_default(); + + let socks5_config = Self { + host, + port, + user_password: if !user.is_empty() { + Some((user, password)) + } else { + None + }, + }; + Ok(Some(socks5_config)) + } else { + Ok(None) + } + } + + pub async fn connect( + &self, + target_addr: &ServerAddress, + timeout: Option, + ) -> io::Result> { + self.to_async_smtp_socks5_config() + .connect(target_addr, timeout) + .await + } + + pub fn to_async_smtp_socks5_config(&self) -> async_smtp::smtp::Socks5Config { + async_smtp::smtp::Socks5Config { + host: self.host.clone(), + port: self.port, + user_password: self.user_password.clone(), + } + } +} + +impl fmt::Display for Socks5Config { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "host:{},port:{},user_password:{}", + self.host, + self.port, + if let Some(user_password) = self.user_password.clone() { + format!("user: {}, password: ***", user_password.0) + } else { + "user: None".to_string() + } + ) + } +} + #[derive(Default, Debug, Clone, PartialEq)] pub struct LoginParam { pub addr: String, @@ -51,6 +136,7 @@ pub struct LoginParam { pub smtp: ServerLoginParam, pub server_flags: i32, pub provider: Option<&'static Provider>, + pub socks5_config: Option, } impl LoginParam { @@ -130,6 +216,8 @@ impl LoginParam { .await? .and_then(|provider_id| get_provider_by_id(&provider_id)); + let socks5_config = Socks5Config::from_database(context).await?; + Ok(LoginParam { addr, imap: ServerLoginParam { @@ -150,6 +238,7 @@ impl LoginParam { }, provider, server_flags, + socks5_config, }) } @@ -334,6 +423,8 @@ mod tests { }, server_flags: 0, provider: get_provider_by_id("example.com"), + // socks5_config is not saved by `save_to_database`, using default value + socks5_config: None, }; param.save_to_database(&t, "foobar_").await?; diff --git a/src/oauth2.rs b/src/oauth2.rs index 7c4d2418c..15d25a1d3 100644 --- a/src/oauth2.rs +++ b/src/oauth2.rs @@ -6,6 +6,7 @@ use anyhow::Result; use percent_encoding::{utf8_percent_encode, NON_ALPHANUMERIC}; use serde::Deserialize; +use crate::config::Config; use crate::context::Context; use crate::dc_tools::time; use crate::provider; @@ -56,7 +57,11 @@ pub async fn dc_get_oauth2_url( addr: &str, redirect_uri: &str, ) -> Option { - if let Some(oauth2) = Oauth2::from_address(addr).await { + let socks5_enabled = context + .get_config_bool(Config::Socks5Enabled) + .await + .unwrap_or(false); + if let Some(oauth2) = Oauth2::from_address(addr, socks5_enabled).await { if context .sql .set_raw_config("oauth2_pending_redirect_uri", Some(redirect_uri)) @@ -80,7 +85,11 @@ pub async fn dc_get_oauth2_access_token( code: &str, regenerate: bool, ) -> Result> { - if let Some(oauth2) = Oauth2::from_address(addr).await { + let socks5_enabled = context + .get_config_bool(Config::Socks5Enabled) + .await + .unwrap_or(false); + if let Some(oauth2) = Oauth2::from_address(addr, socks5_enabled).await { let lock = context.oauth2_mutex.lock().await; // read generated token @@ -225,7 +234,11 @@ pub async fn dc_get_oauth2_addr( addr: &str, code: &str, ) -> Result> { - let oauth2 = match Oauth2::from_address(addr).await { + let socks5_enabled = context + .get_config_bool(Config::Socks5Enabled) + .await + .unwrap_or(false); + let oauth2 = match Oauth2::from_address(addr, socks5_enabled).await { Some(o) => o, None => return Ok(None), }; @@ -253,13 +266,13 @@ pub async fn dc_get_oauth2_addr( } impl Oauth2 { - async fn from_address(addr: &str) -> Option { + async fn from_address(addr: &str, skip_mx: bool) -> Option { let addr_normalized = normalize_addr(addr); if let Some(domain) = addr_normalized .find('@') .map(|index| addr_normalized.split_at(index + 1).1) { - if let Some(oauth2_authorizer) = provider::get_provider_info(domain) + if let Some(oauth2_authorizer) = provider::get_provider_info(domain, skip_mx) .await .and_then(|provider| provider.oauth2_authorizer.as_ref()) { @@ -357,29 +370,29 @@ mod tests { #[async_std::test] async fn test_oauth_from_address() { assert_eq!( - Oauth2::from_address("hello@gmail.com").await, + Oauth2::from_address("hello@gmail.com", false).await, Some(OAUTH2_GMAIL) ); assert_eq!( - Oauth2::from_address("hello@googlemail.com").await, + Oauth2::from_address("hello@googlemail.com", false).await, Some(OAUTH2_GMAIL) ); assert_eq!( - Oauth2::from_address("hello@yandex.com").await, + Oauth2::from_address("hello@yandex.com", false).await, Some(OAUTH2_YANDEX) ); assert_eq!( - Oauth2::from_address("hello@yandex.ru").await, + Oauth2::from_address("hello@yandex.ru", false).await, Some(OAUTH2_YANDEX) ); - assert_eq!(Oauth2::from_address("hello@web.de").await, None); + assert_eq!(Oauth2::from_address("hello@web.de", false).await, None); } #[async_std::test] async fn test_oauth_from_mx() { assert_eq!( - Oauth2::from_address("hello@google.com").await, + Oauth2::from_address("hello@google.com", false).await, Some(OAUTH2_GMAIL) ); } diff --git a/src/provider.rs b/src/provider.rs index 1c094ef52..722cbfd80 100644 --- a/src/provider.rs +++ b/src/provider.rs @@ -89,15 +89,17 @@ pub struct Provider { /// /// For compatibility, email address can be passed to this function /// instead of the domain. -pub async fn get_provider_info(domain: &str) -> Option<&'static Provider> { +pub async fn get_provider_info(domain: &str, skip_mx: bool) -> Option<&'static Provider> { let domain = domain.rsplitn(2, '@').next()?; if let Some(provider) = get_provider_by_domain(domain) { return Some(provider); } - if let Some(provider) = get_provider_by_mx(domain).await { - return Some(provider); + if !skip_mx { + if let Some(provider) = get_provider_by_mx(domain).await { + return Some(provider); + } } None @@ -221,11 +223,17 @@ mod tests { #[async_std::test] async fn test_get_provider_info() { - assert!(get_provider_info("").await.is_none()); - assert!(get_provider_info("google.com").await.unwrap().id == "gmail"); + assert!(get_provider_info("", false).await.is_none()); + assert!(get_provider_info("google.com", false).await.unwrap().id == "gmail"); // get_provider_info() accepts email addresses for backwards compatibility - assert!(get_provider_info("example@google.com").await.unwrap().id == "gmail"); + assert!( + get_provider_info("example@google.com", false) + .await + .unwrap() + .id + == "gmail" + ); } #[test] diff --git a/src/smtp.rs b/src/smtp.rs index 7bf2f7e67..bf1ce1ca8 100644 --- a/src/smtp.rs +++ b/src/smtp.rs @@ -5,11 +5,13 @@ pub mod send; use std::time::{Duration, SystemTime}; use async_smtp::smtp::client::net::ClientTlsParameters; -use async_smtp::{error, smtp, EmailAddress}; +use async_smtp::{error, smtp, EmailAddress, ServerAddress}; use crate::constants::DC_LP_AUTH_OAUTH2; use crate::events::EventType; -use crate::login_param::{dc_build_tls, CertificateChecks, LoginParam, ServerLoginParam}; +use crate::login_param::{ + dc_build_tls, CertificateChecks, LoginParam, ServerLoginParam, Socks5Config, +}; use crate::oauth2::dc_get_oauth2_access_token; use crate::provider::Socket; use crate::{context::Context, scheduler::connectivity::ConnectivityStore}; @@ -29,8 +31,6 @@ pub enum Error { }, #[error("SMTP failed to connect: {0}")] ConnectionFailure(#[source] smtp::error::Error), - #[error("SMTP failed to setup connection: {0}")] - ConnectionSetupFailure(#[source] smtp::error::Error), #[error("SMTP oauth2 error {address}")] Oauth2 { address: String }, #[error("TLS error {0}")] @@ -106,6 +106,7 @@ impl Smtp { self.connect( context, &lp.smtp, + &lp.socks5_config, &lp.addr, lp.server_flags & DC_LP_AUTH_OAUTH2 != 0, lp.provider.map_or(false, |provider| provider.strict_tls), @@ -118,6 +119,7 @@ impl Smtp { &mut self, context: &Context, lp: &ServerLoginParam, + socks5_config: &Option, addr: &str, oauth2: bool, provider_strict_tls: bool, @@ -187,17 +189,20 @@ impl Smtp { _ => smtp::ClientSecurity::Wrapper(tls_parameters), }; - let client = smtp::SmtpClient::with_security((domain.as_str(), port), security) - .await - .map_err(Error::ConnectionSetupFailure)?; + let client = + smtp::SmtpClient::with_security(ServerAddress::new(domain.to_string(), port), security); - let client = client + let mut client = client .smtp_utf8(true) .credentials(creds) .authentication_mechanism(mechanism) .connection_reuse(smtp::ConnectionReuseParameters::ReuseUnlimited) .timeout(Some(Duration::from_secs(SMTP_TIMEOUT))); + if let Some(socks5_config) = socks5_config { + client = client.use_socks5(socks5_config.to_async_smtp_socks5_config()); + } + let mut trans = client.into_transport(); if let Err(err) = trans.connect().await { return Err(Error::ConnectionFailure(err)); diff --git a/standards.md b/standards.md index 749aba997..9d2d22f46 100644 --- a/standards.md +++ b/standards.md @@ -5,6 +5,7 @@ Some of the standards Delta Chat is based on: Tasks | Standards ---------------------------------|--------------------------------------------- Transport | IMAP v4 ([RFC 3501](https://tools.ietf.org/html/rfc3501)), SMTP ([RFC 5321](https://tools.ietf.org/html/rfc5321)) and Internet Message Format (IMF, [RFC 5322](https://tools.ietf.org/html/rfc5322)) +Proxy | SOCKS5 ([RFC 1928](https://tools.ietf.org/html/rfc1928)) Embedded media | MIME Document Series ([RFC 2045](https://tools.ietf.org/html/rfc2045), [RFC 2046](https://tools.ietf.org/html/rfc2046)), Content-Disposition Header ([RFC 2183](https://tools.ietf.org/html/rfc2183)), Multipart/Related ([RFC 2387](https://tools.ietf.org/html/rfc2387)) Text and Quote encoding | Fixed, Flowed ([RFC 3676](https://tools.ietf.org/html/rfc3676)) Filename encoding | Encoded Words ([RFC 2047](https://tools.ietf.org/html/rfc2047)), Encoded Word Extensions ([RFC 2231](https://tools.ietf.org/html/rfc2231))