1use std::io;
7
8use mail_send::Credentials;
9use tracing::debug;
10
11#[doc(inline)]
12pub use super::{Error, Result};
13#[cfg(feature = "oauth2")]
14use crate::account::config::oauth2::{OAuth2Config, OAuth2Method};
15#[cfg(feature = "derive")]
16use crate::serde::deserialize_shell_expanded_string;
17use crate::{account::config::passwd::PasswordConfig, tls::Encryption};
18
19#[derive(Clone, Debug, Default, Eq, PartialEq)]
21#[cfg_attr(
22 feature = "derive",
23 derive(serde::Serialize, serde::Deserialize),
24 serde(rename_all = "kebab-case")
25)]
26pub struct SmtpConfig {
27 pub host: String,
29
30 pub port: u16,
32
33 pub encryption: Option<Encryption>,
37
38 #[cfg_attr(
43 feature = "derive",
44 serde(deserialize_with = "deserialize_shell_expanded_string")
45 )]
46 pub login: String,
47
48 pub auth: SmtpAuthConfig,
53}
54
55impl SmtpConfig {
56 pub fn is_encryption_enabled(&self) -> bool {
58 matches!(
59 self.encryption.as_ref(),
60 None | Some(Encryption::Tls(_)) | Some(Encryption::StartTls(_))
61 )
62 }
63
64 pub fn is_start_tls_encryption_enabled(&self) -> bool {
66 matches!(self.encryption.as_ref(), Some(Encryption::StartTls(_)))
67 }
68
69 pub fn is_encryption_disabled(&self) -> bool {
71 matches!(self.encryption.as_ref(), Some(Encryption::None))
72 }
73
74 pub async fn credentials(&self) -> Result<Credentials<String>> {
80 Ok(match &self.auth {
81 SmtpAuthConfig::Password(passwd) => {
82 let passwd = passwd.get().await.map_err(Error::GetPasswdSmtpError)?;
83 let passwd = passwd
84 .lines()
85 .next()
86 .ok_or(Error::GetPasswdEmptySmtpError)?;
87 Credentials::new(self.login.clone(), passwd.to_owned())
88 }
89 #[cfg(feature = "oauth2")]
90 SmtpAuthConfig::OAuth2(oauth2) => {
91 let access_token = oauth2
92 .access_token()
93 .await
94 .map_err(|_| Error::AccessTokenWasNotAvailable)?;
95
96 match oauth2.method {
97 OAuth2Method::XOAuth2 => {
98 Credentials::new_xoauth2(self.login.clone(), access_token)
99 }
100 OAuth2Method::OAuthBearer => Credentials::new_oauth(access_token),
101 }
102 }
103 })
104 }
105}
106
107#[derive(Clone, Debug, Eq, PartialEq)]
109#[cfg_attr(
110 feature = "derive",
111 derive(serde::Serialize, serde::Deserialize),
112 serde(rename_all = "lowercase"),
113 serde(tag = "type"),
114 serde(from = "SmtpAuthConfigDerive")
115)]
116pub enum SmtpAuthConfig {
117 Password(PasswordConfig),
119
120 #[cfg(feature = "oauth2")]
122 OAuth2(OAuth2Config),
123}
124
125impl SmtpAuthConfig {
126 pub async fn reset(&mut self) -> Result<()> {
128 debug!("resetting smtp backend configuration");
129
130 #[cfg(feature = "oauth2")]
131 if let Self::OAuth2(oauth2) = self {
132 oauth2
133 .reset()
134 .await
135 .map_err(|_| Error::ResettingOAuthFailed)?;
136 }
137
138 Ok(())
139 }
140
141 pub async fn configure(
143 &mut self,
144 #[cfg_attr(not(feature = "oauth2"), allow(unused_variables))]
145 get_client_secret: impl Fn() -> io::Result<String>,
146 ) -> Result<()> {
147 debug!("configuring smtp backend");
148
149 #[cfg(feature = "oauth2")]
150 if let Self::OAuth2(oauth2) = self {
151 oauth2
152 .configure(get_client_secret)
153 .await
154 .map_err(|_| Error::ConfiguringOAuthFailed)?;
155 }
156
157 Ok(())
158 }
159
160 #[cfg(feature = "keyring")]
161 pub fn replace_empty_secrets(&mut self, name: impl AsRef<str>) -> Result<()> {
162 let name = name.as_ref();
163
164 match self {
165 SmtpAuthConfig::Password(secret) => {
166 secret
167 .replace_with_keyring_if_empty(format!("{name}-smtp-passwd"))
168 .map_err(Error::ReplacingKeyringFailed)?;
169 }
170 #[cfg(feature = "oauth2")]
171 SmtpAuthConfig::OAuth2(config) => {
172 if let Some(secret) = config.client_secret.as_mut() {
173 secret
174 .replace_with_keyring_if_empty(format!("{name}-smtp-oauth2-client-secret"))
175 .map_err(Error::ReplacingKeyringFailed)?;
176 }
177
178 config
179 .access_token
180 .replace_with_keyring_if_empty(format!("{name}-smtp-oauth2-access-token"))
181 .map_err(Error::ReplacingKeyringFailed)?;
182 config
183 .refresh_token
184 .replace_with_keyring_if_empty(format!("{name}-smtp-oauth2-refresh-token"))
185 .map_err(Error::ReplacingKeyringFailed)?;
186 }
187 }
188
189 Ok(())
190 }
191}
192
193impl Default for SmtpAuthConfig {
194 fn default() -> Self {
195 Self::Password(PasswordConfig::default())
196 }
197}
198
199#[cfg(feature = "derive")]
200#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
201#[serde(rename_all = "lowercase", tag = "type")]
202pub enum SmtpAuthConfigDerive {
203 Password(PasswordConfig),
204 #[cfg(feature = "oauth2")]
205 OAuth2(OAuth2Config),
206 #[cfg(not(feature = "oauth2"))]
207 #[serde(skip_serializing, deserialize_with = "missing_oauth2_feature")]
208 OAuth2,
209}
210
211#[cfg(all(feature = "derive", not(feature = "oauth2")))]
212fn missing_oauth2_feature<'de, D>(_: D) -> std::result::Result<(), D::Error>
213where
214 D: serde::Deserializer<'de>,
215{
216 Err(serde::de::Error::custom("missing `oauth2` cargo feature"))
217}
218
219#[cfg(feature = "derive")]
220impl From<SmtpAuthConfigDerive> for SmtpAuthConfig {
221 fn from(config: SmtpAuthConfigDerive) -> Self {
222 match config {
223 SmtpAuthConfigDerive::Password(config) => Self::Password(config),
224 #[cfg(feature = "oauth2")]
225 SmtpAuthConfigDerive::OAuth2(config) => Self::OAuth2(config),
226 #[cfg(not(feature = "oauth2"))]
227 SmtpAuthConfigDerive::OAuth2 => unreachable!(),
228 }
229 }
230}