use std::{fmt, io};
#[cfg(feature = "derive")]
use std::{marker::PhantomData, result};
use mail_send::Credentials;
#[doc(inline)]
pub use super::{Error, Result};
#[cfg(feature = "oauth2")]
use crate::account::config::oauth2::{OAuth2Config, OAuth2Method};
use crate::{account::config::passwd::PasswdConfig, debug};
#[derive(Clone, Debug, Default, Eq, PartialEq)]
#[cfg_attr(
feature = "derive",
derive(serde::Serialize, serde::Deserialize),
serde(rename_all = "kebab-case")
)]
pub struct SmtpConfig {
pub host: String,
pub port: u16,
#[cfg_attr(
feature = "derive",
serde(default, deserialize_with = "some_bool_or_kind")
)]
pub encryption: Option<SmtpEncryptionKind>,
pub login: String,
#[cfg_attr(feature = "derive", 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::GetPasswdSmtpError)?;
let passwd = passwd
.lines()
.next()
.ok_or(Error::GetPasswdEmptySmtpError)?;
Credentials::new(self.login.clone(), passwd.to_owned())
}
#[cfg(feature = "oauth2")]
SmtpAuthConfig::OAuth2(oauth2) => {
let access_token = oauth2
.access_token()
.await
.map_err(|_| Error::AccessTokenWasNotAvailable)?;
match oauth2.method {
OAuth2Method::XOAuth2 => {
Credentials::new_xoauth2(self.login.clone(), access_token)
}
OAuth2Method::OAuthBearer => Credentials::new_oauth(access_token),
}
}
})
}
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
#[cfg_attr(
feature = "derive",
derive(serde::Serialize, serde::Deserialize),
serde(rename_all = "kebab-case")
)]
pub enum SmtpEncryptionKind {
#[default]
#[cfg_attr(feature = "derive", serde(alias = "ssl"))]
Tls,
#[cfg_attr(feature = "derive", 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)]
#[cfg_attr(
feature = "derive",
derive(serde::Serialize, serde::Deserialize),
serde(rename_all = "lowercase")
)]
pub enum SmtpAuthConfig {
#[cfg_attr(feature = "derive", serde(alias = "password"))]
Passwd(PasswdConfig),
#[cfg(feature = "oauth2")]
OAuth2(OAuth2Config),
}
impl Default for SmtpAuthConfig {
fn default() -> Self {
Self::Passwd(PasswdConfig::default())
}
}
impl SmtpAuthConfig {
pub async fn reset(&mut self) -> Result<()> {
debug!("resetting smtp backend configuration");
#[cfg(feature = "oauth2")]
if let Self::OAuth2(oauth2) = self {
oauth2
.reset()
.await
.map_err(|_| Error::ResettingOAuthFailed)?;
}
Ok(())
}
pub async fn configure(
&mut self,
#[cfg_attr(not(feature = "oauth2"), allow(unused_variables))]
get_client_secret: impl Fn() -> io::Result<String>,
) -> Result<()> {
debug!("configuring smtp backend");
#[cfg(feature = "oauth2")]
if let Self::OAuth2(oauth2) = self {
oauth2
.configure(get_client_secret)
.await
.map_err(|_| Error::ConfiguringOAuthFailed)?;
}
Ok(())
}
#[cfg(feature = "keyring")]
pub fn replace_undefined_keyring_entries(&mut self, name: impl AsRef<str>) -> Result<()> {
let name = name.as_ref();
match self {
SmtpAuthConfig::Passwd(secret) => {
secret
.replace_undefined_to_keyring(format!("{name}-smtp-passwd"))
.map_err(Error::ReplacingKeyringFailed)?;
}
#[cfg(feature = "oauth2")]
SmtpAuthConfig::OAuth2(config) => {
config
.client_secret
.replace_undefined_to_keyring(format!("{name}-smtp-oauth2-client-secret"))
.map_err(Error::ReplacingKeyringFailed)?;
config
.access_token
.replace_undefined_to_keyring(format!("{name}-smtp-oauth2-access-token"))
.map_err(Error::ReplacingKeyringFailed)?;
config
.refresh_token
.replace_undefined_to_keyring(format!("{name}-smtp-oauth2-refresh-token"))
.map_err(Error::ReplacingKeyringFailed)?;
}
}
Ok(())
}
}
#[cfg(feature = "derive")]
fn some_bool_or_kind<'de, D>(
deserializer: D,
) -> result::Result<Option<SmtpEncryptionKind>, D::Error>
where
D: serde::Deserializer<'de>,
{
struct SomeBoolOrKind(PhantomData<fn() -> Option<SmtpEncryptionKind>>);
impl<'de> serde::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: serde::Deserializer<'de>,
{
struct BoolOrKind(PhantomData<fn() -> SmtpEncryptionKind>);
impl<'de> serde::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: serde::de::Error,
{
Ok(v.into())
}
fn visit_str<E>(self, v: &str) -> result::Result<Self::Value, E>
where
E: serde::de::Error,
{
serde::Deserialize::deserialize(serde::de::value::StrDeserializer::new(v))
}
}
deserializer
.deserialize_any(BoolOrKind(PhantomData))
.map(Option::Some)
}
}
deserializer.deserialize_option(SomeBoolOrKind(PhantomData))
}