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