Merge branch 'master' into synth-relay
This commit is contained in:
commit
f639e0313e
27 changed files with 115 additions and 94 deletions
|
@ -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 Don’t 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
|
|
||||||
|
|
10
CHANGELOG.md
10
CHANGELOG.md
|
@ -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 MTA’s `Received` header) at a different position than the at the
|
header (the MTA’s `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
50
Cargo.lock
generated
|
@ -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"
|
||||||
|
|
|
@ -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"]
|
||||||
|
|
15
README.md
15
README.md
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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");
|
||||||
|
|
|
@ -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())));
|
||||||
|
|
31
src/email.rs
31
src/email.rs
|
@ -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"[..]]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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]
|
||||||
|
|
|
@ -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"))
|
||||||
|
|
|
@ -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");
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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]
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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");
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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]
|
||||||
|
|
|
@ -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"))
|
||||||
|
|
|
@ -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");
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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]
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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]
|
||||||
|
|
Loading…
Reference in a new issue