v0.3.2
This commit is contained in:
@@ -26,11 +26,11 @@ pub mod ldap;
|
||||
pub mod smtp;
|
||||
pub mod sql;
|
||||
|
||||
use directory::{config::ConfigDirectory, DirectoryConfig};
|
||||
use directory::{config::ConfigDirectory, AddressMapping, DirectoryConfig};
|
||||
use mail_send::Credentials;
|
||||
use rustls::{Certificate, PrivateKey, ServerConfig};
|
||||
use rustls_pemfile::{certs, pkcs8_private_keys};
|
||||
use std::{io::BufReader, sync::Arc};
|
||||
use std::{borrow::Cow, io::BufReader, sync::Arc};
|
||||
use tokio_rustls::TlsAcceptor;
|
||||
|
||||
const CONFIG: &str = r#"
|
||||
@@ -389,3 +389,49 @@ impl core::fmt::Debug for Item {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn address_mappings() {
|
||||
const MAPPINGS: &str = r#"
|
||||
[enable]
|
||||
catch-all = true
|
||||
subaddressing = true
|
||||
expected-sub = "john.doe@example.org"
|
||||
expected-catch = "@example.org"
|
||||
|
||||
[disable]
|
||||
catch-all = false
|
||||
subaddressing = false
|
||||
expected-sub = "john.doe+alias@example.org"
|
||||
expected-catch = false
|
||||
|
||||
[custom]
|
||||
catch-all = { map = "(.+)@(.+)$", to = "info@${2}" }
|
||||
subaddressing = { map = "^([^.]+)\.([^.]+)@(.+)$", to = "${2}@${3}" }
|
||||
expected-sub = "doe+alias@example.org"
|
||||
expected-catch = "info@example.org"
|
||||
"#;
|
||||
|
||||
let config = utils::config::Config::parse(MAPPINGS).unwrap();
|
||||
const ADDR: &str = "john.doe+alias@example.org";
|
||||
|
||||
for test in ["enable", "disable", "custom"] {
|
||||
let catch_all = AddressMapping::from_config(&config, (test, "catch-all")).unwrap();
|
||||
let subaddressing = AddressMapping::from_config(&config, (test, "subaddressing")).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
subaddressing.to_subaddress(ADDR),
|
||||
config.value_require((test, "expected-sub")).unwrap(),
|
||||
"failed subaddress for {test:?}"
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
catch_all.to_catch_all(ADDR),
|
||||
config
|
||||
.property_require::<Option<String>>((test, "expected-catch"))
|
||||
.unwrap()
|
||||
.map(Cow::Owned),
|
||||
"failed catch-all for {test:?}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,11 +21,11 @@
|
||||
* for more details.
|
||||
*/
|
||||
|
||||
use std::{fs, net::IpAddr, path::PathBuf, sync::Arc, time::Duration};
|
||||
use std::{borrow::Cow, fs, net::IpAddr, path::PathBuf, sync::Arc, time::Duration};
|
||||
|
||||
use tokio::net::TcpSocket;
|
||||
|
||||
use utils::config::{Config, Listener, Rate, Server, ServerProtocol};
|
||||
use utils::config::{Config, DynValue, Listener, Rate, Server, ServerProtocol};
|
||||
|
||||
use ahash::{AHashMap, AHashSet};
|
||||
use directory::{config::ConfigDirectory, Lookup};
|
||||
@@ -41,6 +41,20 @@ use smtp::{
|
||||
|
||||
use super::add_test_certs;
|
||||
|
||||
struct TestEnvelope {
|
||||
pub local_ip: IpAddr,
|
||||
pub remote_ip: IpAddr,
|
||||
pub sender_domain: String,
|
||||
pub sender: String,
|
||||
pub rcpt_domain: String,
|
||||
pub rcpt: String,
|
||||
pub helo_domain: String,
|
||||
pub authenticated_as: String,
|
||||
pub mx: String,
|
||||
pub listener_id: u16,
|
||||
pub priority: i16,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_conditions() {
|
||||
let mut file = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
||||
@@ -520,18 +534,139 @@ fn parse_servers() {
|
||||
}
|
||||
}
|
||||
|
||||
struct TestEnvelope {
|
||||
pub local_ip: IpAddr,
|
||||
pub remote_ip: IpAddr,
|
||||
pub sender_domain: String,
|
||||
pub sender: String,
|
||||
pub rcpt_domain: String,
|
||||
pub rcpt: String,
|
||||
pub helo_domain: String,
|
||||
pub authenticated_as: String,
|
||||
pub mx: String,
|
||||
pub listener_id: u16,
|
||||
pub priority: i16,
|
||||
#[tokio::test]
|
||||
async fn eval_if() {
|
||||
let mut file = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
||||
file.push("resources");
|
||||
file.push("smtp");
|
||||
file.push("config");
|
||||
file.push("rules-eval.toml");
|
||||
|
||||
let config = Config::parse(&fs::read_to_string(file).unwrap()).unwrap();
|
||||
let servers = vec![
|
||||
Server {
|
||||
id: "smtp".to_string(),
|
||||
internal_id: 123,
|
||||
..Default::default()
|
||||
},
|
||||
Server {
|
||||
id: "smtps".to_string(),
|
||||
internal_id: 456,
|
||||
..Default::default()
|
||||
},
|
||||
];
|
||||
let mut context = ConfigContext::new(&servers);
|
||||
context.directory = config.parse_directory().unwrap();
|
||||
let conditions = config.parse_conditions(&context).unwrap();
|
||||
|
||||
let envelope = TestEnvelope::from_config(&config);
|
||||
|
||||
for (key, conditions) in conditions {
|
||||
//println!("============= Testing {:?} ==================", key);
|
||||
let (_, expected_result) = key.rsplit_once('-').unwrap();
|
||||
assert_eq!(
|
||||
IfBlock {
|
||||
if_then: vec![IfThen {
|
||||
conditions,
|
||||
then: true
|
||||
}],
|
||||
default: false,
|
||||
}
|
||||
.eval(&envelope)
|
||||
.await,
|
||||
&expected_result.parse::<bool>().unwrap(),
|
||||
"failed for {key:?}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn eval_dynvalue() {
|
||||
let mut file = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
||||
file.push("resources");
|
||||
file.push("smtp");
|
||||
file.push("config");
|
||||
file.push("rules-dynvalue.toml");
|
||||
|
||||
let config = Config::parse(&fs::read_to_string(file).unwrap()).unwrap();
|
||||
let mut context = ConfigContext::new(&[]);
|
||||
context.directory = config.parse_directory().unwrap();
|
||||
|
||||
let envelope = TestEnvelope::from_config(&config);
|
||||
|
||||
for test_name in config.sub_keys("eval") {
|
||||
//println!("============= Testing {:?} ==================", key);
|
||||
let if_block = config
|
||||
.parse_if_block::<Option<DynValue>>(
|
||||
("eval", test_name, "test"),
|
||||
&context,
|
||||
&[
|
||||
EnvelopeKey::Recipient,
|
||||
EnvelopeKey::RecipientDomain,
|
||||
EnvelopeKey::Sender,
|
||||
EnvelopeKey::SenderDomain,
|
||||
EnvelopeKey::AuthenticatedAs,
|
||||
EnvelopeKey::Listener,
|
||||
EnvelopeKey::RemoteIp,
|
||||
EnvelopeKey::LocalIp,
|
||||
EnvelopeKey::Priority,
|
||||
EnvelopeKey::Mx,
|
||||
],
|
||||
)
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
let expected = config
|
||||
.property_require::<Option<String>>(("eval", test_name, "expect"))
|
||||
.unwrap()
|
||||
.map(Cow::Owned);
|
||||
|
||||
assert_eq!(
|
||||
if_block.eval_and_capture(&envelope).await.into_value(),
|
||||
expected,
|
||||
"failed for test {test_name:?}"
|
||||
);
|
||||
}
|
||||
|
||||
for test_name in config.sub_keys("maybe-eval") {
|
||||
//println!("============= Testing {:?} ==================", key);
|
||||
let if_block = config
|
||||
.parse_if_block::<Option<DynValue>>(
|
||||
("maybe-eval", test_name, "test"),
|
||||
&context,
|
||||
&[
|
||||
EnvelopeKey::Recipient,
|
||||
EnvelopeKey::RecipientDomain,
|
||||
EnvelopeKey::Sender,
|
||||
EnvelopeKey::SenderDomain,
|
||||
EnvelopeKey::AuthenticatedAs,
|
||||
EnvelopeKey::Listener,
|
||||
EnvelopeKey::RemoteIp,
|
||||
EnvelopeKey::LocalIp,
|
||||
EnvelopeKey::Priority,
|
||||
EnvelopeKey::Mx,
|
||||
],
|
||||
)
|
||||
.unwrap()
|
||||
.unwrap()
|
||||
.map_if_block(
|
||||
&context.directory.directories,
|
||||
("maybe-eval", test_name, "test"),
|
||||
"test",
|
||||
)
|
||||
.unwrap();
|
||||
let expected = config
|
||||
.value_require(("maybe-eval", test_name, "expect"))
|
||||
.unwrap();
|
||||
|
||||
assert!(if_block
|
||||
.eval_and_capture(&envelope)
|
||||
.await
|
||||
.into_value()
|
||||
.unwrap()
|
||||
.is_local_domain(expected)
|
||||
.await
|
||||
.unwrap());
|
||||
}
|
||||
}
|
||||
|
||||
impl Envelope for TestEnvelope {
|
||||
@@ -580,62 +715,22 @@ impl Envelope for TestEnvelope {
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn eval_if() {
|
||||
let mut file = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
||||
file.push("resources");
|
||||
file.push("smtp");
|
||||
file.push("config");
|
||||
file.push("rules-eval.toml");
|
||||
|
||||
let config = Config::parse(&fs::read_to_string(file).unwrap()).unwrap();
|
||||
let servers = vec![
|
||||
Server {
|
||||
id: "smtp".to_string(),
|
||||
internal_id: 123,
|
||||
..Default::default()
|
||||
},
|
||||
Server {
|
||||
id: "smtps".to_string(),
|
||||
internal_id: 456,
|
||||
..Default::default()
|
||||
},
|
||||
];
|
||||
let mut context = ConfigContext::new(&servers);
|
||||
context.directory = config.parse_directory().unwrap();
|
||||
let conditions = config.parse_conditions(&context).unwrap();
|
||||
|
||||
let envelope = TestEnvelope {
|
||||
local_ip: config.property_require("envelope.local-ip").unwrap(),
|
||||
remote_ip: config.property_require("envelope.remote-ip").unwrap(),
|
||||
sender_domain: config.property_require("envelope.sender-domain").unwrap(),
|
||||
sender: config.property_require("envelope.sender").unwrap(),
|
||||
rcpt_domain: config.property_require("envelope.rcpt-domain").unwrap(),
|
||||
rcpt: config.property_require("envelope.rcpt").unwrap(),
|
||||
authenticated_as: config
|
||||
.property_require("envelope.authenticated-as")
|
||||
.unwrap(),
|
||||
mx: config.property_require("envelope.mx").unwrap(),
|
||||
listener_id: config.property_require("envelope.listener").unwrap(),
|
||||
priority: config.property_require("envelope.priority").unwrap(),
|
||||
helo_domain: config.property_require("envelope.helo-domain").unwrap(),
|
||||
};
|
||||
|
||||
for (key, conditions) in conditions {
|
||||
//println!("============= Testing {:?} ==================", key);
|
||||
let (_, expected_result) = key.rsplit_once('-').unwrap();
|
||||
assert_eq!(
|
||||
IfBlock {
|
||||
if_then: vec![IfThen {
|
||||
conditions,
|
||||
then: true
|
||||
}],
|
||||
default: false,
|
||||
}
|
||||
.eval(&envelope)
|
||||
.await,
|
||||
&expected_result.parse::<bool>().unwrap(),
|
||||
"failed for {key:?}"
|
||||
);
|
||||
impl TestEnvelope {
|
||||
pub fn from_config(config: &Config) -> Self {
|
||||
Self {
|
||||
local_ip: config.property_require("envelope.local-ip").unwrap(),
|
||||
remote_ip: config.property_require("envelope.remote-ip").unwrap(),
|
||||
sender_domain: config.property_require("envelope.sender-domain").unwrap(),
|
||||
sender: config.property_require("envelope.sender").unwrap(),
|
||||
rcpt_domain: config.property_require("envelope.rcpt-domain").unwrap(),
|
||||
rcpt: config.property_require("envelope.rcpt").unwrap(),
|
||||
authenticated_as: config
|
||||
.property_require("envelope.authenticated-as")
|
||||
.unwrap(),
|
||||
mx: config.property_require("envelope.mx").unwrap(),
|
||||
listener_id: config.property_require("envelope.listener").unwrap(),
|
||||
priority: config.property_require("envelope.priority").unwrap(),
|
||||
helo_domain: config.property_require("envelope.helo-domain").unwrap(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
|
||||
use directory::config::ConfigDirectory;
|
||||
use smtp_proto::{AUTH_LOGIN, AUTH_PLAIN};
|
||||
use utils::config::Config;
|
||||
use utils::config::{Config, DynValue};
|
||||
|
||||
use crate::smtp::{
|
||||
session::{TestSession, VerifyResponse},
|
||||
@@ -68,7 +68,7 @@ async fn auth() {
|
||||
.parse_if(&ctx);
|
||||
config.directory = r"[{if = 'remote-ip', eq = '10.0.0.1', then = 'local'},
|
||||
{else = false}]"
|
||||
.parse_if::<Option<String>>(&ctx)
|
||||
.parse_if::<Option<DynValue>>(&ctx)
|
||||
.map_if_block(&ctx.directory.directories, "", "")
|
||||
.unwrap();
|
||||
config.errors_max = r"[{if = 'remote-ip', eq = '10.0.0.1', then = 2},
|
||||
|
||||
@@ -30,7 +30,7 @@ use crate::smtp::{
|
||||
ParseTestConfig, TestConfig, TestSMTP,
|
||||
};
|
||||
use smtp::{
|
||||
config::{ConfigContext, IfBlock},
|
||||
config::{ConfigContext, IfBlock, MaybeDynValue},
|
||||
core::{Session, SMTP},
|
||||
};
|
||||
|
||||
@@ -81,7 +81,9 @@ async fn data() {
|
||||
let mut qr = core.init_test_queue("smtp_data_test");
|
||||
let directory = Config::parse(DIRECTORY).unwrap().parse_directory().unwrap();
|
||||
let mut config = &mut core.session.config.rcpt;
|
||||
config.directory = IfBlock::new(Some(directory.directories.get("local").unwrap().clone()));
|
||||
config.directory = IfBlock::new(Some(MaybeDynValue::Static(
|
||||
directory.directories.get("local").unwrap().clone(),
|
||||
)));
|
||||
|
||||
let mut config = &mut core.session.config;
|
||||
config.data.add_auth_results = "[{if = 'remote-ip', eq = '10.0.0.3', then = true},
|
||||
|
||||
@@ -34,7 +34,7 @@ use mail_auth::{
|
||||
report::DmarcResult,
|
||||
spf::Spf,
|
||||
};
|
||||
use utils::config::{Config, Rate};
|
||||
use utils::config::{Config, DynValue, Rate};
|
||||
|
||||
use crate::smtp::{
|
||||
inbound::{sign::TextConfigContext, TestMessage, TestQueueEvent, TestReportingEvent},
|
||||
@@ -42,7 +42,7 @@ use crate::smtp::{
|
||||
ParseTestConfig, TestConfig, TestSMTP,
|
||||
};
|
||||
use smtp::{
|
||||
config::{AggregateFrequency, ConfigContext, IfBlock, VerifyStrategy},
|
||||
config::{AggregateFrequency, ConfigContext, IfBlock, MaybeDynValue, VerifyStrategy},
|
||||
core::{Session, SMTP},
|
||||
};
|
||||
|
||||
@@ -134,7 +134,9 @@ async fn dmarc() {
|
||||
let mut rr = core.init_test_report();
|
||||
let directory = Config::parse(DIRECTORY).unwrap().parse_directory().unwrap();
|
||||
let mut config = &mut core.session.config.rcpt;
|
||||
config.directory = IfBlock::new(Some(directory.directories.get("local").unwrap().clone()));
|
||||
config.directory = IfBlock::new(Some(MaybeDynValue::Static(
|
||||
directory.directories.get("local").unwrap().clone(),
|
||||
)));
|
||||
|
||||
let mut config = &mut core.session.config;
|
||||
config.data.add_auth_results = IfBlock::new(true);
|
||||
@@ -166,11 +168,17 @@ async fn dmarc() {
|
||||
|
||||
let mut config = &mut core.report.config;
|
||||
config.spf.sign = "['rsa']"
|
||||
.parse_if::<Vec<String>>(&ctx)
|
||||
.parse_if::<Vec<DynValue>>(&ctx)
|
||||
.map_if_block(&ctx.signers, "", "")
|
||||
.unwrap();
|
||||
config.dmarc.sign = "['rsa']"
|
||||
.parse_if::<Vec<DynValue>>(&ctx)
|
||||
.map_if_block(&ctx.signers, "", "")
|
||||
.unwrap();
|
||||
config.dkim.sign = "['rsa']"
|
||||
.parse_if::<Vec<DynValue>>(&ctx)
|
||||
.map_if_block(&ctx.signers, "", "")
|
||||
.unwrap();
|
||||
config.dmarc.sign = config.spf.sign.clone();
|
||||
config.dkim.sign = config.spf.sign.clone();
|
||||
|
||||
// SPF must pass
|
||||
let core = Arc::new(core);
|
||||
|
||||
@@ -212,7 +212,7 @@ fn milter_address_modifications() {
|
||||
|
||||
// ChangeFrom
|
||||
assert!(data
|
||||
.apply_modifications(
|
||||
.apply_milter_modifications(
|
||||
vec![Modification::ChangeFrom {
|
||||
sender: "<>".to_string(),
|
||||
args: String::new()
|
||||
@@ -227,7 +227,7 @@ fn milter_address_modifications() {
|
||||
|
||||
// ChangeFrom with parameters
|
||||
assert!(data
|
||||
.apply_modifications(
|
||||
.apply_milter_modifications(
|
||||
vec![Modification::ChangeFrom {
|
||||
sender: "john@example.org".to_string(),
|
||||
args: "REQUIRETLS ENVID=abc123".to_string(), //"NOTIFY=SUCCESS,FAILURE ENVID=abc123\n".to_string()
|
||||
@@ -242,7 +242,7 @@ fn milter_address_modifications() {
|
||||
|
||||
// Add recipients
|
||||
assert!(data
|
||||
.apply_modifications(
|
||||
.apply_milter_modifications(
|
||||
vec![
|
||||
Modification::AddRcpt {
|
||||
recipient: "bill@example.org".to_string(),
|
||||
@@ -276,7 +276,7 @@ fn milter_address_modifications() {
|
||||
|
||||
// Remove recipients
|
||||
assert!(data
|
||||
.apply_modifications(
|
||||
.apply_milter_modifications(
|
||||
vec![
|
||||
Modification::DeleteRcpt {
|
||||
recipient: "bill@example.org".to_string(),
|
||||
@@ -319,7 +319,7 @@ fn milter_message_modifications() {
|
||||
test.result,
|
||||
String::from_utf8(
|
||||
session_data
|
||||
.apply_modifications(test.modifications, &parsed_test_message)
|
||||
.apply_milter_modifications(test.modifications, &parsed_test_message)
|
||||
.unwrap()
|
||||
)
|
||||
.unwrap()
|
||||
|
||||
@@ -42,6 +42,7 @@ pub mod limits;
|
||||
pub mod mail;
|
||||
pub mod milter;
|
||||
pub mod rcpt;
|
||||
pub mod rewrite;
|
||||
pub mod scripts;
|
||||
pub mod sign;
|
||||
pub mod throttle;
|
||||
|
||||
@@ -32,7 +32,7 @@ use crate::smtp::{
|
||||
ParseTestConfig, TestConfig,
|
||||
};
|
||||
use smtp::{
|
||||
config::{ConfigContext, IfBlock},
|
||||
config::{ConfigContext, IfBlock, MaybeDynValue},
|
||||
core::{Session, State, SMTP},
|
||||
};
|
||||
|
||||
@@ -75,7 +75,9 @@ async fn rcpt() {
|
||||
let mut config_ext = &mut core.session.config.extensions;
|
||||
let directory = Config::parse(DIRECTORY).unwrap().parse_directory().unwrap();
|
||||
let mut config = &mut core.session.config.rcpt;
|
||||
config.directory = IfBlock::new(Some(directory.directories.get("local").unwrap().clone()));
|
||||
config.directory = IfBlock::new(Some(MaybeDynValue::Static(
|
||||
directory.directories.get("local").unwrap().clone(),
|
||||
)));
|
||||
config.max_recipients = r"[{if = 'remote-ip', eq = '10.0.0.1', then = 3},
|
||||
{else = 5}]"
|
||||
.parse_if(&ConfigContext::new(&[]));
|
||||
|
||||
169
tests/src/smtp/inbound/rewrite.rs
Normal file
169
tests/src/smtp/inbound/rewrite.rs
Normal file
@@ -0,0 +1,169 @@
|
||||
/*
|
||||
* 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 crate::smtp::{inbound::sign::TextConfigContext, session::TestSession, TestConfig};
|
||||
use directory::config::ConfigDirectory;
|
||||
use smtp::{
|
||||
config::{if_block::ConfigIf, scripts::ConfigSieve, ConfigContext, EnvelopeKey, IfBlock},
|
||||
core::{Session, SMTP},
|
||||
};
|
||||
use utils::config::{Config, DynValue};
|
||||
|
||||
const CONFIG: &str = r#"
|
||||
[session.mail]
|
||||
rewrite = [ { all-of = [ { if = "sender-domain", ends-with = ".foobar.net" },
|
||||
{ if = "sender", matches = "^([^.]+)@([^.]+)\.(.+)$"},
|
||||
], then = "${1}+${2}@${3}" },
|
||||
{ else = false } ]
|
||||
script = [ { if = "sender-domain", eq = "foobar.org", then = "mail" },
|
||||
{ else = false } ]
|
||||
|
||||
[session.rcpt]
|
||||
rewrite = [ { all-of = [ { if = "rcpt-domain", eq = "foobar.net" },
|
||||
{ if = "rcpt", matches = "^([^.]+)\.([^.]+)@(.+)$"},
|
||||
], then = "${1}+${2}@${3}" },
|
||||
{ else = false } ]
|
||||
script = [ { if = "rcpt-domain", eq = "foobar.org", then = "rcpt" },
|
||||
{ else = false } ]
|
||||
|
||||
[sieve]
|
||||
from-name = "Sieve Daemon"
|
||||
from-addr = "sieve@foobar.org"
|
||||
return-path = ""
|
||||
hostname = "mx.foobar.org"
|
||||
|
||||
[sieve.limits]
|
||||
redirects = 3
|
||||
out-messages = 5
|
||||
received-headers = 50
|
||||
cpu = 10000
|
||||
nested-includes = 5
|
||||
duplicate-expiry = "7d"
|
||||
|
||||
[sieve.scripts]
|
||||
mail = '''
|
||||
require ["variables", "envelope"];
|
||||
|
||||
if allof( envelope :domain :is "from" "foobar.org",
|
||||
envelope :localpart :contains "from" "admin" ) {
|
||||
set "envelope.from" "MAILER-DAEMON@foobar.org";
|
||||
}
|
||||
|
||||
'''
|
||||
|
||||
rcpt = '''
|
||||
require ["variables", "envelope", "regex"];
|
||||
|
||||
if allof( envelope :localpart :contains "to" ".",
|
||||
envelope :regex "to" "(.+)@(.+)$") {
|
||||
set :replace "." "" "to" "${1}";
|
||||
set "envelope.to" "${to}@${2}";
|
||||
}
|
||||
|
||||
'''
|
||||
|
||||
"#;
|
||||
|
||||
#[tokio::test]
|
||||
async fn address_rewrite() {
|
||||
/*tracing::subscriber::set_global_default(
|
||||
tracing_subscriber::FmtSubscriber::builder()
|
||||
.with_max_level(tracing::Level::TRACE)
|
||||
.finish(),
|
||||
)
|
||||
.unwrap();*/
|
||||
|
||||
// Prepare config
|
||||
let available_keys = [
|
||||
EnvelopeKey::Sender,
|
||||
EnvelopeKey::SenderDomain,
|
||||
EnvelopeKey::Recipient,
|
||||
EnvelopeKey::RecipientDomain,
|
||||
];
|
||||
let mut core = SMTP::test();
|
||||
let mut ctx = ConfigContext::new(&[]).parse_signatures();
|
||||
let settings = Config::parse(CONFIG).unwrap();
|
||||
ctx.directory = settings.parse_directory().unwrap();
|
||||
core.sieve = settings.parse_sieve(&mut ctx).unwrap();
|
||||
let config = &mut core.session.config;
|
||||
config.mail.script = settings
|
||||
.parse_if_block::<Option<String>>("session.mail.script", &ctx, &available_keys)
|
||||
.unwrap()
|
||||
.unwrap_or_default()
|
||||
.map_if_block(&ctx.scripts, "session.mail.script", "script")
|
||||
.unwrap();
|
||||
config.mail.rewrite = settings
|
||||
.parse_if_block::<Option<DynValue>>("session.mail.rewrite", &ctx, &available_keys)
|
||||
.unwrap()
|
||||
.unwrap_or_default();
|
||||
config.rcpt.script = settings
|
||||
.parse_if_block::<Option<String>>("session.rcpt.script", &ctx, &available_keys)
|
||||
.unwrap()
|
||||
.unwrap_or_default()
|
||||
.map_if_block(&ctx.scripts, "session.rcpt.script", "script")
|
||||
.unwrap();
|
||||
config.rcpt.rewrite = settings
|
||||
.parse_if_block::<Option<DynValue>>("session.rcpt.rewrite", &ctx, &available_keys)
|
||||
.unwrap()
|
||||
.unwrap_or_default();
|
||||
config.rcpt.relay = IfBlock::new(true);
|
||||
|
||||
// Init session
|
||||
let mut session = Session::test(core);
|
||||
session.data.remote_ip = "10.0.0.1".parse().unwrap();
|
||||
session.eval_session_params().await;
|
||||
session.ehlo("mx.doe.org").await;
|
||||
|
||||
// Sender rewrite using regex
|
||||
session.mail_from("bill@doe.foobar.net", "250").await;
|
||||
assert_eq!(
|
||||
session.data.mail_from.as_ref().unwrap().address,
|
||||
"bill+doe@foobar.net"
|
||||
);
|
||||
session.reset();
|
||||
|
||||
// Sender rewrite using sieve
|
||||
session.mail_from("this_is_admin@foobar.org", "250").await;
|
||||
assert_eq!(
|
||||
session.data.mail_from.as_ref().unwrap().address_lcase,
|
||||
"mailer-daemon@foobar.org"
|
||||
);
|
||||
|
||||
// Recipient rewrite using regex
|
||||
session.rcpt_to("mary.smith@foobar.net", "250").await;
|
||||
assert_eq!(
|
||||
session.data.rcpt_to.last().unwrap().address,
|
||||
"mary+smith@foobar.net"
|
||||
);
|
||||
|
||||
// Remove duplicates
|
||||
session.rcpt_to("mary.smith@foobar.net", "250").await;
|
||||
assert_eq!(session.data.rcpt_to.len(), 1);
|
||||
|
||||
// Recipient rewrite using sieve
|
||||
session.rcpt_to("m.a.r.y.s.m.i.t.h@foobar.org", "250").await;
|
||||
assert_eq!(
|
||||
session.data.rcpt_to.last().unwrap().address,
|
||||
"marysmith@foobar.org"
|
||||
);
|
||||
}
|
||||
@@ -28,7 +28,7 @@ use mail_auth::{
|
||||
common::{parse::TxtRecordParser, verify::DomainKey},
|
||||
spf::Spf,
|
||||
};
|
||||
use utils::config::Config;
|
||||
use utils::config::{Config, DynValue};
|
||||
|
||||
use crate::smtp::{
|
||||
inbound::{TestMessage, TestQueueEvent},
|
||||
@@ -36,7 +36,7 @@ use crate::smtp::{
|
||||
ParseTestConfig, TestConfig, TestSMTP,
|
||||
};
|
||||
use smtp::{
|
||||
config::{auth::ConfigAuth, ConfigContext, IfBlock, VerifyStrategy},
|
||||
config::{auth::ConfigAuth, ConfigContext, IfBlock, MaybeDynValue, VerifyStrategy},
|
||||
core::{Session, SMTP},
|
||||
};
|
||||
|
||||
@@ -81,7 +81,9 @@ report = true
|
||||
|
||||
[signature.ed]
|
||||
public-key = '11qYAYKxCrfVS/7TyWQHOg7hcvPapiMlrwIaaPcHURo='
|
||||
private-key = 'nWGxne/9WmC6hEr0kuwsxERJxWl7MmkZcDusAxyuf2A='
|
||||
private-key = '-----BEGIN PRIVATE KEY-----
|
||||
nWGxne/9WmC6hEr0kuwsxERJxWl7MmkZcDusAxyuf2A=
|
||||
-----END PRIVATE KEY-----'
|
||||
domain = 'example.com'
|
||||
selector = 'ed'
|
||||
headers = ['From', 'To', 'Date', 'Subject', 'Message-ID']
|
||||
@@ -152,7 +154,9 @@ async fn sign_and_seal() {
|
||||
|
||||
let directory = Config::parse(DIRECTORY).unwrap().parse_directory().unwrap();
|
||||
let mut config = &mut core.session.config.rcpt;
|
||||
config.directory = IfBlock::new(Some(directory.directories.get("local").unwrap().clone()));
|
||||
config.directory = IfBlock::new(Some(MaybeDynValue::Static(
|
||||
directory.directories.get("local").unwrap().clone(),
|
||||
)));
|
||||
|
||||
let mut config = &mut core.session.config;
|
||||
config.data.add_auth_results = IfBlock::new(true);
|
||||
@@ -170,11 +174,11 @@ async fn sign_and_seal() {
|
||||
config.arc.verify = config.spf.verify_ehlo.clone();
|
||||
config.dmarc.verify = config.spf.verify_ehlo.clone();
|
||||
config.dkim.sign = "['rsa']"
|
||||
.parse_if::<Vec<String>>(&ctx)
|
||||
.parse_if::<Vec<DynValue>>(&ctx)
|
||||
.map_if_block(&ctx.signers, "", "")
|
||||
.unwrap();
|
||||
config.arc.seal = "'ed'"
|
||||
.parse_if::<Option<String>>(&ctx)
|
||||
.parse_if::<Option<DynValue>>(&ctx)
|
||||
.map_if_block(&ctx.sealers, "", "")
|
||||
.unwrap();
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ use crate::smtp::{
|
||||
ParseTestConfig, TestConfig,
|
||||
};
|
||||
use smtp::{
|
||||
config::{ConfigContext, IfBlock},
|
||||
config::{ConfigContext, IfBlock, MaybeDynValue},
|
||||
core::{Session, SMTP},
|
||||
};
|
||||
|
||||
@@ -67,7 +67,9 @@ async fn vrfy_expn() {
|
||||
|
||||
let directory = Config::parse(DIRECTORY).unwrap().parse_directory().unwrap();
|
||||
let mut config = &mut core.session.config.rcpt;
|
||||
config.directory = IfBlock::new(Some(directory.directories.get("local").unwrap().clone()));
|
||||
config.directory = IfBlock::new(Some(MaybeDynValue::Static(
|
||||
directory.directories.get("local").unwrap().clone(),
|
||||
)));
|
||||
|
||||
let mut config = &mut core.session.config.extensions;
|
||||
config.vrfy = r"[{if = 'remote-ip', eq = '10.0.0.1', then = true},
|
||||
|
||||
@@ -25,7 +25,7 @@ use std::time::Duration;
|
||||
|
||||
use directory::config::ConfigDirectory;
|
||||
use smtp_proto::{AUTH_LOGIN, AUTH_PLAIN};
|
||||
use utils::config::Config;
|
||||
use utils::config::{Config, DynValue};
|
||||
|
||||
use crate::{
|
||||
directory::sql::{create_test_directory, create_test_user_with_email, link_test_address},
|
||||
@@ -115,7 +115,7 @@ async fn lookup_sql() {
|
||||
// Enable AUTH
|
||||
let mut config = &mut core.session.config.auth;
|
||||
config.directory = r"'sql'"
|
||||
.parse_if::<Option<String>>(&ctx)
|
||||
.parse_if::<Option<DynValue>>(&ctx)
|
||||
.map_if_block(&ctx.directory.directories, "", "")
|
||||
.unwrap();
|
||||
config.mechanisms = IfBlock::new(AUTH_PLAIN | AUTH_LOGIN);
|
||||
@@ -124,7 +124,7 @@ async fn lookup_sql() {
|
||||
// Enable VRFY/EXPN/RCPT
|
||||
let mut config = &mut core.session.config.rcpt;
|
||||
config.directory = r"'sql'"
|
||||
.parse_if::<Option<String>>(&ctx)
|
||||
.parse_if::<Option<DynValue>>(&ctx)
|
||||
.map_if_block(&ctx.directory.directories, "", "")
|
||||
.unwrap();
|
||||
config.relay = IfBlock::new(false);
|
||||
|
||||
@@ -239,6 +239,7 @@ impl TestConfig for SessionConfig {
|
||||
},
|
||||
mail: Mail {
|
||||
script: IfBlock::new(None),
|
||||
rewrite: IfBlock::new(None),
|
||||
},
|
||||
rcpt: Rcpt {
|
||||
script: IfBlock::new(None),
|
||||
@@ -247,6 +248,7 @@ impl TestConfig for SessionConfig {
|
||||
errors_max: IfBlock::new(3),
|
||||
errors_wait: IfBlock::new(Duration::from_secs(1)),
|
||||
max_recipients: IfBlock::new(3),
|
||||
rewrite: IfBlock::new(None),
|
||||
},
|
||||
data: Data {
|
||||
script: IfBlock::new(None),
|
||||
|
||||
@@ -29,6 +29,7 @@ use std::{
|
||||
|
||||
use smtp_proto::{Response, RCPT_NOTIFY_DELAY, RCPT_NOTIFY_FAILURE, RCPT_NOTIFY_SUCCESS};
|
||||
use tokio::{fs::File, io::AsyncReadExt};
|
||||
use utils::config::DynValue;
|
||||
|
||||
use crate::smtp::{
|
||||
inbound::{sign::TextConfigContext, TestQueueEvent},
|
||||
@@ -109,7 +110,7 @@ async fn generate_dsn() {
|
||||
let ctx = ConfigContext::new(&[]).parse_signatures();
|
||||
let mut config = &mut core.queue.config.dsn;
|
||||
config.sign = "['rsa']"
|
||||
.parse_if::<Vec<String>>(&ctx)
|
||||
.parse_if::<Vec<DynValue>>(&ctx)
|
||||
.map_if_block(&ctx.signers, "", "")
|
||||
.unwrap();
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@ use mail_auth::{
|
||||
dmarc::Dmarc,
|
||||
report::{ActionDisposition, Disposition, DmarcResult, Record, Report},
|
||||
};
|
||||
use utils::config::DynValue;
|
||||
|
||||
use crate::smtp::{
|
||||
inbound::{sign::TextConfigContext, TestMessage, TestQueueEvent},
|
||||
@@ -66,7 +67,7 @@ async fn report_dmarc() {
|
||||
config.path = IfBlock::new(temp_dir.temp_dir.clone());
|
||||
config.hash = IfBlock::new(16);
|
||||
config.dmarc_aggregate.sign = "['rsa']"
|
||||
.parse_if::<Vec<String>>(&ctx)
|
||||
.parse_if::<Vec<DynValue>>(&ctx)
|
||||
.map_if_block(&ctx.signers, "", "")
|
||||
.unwrap();
|
||||
config.dmarc_aggregate.max_size = IfBlock::new(4096);
|
||||
|
||||
@@ -29,6 +29,7 @@ use mail_auth::{
|
||||
mta_sts::TlsRpt,
|
||||
report::tlsrpt::{FailureDetails, PolicyType, ResultType, TlsReport},
|
||||
};
|
||||
use utils::config::DynValue;
|
||||
|
||||
use crate::smtp::{
|
||||
inbound::{sign::TextConfigContext, TestMessage, TestQueueEvent},
|
||||
@@ -63,7 +64,7 @@ async fn report_tls() {
|
||||
config.path = IfBlock::new(temp_dir.temp_dir.clone());
|
||||
config.hash = IfBlock::new(16);
|
||||
config.tls.sign = "['rsa']"
|
||||
.parse_if::<Vec<String>>(&ctx)
|
||||
.parse_if::<Vec<DynValue>>(&ctx)
|
||||
.map_if_block(&ctx.signers, "", "")
|
||||
.unwrap();
|
||||
config.tls.max_size = IfBlock::new(4096);
|
||||
|
||||
Reference in New Issue
Block a user