Merge branch 'master' into synth-relay

This commit is contained in:
David Bürgin 2021-12-23 20:57:44 +01:00
commit f639e0313e
27 changed files with 115 additions and 94 deletions

View file

@ -1,11 +1,7 @@
image: rust image: rust
before_script: before_script:
- apt-get --assume-yes update - apt-get --assume-yes update
# TODO Replace opendkim-tools with miltertest in next Debian stable. - apt-get --assume-yes install pkg-config libmilter-dev spamc miltertest
- apt-get --assume-yes install pkg-config libmilter-dev spamc opendkim-tools
test: test:
script: script:
# TODO Dont run integration tests for now. The miltertest tests depend on - cargo test --verbose
# a patch only available in Debian testing. Update once next Debian stable
# is released.
- PKG_CONFIG_PATH=. cargo test --verbose --lib

View file

@ -1,11 +1,17 @@
# SpamAssassin Milter changelog # SpamAssassin Milter changelog
## 0.2.1 (unreleased) ## 0.2.2 (unreleased)
* Add `--synth-relay` option to allow inserting the synthesised internal relay * Add `--synth-relay` option to allow inserting the synthesised internal relay
header (the MTAs `Received` header) at a different position than the at the header (the MTAs `Received` header) at a different position than at the
very beginning. very beginning.
## 0.2.1 (2021-12-23)
* Various cosmetic improvements in code and tests, and updates to
documentation.
* Update dependencies.
## 0.2.0 (2021-08-26) ## 0.2.0 (2021-08-26)
* Bump minimum supported Rust version to 1.46.0. * Bump minimum supported Rust version to 1.46.0.

50
Cargo.lock generated
View file

