Bump version to 0.0.2
This commit is contained in:
parent
e6817c03e7
commit
4ed2133618
10 changed files with 130 additions and 126 deletions
10
Cargo.lock
generated
10
Cargo.lock
generated
|
@ -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",
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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
|
||||
```
|
||||
|
||||
```
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
86
src/main.rs
86
src/main.rs
|
@ -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);
|
||||
|
|
Loading…
Reference in a new issue