use log::debug;
use mail_send::Credentials;
use serde::{de, Deserialize, Deserializer, Serialize};
use std::{fmt, io, marker::PhantomData, result};
use thiserror::Error;
use crate::{
account::config::{
oauth2::{OAuth2Config, OAuth2Method},
passwd::PasswdConfig,
},
Result,
};
#[derive(Debug, Error)]
pub enum Error {
#[error("cannot get smtp password")]
GetPasswdError(#[source] secret::Error),
#[error("cannot get smtp password: password is empty")]
GetPasswdEmptyError,
}
#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct SmtpConfig {
pub host: String,
pub port: u16,
#[serde(default, deserialize_with = "some_bool_or_kind")]
pub encryption: Option<SmtpEncryptionKind>,
pub login: String,
#[serde(flatten)]
pub auth: SmtpAuthConfig,
}
impl SmtpConfig {
pub fn is_encryption_enabled(&self) -> bool {
matches!(
self.encryption.as_ref(),
None | Some(SmtpEncryptionKind::Tls) | Some(SmtpEncryptionKind::StartTls)
)
}
pub fn is_start_tls_encryption_enabled(&self) -> bool {
matches!(self.encryption.as_ref(), Some(SmtpEncryptionKind::StartTls))
}
pub fn is_encryption_disabled(&self) -> bool {
matches!(self.encryption.as_ref(), Some(SmtpEncryptionKind::None))
}
pub async fn credentials(&self) -> Result<Credentials<String>> {
Ok(match &self.auth {
SmtpAuthConfig::Passwd(passwd) => {
let passwd = passwd.get().await.map_err(Error::GetPasswdError)?;
let passwd = passwd.lines().next().ok_or(Error::GetPasswdEmptyError)?;
Credentials::new(self.login.clone(), passwd.to_owned())
}
SmtpAuthConfig::OAuth2(oauth2) => match oauth2.method {
OAuth2Method::XOAuth2 => {
Credentials::new_xoauth2(self.login.clone(), oauth2.access_token().await?)
}
OAuth2Method::OAuthBearer => Credentials::new_oauth(oauth2.access_token().await?),
},
})
}
}
#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum SmtpEncryptionKind {
#[default]
#[serde(alias = "ssl")]
Tls,
#[serde(alias = "starttls")]
StartTls,
None,
}
impl fmt::Display for SmtpEncryptionKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Tls => write!(f, "SSL/TLS"),
Self::StartTls => write!(f, "StartTLS"),
Self::None => write!(f, "None"),
}
}
}
impl From<bool> for SmtpEncryptionKind {
fn from(value: bool) -> Self {
if value {
Self::Tls
} else {
Self::None
}
}
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum SmtpAuthConfig {
#[serde(alias = "password")]
Passwd(PasswdConfig),
OAuth2(OAuth2Config),
}
impl Default for SmtpAuthConfig {
fn default() -> Self {
Self::Passwd(PasswdConfig::default())
}
}
impl SmtpAuthConfig {
pub async fn reset(&self) -> Result<()> {
debug!("resetting smtp backend configuration");
if let Self::OAuth2(oauth2) = self {
oauth2.reset().await?;
}
Ok(())
}
pub async fn configure(
&self,
get_client_secret: impl Fn() -> io::Result<String>,
) -> Result<()> {
debug!("configuring smtp backend");
if let Self::OAuth2(oauth2) = self {
oauth2.configure(get_client_secret).await?;
}
Ok(())
}
pub fn replace_undefined_keyring_entries(&mut self, name: impl AsRef<str>) {
let name = name.as_ref();
match self {
SmtpAuthConfig::Passwd(secret) => {
secret.set_keyring_entry_if_undefined(format!("{name}-smtp-passwd"));
}
SmtpAuthConfig::OAuth2(config) => {
config
.client_secret
.set_keyring_entry_if_undefined(format!("{name}-smtp-oauth2-client-secret"));
config
.access_token
.set_keyring_entry_if_undefined(format!("{name}-smtp-oauth2-access-token"));
config
.refresh_token
.set_keyring_entry_if_undefined(format!("{name}-smtp-oauth2-refresh-token"));
}
}
}
}
fn some_bool_or_kind<'de, D>(
deserializer: D,
) -> result::Result<Option<SmtpEncryptionKind>, D::Error>
where
D: Deserializer<'de>,
{
struct SomeBoolOrKind(PhantomData<fn() -> Option<SmtpEncryptionKind>>);
impl<'de> de::Visitor<'de> for SomeBoolOrKind {
type Value = Option<SmtpEncryptionKind>;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("some or none")
}
fn visit_some<D>(self, deserializer: D) -> result::Result<Self::Value, D::Error>
where
D: Deserializer<'de>,
{
struct BoolOrKind(PhantomData<fn() -> SmtpEncryptionKind>);
impl<'de> de::Visitor<'de> for BoolOrKind {
type Value = SmtpEncryptionKind;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("boolean or string")
}
fn visit_bool<E>(self, v: bool) -> result::Result<Self::Value, E>
where
E: de::Error,
{
Ok(v.into())
}
fn visit_str<E>(self, v: &str) -> result::Result<Self::Value, E>
where
E: de::Error,
{
Deserialize::deserialize(de::value::StrDeserializer::new(v))
}
}
deserializer
.deserialize_any(BoolOrKind(PhantomData))
.map(Option::Some)
}
}
deserializer.deserialize_option(SomeBoolOrKind(PhantomData))
}