mirror of
https://github.com/chatmail/relay.git
synced 2026-08-17 22:10:51 +00:00
feat!: remove passthrough options that allowed unencrypted mail to pass
see https://github.com/chatmail/relay/pull/970/ for the related removal of chatmail.ini options
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
//! Configuration file handling for filtermail.
|
//! Configuration file handling for filtermail.
|
||||||
|
|
||||||
use serde::{Deserialize, Deserializer};
|
use serde::Deserialize;
|
||||||
use std::net::IpAddr;
|
use std::net::IpAddr;
|
||||||
use std::num::NonZeroU32;
|
use std::num::NonZeroU32;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
@@ -30,10 +30,6 @@ pub struct Config {
|
|||||||
pub max_user_send_per_minute: NonZeroU32,
|
pub max_user_send_per_minute: NonZeroU32,
|
||||||
#[serde(default = "Config::default_max_user_send_burst_size")]
|
#[serde(default = "Config::default_max_user_send_burst_size")]
|
||||||
pub max_user_send_burst_size: NonZeroU32,
|
pub max_user_send_burst_size: NonZeroU32,
|
||||||
#[serde(default, deserialize_with = "deserialize_sequence")]
|
|
||||||
pub passthrough_senders: Vec<String>,
|
|
||||||
#[serde(default, deserialize_with = "deserialize_sequence")]
|
|
||||||
pub passthrough_recipients: Vec<String>,
|
|
||||||
pub mail_domain: String,
|
pub mail_domain: String,
|
||||||
mailboxes_dir: Option<PathBuf>,
|
mailboxes_dir: Option<PathBuf>,
|
||||||
}
|
}
|
||||||
@@ -44,22 +40,6 @@ struct ConfigWrapper {
|
|||||||
pub params: Config,
|
pub params: Config,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Custom deserializer to parse space-separated strings into [`Vec<String>`].
|
|
||||||
fn deserialize_sequence<'de, D>(deserializer: D) -> Result<Vec<String>, D::Error>
|
|
||||||
where
|
|
||||||
D: Deserializer<'de>,
|
|
||||||
{
|
|
||||||
let s: Option<String> = Deserialize::deserialize(deserializer)?;
|
|
||||||
Ok(match s {
|
|
||||||
Some(v) => v
|
|
||||||
.split(' ')
|
|
||||||
.map(|item| item.trim().to_string())
|
|
||||||
.filter(|item| !item.is_empty())
|
|
||||||
.collect(),
|
|
||||||
None => Vec::new(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Config {
|
impl Config {
|
||||||
/// Load configuration from a file.
|
/// Load configuration from a file.
|
||||||
pub fn from_file(path: impl AsRef<Path>) -> Result<Self, crate::error::Error> {
|
pub fn from_file(path: impl AsRef<Path>) -> Result<Self, crate::error::Error> {
|
||||||
@@ -156,8 +136,6 @@ impl Default for Config {
|
|||||||
max_message_size: Self::default_max_message_size(),
|
max_message_size: Self::default_max_message_size(),
|
||||||
max_user_send_per_minute: Self::default_max_user_send_per_minute(),
|
max_user_send_per_minute: Self::default_max_user_send_per_minute(),
|
||||||
max_user_send_burst_size: Self::default_max_user_send_burst_size(),
|
max_user_send_burst_size: Self::default_max_user_send_burst_size(),
|
||||||
passthrough_senders: Vec::new(),
|
|
||||||
passthrough_recipients: Vec::new(),
|
|
||||||
mail_domain: "example.org".to_string(),
|
mail_domain: "example.org".to_string(),
|
||||||
mailboxes_dir: None,
|
mailboxes_dir: None,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -131,19 +131,6 @@ pub fn check_encrypted(mail: &mailparse::ParsedMail, outgoing: bool) -> bool {
|
|||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check if recipient matches a passthrough pattern
|
|
||||||
pub fn recipient_matches_passthrough(recipient: &str, passthrough_recipients: &[String]) -> bool {
|
|
||||||
for addr in passthrough_recipients {
|
|
||||||
if recipient == addr {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if addr.starts_with('@') && recipient.ends_with(addr) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
@@ -151,11 +138,6 @@ mod tests {
|
|||||||
use rstest::*;
|
use rstest::*;
|
||||||
use testresult::TestResult;
|
use testresult::TestResult;
|
||||||
|
|
||||||
#[fixture]
|
|
||||||
fn passthrough_recipients() -> Vec<String> {
|
|
||||||
vec!["pass@example.org".to_string(), "@example.com".to_string()]
|
|
||||||
}
|
|
||||||
|
|
||||||
#[rstest]
|
#[rstest]
|
||||||
#[case::asm("test_data/asm.eml", false)]
|
#[case::asm("test_data/asm.eml", false)]
|
||||||
#[case::encrypted("test_data/encrypted.eml", false)]
|
#[case::encrypted("test_data/encrypted.eml", false)]
|
||||||
@@ -189,18 +171,4 @@ mod tests {
|
|||||||
assert_eq!(check_encrypted(&parsed, false), expected);
|
assert_eq!(check_encrypted(&parsed, false), expected);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[rstest]
|
|
||||||
#[case("pass@example.org", true)]
|
|
||||||
#[case("other@example.org", false)]
|
|
||||||
#[case("anything@example.com", true)]
|
|
||||||
#[case("anything@sub.example.com", false)]
|
|
||||||
fn test_recipient_matches_passthrough(
|
|
||||||
#[case] recipient: &str,
|
|
||||||
#[case] expected: bool,
|
|
||||||
passthrough_recipients: Vec<String>,
|
|
||||||
) {
|
|
||||||
let result = recipient_matches_passthrough(recipient, &passthrough_recipients);
|
|
||||||
assert_eq!(result, expected);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
use crate::ENCRYPTION_NEEDED_523;
|
use crate::ENCRYPTION_NEEDED_523;
|
||||||
use crate::config::Config;
|
use crate::config::Config;
|
||||||
use crate::message::{check_encrypted, is_securejoin, recipient_matches_passthrough};
|
use crate::message::{check_encrypted, is_securejoin};
|
||||||
use crate::smtp_client::SmtpConnectionPool;
|
use crate::smtp_client::SmtpConnectionPool;
|
||||||
pub use crate::smtp_server::Envelope;
|
pub use crate::smtp_server::Envelope;
|
||||||
use crate::smtp_server::SmtpHandler;
|
use crate::smtp_server::SmtpHandler;
|
||||||
@@ -114,11 +114,6 @@ impl SmtpHandler for OutgoingBeforeQueueHandler {
|
|||||||
|
|
||||||
log::info!("Outgoing: Filtering unencrypted mail.");
|
log::info!("Outgoing: Filtering unencrypted mail.");
|
||||||
|
|
||||||
// Allow passthrough senders
|
|
||||||
if self.config.passthrough_senders.contains(&from_addr) {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Allow self-sent Autocrypt Setup Message
|
// Allow self-sent Autocrypt Setup Message
|
||||||
if envelope.rcpt_to.len() == 1
|
if envelope.rcpt_to.len() == 1
|
||||||
&& let Some(rcpt_to) = envelope.rcpt_to.first()
|
&& let Some(rcpt_to) = envelope.rcpt_to.first()
|
||||||
@@ -133,14 +128,8 @@ impl SmtpHandler for OutgoingBeforeQueueHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for recipient in &envelope.rcpt_to {
|
|
||||||
if !recipient_matches_passthrough(recipient, &self.config.passthrough_recipients) {
|
|
||||||
log::warn!("Rejected unencrypted mail from: {from_addr}");
|
log::warn!("Rejected unencrypted mail from: {from_addr}");
|
||||||
return Err(ENCRYPTION_NEEDED_523.to_string());
|
Err(ENCRYPTION_NEEDED_523.to_string())
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn reinject_mail(&self, envelope: &Envelope) -> Result<(), String> {
|
async fn reinject_mail(&self, envelope: &Envelope) -> Result<(), String> {
|
||||||
|
|||||||
Reference in New Issue
Block a user