[−][src]Crate milter
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 for libmilter, that is, the sendmail
mail filter API. As such, it does not try to hide the characteristics of
that venerable C library, but exposes its capabilities faithfully with all
its quirks. The goal is to make milter implementation easier and (as they
say) safer. The functionality exposed through the Context
and
ActionContext
structs as well as flags such as Actions
should be
immediately familiar – though the names have been adapted – if you have used
libmilter before.
Once it has started up, a milter application is driven by the underlying libmilter C implementation. This documentation will speak of the ‘milter library’ in those cases.
Usage
As an example, we can create a milter that counts the envelope recipients of an email, and adds a header recording the count.
This demonstrates all important milter functionality: handling of events with callbacks (the envelope recipients) and returning response statuses (here always ‘continue’), storing data in the callback context (the recipient count), and finally performing some message modification (adding a header).
#[macro_use] extern crate milter; use milter::*; #[on_rcpt(rcpt_callback)] fn handle_rcpt(ctx: Context<usize>, _: Vec<&str>) -> milter::Result<Status> { match ctx.data.borrow_mut()? { Some(mut count) => *count += 1, None => { ctx.data.replace(1)?; } } Ok(Status::Continue) } #[on_eom(eom_callback)] fn handle_eom(ctx: ActionContext<usize>) -> milter::Result<Status> { if let Some(count) = ctx.data.take()? { ctx.add_header("X-Rcpt-Count", &count.to_string())?; } Ok(Status::Continue) } #[on_abort(abort_callback)] fn handle_abort(ctx: Context<usize>) -> Status { let _ = ctx.data.take(); Status::Continue } fn main() { let _ = Milter::new("inet:3000@localhost") .name("RcptMilter") .on_rcpt(rcpt_callback) .on_eom(eom_callback) .on_abort(abort_callback) .actions(Actions::ADD_HEADER) .run(); }
Intended for creation of main.rs binaries. Signals are handled by the milter library, shut down with Ctrl-C.
The remainder of this module documentation highlights some aspects to be aware of when creating a milter application.
Callback flow
One must be aware of the basic flow of callback calls. The flow is as follows. When negotiation is used, this is the very first step, preceding connect.
Several messages can be processed in a single connection. In that case, the
message-oriented stages (mail
to eom
) will be traversed repeatedly.
Message-oriented processing is always bracketed by connection-oriented
stages connect
and close
.
At any point during processing of a message the flow can be derouted to
abort
, which skips the remaining of the message steps and processing
continues at the start of the message loop.
In any case, close
will be called at end of processing, so this is the
natural place to do cleanup of resources.
Notice that resources can be connection-scoped and message-scoped.
At each stage, a Status is returned that decides whether to continue,
reject, etc. Only at the eom
stage, message modification operations can be
applied, such as adding headers or altering the message body.
Callback resource management
The callback context allows storing connection-specific and message-specific
data. This is an area that has one manual memory management requirement. Any
resources stored in the callback context DataHandle
must be reacquired
and dropped before the connection closes.
That is, all code paths through a complete callback flow must include a
final call to DataHandle::take
!
Failure to acquire the data causes that memory to leak.
Safety
In the callback code panicking must be avoided. As callback code is executed through a foreign code layer, restrictions re FFI apply. Stack unwinding through an FFI boundary is undefined behaviour.
That is why Result
is used pervasively.
See also the Result
as return type capability.
Globals
A milter implementation is fundamentally a singleton within the process.
Only one invocation of Milter::run
is allowed per process. Therefore,
globals are an acceptable and reasonable thing to have.
Testing
For integration-level testing of a milter the tool miltertest can be used. This is provided by the opendkim-tools package.
Structs
ActionContext | Context of the end of message, ie actions stage. |
Actions | Flags representing milter actions. |
Context | Milter context supplied to milter callback functions. |
DataHandle | A handle on user data managed in the callback context. |
Error | Errors returned from milter methods, with source attached if available. |
Milter | A configurable milter runner. |
ProtocolOpts | Flags representing milter protocol options. |
Enums
ErrorKind | The kind of milter error. |
Stage | Milter protocol stage. |
Status | Callback response status. |
Functions
set_debug_level | Sets the trace debug level of the milter library to the given value. |
version | Returns the runtime version of the milter library. |
Type Definitions
AbortCallback | The type of the |
BodyCallback | The type of the |
CloseCallback | The type of the |
ConnectCallback | The type of the |
DataCallback | The type of the |
EohCallback | The type of the |
EomCallback | The type of the |
HeaderCallback | The type of the |
HeloCallback | The type of the |
MailCallback | The type of the |
NegotiateCallback | The type of the |
RcptCallback | The type of the |
Result | A result type specialised for milter errors. |
UnknownCallback | The type of the |