diff --git a/deltachat-ffi/deltachat.h b/deltachat-ffi/deltachat.h index 86e8ea4b48..b3201efe4a 100644 --- a/deltachat-ffi/deltachat.h +++ b/deltachat-ffi/deltachat.h @@ -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,88 +4169,83 @@ int64_t dc_lot_get_timestamp (const dc_lot_t* lot); */ #define DC_MSG_VIDEOCHAT_INVITATION 70 - /** * @} */ /** - * @defgroup DC_LP DC_LP + * @defgroup DC_SOCKET DC_SOCKET * - * Flags for configuring IMAP and SMTP servers. - * These flags are optional - * and may be set together with the username, password etc. - * via dc_set_config() using the key "server_flags". + * 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_LP + * @addtogroup DC_SOCKET * @{ */ - /** - * Force OAuth2 authorization. This flag does not skip automatic configuration. - * Before calling dc_configure() with DC_LP_AUTH_OAUTH2 set, - * the user has to confirm access at the URL returned by dc_get_oauth2_url(). + * Choose socket security automatically. */ -#define DC_LP_AUTH_OAUTH2 0x2 +#define DC_SOCKET_AUTO 0 /** - * Force NORMAL authorization, this is the default. - * If this flag is set, automatic configuration is skipped. + * Connect via SSL/TLS. */ -#define DC_LP_AUTH_NORMAL 0x4 +#define DC_SOCKET_SSL 1 /** - * Connect to IMAP via STARTTLS. - * If this flag is set, automatic configuration is skipped. + * Connect via STARTTLS. */ -#define DC_LP_IMAP_SOCKET_STARTTLS 0x100 +#define DC_SOCKET_STARTTLS 2 /** - * Connect to IMAP via SSL. - * If this flag is set, automatic configuration is skipped. + * Connect unencrypted, this should not be used. */ -#define DC_LP_IMAP_SOCKET_SSL 0x200 - +#define DC_SOCKET_PLAIN 3 /** - * 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. + * @defgroup DC_LP DC_LP + * + * Flags for configuring IMAP and SMTP servers. + * These flags are optional + * and may be set together with the username, password etc. + * via dc_set_config() using the key "server_flags". + * + * @addtogroup DC_LP + * @{ */ -#define DC_LP_SMTP_SOCKET_STARTTLS 0x10000 /** - * Connect to SMTP via SSL. - * If this flag is set, automatic configuration is skipped. + * Force OAuth2 authorization. This flag does not skip automatic configuration. + * Before calling dc_configure() with DC_LP_AUTH_OAUTH2 set, + * the user has to confirm access at the URL returned by dc_get_oauth2_url(). */ -#define DC_LP_SMTP_SOCKET_SSL 0x20000 +#define DC_LP_AUTH_OAUTH2 0x2 /** - * Connect to SMTP unencrypted, this should not be used. + * Force NORMAL authorization, this is the default. * If this flag is set, automatic configuration is skipped. */ -#define DC_LP_SMTP_SOCKET_PLAIN 0x40000 ///< +#define DC_LP_AUTH_NORMAL 0x4 + /** * @} */ #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 diff --git a/src/config.rs b/src/config.rs index 6e08fd3f8c..87d06f966c 100644 --- a/src/config.rs +++ b/src/config.rs @@ -25,11 +25,13 @@ pub enum Config { MailUser, MailPw, MailPort, + MailSecurity, ImapCertificateChecks, SendServer, SendUser, SendPw, SendPort, + SendSecurity, SmtpCertificateChecks, ServerFlags, diff --git a/src/configure/auto_mozilla.rs b/src/configure/auto_mozilla.rs index 984012924a..887f4e89d1 100644 --- a/src/configure/auto_mozilla.rs +++ b/src/configure/auto_mozilla.rs @@ -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 { 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( 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 { "; 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); } } diff --git a/src/configure/auto_outlook.rs b/src/configure/auto_outlook.rs index 638ae25059..17622eb5ca 100644 --- a/src/configure/auto_outlook.rs +++ b/src/configure/auto_outlook.rs @@ -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, pub config_server: String, - pub config_port: i32, + pub config_port: u16, pub config_ssl: String, pub config_redirecturl: Option, } @@ -98,10 +98,10 @@ fn parse_xml(xml_raw: &str) -> Result { 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"); diff --git a/src/configure/mod.rs b/src/configure/mod.rs index c87ee9620f..a1bc577896 100644 --- a/src/configure/mod.rs +++ b/src/configure/mod.rs @@ -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 = None; + let mut param_autoconfig: Option = 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, ¶m.addr, ¶m.mail_pw) + if let Some(oauth2_addr) = dc_get_oauth2_addr(ctx, ¶m.addr, ¶m.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.send_pw.is_empty() { - param.send_pw = param.mail_pw.clone() + if param.smtp.user.is_empty() { + param.smtp.user = param.imap.user.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 { +) -> Option { 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 { +fn get_offline_autoconfig(context: &Context, param: &LoginParam) -> Option { info!( context, "checking internal provider-info for offline autoconfig" @@ -364,39 +364,11 @@ fn get_offline_autoconfig(context: &Context, param: &LoginParam) -> Option { 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 Option { + 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 { - 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() + ¶m_domain; + 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.mail_server = "mail.".to_string() + ¶m_domain; + param.imap.server = "mail.".to_string() + ¶m_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 { // 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 { - 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, ¶m, 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, ¶m, 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, ¶m).await { + if imap + .connect( + context, + ¶m.imap, + ¶m.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 { - 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() + ¶m_domain; + 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.send_server = "mail.".to_string() + ¶m_domain; + param.smtp.server = "mail.".to_string() + ¶m_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 { // 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 { - 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, ¶m, 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, ¶m, 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, ¶m).await { + 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); } @@ -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, ¶ms).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_certificate_checks, - CertificateChecks::Automatic - ); - assert_eq!( - found_params.smtp_certificate_checks, - CertificateChecks::Automatic - ); + 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); } } diff --git a/src/constants.rs b/src/constants.rs index 9771485405..45526dedda 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -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; diff --git a/src/contact.rs b/src/contact.rs index 080fb81695..c557496e0e 100644 --- a/src/contact.rs +++ b/src/contact.rs @@ -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 { diff --git a/src/imap/mod.rs b/src/imap/mod.rs index 9048934865..71d878e339 100644 --- a/src/imap/mod.rs +++ b/src/imap/mod.rs @@ -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, pub selected_mailbox: Option, 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 = - 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; - - 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) - } + let connection_res: ImapResult = 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 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, ¶m).await { + if self + .connect( + context, + ¶m.imap, + ¶m.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 diff --git a/src/job.rs b/src/job.rs index ddbaca35bd..b4421b7681 100644 --- a/src/job.rs +++ b/src/job.rs @@ -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, || { diff --git a/src/login_param.rs b/src/login_param.rs index 776a228867..ba2666e259 100644 --- a/src/login_param.rs +++ b/src/login_param.rs @@ -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; +pub type SmtpServers = Vec; + +#[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); } diff --git a/src/provider/mod.rs b/src/provider/mod.rs index f35f9f3ede..83d59e263a 100644 --- a/src/provider/mod.rs +++ b/src/provider/mod.rs @@ -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 { + 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"); diff --git a/src/smtp/mod.rs b/src/smtp/mod.rs index 05707ecac7..f69b012574 100644 --- a/src/smtp/mod.rs +++ b/src/smtp/mod.rs @@ -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(()) diff --git a/src/sql.rs b/src/sql.rs index bf5f2f6a97..70d7d8d2ca 100644 --- a/src/sql.rs +++ b/src/sql.rs @@ -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)