miltr_common/modifications/
mod.rs

1//! Response containing modified data
2//!
3//! Only to an end-of-body the milter can respond with change requests.
4//! These are modification actions.
5
6pub mod body;
7pub mod headers;
8pub mod quarantine;
9pub mod recipients;
10
11use enum_dispatch::enum_dispatch;
12
13use super::{
14    actions::{Action, Continue},
15    ServerMessage,
16};
17
18use crate::encoding::Writable;
19use crate::{actions::Abort, optneg::Capability};
20use bytes::BytesMut;
21
22use body::ReplaceBody;
23use headers::{AddHeader, ChangeHeader, InsertHeader};
24use quarantine::Quarantine;
25use recipients::{AddRecipient, DeleteRecipient};
26
27/// A container for multiple modification requests towards the milter client.
28///
29/// ```
30/// use miltr_common::modifications::{ModificationResponse, headers::AddHeader};
31///
32/// let mut builder = ModificationResponse::builder();
33/// builder.push(AddHeader::new(
34///      "Test Add Header".as_bytes(),
35///      "Add Header Value".as_bytes(),
36///   ));
37/// let response = builder.contin();
38/// ```
39///
40/// # Note on Capabilities
41/// While all [`ModificationAction`] can be pushed into this response,
42/// they might not all be sent.
43/// During option negotiation, client and server agree on supported
44/// [`Capability`].
45#[derive(Debug)]
46pub struct ModificationResponse {
47    modifications: Vec<ModificationAction>,
48    final_action: Action,
49}
50
51impl ModificationResponse {
52    /// Create a builder to assemble a modification response.
53    #[must_use]
54    pub fn builder() -> ModificationResponseBuilder {
55        ModificationResponseBuilder {
56            modifications: Vec::default(),
57        }
58    }
59
60    /// Create an empty `ModificationResponse` just to continue
61    #[must_use]
62    pub fn empty_continue() -> Self {
63        Self {
64            modifications: Vec::new(),
65            final_action: Continue.into(),
66        }
67    }
68
69    /// Filter modification actions in `self`, keep only those which have been
70    /// allowed by the specified `capabilities`.
71    pub fn filter_mods_by_caps(&mut self, capabilities: Capability) {
72        self.modifications
73            .retain(|m| Self::mod_matches_caps(m, capabilities));
74    }
75
76    /// Returns true, if a single modification action matches the set `capabilities`
77    fn mod_matches_caps(modification: &ModificationAction, capabilities: Capability) -> bool {
78        match modification {
79            ModificationAction::AddHeader(_) => capabilities.contains(Capability::SMFIF_ADDHDRS),
80            ModificationAction::ReplaceBody(_) => capabilities.contains(Capability::SMFIF_CHGBODY),
81            ModificationAction::AddRecipient(_) => capabilities.contains(Capability::SMFIF_ADDRCPT),
82            ModificationAction::DeleteRecipient(_) => {
83                capabilities.contains(Capability::SMFIF_DELRCPT)
84            }
85            ModificationAction::ChangeHeader(_) | ModificationAction::InsertHeader(_) => {
86                capabilities.contains(Capability::SMFIF_CHGHDRS)
87            }
88            ModificationAction::Quarantine(_) => {
89                capabilities.contains(Capability::SMFIF_QUARANTINE)
90            }
91        }
92    }
93
94    /// Get the received modification actions
95    #[must_use]
96    pub fn modifications(&self) -> &[ModificationAction] {
97        self.modifications.as_ref()
98    }
99
100    /// Get the final action to be done to the mail
101    #[must_use]
102    pub fn final_action(&self) -> &Action {
103        &self.final_action
104    }
105}
106
107impl From<ModificationResponse> for Vec<ServerMessage> {
108    fn from(value: ModificationResponse) -> Self {
109        let mut resp: Vec<ServerMessage> = Vec::with_capacity(value.modifications.len() + 1);
110        resp.extend(
111            value
112                .modifications
113                .into_iter()
114                .map(ServerMessage::ModificationAction),
115        );
116        resp.push(ServerMessage::Action(value.final_action));
117        resp
118    }
119}
120
121/// Gather up Modification actions to send to the milter client
122#[derive(Debug, Clone)]
123pub struct ModificationResponseBuilder {
124    modifications: Vec<ModificationAction>,
125}
126
127impl ModificationResponseBuilder {
128    /// Push another modification action onto the builder
129    pub fn push<M: Into<ModificationAction>>(&mut self, mod_action: M) {
130        self.modifications.push(mod_action.into());
131    }
132
133    /// Send the `Abort` command to the milter client
134    #[must_use]
135    pub fn abort(self) -> ModificationResponse {
136        self.build(Abort)
137    }
138
139    /// Send a `Continue` command to the milter client with all set
140    /// modification responses.
141    #[must_use]
142    pub fn contin(self) -> ModificationResponse {
143        self.build(Continue)
144    }
145
146    /// Finalize into a [`ModificationResponse`] with a final action
147    #[must_use]
148    pub fn build<A: Into<Action>>(self, final_action: A) -> ModificationResponse {
149        ModificationResponse {
150            modifications: self.modifications,
151            final_action: final_action.into(),
152        }
153    }
154}
155
156/// The container of possible milter modification actions
157#[enum_dispatch]
158#[cfg_attr(feature = "tracing", derive(strum::Display))]
159#[derive(Debug, Clone)]
160pub enum ModificationAction {
161    /// Add recipient
162    AddRecipient,
163    /// Delete recipient
164    DeleteRecipient,
165    // /* add recipient (incl. ESMTP args) */
166    // currently not supported, feel free to implement
167    // SmfirAddrcptPar,
168    // /* 421: shutdown (internal to MTA) */
169    // Not implemented in Milter
170    // SmfirShutdown,
171    /// Replace mail body
172    ReplaceBody,
173    // /* change envelope sender (from) */
174    // currently not supported, feel free to implement
175    // SmfirChgfrom,
176    // /* cause a connection failure */
177    // currently not supported, feel free to implement. But why would you
178    // need the connection to fail? Please, at least try to reason why you
179    // need this
180    // SmfirConnFail,
181    /// Add an arbitrary header
182    AddHeader,
183    /// Insert the header at a specific place
184    InsertHeader,
185    // /* set list of symbols (macros) */
186    // SmfirSetsymlist,
187    /// Change an existing header
188    ChangeHeader,
189    // /* progress */
190    // currently not supported, feel free to implement. May be a bit complicated
191    // and config needed to handle timeouts and when to send and stuff
192    // SmfirProgress,
193    /// Quarantine this mail
194    Quarantine,
195}