Add mail splitting
- If a mail has more then one recipient and not for all recipients an authenticated certificate can be obtained, the mail processing is split into 2 streams - one that encrypts and an other which sends the mail in plaintext. - For both streams the header and the list of recipients is adjusted accordingly. - As the milter interface only allows one stream, the second stream is reinjected into the MTA (via SMTP). The MTA is hardcoded to be localhost:25.
This commit is contained in:
+74
-4
@@ -5,6 +5,11 @@
|
||||
use std::ffi::CString;
|
||||
use std::fmt;
|
||||
|
||||
use anyhow::Result;
|
||||
|
||||
use crate::types::recipient::Recipient;
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub struct CHeader {
|
||||
lines: Vec<(CString, CString)>
|
||||
}
|
||||
@@ -52,6 +57,47 @@ impl CHeader {
|
||||
pub fn iter_lines(&self) -> std::slice::Iter<'_, (CString, CString)> {
|
||||
self.lines.iter()
|
||||
}
|
||||
|
||||
/// Remove a recipient from a value taken from a to, cc or bcc header
|
||||
/// line. Only the email address is matched.
|
||||
pub fn remove_recipient(header_value: &CString, to_remove: &Vec<Recipient>) -> Result<CString> {
|
||||
let content = String::from_utf8_lossy(header_value.as_bytes());
|
||||
let parts = content.split(",")
|
||||
.filter(|part| {
|
||||
let mut keep = true;
|
||||
let p = part.to_ascii_lowercase();
|
||||
for rcpt in to_remove {
|
||||
let rcpt = rcpt.email.to_ascii_lowercase();
|
||||
if p.contains(&rcpt) { keep = false; break; }
|
||||
}
|
||||
keep
|
||||
})
|
||||
.collect::<Vec<&str>>();
|
||||
CString::new(parts.join(","))
|
||||
.map_err(|e| e.into())
|
||||
}
|
||||
|
||||
/// Returns a new header with `to_remove` removed from to, cc and bcc.
|
||||
pub fn without_recipients(&self, to_remove: &Vec<Recipient>) -> CHeader {
|
||||
let mut result = CHeader::new();
|
||||
|
||||
for (key, value) in self.iter_lines() {
|
||||
let new_value = match key.as_bytes().to_ascii_lowercase().as_slice() {
|
||||
b"to"
|
||||
| b"cc"
|
||||
| b"bcc" => Self::remove_recipient(value, to_remove),
|
||||
_ => Ok(value.clone())
|
||||
};
|
||||
if let Ok(value) = new_value {
|
||||
if ! value.is_empty() {
|
||||
result.append(key.clone(), value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
impl fmt::Display for CHeader {
|
||||
@@ -75,10 +121,12 @@ impl fmt::Display for CHeader {
|
||||
#[cfg(test)]
|
||||
pub mod tests {
|
||||
use std::ffi::CString;
|
||||
use crate::types::recipient::Recipient;
|
||||
|
||||
use super::CHeader;
|
||||
|
||||
|
||||
#[test]
|
||||
#[test]
|
||||
pub fn basic_functionality() {
|
||||
let cheader = CHeader::new()
|
||||
.add(CString::new("Content-Type").unwrap(), CString::new("text/plain").unwrap())
|
||||
@@ -87,7 +135,7 @@ pub mod tests {
|
||||
assert_eq!("Content-Type: text/plain\r\nContent-Transfer-Encoding: 7bit\r\n", cheader.to_string());
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[test]
|
||||
pub fn find() {
|
||||
let cheader = CHeader::new()
|
||||
.add(CString::new("Content-Type").unwrap(), CString::new("text/plain").unwrap())
|
||||
@@ -103,7 +151,7 @@ pub mod tests {
|
||||
assert_eq!("text/plain", value);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[test]
|
||||
pub fn find_none() {
|
||||
let cheader = CHeader::new()
|
||||
.add(CString::new("Content-Type").unwrap(), CString::new("text/plain").unwrap())
|
||||
@@ -114,7 +162,7 @@ pub mod tests {
|
||||
assert!(line.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[test]
|
||||
pub fn find_with_default() {
|
||||
let cheader = CHeader::new()
|
||||
.add(CString::new("Content-Type").unwrap(), CString::new("text/plain").unwrap())
|
||||
@@ -124,5 +172,27 @@ pub mod tests {
|
||||
|
||||
assert_eq!(value, "baaz");
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn remove_recipients() {
|
||||
let header = CHeader::new()
|
||||
.add(CString::new("To").unwrap(), CString::new("Alice <alice@example.com>, Bob <bob@other.org>").unwrap())
|
||||
.add(CString::new("Cc").unwrap(), CString::new("carol@example.com").unwrap());
|
||||
|
||||
let modified_header = header.without_recipients(
|
||||
&vec![ Recipient::from("bob@other.org".to_string()) ]
|
||||
);
|
||||
let expected_header = CHeader::new()
|
||||
.add(CString::new("To").unwrap(), CString::new("Alice <alice@example.com>").unwrap())
|
||||
.add(CString::new("Cc").unwrap(), CString::new("carol@example.com").unwrap());
|
||||
assert_eq!(modified_header, expected_header);
|
||||
|
||||
let modified_header = header.without_recipients(
|
||||
&vec![ Recipient::from("CaRol@exAmple.Com".to_string()) ]
|
||||
);
|
||||
let expected_header = CHeader::new()
|
||||
.add(CString::new("To").unwrap(), CString::new("Alice <alice@example.com>, Bob <bob@other.org>").unwrap());
|
||||
assert_eq!(modified_header, expected_header);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user