miltr_server/
milter.rs

1use std::io;
2
3use async_trait::async_trait;
4use thiserror::Error;
5
6use miltr_common::{
7    actions::{Action, Continue},
8    commands::{Body, Connect, Header, Helo, Macro, Mail, Recipient, Unknown},
9    modifications::ModificationResponse,
10    optneg::OptNeg,
11    ProtocolError,
12};
13
14/// A trait to implement a working milter server.
15///
16/// See examples on how to implement this.
17#[async_trait]
18pub trait Milter: Send {
19    /// A user error that might be returned handling this milter communication
20    type Error: Send;
21
22    /// Option negotiation for the connection between the miter client and server.
23    #[doc(alias = "SMFIC_OPTNEG")]
24    #[doc(alias = "xxfi_negotiate")]
25    async fn option_negotiation(&mut self, theirs: OptNeg) -> Result<OptNeg, Error<Self::Error>> {
26        let mut ours = OptNeg::default();
27        ours = ours
28            .merge_compatible(&theirs)
29            .map_err(ProtocolError::CompatibilityError)?;
30        Ok(ours)
31    }
32
33    /// A macro sent by the milter client.
34    #[doc(alias = "SMFIC_MACRO")]
35    async fn macro_(&mut self, _macro: Macro) -> Result<(), Self::Error> {
36        Ok(())
37    }
38
39    /// Connection information about the smtp connection.
40    #[doc(alias = "SMFIC_CONNECT")]
41    #[doc(alias = "xxfi_connect")]
42    async fn connect(&mut self, _connect_info: Connect) -> Result<Action, Self::Error> {
43        Ok(Continue.into())
44    }
45
46    /// The helo name sent by the smtp client.
47    #[doc(alias = "SMFIC_HELO")]
48    #[doc(alias = "xxfi_helo")]
49    async fn helo(&mut self, _helo: Helo) -> Result<Action, Self::Error> {
50        Ok(Continue.into())
51    }
52
53    /// The sender this email is from.
54    #[doc(alias = "SMFIC_MAIL")]
55    #[doc(alias = "from")]
56    #[doc(alias = "xxfi_envfrom")]
57    async fn mail(&mut self, _mail: Mail) -> Result<Action, Self::Error> {
58        Ok(Continue.into())
59    }
60
61    /// A recipient to which this mail is to be transmitted to.
62    #[doc(alias = "SMFIC_RCPT")]
63    #[doc(alias = "to")]
64    #[doc(alias = "xxfi_envrcpt")]
65    async fn rcpt(&mut self, _recipient: Recipient) -> Result<Action, Self::Error> {
66        Ok(Continue.into())
67    }
68
69    /// Called before data (=body + headers) is sent.
70    ///
71    /// This allows to first receive sender and receiver, then the rest of the
72    /// data.
73    #[doc(alias = "SMFIC_DATA")]
74    #[doc(alias = "xxfi_data")]
75    async fn data(&mut self) -> Result<Action, Self::Error> {
76        Ok(Continue.into())
77    }
78
79    /// A single header with it's name and value.
80    ///
81    /// Header names are not unique and might be received multiple times.
82    #[doc(alias = "SMFIC_HEADER")]
83    #[doc(alias = "xxfi_header")]
84    async fn header(&mut self, _header: Header) -> Result<Action, Self::Error> {
85        Ok(Continue.into())
86    }
87
88    /// Called after all headers have been sent.
89    #[doc(alias = "SMFIC_EOH")]
90    #[doc(alias = "xxfi_eoh")]
91    async fn end_of_header(&mut self) -> Result<Action, Self::Error> {
92        Ok(Continue.into())
93    }
94
95    /// A body part was received.
96    ///
97    /// This may be called multiple times until the whole body was transmitted.
98    #[doc(alias = "SMFIC_BODY")]
99    #[doc(alias = "xxfi_body")]
100    async fn body(&mut self, _body: Body) -> Result<Action, Self::Error> {
101        Ok(Continue.into())
102    }
103
104    /// Called after all body parts have been received.
105    ///
106    /// This is the only stage at which to respond with modifications
107    /// to the milter client.
108    #[doc(alias = "SMFIC_BODYEOB")]
109    #[doc(alias = "xxfi_eom")]
110    async fn end_of_body(&mut self) -> Result<ModificationResponse, Self::Error> {
111        Ok(ModificationResponse::empty_continue())
112    }
113
114    /// A command not matching any Code is received as `unknown`.
115    #[doc(alias = "SMFIC_UNKNOWN")]
116    #[doc(alias = "xxfi_unknown")]
117    async fn unknown(&mut self, _cmd: Unknown) -> Result<Action, Self::Error> {
118        Ok(Continue.into())
119    }
120
121    /// Reset the message handling to accept a new connection.
122    ///
123    /// Contrary to it's name, a connection is not aborted here necessarily.
124    /// This function is called at the end of every message processing, regardless
125    /// of outcome, but the connection is kept open and ready to process the next
126    /// message.
127    ///
128    /// This is the only function not covered by a default. The implementor
129    /// needs to reset it's state to handle a new connection.
130    ///
131    /// See [`Server::default_postfix`](crate::Server::default_postfix).
132    #[doc(alias = "SMFIC_ABORT")]
133    #[doc(alias = "xxfi_abort")]
134    async fn abort(&mut self) -> Result<(), Self::Error>;
135
136    /// Called on quitting a connection from a milter client.
137    ///
138    /// Some clients (postfix) do not call this method and instead call
139    /// `abort` with the expectation the connection is closed.
140    ///
141    /// See [`Server::default_postfix`](crate::Server::default_postfix).
142    #[doc(alias = "SMFIC_QUIT")]
143    #[doc(alias = "xxfi_close")]
144    async fn quit(&mut self) -> Result<(), Self::Error> {
145        Ok(())
146    }
147
148    /// Called when a milter client want's to re-use this milter for a new mail.
149    #[doc(alias = "SMFIC_QUIT_NC")]
150    async fn quit_nc(&mut self) -> Result<(), Self::Error> {
151        Ok(())
152    }
153}
154
155/// The main error for this crate encapsulating the different error cases.
156#[derive(Debug, Error)]
157pub enum Error<ImplError> {
158    /// If IO breaks, this will return a [`Error::Io`],
159    /// which is a simple [`std::io::Error`]. Check the underlying transport.
160    #[error(transparent)]
161    Io(#[from] io::Error),
162
163    /// The Codec had problems de/encoding data. This might be
164    /// a problem in the implementation or an incompatibility between this crate
165    #[error(transparent)]
166    Codec(#[from] ProtocolError),
167
168    /// The milter trait implementation returned an error.
169    /// This is plumbed through and returned to the call site.
170    #[error(transparent)]
171    Impl {
172        /// The application error patched through
173        source: ImplError,
174    },
175}
176
177impl<AppError> Error<AppError> {
178    pub(crate) fn from_app_error(source: AppError) -> Self {
179        Self::Impl { source }
180    }
181}