diff --git a/src/configure/mod.rs b/src/configure/mod.rs index 602c316ce..47b2122d5 100644 --- a/src/configure/mod.rs +++ b/src/configure/mod.rs @@ -4,7 +4,7 @@ mod auto_mozilla; mod auto_outlook; mod read_url; -use anyhow::{bail, ensure, format_err, Result}; +use anyhow::{bail, ensure, Context as _, Result}; use async_std::prelude::*; use percent_encoding::{utf8_percent_encode, NON_ALPHANUMERIC}; @@ -66,54 +66,34 @@ impl Context { } async fn inner_configure(&self) -> Result<()> { - let mut success = false; - let mut param_autoconfig: Option = None; - info!(self, "Configure ..."); + let mut param_autoconfig: Option = None; + // Variables that are shared between steps: let mut param = LoginParam::from_database(self, "").await; - // need all vars here to be mutable because rust thinks the same step could be called multiple times - // and also initialize, because otherwise rust thinks it's used while unitilized, even if thats not the case as the loop goes only forward + // need all vars here to be mutable because rust thinks the same step could be called + // multiple times and also initialize, because otherwise rust thinks it's used while + // unitialized, even if thats not the case as the loop goes only forward let mut param_domain = "undefined.undefined".to_owned(); let mut param_addr_urlencoded: String = "Internal Error: this value should never be used".to_owned(); let mut keep_flags = 0; - let mut step_counter: u8 = 0; let (_s, r) = async_std::sync::channel(1); let mut imap = Imap::new(r); let was_configured_before = self.is_configured().await; - while !self.shall_stop_ongoing().await { - step_counter += 1; - - match exec_step( - self, - &mut imap, - &mut param, - &mut param_domain, - &mut param_autoconfig, - &mut param_addr_urlencoded, - &mut keep_flags, - &mut step_counter, - ) - .await - { - Ok(step) => { - success = true; - match step { - Step::Continue => {} - Step::Done => break, - } - } - Err(err) => { - error!(self, "{}", err); - success = false; - break; - } - } - } + let success = exec( + self, + &mut imap, + &mut param, + &mut param_domain, + &mut param_autoconfig, + &mut param_addr_urlencoded, + &mut keep_flags, + ) + .await; if imap.is_connected() { imap.disconnect(self).await; @@ -122,18 +102,16 @@ impl Context { // remember the entered parameters on success // and restore to last-entered on failure. // this way, the parameters visible to the ui are always in-sync with the current configuration. - if success { + if success.is_ok() { LoginParam::from_database(self, "") .await .save_to_database(self, "configured_raw_") - .await - .ok(); + .await?; } else { LoginParam::from_database(self, "configured_raw_") .await .save_to_database(self, "") - .await - .ok(); + .await?; } if let Some(provider) = provider::get_provider_info(¶m.addr) { @@ -158,18 +136,22 @@ impl Context { } } - if success { - progress!(self, 1000); - Ok(()) - } else { - progress!(self, 0); - Err(format_err!("Configure failed")) + match success { + Ok(_) => { + progress!(self, 1000); + Ok(()) + } + Err(err) => { + error!(self, "Configure Failed: {}", err); + progress!(self, 0); + Err(err) + } } } } #[allow(clippy::too_many_arguments)] -async fn exec_step( +async fn exec( ctx: &Context, imap: &mut Imap, param: &mut LoginParam, @@ -177,53 +159,43 @@ async fn exec_step( param_autoconfig: &mut Option, param_addr_urlencoded: &mut String, keep_flags: &mut i32, - step_counter: &mut u8, -) -> Result { - const STEP_12_USE_AUTOCONFIG: u8 = 12; - const STEP_13_AFTER_AUTOCONFIG: u8 = 13; +) -> Result<()> { + let mut use_autoconfig = true; + let mut have_autoconfig = false; - match *step_counter { - // Read login parameters from the database - 1 => { - progress!(ctx, 1); - ensure!(!param.addr.is_empty(), "Please enter an email address."); - } - // Step 1: Load the parameters and check email-address and password - 2 => { - if 0 != param.server_flags & DC_LP_AUTH_OAUTH2 { - // the used oauth2 addr may differ, check this. - // if dc_get_oauth2_addr() is not available in the oauth2 implementation, - // just use the given one. - progress!(ctx, 10); - if let Some(oauth2_addr) = dc_get_oauth2_addr(ctx, ¶m.addr, ¶m.mail_pw) - .await - .and_then(|e| e.parse().ok()) - { - info!(ctx, "Authorized address is {}", oauth2_addr); - param.addr = oauth2_addr; - ctx.sql - .set_raw_config(ctx, "addr", Some(param.addr.as_str())) - .await?; - } - progress!(ctx, 20); - } - // no oauth? - just continue it's no error - } - 3 => { - if let Ok(parsed) = param.addr.parse() { - let parsed: EmailAddress = parsed; - *param_domain = parsed.domain; - *param_addr_urlencoded = - utf8_percent_encode(¶m.addr, NON_ALPHANUMERIC).to_string(); - } else { - bail!("Bad email-address."); - } - } - // Step 2: Autoconfig - 4 => { - progress!(ctx, 200); + // Read login parameters from the database + progress!(ctx, 1); + ensure!(!param.addr.is_empty(), "Please enter an email address."); - if param.mail_server.is_empty() + // Step 1: Load the parameters and check email-address and password + + if 0 != param.server_flags & DC_LP_AUTH_OAUTH2 { + // the used oauth2 addr may differ, check this. + // if dc_get_oauth2_addr() is not available in the oauth2 implementation, + // just use the given one. + progress!(ctx, 10); + if let Some(oauth2_addr) = dc_get_oauth2_addr(ctx, ¶m.addr, ¶m.mail_pw) + .await + .and_then(|e| e.parse().ok()) + { + info!(ctx, "Authorized address is {}", oauth2_addr); + param.addr = oauth2_addr; + ctx.sql + .set_raw_config(ctx, "addr", Some(param.addr.as_str())) + .await?; + } + progress!(ctx, 20); + } + // no oauth? - just continue it's no error + + let parsed: EmailAddress = param.addr.parse().context("Bad email-address")?; + *param_domain = parsed.domain; + *param_addr_urlencoded = utf8_percent_encode(¶m.addr, NON_ALPHANUMERIC).to_string(); + + // Step 2: Autoconfig + progress!(ctx, 200); + + if param.mail_server.is_empty() && param.mail_port == 0 /* && param.mail_user.is_empty() -- the user can enter a loginname which is used by autoconfig then */ && param.send_server.is_empty() @@ -231,238 +203,230 @@ async fn exec_step( && param.send_user.is_empty() /* && param.send_pw.is_empty() -- the password cannot be auto-configured and is no criterion for autoconfig or not */ && (param.server_flags & !DC_LP_AUTH_OAUTH2) == 0 - { - // no advanced parameters entered by the user: query provider-database or do Autoconfig - *keep_flags = param.server_flags & DC_LP_AUTH_OAUTH2; - if let Some(new_param) = get_offline_autoconfig(ctx, ¶m) { - // got parameters from our provider-database, skip Autoconfig, preserve the OAuth2 setting - *param_autoconfig = Some(new_param); - *step_counter = STEP_12_USE_AUTOCONFIG - 1; // minus one as step_counter is increased on next loop - } - } else { - // advanced parameters entered by the user: skip Autoconfig - *step_counter = STEP_13_AFTER_AUTOCONFIG - 1; // minus one as step_counter is increased on next loop - } - } - /* A. Search configurations from the domain used in the email-address, prefer encrypted */ - 5 => { - if param_autoconfig.is_none() { - let url = format!( - "https://autoconfig.{}/mail/config-v1.1.xml?emailaddress={}", - param_domain, param_addr_urlencoded - ); - *param_autoconfig = moz_autoconfigure(ctx, &url, ¶m).await.ok(); - } - } - 6 => { - progress!(ctx, 300); - if param_autoconfig.is_none() { - // the doc does not mention `emailaddress=`, however, Thunderbird adds it, see https://releases.mozilla.org/pub/thunderbird/ , which makes some sense - let url = format!( - "https://{}/.well-known/autoconfig/mail/config-v1.1.xml?emailaddress={}", - param_domain, param_addr_urlencoded - ); - *param_autoconfig = moz_autoconfigure(ctx, &url, ¶m).await.ok(); - } - } - /* Outlook section start ------------- */ - /* Outlook uses always SSL but different domains (this comment describes the next two steps) */ - 7 => { - progress!(ctx, 310); - if param_autoconfig.is_none() { - let url = format!("https://{}/autodiscover/autodiscover.xml", param_domain); - *param_autoconfig = outlk_autodiscover(ctx, &url, ¶m).await.ok(); - } - } - 8 => { - progress!(ctx, 320); - if param_autoconfig.is_none() { - let url = format!( - "https://{}{}/autodiscover/autodiscover.xml", - "autodiscover.", param_domain - ); - *param_autoconfig = outlk_autodiscover(ctx, &url, ¶m).await.ok(); - } - } - /* ----------- Outlook section end */ - 9 => { - progress!(ctx, 330); - if param_autoconfig.is_none() { - let url = format!( - "http://autoconfig.{}/mail/config-v1.1.xml?emailaddress={}", - param_domain, param_addr_urlencoded - ); - *param_autoconfig = moz_autoconfigure(ctx, &url, ¶m).await.ok(); - } - } - 10 => { - progress!(ctx, 340); - if param_autoconfig.is_none() { - // do not transfer the email-address unencrypted - let url = format!( - "http://{}/.well-known/autoconfig/mail/config-v1.1.xml", - param_domain - ); - *param_autoconfig = moz_autoconfigure(ctx, &url, ¶m).await.ok(); - } - } - /* B. If we have no configuration yet, search configuration in Thunderbird's centeral database */ - 11 => { - progress!(ctx, 350); - if param_autoconfig.is_none() { - /* always SSL for Thunderbird's database */ - let url = format!("https://autoconfig.thunderbird.net/v1.1/{}", param_domain); - *param_autoconfig = moz_autoconfigure(ctx, &url, ¶m).await.ok(); - } - } - /* C. Do we have any autoconfig result? - If you change the match-number here, also update STEP_12_COPY_AUTOCONFIG above - */ - STEP_12_USE_AUTOCONFIG => { - 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(); - } - param.mail_server = cfg.mail_server.clone(); /* all other values are always NULL when entering autoconfig */ - 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 you change the match-number here, also update STEP_13_AFTER_AUTOCONFIG above - STEP_13_AFTER_AUTOCONFIG => { - if param.mail_server.is_empty() { - param.mail_server = format!("imap.{}", param_domain,) - } - if param.mail_port == 0 { - param.mail_port = if 0 != param.server_flags & (0x100 | 0x400) { - 143 - } else { - 993 - } - } - if param.mail_user.is_empty() { - param.mail_user = param.addr.clone(); - } - if param.send_server.is_empty() && !param.mail_server.is_empty() { - param.send_server = param.mail_server.clone(); - if param.send_server.starts_with("imap.") { - param.send_server = param.send_server.replacen("imap", "smtp", 1); - } - } - if param.send_port == 0 { - param.send_port = if 0 != param.server_flags & DC_LP_SMTP_SOCKET_STARTTLS as i32 { - 587 - } else if 0 != param.server_flags & DC_LP_SMTP_SOCKET_PLAIN as i32 { - 25 - } else { - 465 - } - } - if param.send_user.is_empty() && !param.mail_user.is_empty() { - param.send_user = param.mail_user.clone(); - } - if param.send_pw.is_empty() && !param.mail_pw.is_empty() { - param.send_pw = param.mail_pw.clone() - } - if !dc_exactly_one_bit_set(param.server_flags & DC_LP_AUTH_FLAGS as i32) { - param.server_flags &= !(DC_LP_AUTH_FLAGS as i32); - param.server_flags |= DC_LP_AUTH_NORMAL as i32 - } - if !dc_exactly_one_bit_set(param.server_flags & DC_LP_IMAP_SOCKET_FLAGS as i32) { - param.server_flags &= !(DC_LP_IMAP_SOCKET_FLAGS as i32); - param.server_flags |= if param.send_port == 143 { - DC_LP_IMAP_SOCKET_STARTTLS as i32 - } else { - DC_LP_IMAP_SOCKET_SSL as i32 - } - } - if !dc_exactly_one_bit_set(param.server_flags & (DC_LP_SMTP_SOCKET_FLAGS as i32)) { - param.server_flags &= !(DC_LP_SMTP_SOCKET_FLAGS as i32); - param.server_flags |= if param.send_port == 587 { - DC_LP_SMTP_SOCKET_STARTTLS as i32 - } else if param.send_port == 25 { - DC_LP_SMTP_SOCKET_PLAIN as i32 - } else { - DC_LP_SMTP_SOCKET_SSL as i32 - } - } - /* do we have a complete configuration? */ - if param.mail_server.is_empty() - || param.mail_port == 0 - || param.mail_user.is_empty() - || param.mail_pw.is_empty() - || param.send_server.is_empty() - || param.send_port == 0 - || param.send_user.is_empty() - || param.send_pw.is_empty() - || param.server_flags == 0 - { - bail!("Account settings incomplete."); - } - } - 14 => { - progress!(ctx, 600); - /* try to connect to IMAP - if we did not got an autoconfig, - do some further tries with different settings and username variations */ - try_imap_connections(ctx, param, param_autoconfig.is_some(), imap).await?; - } - 15 => { - progress!(ctx, 800); - try_smtp_connections(ctx, param, param_autoconfig.is_some()).await?; - } - 16 => { - progress!(ctx, 900); - - let create_mvbox = ctx.get_config_bool(Config::MvboxWatch).await - || ctx.get_config_bool(Config::MvboxMove).await; - - if let Err(err) = imap.configure_folders(ctx, create_mvbox).await { - bail!("configuring folders failed: {:?}", err); - } - - if let Err(err) = imap.select_with_uidvalidity(ctx, "INBOX").await { - bail!("could not read INBOX status: {:?}", err); - } - } - 17 => { - progress!(ctx, 910); - // configuration success - write back the configured parameters with the - // "configured_" prefix; also write the "configured"-flag */ - // the trailing underscore is correct - param.save_to_database(ctx, "configured_").await?; - ctx.sql.set_raw_config_bool(ctx, "configured", true).await?; - } - 18 => { - progress!(ctx, 920); - // we generate the keypair just now - we could also postpone this until the first message is sent, however, - // this may result in a unexpected and annoying delay when the user sends his very first message - // (~30 seconds on a Moto G4 play) and might looks as if message sending is always that slow. - e2ee::ensure_secret_key_exists(ctx).await?; - info!(ctx, "key generation completed"); - progress!(ctx, 940); - return Ok(Step::Done); - } - _ => { - bail!("Internal error: step counter out of bound"); + { + // no advanced parameters entered by the user: query provider-database or do Autoconfig + *keep_flags = param.server_flags & DC_LP_AUTH_OAUTH2; + if let Some(new_param) = get_offline_autoconfig(ctx, ¶m) { + // got parameters from our provider-database, skip Autoconfig, preserve the OAuth2 setting + *param_autoconfig = Some(new_param); + have_autoconfig = true; } + } else { + // advanced parameters entered by the user: skip Autoconfig + use_autoconfig = false; } - Ok(Step::Continue) + if !have_autoconfig { + get_autoconfig( + ctx, + param, + param_domain, + param_autoconfig, + param_addr_urlencoded, + ) + .await?; + } + + /* C. Do we have any autoconfig result? */ + progress!(ctx, 500); + if use_autoconfig { + 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(); + } + param.mail_server = cfg.mail_server.clone(); /* all other values are always NULL when entering autoconfig */ + param.mail_port = cfg.mail_port; + param.send_server = cfg.send_server.clone(); + param.send_port = cfg.send_port; + param.send_user = cfg.send_user.clone(); + param.server_flags = cfg.server_flags; + /* although param_autoconfig's data are no longer needed from, + it is used to later to prevent trying variations of port/server/logins */ + } + param.server_flags |= *keep_flags; + } + + // Step 3: Fill missing fields with defaults + if param.mail_server.is_empty() { + param.mail_server = format!("imap.{}", param_domain,) + } + if param.mail_port == 0 { + param.mail_port = if 0 != param.server_flags & (0x100 | 0x400) { + 143 + } else { + 993 + } + } + if param.mail_user.is_empty() { + param.mail_user = param.addr.clone(); + } + if param.send_server.is_empty() && !param.mail_server.is_empty() { + param.send_server = param.mail_server.clone(); + if param.send_server.starts_with("imap.") { + param.send_server = param.send_server.replacen("imap", "smtp", 1); + } + } + if param.send_port == 0 { + param.send_port = if 0 != param.server_flags & DC_LP_SMTP_SOCKET_STARTTLS as i32 { + 587 + } else if 0 != param.server_flags & DC_LP_SMTP_SOCKET_PLAIN as i32 { + 25 + } else { + 465 + } + } + if param.send_user.is_empty() && !param.mail_user.is_empty() { + param.send_user = param.mail_user.clone(); + } + if param.send_pw.is_empty() && !param.mail_pw.is_empty() { + param.send_pw = param.mail_pw.clone() + } + if !dc_exactly_one_bit_set(param.server_flags & DC_LP_AUTH_FLAGS as i32) { + param.server_flags &= !(DC_LP_AUTH_FLAGS as i32); + param.server_flags |= DC_LP_AUTH_NORMAL as i32 + } + if !dc_exactly_one_bit_set(param.server_flags & DC_LP_IMAP_SOCKET_FLAGS as i32) { + param.server_flags &= !(DC_LP_IMAP_SOCKET_FLAGS as i32); + param.server_flags |= if param.send_port == 143 { + DC_LP_IMAP_SOCKET_STARTTLS as i32 + } else { + DC_LP_IMAP_SOCKET_SSL as i32 + } + } + if !dc_exactly_one_bit_set(param.server_flags & (DC_LP_SMTP_SOCKET_FLAGS as i32)) { + param.server_flags &= !(DC_LP_SMTP_SOCKET_FLAGS as i32); + param.server_flags |= if param.send_port == 587 { + DC_LP_SMTP_SOCKET_STARTTLS as i32 + } else if param.send_port == 25 { + DC_LP_SMTP_SOCKET_PLAIN as i32 + } else { + DC_LP_SMTP_SOCKET_SSL as i32 + } + } + /* do we have a complete configuration? */ + if param.mail_server.is_empty() + || param.mail_port == 0 + || param.mail_user.is_empty() + || param.mail_pw.is_empty() + || param.send_server.is_empty() + || param.send_port == 0 + || param.send_user.is_empty() + || param.send_pw.is_empty() + || param.server_flags == 0 + { + bail!("Account settings incomplete."); + } + + progress!(ctx, 600); + /* try to connect to IMAP - if we did not got an autoconfig, + do some further tries with different settings and username variations */ + try_imap_connections(ctx, param, param_autoconfig.is_some(), imap).await?; + + progress!(ctx, 800); + try_smtp_connections(ctx, param, param_autoconfig.is_some()).await?; + + progress!(ctx, 900); + + let create_mvbox = ctx.get_config_bool(Config::MvboxWatch).await + || ctx.get_config_bool(Config::MvboxMove).await; + + if let Err(err) = imap.configure_folders(ctx, create_mvbox).await { + bail!("configuring folders failed: {:?}", err); + } + + if let Err(err) = imap.select_with_uidvalidity(ctx, "INBOX").await { + bail!("could not read INBOX status: {:?}", err); + } + + progress!(ctx, 910); + // configuration success - write back the configured parameters with the + // "configured_" prefix; also write the "configured"-flag */ + // the trailing underscore is correct + param.save_to_database(ctx, "configured_").await?; + ctx.sql.set_raw_config_bool(ctx, "configured", true).await?; + + progress!(ctx, 920); + // we generate the keypair just now - we could also postpone this until the first message is sent, however, + // this may result in a unexpected and annoying delay when the user sends his very first message + // (~30 seconds on a Moto G4 play) and might looks as if message sending is always that slow. + e2ee::ensure_secret_key_exists(ctx).await?; + info!(ctx, "key generation completed"); + + progress!(ctx, 940); + + Ok(()) } -#[derive(Debug)] -enum Step { - Done, - Continue, +async fn get_autoconfig( + ctx: &Context, + param: &mut LoginParam, + param_domain: &mut String, + param_autoconfig: &mut Option, + param_addr_urlencoded: &mut String, +) -> Result<()> { + /* A. Search configurations from the domain used in the email-address, prefer encrypted */ + if param_autoconfig.is_none() { + let url = format!( + "https://autoconfig.{}/mail/config-v1.1.xml?emailaddress={}", + param_domain, param_addr_urlencoded + ); + *param_autoconfig = moz_autoconfigure(ctx, &url, ¶m).await.ok(); + } + progress!(ctx, 300); + if param_autoconfig.is_none() { + // the doc does not mention `emailaddress=`, however, Thunderbird adds it, see https://releases.mozilla.org/pub/thunderbird/ , which makes some sense + let url = format!( + "https://{}/.well-known/autoconfig/mail/config-v1.1.xml?emailaddress={}", + param_domain, param_addr_urlencoded + ); + *param_autoconfig = moz_autoconfigure(ctx, &url, ¶m).await.ok(); + } + /* Outlook section start ------------- */ + /* Outlook uses always SSL but different domains (this comment describes the next two steps) */ + + progress!(ctx, 310); + if param_autoconfig.is_none() { + let url = format!("https://{}/autodiscover/autodiscover.xml", param_domain); + *param_autoconfig = outlk_autodiscover(ctx, &url, ¶m).await.ok(); + } + progress!(ctx, 320); + if param_autoconfig.is_none() { + let url = format!( + "https://{}{}/autodiscover/autodiscover.xml", + "autodiscover.", param_domain + ); + *param_autoconfig = outlk_autodiscover(ctx, &url, ¶m).await.ok(); + } + /* ----------- Outlook section end */ + + progress!(ctx, 330); + if param_autoconfig.is_none() { + let url = format!( + "http://autoconfig.{}/mail/config-v1.1.xml?emailaddress={}", + param_domain, param_addr_urlencoded + ); + *param_autoconfig = moz_autoconfigure(ctx, &url, ¶m).await.ok(); + } + + progress!(ctx, 340); + if param_autoconfig.is_none() { + // do not transfer the email-address unencrypted + let url = format!( + "http://{}/.well-known/autoconfig/mail/config-v1.1.xml", + param_domain + ); + *param_autoconfig = moz_autoconfigure(ctx, &url, ¶m).await.ok(); + } + /* B. If we have no configuration yet, search configuration in Thunderbird's centeral database */ + progress!(ctx, 350); + if param_autoconfig.is_none() { + /* always SSL for Thunderbird's database */ + let url = format!("https://autoconfig.thunderbird.net/v1.1/{}", param_domain); + *param_autoconfig = moz_autoconfigure(ctx, &url, ¶m).await.ok(); + } + + Ok(()) } #[allow(clippy::unnecessary_unwrap)]