Added retry_num, notify_num, last_error, last_status variables to queue expressions
This commit is contained in:
134
tests/src/smtp/outbound/fallback_relay.rs
Normal file
134
tests/src/smtp/outbound/fallback_relay.rs
Normal file
@@ -0,0 +1,134 @@
|
||||
/*
|
||||
* Copyright (c) 2023 Stalwart Labs Ltd.
|
||||
*
|
||||
* This file is part of Stalwart Mail Server.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
* in the LICENSE file at the top-level directory of this distribution.
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* You can be released from the requirements of the AGPLv3 license by
|
||||
* purchasing a commercial license. Please contact licensing@stalw.art
|
||||
* for more details.
|
||||
*/
|
||||
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use common::config::server::ServerProtocol;
|
||||
use mail_auth::MX;
|
||||
use store::write::now;
|
||||
|
||||
use crate::smtp::{outbound::TestServer, session::TestSession};
|
||||
|
||||
const LOCAL: &str = r#"
|
||||
[queue.outbound]
|
||||
next-hop = [{if = "retry_num > 0", then = "'fallback'"},
|
||||
{else = false}]
|
||||
|
||||
[session.rcpt]
|
||||
relay = true
|
||||
max-recipients = 100
|
||||
|
||||
[session.extensions]
|
||||
dsn = true
|
||||
|
||||
[remote.fallback]
|
||||
address = fallback.foobar.org
|
||||
port = 9925
|
||||
protocol = 'smtp'
|
||||
concurrency = 5
|
||||
|
||||
[remote.fallback.tls]
|
||||
implicit = false
|
||||
allow-invalid-certs = true
|
||||
|
||||
"#;
|
||||
|
||||
const REMOTE: &str = r#"
|
||||
[session.rcpt]
|
||||
relay = true
|
||||
|
||||
[session.ehlo]
|
||||
reject-non-fqdn = false
|
||||
|
||||
[session.extensions]
|
||||
dsn = true
|
||||
chunking = false
|
||||
"#;
|
||||
|
||||
#[tokio::test]
|
||||
#[serial_test::serial]
|
||||
async fn fallback_relay() {
|
||||
/*let disable = 1;
|
||||
tracing::subscriber::set_global_default(
|
||||
tracing_subscriber::FmtSubscriber::builder()
|
||||
.with_max_level(tracing::Level::TRACE)
|
||||
.finish(),
|
||||
)
|
||||
.unwrap();*/
|
||||
|
||||
// Start test server
|
||||
let mut remote = TestServer::new("smtp_fallback_remote", REMOTE, true).await;
|
||||
let _rx = remote.start(&[ServerProtocol::Smtp]).await;
|
||||
let mut local = TestServer::new("smtp_fallback_local", LOCAL, true).await;
|
||||
|
||||
// Add mock DNS entries
|
||||
let core = local.build_smtp();
|
||||
core.core.smtp.resolvers.dns.mx_add(
|
||||
"foobar.org",
|
||||
vec![MX {
|
||||
exchanges: vec!["_dns_error.foobar.org".to_string()],
|
||||
preference: 10,
|
||||
}],
|
||||
Instant::now() + Duration::from_secs(10),
|
||||
);
|
||||
/*core.core.smtp.resolvers.dns.ipv4_add(
|
||||
"unreachable.foobar.org",
|
||||
vec!["127.0.0.2".parse().unwrap()],
|
||||
Instant::now() + Duration::from_secs(10),
|
||||
);*/
|
||||
core.core.smtp.resolvers.dns.ipv4_add(
|
||||
"fallback.foobar.org",
|
||||
vec!["127.0.0.1".parse().unwrap()],
|
||||
Instant::now() + Duration::from_secs(10),
|
||||
);
|
||||
|
||||
let mut session = local.new_session();
|
||||
session.data.remote_ip_str = "10.0.0.1".to_string();
|
||||
session.eval_session_params().await;
|
||||
session.ehlo("mx.test.org").await;
|
||||
session
|
||||
.send_message("john@test.org", &["bill@foobar.org"], "test:no_dkim", "250")
|
||||
.await;
|
||||
local
|
||||
.qr
|
||||
.expect_message_then_deliver()
|
||||
.await
|
||||
.try_deliver(core.clone())
|
||||
.await;
|
||||
let mut retry = local.qr.expect_message().await;
|
||||
let prev_due = retry.domains[0].retry.due;
|
||||
let next_due = now();
|
||||
let queue_id = retry.id;
|
||||
retry.domains[0].retry.due = next_due;
|
||||
retry
|
||||
.save_changes(&core, prev_due.into(), next_due.into())
|
||||
.await;
|
||||
local
|
||||
.qr
|
||||
.delivery_attempt(queue_id)
|
||||
.await
|
||||
.try_deliver(core.clone())
|
||||
.await;
|
||||
tokio::time::sleep(Duration::from_millis(100)).await;
|
||||
remote.qr.expect_message().await;
|
||||
}
|
||||
@@ -42,6 +42,7 @@ use super::{
|
||||
|
||||
pub mod dane;
|
||||
pub mod extensions;
|
||||
pub mod fallback_relay;
|
||||
pub mod ip_lookup;
|
||||
pub mod lmtp;
|
||||
pub mod mta_sts;
|
||||
|
||||
@@ -33,7 +33,7 @@ use crate::smtp::{
|
||||
inbound::TestQueueEvent, outbound::TestServer, queue::manager::new_message,
|
||||
session::TestSession,
|
||||
};
|
||||
use smtp::queue::{Message, QueueEnvelope};
|
||||
use smtp::queue::{Domain, Message, QueueEnvelope, Schedule, Status};
|
||||
|
||||
const CONFIG: &str = r#"
|
||||
[session.rcpt]
|
||||
@@ -113,7 +113,7 @@ async fn throttle_outbound() {
|
||||
for t in &throttle.sender {
|
||||
core.is_allowed(
|
||||
t,
|
||||
&QueueEnvelope::test(&test_message, "", ""),
|
||||
&QueueEnvelope::test(&test_message, 0, ""),
|
||||
&mut in_flight,
|
||||
&span,
|
||||
)
|
||||
@@ -138,7 +138,7 @@ async fn throttle_outbound() {
|
||||
for t in &throttle.sender {
|
||||
core.is_allowed(
|
||||
t,
|
||||
&QueueEnvelope::test(&test_message, "", ""),
|
||||
&QueueEnvelope::test(&test_message, 0, ""),
|
||||
&mut in_flight,
|
||||
&span,
|
||||
)
|
||||
@@ -162,10 +162,17 @@ async fn throttle_outbound() {
|
||||
|
||||
// Expect concurrency throttle for recipient domain 'example.org'
|
||||
test_message.return_path_domain = "test.net".to_string();
|
||||
test_message.domains.push(Domain {
|
||||
domain: "example.org".to_string(),
|
||||
retry: Schedule::now(),
|
||||
notify: Schedule::now(),
|
||||
expires: 0,
|
||||
status: Status::Scheduled,
|
||||
});
|
||||
for t in &throttle.rcpt {
|
||||
core.is_allowed(
|
||||
t,
|
||||
&QueueEnvelope::test(&test_message, "example.org", ""),
|
||||
&QueueEnvelope::test(&test_message, 0, ""),
|
||||
&mut in_flight,
|
||||
&span,
|
||||
)
|
||||
@@ -191,11 +198,18 @@ async fn throttle_outbound() {
|
||||
local.qr.read_event().await.unwrap_on_hold();
|
||||
in_flight.clear();
|
||||
|
||||
// Expect rate limit throttle for recipient domain 'example.org'
|
||||
// Expect rate limit throttle for recipient domain 'example.net'
|
||||
test_message.domains.push(Domain {
|
||||
domain: "example.net".to_string(),
|
||||
retry: Schedule::now(),
|
||||
notify: Schedule::now(),
|
||||
expires: 0,
|
||||
status: Status::Scheduled,
|
||||
});
|
||||
for t in &throttle.rcpt {
|
||||
core.is_allowed(
|
||||
t,
|
||||
&QueueEnvelope::test(&test_message, "example.net", ""),
|
||||
&QueueEnvelope::test(&test_message, 1, ""),
|
||||
&mut in_flight,
|
||||
&span,
|
||||
)
|
||||
@@ -236,10 +250,17 @@ async fn throttle_outbound() {
|
||||
vec!["127.0.0.1".parse().unwrap()],
|
||||
Instant::now() + Duration::from_secs(10),
|
||||
);
|
||||
test_message.domains.push(Domain {
|
||||
domain: "test.org".to_string(),
|
||||
retry: Schedule::now(),
|
||||
notify: Schedule::now(),
|
||||
expires: 0,
|
||||
status: Status::Scheduled,
|
||||
});
|
||||
for t in &throttle.host {
|
||||
core.is_allowed(
|
||||
t,
|
||||
&QueueEnvelope::test(&test_message, "test.org", "mx.test.org"),
|
||||
&QueueEnvelope::test(&test_message, 2, "mx.test.org"),
|
||||
&mut in_flight,
|
||||
&span,
|
||||
)
|
||||
@@ -276,7 +297,7 @@ async fn throttle_outbound() {
|
||||
for t in &throttle.host {
|
||||
core.is_allowed(
|
||||
t,
|
||||
&QueueEnvelope::test(&test_message, "example.net", "mx.test.net"),
|
||||
&QueueEnvelope::test(&test_message, 1, "mx.test.net"),
|
||||
&mut in_flight,
|
||||
&span,
|
||||
)
|
||||
@@ -301,17 +322,18 @@ async fn throttle_outbound() {
|
||||
}
|
||||
|
||||
pub trait TestQueueEnvelope<'x> {
|
||||
fn test(message: &'x Message, domain: &'x str, mx: &'x str) -> Self;
|
||||
fn test(message: &'x Message, current_domain: usize, mx: &'x str) -> Self;
|
||||
}
|
||||
|
||||
impl<'x> TestQueueEnvelope<'x> for QueueEnvelope<'x> {
|
||||
fn test(message: &'x Message, domain: &'x str, mx: &'x str) -> Self {
|
||||
fn test(message: &'x Message, current_domain: usize, mx: &'x str) -> Self {
|
||||
QueueEnvelope {
|
||||
message,
|
||||
domain,
|
||||
mx,
|
||||
remote_ip: IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)),
|
||||
local_ip: IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)),
|
||||
current_domain,
|
||||
current_rcpt: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,7 +41,8 @@ relay = true
|
||||
hostname = "'badtls.foobar.org'"
|
||||
|
||||
[queue.outbound.tls]
|
||||
starttls = "optional"
|
||||
starttls = [ { if = "retry_num > 0 && last_error == 'tls'", then = "disable"},
|
||||
{ else = "optional" }]
|
||||
"#;
|
||||
|
||||
const REMOTE: &str = r#"
|
||||
@@ -104,7 +105,6 @@ async fn starttls_optional() {
|
||||
.try_deliver(core.clone())
|
||||
.await;
|
||||
let mut retry = local.qr.expect_message().await;
|
||||
assert!(retry.domains[0].disable_tls);
|
||||
let prev_due = retry.domains[0].retry.due;
|
||||
let next_due = now();
|
||||
let queue_id = retry.id;
|
||||
|
||||
@@ -96,7 +96,6 @@ async fn generate_dsn() {
|
||||
entity: "mx.domain.org".to_string(),
|
||||
details: "Connection timeout".to_string(),
|
||||
})),
|
||||
disable_tls: false,
|
||||
}],
|
||||
flags: 0,
|
||||
env_id: None,
|
||||
|
||||
@@ -165,7 +165,6 @@ fn domain(domain: &str, retry: u64, notify: u64, expires: u64) -> Domain {
|
||||
notify: Schedule::later(Duration::from_secs(notify)),
|
||||
expires: now() + expires,
|
||||
status: Status::Scheduled,
|
||||
disable_tls: false,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user