Bump version to 0.0.2

This commit is contained in:
David Bürgin 2020-02-09 18:18:22 +01:00
parent e6817c03e7
commit 4ed2133618
10 changed files with 130 additions and 126 deletions

10
Cargo.lock generated
View file

@ -81,9 +81,9 @@ checksum = "d515b1f41455adea1313a4a2ac8a8a477634fbae63cc6100e3aebb207ce61558"
[[package]]
name = "milter"
version = "0.1.8"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf37b1c8105474d1c2b1eef110ded017f097543f09dddf97323b3a320b540e6b"
checksum = "b5f5537d7984b87f0984fde9d1288e142070b4e31638d312486d924083ab7684"
dependencies = [
"bitflags",
"libc",
@ -94,9 +94,9 @@ dependencies = [
[[package]]
name = "milter-callback"
version = "0.1.8"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38bd474a4ccfa8782f6a3f8696e3c4f409bc360544b8ba30fbe4104c2844196d"
checksum = "3a17d9b95c0a40956dc1935e4ce868392add871d1e4cae669323fb3e19a0c91f"
dependencies = [
"proc-macro2",
"quote",
@ -170,7 +170,7 @@ checksum = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84"
[[package]]
name = "spamassassin-milter"
version = "0.0.1"
version = "0.0.2"
dependencies = [
"chrono",
"clap",

View file

@ -1,6 +1,6 @@
[package]
name = "spamassassin-milter"
version = "0.0.1" # remember to update html_root_url
version = "0.0.2" # remember to update html_root_url
description = "Milter for spam filtering with SpamAssassin"
authors = ["David Bürgin <dbuergin@gluet.ch>"]
edition = "2018"
@ -15,7 +15,7 @@ chrono = "0.4"
clap = "2"
ipnet = "2"
libc = "0.2"
milter = "0.1"
milter = "0.2"
once_cell = "1"
[badges]

View file

@ -52,7 +52,7 @@ logging option, this gives good insight in the log about the changes that
SpamAssassin Milter would apply.
```
/usr/sbin/spamassassin-milter --dry-run --verbose inet:3001:localhost
/usr/sbin/spamassassin-milter --dry-run --verbose inet:3001@localhost
```
```

View file

@ -1,4 +1,4 @@
.TH SPAMASSASSIN-MILTER 8 2020-02-07
.TH SPAMASSASSIN-MILTER 8 2020-02-09
.SH NAME
spamassassin-milter \- milter for spam filtering with SpamAssassin
.SH SYNOPSIS
@ -15,12 +15,13 @@ spamassassin-milter \- milter for spam filtering with SpamAssassin
[\fB\-\-\fR \fISPAMC_ARGS\fR...]
.SH DESCRIPTION
.B spamassassin-milter
is a milter that filters email through SpamAssassin server using the spamc
is a milter that filters email through SpamAssassin server using the
.B spamc
client.
It reads the response from SpamAssassin and adds its
.B X-Spam-
headers to the message, and can optionally apply header and body rewriting to
messages flagged as spam, or reject them at the SMTP level.
messages flagged as spam, or reject such messages at the SMTP level.
A message flagged as spam is a message with a header
.BR "X-Spam-Flag: YES" .
.PP
@ -28,12 +29,12 @@ The mandatory
.I SOCKET
argument specifies the listening socket to open.
.I SOCKET
can be either a TCP socket in the form
.BR inet: "\fIPORT\fR" : "\fIHOST\fR"
can be either an IPv4/IPv6 TCP socket in the form
.BR inet: "\fIPORT\fR" @ "\fIHOST\fR"
or
.BR inet6: "\fIPORT\fR" : "\fIHOST\fR"
.BR inet6: "\fIPORT\fR" @ "\fIHOST\fR"
(for example,
.BR inet:3000:localhost ),
.BR inet:3000@localhost ),
or a UNIX domain socket in the form
.BR unix: "\fIPATH\fR"
(for example,
@ -63,8 +64,8 @@ and the MTA (Postfix).
.TP
.BR \-a ", " \-\-auth-untrusted
Treat authenticated senders as untrusted.
If this option is not used, authenticated senders are trusted and their messages
are not processed with SpamAssassin.
If this option is not used, authenticated senders are trusted, and their
messages are not processed with SpamAssassin.
.TP
.BR \-n ", " \-\-dry-run
Process messages as usual, but do not take action or apply any modifications.
@ -101,8 +102,9 @@ is replaced with the body received from SpamAssassin.
.TP
.BR \-H ", " \-\-preserve-headers
Suppress rewriting of headers
.B Subject
.B From
.BR Subject ,
.BR From ,
and
.B To
of spam messages.
If this option is not used, these headers of messages flagged as spam will be
@ -110,8 +112,8 @@ replaced with the values received from SpamAssassin, if necessary.
.TP
.BR \-r ", " \-\-reject-spam
Reject messages flagged as spam at the SMTP level.
Rejection results in a permanent SMTP error being returned to the client, and
the message is not delivered.
Rejection results in a permanent SMTP error reply being returned to the client,
and the message is not delivered.
.TP
.BR \-t ", " \-\-trusted-networks " \fINETS\fR"
Trust connections coming from the IP networks or addresses

View file

@ -2,7 +2,7 @@
Description=SpamAssassin Milter
[Service]
ExecStart=/usr/sbin/spamassassin-milter inet:3001:localhost
ExecStart=/usr/sbin/spamassassin-milter inet:3001@localhost
[Install]
WantedBy=multi-user.target

View file

@ -175,15 +175,13 @@ fn handle_body(mut ctx: Context<Connection>, bytes: &[u8]) -> milter::Result<Sta
client.send_body_chunk(bytes)?;
let max = config::get().max_message_size();
let status = if client.bytes_written() >= max {
Ok(if client.bytes_written() >= max {
let id = queue_id(&ctx.api)?;
verbose!("{}: skipping rest of message larger than {} bytes", id, max);
Status::Skip
} else {
Status::Continue
};
Ok(status)
})
}
#[on_eom(eom_callback)]

View file

@ -134,13 +134,19 @@ impl Client {
date_time: &str,
) -> Result<()> {
let protocol = if tls { "ESMTPS" } else { "ESMTP" };
let buf = format!(
"Received: from {} ({})\r\n\
\tby {} ({}) with {} id {};\r\n\
\t{}\r\n\
\t(envelope-from {})\r\n",
helo_host, client_name_addr, my_hostname, mta, protocol, queue_id, date_time, self.sender
helo_host,
client_name_addr,
my_hostname,
mta,
protocol,
queue_id,
date_time,
self.sender
);
let writer = self.process.writer();
@ -234,11 +240,7 @@ impl Client {
}
}
fn reject_spam(
id: &str,
actions: &impl SetErrorReply,
config: &Config,
) -> milter::Result<Status> {
fn reject_spam(id: &str, actions: &impl SetErrorReply, config: &Config) -> milter::Result<Status> {
Ok(if config.dry_run() {
verbose!(config, "{}: rejected message flagged as spam [dry-run, not done]", id);
Status::Accept
@ -436,7 +438,9 @@ mod tests {
#[test]
fn client_process_rewrite_spam() {
let spamc = MockSpamc::with_output(b"X-Spam-Flag: YES\r\nX-Spam-Level: *****\r\n\r\nReport".to_vec());
let spamc = MockSpamc::with_output(
b"X-Spam-Flag: YES\r\nX-Spam-Level: *****\r\n\r\nReport".to_vec(),
);
let actions = MockActionContext::new();
let config = Config::builder().build();

View file

@ -7,15 +7,15 @@ use std::{collections::HashSet, net::IpAddr};
/// [`Config`]: struct.Config.html
#[derive(Clone, Debug, Default)]
pub struct ConfigBuilder {
has_trusted_networks: bool,
trusted_networks: HashSet<IpNet>,
auth_untrusted: bool,
spamc_args: Vec<String>,
max_message_size: usize,
dry_run: bool,
reject_spam: bool,
preserve_headers: bool,
preserve_body: bool,
dry_run: bool,
has_trusted_networks: bool,
trusted_networks: HashSet<IpNet>,
spamc_args: Vec<String>,
max_message_size: usize,
verbose: bool,
}
@ -36,8 +36,13 @@ impl ConfigBuilder {
self
}
pub fn set_reject_spam(&mut self, value: bool) -> &mut Self {
self.reject_spam = value;
pub fn set_spamc_args(&mut self, value: Vec<String>) -> &mut Self {
self.spamc_args = value;
self
}
pub fn set_max_message_size(&mut self, value: usize) -> &mut Self {
self.max_message_size = value;
self
}
@ -46,6 +51,11 @@ impl ConfigBuilder {
self
}
pub fn set_reject_spam(&mut self, value: bool) -> &mut Self {
self.reject_spam = value;
self
}
pub fn set_preserve_headers(&mut self, value: bool) -> &mut Self {
self.preserve_headers = value;
self
@ -56,16 +66,6 @@ impl ConfigBuilder {
self
}
pub fn set_spamc_args(&mut self, value: Vec<String>) -> &mut Self {
self.spamc_args = value;
self
}
pub fn set_max_message_size(&mut self, value: usize) -> &mut Self {
self.max_message_size = value;
self
}
pub fn set_verbose(&mut self, value: bool) -> &mut Self {
self.verbose = value;
self
@ -73,15 +73,15 @@ impl ConfigBuilder {
pub fn build(self) -> Config {
Config {
has_trusted_networks: self.has_trusted_networks,
trusted_networks: self.trusted_networks,
auth_untrusted: self.auth_untrusted,
spamc_args: self.spamc_args,
max_message_size: self.max_message_size,
dry_run: self.dry_run,
reject_spam: self.reject_spam,
preserve_headers: self.preserve_headers,
preserve_body: self.preserve_body,
dry_run: self.dry_run,
has_trusted_networks: self.has_trusted_networks,
trusted_networks: self.trusted_networks,
spamc_args: self.spamc_args,
max_message_size: self.max_message_size,
verbose: self.verbose,
}
}
@ -90,15 +90,15 @@ impl ConfigBuilder {
/// A configuration object for SpamAssassin Milter.
#[derive(Clone, Debug, Default)]
pub struct Config {
has_trusted_networks: bool,
trusted_networks: HashSet<IpNet>,
auth_untrusted: bool,
spamc_args: Vec<String>,
max_message_size: usize,
dry_run: bool,
reject_spam: bool,
preserve_headers: bool,
preserve_body: bool,
dry_run: bool,
has_trusted_networks: bool,
trusted_networks: HashSet<IpNet>,
spamc_args: Vec<String>,
max_message_size: usize,
verbose: bool,
}
@ -113,26 +113,6 @@ impl Config {
}
}
pub fn auth_untrusted(&self) -> bool {
self.auth_untrusted
}
pub fn reject_spam(&self) -> bool {
self.reject_spam
}
pub fn preserve_body(&self) -> bool {
self.preserve_body
}
pub fn preserve_headers(&self) -> bool {
self.preserve_headers
}
pub fn dry_run(&self) -> bool {
self.dry_run
}
pub fn has_trusted_networks(&self) -> bool {
self.has_trusted_networks
}
@ -141,6 +121,10 @@ impl Config {
self.trusted_networks.iter().any(|n| n.contains(ip))
}
pub fn auth_untrusted(&self) -> bool {
self.auth_untrusted
}
pub fn spamc_args(&self) -> &[String] {
&self.spamc_args
}
@ -149,6 +133,22 @@ impl Config {
self.max_message_size
}
pub fn dry_run(&self) -> bool {
self.dry_run
}
pub fn reject_spam(&self) -> bool {
self.reject_spam
}
pub fn preserve_headers(&self) -> bool {
self.preserve_headers
}
pub fn preserve_body(&self) -> bool {
self.preserve_body
}
pub fn verbose(&self) -> bool {
self.verbose
}

View file

@ -1,6 +1,6 @@
//! The SpamAssassin Milter application library.
#![doc(html_root_url = "https://docs.rs/spamassassin-milter/0.0.1")]
#![doc(html_root_url = "https://docs.rs/spamassassin-milter/0.0.2")]
#![macro_use]
macro_rules! verbose {
@ -31,7 +31,7 @@ use milter::Milter;
pub const MILTER_NAME: &str = "SpamAssassin Milter";
/// The current version string of SpamAssassin Milter.
pub const VERSION: &str = "0.0.1";
pub const VERSION: &str = "0.0.2";
/// Starts SpamAssassin Milter at the given socket using the supplied
/// configuration.

View file

@ -2,29 +2,20 @@ use clap::{App, Arg, ArgMatches};
use spamassassin_milter::Config;
use std::{net::IpAddr, process};
const ARG_SOCKET: &str = "SOCKET";
const ARG_TRUSTED_NETWORKS: &str = "TRUSTED_NETWORKS";
const ARG_AUTH_UNTRUSTED: &str = "AUTH_UNTRUSTED";
const ARG_DRY_RUN: &str = "DRY_RUN";
const ARG_PRESERVE_HEADERS: &str = "PRESERVE_HEADERS";
const ARG_PRESERVE_BODY: &str = "PRESERVE_BODY";
const ARG_REJECT_SPAM: &str = "REJECT_SPAM";
const ARG_MAX_MESSAGE_SIZE: &str = "MAX_MESSAGE_SIZE";
const ARG_SPAMC_ARGS: &str = "SPAMC_ARGS";
const ARG_PRESERVE_BODY: &str = "PRESERVE_BODY";
const ARG_PRESERVE_HEADERS: &str = "PRESERVE_HEADERS";
const ARG_REJECT_SPAM: &str = "REJECT_SPAM";
const ARG_TRUSTED_NETWORKS: &str = "TRUSTED_NETWORKS";
const ARG_VERBOSE: &str = "VERBOSE";
const ARG_SOCKET: &str = "SOCKET";
const ARG_SPAMC_ARGS: &str = "SPAMC_ARGS";
fn main() {
let matches = App::new(spamassassin_milter::MILTER_NAME)
.version(spamassassin_milter::VERSION)
.arg(Arg::with_name(ARG_SOCKET)
.help("Listening socket of the milter")
.required(true))
.arg(Arg::with_name(ARG_TRUSTED_NETWORKS)
.short("t")
.long("trusted-networks")
.value_name("NETS")
.use_delimiter(true)
.help("Trust connections from these networks"))
.arg(Arg::with_name(ARG_AUTH_UNTRUSTED)
.short("a")
.long("auth-untrusted")
@ -33,27 +24,36 @@ fn main() {
.short("n")
.long("dry-run")
.help("Process messages without applying any changes"))
.arg(Arg::with_name(ARG_PRESERVE_HEADERS)
.short("H")
.long("preserve-headers")
.help("Suppress rewriting of Subject/From/To headers"))
.arg(Arg::with_name(ARG_PRESERVE_BODY)
.short("B")
.long("preserve-body")
.help("Suppress rewriting of message body"))
.arg(Arg::with_name(ARG_REJECT_SPAM)
.short("r")
.long("reject-spam")
.help("Reject messages flagged as spam"))
.arg(Arg::with_name(ARG_MAX_MESSAGE_SIZE)
.short("s")
.long("max-message-size")
.value_name("BYTES")
.help("Maximum message size to process"))
.arg(Arg::with_name(ARG_PRESERVE_BODY)
.short("B")
.long("preserve-body")
.help("Suppress rewriting of message body"))
.arg(Arg::with_name(ARG_PRESERVE_HEADERS)
.short("H")
.long("preserve-headers")
.help("Suppress rewriting of Subject/From/To headers"))
.arg(Arg::with_name(ARG_REJECT_SPAM)
.short("r")
.long("reject-spam")
.help("Reject messages flagged as spam"))
.arg(Arg::with_name(ARG_TRUSTED_NETWORKS)
.short("t")
.long("trusted-networks")
.value_name("NETS")
.use_delimiter(true)
.help("Trust connections from these networks"))
.arg(Arg::with_name(ARG_VERBOSE)
.short("v")
.long("verbose")
.help("Enable verbose operation logging"))
.arg(Arg::with_name(ARG_SOCKET)
.help("Listening socket of the milter")
.required(true))
.arg(Arg::with_name(ARG_SPAMC_ARGS)
.last(true)
.multiple(true)
@ -78,6 +78,17 @@ fn main() {
fn build_config(matches: &ArgMatches<'_>) -> Result<Config, String> {
let mut config = Config::builder();
if let Some(s) = matches.value_of(ARG_MAX_MESSAGE_SIZE) {
match s.parse() {
Ok(bytes) => {
config.set_max_message_size(bytes);
}
Err(_) => {
return Err(format!("invalid max message size \"{}\"", s));
}
}
}
if let Some(t) = matches.values_of(ARG_TRUSTED_NETWORKS) {
config.set_has_trusted_networks(true);
@ -93,31 +104,20 @@ fn build_config(matches: &ArgMatches<'_>) -> Result<Config, String> {
}
}
if let Some(s) = matches.value_of(ARG_MAX_MESSAGE_SIZE) {
match s.parse() {
Ok(bytes) => {
config.set_max_message_size(bytes);
}
Err(_) => {
return Err(format!("invalid max message size \"{}\"", s));
}
}
}
if matches.is_present(ARG_AUTH_UNTRUSTED) {
config.set_auth_untrusted(true);
}
if matches.is_present(ARG_REJECT_SPAM) {
config.set_reject_spam(true);
}
if matches.is_present(ARG_DRY_RUN) {
config.set_dry_run(true);
}
if matches.is_present(ARG_PRESERVE_BODY) {
config.set_preserve_body(true);
}
if matches.is_present(ARG_PRESERVE_HEADERS) {
config.set_preserve_headers(true);
}
if matches.is_present(ARG_PRESERVE_BODY) {
config.set_preserve_body(true);
if matches.is_present(ARG_REJECT_SPAM) {
config.set_reject_spam(true);
}
if matches.is_present(ARG_VERBOSE) {
config.set_verbose(true);