1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
use std::io;

use async_trait::async_trait;
use thiserror::Error;

use miltr_common::{
    actions::{Action, Continue},
    commands::{Body, Connect, Header, Helo, Macro, Mail, Recipient, Unknown},
    modifications::ModificationResponse,
    optneg::OptNeg,
    ProtocolError,
};

/// A trait to implement a working milter server.
///
/// See examples on how to implement this.
#[async_trait]
pub trait Milter: Send {
    /// A user error that might be returned handling this milter communication
    type Error: Send;

    /// Option negotiation for the connection between the miter client and server.
    #[doc(alias = "SMFIC_OPTNEG")]
    #[doc(alias = "xxfi_negotiate")]
    async fn option_negotiation(&mut self, theirs: OptNeg) -> Result<OptNeg, Error<Self::Error>> {
        let mut ours = OptNeg::default();
        ours = ours
            .merge_compatible(&theirs)
            .map_err(ProtocolError::CompatibilityError)?;
        Ok(ours)
    }

    /// A macro sent by the milter client.
    #[doc(alias = "SMFIC_MACRO")]
    async fn macro_(&mut self, _macro: Macro) -> Result<(), Self::Error> {
        Ok(())
    }

    /// Connection information about the smtp connection.
    #[doc(alias = "SMFIC_CONNECT")]
    #[doc(alias = "xxfi_connect")]
    async fn connect(&mut self, _connect_info: Connect) -> Result<Action, Self::Error> {
        Ok(Continue.into())
    }

    /// The helo name sent by the smtp client.
    #[doc(alias = "SMFIC_HELO")]
    #[doc(alias = "xxfi_helo")]
    async fn helo(&mut self, _helo: Helo) -> Result<Action, Self::Error> {
        Ok(Continue.into())
    }

    /// The sender this email is from.
    #[doc(alias = "SMFIC_MAIL")]
    #[doc(alias = "from")]
    #[doc(alias = "xxfi_envfrom")]
    async fn mail(&mut self, _mail: Mail) -> Result<Action, Self::Error> {
        Ok(Continue.into())
    }

    /// A recipient to which this mail is to be transmitted to.
    #[doc(alias = "SMFIC_RCPT")]
    #[doc(alias = "to")]
    #[doc(alias = "xxfi_envrcpt")]
    async fn rcpt(&mut self, _recipient: Recipient) -> Result<Action, Self::Error> {
        Ok(Continue.into())
    }

    /// Called before data (=body + headers) is sent.
    ///
    /// This allows to first receive sender and receiver, then the rest of the
    /// data.
    #[doc(alias = "SMFIC_DATA")]
    #[doc(alias = "xxfi_data")]
    async fn data(&mut self) -> Result<Action, Self::Error> {
        Ok(Continue.into())
    }

    /// A single header with it's name and value.
    ///
    /// Header names are not unique and might be received multiple times.
    #[doc(alias = "SMFIC_HEADER")]
    #[doc(alias = "xxfi_header")]
    async fn header(&mut self, _header: Header) -> Result<Action, Self::Error> {
        Ok(Continue.into())
    }

    /// Called after all headers have been sent.
    #[doc(alias = "SMFIC_EOH")]
    #[doc(alias = "xxfi_eoh")]
    async fn end_of_header(&mut self) -> Result<Action, Self::Error> {
        Ok(Continue.into())
    }

    /// A body part was received.
    ///
    /// This may be called multiple times until the whole body was transmitted.
    #[doc(alias = "SMFIC_BODY")]
    #[doc(alias = "xxfi_body")]
    async fn body(&mut self, _body: Body) -> Result<Action, Self::Error> {
        Ok(Continue.into())
    }

    /// Called after all body parts have been received.
    ///
    /// This is the only stage at which to respond with modifications
    /// to the milter client.
    #[doc(alias = "SMFIC_BODYEOB")]
    #[doc(alias = "xxfi_eom")]
    async fn end_of_body(&mut self) -> Result<ModificationResponse, Self::Error> {
        Ok(ModificationResponse::empty_continue())
    }

    /// A command not matching any Code is received as `unknown`.
    #[doc(alias = "SMFIC_UNKNOWN")]
    #[doc(alias = "xxfi_unknown")]
    async fn unknown(&mut self, _cmd: Unknown) -> Result<Action, Self::Error> {
        Ok(Continue.into())
    }

    /// Reset the message handling to accept a new connection.
    ///
    /// Contrary to it's name, a connection is not aborted here necessarily.
    /// This function is called at the end of every message processing, regardless
    /// of outcome, but the connection is kept open and ready to process the next
    /// message.
    ///
    /// This is the only function not covered by a default. The implementor
    /// needs to reset it's state to handle a new connection.
    ///
    /// See [`Server::default_postfix`](crate::server::Server::default_postfix).
    #[doc(alias = "SMFIC_ABORT")]
    #[doc(alias = "xxfi_abort")]
    async fn abort(&mut self) -> Result<Action, Self::Error>;

    /// Called on quitting a connection from a milter client.
    ///
    /// Some clients (postfix) do not call this method and instead call
    /// `abort` with the expectation the connection is closed.
    ///
    /// See [`Server::default_postfix`](crate::server::Server::default_postfix).
    #[doc(alias = "SMFIC_QUIT")]
    #[doc(alias = "xxfi_close")]
    async fn quit(&mut self) -> Result<(), Self::Error> {
        Ok(())
    }

    /// Called when a milter client want's to re-use this milter for a new mail.
    #[doc(alias = "SMFIC_QUIT_NC")]
    async fn quit_nc(&mut self) -> Result<(), Self::Error> {
        Ok(())
    }
}

/// The main error for this crate encapsulating the different error cases.
#[derive(Debug, Error)]
pub enum Error<ImplError> {
    /// If IO breaks, this will return a [`Error::Io`],
    /// which is a simple [`std::io::Error`]. Check the underlying transport.
    #[error(transparent)]
    Io(#[from] io::Error),

    /// The Codec had problems de/encoding data. This might be
    /// a problem in the implementation or an incompatibility between this crate
    #[error(transparent)]
    Codec(#[from] ProtocolError),

    /// The milter trait implementation returned an error.
    /// This is plumbed through and returned to the call site.
    #[error(transparent)]
    Impl {
        /// The application error patched through
        source: ImplError,
    },
}

impl<AppError> Error<AppError> {
    pub(crate) fn from_app_error(source: AppError) -> Self {
        Self::Impl { source }
    }
}