refactor: improve sasl config

This commit is contained in:
Clément DOUIN
2026-06-02 00:34:12 +02:00
parent 164c745120
commit 4b347fda2b
6 changed files with 74 additions and 32 deletions
+10 -15
View File
@@ -180,22 +180,19 @@ imap.server = "example.com"
# SASL PLAIN
# https://datatracker.ietf.org/doc/html/rfc4616
imap.sasl.plain.authcid = "user@example.com"
imap.sasl.plain.passwd.raw = "***"
#imap.sasl.plain.passwd.command = "pass show example"
#imap.sasl.plain.passwd.command = ["mimosa", "password", "read", "example"]
imap.sasl.plain.username = "user@example.com"
imap.sasl.plain.password.raw = "***"
#imap.sasl.plain.password.command = "pass show example"
# SASL LOGIN
# https://datatracker.ietf.org/doc/html/draft-murchison-sasl-login-00
#imap.sasl.login.username = "user@example.com"
#imap.sasl.login.password.raw = "***"
# SASL OAUTHBEARER (host/port echoed in the GS2 header; usually mirror the
# server you actually connect to).
# SASL OAUTHBEARER (host/port for the GS2 header are derived from the
# IMAP server URL at connect time).
# https://datatracker.ietf.org/doc/html/rfc7628
#imap.sasl.oauthbearer.username = "user@example.com"
#imap.sasl.oauthbearer.host = "imap.example.com"
#imap.sasl.oauthbearer.port = 993
#imap.sasl.oauthbearer.token.raw = "***"
#imap.sasl.oauthbearer.token.command = ["ortie", "token", "read", "example"]
@@ -312,19 +309,17 @@ smtp.server = "example.com"
#smtp.sasl.anonymous.message = "himalaya"
# SASL PLAIN
smtp.sasl.plain.authcid = "user@example.com"
smtp.sasl.plain.passwd.raw = "***"
#smtp.sasl.plain.passwd.command = "pass show example"
#smtp.sasl.plain.passwd.command = ["mimosa", "password", "read", "example"]
smtp.sasl.plain.username = "user@example.com"
smtp.sasl.plain.password.raw = "***"
#smtp.sasl.plain.password.command = "pass show example"
# SASL LOGIN
#smtp.sasl.login.username = "user@example.com"
#smtp.sasl.login.password.raw = "***"
# SASL OAUTHBEARER
# SASL OAUTHBEARER (host/port for the GS2 header are derived from the
# SMTP server URL at connect time).
#smtp.sasl.oauthbearer.username = "user@example.com"
#smtp.sasl.oauthbearer.host = "smtp.example.com"
#smtp.sasl.oauthbearer.port = 465
#smtp.sasl.oauthbearer.token.raw = "***"
# SASL XOAUTH2
+18 -2
View File
@@ -120,9 +120,17 @@ fn check_imap(
let result = (|| -> Result<()> {
let tls = imap_config.tls.clone().into_tls(imap_config.alpn.clone());
let sasl: Option<Sasl> = imap_config.sasl.clone().map(Sasl::try_from).transpose()?;
let auto_id = resolve_auto_id_params(&imap_config.id)?;
let server = crate::imap::client::parse_imap_server(&imap_config.server)?;
let sasl: Option<Sasl> = imap_config
.sasl
.clone()
.and_then(|cfg| {
let host = server.host_str()?;
let port = server.port_or_known_default()?;
Some(cfg.try_into_sasl(host, port))
})
.transpose()?;
let _ = ImapClientStd::connect(&server, &tls, imap_config.starttls, sasl, auto_id)?;
Ok(())
})();
@@ -184,9 +192,17 @@ fn check_smtp(
let result = (|| -> Result<()> {
let tls = smtp_config.tls.clone().into_tls(smtp_config.alpn.clone());
let sasl: Option<Sasl> = smtp_config.sasl.clone().map(Sasl::try_from).transpose()?;
let domain: EhloDomain<'static> = Ipv4Addr::new(127, 0, 0, 1).into();
let server = crate::smtp::client::parse_smtp_server(&smtp_config.server)?;
let sasl: Option<Sasl> = smtp_config
.sasl
.clone()
.and_then(|cfg| {
let host = server.host_str()?;
let port = server.port_or_known_default()?;
Some(cfg.try_into_sasl(host, port))
})
.transpose()?;
let _client = SmtpClientStd::connect(&server, &tls, smtp_config.starttls, domain, sasl)?;
Ok(())
})();
+14 -11
View File
@@ -518,14 +518,17 @@ pub struct SaslLoginConfig {
pub struct SaslPlainConfig {
pub authzid: Option<String>,
#[serde(deserialize_with = "shell_expanded_string")]
#[serde(alias = "username")]
pub authcid: String,
#[serde(alias = "password")]
pub passwd: Secret,
}
/// SASL OAUTHBEARER configuration <sup>[rfc7628]</sup>.
///
/// `host` and `port` are echoed verbatim in the GS2 header and should
/// match the server the connection is actually opened against.
/// The `host` and `port` echoed in the GS2 header are derived from
/// the live IMAP/SMTP server URL at connect time, so they aren't part
/// of the user-facing config.
///
/// [rfc7628]: https://www.iana.org/go/rfc7628
#[derive(Clone, Debug, Deserialize, Serialize)]
@@ -533,8 +536,6 @@ pub struct SaslPlainConfig {
pub struct SaslOauthbearerConfig {
#[serde(deserialize_with = "shell_expanded_string")]
pub username: String,
pub host: String,
pub port: u16,
pub token: Secret,
}
@@ -559,11 +560,13 @@ pub struct SaslScramSha256Config {
pub password: Secret,
}
impl TryFrom<SaslConfig> for Sasl {
type Error = anyhow::Error;
fn try_from(config: SaslConfig) -> Result<Self> {
Ok(match config {
impl SaslConfig {
/// Resolves the SASL config into a runtime [`Sasl`]. `host` and
/// `port` come from the live server URL; they are only used by
/// OAUTHBEARER (echoed in the GS2 header) and ignored by every
/// other mechanism.
pub fn try_into_sasl(self, host: impl ToString, port: u16) -> Result<Sasl> {
Ok(match self {
SaslConfig::Anonymous(c) => Sasl::Anonymous(SaslAnonymous { message: c.message }),
SaslConfig::Login(c) => Sasl::Login(SaslLogin {
username: c.username,
@@ -576,8 +579,8 @@ impl TryFrom<SaslConfig> for Sasl {
}),
SaslConfig::Oauthbearer(c) => Sasl::Oauthbearer(SaslOauthbearer {
username: c.username,
host: c.host,
port: c.port,
host: host.to_string(),
port,
token: c.token.get()?,
}),
SaslConfig::Xoauth2(c) => Sasl::Xoauth2(SaslXoauth2 {
+8 -1
View File
@@ -49,9 +49,16 @@ impl ImapClient {
/// [`Inner::capability`] explicitly.
pub fn new(config: ImapConfig) -> Result<Self> {
let tls = config.tls.into_tls(config.alpn);
let sasl: Option<Sasl> = config.sasl.map(Sasl::try_from).transpose()?;
let auto_id = resolve_auto_id_params(&config.id)?;
let server = parse_imap_server(&config.server)?;
let sasl: Option<Sasl> = config
.sasl
.and_then(|cfg| {
let host = server.host_str()?;
let port = server.port_or_known_default()?;
Some(cfg.try_into_sasl(host, port))
})
.transpose()?;
let (inner, _capability) = Inner::connect(&server, &tls, config.starttls, sasl, auto_id)?;
Ok(Self { inner })
}
+16 -2
View File
@@ -78,9 +78,16 @@ impl EmailClient {
use crate::imap::id::resolve_auto_id_params;
let tls = imap_config.tls.into_tls(imap_config.alpn);
let sasl: Option<Sasl> = imap_config.sasl.map(Sasl::try_from).transpose()?;
let auto_id = resolve_auto_id_params(&imap_config.id)?;
let server = crate::imap::client::parse_imap_server(&imap_config.server)?;
let sasl: Option<Sasl> = imap_config
.sasl
.and_then(|cfg| {
let host = server.host_str()?;
let port = server.port_or_known_default()?;
Some(cfg.try_into_sasl(host, port))
})
.transpose()?;
let imap =
ImapClientStd::connect(&server, &tls, imap_config.starttls, sasl, auto_id)?;
inner = inner.with_imap(imap);
@@ -126,9 +133,16 @@ impl EmailClient {
use pimalaya_stream::sasl::Sasl;
let tls = smtp_config.tls.into_tls(smtp_config.alpn);
let sasl: Option<Sasl> = smtp_config.sasl.map(Sasl::try_from).transpose()?;
let domain: EhloDomain<'static> = Ipv4Addr::new(127, 0, 0, 1).into();
let server = crate::smtp::client::parse_smtp_server(&smtp_config.server)?;
let sasl: Option<Sasl> = smtp_config
.sasl
.and_then(|cfg| {
let host = server.host_str()?;
let port = server.port_or_known_default()?;
Some(cfg.try_into_sasl(host, port))
})
.transpose()?;
inner = inner.with_smtp(SmtpClientStd::connect(
&server,
&tls,
+8 -1
View File
@@ -46,9 +46,16 @@ impl SmtpClient {
/// SASL).
pub fn new(config: SmtpConfig) -> Result<Self> {
let tls = config.tls.into_tls(config.alpn);
let sasl: Option<Sasl> = config.sasl.map(Sasl::try_from).transpose()?;
let domain: EhloDomain<'static> = Ipv4Addr::new(127, 0, 0, 1).into();
let server = parse_smtp_server(&config.server)?;
let sasl: Option<Sasl> = config
.sasl
.and_then(|cfg| {
let host = server.host_str()?;
let port = server.port_or_known_default()?;
Some(cfg.try_into_sasl(host, port))
})
.transpose()?;
let inner = Inner::connect(&server, &tls, config.starttls, domain, sasl)?;
Ok(Self { inner })
}