mirror of
https://github.com/chatmail/core.git
synced 2026-04-23 08:26:30 +03:00
Implement socks5 support
This adds following settings: - Socks5Enabled - Socks5Host - Socks5Port - Socks5User - Socks5Password Currently http requests and dns requests are not getting executed as they currently can't get tunneled through socks5 proxy. Therefore gmail with oauth2 wont work through tor.
This commit is contained in:
17
Cargo.lock
generated
17
Cargo.lock
generated
@@ -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"
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(),
|
||||
}
|
||||
|
||||
@@ -1168,7 +1168,11 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
||||
}
|
||||
"providerinfo" => {
|
||||
ensure!(!arg1.is_empty(), "Argument <addr> 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);
|
||||
|
||||
@@ -48,6 +48,12 @@ pub enum Config {
|
||||
SmtpCertificateChecks,
|
||||
ServerFlags,
|
||||
|
||||
Socks5Enabled,
|
||||
Socks5Host,
|
||||
Socks5Port,
|
||||
Socks5User,
|
||||
Socks5Password,
|
||||
|
||||
Displayname,
|
||||
Selfstatus,
|
||||
Selfavatar,
|
||||
|
||||
@@ -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<Socks5Config>,
|
||||
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<Socks5Config>,
|
||||
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);
|
||||
|
||||
@@ -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(|| "<unset>".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();
|
||||
|
||||
35
src/imap.rs
35
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<Socks5Config>,
|
||||
pub strict_tls: bool,
|
||||
pub oauth2: bool,
|
||||
pub selected_folder: Option<String>,
|
||||
@@ -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<Socks5Config>,
|
||||
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 {
|
||||
|
||||
@@ -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<Self> {
|
||||
let socks5_stream: Box<dyn SessionStream> = 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<dyn SessionStream> =
|
||||
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<Self> {
|
||||
let socks5_stream: Box<dyn SessionStream> = 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<Client> {
|
||||
if self.is_secure {
|
||||
Ok(self)
|
||||
|
||||
@@ -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<Box<dyn SessionStream>> {}
|
||||
impl SessionStream for TlsStream<TcpStream> {}
|
||||
impl SessionStream for TcpStream {}
|
||||
impl SessionStream for Socks5Stream<TcpStream> {}
|
||||
|
||||
impl Deref for Session {
|
||||
type Target = ImapSession<Box<dyn SessionStream>>;
|
||||
|
||||
@@ -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<Option<Self>> {
|
||||
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<Duration>,
|
||||
) -> io::Result<Socks5Stream<TcpStream>> {
|
||||
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<Socks5Config>,
|
||||
}
|
||||
|
||||
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?;
|
||||
|
||||
@@ -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<String> {
|
||||
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<Option<String>> {
|
||||
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<Option<String>> {
|
||||
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<Self> {
|
||||
async fn from_address(addr: &str, skip_mx: bool) -> Option<Self> {
|
||||
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)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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]
|
||||
|
||||
21
src/smtp.rs
21
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<Socks5Config>,
|
||||
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));
|
||||
|
||||
@@ -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))
|
||||
|
||||
Reference in New Issue
Block a user