Separate IMAP and SMTP configuration

Co-Authored-By: link2xt <ilabdsf@gmail.com>
Co-Authored-By: bjoern <r10s@b44t.com>
This commit is contained in:
Hocuri
2020-08-14 17:58:05 +02:00
committed by link2xt
parent 4bd2a9084c
commit 0fc57bdb35
13 changed files with 548 additions and 432 deletions

View File

@@ -274,10 +274,12 @@ char* dc_get_blobdir (const dc_context_t* context);
* - `mail_user` = IMAP-username, guessed if left out
* - `mail_pw` = IMAP-password (always needed)
* - `mail_port` = IMAP-port, guessed if left out
* - `mail_security`= IMAP-socket, one of @ref DC_SOCKET, defaults to #DC_SOCKET_AUTO
* - `send_server` = SMTP-server, guessed if left out
* - `send_user` = SMTP-user, guessed if left out
* - `send_pw` = SMTP-password, guessed if left out
* - `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
* - `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)
@@ -4167,6 +4169,44 @@ int64_t dc_lot_get_timestamp (const dc_lot_t* lot);
*/
#define DC_MSG_VIDEOCHAT_INVITATION 70
/**
* @}
*/
/**
* @defgroup DC_SOCKET DC_SOCKET
*
* These constants configure socket security.
* To set socket security, use dc_set_config() with the keys "mail_security" and/or "send_security".
* If no socket-configuration is explicitly specified, #DC_SOCKET_AUTO is used.
*
* @addtogroup DC_SOCKET
* @{
*/
/**
* Choose socket security automatically.
*/
#define DC_SOCKET_AUTO 0
/**
* Connect via SSL/TLS.
*/
#define DC_SOCKET_SSL 1
/**
* Connect via STARTTLS.
*/
#define DC_SOCKET_STARTTLS 2
/**
* Connect unencrypted, this should not be used.
*/
#define DC_SOCKET_PLAIN 3
/**
* @}
@@ -4201,54 +4241,11 @@ int64_t dc_lot_get_timestamp (const dc_lot_t* lot);
#define DC_LP_AUTH_NORMAL 0x4
/**
* Connect to IMAP via STARTTLS.
* If this flag is set, automatic configuration is skipped.
*/
#define DC_LP_IMAP_SOCKET_STARTTLS 0x100
/**
* Connect to IMAP via SSL.
* If this flag is set, automatic configuration is skipped.
*/
#define DC_LP_IMAP_SOCKET_SSL 0x200
/**
* Connect to IMAP unencrypted, this should not be used.
* If this flag is set, automatic configuration is skipped.
*/
#define DC_LP_IMAP_SOCKET_PLAIN 0x400
/**
* Connect to SMTP via STARTTLS.
* If this flag is set, automatic configuration is skipped.
*/
#define DC_LP_SMTP_SOCKET_STARTTLS 0x10000
/**
* Connect to SMTP via SSL.
* If this flag is set, automatic configuration is skipped.
*/
#define DC_LP_SMTP_SOCKET_SSL 0x20000
/**
* Connect to SMTP unencrypted, this should not be used.
* If this flag is set, automatic configuration is skipped.
*/
#define DC_LP_SMTP_SOCKET_PLAIN 0x40000 ///<
/**
* @}
*/
#define DC_LP_AUTH_FLAGS (DC_LP_AUTH_OAUTH2|DC_LP_AUTH_NORMAL) // if none of these flags are set, the default is chosen
#define DC_LP_IMAP_SOCKET_FLAGS (DC_LP_IMAP_SOCKET_STARTTLS|DC_LP_IMAP_SOCKET_SSL|DC_LP_IMAP_SOCKET_PLAIN) // if none of these flags are set, the default is chosen
#define DC_LP_SMTP_SOCKET_FLAGS (DC_LP_SMTP_SOCKET_STARTTLS|DC_LP_SMTP_SOCKET_SSL|DC_LP_SMTP_SOCKET_PLAIN) // if none of these flags are set, the default is chosen
/**
* @defgroup DC_CERTCK DC_CERTCK

View File

@@ -25,11 +25,13 @@ pub enum Config {
MailUser,
MailPw,
MailPort,
MailSecurity,
ImapCertificateChecks,
SendServer,
SendUser,
SendPw,
SendPort,
SendSecurity,
SmtpCertificateChecks,
ServerFlags,

View File

@@ -3,9 +3,9 @@
//! Documentation: https://developer.mozilla.org/en-US/docs/Mozilla/Thunderbird/Autoconfiguration */
use quick_xml::events::{BytesEnd, BytesStart, BytesText};
use crate::constants::*;
use crate::context::Context;
use crate::login_param::LoginParam;
use crate::provider::Socket;
use super::read_url::read_url;
use super::Error;
@@ -83,10 +83,10 @@ fn parse_xml(in_emailaddr: &str, xml_raw: &str) -> Result<LoginParam, Error> {
buf.clear();
}
if moz_ac.out.mail_server.is_empty()
|| moz_ac.out.mail_port == 0
|| moz_ac.out.send_server.is_empty()
|| moz_ac.out.send_port == 0
if moz_ac.out.imap.server.is_empty()
|| moz_ac.out.imap.port == 0
|| moz_ac.out.smtp.server.is_empty()
|| moz_ac.out.smtp.port == 0
{
Err(Error::IncompleteAutoconfig(moz_ac.out))
} else {
@@ -130,37 +130,37 @@ fn moz_autoconfigure_text_cb<B: std::io::BufRead>(
match moz_ac.tag_server {
MozServer::Imap => match moz_ac.tag_config {
MozConfigTag::Hostname => moz_ac.out.mail_server = val,
MozConfigTag::Port => moz_ac.out.mail_port = val.parse().unwrap_or_default(),
MozConfigTag::Username => moz_ac.out.mail_user = val,
MozConfigTag::Hostname => moz_ac.out.imap.server = val,
MozConfigTag::Port => moz_ac.out.imap.port = val.parse().unwrap_or_default(),
MozConfigTag::Username => moz_ac.out.imap.user = val,
MozConfigTag::Sockettype => {
let val_lower = val.to_lowercase();
if val_lower == "ssl" {
moz_ac.out.server_flags |= DC_LP_IMAP_SOCKET_SSL as i32
moz_ac.out.imap.security = Socket::SSL;
}
if val_lower == "starttls" {
moz_ac.out.server_flags |= DC_LP_IMAP_SOCKET_STARTTLS as i32
moz_ac.out.imap.security = Socket::STARTTLS;
}
if val_lower == "plain" {
moz_ac.out.server_flags |= DC_LP_IMAP_SOCKET_PLAIN as i32
moz_ac.out.imap.security = Socket::Plain;
}
}
_ => {}
},
MozServer::Smtp => match moz_ac.tag_config {
MozConfigTag::Hostname => moz_ac.out.send_server = val,
MozConfigTag::Port => moz_ac.out.send_port = val.parse().unwrap_or_default(),
MozConfigTag::Username => moz_ac.out.send_user = val,
MozConfigTag::Hostname => moz_ac.out.smtp.server = val,
MozConfigTag::Port => moz_ac.out.smtp.port = val.parse().unwrap_or_default(),
MozConfigTag::Username => moz_ac.out.smtp.user = val,
MozConfigTag::Sockettype => {
let val_lower = val.to_lowercase();
if val_lower == "ssl" {
moz_ac.out.server_flags |= DC_LP_SMTP_SOCKET_SSL as i32
moz_ac.out.smtp.security = Socket::SSL;
}
if val_lower == "starttls" {
moz_ac.out.server_flags |= DC_LP_SMTP_SOCKET_STARTTLS as i32
moz_ac.out.smtp.security = Socket::STARTTLS;
}
if val_lower == "plain" {
moz_ac.out.server_flags |= DC_LP_SMTP_SOCKET_PLAIN as i32
moz_ac.out.smtp.security = Socket::Plain;
}
}
_ => {}
@@ -314,9 +314,9 @@ mod tests {
</webMail>
</clientConfig>";
let res = parse_xml("example@outlook.com", xml_raw).expect("XML parsing failed");
assert_eq!(res.mail_server, "outlook.office365.com");
assert_eq!(res.mail_port, 993);
assert_eq!(res.send_server, "smtp.office365.com");
assert_eq!(res.send_port, 587);
assert_eq!(res.imap.server, "outlook.office365.com");
assert_eq!(res.imap.port, 993);
assert_eq!(res.smtp.server, "smtp.office365.com");
assert_eq!(res.smtp.port, 587);
}
}

View File

@@ -2,9 +2,9 @@
use quick_xml::events::BytesEnd;
use crate::constants::*;
use crate::context::Context;
use crate::login_param::LoginParam;
use crate::provider::Socket;
use super::read_url::read_url;
use super::Error;
@@ -15,7 +15,7 @@ struct OutlookAutodiscover {
pub out_smtp_set: bool,
pub config_type: Option<String>,
pub config_server: String,
pub config_port: i32,
pub config_port: u16,
pub config_ssl: String,
pub config_redirecturl: Option<String>,
}
@@ -98,10 +98,10 @@ fn parse_xml(xml_raw: &str) -> Result<ParsingResult, Error> {
let res = if outlk_ad.config_redirecturl.is_none()
|| outlk_ad.config_redirecturl.as_ref().unwrap().is_empty()
{
if outlk_ad.out.mail_server.is_empty()
|| outlk_ad.out.mail_port == 0
|| outlk_ad.out.send_server.is_empty()
|| outlk_ad.out.send_port == 0
if outlk_ad.out.imap.server.is_empty()
|| outlk_ad.out.imap.port == 0
|| outlk_ad.out.smtp.server.is_empty()
|| outlk_ad.out.smtp.port == 0
{
return Err(Error::IncompleteAutoconfig(outlk_ad.out));
}
@@ -142,23 +142,23 @@ fn outlk_autodiscover_endtag_cb(event: &BytesEnd, outlk_ad: &mut OutlookAutodisc
let ssl_on = outlk_ad.config_ssl == "on";
let ssl_off = outlk_ad.config_ssl == "off";
if type_ == "imap" && !outlk_ad.out_imap_set {
outlk_ad.out.mail_server =
outlk_ad.out.imap.server =
std::mem::replace(&mut outlk_ad.config_server, String::new());
outlk_ad.out.mail_port = port;
outlk_ad.out.imap.port = port;
if ssl_on {
outlk_ad.out.server_flags |= DC_LP_IMAP_SOCKET_SSL as i32
outlk_ad.out.imap.security = Socket::SSL
} else if ssl_off {
outlk_ad.out.server_flags |= DC_LP_IMAP_SOCKET_PLAIN as i32
outlk_ad.out.imap.security = Socket::Plain
}
outlk_ad.out_imap_set = true
} else if type_ == "smtp" && !outlk_ad.out_smtp_set {
outlk_ad.out.send_server =
outlk_ad.out.smtp.server =
std::mem::replace(&mut outlk_ad.config_server, String::new());
outlk_ad.out.send_port = outlk_ad.config_port;
outlk_ad.out.smtp.port = outlk_ad.config_port;
if ssl_on {
outlk_ad.out.server_flags |= DC_LP_SMTP_SOCKET_SSL as i32
outlk_ad.out.smtp.security = Socket::SSL
} else if ssl_off {
outlk_ad.out.server_flags |= DC_LP_SMTP_SOCKET_PLAIN as i32
outlk_ad.out.smtp.security = Socket::Plain
}
outlk_ad.out_smtp_set = true
}
@@ -229,10 +229,10 @@ mod tests {
match res {
ParsingResult::LoginParam(lp) => {
assert_eq!(lp.mail_server, "example.com");
assert_eq!(lp.mail_port, 993);
assert_eq!(lp.send_server, "smtp.example.com");
assert_eq!(lp.send_port, 25);
assert_eq!(lp.imap.server, "example.com");
assert_eq!(lp.imap.port, 993);
assert_eq!(lp.smtp.server, "smtp.example.com");
assert_eq!(lp.smtp.port, 25);
}
ParsingResult::RedirectUrl(_) => {
panic!("RedirectUrl is not expected");

View File

@@ -13,14 +13,16 @@ use crate::constants::*;
use crate::context::Context;
use crate::dc_tools::*;
use crate::imap::Imap;
use crate::login_param::{CertificateChecks, LoginParam};
use crate::login_param::{CertificateChecks, LoginParam, LoginParamNew, ServerParams};
use crate::message::Message;
use crate::oauth2::*;
use crate::provider::Socket;
use crate::smtp::Smtp;
use crate::{chat, e2ee, provider};
use auto_mozilla::moz_autoconfigure;
use auto_outlook::outlk_autodiscover;
use provider::{Protocol, UsernamePattern};
macro_rules! progress {
($context:tt, $progress:expr) => {
@@ -115,7 +117,7 @@ impl Context {
}
async fn configure(ctx: &Context, param: &mut LoginParam) -> Result<()> {
let mut param_autoconfig: Option<LoginParam> = None;
let mut param_autoconfig: Option<LoginParamNew> = None;
let mut keep_flags = 0;
// Read login parameters from the database
@@ -128,7 +130,7 @@ async fn configure(ctx: &Context, param: &mut LoginParam) -> Result<()> {
// 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, &param.addr, &param.mail_pw)
if let Some(oauth2_addr) = dc_get_oauth2_addr(ctx, &param.addr, &param.imap.password)
.await
.and_then(|e| e.parse().ok())
{
@@ -152,11 +154,11 @@ async fn configure(ctx: &Context, param: &mut LoginParam) -> Result<()> {
// 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()
if param.imap.server.is_empty()
&& param.imap.port == 0
&& param.smtp.server.is_empty()
&& param.smtp.port == 0
&& 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
@@ -175,34 +177,32 @@ async fn configure(ctx: &Context, param: &mut LoginParam) -> Result<()> {
// 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();
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
}
// 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.send_user.is_empty() {
param.send_user = param.mail_user.clone();
if param.smtp.user.is_empty() {
param.smtp.user = param.imap.user.clone();
}
if param.send_pw.is_empty() {
param.send_pw = param.mail_pw.clone()
}
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);
}
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);
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);
@@ -211,7 +211,7 @@ async fn configure(ctx: &Context, param: &mut LoginParam) -> Result<()> {
// do we have a complete configuration?
ensure!(
!param.mail_pw.is_empty() && !param.send_pw.is_empty(),
!param.imap.password.is_empty() && !param.smtp.password.is_empty(),
"Account settings incomplete."
);
@@ -337,7 +337,7 @@ async fn get_autoconfig(
param: &LoginParam,
param_domain: &str,
param_addr_urlencoded: &str,
) -> Option<LoginParam> {
) -> Option<LoginParamNew> {
let sources = AutoconfigSource::all(param_domain, param_addr_urlencoded);
let mut progress = 300;
@@ -346,14 +346,14 @@ async fn get_autoconfig(
progress!(ctx, progress);
progress += 10;
if let Ok(res) = res {
return Some(res);
return Some(loginparam_old_to_new(res));
}
}
None
}
fn get_offline_autoconfig(context: &Context, param: &LoginParam) -> Option<LoginParam> {
fn get_offline_autoconfig(context: &Context, param: &LoginParam) -> Option<LoginParamNew> {
info!(
context,
"checking internal provider-info for offline autoconfig"
@@ -364,39 +364,11 @@ fn get_offline_autoconfig(context: &Context, param: &LoginParam) -> Option<Login
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::Automatic;
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::Automatic;
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;
return Some(LoginParamNew {
addr: param.addr.clone(),
imap,
smtp,
});
}
provider::Status::BROKEN => {
info!(context, "offline autoconfig found, provider is broken");
@@ -408,28 +380,83 @@ fn get_offline_autoconfig(context: &Context, param: &LoginParam) -> Option<Login
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.mail_server.is_empty() {
if param.imap.server.is_empty() {
let parsed: EmailAddress = param.addr.parse().context("Bad email-address")?;
let param_domain = parsed.domain;
param.mail_server = param_domain.clone();
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.mail_server = "imap.".to_string() + &param_domain;
param.imap.server = "imap.".to_string() + &param_domain;
if let Ok(param) = try_imap_ports(context, param.clone(), imap).await {
return Ok(param);
}
progress!(context, 700);
param.mail_server = "mail.".to_string() + &param_domain;
param.imap.server = "mail.".to_string() + &param_domain;
try_imap_ports(context, param, imap).await
} else {
progress!(context, 700);
@@ -444,44 +471,39 @@ async fn try_imap_ports(
imap: &mut Imap,
) -> Result<LoginParam> {
// Try to infer port from socket security.
if param.mail_port == 0 {
if 0 != param.server_flags & DC_LP_IMAP_SOCKET_SSL {
param.mail_port = 993
}
if 0 != param.server_flags & (DC_LP_IMAP_SOCKET_STARTTLS | DC_LP_IMAP_SOCKET_PLAIN) {
param.mail_port = 143
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.mail_port == 0 {
if param.imap.port == 0 {
// Neither port nor security is set.
//
// Try common secure combinations.
// Try TLS over port 993
param.server_flags &= !(DC_LP_IMAP_SOCKET_FLAGS as i32);
param.server_flags |= DC_LP_IMAP_SOCKET_SSL as i32;
param.mail_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.server_flags &= !(DC_LP_IMAP_SOCKET_FLAGS as i32);
param.server_flags |= DC_LP_IMAP_SOCKET_STARTTLS as i32;
param.mail_port = 143;
param.imap.security = Socket::STARTTLS;
param.imap.port = 143;
try_imap_usernames(context, param, imap).await
} else if 0 == param.server_flags & DC_LP_SMTP_SOCKET_FLAGS as i32 {
} else if param.imap.security == Socket::Automatic {
// Try TLS over user-provided port.
param.server_flags &= !(DC_LP_IMAP_SOCKET_FLAGS as i32);
param.server_flags |= DC_LP_IMAP_SOCKET_SSL as i32;
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.server_flags &= !(DC_LP_IMAP_SOCKET_FLAGS as i32);
param.server_flags |= DC_LP_IMAP_SOCKET_STARTTLS as i32;
param.imap.security = Socket::STARTTLS;
try_imap_usernames(context, param, imap).await
} else {
try_imap_usernames(context, param, imap).await
@@ -493,11 +515,11 @@ async fn try_imap_usernames(
mut param: LoginParam,
imap: &mut Imap,
) -> Result<LoginParam> {
if param.mail_user.is_empty() {
param.mail_user = param.addr.clone();
if param.imap.user.is_empty() {
param.imap.user = param.addr.clone();
if let Err(e) = try_imap_one_param(context, &param, imap).await {
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.imap.user.find('@') {
param.imap.user = param.imap.user.split_at(at).0.to_string();
try_imap_one_param(context, &param, imap).await?;
} else {
return Err(e);
@@ -512,16 +534,25 @@ async fn try_imap_usernames(
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,
"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,
param.imap_certificate_checks
);
info!(context, "Trying: {}", inf);
if imap.connect(context, &param).await {
if imap
.connect(
context,
&param.imap,
&param.addr,
param.server_flags & DC_LP_AUTH_OAUTH2 != 0,
)
.await
{
info!(context, "success: {}", inf);
return Ok(());
}
@@ -534,23 +565,23 @@ async fn try_smtp_hostnames(
mut param: LoginParam,
smtp: &mut Smtp,
) -> Result<LoginParam> {
if param.send_server.is_empty() {
if param.smtp.server.is_empty() {
let parsed: EmailAddress = param.addr.parse().context("Bad email-address")?;
let param_domain = parsed.domain;
param.send_server = param_domain.clone();
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.send_server = "smtp.".to_string() + &param_domain;
param.smtp.server = "smtp.".to_string() + &param_domain;
if let Ok(param) = try_smtp_ports(context, param.clone(), smtp).await {
return Ok(param);
}
progress!(context, 850);
param.send_server = "mail.".to_string() + &param_domain;
param.smtp.server = "mail.".to_string() + &param_domain;
try_smtp_ports(context, param, smtp).await
} else {
progress!(context, 850);
@@ -565,47 +596,39 @@ async fn try_smtp_ports(
smtp: &mut Smtp,
) -> Result<LoginParam> {
// Try to infer port from socket security.
if param.send_port == 0 {
if 0 != param.server_flags & DC_LP_SMTP_SOCKET_STARTTLS as i32 {
param.send_port = 587;
}
if 0 != param.server_flags & DC_LP_SMTP_SOCKET_PLAIN as i32 {
param.send_port = 25;
}
if 0 != param.server_flags & DC_LP_SMTP_SOCKET_SSL as i32 {
param.send_port = 465;
}
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.send_port == 0 {
if param.smtp.port == 0 {
// Neither port nor security is set.
//
// Try common secure combinations.
// Try TLS over port 465.
param.server_flags &= !(DC_LP_SMTP_SOCKET_FLAGS as i32);
param.server_flags |= DC_LP_SMTP_SOCKET_SSL as i32;
param.send_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.server_flags &= !(DC_LP_SMTP_SOCKET_FLAGS as i32);
param.server_flags |= DC_LP_SMTP_SOCKET_STARTTLS as i32;
param.send_port = 587;
param.smtp.security = Socket::STARTTLS;
param.smtp.port = 587;
try_smtp_usernames(context, param, smtp).await
} else if 0 == param.server_flags & DC_LP_SMTP_SOCKET_FLAGS as i32 {
} else if param.smtp.security == Socket::Automatic {
// Try TLS over user-provided port.
param.server_flags &= !(DC_LP_SMTP_SOCKET_FLAGS as i32);
param.server_flags |= DC_LP_SMTP_SOCKET_SSL as i32;
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.server_flags &= !(DC_LP_SMTP_SOCKET_FLAGS as i32);
param.server_flags |= DC_LP_SMTP_SOCKET_STARTTLS as i32;
param.smtp.security = Socket::STARTTLS;
try_smtp_usernames(context, param, smtp).await
} else {
try_smtp_usernames(context, param, smtp).await
@@ -617,11 +640,11 @@ async fn try_smtp_usernames(
mut param: LoginParam,
smtp: &mut Smtp,
) -> Result<LoginParam> {
if param.send_user.is_empty() {
param.send_user = param.addr.clone();
if param.smtp.user.is_empty() {
param.smtp.user = param.addr.clone();
if let Err(e) = try_smtp_one_param(context, &param, smtp).await {
if let Some(at) = param.send_user.find('@') {
param.send_user = param.send_user.split_at(at).0.to_string();
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, &param, smtp).await?;
} else {
return Err(e);
@@ -636,12 +659,25 @@ async fn try_smtp_usernames(
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
"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
);
info!(context, "Trying: {}", inf);
if let Err(err) = smtp.connect(context, &param).await {
if let Err(err) = smtp
.connect(
context,
&param.smtp,
&param.addr,
param.server_flags & DC_LP_AUTH_OAUTH2 != 0,
)
.await
{
bail!("could not connect: {}", err);
}
@@ -704,16 +740,13 @@ mod tests {
let mut params = LoginParam::new();
params.addr = "someone123@nauta.cu".to_string();
let found_params = get_offline_autoconfig(&context, &params).unwrap();
assert_eq!(found_params.mail_server, "imap.nauta.cu".to_string());
assert_eq!(found_params.send_server, "smtp.nauta.cu".to_string());
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());
assert_eq!(
found_params.imap_certificate_checks,
CertificateChecks::Automatic
);
assert_eq!(
found_params.smtp_certificate_checks,
CertificateChecks::Automatic
);
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);
}
}

View File

@@ -199,38 +199,8 @@ pub const DC_LP_AUTH_OAUTH2: i32 = 0x2;
/// If this flag is set, automatic configuration is skipped.
pub const DC_LP_AUTH_NORMAL: i32 = 0x4;
/// Connect to IMAP via STARTTLS.
/// If this flag is set, automatic configuration is skipped.
pub const DC_LP_IMAP_SOCKET_STARTTLS: i32 = 0x100;
/// Connect to IMAP via SSL.
/// If this flag is set, automatic configuration is skipped.
pub const DC_LP_IMAP_SOCKET_SSL: i32 = 0x200;
/// Connect to IMAP unencrypted, this should not be used.
/// If this flag is set, automatic configuration is skipped.
pub const DC_LP_IMAP_SOCKET_PLAIN: i32 = 0x400;
/// Connect to SMTP via STARTTLS.
/// If this flag is set, automatic configuration is skipped.
pub const DC_LP_SMTP_SOCKET_STARTTLS: usize = 0x10000;
/// Connect to SMTP via SSL.
/// If this flag is set, automatic configuration is skipped.
pub const DC_LP_SMTP_SOCKET_SSL: usize = 0x20000;
/// Connect to SMTP unencrypted, this should not be used.
/// If this flag is set, automatic configuration is skipped.
pub const DC_LP_SMTP_SOCKET_PLAIN: usize = 0x40000;
/// if none of these flags are set, the default is chosen
pub const DC_LP_AUTH_FLAGS: i32 = DC_LP_AUTH_OAUTH2 | DC_LP_AUTH_NORMAL;
/// if none of these flags are set, the default is chosen
pub const DC_LP_IMAP_SOCKET_FLAGS: i32 =
DC_LP_IMAP_SOCKET_STARTTLS | DC_LP_IMAP_SOCKET_SSL | DC_LP_IMAP_SOCKET_PLAIN;
/// if none of these flags are set, the default is chosen
pub const DC_LP_SMTP_SOCKET_FLAGS: usize =
DC_LP_SMTP_SOCKET_STARTTLS | DC_LP_SMTP_SOCKET_SSL | DC_LP_SMTP_SOCKET_PLAIN;
// QR code scanning (view from Bob, the joiner)
pub const DC_VC_AUTH_REQUIRED: i32 = 2;

View File

@@ -20,6 +20,7 @@ use crate::message::{MessageState, MsgId};
use crate::mimeparser::AvatarAction;
use crate::param::*;
use crate::peerstate::*;
use crate::provider::Socket;
use crate::stock::StockMessage;
/// An object representing a single contact in memory.
@@ -729,8 +730,8 @@ impl Contact {
);
cat_fingerprint(&mut ret, &loginparam.addr, &fingerprint_self, "");
}
} else if 0 == loginparam.server_flags & DC_LP_IMAP_SOCKET_PLAIN as i32
&& 0 == loginparam.server_flags & DC_LP_SMTP_SOCKET_PLAIN as i32
} else if loginparam.imap.security != Socket::Plain
&& loginparam.smtp.security != Socket::Plain
{
ret += &context.stock_str(StockMessage::EncrTransp).await;
} else {

View File

@@ -22,12 +22,12 @@ use crate::dc_receive_imf::{
use crate::events::EventType;
use crate::headerdef::{HeaderDef, HeaderDefMap};
use crate::job::{self, Action};
use crate::login_param::{CertificateChecks, LoginParam};
use crate::login_param::{CertificateChecks, LoginParam, ServerLoginParam};
use crate::message::{self, update_server_uid, MessageState};
use crate::mimeparser;
use crate::oauth2::dc_get_oauth2_access_token;
use crate::param::Params;
use crate::provider::get_provider_info;
use crate::provider::{get_provider_info, Socket};
use crate::{
chat, dc_tools::dc_extract_grpid_from_rfc724_mid, scheduler::InterruptInfo, stock::StockMessage,
};
@@ -155,8 +155,9 @@ struct ImapConfig {
pub imap_port: u16,
pub imap_user: String,
pub imap_pw: String,
pub security: Socket,
pub strict_tls: bool,
pub server_flags: usize,
pub oauth2: bool,
pub selected_folder: Option<String>,
pub selected_mailbox: Option<Mailbox>,
pub selected_folder_needs_expunge: bool,
@@ -175,8 +176,9 @@ impl Default for ImapConfig {
imap_port: 0,
imap_user: "".into(),
imap_pw: "".into(),
security: Default::default(),
strict_tls: false,
server_flags: 0,
oauth2: false,
selected_folder: None,
selected_mailbox: None,
selected_folder_needs_expunge: false,
@@ -223,32 +225,32 @@ impl Imap {
return Ok(());
}
let server_flags = self.config.server_flags as i32;
let oauth2 = self.config.oauth2;
let connection_res: ImapResult<Client> =
if (server_flags & (DC_LP_IMAP_SOCKET_STARTTLS | DC_LP_IMAP_SOCKET_PLAIN)) != 0 {
let config = &mut self.config;
let imap_server: &str = config.imap_server.as_ref();
let imap_port = config.imap_port;
let connection_res: ImapResult<Client> = if self.config.security == Socket::STARTTLS
|| self.config.security == Socket::Plain
{
let config = &mut self.config;
let imap_server: &str = config.imap_server.as_ref();
let imap_port = config.imap_port;
match Client::connect_insecure((imap_server, imap_port)).await {
Ok(client) => {
if (server_flags & DC_LP_IMAP_SOCKET_STARTTLS) != 0 {
client.secure(imap_server, config.strict_tls).await
} else {
Ok(client)
}
match Client::connect_insecure((imap_server, imap_port)).await {
Ok(client) => {
if config.security == Socket::STARTTLS {
client.secure(imap_server, config.strict_tls).await
} else {
Ok(client)
}
Err(err) => Err(err),
}
} else {
let config = &self.config;
let imap_server: &str = config.imap_server.as_ref();
let imap_port = config.imap_port;
Err(err) => Err(err),
}
} else {
let config = &self.config;
let imap_server: &str = config.imap_server.as_ref();
let imap_port = config.imap_port;
Client::connect_secure((imap_server, imap_port), imap_server, config.strict_tls)
.await
};
Client::connect_secure((imap_server, imap_port), imap_server, config.strict_tls).await
};
let login_res = match connection_res {
Ok(client) => {
@@ -256,7 +258,7 @@ impl Imap {
let imap_user: &str = config.imap_user.as_ref();
let imap_pw: &str = config.imap_pw.as_ref();
if (server_flags & DC_LP_AUTH_OAUTH2) != 0 {
if oauth2 {
let addr: &str = config.addr.as_ref();
if let Some(token) =
@@ -382,7 +384,15 @@ impl Imap {
let param = LoginParam::from_database(context, "configured_").await;
// the trailing underscore is correct
if self.connect(context, &param).await {
if self
.connect(
context,
&param.imap,
&param.addr,
param.server_flags & DC_LP_AUTH_OAUTH2 != 0,
)
.await
{
self.ensure_configured_folders(context, true).await
} else {
Err(Error::ConnectionFailed(format!("{}", param)))
@@ -390,18 +400,24 @@ impl Imap {
}
/// Tries connecting to imap account using the specific login parameters.
pub async fn connect(&mut self, context: &Context, lp: &LoginParam) -> bool {
if lp.mail_server.is_empty() || lp.mail_user.is_empty() || lp.mail_pw.is_empty() {
///
/// `addr` is used to renew token if OAuth2 authentication is used.
pub async fn connect(
&mut self,
context: &Context,
lp: &ServerLoginParam,
addr: &str,
oauth2: bool,
) -> bool {
if lp.server.is_empty() || lp.user.is_empty() || lp.password.is_empty() {
return false;
}
{
let addr = &lp.addr;
let imap_server = &lp.mail_server;
let imap_port = lp.mail_port as u16;
let imap_user = &lp.mail_user;
let imap_pw = &lp.mail_pw;
let server_flags = lp.server_flags as usize;
let imap_server = &lp.server;
let imap_port = lp.port;
let imap_user = &lp.user;
let imap_pw = &lp.password;
let mut config = &mut self.config;
config.addr = addr.to_string();
@@ -409,8 +425,8 @@ impl Imap {
config.imap_port = imap_port;
config.imap_user = imap_user.to_string();
config.imap_pw = imap_pw.to_string();
let provider = get_provider_info(&lp.addr);
config.strict_tls = match lp.imap_certificate_checks {
let provider = get_provider_info(&addr);
config.strict_tls = match lp.certificate_checks {
CertificateChecks::Automatic => {
provider.map_or(false, |provider| provider.strict_tls)
}
@@ -418,7 +434,7 @@ impl Imap {
CertificateChecks::AcceptInvalidCertificates
| CertificateChecks::AcceptInvalidCertificates2 => false,
};
config.server_flags = server_flags;
config.oauth2 = oauth2;
}
if let Err(err) = self.setup_handle_if_needed(context).await {
@@ -431,7 +447,7 @@ impl Imap {
Some(ref mut session) => match session.capabilities().await {
Ok(caps) => {
if !context.sql.is_open().await {
warn!(context, "IMAP-LOGIN as {} ok but ABORTING", lp.mail_user,);
warn!(context, "IMAP-LOGIN as {} ok but ABORTING", lp.user,);
true
} else {
let can_idle = caps.has_str("IDLE");
@@ -451,7 +467,7 @@ impl Imap {
context,
EventType::ImapConnected(format!(
"IMAP-LOGIN as {}, capabilities: {}",
lp.mail_user, caps_list,
lp.user, caps_list,
))
);
false

View File

@@ -26,7 +26,6 @@ use crate::error::{bail, ensure, format_err, Error, Result};
use crate::events::EventType;
use crate::imap::*;
use crate::location;
use crate::login_param::LoginParam;
use crate::message::MsgId;
use crate::message::{self, Message, MessageState};
use crate::mimefactory::MimeFactory;
@@ -343,12 +342,9 @@ impl Job {
pub(crate) async fn send_msg_to_smtp(&mut self, context: &Context, smtp: &mut Smtp) -> Status {
// SMTP server, if not yet done
if !smtp.is_connected().await {
let loginparam = LoginParam::from_database(context, "configured_").await;
if let Err(err) = smtp.connect(context, &loginparam).await {
warn!(context, "SMTP connection failure: {:?}", err);
return Status::RetryLater;
}
if let Err(err) = smtp.connect_configured(context).await {
warn!(context, "SMTP connection failure: {:?}", err);
return Status::RetryLater;
}
let filename = job_try!(job_try!(self
@@ -491,12 +487,9 @@ impl Job {
let recipients = vec![recipient];
// connect to SMTP server, if not yet done
if !smtp.is_connected().await {
let loginparam = LoginParam::from_database(context, "configured_").await;
if let Err(err) = smtp.connect(context, &loginparam).await {
warn!(context, "SMTP connection failure: {:?}", err);
return Status::RetryLater;
}
if let Err(err) = smtp.connect_configured(context).await {
warn!(context, "SMTP connection failure: {:?}", err);
return Status::RetryLater;
}
self.smtp_send(context, recipients, body, self.job_id, smtp, || {

View File

@@ -3,13 +3,19 @@
use std::borrow::Cow;
use std::fmt;
use crate::context::Context;
use crate::{
context::Context,
provider::{Protocol, Socket, UsernamePattern},
};
#[derive(Copy, Clone, Debug, Display, FromPrimitive, PartialEq, Eq)]
#[repr(i32)]
#[strum(serialize_all = "snake_case")]
pub enum CertificateChecks {
/// Same as AcceptInvalidCertificates unless overridden by
/// `strict_tls` setting in provider database.
Automatic = 0,
Strict = 1,
/// Same as AcceptInvalidCertificates
@@ -25,21 +31,58 @@ impl Default for CertificateChecks {
}
}
#[derive(Debug)]
pub struct ServerParams {
pub protocol: Protocol,
pub socket: Socket,
pub hostname: String,
pub port: u16,
pub username_pattern: UsernamePattern,
}
pub type ImapServers = Vec<ServerParams>;
pub type SmtpServers = Vec<ServerParams>;
#[derive(Debug)]
pub struct LoginParamNew {
pub addr: String,
pub imap: ImapServers,
pub smtp: SmtpServers,
}
impl ServerParams {
pub fn apply_username_pattern(&self, addr: String) -> String {
match self.username_pattern {
UsernamePattern::EMAIL => addr,
UsernamePattern::EMAILLOCALPART => {
if let Some(at) = addr.find('@') {
return addr.split_at(at).0.to_string();
}
addr
}
}
}
}
/// Login parameters for a single server, either IMAP or SMTP
#[derive(Default, Debug, Clone)]
pub struct ServerLoginParam {
pub server: String,
pub user: String,
pub password: String,
pub port: u16,
pub security: Socket,
/// TLS options: whether to allow invalid certificates and/or
/// invalid hostnames
pub certificate_checks: CertificateChecks,
}
#[derive(Default, Debug, Clone)]
pub struct LoginParam {
pub addr: String,
pub mail_server: String,
pub mail_user: String,
pub mail_pw: String,
pub mail_port: i32,
/// IMAP TLS options: whether to allow invalid certificates and/or invalid hostnames
pub imap_certificate_checks: CertificateChecks,
pub send_server: String,
pub send_user: String,
pub send_pw: String,
pub send_port: i32,
/// SMTP TLS options: whether to allow invalid certificates and/or invalid hostnames
pub smtp_certificate_checks: CertificateChecks,
pub imap: ServerLoginParam,
pub smtp: ServerLoginParam,
pub server_flags: i32,
}
@@ -77,6 +120,13 @@ impl LoginParam {
let key = format!("{}mail_pw", prefix);
let mail_pw = sql.get_raw_config(context, key).await.unwrap_or_default();
let key = format!("{}mail_security", prefix);
let mail_security = sql
.get_raw_config_int(context, key)
.await
.and_then(num_traits::FromPrimitive::from_i32)
.unwrap_or_default();
let key = format!("{}imap_certificate_checks", prefix);
let imap_certificate_checks =
if let Some(certificate_checks) = sql.get_raw_config_int(context, key).await {
@@ -100,6 +150,13 @@ impl LoginParam {
let key = format!("{}send_pw", prefix);
let send_pw = sql.get_raw_config(context, key).await.unwrap_or_default();
let key = format!("{}send_security", prefix);
let send_security = sql
.get_raw_config_int(context, key)
.await
.and_then(num_traits::FromPrimitive::from_i32)
.unwrap_or_default();
let key = format!("{}smtp_certificate_checks", prefix);
let smtp_certificate_checks =
if let Some(certificate_checks) = sql.get_raw_config_int(context, key).await {
@@ -116,16 +173,22 @@ impl LoginParam {
LoginParam {
addr,
mail_server,
mail_user,
mail_pw,
mail_port,
imap_certificate_checks,
send_server,
send_user,
send_pw,
send_port,
smtp_certificate_checks,
imap: ServerLoginParam {
server: mail_server,
user: mail_user,
password: mail_pw,
port: mail_port as u16,
security: mail_security,
certificate_checks: imap_certificate_checks,
},
smtp: ServerLoginParam {
server: send_server,
user: send_user,
password: send_pw,
port: send_port as u16,
security: send_security,
certificate_checks: smtp_certificate_checks,
},
server_flags,
}
}
@@ -143,41 +206,51 @@ impl LoginParam {
sql.set_raw_config(context, key, Some(&self.addr)).await?;
let key = format!("{}mail_server", prefix);
sql.set_raw_config(context, key, Some(&self.mail_server))
sql.set_raw_config(context, key, Some(&self.imap.server))
.await?;
let key = format!("{}mail_port", prefix);
sql.set_raw_config_int(context, key, self.mail_port).await?;
sql.set_raw_config_int(context, key, self.imap.port as i32)
.await?;
let key = format!("{}mail_user", prefix);
sql.set_raw_config(context, key, Some(&self.mail_user))
sql.set_raw_config(context, key, Some(&self.imap.user))
.await?;
let key = format!("{}mail_pw", prefix);
sql.set_raw_config(context, key, Some(&self.mail_pw))
sql.set_raw_config(context, key, Some(&self.imap.password))
.await?;
let key = format!("{}mail_security", prefix);
sql.set_raw_config_int(context, key, self.imap.security as i32)
.await?;
let key = format!("{}imap_certificate_checks", prefix);
sql.set_raw_config_int(context, key, self.imap_certificate_checks as i32)
sql.set_raw_config_int(context, key, self.imap.certificate_checks as i32)
.await?;
let key = format!("{}send_server", prefix);
sql.set_raw_config(context, key, Some(&self.send_server))
sql.set_raw_config(context, key, Some(&self.smtp.server))
.await?;
let key = format!("{}send_port", prefix);
sql.set_raw_config_int(context, key, self.send_port).await?;
sql.set_raw_config_int(context, key, self.smtp.port as i32)
.await?;
let key = format!("{}send_user", prefix);
sql.set_raw_config(context, key, Some(&self.send_user))
sql.set_raw_config(context, key, Some(&self.smtp.user))
.await?;
let key = format!("{}send_pw", prefix);
sql.set_raw_config(context, key, Some(&self.send_pw))
sql.set_raw_config(context, key, Some(&self.smtp.password))
.await?;
let key = format!("{}send_security", prefix);
sql.set_raw_config_int(context, key, self.smtp.security as i32)
.await?;
let key = format!("{}smtp_certificate_checks", prefix);
sql.set_raw_config_int(context, key, self.smtp_certificate_checks as i32)
sql.set_raw_config_int(context, key, self.smtp.certificate_checks as i32)
.await?;
let key = format!("{}server_flags", prefix);
@@ -199,16 +272,24 @@ impl fmt::Display for LoginParam {
f,
"{} imap:{}:{}:{}:{}:cert_{} smtp:{}:{}:{}:{}:cert_{} {}",
unset_empty(&self.addr),
unset_empty(&self.mail_user),
if !self.mail_pw.is_empty() { pw } else { unset },
unset_empty(&self.mail_server),
self.mail_port,
self.imap_certificate_checks,
unset_empty(&self.send_user),
if !self.send_pw.is_empty() { pw } else { unset },
unset_empty(&self.send_server),
self.send_port,
self.smtp_certificate_checks,
unset_empty(&self.imap.user),
if !self.imap.password.is_empty() {
pw
} else {
unset
},
unset_empty(&self.imap.server),
self.imap.port,
self.imap.certificate_checks,
unset_empty(&self.smtp.user),
if !self.smtp.password.is_empty() {
pw
} else {
unset
},
unset_empty(&self.smtp.server),
self.smtp.port,
self.smtp.certificate_checks,
flags_readable,
)
}
@@ -237,30 +318,6 @@ fn get_readable_flags(flags: i32) -> String {
res += "AUTH_NORMAL ";
flag_added = true;
}
if 1 << bit == 0x100 {
res += "IMAP_STARTTLS ";
flag_added = true;
}
if 1 << bit == 0x200 {
res += "IMAP_SSL ";
flag_added = true;
}
if 1 << bit == 0x400 {
res += "IMAP_PLAIN ";
flag_added = true;
}
if 1 << bit == 0x10000 {
res += "SMTP_STARTTLS ";
flag_added = true;
}
if 1 << bit == 0x20000 {
res += "SMTP_SSL ";
flag_added = true;
}
if 1 << bit == 0x40000 {
res += "SMTP_PLAIN ";
flag_added = true;
}
if flag_added {
res += &format!("{:#0x}", 1 << bit);
}

View File

@@ -4,9 +4,12 @@ mod data;
use crate::config::Config;
use crate::dc_tools::EmailAddress;
use crate::provider::data::PROVIDER_DATA;
use crate::{
login_param::{ImapServers, ServerParams, SmtpServers},
provider::data::PROVIDER_DATA,
};
#[derive(Debug, Copy, Clone, PartialEq, ToPrimitive)]
#[derive(Debug, Display, Copy, Clone, PartialEq, FromPrimitive, ToPrimitive)]
#[repr(u8)]
pub enum Status {
OK = 1,
@@ -14,21 +17,29 @@ pub enum Status {
BROKEN = 3,
}
#[derive(Debug, PartialEq)]
#[derive(Debug, Display, PartialEq, Copy, Clone, FromPrimitive, ToPrimitive)]
#[repr(u8)]
pub enum Protocol {
SMTP = 1,
IMAP = 2,
}
#[derive(Debug, PartialEq)]
#[derive(Debug, Display, PartialEq, Copy, Clone, FromPrimitive, ToPrimitive)]
#[repr(u8)]
pub enum Socket {
STARTTLS = 1,
SSL = 2,
Automatic = 0,
SSL = 1,
STARTTLS = 2,
Plain = 3,
}
#[derive(Debug, PartialEq)]
impl Default for Socket {
fn default() -> Self {
Socket::Automatic
}
}
#[derive(Debug, PartialEq, Clone)]
#[repr(u8)]
pub enum UsernamePattern {
EMAIL = 1,
@@ -42,7 +53,7 @@ pub enum Oauth2Authorizer {
Gmail = 2,
}
#[derive(Debug)]
#[derive(Debug, Clone)]
pub struct Server {
pub protocol: Protocol,
pub socket: Socket,
@@ -51,20 +62,6 @@ pub struct Server {
pub username_pattern: UsernamePattern,
}
impl Server {
pub fn apply_username_pattern(&self, addr: String) -> String {
match self.username_pattern {
UsernamePattern::EMAIL => addr,
UsernamePattern::EMAILLOCALPART => {
if let Some(at) = addr.find('@') {
return addr.split_at(at).0.to_string();
}
addr
}
}
}
}
#[derive(Debug)]
pub struct ConfigDefault {
pub key: Config,
@@ -84,20 +81,25 @@ pub struct Provider {
}
impl Provider {
pub fn get_server(&self, protocol: Protocol) -> Option<&Server> {
for record in self.server.iter() {
if record.protocol == protocol {
return Some(record);
}
}
None
pub fn get_server(&self, protocol: Protocol) -> Vec<ServerParams> {
self.server
.iter()
.filter(|s| s.protocol == protocol)
.map(|s| ServerParams {
protocol: s.protocol,
socket: s.socket,
hostname: s.hostname.to_string(),
port: s.port,
username_pattern: s.username_pattern.clone(),
})
.collect()
}
pub fn get_imap_server(&self) -> Option<&Server> {
pub fn get_imap_server(&self) -> ImapServers {
self.get_server(Protocol::IMAP)
}
pub fn get_smtp_server(&self) -> Option<&Server> {
pub fn get_smtp_server(&self) -> SmtpServers {
self.get_server(Protocol::SMTP)
}
}
@@ -139,13 +141,13 @@ mod tests {
let provider = get_provider_info("user@nauta.cu").unwrap();
assert!(provider.status == Status::OK);
let server = provider.get_imap_server().unwrap();
let server = &provider.get_imap_server()[0];
assert_eq!(server.protocol, Protocol::IMAP);
assert_eq!(server.socket, Socket::STARTTLS);
assert_eq!(server.hostname, "imap.nauta.cu");
assert_eq!(server.port, 143);
assert_eq!(server.username_pattern, UsernamePattern::EMAIL);
let server = provider.get_smtp_server().unwrap();
let server = &provider.get_smtp_server()[0];
assert_eq!(server.protocol, Protocol::SMTP);
assert_eq!(server.socket, Socket::STARTTLS);
assert_eq!(server.hostname, "smtp.nauta.cu");

View File

@@ -10,9 +10,9 @@ use async_smtp::*;
use crate::constants::*;
use crate::context::Context;
use crate::events::EventType;
use crate::login_param::{dc_build_tls, CertificateChecks, LoginParam};
use crate::login_param::{dc_build_tls, CertificateChecks, LoginParam, ServerLoginParam};
use crate::oauth2::*;
use crate::provider::get_provider_info;
use crate::provider::{get_provider_info, Socket};
use crate::stock::StockMessage;
/// SMTP write and read timeout in seconds.
@@ -94,31 +94,53 @@ impl Smtp {
.unwrap_or_default()
}
/// Connect using configured parameters.
pub async fn connect_configured(&mut self, context: &Context) -> Result<()> {
if self.is_connected().await {
return Ok(());
}
let lp = LoginParam::from_database(context, "configured_").await;
self.connect(
context,
&lp.smtp,
&lp.addr,
lp.server_flags & DC_LP_AUTH_OAUTH2 != 0,
)
.await
}
/// Connect using the provided login params.
pub async fn connect(&mut self, context: &Context, lp: &LoginParam) -> Result<()> {
pub async fn connect(
&mut self,
context: &Context,
lp: &ServerLoginParam,
addr: &str,
oauth2: bool,
) -> Result<()> {
if self.is_connected().await {
warn!(context, "SMTP already connected.");
return Ok(());
}
if lp.send_server.is_empty() || lp.send_port == 0 {
if lp.server.is_empty() || lp.port == 0 {
context.emit_event(EventType::ErrorNetwork("SMTP bad parameters.".into()));
return Err(Error::BadParameters);
}
let from =
EmailAddress::new(lp.addr.clone()).map_err(|err| Error::InvalidLoginAddress {
address: lp.addr.clone(),
EmailAddress::new(addr.to_string()).map_err(|err| Error::InvalidLoginAddress {
address: addr.to_string(),
error: err,
})?;
self.from = Some(from);
let domain = &lp.send_server;
let port = lp.send_port as u16;
let domain = &lp.server;
let port = lp.port;
let provider = get_provider_info(&lp.addr);
let strict_tls = match lp.smtp_certificate_checks {
let provider = get_provider_info(addr);
let strict_tls = match lp.certificate_checks {
CertificateChecks::Automatic => provider.map_or(false, |provider| provider.strict_tls),
CertificateChecks::Strict => true,
CertificateChecks::AcceptInvalidCertificates
@@ -127,17 +149,16 @@ impl Smtp {
let tls_config = dc_build_tls(strict_tls);
let tls_parameters = ClientTlsParameters::new(domain.to_string(), tls_config);
let (creds, mechanism) = if 0 != lp.server_flags & (DC_LP_AUTH_OAUTH2 as i32) {
let (creds, mechanism) = if oauth2 {
// oauth2
let addr = &lp.addr;
let send_pw = &lp.send_pw;
let send_pw = &lp.password;
let access_token = dc_get_oauth2_access_token(context, addr, send_pw, false).await;
if access_token.is_none() {
return Err(Error::Oauth2Error {
address: addr.to_string(),
});
}
let user = &lp.send_user;
let user = &lp.user;
(
smtp::authentication::Credentials::new(
user.to_string(),
@@ -147,8 +168,8 @@ impl Smtp {
)
} else {
// plain
let user = lp.send_user.clone();
let pw = lp.send_pw.clone();
let user = lp.user.clone();
let pw = lp.password.clone();
(
smtp::authentication::Credentials::new(user, pw),
vec![
@@ -158,12 +179,9 @@ impl Smtp {
)
};
let security = if 0
!= lp.server_flags & (DC_LP_SMTP_SOCKET_STARTTLS | DC_LP_SMTP_SOCKET_PLAIN) as i32
{
smtp::ClientSecurity::Opportunistic(tls_parameters)
} else {
smtp::ClientSecurity::Wrapper(tls_parameters)
let security = match lp.security {
Socket::STARTTLS | Socket::Plain => smtp::ClientSecurity::Opportunistic(tls_parameters),
_ => smtp::ClientSecurity::Wrapper(tls_parameters),
};
let client = smtp::SmtpClient::with_security((domain.as_str(), port), security)
@@ -196,7 +214,7 @@ impl Smtp {
context.emit_event(EventType::SmtpConnected(format!(
"SMTP-LOGIN as {} ok",
lp.send_user,
lp.user,
)));
Ok(())

View File

@@ -1288,6 +1288,33 @@ async fn open(
update_icons = true;
sql.set_raw_config_int(context, "dbversion", 66).await?;
}
if dbversion < 67 {
info!(context, "[migration] v67");
for prefix in &["", "configured_"] {
if let Some(server_flags) = sql
.get_raw_config_int(context, format!("{}server_flags", prefix))
.await
{
let imap_socket_flags = server_flags & 0x700;
let key = format!("{}mail_security", prefix);
match imap_socket_flags {
0x100 => sql.set_raw_config_int(context, key, 2).await?, // STARTTLS
0x200 => sql.set_raw_config_int(context, key, 1).await?, // SSL/TLS
0x400 => sql.set_raw_config_int(context, key, 3).await?, // Plain
_ => sql.set_raw_config_int(context, key, 0).await?,
}
let smtp_socket_flags = server_flags & 0x70000;
let key = format!("{}send_security", prefix);
match smtp_socket_flags {
0x10000 => sql.set_raw_config_int(context, key, 2).await?, // STARTTLS
0x20000 => sql.set_raw_config_int(context, key, 1).await?, // SSL/TLS
0x40000 => sql.set_raw_config_int(context, key, 3).await?, // Plain
_ => sql.set_raw_config_int(context, key, 0).await?,
}
}
}
sql.set_raw_config_int(context, "dbversion", 67).await?;
}
// (2) updates that require high-level objects
// (the structure is complete now and all objects are usable)