mirror of
https://github.com/chatmail/core.git
synced 2026-04-17 13:36:30 +03:00
317 lines
9.6 KiB
Rust
317 lines
9.6 KiB
Rust
use std::borrow::Cow;
|
|
use std::fmt;
|
|
|
|
use crate::context::Context;
|
|
use crate::error::Error;
|
|
use async_std::sync::Arc;
|
|
use rustls;
|
|
use webpki;
|
|
use webpki_roots;
|
|
|
|
#[derive(Copy, Clone, Debug, Display, FromPrimitive)]
|
|
#[repr(i32)]
|
|
#[strum(serialize_all = "snake_case")]
|
|
pub enum CertificateChecks {
|
|
Automatic = 0,
|
|
Strict = 1,
|
|
AcceptInvalidHostnames = 2,
|
|
AcceptInvalidCertificates = 3,
|
|
}
|
|
|
|
impl Default for CertificateChecks {
|
|
fn default() -> Self {
|
|
Self::Automatic
|
|
}
|
|
}
|
|
|
|
#[derive(Default, Debug)]
|
|
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 server_flags: i32,
|
|
}
|
|
|
|
impl LoginParam {
|
|
/// Create a new `LoginParam` with default values.
|
|
pub fn new() -> Self {
|
|
Default::default()
|
|
}
|
|
|
|
/// Read the login parameters from the database.
|
|
pub fn from_database(context: &Context, prefix: impl AsRef<str>) -> Self {
|
|
let prefix = prefix.as_ref();
|
|
let sql = &context.sql;
|
|
|
|
let key = format!("{}addr", prefix);
|
|
let addr = sql
|
|
.get_raw_config(context, key)
|
|
.unwrap_or_default()
|
|
.trim()
|
|
.to_string();
|
|
|
|
let key = format!("{}mail_server", prefix);
|
|
let mail_server = sql.get_raw_config(context, key).unwrap_or_default();
|
|
|
|
let key = format!("{}mail_port", prefix);
|
|
let mail_port = sql.get_raw_config_int(context, key).unwrap_or_default();
|
|
|
|
let key = format!("{}mail_user", prefix);
|
|
let mail_user = sql.get_raw_config(context, key).unwrap_or_default();
|
|
|
|
let key = format!("{}mail_pw", prefix);
|
|
let mail_pw = sql.get_raw_config(context, key).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) {
|
|
num_traits::FromPrimitive::from_i32(certificate_checks).unwrap()
|
|
} else {
|
|
Default::default()
|
|
};
|
|
|
|
let key = format!("{}send_server", prefix);
|
|
let send_server = sql.get_raw_config(context, key).unwrap_or_default();
|
|
|
|
let key = format!("{}send_port", prefix);
|
|
let send_port = sql.get_raw_config_int(context, key).unwrap_or_default();
|
|
|
|
let key = format!("{}send_user", prefix);
|
|
let send_user = sql.get_raw_config(context, key).unwrap_or_default();
|
|
|
|
let key = format!("{}send_pw", prefix);
|
|
let send_pw = sql.get_raw_config(context, key).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) {
|
|
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(context, key).unwrap_or_default();
|
|
|
|
LoginParam {
|
|
addr: addr.to_string(),
|
|
mail_server,
|
|
mail_user,
|
|
mail_pw,
|
|
mail_port,
|
|
imap_certificate_checks,
|
|
send_server,
|
|
send_user,
|
|
send_pw,
|
|
send_port,
|
|
smtp_certificate_checks,
|
|
server_flags,
|
|
}
|
|
}
|
|
|
|
pub fn addr_str(&self) -> &str {
|
|
self.addr.as_str()
|
|
}
|
|
|
|
/// Save this loginparam to the database.
|
|
pub fn save_to_database(
|
|
&self,
|
|
context: &Context,
|
|
prefix: impl AsRef<str>,
|
|
) -> Result<(), Error> {
|
|
let prefix = prefix.as_ref();
|
|
let sql = &context.sql;
|
|
|
|
let key = format!("{}addr", prefix);
|
|
sql.set_raw_config(context, key, Some(&self.addr))?;
|
|
|
|
let key = format!("{}mail_server", prefix);
|
|
sql.set_raw_config(context, key, Some(&self.mail_server))?;
|
|
|
|
let key = format!("{}mail_port", prefix);
|
|
sql.set_raw_config_int(context, key, self.mail_port)?;
|
|
|
|
let key = format!("{}mail_user", prefix);
|
|
sql.set_raw_config(context, key, Some(&self.mail_user))?;
|
|
|
|
let key = format!("{}mail_pw", prefix);
|
|
sql.set_raw_config(context, key, Some(&self.mail_pw))?;
|
|
|
|
let key = format!("{}imap_certificate_checks", prefix);
|
|
sql.set_raw_config_int(context, key, self.imap_certificate_checks as i32)?;
|
|
|
|
let key = format!("{}send_server", prefix);
|
|
sql.set_raw_config(context, key, Some(&self.send_server))?;
|
|
|
|
let key = format!("{}send_port", prefix);
|
|
sql.set_raw_config_int(context, key, self.send_port)?;
|
|
|
|
let key = format!("{}send_user", prefix);
|
|
sql.set_raw_config(context, key, Some(&self.send_user))?;
|
|
|
|
let key = format!("{}send_pw", prefix);
|
|
sql.set_raw_config(context, key, Some(&self.send_pw))?;
|
|
|
|
let key = format!("{}smtp_certificate_checks", prefix);
|
|
sql.set_raw_config_int(context, key, self.smtp_certificate_checks as i32)?;
|
|
|
|
let key = format!("{}server_flags", prefix);
|
|
sql.set_raw_config_int(context, key, self.server_flags)?;
|
|
|
|
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.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,
|
|
flags_readable,
|
|
)
|
|
}
|
|
}
|
|
|
|
fn unset_empty(s: &String) -> Cow<String> {
|
|
if s.is_empty() {
|
|
Cow::Owned("unset".to_string())
|
|
} else {
|
|
Cow::Borrowed(s)
|
|
}
|
|
}
|
|
|
|
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 = 0;
|
|
if 1 << bit == 0x2 {
|
|
res += "OAUTH2 ";
|
|
flag_added = 1;
|
|
}
|
|
if 1 << bit == 0x4 {
|
|
res += "AUTH_NORMAL ";
|
|
flag_added = 1;
|
|
}
|
|
if 1 << bit == 0x100 {
|
|
res += "IMAP_STARTTLS ";
|
|
flag_added = 1;
|
|
}
|
|
if 1 << bit == 0x200 {
|
|
res += "IMAP_SSL ";
|
|
flag_added = 1;
|
|
}
|
|
if 1 << bit == 0x400 {
|
|
res += "IMAP_PLAIN ";
|
|
flag_added = 1;
|
|
}
|
|
if 1 << bit == 0x10000 {
|
|
res += "SMTP_STARTTLS ";
|
|
flag_added = 1
|
|
}
|
|
if 1 << bit == 0x20000 {
|
|
res += "SMTP_SSL ";
|
|
flag_added = 1
|
|
}
|
|
if 1 << bit == 0x40000 {
|
|
res += "SMTP_PLAIN ";
|
|
flag_added = 1
|
|
}
|
|
if 0 == flag_added {
|
|
res += &format!("{:#0x}", 1 << bit);
|
|
}
|
|
}
|
|
}
|
|
if res.is_empty() {
|
|
res += "0";
|
|
}
|
|
|
|
res
|
|
}
|
|
|
|
pub struct NoCertificateVerification {}
|
|
|
|
impl rustls::ServerCertVerifier for NoCertificateVerification {
|
|
fn verify_server_cert(
|
|
&self,
|
|
_roots: &rustls::RootCertStore,
|
|
_presented_certs: &[rustls::Certificate],
|
|
_dns_name: webpki::DNSNameRef<'_>,
|
|
_ocsp: &[u8],
|
|
) -> Result<rustls::ServerCertVerified, rustls::TLSError> {
|
|
Ok(rustls::ServerCertVerified::assertion())
|
|
}
|
|
}
|
|
|
|
pub fn dc_build_tls_config(certificate_checks: CertificateChecks) -> rustls::ClientConfig {
|
|
let mut config = rustls::ClientConfig::new();
|
|
config
|
|
.root_store
|
|
.add_server_trust_anchors(&webpki_roots::TLS_SERVER_ROOTS);
|
|
|
|
match certificate_checks {
|
|
CertificateChecks::Strict => {}
|
|
CertificateChecks::Automatic => {
|
|
// Same as AcceptInvalidCertificates for now.
|
|
// TODO: use provider database when it becomes available
|
|
config
|
|
.dangerous()
|
|
.set_certificate_verifier(Arc::new(NoCertificateVerification {}));
|
|
}
|
|
CertificateChecks::AcceptInvalidCertificates => {
|
|
// TODO: only accept invalid certs
|
|
config
|
|
.dangerous()
|
|
.set_certificate_verifier(Arc::new(NoCertificateVerification {}));
|
|
}
|
|
CertificateChecks::AcceptInvalidHostnames => {
|
|
// TODO: only accept invalid hostnames
|
|
config
|
|
.dangerous()
|
|
.set_certificate_verifier(Arc::new(NoCertificateVerification {}));
|
|
}
|
|
}
|
|
config
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_certificate_checks_display() {
|
|
use std::string::ToString;
|
|
|
|
assert_eq!(
|
|
"accept_invalid_hostnames".to_string(),
|
|
CertificateChecks::AcceptInvalidHostnames.to_string()
|
|
);
|
|
}
|
|
}
|