miltr_common/optneg/
mod.rs

1//! Contains anything related to option negotiation between server and client
2
3mod capability;
4mod macros;
5mod protocol;
6
7use bytes::{Buf, BytesMut};
8use thiserror::Error;
9
10use crate::decoding::Parsable;
11use crate::encoding::Writable;
12use crate::error::STAGE_DECODING;
13use crate::{NotEnoughData, ProtocolError};
14
15pub use capability::Capability;
16pub use macros::{MacroStage, MacroStages};
17pub use protocol::Protocol;
18
19/// `SMFIC_OPTNEG`
20#[derive(Clone, PartialEq, Debug)]
21pub struct OptNeg {
22    /// The milter protocol version this implementation speaks
23    pub version: u32,
24    /// Which modifications this milter may send to the client
25    pub capabilities: Capability,
26    /// How the client should behave using this protocol
27    pub protocol: Protocol,
28    /// Which macros this milter would like to get from the client
29    pub macro_stages: MacroStages,
30}
31
32impl Default for OptNeg {
33    fn default() -> Self {
34        Self {
35            version: Self::VERSION,
36            capabilities: Capability::default(),
37            protocol: Protocol::default(),
38            macro_stages: MacroStages::default(),
39        }
40    }
41}
42
43/// Comparing compatibilities between different optneg pacakges may produce
44/// this error. See [`OptNeg::merge_compatible`] for details.
45#[derive(Debug, Error)]
46pub enum CompatibilityError {
47    /// Thrown if this implementation does not support a received version
48    #[error("Received version {received} which is not compatible with {supported}")]
49    UnsupportedVersion {
50        /// The version received
51        received: u32,
52        /// The version supported
53        supported: u32,
54    },
55}
56
57impl OptNeg {
58    /* VERSION: the Milter protocol version that Postfix should use. The default version is 6
59       (before Postfix 2.6 the default version is 2).
60    */
61    /* etc/postfix/main.cf:
62    # Postfix ≥ 2.6
63    milter_protocol = 6
64    # 2.3 ≤ Postfix ≤ 2.5
65    milter_protocol = 2 */
66
67    /* If the Postfix milter_protocol setting specifies a too low version, the libmilter library will log an error message like this:
68
69    application name: st_optionneg[xxxxx]: 0xyy does not fulfill action requirements 0xzz
70    The remedy is to increase the Postfix milter_protocol version number. See, however, the limitations section below for features that aren't supported by Postfix.
71
72    With Postfix 2.7 and earlier, if the Postfix milter_protocol setting specifies a too high version, the libmilter library simply hangs up without logging a warning, and you see a Postfix warning message like one of the following:
73
74    warning: milter inet:host:port: can't read packet header: Unknown error : 0
75    warning: milter inet:host:port: can't read packet header: Success
76    warning: milter inet:host:port: can't read SMFIC_DATA reply packet header: No such file or directory
77    The remedy is to lower the Postfix milter_protocol version number. Postfix 2.8 and later will automatically turn off protocol features that the application's libmilter library does not expect. */
78
79    const VERSION: u32 = 6;
80
81    const DATA_SIZE: usize = 4 + 4 + 4;
82    const CODE: u8 = b'O';
83
84    /// Check whether `self` is compatible with `other`
85    ///
86    /// This includes comparing versions, the protocol and capabilities.
87    ///
88    /// # Errors
89    /// This errors when discovering an incompatibility between `self` and `other`
90    pub fn merge_compatible(mut self, other: &Self) -> Result<Self, CompatibilityError> {
91        if self.version < other.version {
92            return Err(CompatibilityError::UnsupportedVersion {
93                received: other.version,
94                supported: self.version,
95            });
96        }
97
98        self.protocol = self
99            .protocol
100            .merge_regarding_version(self.version, other.protocol);
101
102        self.capabilities = self
103            .capabilities
104            .merge_regarding_version(self.version, other.capabilities);
105
106        Ok(self)
107    }
108
109    // pub fn request_macro<S: ToString>(&mut self, stage: &MacroStage, macros: &[S]) {
110    //     let index: u32 = stage.clone().into();
111    //     self.macro_stages[index as usize] = macros.iter().map(ToString::to_string).collect();
112    // }
113}
114
115impl Parsable for OptNeg {
116    const CODE: u8 = Self::CODE;
117
118    fn parse(mut buffer: BytesMut) -> Result<Self, ProtocolError> {
119        if buffer.len() != Self::DATA_SIZE {
120            return Err(NotEnoughData::new(
121                STAGE_DECODING,
122                "Option negotiation",
123                "not enough bits",
124                Self::DATA_SIZE,
125                buffer.len(),
126                buffer,
127            )
128            .into());
129        }
130
131        let mut version: [u8; 4] = [0; 4];
132        version.copy_from_slice(&buffer[0..4]);
133        let version = u32::from_be_bytes(version);
134
135        let mut capabilities: [u8; 4] = [0; 4];
136        capabilities.copy_from_slice(&buffer[4..8]);
137        let capabilities: Capability =
138            Capability::from_bits_retain(u32::from_be_bytes(capabilities));
139
140        let mut protocol: [u8; 4] = [0; 4];
141        protocol.copy_from_slice(&buffer[8..12]);
142        let protocol: Protocol = Protocol::from_bits_retain(u32::from_be_bytes(protocol));
143
144        buffer.advance(12);
145        Ok(Self {
146            version,
147            capabilities,
148            protocol,
149            // todo actually parse incoming macros
150            macro_stages: MacroStages::default(),
151        })
152    }
153}
154
155//const MACRO_TEST: &[u8] = b"\x00\x00\x00\x01j {client_ptr}\x00\x00\x00\x00\x03j {rcpt_addr}\x00";
156
157impl Writable for OptNeg {
158    fn write(&self, buffer: &mut BytesMut) {
159        buffer.extend_from_slice(&self.version.to_be_bytes());
160        buffer.extend_from_slice(&self.capabilities.bits().to_be_bytes());
161        buffer.extend_from_slice(&self.protocol.bits().to_be_bytes());
162
163        self.macro_stages.write(buffer);
164    }
165
166    fn len(&self) -> usize {
167        Self::DATA_SIZE + self.macro_stages.len()
168    }
169
170    fn code(&self) -> u8 {
171        Self::CODE
172    }
173
174    fn is_empty(&self) -> bool {
175        self.len() == 0
176    }
177}
178
179#[cfg(test)]
180mod tests {
181
182    use super::*;
183    use pretty_assertions::assert_eq;
184
185    fn ver_caps_prot() -> ([u8; 4], [u8; 4], [u8; 4]) {
186        let version = [0u8, 0u8, 0u8, 6u8];
187        let capabilities = [0u8, 0u8, 0u8, 255u8];
188        let protocol = [0u8, 0u8, 0u8, 0u8];
189
190        (version, capabilities, protocol)
191    }
192
193    #[cfg(feature = "count-allocations")]
194    #[allow(clippy::type_complexity)] // Small function, well named return vars
195    fn create_optneg_from_bytes() -> (BytesMut, ([u8; 4], [u8; 4], [u8; 4])) {
196        let mut buffer = BytesMut::new();
197
198        let (version, capabilities, protocol) = ver_caps_prot();
199
200        buffer.extend_from_slice(&version);
201        buffer.extend_from_slice(&capabilities);
202        buffer.extend_from_slice(&protocol);
203
204        (buffer, (version, capabilities, protocol))
205    }
206
207    #[cfg(feature = "count-allocations")]
208    #[test]
209    fn test_parse_optneg() {
210        use super::OptNeg;
211
212        let (buffer, _) = create_optneg_from_bytes();
213
214        let info = allocation_counter::measure(|| {
215            let res = OptNeg::parse(buffer);
216            allocation_counter::opt_out(|| {
217                println!("{res:?}");
218                assert!(res.is_ok());
219            });
220        });
221        println!("{}", &info.count_total);
222        assert_eq!(info.count_total, 0);
223    }
224
225    #[test]
226    fn test_write_optneg() {
227        // Setup expectations
228        let (version, capabilities, protocol) = ver_caps_prot();
229        let mut expected = Vec::new();
230        expected.extend_from_slice(&version);
231        expected.extend_from_slice(&capabilities);
232        expected.extend_from_slice(&protocol);
233
234        // Write a default optneg to a buffer
235        let mut buffer = BytesMut::new();
236        let optneg = OptNeg::default();
237        optneg.write(&mut buffer);
238
239        // Check
240        assert_eq!(optneg.len(), buffer.len());
241        assert_eq!(optneg.code(), b'O');
242        assert_eq!(expected, buffer.to_vec());
243    }
244}