mirror of
https://github.com/chatmail/core.git
synced 2026-04-17 21:46:35 +03:00
Show a better toast and a notification when the password is wrong (because it was changed on the server)
660 lines
22 KiB
Rust
660 lines
22 KiB
Rust
//! Email accounts autoconfiguration process module
|
|
|
|
mod auto_mozilla;
|
|
mod auto_outlook;
|
|
mod read_url;
|
|
|
|
use anyhow::{bail, ensure, format_err, Context as _, Result};
|
|
use async_std::prelude::*;
|
|
use percent_encoding::{utf8_percent_encode, NON_ALPHANUMERIC};
|
|
|
|
use crate::config::Config;
|
|
use crate::constants::*;
|
|
use crate::context::Context;
|
|
use crate::dc_tools::*;
|
|
use crate::imap::Imap;
|
|
use crate::login_param::{CertificateChecks, LoginParam};
|
|
use crate::message::Message;
|
|
use crate::oauth2::*;
|
|
use crate::smtp::Smtp;
|
|
use crate::{chat, e2ee, provider};
|
|
|
|
use auto_mozilla::moz_autoconfigure;
|
|
use auto_outlook::outlk_autodiscover;
|
|
|
|
macro_rules! progress {
|
|
($context:tt, $progress:expr) => {
|
|
assert!(
|
|
$progress <= 1000,
|
|
"value in range 0..1000 expected with: 0=error, 1..999=progress, 1000=success"
|
|
);
|
|
$context.emit_event($crate::events::Event::ConfigureProgress($progress));
|
|
};
|
|
}
|
|
|
|
impl Context {
|
|
/// Checks if the context is already configured.
|
|
pub async fn is_configured(&self) -> bool {
|
|
self.sql.get_raw_config_bool(self, "configured").await
|
|
}
|
|
|
|
/// Configures this account with the currently set parameters.
|
|
pub async fn configure(&self) -> Result<()> {
|
|
use futures::future::FutureExt;
|
|
|
|
ensure!(
|
|
!self.scheduler.read().await.is_running(),
|
|
"cannot configure, already running"
|
|
);
|
|
ensure!(
|
|
self.sql.is_open().await,
|
|
"cannot configure, database not opened."
|
|
);
|
|
let cancel_channel = self.alloc_ongoing().await?;
|
|
|
|
let res = self
|
|
.inner_configure()
|
|
.race(cancel_channel.recv().map(|_| {
|
|
progress!(self, 0);
|
|
Ok(())
|
|
}))
|
|
.await;
|
|
|
|
self.free_ongoing().await;
|
|
|
|
res
|
|
}
|
|
|
|
async fn inner_configure(&self) -> Result<()> {
|
|
info!(self, "Configure ...");
|
|
|
|
let mut param = LoginParam::from_database(self, "").await;
|
|
let success = configure(self, &mut param).await;
|
|
self.set_config(Config::NotifyAboutWrongPw, None).await?;
|
|
|
|
if let Some(provider) = provider::get_provider_info(¶m.addr) {
|
|
if let Some(config_defaults) = &provider.config_defaults {
|
|
for def in config_defaults.iter() {
|
|
if !self.config_exists(def.key).await {
|
|
info!(self, "apply config_defaults {}={}", def.key, def.value);
|
|
self.set_config(def.key, Some(def.value)).await?;
|
|
} else {
|
|
info!(
|
|
self,
|
|
"skip already set config_defaults {}={}", def.key, def.value
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
if !provider.after_login_hint.is_empty() {
|
|
let mut msg = Message::new(Viewtype::Text);
|
|
msg.text = Some(provider.after_login_hint.to_string());
|
|
if chat::add_device_msg(self, Some("core-provider-info"), Some(&mut msg))
|
|
.await
|
|
.is_err()
|
|
{
|
|
warn!(self, "cannot add after_login_hint as core-provider-info");
|
|
}
|
|
}
|
|
}
|
|
|
|
match success {
|
|
Ok(_) => {
|
|
self.set_config(Config::NotifyAboutWrongPw, Some("1"))
|
|
.await?;
|
|
progress!(self, 1000);
|
|
Ok(())
|
|
}
|
|
Err(err) => {
|
|
progress!(self, 0);
|
|
Err(err)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
async fn configure(ctx: &Context, param: &mut LoginParam) -> Result<()> {
|
|
let mut param_autoconfig: Option<LoginParam> = None;
|
|
let mut keep_flags = 0;
|
|
|
|
// Read login parameters from the database
|
|
progress!(ctx, 1);
|
|
ensure!(!param.addr.is_empty(), "Please enter an email address.");
|
|
|
|
// Step 1: Load the parameters and check email-address and password
|
|
|
|
if 0 != param.server_flags & DC_LP_AUTH_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);
|
|
if let Some(oauth2_addr) = dc_get_oauth2_addr(ctx, ¶m.addr, ¶m.mail_pw)
|
|
.await
|
|
.and_then(|e| e.parse().ok())
|
|
{
|
|
info!(ctx, "Authorized address is {}", oauth2_addr);
|
|
param.addr = oauth2_addr;
|
|
ctx.sql
|
|
.set_raw_config(ctx, "addr", Some(param.addr.as_str()))
|
|
.await?;
|
|
}
|
|
progress!(ctx, 20);
|
|
}
|
|
// no oauth? - just continue it's no error
|
|
|
|
let parsed: EmailAddress = param.addr.parse().context("Bad email-address")?;
|
|
let param_domain = parsed.domain;
|
|
let param_addr_urlencoded = utf8_percent_encode(¶m.addr, NON_ALPHANUMERIC).to_string();
|
|
|
|
// 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
|
|
if param.mail_server.is_empty()
|
|
&& param.mail_port == 0
|
|
&& param.send_server.is_empty()
|
|
&& param.send_port == 0
|
|
&& param.send_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() {
|
|
param_autoconfig =
|
|
get_autoconfig(ctx, param, ¶m_domain, ¶m_addr_urlencoded).await;
|
|
}
|
|
}
|
|
|
|
// C. Do we have any autoconfig result?
|
|
progress!(ctx, 500);
|
|
if let Some(ref cfg) = param_autoconfig {
|
|
info!(ctx, "Got autoconfig: {}", &cfg);
|
|
if !cfg.mail_user.is_empty() {
|
|
param.mail_user = cfg.mail_user.clone();
|
|
}
|
|
// all other values are always NULL when entering autoconfig
|
|
param.mail_server = cfg.mail_server.clone();
|
|
param.mail_port = cfg.mail_port;
|
|
param.send_server = cfg.send_server.clone();
|
|
param.send_port = cfg.send_port;
|
|
param.send_user = cfg.send_user.clone();
|
|
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.mail_server.is_empty() {
|
|
param.mail_server = format!("imap.{}", param_domain,)
|
|
}
|
|
if param.mail_port == 0 {
|
|
param.mail_port = if 0 != param.server_flags & (0x100 | 0x400) {
|
|
143
|
|
} else {
|
|
993
|
|
}
|
|
}
|
|
if param.mail_user.is_empty() {
|
|
param.mail_user = param.addr.clone();
|
|
}
|
|
if param.send_server.is_empty() && !param.mail_server.is_empty() {
|
|
param.send_server = param.mail_server.clone();
|
|
if param.send_server.starts_with("imap.") {
|
|
param.send_server = param.send_server.replacen("imap", "smtp", 1);
|
|
}
|
|
}
|
|
if param.send_port == 0 {
|
|
param.send_port = if 0 != param.server_flags & DC_LP_SMTP_SOCKET_STARTTLS as i32 {
|
|
587
|
|
} else if 0 != param.server_flags & DC_LP_SMTP_SOCKET_PLAIN as i32 {
|
|
25
|
|
} else {
|
|
465
|
|
}
|
|
}
|
|
if param.send_user.is_empty() && !param.mail_user.is_empty() {
|
|
param.send_user = param.mail_user.clone();
|
|
}
|
|
if param.send_pw.is_empty() && !param.mail_pw.is_empty() {
|
|
param.send_pw = param.mail_pw.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
|
|
}
|
|
if !dc_exactly_one_bit_set(param.server_flags & DC_LP_IMAP_SOCKET_FLAGS as i32) {
|
|
param.server_flags &= !(DC_LP_IMAP_SOCKET_FLAGS as i32);
|
|
param.server_flags |= if param.send_port == 143 {
|
|
DC_LP_IMAP_SOCKET_STARTTLS as i32
|
|
} else {
|
|
DC_LP_IMAP_SOCKET_SSL as i32
|
|
}
|
|
}
|
|
if !dc_exactly_one_bit_set(param.server_flags & (DC_LP_SMTP_SOCKET_FLAGS as i32)) {
|
|
param.server_flags &= !(DC_LP_SMTP_SOCKET_FLAGS as i32);
|
|
param.server_flags |= if param.send_port == 587 {
|
|
DC_LP_SMTP_SOCKET_STARTTLS as i32
|
|
} else if param.send_port == 25 {
|
|
DC_LP_SMTP_SOCKET_PLAIN as i32
|
|
} else {
|
|
DC_LP_SMTP_SOCKET_SSL as i32
|
|
}
|
|
}
|
|
|
|
// do we have a complete configuration?
|
|
ensure!(
|
|
!param.mail_server.is_empty()
|
|
&& param.mail_port != 0
|
|
&& !param.mail_user.is_empty()
|
|
&& !param.mail_pw.is_empty()
|
|
&& !param.send_server.is_empty()
|
|
&& param.send_port != 0
|
|
&& !param.send_user.is_empty()
|
|
&& !param.send_pw.is_empty()
|
|
&& param.server_flags != 0,
|
|
"Account settings incomplete."
|
|
);
|
|
|
|
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);
|
|
|
|
try_imap_connections(ctx, param, param_autoconfig.is_some(), &mut imap).await?;
|
|
progress!(ctx, 800);
|
|
|
|
try_smtp_connections(ctx, param, param_autoconfig.is_some()).await?;
|
|
progress!(ctx, 900);
|
|
|
|
let create_mvbox = ctx.get_config_bool(Config::MvboxWatch).await
|
|
|| ctx.get_config_bool(Config::MvboxMove).await;
|
|
|
|
imap.configure_folders(ctx, create_mvbox).await?;
|
|
|
|
imap.select_with_uidvalidity(ctx, "INBOX")
|
|
.await
|
|
.context("could not read INBOX status")?;
|
|
|
|
drop(imap);
|
|
|
|
progress!(ctx, 910);
|
|
// configuration success - write back the configured parameters with the
|
|
// "configured_" prefix; also write the "configured"-flag */
|
|
// the trailing underscore is correct
|
|
param.save_to_database(ctx, "configured_").await?;
|
|
ctx.sql.set_raw_config_bool(ctx, "configured", true).await?;
|
|
|
|
progress!(ctx, 920);
|
|
|
|
e2ee::ensure_secret_key_exists(ctx).await?;
|
|
info!(ctx, "key generation completed");
|
|
|
|
progress!(ctx, 940);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[derive(Debug, PartialEq, Eq)]
|
|
enum AutoconfigProvider {
|
|
Mozilla,
|
|
Outlook,
|
|
}
|
|
|
|
#[derive(Debug, PartialEq, Eq)]
|
|
struct AutoconfigSource {
|
|
provider: AutoconfigProvider,
|
|
url: String,
|
|
}
|
|
|
|
impl AutoconfigSource {
|
|
fn all(domain: &str, addr: &str) -> [Self; 5] {
|
|
[
|
|
AutoconfigSource {
|
|
provider: AutoconfigProvider::Mozilla,
|
|
url: format!(
|
|
"https://autoconfig.{}/mail/config-v1.1.xml?emailaddress={}",
|
|
domain, addr,
|
|
),
|
|
},
|
|
// the doc does not mention `emailaddress=`, however, Thunderbird adds it, see https://releases.mozilla.org/pub/thunderbird/ , which makes some sense
|
|
AutoconfigSource {
|
|
provider: AutoconfigProvider::Mozilla,
|
|
url: format!(
|
|
"https://{}/.well-known/autoconfig/mail/config-v1.1.xml?emailaddress={}",
|
|
domain, addr
|
|
),
|
|
},
|
|
AutoconfigSource {
|
|
provider: AutoconfigProvider::Outlook,
|
|
url: format!("https://{}/autodiscover/autodiscover.xml", domain),
|
|
},
|
|
// Outlook uses always SSL but different domains (this comment describes the next two steps)
|
|
AutoconfigSource {
|
|
provider: AutoconfigProvider::Outlook,
|
|
url: format!(
|
|
"https://{}{}/autodiscover/autodiscover.xml",
|
|
"autodiscover.", domain
|
|
),
|
|
},
|
|
// always SSL for Thunderbird's database
|
|
AutoconfigSource {
|
|
provider: AutoconfigProvider::Mozilla,
|
|
url: format!("https://autoconfig.thunderbird.net/v1.1/{}", domain),
|
|
},
|
|
]
|
|
}
|
|
|
|
async fn fetch(&self, ctx: &Context, param: &LoginParam) -> Result<LoginParam> {
|
|
let params = match self.provider {
|
|
AutoconfigProvider::Mozilla => moz_autoconfigure(ctx, &self.url, ¶m).await?,
|
|
AutoconfigProvider::Outlook => outlk_autodiscover(ctx, &self.url, ¶m).await?,
|
|
};
|
|
|
|
Ok(params)
|
|
}
|
|
}
|
|
|
|
/// Retrieve available autoconfigurations.
|
|
///
|
|
/// A Search configurations from the domain used in the email-address, prefer encrypted
|
|
/// B. If we have no configuration yet, search configuration in Thunderbird's centeral database
|
|
async fn get_autoconfig(
|
|
ctx: &Context,
|
|
param: &LoginParam,
|
|
param_domain: &str,
|
|
param_addr_urlencoded: &str,
|
|
) -> Option<LoginParam> {
|
|
let sources = AutoconfigSource::all(param_domain, param_addr_urlencoded);
|
|
|
|
let mut progress = 300;
|
|
for source in &sources {
|
|
let res = source.fetch(ctx, param).await;
|
|
progress!(ctx, progress);
|
|
progress += 10;
|
|
if let Ok(res) = res {
|
|
return Some(res);
|
|
}
|
|
}
|
|
|
|
None
|
|
}
|
|
|
|
fn get_offline_autoconfig(context: &Context, param: &LoginParam) -> Option<LoginParam> {
|
|
info!(
|
|
context,
|
|
"checking internal provider-info for offline autoconfig"
|
|
);
|
|
|
|
if let Some(provider) = provider::get_provider_info(¶m.addr) {
|
|
match provider.status {
|
|
provider::Status::OK | provider::Status::PREPARATION => {
|
|
let imap = provider.get_imap_server();
|
|
let smtp = provider.get_smtp_server();
|
|
// clippy complains about these is_some()/unwrap() settings,
|
|
// however, rewriting the code to "if let" would make things less obvious,
|
|
// esp. if we allow more combinations of servers (pop, jmap).
|
|
// therefore, #[allow(clippy::unnecessary_unwrap)] is added above.
|
|
if let Some(imap) = imap {
|
|
if let Some(smtp) = smtp {
|
|
let mut p = LoginParam::new();
|
|
p.addr = param.addr.clone();
|
|
|
|
p.mail_server = imap.hostname.to_string();
|
|
p.mail_user = imap.apply_username_pattern(param.addr.clone());
|
|
p.mail_port = imap.port as i32;
|
|
p.imap_certificate_checks = CertificateChecks::AcceptInvalidCertificates;
|
|
p.server_flags |= match imap.socket {
|
|
provider::Socket::STARTTLS => DC_LP_IMAP_SOCKET_STARTTLS,
|
|
provider::Socket::SSL => DC_LP_IMAP_SOCKET_SSL,
|
|
};
|
|
|
|
p.send_server = smtp.hostname.to_string();
|
|
p.send_user = smtp.apply_username_pattern(param.addr.clone());
|
|
p.send_port = smtp.port as i32;
|
|
p.smtp_certificate_checks = CertificateChecks::AcceptInvalidCertificates;
|
|
p.server_flags |= match smtp.socket {
|
|
provider::Socket::STARTTLS => DC_LP_SMTP_SOCKET_STARTTLS as i32,
|
|
provider::Socket::SSL => DC_LP_SMTP_SOCKET_SSL as i32,
|
|
};
|
|
|
|
info!(context, "offline autoconfig found: {}", p);
|
|
return Some(p);
|
|
}
|
|
}
|
|
info!(context, "offline autoconfig found, but no servers defined");
|
|
return None;
|
|
}
|
|
provider::Status::BROKEN => {
|
|
info!(context, "offline autoconfig found, provider is broken");
|
|
return None;
|
|
}
|
|
}
|
|
}
|
|
info!(context, "no offline autoconfig found");
|
|
None
|
|
}
|
|
|
|
async fn try_imap_connections(
|
|
context: &Context,
|
|
param: &mut LoginParam,
|
|
was_autoconfig: bool,
|
|
imap: &mut Imap,
|
|
) -> Result<()> {
|
|
// manually_set_param is used to check whether a particular setting was set manually by the user.
|
|
// If yes, we do not want to change it to avoid confusing error messages
|
|
// (you set port 443, but the app tells you it couldn't connect on port 993).
|
|
let manually_set_param = LoginParam::from_database(context, "").await;
|
|
|
|
// progress 650 and 660
|
|
if try_imap_connection(context, param, &manually_set_param, was_autoconfig, 0, imap)
|
|
.await
|
|
.is_ok()
|
|
{
|
|
return Ok(());
|
|
}
|
|
if was_autoconfig {
|
|
bail!("autoconfig did not succeed");
|
|
}
|
|
|
|
progress!(context, 670);
|
|
// try_imap_connection() changed the flags and port. Change them back:
|
|
if manually_set_param.server_flags & DC_LP_IMAP_SOCKET_FLAGS == 0 {
|
|
param.server_flags &= !(DC_LP_IMAP_SOCKET_FLAGS);
|
|
param.server_flags |= DC_LP_IMAP_SOCKET_SSL;
|
|
}
|
|
if manually_set_param.mail_port == 0 {
|
|
param.mail_port = 993;
|
|
}
|
|
if let Some(at) = param.mail_user.find('@') {
|
|
param.mail_user = param.mail_user.split_at(at).0.to_string();
|
|
}
|
|
if let Some(at) = param.send_user.find('@') {
|
|
param.send_user = param.send_user.split_at(at).0.to_string();
|
|
}
|
|
// progress 680 and 690
|
|
try_imap_connection(context, param, &manually_set_param, was_autoconfig, 1, imap).await
|
|
}
|
|
|
|
async fn try_imap_connection(
|
|
context: &Context,
|
|
param: &mut LoginParam,
|
|
manually_set_param: &LoginParam,
|
|
was_autoconfig: bool,
|
|
variation: usize,
|
|
imap: &mut Imap,
|
|
) -> Result<()> {
|
|
if try_imap_one_param(context, param, imap).await.is_ok() {
|
|
return Ok(());
|
|
}
|
|
if was_autoconfig {
|
|
bail!("autoconfig did not succeed");
|
|
}
|
|
|
|
progress!(context, 650 + variation * 30);
|
|
|
|
if manually_set_param.server_flags & DC_LP_IMAP_SOCKET_FLAGS == 0 {
|
|
param.server_flags &= !(DC_LP_IMAP_SOCKET_FLAGS);
|
|
param.server_flags |= DC_LP_IMAP_SOCKET_STARTTLS;
|
|
|
|
if try_imap_one_param(context, ¶m, imap).await.is_ok() {
|
|
return Ok(());
|
|
}
|
|
}
|
|
|
|
progress!(context, 660 + variation * 30);
|
|
|
|
if manually_set_param.mail_port == 0 {
|
|
param.mail_port = 143;
|
|
try_imap_one_param(context, param, imap).await
|
|
} else {
|
|
Err(format_err!("no more possible configs"))
|
|
}
|
|
}
|
|
|
|
async fn try_imap_one_param(context: &Context, param: &LoginParam, imap: &mut Imap) -> Result<()> {
|
|
let inf = format!(
|
|
"imap: {}@{}:{} flags=0x{:x} certificate_checks={}",
|
|
param.mail_user,
|
|
param.mail_server,
|
|
param.mail_port,
|
|
param.server_flags,
|
|
param.imap_certificate_checks
|
|
);
|
|
info!(context, "Trying: {}", inf);
|
|
|
|
if imap.connect(context, ¶m).await {
|
|
info!(context, "success: {}", inf);
|
|
return Ok(());
|
|
}
|
|
|
|
bail!("Could not connect: {}", inf);
|
|
}
|
|
|
|
async fn try_smtp_connections(
|
|
context: &Context,
|
|
param: &mut LoginParam,
|
|
was_autoconfig: bool,
|
|
) -> Result<()> {
|
|
// manually_set_param is used to check whether a particular setting was set manually by the user.
|
|
// If yes, we do not want to change it to avoid confusing error messages
|
|
// (you set port 443, but the app tells you it couldn't connect on port 993).
|
|
let manually_set_param = LoginParam::from_database(context, "").await;
|
|
|
|
let mut smtp = Smtp::new();
|
|
// try to connect to SMTP - if we did not got an autoconfig, the first try was SSL-465 and we do
|
|
// a second try with STARTTLS-587
|
|
if try_smtp_one_param(context, param, &mut smtp).await.is_ok() {
|
|
return Ok(());
|
|
}
|
|
if was_autoconfig {
|
|
bail!("No SMTP connection");
|
|
}
|
|
progress!(context, 850);
|
|
|
|
if manually_set_param.server_flags & (DC_LP_SMTP_SOCKET_FLAGS as i32) == 0 {
|
|
param.server_flags &= !(DC_LP_SMTP_SOCKET_FLAGS as i32);
|
|
param.server_flags |= DC_LP_SMTP_SOCKET_STARTTLS as i32;
|
|
}
|
|
if manually_set_param.send_port == 0 {
|
|
param.send_port = 587;
|
|
}
|
|
|
|
if try_smtp_one_param(context, param, &mut smtp).await.is_ok() {
|
|
return Ok(());
|
|
}
|
|
progress!(context, 860);
|
|
|
|
if manually_set_param.server_flags & (DC_LP_SMTP_SOCKET_FLAGS as i32) == 0 {
|
|
param.server_flags &= !(DC_LP_SMTP_SOCKET_FLAGS as i32);
|
|
param.server_flags |= DC_LP_SMTP_SOCKET_STARTTLS as i32;
|
|
}
|
|
if manually_set_param.send_port == 0 {
|
|
param.send_port = 25;
|
|
}
|
|
try_smtp_one_param(context, param, &mut smtp).await
|
|
}
|
|
|
|
async fn try_smtp_one_param(context: &Context, param: &LoginParam, smtp: &mut Smtp) -> Result<()> {
|
|
let inf = format!(
|
|
"smtp: {}@{}:{} flags: 0x{:x}",
|
|
param.send_user, param.send_server, param.send_port, param.server_flags
|
|
);
|
|
info!(context, "Trying: {}", inf);
|
|
|
|
if let Err(err) = smtp.connect(context, ¶m).await {
|
|
bail!("could not connect: {}", err);
|
|
}
|
|
|
|
info!(context, "success: {}", inf);
|
|
smtp.disconnect().await;
|
|
Ok(())
|
|
}
|
|
|
|
#[derive(Debug, thiserror::Error)]
|
|
pub enum Error {
|
|
#[error("Invalid email address: {0:?}")]
|
|
InvalidEmailAddress(String),
|
|
|
|
#[error("XML error at position {position}")]
|
|
InvalidXml {
|
|
position: usize,
|
|
#[source]
|
|
error: quick_xml::Error,
|
|
},
|
|
|
|
#[error("Bad or incomplete autoconfig")]
|
|
IncompleteAutoconfig(LoginParam),
|
|
|
|
#[error("Failed to get URL")]
|
|
ReadUrlError(#[from] self::read_url::Error),
|
|
|
|
#[error("Number of redirection is exceeded")]
|
|
RedirectionError,
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
|
|
use super::*;
|
|
use crate::config::*;
|
|
use crate::test_utils::*;
|
|
|
|
#[async_std::test]
|
|
async fn test_no_panic_on_bad_credentials() {
|
|
let t = TestContext::new().await;
|
|
t.ctx
|
|
.set_config(Config::Addr, Some("probably@unexistant.addr"))
|
|
.await
|
|
.unwrap();
|
|
t.ctx
|
|
.set_config(Config::MailPw, Some("123456"))
|
|
.await
|
|
.unwrap();
|
|
assert!(t.ctx.configure().await.is_err());
|
|
}
|
|
|
|
#[async_std::test]
|
|
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 mut params = LoginParam::new();
|
|
params.addr = "someone123@nauta.cu".to_string();
|
|
let found_params = get_offline_autoconfig(&context, ¶ms).unwrap();
|
|
assert_eq!(found_params.mail_server, "imap.nauta.cu".to_string());
|
|
assert_eq!(found_params.send_server, "smtp.nauta.cu".to_string());
|
|
}
|
|
}
|