use std::io;
use mail_send::Credentials;
use tracing::debug;
#[doc(inline)]
pub use super::{Error, Result};
#[cfg(feature = "oauth2")]
use crate::account::config::oauth2::{OAuth2Config, OAuth2Method};
#[cfg(feature = "derive")]
use crate::serde::deserialize_shell_expanded_string;
use crate::{account::config::passwd::PasswordConfig, tls::Encryption};
#[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,
pub encryption: Option<Encryption>,
#[cfg_attr(
feature = "derive",
serde(deserialize_with = "deserialize_shell_expanded_string")
)]
pub login: String,
pub auth: SmtpAuthConfig,
}
impl SmtpConfig {
pub fn is_encryption_enabled(&self) -> bool {
matches!(
self.encryption.as_ref(),
None | Some(Encryption::Tls(_)) | Some(Encryption::StartTls(_))
)
}
pub fn is_start_tls_encryption_enabled(&self) -> bool {
matches!(self.encryption.as_ref(), Some(Encryption::StartTls(_)))
}
pub fn is_encryption_disabled(&self) -> bool {
matches!(self.encryption.as_ref(), Some(Encryption::None))
}
pub async fn credentials(&self) -> Result<Credentials<String>> {
Ok(match &self.auth {
SmtpAuthConfig::Password(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, Eq, PartialEq)]
#[cfg_attr(
feature = "derive",
derive(serde::Serialize, serde::Deserialize),
serde(rename_all = "lowercase"),
serde(tag = "type"),
serde(from = "SmtpAuthConfigDerive")
)]
pub enum SmtpAuthConfig {
Password(PasswordConfig),
#[cfg(feature = "oauth2")]
OAuth2(OAuth2Config),
}
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_empty_secrets(&mut self, name: impl AsRef<str>) -> Result<()> {
let name = name.as_ref();
match self {
SmtpAuthConfig::Password(secret) => {
secret
.replace_with_keyring_if_empty(format!("{name}-smtp-passwd"))
.map_err(Error::ReplacingKeyringFailed)?;
}
#[cfg(feature = "oauth2")]
SmtpAuthConfig::OAuth2(config) => {
if let Some(secret) = config.client_secret.as_mut() {
secret
.replace_with_keyring_if_empty(format!("{name}-smtp-oauth2-client-secret"))
.map_err(Error::ReplacingKeyringFailed)?;
}
config
.access_token
.replace_with_keyring_if_empty(format!("{name}-smtp-oauth2-access-token"))
.map_err(Error::ReplacingKeyringFailed)?;
config
.refresh_token
.replace_with_keyring_if_empty(format!("{name}-smtp-oauth2-refresh-token"))
.map_err(Error::ReplacingKeyringFailed)?;
}
}
Ok(())
}
}
impl Default for SmtpAuthConfig {
fn default() -> Self {
Self::Password(PasswordConfig::default())
}
}
#[cfg(feature = "derive")]
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "lowercase", tag = "type")]
pub enum SmtpAuthConfigDerive {
Password(PasswordConfig),
#[cfg(feature = "oauth2")]
OAuth2(OAuth2Config),
#[cfg(not(feature = "oauth2"))]
#[serde(skip_serializing, deserialize_with = "missing_oauth2_feature")]
OAuth2,
}
#[cfg(all(feature = "derive", not(feature = "oauth2")))]
fn missing_oauth2_feature<'de, D>(_: D) -> std::result::Result<(), D::Error>
where
D: serde::Deserializer<'de>,
{
Err(serde::de::Error::custom("missing `oauth2` cargo feature"))
}
#[cfg(feature = "derive")]
impl From<SmtpAuthConfigDerive> for SmtpAuthConfig {
fn from(config: SmtpAuthConfigDerive) -> Self {
match config {
SmtpAuthConfigDerive::Password(config) => Self::Password(config),
#[cfg(feature = "oauth2")]
SmtpAuthConfigDerive::OAuth2(config) => Self::OAuth2(config),
#[cfg(not(feature = "oauth2"))]
SmtpAuthConfigDerive::OAuth2 => unreachable!(),
}
}
}