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
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
//! A library for writing *milters*: mail filtering applications that can be
//! integrated with MTAs (mail servers) over the sendmail milter protocol.
//!
//! This crate contains the Rust bindings to *libmilter*, the sendmail mail
//! filter API. As such, it does not try to hide the nature of that venerable C
//! library, but exposes its capabilities faithfully with all its quirks. If you
//! have used libmilter before, the functionality exposed on the context API
//! structs, as well as flags such as `Actions` and `ProtocolOpts` will be
//! immediately familiar, though some of the names have been adapted.
//!
//! Once it has started up, a milter application is driven by the underlying C
//! library. This documentation will speak of ‘libmilter’ or ‘the libmilter
//! library’ where appropriate.
//!
//! # Usage
//!
//! To give an idea of how to use this crate, let’s create a milter that counts
//! the envelope recipients of a message, and adds a header recording the count.
//!
//! This simple example demonstrates all important aspects of a milter
//! application: handling of SMTP events with callbacks (each envelope
//! recipient), storing data in the callback context (the recipient count), and
//! finally performing some message modification operation (adding a header).
//!
//! ```no_run
//! use milter::*;
//!
//! fn main() {
//!     Milter::new("inet:3000@localhost")
//!         .name("RcptCountMilter")
//!         .on_rcpt(rcpt_callback)
//!         .on_eom(eom_callback)
//!         .on_abort(abort_callback)
//!         .actions(Actions::ADD_HEADER)
//!         .run()
//!         .expect("milter execution failed");
//! }
//!
//! #[on_rcpt(rcpt_callback)]
//! fn handle_rcpt(mut context: Context<u32>, _: Vec<&str>) -> milter::Result<Status> {
//!     match context.data.borrow_mut() {
//!         Some(count) => *count += 1,
//!         None => {
//!             context.data.replace(1)?;
//!         }
//!     }
//!
//!     Ok(Status::Continue)
//! }
//!
//! #[on_eom(eom_callback)]
//! fn handle_eom(mut context: Context<u32>) -> milter::Result<Status> {
//!     if let Some(count) = context.data.take()? {
//!         context.api.add_header("X-Rcpt-Count", &count.to_string())?;
//!     }
//!
//!     Ok(Status::Continue)
//! }
//!
//! #[on_abort(abort_callback)]
//! fn handle_abort(mut context: Context<u32>) -> Status {
//!     let _ = context.data.take();
//!
//!     Status::Continue
//! }
//! ```
//!
//! A milter’s behaviour is implemented as **callback functions** that get
//! called as certain events happen during an SMTP conversation. Callback
//! functions are marked up with attribute macros. For example, [`on_rcpt`],
//! called for each `RCPT TO` command or envelope recipient.
//!
//! All callback functions return a **response [`Status`]** that determines how
//! to proceed after completing the callback. The callbacks in the example all
//! return [`Continue`], meaning ‘proceed to the next stage’.
//!
//! The callback functions are then configured on a **`Milter`** instance in
//! `main`. [`Milter`] serves as the entry point to configuring and running a
//! milter application.
//!
//! The example also shows how to **store data in the callback context**.
//! Context storage is accessible through a generic [`DataHandle<T>`] exposed on
//! the `Context` struct. A thing to keep in mind is that management of the
//! data’s life cycle is not entirely automatic; in order to avoid leaking
//! memory, care must be taken to reacquire (and drop) the data before the
//! connection closes. In our example this is done in `handle_abort`,
//! implemented just for this purpose.
//!
//! Finally, the [`on_eom`] end-of-message callback is the place where **actions
//! may be applied to a message**. These actions – such as adding a header – can
//! be found as methods of the [`ContextApi`] struct that is part of the
//! context.
//!
//! The example is complete and ready to run. A call to `Milter::run` starts the
//! application, passing control to the libmilter library. A running milter can
//! be stopped by sending a termination signal, for example by pressing
//! Control-C.
//!
//! The remainder of this module documentation discusses some topics to be aware
//! of when creating milter applications.
//!
//! # Callback flow
//!
//! For milter writing one must have an understanding of the ‘flow’ of callback
//! calls. This flow mirrors the succession of events during an SMTP
//! conversation.
//!
//! The callback flow is as follows (when [negotiation] is used, it is the very
//! first step, preceding `connect`):
//!
//! * [`connect`]
//! * [`helo`]\*
//! * *for each message:*
//!   * [`mail`]
//!   * [`rcpt`]\*
//!   * [`data`]
//!   * [`header`]\*
//!   * [`eoh`]
//!   * [`body`]\*
//!   * **[`eom`]**
//! * [`close`]
//!
//! Several messages may be processed in a single connection. When that is the
//! case, the message-scoped stages `mail` to `eom` will be traversed
//! repeatedly. Of the connection-scoped and message-scoped stages the ones
//! indicated may be executed repeatedly. The message-scoped stages are always
//! bracketed by the connection-scoped stages `connect` and `close`.
//!
//! At any point during processing of a *message* the flow may be diverted to
//! [`abort`], in which case the remaining message stages are skipped and
//! processing continues at the beginning of the message loop. In any case
//! `close` will be called at the very end.
//!
//! For each stage, a response status returned from the callback determines what
//! to do with the entity being processed: whether to continue, accept, or
//! reject it. Only at the `eom` (end-of-message) stage may message modification
//! operations such as adding headers or altering the message body be applied.
//!
//! # Callback resource management
//!
//! The callback context allows storing connection-local [data]. Indeed, given
//! that libmilter may employ multiple threads of execution for handling
//! requests, all data shared across callback functions must be accessed using
//! that `DataHandle`.
//!
//! Context data need to be allocated and released at an appropriate place in
//! the callback flow. From the previous section it follows that resources may
//! logically be connection-scoped or message-scoped. For cleaning up
//! message-scoped resources, `eom` and `abort` are the natural stages to do so,
//! whereas for connection-scoped resources it is the `close` stage.
//!
//! Note that callback resource management is not automatic. Take care to
//! reacquire and drop any resources stored in the callback context before the
//! connection closes. As a rule of thumb, all paths through the callback flow
//! must include a final call to [`DataHandle::take`]. Failure to drop the data
//! in time causes that memory to leak.
//!
//! # Safety and error handling
//!
//! As the libmilter library is written in C, your Rust callback code is
//! ultimately always invoked by a foreign, C caller. Thanks to the attribute
//! macro-generated conversion layer, your code is safe even in the presence of
//! panics: In Rust, panicking across an FFI boundary is undefined behaviour;
//! the macro-generated layer catches unwinding panics, and so panicking in user
//! code remains safe.
//!
//! As usual, panic is treated as a fatal error. A panic triggered in a callback
//! results in milter shutdown.
//!
//! A less extreme failure mode can be chosen by wrapping the callback return
//! type in [`milter::Result`], for example `milter::Result<Status>` instead of
//! `Status`. Then, the `?` operator can be used to propagate unanticipated
//! errors out of the callback. An `Err` result corresponds to a [`Tempfail`]
//! response and the milter does not shut down.
//!
//! Finally, two safety hazards concern the context’s generic `DataHandle`:
//! First, we noted above the possibility of leaking memory in the `DataHandle`.
//! Second, there is a requirement to select the same generic type argument `T`
//! when writing out the callback function arguments: see the safety note at
//! [`Context`]. For both of these some programmer discipline is necessary.
//!
//! # Globals
//!
//! According with the design of the libmilter library, a milter application is
//! a singleton (one and only one instance). Only a single invocation of
//! `Milter::run` is allowed to be active at a time per process. Therefore,
//! global variables are an acceptable and reasonable thing to have.
//!
//! Nevertheless, as libmilter may use multiple threads to handle callbacks, any
//! use of static items should use an adequate synchronisation mechanism.
//!
//! [`on_rcpt`]: https://docs.rs/milter-callback/0.2.4/milter_callback/attr.on_rcpt.html
//! [`Continue`]: Status::Continue
//! [`on_eom`]: https://docs.rs/milter-callback/0.2.4/milter_callback/attr.on_eom.html
//! [negotiation]: Milter::on_negotiate
//! [`connect`]: Milter::on_connect
//! [`helo`]: Milter::on_helo
//! [`mail`]: Milter::on_mail
//! [`rcpt`]: Milter::on_rcpt
//! [`data`]: Milter::on_data
//! [`header`]: Milter::on_header
//! [`eoh`]: Milter::on_eoh
//! [`body`]: Milter::on_body
//! [`eom`]: Milter::on_eom
//! [`close`]: Milter::on_close
//! [`abort`]: Milter::on_abort
//! [data]: Context::data
//! [`milter::Result`]: Result
//! [`Tempfail`]: Status::Tempfail

