mirror of
https://github.com/chatmail/core.git
synced 2026-04-18 22:16:30 +03:00
* Remove sql::error submodule Use anyhow errors instead. * Remove explicit checks for open SQL connection An error will be thrown anyway during attempt to execute query. * Don't use `with_conn()` and remove it * Remove unused `with_conn_async` * Resultify markseen_msgs
346 lines
11 KiB
Rust
346 lines
11 KiB
Rust
//! # Login parameters
|
|
|
|
use std::borrow::Cow;
|
|
use std::fmt;
|
|
|
|
use crate::provider::{get_provider_by_id, Provider};
|
|
use crate::{context::Context, provider::Socket};
|
|
use anyhow::Result;
|
|
|
|
#[derive(Copy, Clone, Debug, Display, FromPrimitive, PartialEq, Eq)]
|
|
#[repr(u32)]
|
|
#[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
|
|
/// Previously known as AcceptInvalidHostnames, now deprecated.
|
|
AcceptInvalidCertificates2 = 2,
|
|
|
|
AcceptInvalidCertificates = 3,
|
|
}
|
|
|
|
impl Default for CertificateChecks {
|
|
fn default() -> Self {
|
|
Self::Automatic
|
|
}
|
|
}
|
|
|
|
/// Login parameters for a single server, either IMAP or SMTP
|
|
#[derive(Default, Debug, Clone, PartialEq)]
|
|
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, PartialEq)]
|
|
pub struct LoginParam {
|
|
pub addr: String,
|
|
pub imap: ServerLoginParam,
|
|
pub smtp: ServerLoginParam,
|
|
pub server_flags: i32,
|
|
pub provider: Option<&'static Provider>,
|
|
}
|
|
|
|
impl LoginParam {
|
|
/// Read the login parameters from the database.
|
|
pub async fn from_database(context: &Context, prefix: impl AsRef<str>) -> Result<Self> {
|
|
let prefix = prefix.as_ref();
|
|
let sql = &context.sql;
|
|
|
|
let key = format!("{}addr", prefix);
|
|
let addr = sql
|
|
.get_raw_config(key)
|
|
.await?
|
|
.unwrap_or_default()
|
|
.trim()
|
|
.to_string();
|
|
|
|
let key = format!("{}mail_server", prefix);
|
|
let mail_server = sql.get_raw_config(key).await?.unwrap_or_default();
|
|
|
|
let key = format!("{}mail_port", prefix);
|
|
let mail_port = sql.get_raw_config_int(key).await?.unwrap_or_default();
|
|
|
|
let key = format!("{}mail_user", prefix);
|
|
let mail_user = sql.get_raw_config(key).await?.unwrap_or_default();
|
|
|
|
let key = format!("{}mail_pw", prefix);
|
|
let mail_pw = sql.get_raw_config(key).await?.unwrap_or_default();
|
|
|
|
let key = format!("{}mail_security", prefix);
|
|
let mail_security = sql
|
|
.get_raw_config_int(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(key).await? {
|
|
num_traits::FromPrimitive::from_i32(certificate_checks).unwrap()
|
|
} else {
|
|
Default::default()
|
|
};
|
|
|
|
let key = format!("{}send_server", prefix);
|
|
let send_server = sql.get_raw_config(key).await?.unwrap_or_default();
|
|
|
|
let key = format!("{}send_port", prefix);
|
|
let send_port = sql.get_raw_config_int(key).await?.unwrap_or_default();
|
|
|
|
let key = format!("{}send_user", prefix);
|
|
let send_user = sql.get_raw_config(key).await?.unwrap_or_default();
|
|
|
|
let key = format!("{}send_pw", prefix);
|
|
let send_pw = sql.get_raw_config(key).await?.unwrap_or_default();
|
|
|
|
let key = format!("{}send_security", prefix);
|
|
let send_security = sql
|
|
.get_raw_config_int(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(key).await? {
|
|
num_traits::FromPrimitive::from_i32(certificate_checks).unwrap()
|
|
} else {
|
|
Default::default()
|
|
};
|
|
|
|
let key = format!("{}server_flags", prefix);
|
|
let server_flags = sql.get_raw_config_int(key).await?.unwrap_or_default();
|
|
|
|
let key = format!("{}provider", prefix);
|
|
let provider = sql
|
|
.get_raw_config(key)
|
|
.await?
|
|
.and_then(|provider_id| get_provider_by_id(&provider_id));
|
|
|
|
Ok(LoginParam {
|
|
addr,
|
|
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,
|
|
},
|
|
provider,
|
|
server_flags,
|
|
})
|
|
}
|
|
|
|
/// Save this loginparam to the database.
|
|
pub async fn save_to_database(&self, context: &Context, prefix: impl AsRef<str>) -> Result<()> {
|
|
let prefix = prefix.as_ref();
|
|
let sql = &context.sql;
|
|
|
|
let key = format!("{}addr", prefix);
|
|
sql.set_raw_config(key, Some(&self.addr)).await?;
|
|
|
|
let key = format!("{}mail_server", prefix);
|
|
sql.set_raw_config(key, Some(&self.imap.server)).await?;
|
|
|
|
let key = format!("{}mail_port", prefix);
|
|
sql.set_raw_config_int(key, self.imap.port as i32).await?;
|
|
|
|
let key = format!("{}mail_user", prefix);
|
|
sql.set_raw_config(key, Some(&self.imap.user)).await?;
|
|
|
|
let key = format!("{}mail_pw", prefix);
|
|
sql.set_raw_config(key, Some(&self.imap.password)).await?;
|
|
|
|
let key = format!("{}mail_security", prefix);
|
|
sql.set_raw_config_int(key, self.imap.security as i32)
|
|
.await?;
|
|
|
|
let key = format!("{}imap_certificate_checks", prefix);
|
|
sql.set_raw_config_int(key, self.imap.certificate_checks as i32)
|
|
.await?;
|
|
|
|
let key = format!("{}send_server", prefix);
|
|
sql.set_raw_config(key, Some(&self.smtp.server)).await?;
|
|
|
|
let key = format!("{}send_port", prefix);
|
|
sql.set_raw_config_int(key, self.smtp.port as i32).await?;
|
|
|
|
let key = format!("{}send_user", prefix);
|
|
sql.set_raw_config(key, Some(&self.smtp.user)).await?;
|
|
|
|
let key = format!("{}send_pw", prefix);
|
|
sql.set_raw_config(key, Some(&self.smtp.password)).await?;
|
|
|
|
let key = format!("{}send_security", prefix);
|
|
sql.set_raw_config_int(key, self.smtp.security as i32)
|
|
.await?;
|
|
|
|
let key = format!("{}smtp_certificate_checks", prefix);
|
|
sql.set_raw_config_int(key, self.smtp.certificate_checks as i32)
|
|
.await?;
|
|
|
|
let key = format!("{}server_flags", prefix);
|
|
sql.set_raw_config_int(key, self.server_flags).await?;
|
|
|
|
if let Some(provider) = self.provider {
|
|
let key = format!("{}provider", prefix);
|
|
sql.set_raw_config(key, Some(provider.id)).await?;
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl fmt::Display for LoginParam {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
let unset = "0";
|
|
let pw = "***";
|
|
|
|
let flags_readable = get_readable_flags(self.server_flags);
|
|
|
|
write!(
|
|
f,
|
|
"{} imap:{}:{}:{}:{}:cert_{} 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.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,
|
|
)
|
|
}
|
|
}
|
|
|
|
#[allow(clippy::ptr_arg)]
|
|
fn unset_empty(s: &String) -> Cow<String> {
|
|
if s.is_empty() {
|
|
Cow::Owned("unset".to_string())
|
|
} else {
|
|
Cow::Borrowed(s)
|
|
}
|
|
}
|
|
|
|
#[allow(clippy::useless_let_if_seq)]
|
|
fn get_readable_flags(flags: i32) -> String {
|
|
let mut res = String::new();
|
|
for bit in 0..31 {
|
|
if 0 != flags & 1 << bit {
|
|
let mut flag_added = false;
|
|
if 1 << bit == 0x2 {
|
|
res += "OAUTH2 ";
|
|
flag_added = true;
|
|
}
|
|
if 1 << bit == 0x4 {
|
|
res += "AUTH_NORMAL ";
|
|
flag_added = true;
|
|
}
|
|
if flag_added {
|
|
res += &format!("{:#0x}", 1 << bit);
|
|
}
|
|
}
|
|
}
|
|
if res.is_empty() {
|
|
res += "0";
|
|
}
|
|
|
|
res
|
|
}
|
|
|
|
pub fn dc_build_tls(strict_tls: bool) -> async_native_tls::TlsConnector {
|
|
let tls_builder = async_native_tls::TlsConnector::new();
|
|
|
|
if strict_tls {
|
|
tls_builder
|
|
} else {
|
|
tls_builder
|
|
.danger_accept_invalid_hostnames(true)
|
|
.danger_accept_invalid_certs(true)
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
use crate::test_utils::TestContext;
|
|
|
|
#[test]
|
|
fn test_certificate_checks_display() {
|
|
use std::string::ToString;
|
|
|
|
assert_eq!(
|
|
"accept_invalid_certificates".to_string(),
|
|
CertificateChecks::AcceptInvalidCertificates.to_string()
|
|
);
|
|
}
|
|
|
|
#[async_std::test]
|
|
async fn test_save_load_login_param() -> Result<()> {
|
|
let t = TestContext::new().await;
|
|
|
|
let param = LoginParam {
|
|
addr: "alice@example.com".to_string(),
|
|
imap: ServerLoginParam {
|
|
server: "imap.example.com".to_string(),
|
|
user: "alice".to_string(),
|
|
password: "foo".to_string(),
|
|
port: 123,
|
|
security: Socket::Starttls,
|
|
certificate_checks: CertificateChecks::Strict,
|
|
},
|
|
smtp: ServerLoginParam {
|
|
server: "smtp.example.com".to_string(),
|
|
user: "alice@example.com".to_string(),
|
|
password: "bar".to_string(),
|
|
port: 456,
|
|
security: Socket::Ssl,
|
|
certificate_checks: CertificateChecks::AcceptInvalidCertificates,
|
|
},
|
|
server_flags: 0,
|
|
provider: get_provider_by_id("example.com"),
|
|
};
|
|
|
|
param.save_to_database(&t, "foobar_").await?;
|
|
let loaded = LoginParam::from_database(&t, "foobar_").await?;
|
|
|
|
assert_eq!(param, loaded);
|
|
Ok(())
|
|
}
|
|
}
|