Files
inbuxa-server/tests/src/smtp/inbound/data.rs
T
jcoffey-dev 7dae9b29fd Import upstream v0.16.22, stripped
Upstream commit: 474dd0229cb20cf513036619781ed97bd8073c3f
Enterprise-only files removed or emptied: 63
Enterprise-only snippets removed: 117 in 50 files
Dangling module declarations removed: 5
Cargo edits turning enterprise off: 14
Verification: clean
Enterprise feature gates left for rebuilt features: 19 in 18 files

Produced by tools/fork/strip.py. The full report is in docs/fork/strip-reports/ on main.
2026-09-18 10:21:56 -07:00

392 lines
12 KiB
Rust

/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use crate::{
smtp::{
inbound::TestMessage,
session::{TestSession, VerifyResponse, load_test_message},
},
utils::server::TestServerBuilder,
};
use registry::{
schema::{
enums::MtaQueueQuotaKey,
prelude::ObjectType,
structs::{
Expression, ExpressionMatch, MtaQueueQuota, MtaStageData, SenderAuth, SpamSettings,
},
},
types::{list::List, map::Map},
};
#[tokio::test]
async fn data() {
let mut test = TestServerBuilder::new("smtp_data_test")
.await
.with_http_listener(19004)
.await
.disable_services()
.capture_queue()
.build()
.await;
// Create test users
let admin = test.account("admin");
for (name, secret, description, aliases) in [
("[email protected]", "12345 + extra safety", "John Doe", &[]),
("[email protected]", "abcde + extra safety", "Jane Smith", &[]),
(
"[email protected]",
"p4ssw0rd + extra safety",
"Bill Foobar",
&[],
),
(
"[email protected]",
"p4ssw0rd + extra safety",
"Mike Foobar",
&[],
),
] {
admin
.create_user_account(name, secret, description, aliases, vec![])
.await;
}
// Add test settings
admin.mta_no_auth().await;
admin
.registry_create_object(SpamSettings {
enable: false,
..Default::default()
})
.await;
admin
.registry_create_object(SenderAuth {
dmarc_verify: Expression {
else_: "relaxed".into(),
..Default::default()
},
reverse_ip_verify: Expression {
else_: "relaxed".into(),
..Default::default()
},
spf_ehlo_verify: Expression {
else_: "relaxed".into(),
..Default::default()
},
spf_from_verify: Expression {
else_: "relaxed".into(),
..Default::default()
},
..Default::default()
})
.await;
admin
.registry_create_object(MtaStageData {
add_auth_results_header: Expression {
match_: List::from_iter([ExpressionMatch {
if_: "remote_ip = '10.0.0.3'".into(),
then: "true".into(),
}]),
else_: "false".into(),
},
add_date_header: Expression {
match_: List::from_iter([ExpressionMatch {
if_: "remote_ip = '10.0.0.3'".into(),
then: "true".into(),
}]),
else_: "false".into(),
},
add_message_id_header: Expression {
match_: List::from_iter([ExpressionMatch {
if_: "remote_ip = '10.0.0.3'".into(),
then: "true".into(),
}]),
else_: "false".into(),
},
add_received_header: Expression {
match_: List::from_iter([ExpressionMatch {
if_: "remote_ip = '10.0.0.3'".into(),
then: "true".into(),
}]),
else_: "false".into(),
},
add_received_spf_header: Expression {
match_: List::from_iter([ExpressionMatch {
if_: "remote_ip = '10.0.0.3'".into(),
then: "true".into(),
}]),
else_: "false".into(),
},
add_return_path_header: Expression {
match_: List::from_iter([ExpressionMatch {
if_: "remote_ip = '10.0.0.3'".into(),
then: "true".into(),
}]),
else_: "false".into(),
},
max_messages: Expression {
match_: List::from_iter([ExpressionMatch {
if_: "remote_ip = '10.0.0.1'".into(),
then: "1".into(),
}]),
else_: "100".into(),
},
max_received_headers: Expression {
else_: "3".into(),
..Default::default()
},
max_message_size: Expression {
match_: List::from_iter([
ExpressionMatch {
if_: "remote_ip = '10.0.0.4'".into(),
then: "100".into(),
},
ExpressionMatch {
if_: "remote_ip = '10.0.0.5'".into(),
then: "0".into(),
},
]),
else_: "104857600".into(),
},
..Default::default()
})
.await;
admin
.registry_create_object(MtaQueueQuota {
description: None,
enable: true,
key: Map::new(vec![MtaQueueQuotaKey::Sender]),
match_: Expression {
else_: "sender = '[email protected]'".into(),
..Default::default()
},
messages: Some(1),
size: None,
})
.await;
admin
.registry_create_object(MtaQueueQuota {
description: None,
enable: true,
key: Map::new(vec![MtaQueueQuotaKey::RcptDomain]),
match_: Expression {
else_: "rcpt_domain = 'foobar.org'".into(),
..Default::default()
},
messages: None,
size: Some(450),
})
.await;
admin
.registry_create_object(MtaQueueQuota {
description: None,
enable: true,
key: Map::new(vec![MtaQueueQuotaKey::Rcpt]),
match_: Expression {
else_: "rcpt = '[email protected]'".into(),
..Default::default()
},
messages: None,
size: Some(450),
})
.await;
admin.reload_settings().await;
test.reload_core();
test.expect_reload_settings().await;
// Test queue message builder
let mut session = test.new_mta_session();
session.data.remote_ip_str = "10.0.0.1".into();
session.eval_session_params().await;
session.test_builder().await;
// Send DATA without RCPT
session.ehlo("mx.doe.org").await;
session.ingest(b"DATA\r\n").await.unwrap();
session.response().assert_code("503 5.5.1");
// Send BDAT without MAIL FROM
session.ingest(b"BDAT 10\r\n0123456789").await.unwrap();
session
.response()
.assert_code("503 5.5.1")
.assert_contains("MAIL is required")
.assert_not_contains("552");
// Send BDAT without RCPT
session.mail_from("[email protected]", "250").await;
session.ingest(b"BDAT 10\r\n0123456789").await.unwrap();
session
.response()
.assert_code("503 5.5.1")
.assert_contains("RCPT is required")
.assert_not_contains("552");
session.rset().await;
// Send a BDAT chunk exceeding the maximum message size
let mut size_session = test.new_mta_session();
size_session.data.remote_ip_str = "10.0.0.4".into();
size_session.eval_session_params().await;
size_session.ehlo("mx.doe.org").await;
size_session.mail_from("[email protected]", "250").await;
size_session.rcpt_to("[email protected]", "250").await;
let mut chunk = b"BDAT 200 LAST\r\n".to_vec();
chunk.extend_from_slice(&[b'A'; 200]);
size_session.ingest(&chunk).await.unwrap();
size_session.response().assert_code("552 5.3.4");
size_session.rset().await;
// A maximum message size of zero disables the limit
size_session.data.remote_ip_str = "10.0.0.5".into();
size_session.eval_session_params().await;
size_session
.ingest(b"MAIL FROM:<[email protected]> SIZE=1073741824\r\n")
.await
.unwrap();
size_session.response().assert_code("250");
size_session.rset().await;
size_session
.send_message("[email protected]", &["[email protected]"], "test:no_dkim", "250")
.await;
test.expect_message().await;
// Send broken message
session
.send_message("[email protected]", &["[email protected]"], "invalid", "550 5.7.7")
.await;
// Naive Loop detection
session
.send_message(
"[email protected]",
&["[email protected]"],
"test:loop",
"450 4.4.6",
)
.await;
// No headers should be added to messages from 10.0.0.1
session
.send_message("[email protected]", &["[email protected]"], "test:no_msgid", "250")
.await;
assert_eq!(
test.expect_message().await.read_message(&test).await,
format!("{}\r\n", load_test_message("no_msgid", "messages"))
);
// Maximum one message per session is allowed for 10.0.0.1
session.mail_from("[email protected]", "250").await;
session.rcpt_to("[email protected]", "250").await;
session.ingest(b"DATA\r\n").await.unwrap();
session.response().assert_code("452 4.4.5");
session.rset().await;
// Headers should be added to messages from 10.0.0.3
session.data.remote_ip_str = "10.0.0.3".into();
session.eval_session_params().await;
session
.send_message("[email protected]", &["[email protected]"], "test:no_msgid", "250")
.await;
test.expect_message()
.await
.read_lines(&test)
.await
.assert_contains("From: ")
.assert_contains("To: ")
.assert_contains("Subject: ")
.assert_contains("Date: ")
.assert_contains("Message-ID: ")
.assert_contains("Return-Path: ")
.assert_contains("Received: ")
.assert_contains("Authentication-Results: ")
.assert_contains("Received-SPF: ");
// Send a message using multiple BDAT chunks
session.mail_from("[email protected]", "250").await;
session.rcpt_to("[email protected]", "250").await;
let message = load_test_message("no_msgid", "messages");
let (first, last) = message.as_bytes().split_at(message.len() / 2);
let mut chunk = format!("BDAT {}\r\n", first.len()).into_bytes();
chunk.extend_from_slice(first);
session.ingest(&chunk).await.unwrap();
session.response().assert_code("250 2.6.0");
let mut chunk = format!("BDAT {} LAST\r\n", last.len()).into_bytes();
chunk.extend_from_slice(last);
session.ingest(&chunk).await.unwrap();
session.response().assert_code("250");
test.expect_message()
.await
.read_lines(&test)
.await
.assert_contains("Subject: ")
.assert_contains("Received: ");
// Only one message is allowed in the queue from [email protected]
session.data.remote_ip_str = "10.0.0.2".into();
session.eval_session_params().await;
session
.send_message("[email protected]", &["[email protected]"], "test:no_dkim", "250")
.await;
session
.send_message(
"[email protected]",
&["[email protected]"],
"test:no_dkim",
"452 4.3.1",
)
.await;
// Release quota
test.clear_queue().await;
// Only 1500 bytes are allowed in the queue to domain foobar.org
session
.send_message(
"[email protected]",
&["[email protected]"],
"test:no_dkim",
"250",
)
.await;
session
.send_message(
"[email protected]",
&["[email protected]"],
"test:no_dkim",
"452 4.3.1",
)
.await;
// Only 1500 bytes are allowed in the queue to recipient [email protected]
session
.send_message(
"[email protected]",
&["[email protected]"],
"test:no_dkim",
"250",
)
.await;
session
.send_message(
"[email protected]",
&["[email protected]"],
"test:no_dkim",
"452 4.3.1",
)
.await;
// Make sure store is empty
test.clear_queue().await;
let admin = test.account("admin");
admin.registry_destroy_all(ObjectType::MtaQueueQuota).await;
admin
.registry_destroy_all(ObjectType::MtaInboundThrottle)
.await;
test.assert_is_empty().await;
}