milter 0.2.4

Bindings to the sendmail milter library
Documentation
//! 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));
    }
}