milter/
lib.rs

1//! A library for writing *milters*: mail filtering applications that can be
2//! integrated with MTAs (mail servers) over the sendmail milter protocol.
3//!
4//! This crate contains the Rust bindings to *libmilter*, the sendmail mail
5//! filter API. As such, it does not try to hide the nature of that venerable C
6//! library, but exposes its capabilities faithfully with all its quirks. If you
7//! have used libmilter before, the functionality exposed on the context API
8//! structs, as well as flags such as `Actions` and `ProtocolOpts` will be
9//! immediately familiar, though some of the names have been adapted.
10//!
11//! Once it has started up, a milter application is driven by the underlying C
12//! library. This documentation will speak of ‘libmilter’ or ‘the libmilter
13//! library’ where appropriate.
14//!
15//! # Usage
16//!
17//! To give an idea of how to use this crate, let’s create a milter that counts
18//! the envelope recipients of a message, and adds a header recording the count.
19//!
20//! This simple example demonstrates all important aspects of a milter
21//! application: handling of SMTP events with callbacks (each envelope
22//! recipient), storing data in the callback context (the recipient count), and
23//! finally performing some message modification operation (adding a header).
24//!
25//! ```no_run
26//! use milter::*;
27//!
28//! fn main() {
29//!     Milter::new("inet:3000@localhost")
30//!         .name("RcptCountMilter")
31//!         .on_rcpt(rcpt_callback)
32//!         .on_eom(eom_callback)
33//!         .on_abort(abort_callback)
34//!         .actions(Actions::ADD_HEADER)
35//!         .run()
36//!         .expect("milter execution failed");
37//! }
38//!
39//! #[on_rcpt(rcpt_callback)]
40//! fn handle_rcpt(mut context: Context<u32>, _: Vec<&str>) -> milter::Result<Status> {
41//!     match context.data.borrow_mut() {
42//!         Some(count) => *count += 1,
43//!         None => {
44//!             context.data.replace(1)?;
45//!         }
46//!     }
47//!
48//!     Ok(Status::Continue)
49//! }
50//!
51//! #[on_eom(eom_callback)]
52//! fn handle_eom(mut context: Context<u32>) -> milter::Result<Status> {
53//!     if let Some(count) = context.data.take()? {
54//!         context.api.add_header("X-Rcpt-Count", &count.to_string())?;
55//!     }
56//!
57//!     Ok(Status::Continue)
58//! }
59//!
60//! #[on_abort(abort_callback)]
61//! fn handle_abort(mut context: Context<u32>) -> Status {
62//!     let _ = context.data.take();
63//!
64//!     Status::Continue
65//! }
66//! ```
67//!
68//! A milter’s behaviour is implemented as **callback functions** that get
69//! called as certain events happen during an SMTP conversation. Callback
70//! functions are marked up with attribute macros. For example, [`on_rcpt`],
71//! called for each `RCPT TO` command or envelope recipient.
72//!
73//! All callback functions return a **response [`Status`]** that determines how
74//! to proceed after completing the callback. The callbacks in the example all
75//! return [`Continue`], meaning ‘proceed to the next stage’.
76//!
77//! The callback functions are then configured on a **`Milter`** instance in
78//! `main`. [`Milter`] serves as the entry point to configuring and running a
79//! milter application.
80//!
81//! The example also shows how to **store data in the callback context**.
82//! Context storage is accessible through a generic [`DataHandle<T>`] exposed on
83//! the `Context` struct. A thing to keep in mind is that management of the
84//! data’s life cycle is not entirely automatic; in order to avoid leaking
85//! memory, care must be taken to reacquire (and drop) the data before the
86//! connection closes. In our example this is done in `handle_abort`,
87//! implemented just for this purpose.
88//!
89//! Finally, the [`on_eom`] end-of-message callback is the place where **actions
90//! may be applied to a message**. These actions – such as adding a header – can
91//! be found as methods of the [`ContextApi`] struct that is part of the
92//! context.
93//!
94//! The example is complete and ready to run. A call to `Milter::run` starts the
95//! application, passing control to the libmilter library. A running milter can
96//! be stopped by sending a termination signal, for example by pressing
97//! Control-C.
98//!
99//! The remainder of this module documentation discusses some topics to be aware
100//! of when creating milter applications.
101//!
102//! # Callback flow
103//!
104//! For milter writing one must have an understanding of the ‘flow’ of callback
105//! calls. This flow mirrors the succession of events during an SMTP
106//! conversation.
107//!
108//! The callback flow is as follows (when [negotiation] is used, it is the very
109//! first step, preceding `connect`):
110//!
111//! * [`connect`]
112//! * [`helo`]\*
113//! * *for each message:*
114//!   * [`mail`]
115//!   * [`rcpt`]\*
116//!   * [`data`]
117//!   * [`header`]\*
118//!   * [`eoh`]
119//!   * [`body`]\*
120//!   * **[`eom`]**
121//! * [`close`]
122//!
123//! Several messages may be processed in a single connection. When that is the
124//! case, the message-scoped stages `mail` to `eom` will be traversed
125//! repeatedly. Of the connection-scoped and message-scoped stages the ones
126//! indicated may be executed repeatedly. The message-scoped stages are always
127//! bracketed by the connection-scoped stages `connect` and `close`.
128//!
129//! At any point during processing of a *message* the flow may be diverted to
130//! [`abort`], in which case the remaining message stages are skipped and
131//! processing continues at the beginning of the message loop. In any case
132//! `close` will be called at the very end.
133//!
134//! For each stage, a response status returned from the callback determines what
135//! to do with the entity being processed: whether to continue, accept, or
136//! reject it. Only at the `eom` (end-of-message) stage may message modification
137//! operations such as adding headers or altering the message body be applied.
138//!
139//! # Callback resource management
140//!
141//! The callback context allows storing connection-local [data]. Indeed, given
142//! that libmilter may employ multiple threads of execution for handling
143//! requests, all data shared across callback functions must be accessed using
144//! that `DataHandle`.
145//!
146//! Context data need to be allocated and released at an appropriate place in
147//! the callback flow. From the previous section it follows that resources may
148//! logically be connection-scoped or message-scoped. For cleaning up
149//! message-scoped resources, `eom` and `abort` are the natural stages to do so,
150//! whereas for connection-scoped resources it is the `close` stage.
151//!
152//! Note that callback resource management is not automatic. Take care to
153//! reacquire and drop any resources stored in the callback context before the
154//! connection closes. As a rule of thumb, all paths through the callback flow
155//! must include a final call to [`DataHandle::take`]. Failure to drop the data
156//! in time causes that memory to leak.
157//!
158//! # Safety and error handling
159//!
160//! As the libmilter library is written in C, your Rust callback code is
161//! ultimately always invoked by a foreign, C caller. Thanks to the attribute
162//! macro-generated conversion layer, your code is safe even in the presence of
163//! panics: In Rust, panicking across an FFI boundary is undefined behaviour;
164//! the macro-generated layer catches unwinding panics, and so panicking in user
165//! code remains safe.
166//!
167//! As usual, panic is treated as a fatal error. A panic triggered in a callback
168//! results in milter shutdown.
169//!
170//! A less extreme failure mode can be chosen by wrapping the callback return
171//! type in [`milter::Result`], for example `milter::Result<Status>` instead of
172//! `Status`. Then, the `?` operator can be used to propagate unanticipated
173//! errors out of the callback. An `Err` result corresponds to a [`Tempfail`]
174//! response and the milter does not shut down.
175//!
176//! Finally, two safety hazards concern the context’s generic `DataHandle`:
177//! First, we noted above the possibility of leaking memory in the `DataHandle`.
178//! Second, there is a requirement to select the same generic type argument `T`
179//! when writing out the callback function arguments: see the safety note at
180//! [`Context`]. For both of these some programmer discipline is necessary.
181//!
182//! # Globals
183//!
184//! According with the design of the libmilter library, a milter application is
185//! a singleton (one and only one instance). Only a single invocation of
186//! `Milter::run` is allowed to be active at a time per process. Therefore,
187//! global variables are an acceptable and reasonable thing to have.
188//!
189//! Nevertheless, as libmilter may use multiple threads to handle callbacks, any
190//! use of static items should use an adequate synchronisation mechanism.
191//!
192//! [`on_rcpt`]: https://docs.rs/milter-callback/0.2.4/milter_callback/attr.on_rcpt.html
193//! [`Continue`]: Status::Continue
194//! [`on_eom`]: https://docs.rs/milter-callback/0.2.4/milter_callback/attr.on_eom.html
195//! [negotiation]: Milter::on_negotiate
196//! [`connect`]: Milter::on_connect
197//! [`helo`]: Milter::on_helo
198//! [`mail`]: Milter::on_mail
199//! [`rcpt`]: Milter::on_rcpt
200//! [`data`]: Milter::on_data
201//! [`header`]: Milter::on_header
202//! [`eoh`]: Milter::on_eoh
203//! [`body`]: Milter::on_body
204//! [`eom`]: Milter::on_eom
205//! [`close`]: Milter::on_close
206//! [`abort`]: Milter::on_abort
207//! [data]: Context::data
208//! [`milter::Result`]: Result
209//! [`Tempfail`]: Status::Tempfail
210
211mod context;
212mod enums;
213mod error;
214pub mod internal;  // private API for use by milter-callback macros only
215mod runner;
216
217pub use crate::{context::*, enums::*, error::*, runner::*};
218
219#[doc(hidden)]
220pub use milter_callback::*;
221
222use milter_sys as sys;
223
224#[doc(hidden)]
225pub use sys::{sfsistat, SMFICTX};
226
227/// Instructs the libmilter library to exit its event loop, thereby shutting
228/// down any currently running milter.
229///
230/// A call to `shutdown` performs a graceful termination of a milter. It causes
231/// a currently executing blocking call to [`Milter::run`] to return. Note that
232/// **`shutdown` is a terminal operation which disables any further executions
233/// of `Milter::run` in this process.**
234pub fn shutdown() {
235    let _ = unsafe { sys::smfi_stop() };
236}
237
238/// Returns the runtime version triple of the libmilter library.
239///
240/// # Examples
241///
242/// ```
243/// let (major, minor, patch) = milter::version();
244///
245/// println!("milter v{}.{}.{}", major, minor, patch);
246/// ```
247pub fn version() -> (u32, u32, u32) {
248    let (mut major, mut minor, mut patch) = (0, 0, 0);
249
250    let _ = unsafe { sys::smfi_version(&mut major, &mut minor, &mut patch) };
251
252    (major, minor, patch)
253}
254
255/// Sets the trace debug level of the libmilter library to the given value.
256///
257/// The value range is unspecified, but should fall somewhere between 0 (no
258/// logging, the default) and 6 (maximum logging volume).
259pub fn set_debug_level(level: i32) {
260    let _ = unsafe { sys::smfi_setdbg(level) };
261}
262
263#[cfg(test)]
264mod tests {
265    use super::*;
266
267    #[test]
268    fn test_version() {
269        assert_ne!(version(), (0, 0, 0));
270    }
271}