Warn when max message size misconfigured

This commit is contained in:
David Bürgin 2020-06-29 09:37:25 +02:00
parent 300a3b2d13
commit 2e1443578a
4 changed files with 57 additions and 4 deletions

View file

@ -4,6 +4,9 @@
* Add `--reply-code`, `--reply-status-code`, and `--reply-text` options to
allow customising the SMTP reply when rejecting spam.
* Log a warning and do not truncate message body when `--max-message-size` is
misconfigured (must be ≥ `spamc` max size as documented).
* Update dependencies in `Cargo.lock`.
## 0.1.2 (2020-06-07)

View file

@ -85,8 +85,8 @@ Maximum message size in bytes to pass to
.I BYTES
must be equal to or greater than the max size configured for
.BR spamc ,
else it is possible that the body of spam messages is truncated to the size
configured for
in order to ensure that SpamAssassin does not process messages truncated to the
size configured for
.BR spamc .
Defaults to the
.B spamc

View file

@ -185,7 +185,8 @@ fn handle_body(mut ctx: Context, bytes: &[u8]) -> milter::Result<Status> {
client.send_body_chunk(bytes)?;
let max = config::get().max_message_size();
Ok(if client.bytes_written() >= max {
Ok(if client.bytes_written() > max {
client.skip_body();
let id = queue_id(&ctx.api)?;
verbose!("{}: skipping rest of message larger than {} bytes", id, max);
Status::Skip

View file

@ -96,6 +96,7 @@ pub struct Client {
recipients: Vec<String>,
headers: HeaderMap,
bytes: usize,
skipped: bool,
}
impl Client {
@ -106,6 +107,7 @@ impl Client {
recipients: vec![],
headers: HeaderMap::new(),
bytes: 0,
skipped: false,
}
}
@ -117,6 +119,10 @@ impl Client {
self.bytes
}
pub fn skip_body(&mut self) {
self.skipped = true;
}
pub fn connect(&mut self) -> Result<()> {
self.process.connect()
}
@ -252,7 +258,7 @@ impl Client {
}
if !config.preserve_body() {
rewriter.rewrite_report_headers(id, actions)?;
email::replace_body(id, email.body, actions, config)?;
replace_body(id, email.body, self.skipped, actions, config)?;
}
}
@ -280,6 +286,29 @@ fn reject_spam(id: &str, actions: &impl SetErrorReply, config: &Config) -> milte
}
}
fn replace_body(
id: &str,
body: &[u8],
skipped: bool,
actions: &impl ActionContext,
config: &Config,
) -> milter::Result<()> {
// Do not replace the message body when part of it was skipped. This only
// occurs when the milters max message size is less than that of `spamc`.
// This condition ensures message integrity in such a misconfigured setup.
if skipped {
eprintln!(
"{}: not replacing possibly truncated message body; \
please review max message size setting ({})",
id,
config.max_message_size()
);
Ok(())
} else {
email::replace_body(id, body, actions, config)
}
}
#[cfg(test)]
mod tests {
use super::*;
@ -514,4 +543,24 @@ mod tests {
assert!(expected.contains(&a));
}
}
#[test]
fn client_process_skipped_body_not_replaced() {
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 = Default::default();
let mut client = Client::new(spamc, String::from("sender"));
client.skip_body();
let status = client.process("id", &actions, &config).unwrap();
assert_eq!(status, Status::Accept);
let called = actions.called.borrow();
assert!(called.contains(&Action::AddHeader("X-Spam-Level".into(), " *****".into())));
assert!(!called.contains(&Action::AppendBodyChunk(b"Report".to_vec())));
}
}