mod context;
mod enums;
mod error;
pub mod internal;  // private API for use by milter-callback macros only
mod runner;

pub use crate::{context::*, enums::*, error::*, runner::*};

#[doc(hidden)]
pub use milter_callback::*;

use milter_sys as sys;

#[doc(hidden)]
pub use sys::{sfsistat, SMFICTX};

/// Instructs the libmilter library to exit its event loop, thereby shutting
/// down any currently running milter.
///
/// A call to `shutdown` performs a graceful termination of a milter. It causes
/// a currently executing blocking call to [`Milter::run`] to return. Note that
/// **`shutdown` is a terminal operation which disables any further executions
/// of `Milter::run` in this process.**
pub fn shutdown() {
    let _ = unsafe { sys::smfi_stop() };
}

/// Returns the runtime version triple of the libmilter library.
///
/// # Examples
///
/// ```
/// let (major, minor, patch) = milter::version();
///
/// println!("milter v{}.{}.{}", major, minor, patch);
/// ```
pub fn version() -> (u32, u32, u32) {
    let (mut major, mut minor, mut patch) = (0, 0, 0);

    let _ = unsafe { sys::smfi_version(&mut major, &mut minor, &mut patch) };

    (major, minor, patch)
}

/// Sets the trace debug level of the libmilter library to the given value.
///
/// The value range is unspecified, but should fall somewhere between 0 (no
/// logging, the default) and 6 (maximum logging volume).
pub fn set_debug_level(level: i32) {
    let _ = unsafe { sys::smfi_setdbg(level) };
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_version() {
        assert_ne!(version(), (0, 0, 0));
    }
}