Skip to content

Commit 1561a60

Browse files
committed
Stricter RCPT fail2ban + Do not send SPF failures reports to local domains
1 parent b74e6fd commit 1561a60

File tree

3 files changed

+42
-20
lines changed

3 files changed

+42
-20
lines changed

crates/smtp/src/inbound/mail.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -542,6 +542,25 @@ impl<T: SessionStream> Session<T> {
542542
)
543543
.await,
544544
) {
545+
// Do not send SPF auth failures to local domains, as they are likely relay attempts (which are blocked later on)
546+
match self
547+
.server
548+
.core
549+
.storage
550+
.directory
551+
.is_local_domain(recipient.domain_part())
552+
.await
553+
{
554+
Ok(true) => return Ok(result),
555+
Ok(false) => (),
556+
Err(err) => {
557+
trc::error!(err
558+
.caused_by(trc::location!())
559+
.span_id(self.data.session_id)
560+
.details("Failed to lookup local domain"));
561+
}
562+
}
563+
545564
self.send_spf_report(recipient, &rate, !result, spf_output)
546565
.await;
547566
}

crates/smtp/src/inbound/rcpt.rs

Lines changed: 22 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -304,33 +304,36 @@ impl<T: SessionStream> Session<T> {
304304
async fn rcpt_error(&mut self, response: &[u8]) -> Result<(), ()> {
305305
tokio::time::sleep(self.params.rcpt_errors_wait).await;
306306
self.data.rcpt_errors += 1;
307-
self.write(response).await?;
308-
if self.data.rcpt_errors < self.params.rcpt_errors_max {
309-
Ok(())
310-
} else {
311-
match self.server.is_rcpt_fail2banned(self.data.remote_ip).await {
312-
Ok(true) => {
313-
trc::event!(
314-
Security(SecurityEvent::BruteForceBan),
315-
SpanId = self.data.session_id,
316-
RemoteIp = self.data.remote_ip,
317-
);
318-
}
319-
Ok(false) => {
307+
let has_too_many_errors = self.data.rcpt_errors >= self.params.rcpt_errors_max;
308+
309+
match self.server.is_rcpt_fail2banned(self.data.remote_ip).await {
310+
Ok(true) => {
311+
trc::event!(
312+
Security(SecurityEvent::BruteForceBan),
313+
SpanId = self.data.session_id,
314+
RemoteIp = self.data.remote_ip,
315+
);
316+
}
317+
Ok(false) => {
318+
if has_too_many_errors {
320319
trc::event!(
321320
Smtp(SmtpEvent::TooManyInvalidRcpt),
322321
SpanId = self.data.session_id,
323322
Limit = self.params.rcpt_errors_max,
324323
);
325324
}
326-
Err(err) => {
327-
trc::error!(err
328-
.span_id(self.data.session_id)
329-
.caused_by(trc::location!())
330-
.details("Failed to check if IP should be banned."));
331-
}
332325
}
326+
Err(err) => {
327+
trc::error!(err
328+
.span_id(self.data.session_id)
329+
.caused_by(trc::location!())
330+
.details("Failed to check if IP should be banned."));
331+
}
332+
}
333333

334+
if !has_too_many_errors {
335+
self.write(response).await
336+
} else {
334337
self.write(b"421 4.3.0 Too many errors, disconnecting.\r\n")
335338
.await?;
336339
Err(())

tests/src/jmap/auth_oauth.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ pub async fn test(params: &mut JMAPTest) {
103103
&OAuthCodeRequest::Code {
104104
client_id: client_id.to_string(),
105105
redirect_uri: "https://localhost".to_string().into(),
106+
nonce: "abc1234".to_string().into(),
106107
},
107108
)
108109
.await
@@ -136,7 +137,6 @@ pub async fn test(params: &mut JMAPTest) {
136137

137138
// Obtain token
138139
token_params.insert("redirect_uri".to_string(), "https://localhost".to_string());
139-
token_params.insert("nonce".to_string(), "abc1234".to_string());
140140
let (token, refresh_token, id_token) =
141141
unwrap_oidc_token_response(post(&metadata.token_endpoint, &token_params).await);
142142

0 commit comments

Comments
 (0)