Bump MSRV to 1.61.0, replace panicking [e]println!
This commit is contained in:
parent
5cfebe9e87
commit
1823f6a178
13 changed files with 100 additions and 66 deletions
13
CHANGELOG.md
13
CHANGELOG.md
|
@ -1,5 +1,18 @@
|
||||||
# SpamAssassin Milter changelog
|
# SpamAssassin Milter changelog
|
||||||
|
|
||||||
|
## 0.4.0 (unreleased)
|
||||||
|
|
||||||
|
The minimum supported Rust version is now 1.61.0.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
* The minimum supported Rust version has been raised to 1.61.0.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
* The program no longer panics when it cannot write to standard error during
|
||||||
|
operation.
|
||||||
|
|
||||||
## 0.3.2 (2022-08-31)
|
## 0.3.2 (2022-08-31)
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
name = "spamassassin-milter"
|
name = "spamassassin-milter"
|
||||||
version = "0.3.2"
|
version = "0.3.2"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
rust-version = "1.56.1"
|
rust-version = "1.61.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"]
|
||||||
|
|
|
@ -50,7 +50,7 @@ server. By default, `/usr/bin/spamc` is used as the executable. To override
|
||||||
this, set the environment variable `SPAMASSASSIN_MILTER_SPAMC` to the desired
|
this, set the environment variable `SPAMASSASSIN_MILTER_SPAMC` to the desired
|
||||||
path when building the application.
|
path when building the application.
|
||||||
|
|
||||||
The minimum supported Rust version is 1.56.1.
|
The minimum supported Rust version is 1.61.0.
|
||||||
|
|
||||||
[Rust]: https://www.rust-lang.org
|
[Rust]: https://www.rust-lang.org
|
||||||
|
|
||||||
|
@ -261,7 +261,7 @@ messages with score 5.0 or above into ‘Junk’.
|
||||||
|
|
||||||
## Licence
|
## Licence
|
||||||
|
|
||||||
Copyright © 2020–2022 David Bürgin
|
Copyright © 2020–2023 David Bürgin
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify it under
|
This program is free software: you can redistribute it and/or modify it under
|
||||||
the terms of the GNU General Public License as published by the Free Software
|
the terms of the GNU General Public License as published by the Free Software
|
||||||
|
|
|
@ -38,7 +38,7 @@ use std::{
|
||||||
macro_rules! ok_or_tempfail {
|
macro_rules! ok_or_tempfail {
|
||||||
($expr:expr) => {
|
($expr:expr) => {
|
||||||
if let ::std::result::Result::Err(e) = $expr {
|
if let ::std::result::Result::Err(e) = $expr {
|
||||||
::std::eprintln!("failed to communicate with spamc: {}", e);
|
eprintln!("failed to communicate with spamc: {e}");
|
||||||
return ::indymilter::Status::Tempfail;
|
return ::indymilter::Status::Tempfail;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -149,7 +149,7 @@ async fn handle_connect(
|
||||||
|
|
||||||
if config.use_trusted_networks() {
|
if config.use_trusted_networks() {
|
||||||
if config.is_in_trusted_networks(&ip) {
|
if config.is_in_trusted_networks(&ip) {
|
||||||
verbose!(config, "accepted connection from trusted network address {}", ip);
|
verbose!(config, "accepted connection from trusted network address {ip}");
|
||||||
return Status::Accept;
|
return Status::Accept;
|
||||||
}
|
}
|
||||||
} else if ip.is_loopback() {
|
} else if ip.is_loopback() {
|
||||||
|
@ -179,7 +179,7 @@ async fn handle_mail(
|
||||||
) -> Status {
|
) -> Status {
|
||||||
if !config.auth_untrusted() {
|
if !config.auth_untrusted() {
|
||||||
if let Some(login) = context.macros.get_string(c_str!("{auth_authen}")) {
|
if let Some(login) = context.macros.get_string(c_str!("{auth_authen}")) {
|
||||||
verbose!(config, "accepted message from sender authenticated as \"{}\"", login);
|
verbose!(config, "accepted message from sender authenticated as \"{login}\"");
|
||||||
return Status::Accept;
|
return Status::Accept;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -212,7 +212,7 @@ async fn handle_data(context: &mut Context<Connection>) -> Status {
|
||||||
let id = context.macros.queue_id();
|
let id = context.macros.queue_id();
|
||||||
|
|
||||||
if let Err(e) = client.connect() {
|
if let Err(e) = client.connect() {
|
||||||
eprintln!("{}: failed to start spamc: {}", id, e);
|
eprintln!("{id}: failed to start spamc: {e}");
|
||||||
return Status::Tempfail;
|
return Status::Tempfail;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -274,7 +274,7 @@ async fn handle_body(
|
||||||
let max = config.max_message_size();
|
let max = config.max_message_size();
|
||||||
if client.bytes_written() > max {
|
if client.bytes_written() > max {
|
||||||
let id = context.macros.queue_id();
|
let id = context.macros.queue_id();
|
||||||
verbose!(config, "{}: skipping rest of message larger than {} bytes", id, max);
|
verbose!(config, "{id}: skipping rest of message larger than {max} bytes");
|
||||||
client.skip_body();
|
client.skip_body();
|
||||||
Status::Skip
|
Status::Skip
|
||||||
} else {
|
} else {
|
||||||
|
@ -291,7 +291,7 @@ async fn handle_eom(config: Arc<Config>, context: &mut EomContext<Connection>) -
|
||||||
match client.process(&id, &mut context.reply, &context.actions, &config).await {
|
match client.process(&id, &mut context.reply, &context.actions, &config).await {
|
||||||
Ok(status) => status,
|
Ok(status) => status,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
eprintln!("{}: failed to process message: {}", id, e);
|
eprintln!("{id}: failed to process message: {e}");
|
||||||
Status::Tempfail
|
Status::Tempfail
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -114,7 +114,7 @@ impl Process for Spamc {
|
||||||
"spamc terminated by signal {}",
|
"spamc terminated by signal {}",
|
||||||
status.signal().unwrap()
|
status.signal().unwrap()
|
||||||
)),
|
)),
|
||||||
Some(code) => Error::Io(format!("spamc exited with status code {}", code)),
|
Some(code) => Error::Io(format!("spamc exited with status code {code}")),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -236,7 +236,7 @@ impl Client {
|
||||||
// As requested during milter protocol negotiation, the value includes
|
// As requested during milter protocol negotiation, the value includes
|
||||||
// leading whitespace. This lets us pass on whitespace exactly as is.
|
// leading whitespace. This lets us pass on whitespace exactly as is.
|
||||||
let value = email::ensure_crlf(value);
|
let value = email::ensure_crlf(value);
|
||||||
let buf = format!("{}:{}\r\n", name, value);
|
let buf = format!("{name}:{value}\r\n");
|
||||||
|
|
||||||
if email::is_spam_assassin_header(name)
|
if email::is_spam_assassin_header(name)
|
||||||
|| email::REWRITE_HEADERS.contains(name)
|
|| email::REWRITE_HEADERS.contains(name)
|
||||||
|
@ -277,7 +277,7 @@ impl Client {
|
||||||
let output = match self.process.finish().await {
|
let output = match self.process.finish().await {
|
||||||
Ok(output) => output,
|
Ok(output) => output,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
eprintln!("{}: failed to complete spamc communication: {}", id, e);
|
eprintln!("{id}: failed to complete spamc communication: {e}");
|
||||||
return Ok(Status::Tempfail);
|
return Ok(Status::Tempfail);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -285,7 +285,7 @@ impl Client {
|
||||||
let email = match Email::parse(&output) {
|
let email = match Email::parse(&output) {
|
||||||
Ok(email) => email,
|
Ok(email) => email,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
eprintln!("{}: invalid response from spamc: {}", id, e);
|
eprintln!("{id}: invalid response from spamc: {e}");
|
||||||
return Ok(Status::Tempfail);
|
return Ok(Status::Tempfail);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -319,7 +319,7 @@ impl Client {
|
||||||
|
|
||||||
fn reject_spam(id: &str, reply: &mut impl SetErrorReply, config: &Config) -> Result<Status> {
|
fn reject_spam(id: &str, reply: &mut impl SetErrorReply, config: &Config) -> Result<Status> {
|
||||||
if config.dry_run() {
|
if config.dry_run() {
|
||||||
verbose!(config, "{}: rejected message flagged as spam [dry run, not done]", id);
|
verbose!(config, "{id}: rejected message flagged as spam [dry run, not done]");
|
||||||
Ok(Status::Accept)
|
Ok(Status::Accept)
|
||||||
} else {
|
} else {
|
||||||
reply.set_error_reply(
|
reply.set_error_reply(
|
||||||
|
@ -328,7 +328,7 @@ fn reject_spam(id: &str, reply: &mut impl SetErrorReply, config: &Config) -> Res
|
||||||
config.reply_text().lines(),
|
config.reply_text().lines(),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
verbose!(config, "{}: rejected message flagged as spam", id);
|
verbose!(config, "{id}: rejected message flagged as spam");
|
||||||
Ok(if config.reply_code().starts_with('5') {
|
Ok(if config.reply_code().starts_with('5') {
|
||||||
Status::Reject
|
Status::Reject
|
||||||
} else {
|
} else {
|
||||||
|
@ -349,9 +349,8 @@ async fn replace_body(
|
||||||
// This condition ensures message integrity in such a misconfigured setup.
|
// This condition ensures message integrity in such a misconfigured setup.
|
||||||
if skipped {
|
if skipped {
|
||||||
eprintln!(
|
eprintln!(
|
||||||
"{}: not replacing possibly truncated message body; \
|
"{id}: not replacing possibly truncated message body; \
|
||||||
please review max message size setting ({})",
|
please review max message size setting ({})",
|
||||||
id,
|
|
||||||
config.max_message_size()
|
config.max_message_size()
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
@ -589,10 +588,9 @@ mod tests {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
as_mock_spamc(client.process.as_ref()).buf,
|
as_mock_spamc(client.process.as_ref()).buf,
|
||||||
format!(
|
format!(
|
||||||
"X-Envelope-From: {}\r\n\
|
"X-Envelope-From: {sender}\r\n\
|
||||||
X-Envelope-To: {},\r\n\
|
X-Envelope-To: {recipient1},\r\n\
|
||||||
\t{}\r\n",
|
\t{recipient2}\r\n",
|
||||||
sender, recipient1, recipient2
|
|
||||||
)
|
)
|
||||||
.as_bytes()
|
.as_bytes()
|
||||||
);
|
);
|
||||||
|
|
14
src/email.rs
14
src/email.rs
|
@ -300,9 +300,9 @@ where
|
||||||
{
|
{
|
||||||
for m in mods {
|
for m in mods {
|
||||||
if config.dry_run() {
|
if config.dry_run() {
|
||||||
verbose!(config, "{}: rewriting header: {} [dry run, not done]", id, m);
|
verbose!(config, "{id}: rewriting header: {m} [dry run, not done]");
|
||||||
} else {
|
} else {
|
||||||
verbose!(config, "{}: rewriting header: {}", id, m);
|
verbose!(config, "{id}: rewriting header: {m}");
|
||||||
m.execute(actions).await?;
|
m.execute(actions).await?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -316,9 +316,9 @@ pub async fn replace_body(
|
||||||
config: &Config,
|
config: &Config,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
if config.dry_run() {
|
if config.dry_run() {
|
||||||
verbose!(config, "{}: replacing message body [dry run, not done]", id);
|
verbose!(config, "{id}: replacing message body [dry run, not done]");
|
||||||
} else {
|
} else {
|
||||||
verbose!(config, "{}: replacing message body", id);
|
verbose!(config, "{id}: replacing message body");
|
||||||
actions.replace_body(body).await?;
|
actions.replace_body(body).await?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -377,11 +377,11 @@ impl Display for HeaderMod<'_> {
|
||||||
use HeaderMod::*;
|
use HeaderMod::*;
|
||||||
|
|
||||||
match self {
|
match self {
|
||||||
Add { name, .. } => write!(f, "add header \"{}\"", name),
|
Add { name, .. } => write!(f, "add header \"{name}\""),
|
||||||
Replace { name, .. } | Modify { name, .. } => {
|
Replace { name, .. } | Modify { name, .. } => {
|
||||||
write!(f, "replace header \"{}\"", name)
|
write!(f, "replace header \"{name}\"")
|
||||||
}
|
}
|
||||||
Delete { name } => write!(f, "delete header \"{}\"", name),
|
Delete { name } => write!(f, "delete header \"{name}\""),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
34
src/lib.rs
34
src/lib.rs
|
@ -23,10 +23,23 @@
|
||||||
//!
|
//!
|
||||||
//! [SpamAssassin Milter]: https://crates.io/crates/spamassassin-milter
|
//! [SpamAssassin Milter]: https://crates.io/crates/spamassassin-milter
|
||||||
|
|
||||||
|
// The standard `eprintln` macro is replaced throughout with a best-effort,
|
||||||
|
// non-panicking version.
|
||||||
|
macro_rules! eprintln {
|
||||||
|
($($arg:tt)*) => {
|
||||||
|
{
|
||||||
|
use ::std::io::Write;
|
||||||
|
let _ = ::std::writeln!(::std::io::stderr(), $($arg)*);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
macro_rules! verbose {
|
macro_rules! verbose {
|
||||||
($config:ident, $($arg:tt)*) => {
|
($config:ident, $($arg:tt)*) => {
|
||||||
if $config.verbose() {
|
if $config.verbose() {
|
||||||
::std::eprintln!($($arg)*);
|
// Note: not qualifying `eprintln!` here makes it use textual scope
|
||||||
|
// and thus refer to above definition.
|
||||||
|
eprintln!($($arg)*);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -40,7 +53,10 @@ mod error;
|
||||||
|
|
||||||
pub use crate::config::{Config, ConfigBuilder};
|
pub use crate::config::{Config, ConfigBuilder};
|
||||||
use indymilter::IntoListener;
|
use indymilter::IntoListener;
|
||||||
use std::{future::Future, io};
|
use std::{
|
||||||
|
future::Future,
|
||||||
|
io::{self, stderr, Write},
|
||||||
|
};
|
||||||
|
|
||||||
/// The name of the SpamAssassin Milter application.
|
/// The name of the SpamAssassin Milter application.
|
||||||
pub const MILTER_NAME: &str = "SpamAssassin Milter";
|
pub const MILTER_NAME: &str = "SpamAssassin Milter";
|
||||||
|
@ -67,7 +83,7 @@ pub const VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||||
/// let shutdown = signal::ctrl_c();
|
/// let shutdown = signal::ctrl_c();
|
||||||
///
|
///
|
||||||
/// if let Err(e) = spamassassin_milter::run(listener, config, shutdown).await {
|
/// if let Err(e) = spamassassin_milter::run(listener, config, shutdown).await {
|
||||||
/// eprintln!("failed to run spamassassin-milter: {}", e);
|
/// eprintln!("failed to run spamassassin-milter: {e}");
|
||||||
/// process::exit(1);
|
/// process::exit(1);
|
||||||
/// }
|
/// }
|
||||||
/// # Ok(())
|
/// # Ok(())
|
||||||
|
@ -81,5 +97,15 @@ pub async fn run(
|
||||||
let callbacks = callbacks::make_callbacks(config);
|
let callbacks = callbacks::make_callbacks(config);
|
||||||
let config = Default::default();
|
let config = Default::default();
|
||||||
|
|
||||||
indymilter::run(listener, callbacks, config, shutdown).await
|
// Propagate any I/O error here: don’t start if stderr cannot be written to.
|
||||||
|
writeln!(stderr(), "{MILTER_NAME} {VERSION} starting")?;
|
||||||
|
|
||||||
|
let result = indymilter::run(listener, callbacks, config, shutdown).await;
|
||||||
|
|
||||||
|
match &result {
|
||||||
|
Ok(()) => eprintln!("{MILTER_NAME} {VERSION} shut down"),
|
||||||
|
Err(e) => eprintln!("{MILTER_NAME} {VERSION} terminated with error: {e}"),
|
||||||
|
}
|
||||||
|
|
||||||
|
result
|
||||||
}
|
}
|
||||||
|
|
55
src/main.rs
55
src/main.rs
|
@ -20,7 +20,14 @@ use signal_hook::consts::{SIGINT, SIGTERM};
|
||||||
use signal_hook_tokio::{Handle, Signals};
|
use signal_hook_tokio::{Handle, Signals};
|
||||||
use spamassassin_milter::{Config, MILTER_NAME, VERSION};
|
use spamassassin_milter::{Config, MILTER_NAME, VERSION};
|
||||||
use std::{
|
use std::{
|
||||||
env, error::Error, net::IpAddr, os::unix::fs::FileTypeExt, path::Path, process, str::FromStr,
|
env,
|
||||||
|
error::Error,
|
||||||
|
io::{stderr, stdout, Write},
|
||||||
|
net::IpAddr,
|
||||||
|
os::unix::fs::FileTypeExt,
|
||||||
|
path::Path,
|
||||||
|
process,
|
||||||
|
str::FromStr,
|
||||||
};
|
};
|
||||||
use tokio::{
|
use tokio::{
|
||||||
fs,
|
fs,
|
||||||
|
@ -29,12 +36,14 @@ use tokio::{
|
||||||
task::JoinHandle,
|
task::JoinHandle,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const PROGRAM_NAME: &str = env!("CARGO_BIN_NAME");
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
let (socket, config) = match parse_args() {
|
let (socket, config) = match parse_args() {
|
||||||
Ok(config) => config,
|
Ok(config) => config,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
eprintln!("error: {}", e);
|
let _ = writeln!(stderr(), "{PROGRAM_NAME}: {e}");
|
||||||
process::exit(1);
|
process::exit(1);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -52,7 +61,7 @@ async fn main() {
|
||||||
let listener = match TcpListener::bind(socket).await {
|
let listener = match TcpListener::bind(socket).await {
|
||||||
Ok(listener) => listener,
|
Ok(listener) => listener,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
eprintln!("error: could not bind TCP socket: {}", e);
|
let _ = writeln!(stderr(), "{PROGRAM_NAME}: could not bind TCP socket: {e}");
|
||||||
process::exit(1);
|
process::exit(1);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -68,7 +77,7 @@ async fn main() {
|
||||||
let listener = match UnixListener::bind(socket) {
|
let listener = match UnixListener::bind(socket) {
|
||||||
Ok(listener) => listener,
|
Ok(listener) => listener,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
eprintln!("error: could not create UNIX domain socket: {}", e);
|
let _ = writeln!(stderr(), "{PROGRAM_NAME}: could not create UNIX domain socket: {e}");
|
||||||
process::exit(1);
|
process::exit(1);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -81,20 +90,12 @@ async fn main() {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
eprintln!("{} {} starting", MILTER_NAME, VERSION);
|
|
||||||
|
|
||||||
let result = spamassassin_milter::run(listener, config, shutdown).await;
|
let result = spamassassin_milter::run(listener, config, shutdown).await;
|
||||||
|
|
||||||
cleanup(signals_handle, signals_task, socket_path).await;
|
cleanup(signals_handle, signals_task, socket_path).await;
|
||||||
|
|
||||||
match result {
|
if result.is_err() {
|
||||||
Ok(()) => {
|
process::exit(1);
|
||||||
eprintln!("{} {} shut down", MILTER_NAME, VERSION);
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
eprintln!("{} {} terminated with error: {}", MILTER_NAME, VERSION, e);
|
|
||||||
process::exit(1);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -112,14 +113,13 @@ impl FromStr for Socket {
|
||||||
} else if let Some(s) = s.strip_prefix("unix:") {
|
} else if let Some(s) = s.strip_prefix("unix:") {
|
||||||
Ok(Self::Unix(s.into()))
|
Ok(Self::Unix(s.into()))
|
||||||
} else {
|
} else {
|
||||||
Err(format!("invalid value for socket: \"{}\"", s))
|
Err(format!("invalid value for socket: \"{s}\""))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const USAGE_TEXT: &str = "\
|
const USAGE_TEXT: &str = "\
|
||||||
Usage:
|
[OPTIONS] <SOCKET> [--] [<SPAMC_ARGS>...]
|
||||||
spamassassin-milter [OPTIONS] <SOCKET> [--] [<SPAMC_ARGS>...]
|
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
<SOCKET> Listening socket of the milter
|
<SOCKET> Listening socket of the milter
|
||||||
|
@ -154,17 +154,15 @@ fn parse_args() -> Result<(Socket, Config), Box<dyn Error>> {
|
||||||
let socket = loop {
|
let socket = loop {
|
||||||
let arg = args.next().ok_or("required argument <SOCKET> missing")??;
|
let arg = args.next().ok_or("required argument <SOCKET> missing")??;
|
||||||
|
|
||||||
let missing_value = || format!("missing value for option {}", arg);
|
let missing_value = || format!("missing value for option {arg}");
|
||||||
|
|
||||||
match arg.as_str() {
|
match arg.as_str() {
|
||||||
"-h" | "--help" => {
|
"-h" | "--help" => {
|
||||||
println!("{} {}", MILTER_NAME, VERSION);
|
write!(stdout(), "Usage: {PROGRAM_NAME} {USAGE_TEXT}")?;
|
||||||
println!();
|
|
||||||
print!("{}", USAGE_TEXT);
|
|
||||||
process::exit(0);
|
process::exit(0);
|
||||||
}
|
}
|
||||||
"-V" | "--version" => {
|
"-V" | "--version" => {
|
||||||
println!("{} {}", MILTER_NAME, VERSION);
|
writeln!(stdout(), "{MILTER_NAME} {VERSION}")?;
|
||||||
process::exit(0);
|
process::exit(0);
|
||||||
}
|
}
|
||||||
"-a" | "--auth-untrusted" => {
|
"-a" | "--auth-untrusted" => {
|
||||||
|
@ -188,7 +186,7 @@ fn parse_args() -> Result<(Socket, Config), Box<dyn Error>> {
|
||||||
"-s" | "--max-message-size" => {
|
"-s" | "--max-message-size" => {
|
||||||
let arg = args.next().ok_or_else(missing_value)??;
|
let arg = args.next().ok_or_else(missing_value)??;
|
||||||
let bytes = arg.parse()
|
let bytes = arg.parse()
|
||||||
.map_err(|_| format!("invalid value for max message size: \"{}\"", arg))?;
|
.map_err(|_| format!("invalid value for max message size: \"{arg}\""))?;
|
||||||
|
|
||||||
config = config.max_message_size(bytes);
|
config = config.max_message_size(bytes);
|
||||||
}
|
}
|
||||||
|
@ -217,7 +215,7 @@ fn parse_args() -> Result<(Socket, Config), Box<dyn Error>> {
|
||||||
// supported.
|
// supported.
|
||||||
let net = net.parse()
|
let net = net.parse()
|
||||||
.or_else(|_| net.parse::<IpAddr>().map(From::from))
|
.or_else(|_| net.parse::<IpAddr>().map(From::from))
|
||||||
.map_err(|_| format!("invalid value for trusted network address: \"{}\"", net))?;
|
.map_err(|_| format!("invalid value for trusted network address: \"{net}\""))?;
|
||||||
|
|
||||||
config = config.trusted_network(net);
|
config = config.trusted_network(net);
|
||||||
}
|
}
|
||||||
|
@ -229,7 +227,7 @@ fn parse_args() -> Result<(Socket, Config), Box<dyn Error>> {
|
||||||
if arg.starts_with('-')
|
if arg.starts_with('-')
|
||||||
&& arg.chars().all(|c| c.is_ascii_alphanumeric() || c == '-')
|
&& arg.chars().all(|c| c.is_ascii_alphanumeric() || c == '-')
|
||||||
{
|
{
|
||||||
return Err(format!("unrecognized option: \"{}\"", arg).into());
|
return Err(format!("unrecognized option: \"{arg}\"").into());
|
||||||
}
|
}
|
||||||
|
|
||||||
break arg.parse()?;
|
break arg.parse()?;
|
||||||
|
@ -270,16 +268,15 @@ fn validate_reply_codes(
|
||||||
if !((c1.starts_with('4') || c1.starts_with('5')) && c2.starts_with(&c1[..1])) =>
|
if !((c1.starts_with('4') || c1.starts_with('5')) && c2.starts_with(&c1[..1])) =>
|
||||||
{
|
{
|
||||||
Err(format!(
|
Err(format!(
|
||||||
"invalid or incompatible values for reply code and status code: \"{}\", \"{}\"",
|
"invalid or incompatible values for reply code and status code: \"{c1}\", \"{c2}\""
|
||||||
c1, c2
|
|
||||||
)
|
)
|
||||||
.into())
|
.into())
|
||||||
}
|
}
|
||||||
(Some(c), None) if !c.starts_with('5') => {
|
(Some(c), None) if !c.starts_with('5') => {
|
||||||
Err(format!("invalid value for reply code (5XX): \"{}\"", c).into())
|
Err(format!("invalid value for reply code (5XX): \"{c}\"").into())
|
||||||
}
|
}
|
||||||
(None, Some(c)) if !c.starts_with('5') => {
|
(None, Some(c)) if !c.starts_with('5') => {
|
||||||
Err(format!("invalid value for reply status code (5.X.X): \"{}\"", c).into())
|
Err(format!("invalid value for reply status code (5.X.X): \"{c}\"").into())
|
||||||
}
|
}
|
||||||
_ => Ok(()),
|
_ => Ok(()),
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ use spamassassin_milter::*;
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn ham_message() {
|
async fn ham_message() {
|
||||||
let config = configure_spamc(Config::builder())
|
let config = configure_spamc(Config::builder())
|
||||||
.spamc_args(&[format!("--port={}", SPAMD_PORT)])
|
.spamc_args([format!("--port={SPAMD_PORT}")])
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
let server = spawn_mock_spamd_server(SPAMD_PORT, |ham| {
|
let server = spawn_mock_spamd_server(SPAMD_PORT, |ham| {
|
||||||
|
|
|
@ -9,7 +9,7 @@ async fn reject_spam() {
|
||||||
.reject_spam(true)
|
.reject_spam(true)
|
||||||
.reply_code("554".into())
|
.reply_code("554".into())
|
||||||
.reply_text("Not allowed!".into())
|
.reply_text("Not allowed!".into())
|
||||||
.spamc_args(&[format!("--port={}", SPAMD_PORT)])
|
.spamc_args([format!("--port={SPAMD_PORT}")])
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
let server = spawn_mock_spamd_server(SPAMD_PORT, |spam| {
|
let server = spawn_mock_spamd_server(SPAMD_PORT, |spam| {
|
||||||
|
|
|
@ -7,7 +7,7 @@ use spamassassin_milter::*;
|
||||||
async fn skip_oversized() {
|
async fn skip_oversized() {
|
||||||
let config = configure_spamc(Config::builder())
|
let config = configure_spamc(Config::builder())
|
||||||
.max_message_size(512)
|
.max_message_size(512)
|
||||||
.spamc_args(&[format!("--port={}", SPAMD_PORT)])
|
.spamc_args([format!("--port={SPAMD_PORT}")])
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
let server = spawn_mock_spamd_server(SPAMD_PORT, Ok).await.unwrap();
|
let server = spawn_mock_spamd_server(SPAMD_PORT, Ok).await.unwrap();
|
||||||
|
|
|
@ -6,7 +6,7 @@ use spamassassin_milter::*;
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn spam_message() {
|
async fn spam_message() {
|
||||||
let config = configure_spamc(Config::builder())
|
let config = configure_spamc(Config::builder())
|
||||||
.spamc_args(&[format!("--port={}", SPAMD_PORT)])
|
.spamc_args([format!("--port={SPAMD_PORT}")])
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
let server = spawn_mock_spamd_server(SPAMD_PORT, |spam| {
|
let server = spawn_mock_spamd_server(SPAMD_PORT, |spam| {
|
||||||
|
|
|
@ -6,7 +6,7 @@ use spamassassin_milter::*;
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn spamc_connection_error() {
|
async fn spamc_connection_error() {
|
||||||
let config = configure_spamc(Config::builder())
|
let config = configure_spamc(Config::builder())
|
||||||
.spamc_args(&[format!("--port={}", SPAMD_PORT)])
|
.spamc_args([format!("--port={SPAMD_PORT}")])
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
let milter = SpamAssassinMilter::spawn(LOCALHOST, config).await.unwrap();
|
let milter = SpamAssassinMilter::spawn(LOCALHOST, config).await.unwrap();
|
||||||
|
|
Loading…
Reference in a new issue