Skip to main content

spamassassin_milter/
lib.rs

1// SpamAssassin Milter – milter for spam filtering with SpamAssassin
2// Copyright © 2020–2024 David Bürgin <dbuergin@gluet.ch>
3//
4// This program is free software: you can redistribute it and/or modify it under
5// the terms of the GNU General Public License as published by the Free Software
6// Foundation, either version 3 of the License, or (at your option) any later
7// version.
8//
9// This program is distributed in the hope that it will be useful, but WITHOUT
10// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
11// FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
12// details.
13//
14// You should have received a copy of the GNU General Public License along with
15// this program. If not, see <https://www.gnu.org/licenses/>.
16
17//! The SpamAssassin Milter application library.
18//!
19//! This library was published to facilitate integration testing of the
20//! [SpamAssassin Milter application][SpamAssassin Milter]. No backwards
21//! compatibility guarantees are made for the public API in this library. Please
22//! look into the application instead.
23//!
24//! [SpamAssassin Milter]: https://crates.io/crates/spamassassin-milter
25
26// The standard `eprintln` macro is replaced throughout with a best-effort,
27// non-panicking version.
28macro_rules! eprintln {
29    ($($arg:tt)*) => {
30        {
31            use ::std::io::Write;
32            let _ = ::std::writeln!(::std::io::stderr(), $($arg)*);
33        }
34    };
35}
36
37macro_rules! verbose {
38    ($config:ident, $($arg:tt)*) => {
39        if $config.verbose() {
40            // Note: not qualifying `eprintln!` here makes it use textual scope
41            // and thus refer to above definition.
42            eprintln!($($arg)*);
43        }
44    };
45}
46
47mod callbacks;
48mod client;
49mod collections;
50mod config;
51mod email;
52mod error;
53
54pub use crate::config::{Config, ConfigBuilder};
55use indymilter::Listener;
56use std::{future::Future, io};
57
58/// The name of the SpamAssassin Milter application.
59pub const MILTER_NAME: &str = "SpamAssassin Milter";
60
61/// The current version string of SpamAssassin Milter.
62pub const VERSION: &str = env!("CARGO_PKG_VERSION");
63
64/// Starts SpamAssassin Milter listening on the given socket using the supplied
65/// configuration.
66///
67/// # Errors
68///
69/// If execution of the milter fails, an error is returned.
70///
71/// # Examples
72///
73/// ```
74/// # async fn f() -> std::io::Result<()> {
75/// use std::process;
76/// use tokio::{net::TcpListener, signal};
77///
78/// let listener = TcpListener::bind("127.0.0.1:3000").await?;
79/// let config = Default::default();
80/// let shutdown = signal::ctrl_c();
81///
82/// if let Err(e) = spamassassin_milter::run(listener, config, shutdown).await {
83///     eprintln!("failed to run spamassassin-milter: {e}");
84///     process::exit(1);
85/// }
86/// # Ok(())
87/// # }
88/// ```
89pub async fn run(
90    listener: impl Listener,
91    config: Config,
92    shutdown: impl Future,
93) -> io::Result<()> {
94    let callbacks = callbacks::make_callbacks(config);
95    let config = Default::default();
96
97    eprintln!("{MILTER_NAME} {VERSION} starting");
98
99    let result = indymilter::run(listener, callbacks, config, shutdown).await;
100
101    match &result {
102        Ok(()) => eprintln!("{MILTER_NAME} {VERSION} shut down"),
103        Err(e) => eprintln!("{MILTER_NAME} {VERSION} terminated with error: {e}"),
104    }
105
106    result
107}