Mail flow rules: carry out the transport actions
Phase 2g of the DLP and mail flow rules spec: transport rules now act, on outgoing and incoming mail. - features/mailflow/rewrite.rs: add or remove a header, prefix or set the subject (an RFC 2047 word when not ASCII), add a disclaimer. A disclaimer edits the message's main text and HTML bodies only, each decoded, changed and written back as UTF-8 quoted-printable with its other headers kept, top or bottom (after <body> or before </body> in HTML); attachments and attached messages are left alone, and a disclaimer already present isn't added again. - smtp/inbound/mailflow.rs: the check runs for incoming mail too (transport rules only; DLP stays outgoing). After DLP passes, each matched transport rule's actions run in order: message edits, add-recipient and redirect (envelope changes DATA applies), route (a per-message queue ahead of the queue strategy), refuse (550 5.7.1 with the rule's text). The override tag is stripped with the same subject writer, so a non-ASCII subject stays valid. - Audit: refusals and changes to where mail goes are recorded (sender, or system:mail-flow for incoming mail); wording and header changes aren't, or a banner rule would record every message (spec §2.7). Tests: rewrite unit tests (headers, encoded subjects, disclaimers on a single part and on multipart/alternative with an attachment, once only); mail_rules_tests gains the actions end to end: disclaimer, header and subject prefix on a delivered message, a redirect, a refusal, a banner on incoming LMTP mail that outgoing rules leave alone, and which of those are audited.
This commit is contained in:
@@ -745,7 +745,26 @@ impl<T: SessionStream> Session<T> {
|
||||
.await
|
||||
{
|
||||
super::mailflow::Checked::Accept => {}
|
||||
super::mailflow::Checked::Replace(message) => edited_message = Some(message),
|
||||
super::mailflow::Checked::Changed { message, envelope } => {
|
||||
if let Some(message) = message {
|
||||
edited_message = Some(message);
|
||||
}
|
||||
for change in envelope {
|
||||
match change {
|
||||
super::mailflow::EnvelopeChange::AddRecipient(address) => {
|
||||
if !self.data.rcpt_to.iter().any(|r| r.address_lcase.eq_ignore_ascii_case(&address)) {
|
||||
self.data.rcpt_to.push(SessionAddress::new(address));
|
||||
}
|
||||
}
|
||||
super::mailflow::EnvelopeChange::Redirect(addresses) => {
|
||||
self.data.rcpt_to = addresses.into_iter().map(SessionAddress::new).collect();
|
||||
}
|
||||
super::mailflow::EnvelopeChange::Route(queue) => {
|
||||
self.data.mailflow_queue = Some(queue);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
super::mailflow::Checked::Refuse(reply, refusal) => {
|
||||
self.data.dlp_refusal = refusal;
|
||||
return reply.into();
|
||||
@@ -917,8 +936,10 @@ impl<T: SessionStream> Session<T> {
|
||||
};
|
||||
|
||||
// Resolve queue
|
||||
let queue = self.server.get_queue_or_default(
|
||||
&self
|
||||
// inbuxa: a mail flow rule's route comes before the strategy
|
||||
let queue_name = match &self.data.mailflow_queue {
|
||||
Some(queue) => queue.clone(),
|
||||
None => self
|
||||
.server
|
||||
.eval_if::<String, _>(
|
||||
&self.server.core.smtp.queue.queue,
|
||||
@@ -927,8 +948,10 @@ impl<T: SessionStream> Session<T> {
|
||||
)
|
||||
.await
|
||||
.unwrap_or_else(|| "default".to_string()),
|
||||
self.data.session_id,
|
||||
);
|
||||
};
|
||||
let queue = self
|
||||
.server
|
||||
.get_queue_or_default(&queue_name, self.data.session_id);
|
||||
|
||||
// Set expiration and notification times
|
||||
let num_intervals = std::cmp::max(queue.notify.len(), 1);
|
||||
|
||||
Reference in New Issue
Block a user