mirror of
https://github.com/chatmail/core.git
synced 2026-04-25 01:16:29 +03:00
configure: try multiple servers for each protocol
LoginParamNew structure, which contained possible IMAP and SMTP configurations to try is replaced with uniform vectors of ServerParams structures. These vectors are initialized from provider database, online Mozilla or Outlook XML configuration or user entered parameters. During configuration, vectors of ServerParams are expanded to replace unknown values with all possible variants, which are tried one by one until configuration succeeds or all variants for a particular protocol (IMAP or SMTP) are exhausted. ServerParams structure is moved into configure submodule, and all dependencies on it outside of this submodule are removed.
This commit is contained in:
committed by
link2xt
parent
927c7eb59d
commit
4481ab18f5
@@ -3,6 +3,7 @@
|
||||
mod auto_mozilla;
|
||||
mod auto_outlook;
|
||||
mod read_url;
|
||||
mod server_params;
|
||||
|
||||
use anyhow::{bail, ensure, Context as _, Result};
|
||||
use async_std::prelude::*;
|
||||
@@ -13,16 +14,16 @@ use crate::constants::*;
|
||||
use crate::context::Context;
|
||||
use crate::dc_tools::*;
|
||||
use crate::imap::Imap;
|
||||
use crate::login_param::{CertificateChecks, LoginParam, LoginParamNew, ServerParams};
|
||||
use crate::login_param::{LoginParam, ServerLoginParam};
|
||||
use crate::message::Message;
|
||||
use crate::oauth2::*;
|
||||
use crate::provider::Socket;
|
||||
use crate::provider::{Protocol, Socket, UsernamePattern};
|
||||
use crate::smtp::Smtp;
|
||||
use crate::{chat, e2ee, provider};
|
||||
|
||||
use auto_mozilla::moz_autoconfigure;
|
||||
use auto_outlook::outlk_autodiscover;
|
||||
use provider::{Protocol, UsernamePattern};
|
||||
use server_params::ServerParams;
|
||||
|
||||
macro_rules! progress {
|
||||
($context:tt, $progress:expr) => {
|
||||
@@ -117,16 +118,33 @@ impl Context {
|
||||
}
|
||||
|
||||
async fn configure(ctx: &Context, param: &mut LoginParam) -> Result<()> {
|
||||
let mut param_autoconfig: Option<LoginParamNew> = None;
|
||||
let mut keep_flags = 0;
|
||||
|
||||
// Read login parameters from the database
|
||||
progress!(ctx, 1);
|
||||
|
||||
// Check basic settings.
|
||||
ensure!(!param.addr.is_empty(), "Please enter an email address.");
|
||||
|
||||
// Only check for IMAP password, SMTP password is an "advanced" setting.
|
||||
ensure!(!param.imap.password.is_empty(), "Please enter a password.");
|
||||
if param.smtp.password.is_empty() {
|
||||
param.smtp.password = param.imap.password.clone()
|
||||
}
|
||||
|
||||
// Normalize authentication flags.
|
||||
let oauth2 = match param.server_flags & DC_LP_AUTH_FLAGS as i32 {
|
||||
DC_LP_AUTH_OAUTH2 => true,
|
||||
DC_LP_AUTH_NORMAL => false,
|
||||
_ => false,
|
||||
};
|
||||
param.server_flags &= !(DC_LP_AUTH_FLAGS as i32);
|
||||
param.server_flags |= if oauth2 {
|
||||
DC_LP_AUTH_OAUTH2 as i32
|
||||
} else {
|
||||
DC_LP_AUTH_NORMAL as i32
|
||||
};
|
||||
|
||||
// Step 1: Load the parameters and check email-address and password
|
||||
|
||||
if 0 != param.server_flags & DC_LP_AUTH_OAUTH2 {
|
||||
if oauth2 {
|
||||
// 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);
|
||||
@@ -151,93 +169,106 @@ async fn configure(ctx: &Context, param: &mut LoginParam) -> Result<()> {
|
||||
// Step 2: Autoconfig
|
||||
progress!(ctx, 200);
|
||||
|
||||
// param.mail_user.is_empty() -- the user can enter a loginname which is used by autoconfig then
|
||||
// param.send_pw.is_empty() -- the password cannot be auto-configured and is no criterion for
|
||||
// autoconfig or not
|
||||
let param_autoconfig;
|
||||
if param.imap.server.is_empty()
|
||||
&& param.imap.port == 0
|
||||
&& param.imap.security == Socket::Automatic
|
||||
&& param.imap.user.is_empty()
|
||||
&& param.smtp.server.is_empty()
|
||||
&& param.smtp.port == 0
|
||||
&& param.smtp.security == Socket::Automatic
|
||||
&& param.smtp.user.is_empty()
|
||||
&& (param.server_flags & !DC_LP_AUTH_OAUTH2) == 0
|
||||
{
|
||||
// no advanced parameters entered by the user: query provider-database or do Autoconfig
|
||||
keep_flags = param.server_flags & DC_LP_AUTH_OAUTH2;
|
||||
if let Some(new_param) = get_offline_autoconfig(ctx, ¶m) {
|
||||
// got parameters from our provider-database, skip Autoconfig, preserve the OAuth2 setting
|
||||
param_autoconfig = Some(new_param);
|
||||
}
|
||||
|
||||
if param_autoconfig.is_none() {
|
||||
if let Some(servers) = get_offline_autoconfig(ctx, ¶m.addr) {
|
||||
param_autoconfig = Some(servers);
|
||||
} else {
|
||||
param_autoconfig =
|
||||
get_autoconfig(ctx, param, ¶m_domain, ¶m_addr_urlencoded).await;
|
||||
}
|
||||
} else {
|
||||
param_autoconfig = None;
|
||||
}
|
||||
|
||||
// C. Do we have any autoconfig result?
|
||||
progress!(ctx, 500);
|
||||
if let Some(ref cfg) = param_autoconfig {
|
||||
if let Some(cfg) = loginparam_new_to_old(ctx, cfg) {
|
||||
info!(ctx, "Got autoconfig: {:?}", &cfg);
|
||||
if !cfg.imap.user.is_empty() {
|
||||
param.imap.user = cfg.imap.user.clone();
|
||||
}
|
||||
// all other values are always NULL when entering autoconfig
|
||||
param.imap.server = cfg.imap.server.clone();
|
||||
param.imap.port = cfg.imap.port;
|
||||
param.imap.security = cfg.imap.security;
|
||||
param.smtp.server = cfg.smtp.server.clone();
|
||||
param.smtp.port = cfg.smtp.port;
|
||||
param.smtp.user = cfg.smtp.user.clone();
|
||||
param.smtp.security = cfg.smtp.security;
|
||||
param.server_flags = cfg.server_flags;
|
||||
// although param_autoconfig's data are no longer needed from,
|
||||
// it is used to later to prevent trying variations of port/server/logins
|
||||
}
|
||||
}
|
||||
param.server_flags |= keep_flags;
|
||||
|
||||
// Step 3: Fill missing fields with defaults
|
||||
if param.smtp.user.is_empty() {
|
||||
param.smtp.user = param.imap.user.clone();
|
||||
}
|
||||
if param.smtp.password.is_empty() {
|
||||
param.smtp.password = param.imap.password.clone()
|
||||
}
|
||||
if !dc_exactly_one_bit_set(param.server_flags & DC_LP_AUTH_FLAGS as i32) {
|
||||
param.server_flags &= !(DC_LP_AUTH_FLAGS as i32);
|
||||
param.server_flags |= DC_LP_AUTH_NORMAL as i32
|
||||
}
|
||||
|
||||
// do we have a complete configuration?
|
||||
ensure!(
|
||||
!param.imap.password.is_empty() && !param.smtp.password.is_empty(),
|
||||
"Account settings incomplete."
|
||||
);
|
||||
let servers: Vec<ServerParams> = param_autoconfig
|
||||
.unwrap_or_else(|| {
|
||||
vec![
|
||||
ServerParams {
|
||||
protocol: Protocol::IMAP,
|
||||
hostname: param.imap.server.clone(),
|
||||
port: param.imap.port,
|
||||
socket: param.imap.security,
|
||||
username: param.imap.user.clone(),
|
||||
},
|
||||
ServerParams {
|
||||
protocol: Protocol::SMTP,
|
||||
hostname: param.smtp.server.clone(),
|
||||
port: param.smtp.port,
|
||||
socket: param.smtp.security,
|
||||
username: param.smtp.user.clone(),
|
||||
},
|
||||
]
|
||||
})
|
||||
.into_iter()
|
||||
// The order of expansion is important: ports are expanded the
|
||||
// last, so they are changed the first. Username is only
|
||||
// changed if default value (address with domain) didn't work
|
||||
// for all available hosts and ports.
|
||||
.flat_map(|params| params.expand_usernames(¶m.addr).into_iter())
|
||||
.flat_map(|params| params.expand_hostnames(¶m_domain).into_iter())
|
||||
.flat_map(|params| params.expand_ports().into_iter())
|
||||
.collect();
|
||||
|
||||
// Configure IMAP
|
||||
progress!(ctx, 600);
|
||||
// try to connect to IMAP - if we did not got an autoconfig,
|
||||
// do some further tries with different settings and username variations
|
||||
let (_s, r) = async_std::sync::channel(1);
|
||||
let mut imap = Imap::new(r);
|
||||
|
||||
if param_autoconfig.is_some() {
|
||||
if try_imap_one_param(ctx, ¶m, &mut imap).await.is_err() {
|
||||
bail!("IMAP autoconfig did not succeed");
|
||||
}
|
||||
} else {
|
||||
*param = try_imap_hostnames(ctx, param.clone(), &mut imap).await?;
|
||||
}
|
||||
progress!(ctx, 750);
|
||||
let mut imap_configured = false;
|
||||
for imap_server in servers
|
||||
.iter()
|
||||
.filter(|params| params.protocol == Protocol::IMAP)
|
||||
{
|
||||
param.imap.user = imap_server.username.clone();
|
||||
param.imap.server = imap_server.hostname.clone();
|
||||
param.imap.port = imap_server.port;
|
||||
param.imap.security = imap_server.socket;
|
||||
|
||||
let mut smtp = Smtp::new();
|
||||
if param_autoconfig.is_some() {
|
||||
if try_smtp_one_param(ctx, ¶m, &mut smtp).await.is_err() {
|
||||
bail!("SMTP autoconfig did not succeed");
|
||||
if try_imap_one_param(ctx, ¶m.imap, ¶m.addr, oauth2, &mut imap).await {
|
||||
imap_configured = true;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
*param = try_smtp_hostnames(ctx, param.clone(), &mut smtp).await?;
|
||||
}
|
||||
if !imap_configured {
|
||||
bail!("IMAP autoconfig did not succeed");
|
||||
}
|
||||
|
||||
// Configure SMTP
|
||||
progress!(ctx, 750);
|
||||
let mut smtp = Smtp::new();
|
||||
|
||||
let mut smtp_configured = false;
|
||||
for smtp_server in servers
|
||||
.iter()
|
||||
.filter(|params| params.protocol == Protocol::SMTP)
|
||||
{
|
||||
param.smtp.user = smtp_server.username.clone();
|
||||
param.smtp.server = smtp_server.hostname.clone();
|
||||
param.smtp.port = smtp_server.port;
|
||||
param.smtp.security = smtp_server.socket;
|
||||
|
||||
if try_smtp_one_param(ctx, ¶m.smtp, ¶m.addr, oauth2, &mut smtp).await {
|
||||
smtp_configured = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if !smtp_configured {
|
||||
bail!("SMTP autoconfig did not succeed");
|
||||
}
|
||||
|
||||
progress!(ctx, 900);
|
||||
|
||||
let create_mvbox = ctx.get_config_bool(Config::MvboxWatch).await
|
||||
@@ -306,8 +337,8 @@ impl AutoconfigSource {
|
||||
AutoconfigSource {
|
||||
provider: AutoconfigProvider::Outlook,
|
||||
url: format!(
|
||||
"https://{}{}/autodiscover/autodiscover.xml",
|
||||
"autodiscover.", domain
|
||||
"https://autodiscover.{}/autodiscover/autodiscover.xml",
|
||||
domain
|
||||
),
|
||||
},
|
||||
// always SSL for Thunderbird's database
|
||||
@@ -318,10 +349,10 @@ impl AutoconfigSource {
|
||||
]
|
||||
}
|
||||
|
||||
async fn fetch(&self, ctx: &Context, param: &LoginParam) -> Result<LoginParam> {
|
||||
async fn fetch(&self, ctx: &Context, param: &LoginParam) -> Result<Vec<ServerParams>> {
|
||||
let params = match self.provider {
|
||||
AutoconfigProvider::Mozilla => moz_autoconfigure(ctx, &self.url, ¶m).await?,
|
||||
AutoconfigProvider::Outlook => outlk_autodiscover(ctx, &self.url, ¶m).await?,
|
||||
AutoconfigProvider::Outlook => outlk_autodiscover(ctx, &self.url).await?,
|
||||
};
|
||||
|
||||
Ok(params)
|
||||
@@ -337,7 +368,7 @@ async fn get_autoconfig(
|
||||
param: &LoginParam,
|
||||
param_domain: &str,
|
||||
param_addr_urlencoded: &str,
|
||||
) -> Option<LoginParamNew> {
|
||||
) -> Option<Vec<ServerParams>> {
|
||||
let sources = AutoconfigSource::all(param_domain, param_addr_urlencoded);
|
||||
|
||||
let mut progress = 300;
|
||||
@@ -346,344 +377,104 @@ async fn get_autoconfig(
|
||||
progress!(ctx, progress);
|
||||
progress += 10;
|
||||
if let Ok(res) = res {
|
||||
return Some(loginparam_old_to_new(res));
|
||||
return Some(res);
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn get_offline_autoconfig(context: &Context, param: &LoginParam) -> Option<LoginParamNew> {
|
||||
fn get_offline_autoconfig(context: &Context, addr: &str) -> Option<Vec<ServerParams>> {
|
||||
info!(
|
||||
context,
|
||||
"checking internal provider-info for offline autoconfig"
|
||||
);
|
||||
|
||||
if let Some(provider) = provider::get_provider_info(¶m.addr) {
|
||||
if let Some(provider) = provider::get_provider_info(&addr) {
|
||||
match provider.status {
|
||||
provider::Status::OK | provider::Status::PREPARATION => {
|
||||
let imap = provider.get_imap_server();
|
||||
let smtp = provider.get_smtp_server();
|
||||
return Some(LoginParamNew {
|
||||
addr: param.addr.clone(),
|
||||
imap,
|
||||
smtp,
|
||||
});
|
||||
if provider.server.is_empty() {
|
||||
info!(context, "offline autoconfig found, but no servers defined");
|
||||
None
|
||||
} else {
|
||||
info!(context, "offline autoconfig found");
|
||||
let servers = provider
|
||||
.server
|
||||
.iter()
|
||||
.map(|s| ServerParams {
|
||||
protocol: s.protocol,
|
||||
socket: s.socket,
|
||||
hostname: s.hostname.to_string(),
|
||||
port: s.port,
|
||||
username: match s.username_pattern {
|
||||
UsernamePattern::EMAIL => addr.to_string(),
|
||||
UsernamePattern::EMAILLOCALPART => {
|
||||
if let Some(at) = addr.find('@') {
|
||||
addr.split_at(at).0.to_string()
|
||||
} else {
|
||||
addr.to_string()
|
||||
}
|
||||
}
|
||||
},
|
||||
})
|
||||
.collect();
|
||||
Some(servers)
|
||||
}
|
||||
}
|
||||
provider::Status::BROKEN => {
|
||||
info!(context, "offline autoconfig found, provider is broken");
|
||||
return None;
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
info!(context, "no offline autoconfig found");
|
||||
None
|
||||
}
|
||||
|
||||
pub fn loginparam_new_to_old(context: &Context, servers: &LoginParamNew) -> Option<LoginParam> {
|
||||
let LoginParamNew { addr, imap, smtp } = servers;
|
||||
if let Some(imap) = imap.get(0) {
|
||||
if let Some(smtp) = smtp.get(0) {
|
||||
let mut p = LoginParam::new();
|
||||
p.addr = addr.clone();
|
||||
|
||||
p.imap.server = imap.hostname.to_string();
|
||||
p.imap.user = imap.apply_username_pattern(addr.clone());
|
||||
p.imap.port = imap.port;
|
||||
p.imap.security = imap.socket;
|
||||
p.imap.certificate_checks = CertificateChecks::Automatic;
|
||||
|
||||
p.smtp.server = smtp.hostname.to_string();
|
||||
p.smtp.user = smtp.apply_username_pattern(addr.clone());
|
||||
p.smtp.port = smtp.port;
|
||||
p.smtp.security = smtp.socket;
|
||||
p.smtp.certificate_checks = CertificateChecks::Automatic;
|
||||
|
||||
info!(context, "offline autoconfig found: {}", p);
|
||||
return Some(p);
|
||||
}
|
||||
}
|
||||
info!(context, "offline autoconfig found, but no servers defined");
|
||||
None
|
||||
}
|
||||
|
||||
pub fn loginparam_old_to_new(p: LoginParam) -> LoginParamNew {
|
||||
LoginParamNew {
|
||||
addr: p.addr.clone(),
|
||||
imap: vec![ServerParams {
|
||||
protocol: Protocol::IMAP,
|
||||
socket: p.imap.security,
|
||||
port: p.imap.port,
|
||||
hostname: p.imap.server,
|
||||
username_pattern: if p.imap.user.contains('@') {
|
||||
UsernamePattern::EMAIL
|
||||
} else {
|
||||
UsernamePattern::EMAILLOCALPART
|
||||
},
|
||||
}],
|
||||
smtp: vec![ServerParams {
|
||||
protocol: Protocol::SMTP,
|
||||
socket: p.smtp.security,
|
||||
port: p.smtp.port,
|
||||
hostname: p.smtp.server,
|
||||
username_pattern: if p.smtp.user.contains('@') {
|
||||
UsernamePattern::EMAIL
|
||||
} else {
|
||||
provider::UsernamePattern::EMAILLOCALPART
|
||||
},
|
||||
}],
|
||||
}
|
||||
}
|
||||
|
||||
async fn try_imap_hostnames(
|
||||
context: &Context,
|
||||
mut param: LoginParam,
|
||||
imap: &mut Imap,
|
||||
) -> Result<LoginParam> {
|
||||
if param.imap.server.is_empty() {
|
||||
let parsed: EmailAddress = param.addr.parse().context("Bad email-address")?;
|
||||
let param_domain = parsed.domain;
|
||||
|
||||
param.imap.server = param_domain.clone();
|
||||
if let Ok(param) = try_imap_ports(context, param.clone(), imap).await {
|
||||
return Ok(param);
|
||||
}
|
||||
|
||||
progress!(context, 650);
|
||||
param.imap.server = "imap.".to_string() + ¶m_domain;
|
||||
if let Ok(param) = try_imap_ports(context, param.clone(), imap).await {
|
||||
return Ok(param);
|
||||
}
|
||||
|
||||
progress!(context, 700);
|
||||
param.imap.server = "mail.".to_string() + ¶m_domain;
|
||||
try_imap_ports(context, param, imap).await
|
||||
} else {
|
||||
progress!(context, 700);
|
||||
try_imap_ports(context, param, imap).await
|
||||
info!(context, "no offline autoconfig found");
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
// Try various IMAP ports and corresponding TLS settings.
|
||||
async fn try_imap_ports(
|
||||
async fn try_imap_one_param(
|
||||
context: &Context,
|
||||
mut param: LoginParam,
|
||||
param: &ServerLoginParam,
|
||||
addr: &str,
|
||||
oauth2: bool,
|
||||
imap: &mut Imap,
|
||||
) -> Result<LoginParam> {
|
||||
// Try to infer port from socket security.
|
||||
if param.imap.port == 0 {
|
||||
param.imap.port = match param.imap.security {
|
||||
Socket::SSL => 993,
|
||||
Socket::STARTTLS | Socket::Plain => 143,
|
||||
Socket::Automatic => 0,
|
||||
}
|
||||
}
|
||||
|
||||
if param.imap.port == 0 {
|
||||
// Neither port nor security is set.
|
||||
//
|
||||
// Try common secure combinations.
|
||||
|
||||
// Try TLS over port 993
|
||||
param.imap.security = Socket::SSL;
|
||||
param.imap.port = 993;
|
||||
if let Ok(login_param) = try_imap_usernames(context, param.clone(), imap).await {
|
||||
return Ok(login_param);
|
||||
}
|
||||
|
||||
// Try STARTTLS over port 143
|
||||
param.imap.security = Socket::STARTTLS;
|
||||
param.imap.port = 143;
|
||||
try_imap_usernames(context, param, imap).await
|
||||
} else if param.imap.security == Socket::Automatic {
|
||||
// Try TLS over user-provided port.
|
||||
param.imap.security = Socket::SSL;
|
||||
if let Ok(login_param) = try_imap_usernames(context, param.clone(), imap).await {
|
||||
return Ok(login_param);
|
||||
}
|
||||
|
||||
// Try STARTTLS over user-provided port.
|
||||
param.imap.security = Socket::STARTTLS;
|
||||
try_imap_usernames(context, param, imap).await
|
||||
} else {
|
||||
try_imap_usernames(context, param, imap).await
|
||||
}
|
||||
}
|
||||
|
||||
async fn try_imap_usernames(
|
||||
context: &Context,
|
||||
mut param: LoginParam,
|
||||
imap: &mut Imap,
|
||||
) -> Result<LoginParam> {
|
||||
if param.imap.user.is_empty() {
|
||||
param.imap.user = param.addr.clone();
|
||||
if let Err(e) = try_imap_one_param(context, ¶m, imap).await {
|
||||
if let Some(at) = param.imap.user.find('@') {
|
||||
param.imap.user = param.imap.user.split_at(at).0.to_string();
|
||||
try_imap_one_param(context, ¶m, imap).await?;
|
||||
} else {
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
Ok(param)
|
||||
} else {
|
||||
try_imap_one_param(context, ¶m, imap).await?;
|
||||
Ok(param)
|
||||
}
|
||||
}
|
||||
|
||||
async fn try_imap_one_param(context: &Context, param: &LoginParam, imap: &mut Imap) -> Result<()> {
|
||||
) -> bool {
|
||||
let inf = format!(
|
||||
"imap: {}@{}:{} security={} certificate_checks={} flags=0x{:x}",
|
||||
param.imap.user,
|
||||
param.imap.server,
|
||||
param.imap.port,
|
||||
param.imap.security,
|
||||
param.imap.certificate_checks,
|
||||
param.server_flags,
|
||||
"imap: {}@{}:{} security={} certificate_checks={} oauth2={}",
|
||||
param.user, param.server, param.port, param.security, param.certificate_checks, oauth2
|
||||
);
|
||||
info!(context, "Trying: {}", inf);
|
||||
|
||||
if imap
|
||||
.connect(
|
||||
context,
|
||||
¶m.imap,
|
||||
¶m.addr,
|
||||
param.server_flags & DC_LP_AUTH_OAUTH2 != 0,
|
||||
)
|
||||
.await
|
||||
{
|
||||
if imap.connect(context, param, addr, oauth2).await {
|
||||
info!(context, "success: {}", inf);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
bail!("Could not connect: {}", inf);
|
||||
}
|
||||
|
||||
async fn try_smtp_hostnames(
|
||||
context: &Context,
|
||||
mut param: LoginParam,
|
||||
smtp: &mut Smtp,
|
||||
) -> Result<LoginParam> {
|
||||
if param.smtp.server.is_empty() {
|
||||
let parsed: EmailAddress = param.addr.parse().context("Bad email-address")?;
|
||||
let param_domain = parsed.domain;
|
||||
|
||||
param.smtp.server = param_domain.clone();
|
||||
if let Ok(param) = try_smtp_ports(context, param.clone(), smtp).await {
|
||||
return Ok(param);
|
||||
}
|
||||
|
||||
progress!(context, 800);
|
||||
param.smtp.server = "smtp.".to_string() + ¶m_domain;
|
||||
if let Ok(param) = try_smtp_ports(context, param.clone(), smtp).await {
|
||||
return Ok(param);
|
||||
}
|
||||
|
||||
progress!(context, 850);
|
||||
param.smtp.server = "mail.".to_string() + ¶m_domain;
|
||||
try_smtp_ports(context, param, smtp).await
|
||||
true
|
||||
} else {
|
||||
progress!(context, 850);
|
||||
try_smtp_ports(context, param, smtp).await
|
||||
info!(context, "failure: {}", inf);
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
// Try various SMTP ports and corresponding TLS settings.
|
||||
async fn try_smtp_ports(
|
||||
async fn try_smtp_one_param(
|
||||
context: &Context,
|
||||
mut param: LoginParam,
|
||||
param: &ServerLoginParam,
|
||||
addr: &str,
|
||||
oauth2: bool,
|
||||
smtp: &mut Smtp,
|
||||
) -> Result<LoginParam> {
|
||||
// Try to infer port from socket security.
|
||||
if param.smtp.port == 0 {
|
||||
param.smtp.port = match param.smtp.security {
|
||||
Socket::Automatic => 0,
|
||||
Socket::STARTTLS | Socket::Plain => 587,
|
||||
Socket::SSL => 465,
|
||||
};
|
||||
}
|
||||
|
||||
if param.smtp.port == 0 {
|
||||
// Neither port nor security is set.
|
||||
//
|
||||
// Try common secure combinations.
|
||||
|
||||
// Try TLS over port 465.
|
||||
param.smtp.security = Socket::SSL;
|
||||
param.smtp.port = 465;
|
||||
if let Ok(login_param) = try_smtp_usernames(context, param.clone(), smtp).await {
|
||||
return Ok(login_param);
|
||||
}
|
||||
|
||||
// Try STARTTLS over port 587.
|
||||
param.smtp.security = Socket::STARTTLS;
|
||||
param.smtp.port = 587;
|
||||
try_smtp_usernames(context, param, smtp).await
|
||||
} else if param.smtp.security == Socket::Automatic {
|
||||
// Try TLS over user-provided port.
|
||||
param.smtp.security = Socket::SSL;
|
||||
if let Ok(param) = try_smtp_usernames(context, param.clone(), smtp).await {
|
||||
return Ok(param);
|
||||
}
|
||||
|
||||
// Try STARTTLS over user-provided port.
|
||||
param.smtp.security = Socket::STARTTLS;
|
||||
try_smtp_usernames(context, param, smtp).await
|
||||
} else {
|
||||
try_smtp_usernames(context, param, smtp).await
|
||||
}
|
||||
}
|
||||
|
||||
async fn try_smtp_usernames(
|
||||
context: &Context,
|
||||
mut param: LoginParam,
|
||||
smtp: &mut Smtp,
|
||||
) -> Result<LoginParam> {
|
||||
if param.smtp.user.is_empty() {
|
||||
param.smtp.user = param.addr.clone();
|
||||
if let Err(e) = try_smtp_one_param(context, ¶m, smtp).await {
|
||||
if let Some(at) = param.smtp.user.find('@') {
|
||||
param.smtp.user = param.smtp.user.split_at(at).0.to_string();
|
||||
try_smtp_one_param(context, ¶m, smtp).await?;
|
||||
} else {
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
Ok(param)
|
||||
} else {
|
||||
try_smtp_one_param(context, ¶m, smtp).await?;
|
||||
Ok(param)
|
||||
}
|
||||
}
|
||||
|
||||
async fn try_smtp_one_param(context: &Context, param: &LoginParam, smtp: &mut Smtp) -> Result<()> {
|
||||
) -> bool {
|
||||
let inf = format!(
|
||||
"smtp: {}@{}:{} security={} certificate_checks={} flags=0x{:x}",
|
||||
param.smtp.user,
|
||||
param.smtp.server,
|
||||
param.smtp.port,
|
||||
param.smtp.security,
|
||||
param.smtp.certificate_checks,
|
||||
param.server_flags
|
||||
"smtp: {}@{}:{} security={} certificate_checks={} oauth2={}",
|
||||
param.user, param.server, param.port, param.security, param.certificate_checks, oauth2
|
||||
);
|
||||
info!(context, "Trying: {}", inf);
|
||||
|
||||
if let Err(err) = smtp
|
||||
.connect(
|
||||
context,
|
||||
¶m.smtp,
|
||||
¶m.addr,
|
||||
param.server_flags & DC_LP_AUTH_OAUTH2 != 0,
|
||||
)
|
||||
.await
|
||||
{
|
||||
bail!("could not connect: {}", err);
|
||||
if let Err(err) = smtp.connect(context, param, addr, oauth2).await {
|
||||
info!(context, "failure: {}", err);
|
||||
false
|
||||
} else {
|
||||
info!(context, "success: {}", inf);
|
||||
smtp.disconnect().await;
|
||||
true
|
||||
}
|
||||
|
||||
info!(context, "success: {}", inf);
|
||||
smtp.disconnect().await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
@@ -698,9 +489,6 @@ pub enum Error {
|
||||
error: quick_xml::Error,
|
||||
},
|
||||
|
||||
#[error("Bad or incomplete autoconfig")]
|
||||
IncompleteAutoconfig(LoginParam),
|
||||
|
||||
#[error("Failed to get URL")]
|
||||
ReadUrlError(#[from] self::read_url::Error),
|
||||
|
||||
@@ -733,20 +521,15 @@ mod tests {
|
||||
async fn test_get_offline_autoconfig() {
|
||||
let context = TestContext::new().await.ctx;
|
||||
|
||||
let mut params = LoginParam::new();
|
||||
params.addr = "someone123@example.org".to_string();
|
||||
assert!(get_offline_autoconfig(&context, ¶ms).is_none());
|
||||
let addr = "someone123@example.org";
|
||||
assert!(get_offline_autoconfig(&context, addr).is_none());
|
||||
|
||||
let mut params = LoginParam::new();
|
||||
params.addr = "someone123@nauta.cu".to_string();
|
||||
let found_params = get_offline_autoconfig(&context, ¶ms).unwrap();
|
||||
assert_eq!(found_params.imap.len(), 1);
|
||||
assert_eq!(found_params.smtp.len(), 1);
|
||||
assert_eq!(found_params.imap[0].hostname, "imap.nauta.cu".to_string());
|
||||
assert_eq!(found_params.smtp[0].hostname, "smtp.nauta.cu".to_string());
|
||||
|
||||
let lp_old = loginparam_new_to_old(&context, &found_params).unwrap();
|
||||
assert_eq!(lp_old.imap.certificate_checks, CertificateChecks::Automatic);
|
||||
assert_eq!(lp_old.smtp.certificate_checks, CertificateChecks::Automatic);
|
||||
let addr = "someone123@nauta.cu";
|
||||
let found_params = get_offline_autoconfig(&context, addr).unwrap();
|
||||
assert_eq!(found_params.len(), 2);
|
||||
assert_eq!(found_params[0].protocol, Protocol::IMAP);
|
||||
assert_eq!(found_params[0].hostname, "imap.nauta.cu".to_string());
|
||||
assert_eq!(found_params[1].protocol, Protocol::SMTP);
|
||||
assert_eq!(found_params[1].hostname, "smtp.nauta.cu".to_string());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user