@ -13,9 +13,9 @@ dependencies = [
[[package]] [[package]]
name = "ansi_term" name = "ansi_term"
version = "0.11.0" version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2"
dependencies = [ dependencies = [
"winapi", "winapi",
] ]
@ -58,9 +58,9 @@ dependencies = [
[[package]] [[package]]
name = "clap" name = "clap"
version = "2.33.3" version = "2.34.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002" checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c"
dependencies = [ dependencies = [
"ansi_term", "ansi_term",
"atty", "atty",
@ -88,9 +88,9 @@ checksum = "68f2d64f2edebec4ce84ad108148e67e1064789bee435edc5b60ad398714a3a9"
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.99" version = "0.2.112"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7f823d141fe0a24df1e23b4af4e3c7ba9e5966ec514ea068c93024aa7deb765" checksum = "1b03d17f364a3a042d5e5d46b053bbbf82c92c9430c592dd4c064dc6ee997125"
[[package]] [[package]]
name = "memchr" name = "memchr"
@ -100,9 +100,9 @@ checksum = "b16bd47d9e329435e309c58469fe0791c2d0d1ba96ec0954152a5ae2b04387dc"
[[package]] [[package]]
name = "milter" name = "milter"
version = "0.2.3" version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92e63d2a1125192952a422c891f6860ee48af6e256c07a48792d181e2752e20d" checksum = "6f8303a6ac7b50962cb1a24ea9f35fd336dc680a628e167962adee4a9a1babf3"
dependencies = [ dependencies = [
"bitflags", "bitflags",
"libc", "libc",
@ -113,9 +113,9 @@ dependencies = [
[[package]] [[package]]
name = "milter-callback" name = "milter-callback"
version = "0.2.3" version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bf7ad54ab8a2d8f324b5628140a585cfae22081d7bde7ebdc11bb7568e890ea" checksum = "6288a38fe087aff33e732c50d4eb05a782a19eef0f29eaf1f9b931ba9ef37f1e"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -124,9 +124,9 @@ dependencies = [
[[package]] [[package]]
name = "milter-sys" name = "milter-sys"
version = "0.2.2" version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c6a5a6fe12debcf5428e77f5c0e569444a131c8c6043b1e2d1513680fa93cd25" checksum = "46d0d54709e88c3b120d9c7bc90555bd5a1584c19f78ca15cd262d4de7faf34c"
dependencies = [ dependencies = [
"libc", "libc",
"pkg-config", "pkg-config",
@ -153,30 +153,30 @@ dependencies = [
[[package]] [[package]]
name = "once_cell" name = "once_cell"
version = "1.8.0" version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56" checksum = "da32515d9f6e6e489d7bc9d84c71b060db7247dc035bbe44eac88cf87486d8d5"
[[package]] [[package]]
name = "pkg-config" name = "pkg-config"
version = "0.3.19" version = "0.3.24"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c" checksum = "58893f751c9b0412871a09abd62ecd2a00298c6c83befa223ef98c52aef40cbe"
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.28" version = "1.0.34"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c7ed8b8c7b886ea3ed7dde405212185f423ab44682667c8c6dd14aa1d9f6612" checksum = "2f84e92c0f7c9d58328b85a78557813e4bd845130db68d7184635344399423b1"
dependencies = [ dependencies = [
"unicode-xid", "unicode-xid",
] ]
[[package]] [[package]]
name = "quote" name = "quote"
version = "1.0.9" version = "1.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" checksum = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
] ]
@ -200,7 +200,7 @@ checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
[[package]] [[package]]
name = "spamassassin-milter" name = "spamassassin-milter"
version = "0.2.0" version = "0.2.1"
dependencies = [ dependencies = [
"chrono", "chrono",
"clap", "clap",
@ -219,9 +219,9 @@ checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
[[package]] [[package]]
name = "syn" name = "syn"
version = "1.0.74" version = "1.0.82"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1873d832550d4588c3dbc20f01361ab00bfe741048f71e3fecf145a7cc18b29c" checksum = "8daf5dd0bb60cbd4137b1b587d2fc0ae729bc07cf01cd70b36a1ed5ade3b9d59"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -250,9 +250,9 @@ dependencies = [
[[package]] [[package]]
name = "unicode-width" name = "unicode-width"
version = "0.1.8" version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973"
[[package]] [[package]]
name = "unicode-xid" name = "unicode-xid"

View file

@ -1,7 +1,8 @@
[package] [package]
name = "spamassassin-milter" name = "spamassassin-milter"
version = "0.2.0" version = "0.2.1"
edition = "2018" edition = "2018"
rust-version = "1.46.0"
description = "Milter for spam filtering with SpamAssassin" description = "Milter for spam filtering with SpamAssassin"
license = "GPL-3.0-or-later" license = "GPL-3.0-or-later"
categories = ["email"] categories = ["email"]

View file

@ -55,7 +55,7 @@ provided `milter.pc` file. Put this file on the pkg-config path when running any
Cargo command: Cargo command:
``` ```
PKG_CONFIG_PATH=. cargo build PKG_CONFIG_PATH=/path/to/milter.pc cargo build
``` ```
SpamAssassin Milter uses the `spamc` program for communication with SpamAssassin SpamAssassin Milter uses the `spamc` program for communication with SpamAssassin
@ -151,6 +151,12 @@ property. The classification threshold can be adjusted by setting
required_score 9.0 required_score 9.0
``` ```
In SpamAssassin Milter, the `--reject-spam` option will cause messages flagged
as spam to be rejected during the SMTP conversation. When rejecting spam in this
manner, accepted (not rejected) messages will not have the `X-Spam-Flag: YES`
header; if you need to make a further decision about the message coming through,
look at the score in the `X-Spam-Level` header instead.
By default, SpamAssassin creates reports for messages it recognises as spam. By default, SpamAssassin creates reports for messages it recognises as spam.
These reports replace the message body, that is, the message body is rewritten These reports replace the message body, that is, the message body is rewritten
to present a report instead of the original message, and the original message is to present a report instead of the original message, and the original message is
@ -244,7 +250,8 @@ on those.
As an example, in case [Dovecot] does mail delivery using [LMTP], enable the As an example, in case [Dovecot] does mail delivery using [LMTP], enable the
Sieve plugin for the LMTP protocol, and then set up a global Sieve script that Sieve plugin for the LMTP protocol, and then set up a global Sieve script that
files messages flagged as spam into the Junk folder: files messages flagged as spam into the Junk folder. That is, test for the
header `X-Spam-Flag: YES`:
``` ```
require "fileinto"; require "fileinto";
@ -255,6 +262,10 @@ if header "X-Spam-Flag" "YES" {
} }
``` ```
Alternatively, use the value of the `X-Spam-Level` header instead. In the
example above, the test `header :contains "X-Spam-Level" "*****"` would file
messages with score 5.0 or above into Junk.
[Sieve]: http://sieve.info [Sieve]: http://sieve.info
[Dovecot]: https://dovecot.org [Dovecot]: https://dovecot.org
[LMTP]: https://doc.dovecot.org/configuration_manual/protocols/lmtp_server [LMTP]: https://doc.dovecot.org/configuration_manual/protocols/lmtp_server

View file

@ -1,4 +1,4 @@
.TH SPAMASSASSIN-MILTER 8 2021-08-26 .TH SPAMASSASSIN-MILTER 8 2021-12-23
.SH NAME .SH NAME
spamassassin-milter \- milter for spam filtering with SpamAssassin spamassassin-milter \- milter for spam filtering with SpamAssassin
.SH SYNOPSIS .SH SYNOPSIS

View file

@ -48,7 +48,8 @@ fn handle_negotiate(
} }
} }
let req_protocol_opts = ProtocolOpts::SKIP | ProtocolOpts::HEADER_LEADING_SPACE; let req_protocol_opts =
ProtocolOpts::NO_UNKNOWN | ProtocolOpts::SKIP | ProtocolOpts::HEADER_LEADING_SPACE;
assert!(actions.contains(req_actions), "required milter actions not supported"); assert!(actions.contains(req_actions), "required milter actions not supported");
assert!(protocol_opts.contains(req_protocol_opts), "required milter protocol options not supported"); assert!(protocol_opts.contains(req_protocol_opts), "required milter protocol options not supported");

View file

@ -319,7 +319,7 @@ impl Client {
} }
} }
Ok(Status::Accept) Ok(Status::Continue)
} }
} }
@ -435,27 +435,27 @@ mod tests {
impl ActionContext for MockActionContext { impl ActionContext for MockActionContext {
fn add_header(&self, name: &str, value: &str) -> milter::Result<()> { fn add_header(&self, name: &str, value: &str) -> milter::Result<()> {
Ok(self.called.borrow_mut().push( let action = Action::AddHeader(name.to_owned(), value.to_owned());
Action::AddHeader(name.to_owned(), value.to_owned()) self.called.borrow_mut().push(action);
)) Ok(())
} }
fn insert_header(&self, index: usize, name: &str, value: &str) -> milter::Result<()> { fn insert_header(&self, index: usize, name: &str, value: &str) -> milter::Result<()> {
Ok(self.called.borrow_mut().push( let action = Action::InsertHeader(index, name.to_owned(), value.to_owned());
Action::InsertHeader(index, name.to_owned(), value.to_owned()) self.called.borrow_mut().push(action);
)) Ok(())
} }
fn replace_header(&self, name: &str, index: usize, value: Option<&str>) -> milter::Result<()> { fn replace_header(&self, name: &str, index: usize, value: Option<&str>) -> milter::Result<()> {
Ok(self.called.borrow_mut().push( let action = Action::ReplaceHeader(name.to_owned(), index, value.map(|v| v.to_owned()));
Action::ReplaceHeader(name.to_owned(), index, value.map(|v| v.to_owned())) self.called.borrow_mut().push(action);
)) Ok(())
} }
fn append_body_chunk(&self, bytes: &[u8]) -> milter::Result<()> { fn append_body_chunk(&self, bytes: &[u8]) -> milter::Result<()> {
Ok(self.called.borrow_mut().push( let action = Action::AppendBodyChunk(bytes.to_owned());
Action::AppendBodyChunk(bytes.to_owned()) self.called.borrow_mut().push(action);
)) Ok(())
} }
fn replace_sender(&self, _: &str, _: Option<&str>) -> milter::Result<()> { fn replace_sender(&self, _: &str, _: Option<&str>) -> milter::Result<()> {
@ -486,11 +486,13 @@ mod tests {
ext_code: Option<&str>, ext_code: Option<&str>,
msg_lines: Vec<&str>, msg_lines: Vec<&str>,
) -> milter::Result<()> { ) -> milter::Result<()> {
Ok(self.called.borrow_mut().push(Action::SetErrorReply( let action = Action::SetErrorReply(
code.to_owned(), code.to_owned(),
ext_code.map(|c| c.to_owned()), ext_code.map(|c| c.to_owned()),
msg_lines.into_iter().map(|l| l.to_owned()).collect(), msg_lines.into_iter().map(|l| l.to_owned()).collect(),
))) );
self.called.borrow_mut().push(action);
Ok(())
} }
} }
@ -590,7 +592,7 @@ mod tests {
let status = client.process("id", &actions, &config).unwrap(); let status = client.process("id", &actions, &config).unwrap();
assert_eq!(status, Status::Accept); assert_eq!(status, Status::Continue);
assert_eq!( assert_eq!(
actions.called.borrow().as_slice(), actions.called.borrow().as_slice(),
[ [
@ -616,7 +618,7 @@ mod tests {
let status = client.process("id", &actions, &config).unwrap(); let status = client.process("id", &actions, &config).unwrap();
assert_eq!(status, Status::Accept); assert_eq!(status, Status::Continue);
let called = actions.called.borrow(); let called = actions.called.borrow();
assert!(called.contains(&Action::InsertHeader(0, "X-Spam-Level".into(), " *****".into()))); assert!(called.contains(&Action::InsertHeader(0, "X-Spam-Level".into(), " *****".into())));

View file

@ -74,7 +74,7 @@ fn header_lines(header: &[u8]) -> Vec<&[u8]> {
fn parse_header_line(bytes: &[u8]) -> Result<Header<'_>> { fn parse_header_line(bytes: &[u8]) -> Result<Header<'_>> {
// This assumes that headers received back from SpamAssassin are valid // This assumes that headers received back from SpamAssassin are valid
// UTF-8, which is plausible since the client only sent UTF-8 earlier. // UTF-8, which should be the case since the client only sent UTF-8 earlier.
let line = str::from_utf8(bytes).map_err(|_| Error::ParseEmail)?; let line = str::from_utf8(bytes).map_err(|_| Error::ParseEmail)?;
let (name, value) = line.split_at(line.find(':').ok_or(Error::ParseEmail)?); let (name, value) = line.split_at(line.find(':').ok_or(Error::ParseEmail)?);
@ -235,12 +235,13 @@ impl<'a, 'c> HeaderRewriter<'a, 'c> {
id: &str, id: &str,
actions: &impl ActionContext, actions: &impl ActionContext,
) -> milter::Result<()> { ) -> milter::Result<()> {
let mods = self.spam_assassin_mods.iter();
if self.prepend.unwrap_or(false) { if self.prepend.unwrap_or(false) {
// Prepend X-Spam- headers in reverse order, so that they appear // Prepend X-Spam- headers in reverse order, so that they appear
// in the order received from SpamAssassin. // in the order received from SpamAssassin.
execute_mods(id, self.spam_assassin_mods.iter().rev(), actions, self.config)?; execute_mods(id, mods.rev(), actions, self.config)?;
} else { } else {
execute_mods(id, self.spam_assassin_mods.iter(), actions, self.config)?; execute_mods(id, mods, actions, self.config)?;
} }
// Delete all incoming X-Spam- headers not returned by SpamAssassin to // Delete all incoming X-Spam- headers not returned by SpamAssassin to
@ -370,9 +371,9 @@ mod tests {
#[test] #[test]
fn email_split_at_eoh() { fn email_split_at_eoh() {
assert_eq!(split_at_eoh(b"x\r\n\r\ny"), Ok((b"x\r\n" as &[_], b"y" as &[_]))); assert_eq!(split_at_eoh(b"x\r\n\r\ny"), Ok((&b"x\r\n"[..], &b"y"[..])));
assert_eq!(split_at_eoh(b"x\r\n\r\n"), Ok((b"x\r\n" as &[_], b"" as &[_]))); assert_eq!(split_at_eoh(b"x\r\n\r\n"), Ok((&b"x\r\n"[..], &b""[..])));
assert_eq!(split_at_eoh(b"\r\n\r\ny"), Ok((b"\r\n" as &[_], b"y" as &[_]))); assert_eq!(split_at_eoh(b"\r\n\r\ny"), Ok((&b"\r\n"[..], &b"y"[..])));
assert_eq!(split_at_eoh(b"\r\ny"), Err(Error::ParseEmail)); assert_eq!(split_at_eoh(b"\r\ny"), Err(Error::ParseEmail));
assert_eq!(split_at_eoh(b"y"), Err(Error::ParseEmail)); assert_eq!(split_at_eoh(b"y"), Err(Error::ParseEmail));
} }
@ -380,25 +381,25 @@ mod tests {
#[test] #[test]
fn email_header_lines_empty() { fn email_header_lines_empty() {
assert_eq!(header_lines(b""), Vec::<&[_]>::new()); assert_eq!(header_lines(b""), Vec::<&[_]>::new());
assert_eq!(header_lines(b"\r\n"), vec![b"" as &[_]]); assert_eq!(header_lines(b"\r\n"), vec![&b""[..]]);
assert_eq!(header_lines(b"\r\n\r\n"), vec![b"" as &[_], b"" as &[_]]); assert_eq!(header_lines(b"\r\n\r\n"), vec![&b""[..], &b""[..]]);
} }
#[test] #[test]
fn email_header_lines_simple() { fn email_header_lines_simple() {
assert_eq!(header_lines(b"x\r\n"), vec![b"x" as &[_]]); assert_eq!(header_lines(b"x\r\n"), vec![&b"x"[..]]);
assert_eq!(header_lines(b"x\r\ny"), vec![b"x" as &[_], b"y" as &[_]]); assert_eq!(header_lines(b"x\r\ny"), vec![&b"x"[..], &b"y"[..]]);
assert_eq!(header_lines(b"x\r\ny\r\n"), vec![b"x" as &[_], b"y" as &[_]]); assert_eq!(header_lines(b"x\r\ny\r\n"), vec![&b"x"[..], &b"y"[..]]);
} }
#[test] #[test]
fn email_header_lines_multi() { fn email_header_lines_multi() {
assert_eq!(header_lines(b"x\r\n\t"), vec![b"x\r\n\t" as &[_]]); assert_eq!(header_lines(b"x\r\n\t"), vec![&b"x\r\n\t"[..]]);
assert_eq!(header_lines(b"x\r\n\ty"), vec![b"x\r\n\ty" as &[_]]); assert_eq!(header_lines(b"x\r\n\ty"), vec![&b"x\r\n\ty"[..]]);
assert_eq!(header_lines(b"x\r\n\ty\r\n"), vec![b"x\r\n\ty" as &[_]]); assert_eq!(header_lines(b"x\r\n\ty\r\n"), vec![&b"x\r\n\ty"[..]]);
assert_eq!( assert_eq!(
header_lines(b"x\r\n\ty\r\n\tz\r\nq"), header_lines(b"x\r\n\ty\r\n\tz\r\nq"),
vec![b"x\r\n\ty\r\n\tz" as &[_], b"q" as &[_]] vec![&b"x\r\n\ty\r\n\tz"[..], &b"q"[..]]
); );
} }

View file

@ -1,6 +1,6 @@
-- An authenticated sender is accepted, the message is not processed. -- An authenticated sender is accepted, the message is not processed.
conn = mt.connect("inet:3333@localhost") local conn = mt.connect("inet:3333@localhost")
assert(conn, "could not open connection") assert(conn, "could not open connection")
local err = mt.conninfo(conn, "client.gluet.ch", "123.123.123.123") local err = mt.conninfo(conn, "client.gluet.ch", "123.123.123.123")

View file

@ -1,6 +1,6 @@
mod common; mod common;
pub use common::*; // `pub` only to silence unused code warnings pub use common::*;
use spamassassin_milter::*; use spamassassin_milter::*;
#[test] #[test]

View file

@ -1,6 +1,6 @@
-- Happy path processing of an ordinary ham (not spam) message. -- Happy path processing of an ordinary ham (not spam) message.
conn = mt.connect("inet:3333@localhost") local conn = mt.connect("inet:3333@localhost")
assert(conn, "could not open connection") assert(conn, "could not open connection")
local err = mt.conninfo(conn, "client.gluet.ch", "123.123.123.123") local err = mt.conninfo(conn, "client.gluet.ch", "123.123.123.123")
@ -65,7 +65,7 @@ assert(mt.getreply(conn) == SMFIR_CONTINUE)
local err = mt.eom(conn) local err = mt.eom(conn)
assert(err == nil, err) assert(err == nil, err)
assert(mt.getreply(conn) == SMFIR_ACCEPT) assert(mt.getreply(conn) == SMFIR_CONTINUE)
assert(mt.eom_check(conn, MT_HDRINSERT, "X-Spam-Custom", " Custom-Value")) assert(mt.eom_check(conn, MT_HDRINSERT, "X-Spam-Custom", " Custom-Value"))
assert(mt.eom_check(conn, MT_HDRDELETE, "X-Spam-Checker-Version")) assert(mt.eom_check(conn, MT_HDRDELETE, "X-Spam-Checker-Version"))

View file

@ -1,6 +1,6 @@
mod common; mod common;
pub use common::*; // `pub` only to silence unused code warnings pub use common::*;
use spamassassin_milter::*; use spamassassin_milter::*;
#[test] #[test]
@ -23,6 +23,7 @@ fn ham_message() {
Ok(ham) Ok(ham)
}); });
let miltertest = spawn_miltertest_runner(file!()); let miltertest = spawn_miltertest_runner(file!());
run("inet:3333@localhost", config).expect("milter execution failed"); run("inet:3333@localhost", config).expect("milter execution failed");

View file

@ -1,6 +1,6 @@
-- Live test against a real SpamAssassin server. -- Live test against a real SpamAssassin server.
conn = mt.connect("inet:3333@localhost") local conn = mt.connect("inet:3333@localhost")
assert(conn, "could not open connection") assert(conn, "could not open connection")
local err = mt.conninfo(conn, "client.gluet.ch", "123.123.123.123") local err = mt.conninfo(conn, "client.gluet.ch", "123.123.123.123")
@ -65,7 +65,7 @@ assert(mt.getreply(conn) == SMFIR_CONTINUE)
local err = mt.eom(conn) local err = mt.eom(conn)
assert(err == nil, err) assert(err == nil, err)
assert(mt.getreply(conn) == SMFIR_ACCEPT) assert(mt.getreply(conn) == SMFIR_CONTINUE)
local err = mt.disconnect(conn) local err = mt.disconnect(conn)
assert(err == nil, err) assert(err == nil, err)

View file

@ -1,6 +1,6 @@
mod common; mod common;
pub use common::*; // `pub` only to silence unused code warnings pub use common::*;
use spamassassin_milter::*; use spamassassin_milter::*;
/// Runs a live test against a real SpamAssassin server instance. This test is /// Runs a live test against a real SpamAssassin server instance. This test is

View file

@ -1,6 +1,6 @@
-- 1) A connection from the loopback IP address is accepted. -- 1) A connection from the loopback IP address is accepted.
conn = mt.connect("inet:3333@localhost") local conn = mt.connect("inet:3333@localhost")
assert(conn, "could not open connection") assert(conn, "could not open connection")
local err = mt.conninfo(conn, nil, "127.0.0.1") local err = mt.conninfo(conn, nil, "127.0.0.1")
@ -13,7 +13,7 @@ assert(err == nil, err)
-- 2) A connection from an unknown IP address (for example, from a UNIX -- 2) A connection from an unknown IP address (for example, from a UNIX
-- domain socket) is also accepted. -- domain socket) is also accepted.
conn = mt.connect("inet:3333@localhost") local conn = mt.connect("inet:3333@localhost")
assert(conn, "could not open connection") assert(conn, "could not open connection")
local err = mt.conninfo(conn, nil, "unspec") local err = mt.conninfo(conn, nil, "unspec")

View file

@ -1,6 +1,6 @@
mod common; mod common;
pub use common::*; // `pub` only to silence unused code warnings pub use common::*;
use spamassassin_milter::*; use spamassassin_milter::*;
#[test] #[test]

View file

@ -1,6 +1,6 @@
-- A spam message is rejected with an SMTP error reply. -- A spam message is rejected with an SMTP error reply.
conn = mt.connect("inet:3333@localhost") local conn = mt.connect("inet:3333@localhost")
assert(conn, "could not open connection") assert(conn, "could not open connection")
local err = mt.conninfo(conn, "client.gluet.ch", "123.123.123.123") local err = mt.conninfo(conn, "client.gluet.ch", "123.123.123.123")
@ -56,7 +56,7 @@ local err = mt.bodystring(conn, "Test message body")
assert(err == nil, err) assert(err == nil, err)
assert(mt.getreply(conn) == SMFIR_CONTINUE) assert(mt.getreply(conn) == SMFIR_CONTINUE)
-- A `miltertest` (or milter protocol?) pitfall: Even though we return the -- A `miltertest` (or milter protocol) pitfall: Even though we return the
-- `SMFIR_REJECT` status in the application code, because we use a custom error -- `SMFIR_REJECT` status in the application code, because we use a custom error
-- reply, we must check for `SMFIR_REPLYCODE` instead. -- reply, we must check for `SMFIR_REPLYCODE` instead.
local err = mt.eom(conn) local err = mt.eom(conn)

View file

@ -1,6 +1,6 @@
mod common; mod common;
pub use common::*; // `pub` only to silence unused code warnings pub use common::*;
use spamassassin_milter::*; use spamassassin_milter::*;
#[test] #[test]
@ -16,6 +16,7 @@ fn reject_spam() {
let server = spawn_mock_spamd_server(SPAMD_PORT, |spam| { let server = spawn_mock_spamd_server(SPAMD_PORT, |spam| {
Err(spam.replacen("\r\n\r\n", "\r\nX-Spam-Flag: YES\r\n\r\n", 1)) Err(spam.replacen("\r\n\r\n", "\r\nX-Spam-Flag: YES\r\n\r\n", 1))
}); });
let miltertest = spawn_miltertest_runner(file!()); let miltertest = spawn_miltertest_runner(file!());
run("inet:3333@localhost", config).expect("milter execution failed"); run("inet:3333@localhost", config).expect("milter execution failed");

View file

@ -2,7 +2,7 @@
-- reached, the rest is skipped (oversized messages are not processed by -- reached, the rest is skipped (oversized messages are not processed by
-- SpamAssassin, so it is futile to send the whole message in this case). -- SpamAssassin, so it is futile to send the whole message in this case).
conn = mt.connect("inet:3333@localhost") local conn = mt.connect("inet:3333@localhost")
assert(conn, "could not open connection") assert(conn, "could not open connection")
local err = mt.conninfo(conn, "client.gluet.ch", "123.123.123.123") local err = mt.conninfo(conn, "client.gluet.ch", "123.123.123.123")
@ -72,7 +72,7 @@ assert(mt.getreply(conn) == SMFIR_SKIP)
local err = mt.eom(conn) local err = mt.eom(conn)
assert(err == nil, err) assert(err == nil, err)
assert(mt.getreply(conn) == SMFIR_ACCEPT) assert(mt.getreply(conn) == SMFIR_CONTINUE)
local err = mt.disconnect(conn) local err = mt.disconnect(conn)
assert(err == nil, err) assert(err == nil, err)

View file

@ -1,6 +1,6 @@
mod common; mod common;
pub use common::*; // `pub` only to silence unused code warnings pub use common::*;
use spamassassin_milter::*; use spamassassin_milter::*;
#[test] #[test]

View file

@ -1,6 +1,6 @@
-- Happy path processing of a spam message. -- Happy path processing of a spam message.
conn = mt.connect("inet:3333@localhost") local conn = mt.connect("inet:3333@localhost")
assert(conn, "could not open connection") assert(conn, "could not open connection")
local err = mt.conninfo(conn, "client.gluet.ch", "123.123.123.123") local err = mt.conninfo(conn, "client.gluet.ch", "123.123.123.123")
@ -65,7 +65,7 @@ assert(mt.getreply(conn) == SMFIR_CONTINUE)
local err = mt.eom(conn) local err = mt.eom(conn)
assert(err == nil, err) assert(err == nil, err)
assert(mt.getreply(conn) == SMFIR_ACCEPT) assert(mt.getreply(conn) == SMFIR_CONTINUE)
assert(mt.eom_check(conn, MT_HDRDELETE, "X-Spam-Checker-Version")) assert(mt.eom_check(conn, MT_HDRDELETE, "X-Spam-Checker-Version"))
assert(mt.eom_check(conn, MT_HDRADD, "X-Spam-Checker-Version", " MyChecker 1.0.0")) assert(mt.eom_check(conn, MT_HDRADD, "X-Spam-Checker-Version", " MyChecker 1.0.0"))

View file

@ -1,6 +1,6 @@
mod common; mod common;
pub use common::*; // `pub` only to silence unused code warnings pub use common::*;
use spamassassin_milter::*; use spamassassin_milter::*;
#[test] #[test]
@ -37,6 +37,7 @@ fn spam_message() {
Err(spam) Err(spam)
}); });
let miltertest = spawn_miltertest_runner(file!()); let miltertest = spawn_miltertest_runner(file!());
run("inet:3333@localhost", config).expect("milter execution failed"); run("inet:3333@localhost", config).expect("milter execution failed");

View file

@ -1,6 +1,6 @@
-- When no `spamd` server is available, `spamc` fails to connect. -- When no `spamd` server is available, `spamc` fails to connect.
conn = mt.connect("inet:3333@localhost") local conn = mt.connect("inet:3333@localhost")
assert(conn, "could not open connection") assert(conn, "could not open connection")
local err = mt.conninfo(conn, "client.gluet.ch", "123.123.123.123") local err = mt.conninfo(conn, "client.gluet.ch", "123.123.123.123")

View file

@ -1,6 +1,6 @@
mod common; mod common;
pub use common::*; // `pub` only to silence unused code warnings pub use common::*;
use spamassassin_milter::*; use spamassassin_milter::*;
#[test] #[test]

View file

@ -1,6 +1,6 @@
-- A connection from a trusted network is accepted. -- A connection from a trusted network is accepted.
conn = mt.connect("inet:3333@localhost") local conn = mt.connect("inet:3333@localhost")
assert(conn, "could not open connection") assert(conn, "could not open connection")
local err = mt.conninfo(conn, "client.gluet.ch", "123.123.123.123") local err = mt.conninfo(conn, "client.gluet.ch", "123.123.123.123")

View file

@ -1,6 +1,6 @@
mod common; mod common;
pub use common::*; // `pub` only to silence unused code warnings pub use common::*;
use spamassassin_milter::*; use spamassassin_milter::*;
#[test] #[test]