//! # Login parameters. use std::fmt; use anyhow::{format_err, Context as _, Result}; use deltachat_contact_tools::EmailAddress; use serde::{Deserialize, Serialize}; use crate::config::Config; use crate::configure::server_params::{expand_param_vector, ServerParams}; use crate::constants::{DC_LP_AUTH_FLAGS, DC_LP_AUTH_NORMAL, DC_LP_AUTH_OAUTH2}; use crate::context::Context; use crate::net::load_connection_timestamp; use crate::net::proxy::ProxyConfig; use crate::provider::{Protocol, Provider, Socket, UsernamePattern}; use crate::sql::Sql; /// User-entered setting for certificate checks. /// /// Should be saved into `imap_certificate_checks` before running configuration. #[derive(Copy, Clone, Debug, Default, Display, FromPrimitive, ToPrimitive, PartialEq, Eq)] #[repr(u32)] #[strum(serialize_all = "snake_case")] pub enum EnteredCertificateChecks { /// `Automatic` means that provider database setting should be taken. /// If there is no provider database setting for certificate checks, /// check certificates strictly. #[default] Automatic = 0, /// Ensure that TLS certificate is valid for the server hostname. Strict = 1, /// Accept certificates that are expired, self-signed /// or otherwise not valid for the server hostname. AcceptInvalidCertificates = 2, /// Alias for `AcceptInvalidCertificates` /// for API compatibility. AcceptInvalidCertificates2 = 3, } /// Values saved into `imap_certificate_checks`. #[derive(Copy, Clone, Debug, Display, FromPrimitive, ToPrimitive, PartialEq, Eq)] #[repr(u32)] #[strum(serialize_all = "snake_case")] pub enum ConfiguredCertificateChecks { /// Use configuration from the provider database. /// If there is no provider database setting for certificate checks, /// accept invalid certificates. /// /// Must not be saved by new versions. /// /// Previous Delta Chat versions before core 1.133.0 /// stored this in `configured_imap_certificate_checks` /// if Automatic configuration /// was selected, configuration with strict TLS checks failed /// and configuration without strict TLS checks succeeded. OldAutomatic = 0, /// Ensure that TLS certificate is valid for the server hostname. Strict = 1, /// Accept certificates that are expired, self-signed /// or otherwise not valid for the server hostname. AcceptInvalidCertificates = 2, /// Accept certificates that are expired, self-signed /// or otherwise not valid for the server hostname. /// /// Alias to `AcceptInvalidCertificates` for compatibility. AcceptInvalidCertificates2 = 3, /// Use configuration from the provider database. /// If there is no provider database setting for certificate checks, /// apply strict checks to TLS certificates. Automatic = 4, } /// Login parameters for a single server, either IMAP or SMTP #[derive(Debug, Default, Clone, PartialEq, Eq)] pub struct EnteredServerLoginParam { /// Server hostname or IP address. pub server: String, /// Server port. /// /// 0 if not specified. pub port: u16, /// Socket security. pub security: Socket, /// Username. /// /// Empty string if not specified. pub user: String, /// Password. pub password: String, } /// Login parameters entered by the user. #[derive(Debug, Default, Clone, PartialEq, Eq)] pub struct EnteredLoginParam { /// Email address. pub addr: String, /// IMAP settings. pub imap: EnteredServerLoginParam, /// SMTP settings. pub smtp: EnteredServerLoginParam, /// TLS options: whether to allow invalid certificates and/or /// invalid hostnames pub certificate_checks: EnteredCertificateChecks, /// Proxy configuration. pub proxy_config: Option, pub oauth2: bool, } impl EnteredLoginParam { /// Loads entered account settings. pub async fn load(context: &Context) -> Result { let addr = context .get_config(Config::Addr) .await? .unwrap_or_default() .trim() .to_string(); let mail_server = context .get_config(Config::MailServer) .await? .unwrap_or_default(); let mail_port = context .get_config_parsed::(Config::MailPort) .await? .unwrap_or_default(); let mail_security = context .get_config_parsed::(Config::MailSecurity) .await? .and_then(num_traits::FromPrimitive::from_i32) .unwrap_or_default(); let mail_user = context .get_config(Config::MailUser) .await? .unwrap_or_default(); let mail_pw = context .get_config(Config::MailPw) .await? .unwrap_or_default(); // The setting is named `imap_certificate_checks` // for backwards compatibility, // but now it is a global setting applied to all protocols, // while `smtp_certificate_checks` is ignored. let certificate_checks = if let Some(certificate_checks) = context .get_config_parsed::(Config::ImapCertificateChecks) .await? { num_traits::FromPrimitive::from_i32(certificate_checks) .context("Unknown imap_certificate_checks value")? } else { Default::default() }; let send_server = context .get_config(Config::SendServer) .await? .unwrap_or_default(); let send_port = context .get_config_parsed::(Config::SendPort) .await? .unwrap_or_default(); let send_security = context .get_config_parsed::(Config::SendSecurity) .await? .and_then(num_traits::FromPrimitive::from_i32) .unwrap_or_default(); let send_user = context .get_config(Config::SendUser) .await? .unwrap_or_default(); let send_pw = context .get_config(Config::SendPw) .await? .unwrap_or_default(); let server_flags = context .get_config_parsed::(Config::ServerFlags) .await? .unwrap_or_default(); let oauth2 = matches!(server_flags & DC_LP_AUTH_FLAGS, DC_LP_AUTH_OAUTH2); let proxy_config = ProxyConfig::load(context).await?; Ok(EnteredLoginParam { addr, imap: EnteredServerLoginParam { server: mail_server, port: mail_port, security: mail_security, user: mail_user, password: mail_pw, }, smtp: EnteredServerLoginParam { server: send_server, port: send_port, security: send_security, user: send_user, password: send_pw, }, certificate_checks, proxy_config, oauth2, }) } } impl fmt::Display for EnteredLoginParam { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let unset = "0"; let pw = "***"; write!( f, "{} imap:{}:{}:{}:{}:{}:{} smtp:{}:{}:{}:{}:{}:{} cert_{}", unset_empty(&self.addr), unset_empty(&self.imap.user), if !self.imap.password.is_empty() { pw } else { unset }, unset_empty(&self.imap.server), self.imap.port, self.imap.security, if self.oauth2 { "OAUTH2" } else { "AUTH_NORMAL" }, unset_empty(&self.smtp.user), if !self.smtp.password.is_empty() { pw } else { unset }, unset_empty(&self.smtp.server), self.smtp.port, self.smtp.security, if self.oauth2 { "OAUTH2" } else { "AUTH_NORMAL" }, self.certificate_checks ) } } fn unset_empty(s: &str) -> &str { if s.is_empty() { "unset" } else { s } } #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub(crate) struct ConnectionCandidate { /// Server hostname or IP address. pub host: String, /// Server port. pub port: u16, /// Transport layer security. pub security: ConnectionSecurity, } impl fmt::Display for ConnectionCandidate { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}:{}:{}", &self.host, self.port, self.security)?; Ok(()) } } #[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub(crate) enum ConnectionSecurity { /// Implicit TLS. Tls, // STARTTLS. Starttls, /// Plaintext. Plain, } impl fmt::Display for ConnectionSecurity { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Tls => write!(f, "tls")?, Self::Starttls => write!(f, "starttls")?, Self::Plain => write!(f, "plain")?, } Ok(()) } } impl TryFrom for ConnectionSecurity { type Error = anyhow::Error; fn try_from(socket: Socket) -> Result { match socket { Socket::Automatic => Err(format_err!("Socket security is not configured")), Socket::Ssl => Ok(Self::Tls), Socket::Starttls => Ok(Self::Starttls), Socket::Plain => Ok(Self::Plain), } } } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct ConfiguredServerLoginParam { pub connection: ConnectionCandidate, /// Username. pub user: String, } impl fmt::Display for ConfiguredServerLoginParam { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}:{}", self.connection, &self.user)?; Ok(()) } } pub(crate) async fn prioritize_server_login_params( sql: &Sql, params: &[ConfiguredServerLoginParam], alpn: &str, ) -> Result> { let mut res: Vec<(Option, ConfiguredServerLoginParam)> = Vec::with_capacity(params.len()); for param in params { let timestamp = load_connection_timestamp( sql, alpn, ¶m.connection.host, param.connection.port, None, ) .await?; res.push((timestamp, param.clone())); } res.sort_by_key(|(ts, _param)| std::cmp::Reverse(*ts)); Ok(res.into_iter().map(|(_ts, param)| param).collect()) } /// Login parameters saved to the database /// after successful configuration. #[derive(Debug, Clone, PartialEq, Eq)] pub struct ConfiguredLoginParam { /// `From:` address that was used at the time of configuration. pub addr: String, pub imap: Vec, // Custom IMAP user. // // This overwrites autoconfig from the provider database // if non-empty. pub imap_user: String, pub imap_password: String, pub smtp: Vec, // Custom SMTP user. // // This overwrites autoconfig from the provider database // if non-empty. pub smtp_user: String, pub smtp_password: String, /// Proxy configuration. pub proxy_config: Option, pub provider: Option<&'static Provider>, /// TLS options: whether to allow invalid certificates and/or /// invalid hostnames pub certificate_checks: ConfiguredCertificateChecks, pub oauth2: bool, } impl fmt::Display for ConfiguredLoginParam { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let addr = &self.addr; let provider_id = match self.provider { Some(provider) => provider.id, None => "none", }; let certificate_checks = self.certificate_checks; write!(f, "{addr} imap:[")?; let mut first = true; for imap in &self.imap { if !first { write!(f, ", ")?; } write!(f, "{imap}")?; first = false; } write!(f, "] smtp:[")?; let mut first = true; for smtp in &self.smtp { if !first { write!(f, ", ")?; } write!(f, "{smtp}")?; first = false; } write!(f, "] provider:{provider_id} cert_{certificate_checks}")?; Ok(()) } } impl ConfiguredLoginParam { /// Load configured account settings from the database. /// /// Returns `None` if account is not configured. pub async fn load(context: &Context) -> Result> { if !context.get_config_bool(Config::Configured).await? { return Ok(None); } let addr = context .get_config(Config::ConfiguredAddr) .await? .unwrap_or_default() .trim() .to_string(); let certificate_checks: ConfiguredCertificateChecks = if let Some(certificate_checks) = context .get_config_parsed::(Config::ConfiguredImapCertificateChecks) .await? { num_traits::FromPrimitive::from_i32(certificate_checks) .context("Invalid configured_imap_certificate_checks value")? } else { // This is true for old accounts configured using C core // which did not check TLS certificates. ConfiguredCertificateChecks::OldAutomatic }; let send_pw = context .get_config(Config::ConfiguredSendPw) .await? .context("SMTP password is not configured")?; let mail_pw = context .get_config(Config::ConfiguredMailPw) .await? .context("IMAP password is not configured")?; let server_flags = context .get_config_parsed::(Config::ConfiguredServerFlags) .await? .unwrap_or_default(); let oauth2 = matches!(server_flags & DC_LP_AUTH_FLAGS, DC_LP_AUTH_OAUTH2); let provider = context.get_configured_provider().await?; let imap; let smtp; let mail_user = context .get_config(Config::ConfiguredMailUser) .await? .unwrap_or_default(); let send_user = context .get_config(Config::ConfiguredSendUser) .await? .unwrap_or_default(); if let Some(provider) = provider { let parsed_addr = EmailAddress::new(&addr).context("Bad email-address")?; let addr_localpart = parsed_addr.local; if provider.server.is_empty() { let servers = vec![ ServerParams { protocol: Protocol::Imap, hostname: context .get_config(Config::ConfiguredMailServer) .await? .unwrap_or_default(), port: context .get_config_parsed::(Config::ConfiguredMailPort) .await? .unwrap_or_default(), socket: context .get_config_parsed::(Config::ConfiguredMailSecurity) .await? .and_then(num_traits::FromPrimitive::from_i32) .unwrap_or_default(), username: mail_user.clone(), }, ServerParams { protocol: Protocol::Smtp, hostname: context .get_config(Config::ConfiguredSendServer) .await? .unwrap_or_default(), port: context .get_config_parsed::(Config::ConfiguredSendPort) .await? .unwrap_or_default(), socket: context .get_config_parsed::(Config::ConfiguredSendSecurity) .await? .and_then(num_traits::FromPrimitive::from_i32) .unwrap_or_default(), username: send_user.clone(), }, ]; let servers = expand_param_vector(servers, &addr, &parsed_addr.domain); imap = servers .iter() .filter_map(|params| { let Ok(security) = params.socket.try_into() else { return None; }; if params.protocol == Protocol::Imap { Some(ConfiguredServerLoginParam { connection: ConnectionCandidate { host: params.hostname.clone(), port: params.port, security, }, user: params.username.clone(), }) } else { None } }) .collect(); smtp = servers .iter() .filter_map(|params| { let Ok(security) = params.socket.try_into() else { return None; }; if params.protocol == Protocol::Smtp { Some(ConfiguredServerLoginParam { connection: ConnectionCandidate { host: params.hostname.clone(), port: params.port, security, }, user: params.username.clone(), }) } else { None } }) .collect(); } else { imap = provider .server .iter() .filter_map(|server| { if server.protocol != Protocol::Imap { return None; } let Ok(security) = server.socket.try_into() else { return None; }; Some(ConfiguredServerLoginParam { connection: ConnectionCandidate { host: server.hostname.to_string(), port: server.port, security, }, user: if !mail_user.is_empty() { mail_user.clone() } else { match server.username_pattern { UsernamePattern::Email => addr.to_string(), UsernamePattern::Emaillocalpart => addr_localpart.clone(), } }, }) }) .collect(); smtp = provider .server .iter() .filter_map(|server| { if server.protocol != Protocol::Smtp { return None; } let Ok(security) = server.socket.try_into() else { return None; }; Some(ConfiguredServerLoginParam { connection: ConnectionCandidate { host: server.hostname.to_string(), port: server.port, security, }, user: if !send_user.is_empty() { send_user.clone() } else { match server.username_pattern { UsernamePattern::Email => addr.to_string(), UsernamePattern::Emaillocalpart => addr_localpart.clone(), } }, }) }) .collect(); } } else if let (Some(configured_mail_servers), Some(configured_send_servers)) = ( context.get_config(Config::ConfiguredImapServers).await?, context.get_config(Config::ConfiguredSmtpServers).await?, ) { imap = serde_json::from_str(&configured_mail_servers) .context("Failed to parse configured IMAP servers")?; smtp = serde_json::from_str(&configured_send_servers) .context("Failed to parse configured SMTP servers")?; } else { // Load legacy settings storing a single IMAP and single SMTP server. let mail_server = context .get_config(Config::ConfiguredMailServer) .await? .unwrap_or_default(); let mail_port = context .get_config_parsed::(Config::ConfiguredMailPort) .await? .unwrap_or_default(); let mail_security: Socket = context .get_config_parsed::(Config::ConfiguredMailSecurity) .await? .and_then(num_traits::FromPrimitive::from_i32) .unwrap_or_default(); let send_server = context .get_config(Config::ConfiguredSendServer) .await? .context("SMTP server is not configured")?; let send_port = context .get_config_parsed::(Config::ConfiguredSendPort) .await? .unwrap_or_default(); let send_security: Socket = context .get_config_parsed::(Config::ConfiguredSendSecurity) .await? .and_then(num_traits::FromPrimitive::from_i32) .unwrap_or_default(); imap = vec![ConfiguredServerLoginParam { connection: ConnectionCandidate { host: mail_server, port: mail_port, security: mail_security.try_into()?, }, user: mail_user.clone(), }]; smtp = vec![ConfiguredServerLoginParam { connection: ConnectionCandidate { host: send_server, port: send_port, security: send_security.try_into()?, }, user: send_user.clone(), }]; } let proxy_config = ProxyConfig::load(context).await?; Ok(Some(ConfiguredLoginParam { addr, imap, imap_user: mail_user, imap_password: mail_pw, smtp, smtp_user: send_user, smtp_password: send_pw, certificate_checks, provider, proxy_config, oauth2, })) } /// Save this loginparam to the database. pub async fn save_as_configured_params(&self, context: &Context) -> Result<()> { context.set_primary_self_addr(&self.addr).await?; context .set_config( Config::ConfiguredImapServers, Some(&serde_json::to_string(&self.imap)?), ) .await?; context .set_config( Config::ConfiguredSmtpServers, Some(&serde_json::to_string(&self.smtp)?), ) .await?; context .set_config(Config::ConfiguredMailUser, Some(&self.imap_user)) .await?; context .set_config(Config::ConfiguredMailPw, Some(&self.imap_password)) .await?; context .set_config(Config::ConfiguredSendUser, Some(&self.smtp_user)) .await?; context .set_config(Config::ConfiguredSendPw, Some(&self.smtp_password)) .await?; context .set_config_u32( Config::ConfiguredImapCertificateChecks, self.certificate_checks as u32, ) .await?; context .set_config_u32( Config::ConfiguredSmtpCertificateChecks, self.certificate_checks as u32, ) .await?; // Remove legacy settings. context .set_config(Config::ConfiguredMailServer, None) .await?; context.set_config(Config::ConfiguredMailPort, None).await?; context .set_config(Config::ConfiguredMailSecurity, None) .await?; context .set_config(Config::ConfiguredSendServer, None) .await?; context.set_config(Config::ConfiguredSendPort, None).await?; context .set_config(Config::ConfiguredSendSecurity, None) .await?; let server_flags = match self.oauth2 { true => DC_LP_AUTH_OAUTH2, false => DC_LP_AUTH_NORMAL, }; context .set_config_u32(Config::ConfiguredServerFlags, server_flags as u32) .await?; context .set_config( Config::ConfiguredProvider, self.provider.map(|provider| provider.id), ) .await?; Ok(()) } pub fn strict_tls(&self) -> bool { let provider_strict_tls = self.provider.map(|provider| provider.opt.strict_tls); match self.certificate_checks { ConfiguredCertificateChecks::OldAutomatic => { provider_strict_tls.unwrap_or(self.proxy_config.is_some()) } ConfiguredCertificateChecks::Automatic => provider_strict_tls.unwrap_or(true), ConfiguredCertificateChecks::Strict => true, ConfiguredCertificateChecks::AcceptInvalidCertificates | ConfiguredCertificateChecks::AcceptInvalidCertificates2 => false, } } } #[cfg(test)] mod tests { use super::*; use crate::provider::get_provider_by_id; use crate::test_utils::TestContext; #[test] fn test_certificate_checks_display() { use std::string::ToString; assert_eq!( "accept_invalid_certificates".to_string(), EnteredCertificateChecks::AcceptInvalidCertificates.to_string() ); assert_eq!( "accept_invalid_certificates".to_string(), ConfiguredCertificateChecks::AcceptInvalidCertificates.to_string() ); } #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_entered_login_param() -> Result<()> { let t = &TestContext::new().await; t.set_config(Config::Addr, Some("alice@example.org")) .await?; t.set_config(Config::MailPw, Some("foobarbaz")).await?; let param = EnteredLoginParam::load(t).await?; assert_eq!(param.addr, "alice@example.org"); assert_eq!( param.certificate_checks, EnteredCertificateChecks::Automatic ); t.set_config(Config::ImapCertificateChecks, Some("1")) .await?; let param = EnteredLoginParam::load(t).await?; assert_eq!(param.certificate_checks, EnteredCertificateChecks::Strict); // Fail to load invalid settings, but do not panic. t.set_config(Config::ImapCertificateChecks, Some("999")) .await?; assert!(EnteredLoginParam::load(t).await.is_err()); Ok(()) } #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_save_load_login_param() -> Result<()> { let t = TestContext::new().await; let param = ConfiguredLoginParam { addr: "alice@example.org".to_string(), imap: vec![ConfiguredServerLoginParam { connection: ConnectionCandidate { host: "imap.example.com".to_string(), port: 123, security: ConnectionSecurity::Starttls, }, user: "alice".to_string(), }], imap_user: "".to_string(), imap_password: "foo".to_string(), smtp: vec![ConfiguredServerLoginParam { connection: ConnectionCandidate { host: "smtp.example.com".to_string(), port: 456, security: ConnectionSecurity::Tls, }, user: "alice@example.org".to_string(), }], smtp_user: "".to_string(), smtp_password: "bar".to_string(), // proxy_config is not saved by `save_to_database`, using default value proxy_config: None, provider: None, certificate_checks: ConfiguredCertificateChecks::Strict, oauth2: false, }; param.save_as_configured_params(&t).await?; assert_eq!( t.get_config(Config::ConfiguredImapServers).await?.unwrap(), r#"[{"connection":{"host":"imap.example.com","port":123,"security":"Starttls"},"user":"alice"}]"# ); t.set_config(Config::Configured, Some("1")).await?; let loaded = ConfiguredLoginParam::load(&t).await?.unwrap(); assert_eq!(param, loaded); // Test that we don't panic on unknown ConfiguredImapCertificateChecks values. t.set_config(Config::ConfiguredImapCertificateChecks, Some("999")) .await?; assert!(ConfiguredLoginParam::load(&t).await.is_err()); Ok(()) } #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_posteo_alias() -> Result<()> { let t = TestContext::new().await; let user = "alice@posteo.de"; // Alice has old config with "alice@posteo.at" address // and "alice@posteo.de" username. t.set_config(Config::Configured, Some("1")).await?; t.set_config(Config::ConfiguredProvider, Some("posteo")) .await?; t.set_config(Config::ConfiguredAddr, Some("alice@posteo.at")) .await?; t.set_config(Config::ConfiguredMailServer, Some("posteo.de")) .await?; t.set_config(Config::ConfiguredMailPort, Some("993")) .await?; t.set_config(Config::ConfiguredMailSecurity, Some("1")) .await?; // TLS t.set_config(Config::ConfiguredMailUser, Some(user)).await?; t.set_config(Config::ConfiguredMailPw, Some("foobarbaz")) .await?; t.set_config(Config::ConfiguredImapCertificateChecks, Some("1")) .await?; // Strict t.set_config(Config::ConfiguredSendServer, Some("posteo.de")) .await?; t.set_config(Config::ConfiguredSendPort, Some("465")) .await?; t.set_config(Config::ConfiguredSendSecurity, Some("1")) .await?; // TLS t.set_config(Config::ConfiguredSendUser, Some(user)).await?; t.set_config(Config::ConfiguredSendPw, Some("foobarbaz")) .await?; t.set_config(Config::ConfiguredSmtpCertificateChecks, Some("1")) .await?; // Strict t.set_config(Config::ConfiguredServerFlags, Some("0")) .await?; let param = ConfiguredLoginParam { addr: "alice@posteo.at".to_string(), imap: vec![ ConfiguredServerLoginParam { connection: ConnectionCandidate { host: "posteo.de".to_string(), port: 993, security: ConnectionSecurity::Tls, }, user: user.to_string(), }, ConfiguredServerLoginParam { connection: ConnectionCandidate { host: "posteo.de".to_string(), port: 143, security: ConnectionSecurity::Starttls, }, user: user.to_string(), }, ], imap_user: "alice@posteo.de".to_string(), imap_password: "foobarbaz".to_string(), smtp: vec![ ConfiguredServerLoginParam { connection: ConnectionCandidate { host: "posteo.de".to_string(), port: 465, security: ConnectionSecurity::Tls, }, user: user.to_string(), }, ConfiguredServerLoginParam { connection: ConnectionCandidate { host: "posteo.de".to_string(), port: 587, security: ConnectionSecurity::Starttls, }, user: user.to_string(), }, ], smtp_user: "alice@posteo.de".to_string(), smtp_password: "foobarbaz".to_string(), proxy_config: None, provider: get_provider_by_id("posteo"), certificate_checks: ConfiguredCertificateChecks::Strict, oauth2: false, }; let loaded = ConfiguredLoginParam::load(&t).await?.unwrap(); assert_eq!(loaded, param); Ok(()) } #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_empty_server_list() -> Result<()> { // Find a provider that does not have server list set. // // There is at least one such provider in the provider database. let (domain, provider) = crate::provider::data::PROVIDER_DATA .iter() .find(|(_domain, provider)| provider.server.is_empty()) .unwrap(); let t = TestContext::new().await; let addr = format!("alice@{domain}"); t.set_config(Config::Configured, Some("1")).await?; t.set_config(Config::ConfiguredProvider, Some(provider.id)) .await?; t.set_config(Config::ConfiguredAddr, Some(&addr)).await?; t.set_config(Config::ConfiguredMailPw, Some("foobarbaz")) .await?; t.set_config(Config::ConfiguredImapCertificateChecks, Some("1")) .await?; // Strict t.set_config(Config::ConfiguredSendPw, Some("foobarbaz")) .await?; t.set_config(Config::ConfiguredSmtpCertificateChecks, Some("1")) .await?; // Strict t.set_config(Config::ConfiguredServerFlags, Some("0")) .await?; let loaded = ConfiguredLoginParam::load(&t).await?.unwrap(); assert_eq!(loaded.provider, Some(*provider)); assert_eq!(loaded.imap.is_empty(), false); assert_eq!(loaded.smtp.is_empty(), false); Ok(()) } }