Skip to content

Commit

Permalink
add basic outbound LMTP support
Browse files Browse the repository at this point in the history
This enables LMTP over TCP by setting `use_lmtp = true` in the
egress path configuration.

refs: #267
  • Loading branch information
wez committed Dec 17, 2024
1 parent 05295e4 commit 8be4ad8
Show file tree
Hide file tree
Showing 7 changed files with 57 additions and 8 deletions.
5 changes: 5 additions & 0 deletions crates/kumo-api-types/src/egress_path.rs
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,10 @@ pub struct EgressPathConfig {
/// TLS disabled.
#[serde(default)]
pub opportunistic_tls_reconnect_on_failed_handshake: bool,

/// If true, rather than ESMTP, use the LMTP protocol
#[serde(default)]
pub use_lmtp: bool,
}

#[cfg(feature = "lua")]
Expand Down Expand Up @@ -289,6 +293,7 @@ impl Default for EgressPathConfig {
provider_name: None,
remember_broken_tls: None,
opportunistic_tls_reconnect_on_failed_handshake: false,
use_lmtp: false,
}
}
}
Expand Down
4 changes: 4 additions & 0 deletions crates/kumo-api-types/src/shaping.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1889,6 +1889,7 @@ MergedEntry {
provider_name: None,
remember_broken_tls: None,
opportunistic_tls_reconnect_on_failed_handshake: false,
use_lmtp: false,
},
sources: {},
automation: [
Expand Down Expand Up @@ -2024,6 +2025,7 @@ MergedEntry {
provider_name: None,
remember_broken_tls: None,
opportunistic_tls_reconnect_on_failed_handshake: false,
use_lmtp: false,
},
sources: {
"my source name": EgressPathConfig {
Expand Down Expand Up @@ -2072,6 +2074,7 @@ MergedEntry {
provider_name: None,
remember_broken_tls: None,
opportunistic_tls_reconnect_on_failed_handshake: false,
use_lmtp: false,
},
},
automation: [
Expand Down Expand Up @@ -2213,6 +2216,7 @@ MergedEntry {
provider_name: None,
remember_broken_tls: None,
opportunistic_tls_reconnect_on_failed_handshake: false,
use_lmtp: false,
},
sources: {},
automation: [
Expand Down
13 changes: 7 additions & 6 deletions crates/kumod/src/smtp_dispatcher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -346,11 +346,12 @@ impl SmtpDispatcher {
.with_context(|| connect_context.clone())?;
self.source_address.replace(source_address);

// Say EHLO
// Say EHLO/LHLO
let helo_verb = if path_config.use_lmtp { "LHLO" } else { "EHLO" };
let pretls_caps = client
.ehlo(&ehlo_name)
.ehlo_lhlo(&ehlo_name, path_config.use_lmtp)
.await
.with_context(|| format!("{address}:{port}: EHLO after banner"))?;
.with_context(|| format!("{address}:{port}: {helo_verb} after banner"))?;

// Use STARTTLS if available.
let has_tls = pretls_caps.contains_key("STARTTLS");
Expand Down Expand Up @@ -531,7 +532,7 @@ impl SmtpDispatcher {
// incorrectly roll over failed TLS into the following command,
// and we want to consider those as connection errors rather than
// having them show up per-message in MAIL FROM
match client.ehlo(&ehlo_name).await {
match client.ehlo_lhlo(&ehlo_name, path_config.use_lmtp).await {
Ok(_) => enabled,
Err(error) => {
self.remember_broken_tls(&dispatcher.name, &path_config);
Expand Down Expand Up @@ -603,9 +604,9 @@ impl SmtpDispatcher {
}

match client
.ehlo(&ehlo_name)
.ehlo_lhlo(&ehlo_name, path_config.use_lmtp)
.await
.with_context(|| format!("{address:?}:{port}: EHLO after STARTTLS"))
.with_context(|| format!("{address:?}:{port}: {helo_verb} after STARTTLS"))
{
Ok(_) => true,
Err(err) => {
Expand Down
2 changes: 1 addition & 1 deletion crates/kumod/src/smtp_server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1595,7 +1595,7 @@ impl SmtpServer {
self.write_response(250, "the goggles do nothing", None)
.await?;
}
Ok(Command::Vrfy(_) | Command::Expn(_) | Command::Help(_)) => {
Ok(Command::Vrfy(_) | Command::Expn(_) | Command::Help(_) | Command::Lhlo(_)) => {
self.write_response(502, format!("5.5.1 Command unimplemented"), Some(line))
.await?;
}
Expand Down
29 changes: 29 additions & 0 deletions crates/rfc5321/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -429,13 +429,42 @@ impl SmtpClient {
results
}

pub async fn ehlo_lhlo(
&mut self,
ehlo_name: &str,
use_lmtp: bool,
) -> Result<&HashMap<String, EsmtpCapability>, ClientError> {
if use_lmtp {
self.lhlo(ehlo_name).await
} else {
self.ehlo(ehlo_name).await
}
}

pub async fn lhlo(
&mut self,
ehlo_name: &str,
) -> Result<&HashMap<String, EsmtpCapability>, ClientError> {
let response = self
.send_command(&Command::Lhlo(Domain::Name(ehlo_name.to_string())))
.await?;
self.ehlo_common(response)
}

pub async fn ehlo(
&mut self,
ehlo_name: &str,
) -> Result<&HashMap<String, EsmtpCapability>, ClientError> {
let response = self
.send_command(&Command::Ehlo(Domain::Name(ehlo_name.to_string())))
.await?;
self.ehlo_common(response)
}

fn ehlo_common(
&mut self,
response: Response,
) -> Result<&HashMap<String, EsmtpCapability>, ClientError> {
if response.code != 250 {
return Err(ClientError::Rejected(response));
}
Expand Down
4 changes: 3 additions & 1 deletion crates/rfc5321/src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,7 @@ impl ToString for EsmtpParameter {
pub enum Command {
Ehlo(Domain),
Helo(Domain),
Lhlo(Domain),
MailFrom {
address: ReversePath,
parameters: Vec<EsmtpParameter>,
Expand Down Expand Up @@ -375,6 +376,7 @@ impl Command {
match self {
Self::Ehlo(domain) => format!("EHLO {}\r\n", domain.to_string()),
Self::Helo(domain) => format!("HELO {}\r\n", domain.to_string()),
Self::Lhlo(domain) => format!("LHLO {}\r\n", domain.to_string()),
Self::MailFrom {
address,
parameters,
Expand Down Expand Up @@ -424,7 +426,7 @@ impl Command {
/// Timeouts for reading the response
pub fn client_timeout(&self, timeouts: &SmtpClientTimeouts) -> Duration {
match self {
Self::Helo(_) | Self::Ehlo(_) => timeouts.ehlo_timeout,
Self::Helo(_) | Self::Ehlo(_) | Self::Lhlo(_) => timeouts.ehlo_timeout,
Self::MailFrom { .. } => timeouts.mail_from_timeout,
Self::RcptTo { .. } => timeouts.rcpt_to_timeout,
Self::Data { .. } => timeouts.data_timeout,
Expand Down
8 changes: 8 additions & 0 deletions docs/reference/kumo/make_egress_path/use_lmtp.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# use_lmtp

{{since('dev')}}

When set to `true` (the default is `false`), use the `LMTP` protocol
as described in [RFC 2033](https://www.rfc-editor.org/rfc/rfc2033)
rather than the `ESMTP` protocol.

0 comments on commit 8be4ad8

Please sign in to comment.