mirror of
https://github.com/chatmail/core.git
synced 2026-04-19 14:36:29 +03:00
feat: automatic reconfiguration
This commit is contained in:
@@ -2,276 +2,205 @@
|
||||
|
||||
use std::fmt;
|
||||
|
||||
use anyhow::{ensure, Result};
|
||||
use anyhow::{format_err, Context as _, Result};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::config::Config;
|
||||
use crate::constants::{DC_LP_AUTH_FLAGS, DC_LP_AUTH_NORMAL, DC_LP_AUTH_OAUTH2};
|
||||
use crate::context::Context;
|
||||
use crate::provider::Socket;
|
||||
use crate::provider::{get_provider_by_id, Provider};
|
||||
use crate::net::load_connection_timestamp;
|
||||
use crate::provider::{get_provider_by_id, Protocol, Provider, Socket, UsernamePattern};
|
||||
use crate::socks::Socks5Config;
|
||||
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 CertificateChecks {
|
||||
/// Same as AcceptInvalidCertificates if stored in the database
|
||||
/// as `configured_{imap,smtp}_certificate_checks`.
|
||||
///
|
||||
/// Previous Delta Chat versions stored this in `configured_*`
|
||||
/// if Automatic configuration
|
||||
/// was selected, configuration with strict TLS checks failed
|
||||
/// and configuration without strict TLS checks succeeded.
|
||||
///
|
||||
/// Currently Delta Chat stores only
|
||||
/// `Strict` or `AcceptInvalidCertificates` variants
|
||||
/// in `configured_*` settings.
|
||||
///
|
||||
/// `Automatic` in `{imap,smtp}_certificate_checks`
|
||||
/// means that provider database setting should be taken.
|
||||
pub enum EnteredCertificateChecks {
|
||||
/// `Automatic` means that provider database setting should be taken.
|
||||
/// If there is no provider database setting for certificate checks,
|
||||
/// `Automatic` is the same as `Strict`.
|
||||
/// check certificates strictly.
|
||||
#[default]
|
||||
Automatic = 0,
|
||||
|
||||
/// Ensure that TLS certificate is valid for the server hostname.
|
||||
Strict = 1,
|
||||
|
||||
/// Same as AcceptInvalidCertificates
|
||||
/// Previously known as AcceptInvalidHostnames, now deprecated.
|
||||
AcceptInvalidCertificates2 = 2,
|
||||
/// Accept certificates that are expired, self-signed
|
||||
/// or otherwise not valid for the server hostname.
|
||||
AcceptInvalidCertificates = 2,
|
||||
|
||||
AcceptInvalidCertificates = 3,
|
||||
/// 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(Default, Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ServerLoginParam {
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct EnteredServerLoginParam {
|
||||
/// Server hostname or IP address.
|
||||
pub server: String,
|
||||
pub user: String,
|
||||
pub password: String,
|
||||
|
||||
/// Server port.
|
||||
///
|
||||
/// 0 if not specified.
|
||||
pub port: u16,
|
||||
|
||||
/// Socket security.
|
||||
pub security: Socket,
|
||||
pub oauth2: bool,
|
||||
|
||||
/// Username.
|
||||
///
|
||||
/// Empty string if not specified.
|
||||
pub user: String,
|
||||
|
||||
/// Password.
|
||||
pub password: String,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, PartialEq, Eq)]
|
||||
pub struct LoginParam {
|
||||
/// Login parameters entered by the user.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct EnteredLoginParam {
|
||||
/// Email address.
|
||||
pub addr: String,
|
||||
pub imap: ServerLoginParam,
|
||||
pub smtp: ServerLoginParam,
|
||||
pub provider: Option<&'static Provider>,
|
||||
pub socks5_config: Option<Socks5Config>,
|
||||
|
||||
/// IMAP settings.
|
||||
pub imap: EnteredServerLoginParam,
|
||||
|
||||
/// SMTP settings.
|
||||
pub smtp: EnteredServerLoginParam,
|
||||
|
||||
/// TLS options: whether to allow invalid certificates and/or
|
||||
/// invalid hostnames
|
||||
pub certificate_checks: CertificateChecks,
|
||||
pub certificate_checks: EnteredCertificateChecks,
|
||||
|
||||
pub socks5_config: Option<Socks5Config>,
|
||||
|
||||
pub oauth2: bool,
|
||||
}
|
||||
|
||||
impl LoginParam {
|
||||
/// Load entered (candidate) account settings
|
||||
pub async fn load_candidate_params(context: &Context) -> Result<Self> {
|
||||
let mut param = Self::load_candidate_params_unchecked(context).await?;
|
||||
ensure!(!param.addr.is_empty(), "Missing email address.");
|
||||
|
||||
// Only check for IMAP password, SMTP password is an "advanced" setting.
|
||||
ensure!(!param.imap.password.is_empty(), "Missing (IMAP) password.");
|
||||
if param.smtp.password.is_empty() {
|
||||
param.smtp.password.clone_from(¶m.imap.password)
|
||||
}
|
||||
Ok(param)
|
||||
}
|
||||
|
||||
/// Load entered (candidate) account settings without validation.
|
||||
///
|
||||
/// This will result in a potentially invalid [`LoginParam`] struct as the values are
|
||||
/// not validated. Only use this if you want to show this directly to the user e.g. in
|
||||
/// [`Context::get_info`].
|
||||
pub async fn load_candidate_params_unchecked(context: &Context) -> Result<Self> {
|
||||
LoginParam::from_database(context, "").await
|
||||
}
|
||||
|
||||
/// Load configured (working) account settings
|
||||
pub async fn load_configured_params(context: &Context) -> Result<Self> {
|
||||
LoginParam::from_database(context, "configured_").await
|
||||
}
|
||||
|
||||
/// Read the login parameters from the database.
|
||||
async fn from_database(context: &Context, prefix: &str) -> Result<Self> {
|
||||
impl EnteredLoginParam {
|
||||
/// Loads entered account settings.
|
||||
pub async fn load(context: &Context) -> Result<Self> {
|
||||
let sql = &context.sql;
|
||||
|
||||
let key = &format!("{prefix}addr");
|
||||
let addr = sql
|
||||
.get_raw_config(key)
|
||||
.get_raw_config("addr")
|
||||
.await?
|
||||
.unwrap_or_default()
|
||||
.trim()
|
||||
.to_string();
|
||||
|
||||
let key = &format!("{prefix}mail_server");
|
||||
let mail_server = sql.get_raw_config(key).await?.unwrap_or_default();
|
||||
|
||||
let key = &format!("{prefix}mail_port");
|
||||
let mail_port = sql.get_raw_config_int(key).await?.unwrap_or_default();
|
||||
|
||||
let key = &format!("{prefix}mail_user");
|
||||
let mail_user = sql.get_raw_config(key).await?.unwrap_or_default();
|
||||
|
||||
let key = &format!("{prefix}mail_pw");
|
||||
let mail_pw = sql.get_raw_config(key).await?.unwrap_or_default();
|
||||
|
||||
let key = &format!("{prefix}mail_security");
|
||||
let mail_server = sql.get_raw_config("mail_server").await?.unwrap_or_default();
|
||||
let mail_port = sql
|
||||
.get_raw_config_int("mail_port")
|
||||
.await?
|
||||
.unwrap_or_default();
|
||||
let mail_security = sql
|
||||
.get_raw_config_int(key)
|
||||
.get_raw_config_int("mail_security")
|
||||
.await?
|
||||
.and_then(num_traits::FromPrimitive::from_i32)
|
||||
.unwrap_or_default();
|
||||
let mail_user = sql.get_raw_config("mail_user").await?.unwrap_or_default();
|
||||
let mail_pw = sql.get_raw_config("mail_pw").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 key = &format!("{prefix}imap_certificate_checks");
|
||||
let certificate_checks =
|
||||
if let Some(certificate_checks) = sql.get_raw_config_int(key).await? {
|
||||
num_traits::FromPrimitive::from_i32(certificate_checks).unwrap()
|
||||
} else {
|
||||
Default::default()
|
||||
};
|
||||
let certificate_checks = if let Some(certificate_checks) =
|
||||
sql.get_raw_config_int("imap_ceritifacte_checks").await?
|
||||
{
|
||||
num_traits::FromPrimitive::from_i32(certificate_checks).unwrap()
|
||||
} else {
|
||||
Default::default()
|
||||
};
|
||||
|
||||
let key = &format!("{prefix}send_server");
|
||||
let send_server = sql.get_raw_config(key).await?.unwrap_or_default();
|
||||
|
||||
let key = &format!("{prefix}send_port");
|
||||
let send_port = sql.get_raw_config_int(key).await?.unwrap_or_default();
|
||||
|
||||
let key = &format!("{prefix}send_user");
|
||||
let send_user = sql.get_raw_config(key).await?.unwrap_or_default();
|
||||
|
||||
let key = &format!("{prefix}send_pw");
|
||||
let send_pw = sql.get_raw_config(key).await?.unwrap_or_default();
|
||||
|
||||
let key = &format!("{prefix}send_security");
|
||||
let send_server = sql.get_raw_config("send_server").await?.unwrap_or_default();
|
||||
let send_port = sql
|
||||
.get_raw_config_int("send_port")
|
||||
.await?
|
||||
.unwrap_or_default();
|
||||
let send_security = sql
|
||||
.get_raw_config_int(key)
|
||||
.get_raw_config_int("send_security")
|
||||
.await?
|
||||
.and_then(num_traits::FromPrimitive::from_i32)
|
||||
.unwrap_or_default();
|
||||
let send_user = sql.get_raw_config("send_user").await?.unwrap_or_default();
|
||||
let send_pw = sql.get_raw_config("send_pw").await?.unwrap_or_default();
|
||||
|
||||
let key = &format!("{prefix}server_flags");
|
||||
let server_flags = sql.get_raw_config_int(key).await?.unwrap_or_default();
|
||||
let oauth2 = matches!(server_flags & DC_LP_AUTH_FLAGS, DC_LP_AUTH_OAUTH2);
|
||||
|
||||
let key = &format!("{prefix}provider");
|
||||
let provider = sql
|
||||
.get_raw_config(key)
|
||||
let server_flags = sql
|
||||
.get_raw_config_int("server_flags")
|
||||
.await?
|
||||
.and_then(|provider_id| get_provider_by_id(&provider_id));
|
||||
.unwrap_or_default();
|
||||
let oauth2 = matches!(server_flags & DC_LP_AUTH_FLAGS, DC_LP_AUTH_OAUTH2);
|
||||
|
||||
let socks5_config = Socks5Config::from_database(&context.sql).await?;
|
||||
|
||||
Ok(LoginParam {
|
||||
Ok(EnteredLoginParam {
|
||||
addr,
|
||||
imap: ServerLoginParam {
|
||||
imap: EnteredServerLoginParam {
|
||||
server: mail_server,
|
||||
user: mail_user,
|
||||
password: mail_pw,
|
||||
port: mail_port as u16,
|
||||
security: mail_security,
|
||||
oauth2,
|
||||
user: mail_user,
|
||||
password: mail_pw,
|
||||
},
|
||||
smtp: ServerLoginParam {
|
||||
smtp: EnteredServerLoginParam {
|
||||
server: send_server,
|
||||
user: send_user,
|
||||
password: send_pw,
|
||||
port: send_port as u16,
|
||||
security: send_security,
|
||||
oauth2,
|
||||
user: send_user,
|
||||
password: send_pw,
|
||||
},
|
||||
certificate_checks,
|
||||
provider,
|
||||
socks5_config,
|
||||
oauth2,
|
||||
})
|
||||
}
|
||||
|
||||
/// Save this loginparam to the database.
|
||||
pub async fn save_as_configured_params(&self, context: &Context) -> Result<()> {
|
||||
let prefix = "configured_";
|
||||
let sql = &context.sql;
|
||||
|
||||
context.set_primary_self_addr(&self.addr).await?;
|
||||
|
||||
let key = &format!("{prefix}mail_server");
|
||||
sql.set_raw_config(key, Some(&self.imap.server)).await?;
|
||||
|
||||
let key = &format!("{prefix}mail_port");
|
||||
sql.set_raw_config_int(key, i32::from(self.imap.port))
|
||||
.await?;
|
||||
|
||||
let key = &format!("{prefix}mail_user");
|
||||
sql.set_raw_config(key, Some(&self.imap.user)).await?;
|
||||
|
||||
let key = &format!("{prefix}mail_pw");
|
||||
sql.set_raw_config(key, Some(&self.imap.password)).await?;
|
||||
|
||||
let key = &format!("{prefix}mail_security");
|
||||
sql.set_raw_config_int(key, self.imap.security as i32)
|
||||
.await?;
|
||||
|
||||
let key = &format!("{prefix}imap_certificate_checks");
|
||||
sql.set_raw_config_int(key, self.certificate_checks as i32)
|
||||
.await?;
|
||||
|
||||
let key = &format!("{prefix}send_server");
|
||||
sql.set_raw_config(key, Some(&self.smtp.server)).await?;
|
||||
|
||||
let key = &format!("{prefix}send_port");
|
||||
sql.set_raw_config_int(key, i32::from(self.smtp.port))
|
||||
.await?;
|
||||
|
||||
let key = &format!("{prefix}send_user");
|
||||
sql.set_raw_config(key, Some(&self.smtp.user)).await?;
|
||||
|
||||
let key = &format!("{prefix}send_pw");
|
||||
sql.set_raw_config(key, Some(&self.smtp.password)).await?;
|
||||
|
||||
let key = &format!("{prefix}send_security");
|
||||
sql.set_raw_config_int(key, self.smtp.security as i32)
|
||||
.await?;
|
||||
|
||||
// This is only saved for compatibility reasons, but never loaded.
|
||||
let key = &format!("{prefix}smtp_certificate_checks");
|
||||
sql.set_raw_config_int(key, self.certificate_checks as i32)
|
||||
.await?;
|
||||
|
||||
// The OAuth2 flag is either set for both IMAP and SMTP or not at all.
|
||||
let key = &format!("{prefix}server_flags");
|
||||
let server_flags = match self.imap.oauth2 {
|
||||
true => DC_LP_AUTH_OAUTH2,
|
||||
false => DC_LP_AUTH_NORMAL,
|
||||
};
|
||||
sql.set_raw_config_int(key, server_flags).await?;
|
||||
|
||||
let key = &format!("{prefix}provider");
|
||||
sql.set_raw_config(key, self.provider.map(|provider| provider.id))
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn strict_tls(&self) -> bool {
|
||||
let user_strict_tls = match self.certificate_checks {
|
||||
CertificateChecks::Automatic => None,
|
||||
CertificateChecks::Strict => Some(true),
|
||||
CertificateChecks::AcceptInvalidCertificates
|
||||
| CertificateChecks::AcceptInvalidCertificates2 => Some(false),
|
||||
};
|
||||
let provider_strict_tls = self.provider.map(|provider| provider.opt.strict_tls);
|
||||
user_strict_tls
|
||||
.or(provider_strict_tls)
|
||||
.unwrap_or(self.socks5_config.is_some())
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for LoginParam {
|
||||
impl fmt::Display for EnteredLoginParam {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let unset = "0";
|
||||
let pw = "***";
|
||||
@@ -289,11 +218,7 @@ impl fmt::Display for LoginParam {
|
||||
unset_empty(&self.imap.server),
|
||||
self.imap.port,
|
||||
self.imap.security,
|
||||
if self.imap.oauth2 {
|
||||
"OAUTH2"
|
||||
} else {
|
||||
"AUTH_NORMAL"
|
||||
},
|
||||
if self.oauth2 { "OAUTH2" } else { "AUTH_NORMAL" },
|
||||
unset_empty(&self.smtp.user),
|
||||
if !self.smtp.password.is_empty() {
|
||||
pw
|
||||
@@ -303,11 +228,7 @@ impl fmt::Display for LoginParam {
|
||||
unset_empty(&self.smtp.server),
|
||||
self.smtp.port,
|
||||
self.smtp.security,
|
||||
if self.smtp.oauth2 {
|
||||
"OAUTH2"
|
||||
} else {
|
||||
"AUTH_NORMAL"
|
||||
},
|
||||
if self.oauth2 { "OAUTH2" } else { "AUTH_NORMAL" },
|
||||
self.certificate_checks
|
||||
)
|
||||
}
|
||||
@@ -321,6 +242,428 @@ fn unset_empty(s: &str) -> &str {
|
||||
}
|
||||
}
|
||||
|
||||
#[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<Socket> for ConnectionSecurity {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
fn try_from(socket: Socket) -> Result<Self> {
|
||||
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<Vec<ConfiguredServerLoginParam>> {
|
||||
let mut res: Vec<(Option<i64>, 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<ConfiguredServerLoginParam>,
|
||||
|
||||
pub imap_password: String,
|
||||
|
||||
pub smtp: Vec<ConfiguredServerLoginParam>,
|
||||
|
||||
pub smtp_password: String,
|
||||
|
||||
pub socks5_config: Option<Socks5Config>,
|
||||
|
||||
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<Option<Self>> {
|
||||
let sql = &context.sql;
|
||||
|
||||
if !context.get_config_bool(Config::Configured).await? {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let addr = sql
|
||||
.get_raw_config("configured_addr")
|
||||
.await?
|
||||
.unwrap_or_default()
|
||||
.trim()
|
||||
.to_string();
|
||||
|
||||
let certificate_checks: ConfiguredCertificateChecks = if let Some(certificate_checks) = sql
|
||||
.get_raw_config_int("configured_imap_certificate_checks")
|
||||
.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 = sql
|
||||
.get_raw_config_int("configured_server_flags")
|
||||
.await?
|
||||
.unwrap_or_default();
|
||||
let oauth2 = matches!(server_flags & DC_LP_AUTH_FLAGS, DC_LP_AUTH_OAUTH2);
|
||||
|
||||
let provider = context
|
||||
.get_config(Config::ConfiguredProvider)
|
||||
.await?
|
||||
.and_then(|provider_id| get_provider_by_id(&provider_id));
|
||||
|
||||
let imap;
|
||||
let smtp;
|
||||
|
||||
if let Some(provider) = provider {
|
||||
let addr_localpart = if let Some(at) = addr.find('@') {
|
||||
addr.split_at(at).0.to_string()
|
||||
} else {
|
||||
addr.to_string()
|
||||
};
|
||||
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: 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: 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 = sql
|
||||
.get_raw_config("configured_mail_server")
|
||||
.await?
|
||||
.unwrap_or_default();
|
||||
let mail_port = sql
|
||||
.get_raw_config_int("configured_mail_port")
|
||||
.await?
|
||||
.unwrap_or_default();
|
||||
|
||||
let mail_user = sql
|
||||
.get_raw_config("configured_mail_user")
|
||||
.await?
|
||||
.unwrap_or_default();
|
||||
let mail_security: Socket = sql
|
||||
.get_raw_config_int("configured_mail_security")
|
||||
.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 = sql
|
||||
.get_raw_config_int("configured_send_port")
|
||||
.await?
|
||||
.unwrap_or_default();
|
||||
let send_user = sql
|
||||
.get_raw_config("configured_send_user")
|
||||
.await?
|
||||
.unwrap_or_default();
|
||||
let send_security: Socket = sql
|
||||
.get_raw_config_int("configured_send_security")
|
||||
.await?
|
||||
.and_then(num_traits::FromPrimitive::from_i32)
|
||||
.unwrap_or_default();
|
||||
|
||||
imap = vec![ConfiguredServerLoginParam {
|
||||
connection: ConnectionCandidate {
|
||||
host: mail_server,
|
||||
port: mail_port as u16,
|
||||
security: mail_security.try_into()?,
|
||||
},
|
||||
user: mail_user,
|
||||
}];
|
||||
smtp = vec![ConfiguredServerLoginParam {
|
||||
connection: ConnectionCandidate {
|
||||
host: send_server,
|
||||
port: send_port as u16,
|
||||
security: send_security.try_into()?,
|
||||
},
|
||||
user: send_user,
|
||||
}];
|
||||
}
|
||||
|
||||
let socks5_config = Socks5Config::from_database(&context.sql).await?;
|
||||
|
||||
Ok(Some(ConfiguredLoginParam {
|
||||
addr,
|
||||
imap,
|
||||
imap_password: mail_pw,
|
||||
smtp,
|
||||
smtp_password: send_pw,
|
||||
certificate_checks,
|
||||
provider,
|
||||
socks5_config,
|
||||
oauth2,
|
||||
}))
|
||||
}
|
||||
|
||||
/// Save this loginparam to the database.
|
||||
pub async fn save_as_configured_params(&self, context: &Context) -> Result<()> {
|
||||
let sql = &context.sql;
|
||||
|
||||
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::ConfiguredMailPw, Some(&self.imap_password))
|
||||
.await?;
|
||||
context
|
||||
.set_config(Config::ConfiguredSendPw, Some(&self.smtp_password))
|
||||
.await?;
|
||||
|
||||
sql.set_raw_config_int(
|
||||
"configured_imap_certificate_checks",
|
||||
self.certificate_checks as i32,
|
||||
)
|
||||
.await?;
|
||||
sql.set_raw_config_int(
|
||||
"configured_smtp_certificate_checks",
|
||||
self.certificate_checks as i32,
|
||||
)
|
||||
.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::ConfiguredMailUser, None).await?;
|
||||
context
|
||||
.set_config(Config::ConfiguredSendServer, None)
|
||||
.await?;
|
||||
context.set_config(Config::ConfiguredSendPort, None).await?;
|
||||
context
|
||||
.set_config(Config::ConfiguredSendSecurity, None)
|
||||
.await?;
|
||||
context.set_config(Config::ConfiguredSendUser, None).await?;
|
||||
|
||||
let server_flags = match self.oauth2 {
|
||||
true => DC_LP_AUTH_OAUTH2,
|
||||
false => DC_LP_AUTH_NORMAL,
|
||||
};
|
||||
sql.set_raw_config_int("configured_server_flags", server_flags)
|
||||
.await?;
|
||||
|
||||
sql.set_raw_config(
|
||||
"configured_provider",
|
||||
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.socks5_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::*;
|
||||
@@ -332,7 +675,12 @@ mod tests {
|
||||
|
||||
assert_eq!(
|
||||
"accept_invalid_certificates".to_string(),
|
||||
CertificateChecks::AcceptInvalidCertificates.to_string()
|
||||
EnteredCertificateChecks::AcceptInvalidCertificates.to_string()
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
"accept_invalid_certificates".to_string(),
|
||||
ConfiguredCertificateChecks::AcceptInvalidCertificates.to_string()
|
||||
);
|
||||
}
|
||||
|
||||
@@ -340,42 +688,42 @@ mod tests {
|
||||
async fn test_save_load_login_param() -> Result<()> {
|
||||
let t = TestContext::new().await;
|
||||
|
||||
let param = LoginParam {
|
||||
let param = ConfiguredLoginParam {
|
||||
addr: "alice@example.org".to_string(),
|
||||
imap: ServerLoginParam {
|
||||
server: "imap.example.com".to_string(),
|
||||
imap: vec![ConfiguredServerLoginParam {
|
||||
connection: ConnectionCandidate {
|
||||
host: "imap.example.com".to_string(),
|
||||
port: 123,
|
||||
security: ConnectionSecurity::Starttls,
|
||||
},
|
||||
user: "alice".to_string(),
|
||||
password: "foo".to_string(),
|
||||
port: 123,
|
||||
security: Socket::Starttls,
|
||||
oauth2: false,
|
||||
},
|
||||
smtp: ServerLoginParam {
|
||||
server: "smtp.example.com".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(),
|
||||
password: "bar".to_string(),
|
||||
port: 456,
|
||||
security: Socket::Ssl,
|
||||
oauth2: false,
|
||||
},
|
||||
provider: get_provider_by_id("example.com"),
|
||||
}],
|
||||
smtp_password: "bar".to_string(),
|
||||
// socks5_config is not saved by `save_to_database`, using default value
|
||||
socks5_config: None,
|
||||
certificate_checks: CertificateChecks::Strict,
|
||||
};
|
||||
|
||||
param.save_as_configured_params(&t).await?;
|
||||
let loaded = LoginParam::load_configured_params(&t).await?;
|
||||
assert_eq!(param, loaded);
|
||||
|
||||
// Remove provider.
|
||||
let param = LoginParam {
|
||||
provider: None,
|
||||
..param
|
||||
certificate_checks: ConfiguredCertificateChecks::Strict,
|
||||
oauth2: false,
|
||||
};
|
||||
|
||||
param.save_as_configured_params(&t).await?;
|
||||
let loaded = LoginParam::load_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);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user