Skip to content

tests: normalizations#3747

Merged
georglauterbach merged 27 commits intomasterfrom
tests/normalizations
Jan 11, 2024
Merged

tests: normalizations#3747
georglauterbach merged 27 commits intomasterfrom
tests/normalizations

Conversation

@georglauterbach
Copy link
Copy Markdown
Member

@georglauterbach georglauterbach commented Jan 4, 2024

Description

See #3739 for what is fixed with this PR. Review commit by commit - I really put more effort into single commits this time, I promise @polarathene 😆 ❤️

Fixes #3739
Fixes #3733

Type of change

  • Improvement (non-breaking change that does improve existing functionality)

Checklist:

  • My code follows the style guidelines of this project
  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation (README.md or the documentation under docs/)
  • If necessary I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes
  • I have added information about changes made in this PR to CHANGELOG.md

@georglauterbach georglauterbach added area/tests kind/improvement Improve an existing feature, configuration file or the documentation meta/feature freeze On hold due to upcoming release process kind/update Update an existing feature, configuration file or the documentation labels Jan 4, 2024
@georglauterbach georglauterbach added this to the v14.0.0 milestone Jan 4, 2024
@georglauterbach georglauterbach self-assigned this Jan 4, 2024
@georglauterbach
Copy link
Copy Markdown
Member Author

georglauterbach commented Jan 4, 2024

@polarathene I'd love if you could assist me with the test failure in hostname.bats - I really don't know what is going on there, especially with SRS. The issue only appears with 2125e41.

@georglauterbach georglauterbach removed the meta/feature freeze On hold due to upcoming release process label Jan 4, 2024
I added `_send_email_unchecked` which performs no check so we can still
use the helper for situations where we check for failure, and
`_send_email` is a wrapper for `_send_mail_unchecked` with an additional
`assert_success`.

I also went through the tests and adjusted them where necessary.
The comments in `_send_email_and_get_id` are pretty self-explanatory.
The whole thing has become more robust while it is now capable of
capturing more log lines.
@georglauterbach georglauterbach force-pushed the tests/normalizations branch 3 times, most recently from 066086c to 783e8df Compare January 4, 2024 21:52
With `--data`, we do not need to handle STDIN. We do need additional
quoting though, because we expand the array later as a simple string in
`_run_in_container_bash`.

Moreover, the `@` was added because, according to the documentation of
`swaks`, it should be prepended when using file names...
because LOGIN is deprecated (according to swaks documentation).
These e-mails are mostly trivial in `swaks`, and we do not need extra
files here.
@polarathene
Copy link
Copy Markdown
Member

polarathene commented Jan 5, 2024

I really put more effort into single commits this time, I promise @polarathene 😆 ❤️

❤️ !

I really appreciate that and it'll make it much easier to review.

At a glance, I see you've already addressed a bunch of TODO notes I've had written up but haven't published yet as a comment/issue 🤣


@polarathene I'd love if you could assist me with the test failure in hostname.bats

For future reader reference (since these failure logs get deleted by Github eventually):

Test failure
not ok 3 [Network - Hostname] should update configuration correctly (ENV OVERRIDE_HOSTNAME) in 6301ms
# (from function `assert_line' in file test/test_helper/bats-assert/src/assert_line.bash, line 193,
#  from function `_should_have_correct_mail_headers' in file test/tests/parallel/set3/container_configuration/hostname.bats, line 238,
#  in test file test/tests/parallel/set3/container_configuration/hostname.bats, line 83)
#   `_should_have_correct_mail_headers 'mail.override.test' 'override.test' 'original.example.test'' failed
# [   INF   ]  mail.override.test is up and running
#
# -- line does not contain substring --
# index     : 14
# substring : @mail.override.test>
# line      : Message-Id: <[email protected]>
# --
#
# dms-test_hostname_env-override-hostname

@test "should update configuration correctly (ENV OVERRIDE_HOSTNAME)" {
export CONTAINER_NAME="${CONTAINER3_NAME}"
local CUSTOM_SETUP_ARGUMENTS=(
--hostname 'original.example.test'
--env OVERRIDE_HOSTNAME='mail.override.test'
--env ENABLE_AMAVIS=1
--env ENABLE_SRS=1
--env PERMIT_DOCKER='container'
--ulimit "nofile=$(ulimit -Sn):$(ulimit -Hn)"
)
_init_with_defaults
_common_container_setup 'CUSTOM_SETUP_ARGUMENTS'
_wait_for_smtp_port_in_container
# Should be the original `--hostname` (`hostname -f`), not `OVERRIDE_HOSTNAME`:
_should_have_expected_hostname 'original.example.test'
_should_be_configured_to_domainname 'override.test'
_should_be_configured_to_fqdn 'mail.override.test'
_should_have_correct_mail_headers 'mail.override.test' 'override.test' 'original.example.test'
# Container hostname should not be found in received mail (due to `OVERRIDE_HOSTNAME`):
_run_in_container grep -R 'original.example.test' /var/mail/localhost.localdomain/user1/new/
assert_failure
}

Fails at _should_have_correct_mail_headers :

_should_have_correct_mail_headers 'mail.override.test' 'override.test' 'original.example.test'
# Container hostname should not be found in received mail (due to `OVERRIDE_HOSTNAME`):
_run_in_container grep -R 'original.example.test' /var/mail/localhost.localdomain/user1/new/
assert_failure

function _should_have_correct_mail_headers() {
local EXPECTED_FQDN=${1}
# NOTE: The next two params should not differ for bare domains:
local EXPECTED_DOMAINPART=${2:-${EXPECTED_FQDN}}
# Required when EXPECTED_FQDN would not match the container hostname:
# (eg: OVERRIDE_HOSTNAME or `--hostname mail --domainname example.test`)
local EXPECTED_HOSTNAME=${3:-${EXPECTED_FQDN}}
_send_email --from '[email protected]' --data 'existing/user1'
_wait_for_empty_mail_queue_in_container

Fails on the last assert at line 238:

# Mail headers should contain EXPECTED_FQDN for lines Received + by + Message-Id
# For `ENABLE_SRS=1`, EXPECTED_DOMAINPART should match lines Return-Path + envelope-from
_run_in_container cat "${MAIL_FILEPATH}"
assert_success
assert_line --index 0 --partial 'Return-Path: <SRS0='
assert_line --index 0 --partial "@${EXPECTED_DOMAINPART}>"
# Passed on from Postfix to Dovecot via LMTP:
assert_line --index 2 --partial "Received: from ${EXPECTED_FQDN}"
assert_line --index 3 --partial "by ${EXPECTED_FQDN} with LMTP"
assert_line --index 5 --partial '(envelope-from <SRS0='
assert_line --index 5 --partial "@${EXPECTED_DOMAINPART}>"
# Arrived via Postfix:
# NOTE: The first `localhost` in this line would actually be `mail.external.tld`,
# but Amavis is changing that. It also changes protocol from SMTP to ESMTP.
assert_line --index 7 --partial 'Received: from localhost (localhost [127.0.0.1])'
assert_line --index 8 --partial "by ${EXPECTED_FQDN} (Postfix) with ESMTP id"
assert_line --index 14 --partial 'Message-Id:'
assert_line --index 14 --partial "@${EXPECTED_FQDN}>"
# Mail contents example:
#
# Return-Path: <[email protected]>
# Delivered-To: [email protected]
# Received: from mail.example.test
# by mail.example.test with LMTP
# id jvJfJk23zGPeBgAAUi6ngw
# (envelope-from <[email protected]>)
# for <[email protected]>; Sun, 22 Jan 2023 04:10:53 +0000
# Received: from localhost (localhost [127.0.0.1])
# by mail.example.test (Postfix) with ESMTP id 8CFC4C30F9C4
# for <[email protected]>; Sun, 22 Jan 2023 04:10:53 +0000 (UTC)
# From: Docker Mail Server <[email protected]>
# To: Existing Local User <[email protected]>
# Date: Sat, 22 May 2010 07:43:25 -0400
# Subject: Test Message existing-user1.txt
# Message-Id: <[email protected]>
#
# This is a test mail.

Failure found @original.example.test instead of @mail.override.test:

# -- line does not contain substring --
# index     : 14
# substring : @mail.override.test>
# line      : Message-Id: <[email protected]>

Commit that apparently (prior commits lack CI test runs) introduced test failure only removed the --data option which provided DATA input of:

From: Docker Mail Server <[email protected]>
To: Existing Local User <[email protected]>
Date: Sat, 22 May 2010 07:43:25 -0400
Subject: Test Message existing-user1.txt
This is a test mail.

Current state of the _send_email() call:

function _send_email_unchecked() {
  [[ -v CONTAINER_NAME ]] || return 1

  # Parameter defaults common to our testing needs:
  local EHLO='mail.external.tld'
  local FROM='[email protected]'
  local TO='[email protected]'
  local SERVER='0.0.0.0'
  local PORT=25
  # Extra options for `swaks` that aren't covered by the default options above:
  local ADDITIONAL_SWAKS_OPTIONS=()

  while [[ ${#} -gt 0 ]]; do
    case "${1}" in
      ( '--ehlo' )   EHLO=${2:?--ehlo given but no argument}     ; shift 2 ;;
      ( '--from' )   FROM=${2:?--from given but no argument}     ; shift 2 ;;
      ( '--to' )     TO=${2:?--to given but no argument}         ; shift 2 ;;
      ( '--server' ) SERVER=${2:?--server given but no argument} ; shift 2 ;;
      ( '--port' )   PORT=${2:?--port given but no argument}     ; shift 2 ;;
      ( '--data' )
        ADDITIONAL_SWAKS_OPTIONS+=('--data')
        ADDITIONAL_SWAKS_OPTIONS+=("@/tmp/docker-mailserver-test/emails/${2:?--data given but no argument provided}")
        shift 2
        ;;
      ( * ) ADDITIONAL_SWAKS_OPTIONS+=("'${1}'") ; shift 1 ;;
    esac
  done

  _run_in_container_bash "swaks --server '${SERVER}' --port '${PORT}' --ehlo '${EHLO}' --from '${FROM}' --to '${TO}' ${ADDITIONAL_SWAKS_OPTIONS[*]}"
}

Resolved (See follow-up comment)

At a glance, by dropping the --data, we're letting swaks provide it's own defaults. Interestingly our BATS asserts for expected line order is still aligned, even though swaks would have some additional headers added.

We can see that the expected Message-ID header is being matched, just the FQDN is wrong (it's the --hostname assigned, not our ENV override that we expect):

# Got:
Message-Id: <[email protected]>

# Expected:
Message-Id: <[email protected]>
  • The swaks docs do have several options that will implicitly fallback to the hostname when necessary.
  • Something changed with the data we originally provided to --data with our fallback values that swaks sent a mail that was assigned a Message-Id header (by Postfix replacing any that swaks provided?):
    • Previous DATA:

      From: Docker Mail Server <[email protected]>
      To: Existing Local User <[email protected]>
      Date: Sat, 22 May 2010 07:43:25 -0400
      Subject: Test Message existing-user1.txt
      This is a test mail.
      
    • Our helper method defaults:

      # Parameter defaults common to our testing needs:
      local EHLO='mail.external.tld'
      local FROM='[email protected]'
      local TO='[email protected]'
      local SERVER='0.0.0.0'
      local PORT=25
      
      # ...
      
      _run_in_container_bash "swaks --server '${SERVER}' --port '${PORT}' --ehlo '${EHLO}' --from '${FROM}' --to '${TO}' ${ADDITIONAL_SWAKS_OPTIONS[*]}"
      

Comparing the two:

  • --from and `--to should be roughly equivalent. These enveloper headers implicitly provide those for the equivalent message sender/recipient headers.
  • I can't imagine the Date/Subject or body mattering here. The --ehlo shouldn't have changed, although it's not resolvable. It's possible that swaks also attempts to resolve the --from / --to FQDNs when they're implicitly inferred from envelope? 🤷‍♂️
  • --server hasn't changed, but since it's for any interface it may affect the transaction in some way 🤔

We also know that all earlier assertions for the ENV override of mail.example.test passed. It's just the value that was used for the Message-Id that's become original.example.test. Since Postfix should be configured for mail.example.test, this information must be sourced from swaks?

While the docs don't state it, part of the implicit defaults for the DATA template may actually add such a header in, which wouldn't be there normally when we provided our own DATA 🤔

@polarathene
Copy link
Copy Markdown
Member

polarathene commented Jan 5, 2024

TL;DR: Issue was due to swaks inserting a message-id header.

  • Refer to the upstream docs on swaks github which actually document this as part of the --data default template. This resource is severely outdated as the change / implementation was documented in 2015.
  • Resolutions:
    • Set a custom message-id header via --header / --h-Message-Id, stick to convention of <a@b>.
    • Provide a custom --data template instead without this header, Postfix will generate it's own with a queue identifier returned to the swaks output.

Resolved

We also know that all earlier assertions for the ENV override of mail.example.test passed. It's just the value that was used for the Message-Id that's become original.example.test. Since Postfix should be configured for mail.example.test, this information must be sourced from swaks?

Just spun up a container and ran swaks to confirm (this program is so nice 🤩 )

# Pretend mail is from `external.test` MTA, to the local DMS MTA:
$ swaks --server 0.0.0.0 --port 465 \
  --ehlo external.test \
  --to [email protected] \
  --from [email protected] \
  --auth-user [email protected] --auth-password bad-password

=== Trying 0.0.0.0:465...
=== Connected to 0.0.0.0.
<-  220 mail.example.test ESMTP
 -> EHLO external.test
<-  250-mail.example.test
<-  250-PIPELINING
<-  250-SIZE 10240000
<-  250-ETRN
<-  250-AUTH PLAIN LOGIN
<-  250-AUTH=PLAIN LOGIN
<-  250-ENHANCEDSTATUSCODES
<-  250-8BITMIME
<-  250-DSN
<-  250 CHUNKING
 -> AUTH LOGIN
<-  334 VXNlcm5hbWU6
 -> aGVsbG9AZXhhbXBsZS50ZXN0
<-  334 UGFzc3dvcmQ6
 -> YmFkLXBhc3N3b3Jk
<-  235 2.7.0 Authentication successful
 -> MAIL FROM:<[email protected]>
<-  250 2.1.0 Ok
 -> RCPT TO:<[email protected]>
<-  250 2.1.5 Ok
 -> DATA
<-  354 End data with <CR><LF>.<CR><LF>
 -> Date: Fri, 05 Jan 2024 02:50:43 +0000
 -> To: [email protected]
 -> From: [email protected]
 -> Subject: test Fri, 05 Jan 2024 02:50:43 +0000
 -> Message-Id: <[email protected]>
 -> X-Mailer: swaks v20201014.0 jetmore.org/john/code/swaks/
 ->
 -> This is a test mailing
 ->
 ->
 -> .
<-  250 2.0.0 Ok: queued as 40D52131DB
 -> QUIT
<-  221 2.0.0 Bye
=== Connection closed with remote host.

Additional Message-Id header is the culprit

=== Trying 0.0.0.0:465...
=== Connected to 0.0.0.0.
<-  220 mail.example.test ESMTP
 -> EHLO external.test
<-  250-mail.example.test

Looking good! 😎

Here's the default --data generated by swaks:

 -> Date: Fri, 05 Jan 2024 02:50:43 +0000
 -> To: [email protected]
 -> From: [email protected]
 -> Subject: test Fri, 05 Jan 2024 02:50:43 +0000
 -> Message-Id: <[email protected]>
 -> X-Mailer: swaks v20201014.0 jetmore.org/john/code/swaks/
 ->
 -> This is a test mailing

Everything looking good there too, except for the Message-Id which is exactly like expected 🤔

swaks doesn't document this behaviour. It's implicitly inferring the FQDN for that via a hostname lookup.

  • A good example of software that relies on such if we were to ever move off --hostname to ENV only like OVERRIDE_HOSTNAME equivalent that's been discussed.
  • This is why I've been reluctant to such a change until we have proper DNS + matrix testing that stands a better chance of catching this. On k8s this'd be something like a container id presumably.

Outdated docs source

While the swaks docs I linked don't mention Message-Id in the defaults, it was introduced in upstream source:


Solution

  • --header 'Message-Id: whatever' / --h-Message-Id whatever can replace this header (NOTE: --add-header will just append, not replace the existing one).
  • Removing the header also works, as was the case before when we set DATA via a file input to --data. Postfix generates an equivalent Message-Id header on it's end.

Technically the header value should not be whatever, there's a format expected <a@b> I think? Postfix does not mind, but Dovecot does (bug report was Feb 2020, still affects DMS v13.2, the bug ID doesn't appear to have a public tracker 🤷‍♂️ ):

Examples (logs with message id header (postfix / amavis / dovecot sieve)
# With: --header 'Message-Id: whatever
# - Postfix message-id matches value
# - Amavis Message-ID wraps in angled brackets
# - Dovecot Sieve msgid fails to parse, value is left-as: unspecified

Jan  5 03:30:11 original postfix/sender-cleanup/cleanup[4733]: 86E90132C3: message-id=whatever
Jan  5 03:30:11 original postfix/cleanup[4737]: A8531132CE: message-id=whatever
Jan  5 03:30:11 original amavis[771]: (00771-06) Passed CLEAN {RelayedInbound}, [127.0.0.1]:52924 <[email protected]> -> <[email protected]>, Queue-ID: 86E90132C3, Message-ID: <whatever>, mail_id: SGMXEJqwjSeG, Hits: -, size: 192, queued_as: A8531132CE, 77 ms
Jan  5 03:30:11 original dovecot: lmtp([email protected])<4739><w4DpKcN3l2WDEgAAmKHxAA>: sieve: msgid=unspecified: stored mail into mailbox 'INBOX'
# With --header 'Message-Id: <[email protected]>
# Amavis does not add brackets for Dovecot Sieve
# Header value just needs to follow the convention of: <a@b>

Jan  5 03:22:24 original postfix/sender-cleanup/cleanup[4022]: CFB49132CD: message-id=<[email protected]>
Jan  5 03:22:24 original postfix/cleanup[4026]: EFFBC132CE: message-id=<[email protected]>
Jan  5 03:22:24 original amavis[770]: (00770-06) Passed CLEAN {RelayedInbound}, [127.0.0.1]:53754 <[email protected]> -> <[email protected]>, Queue-ID: CFB49132CD, Message-ID: <[email protected]>, mail_id: Hr-qo5lwfjPY, Hits: -, size: 212, queued_as: EFFBC132CE, 73 ms
Jan  5 03:22:25 original dovecot: lmtp([email protected])<4028><JuhOO/B1l2W8DwAAmKHxAA>: sieve: msgid=<[email protected]>: stored mail into mailbox 'INBOX'

Output when we provided --data, with a little extra logs for added context of the flow (regarding 202E9131D6 + 3C800131E4 identifiers):

Jan  5 04:00:29 original postfix/submissions/smtpd[7160]: connect from localhost[127.0.0.1]
Jan  5 04:00:29 original opendmarc[610]: ignoring connection from localhost
Jan  5 04:00:29 original postfix/submissions/smtpd[7160]: 202E9131D6: client=localhost[127.0.0.1], sasl_method=LOGIN, [email protected]

Jan  5 04:00:29 original postfix/sender-cleanup/cleanup[7164]: 202E9131D6: message-id=<[email protected]>

Jan  5 04:00:29 original postfix/smtpd-amavis/smtpd[7167]: 3C800131E4: client=localhost[127.0.0.1]
Jan  5 04:00:29 original postfix/cleanup[7171]: 3C800131E4: message-id=<[email protected]>

Jan  5 04:00:29 original amavis[7348]: (07348-02) Passed CLEAN {RelayedInbound}, [127.0.0.1]:56346 <[email protected]> -> <[email protected]>, Queue-ID: 202E9131D6, Message-ID: <[email protected]>, mail_id: XEdTsF42rGU5, Hits: -, size: 281, queued_as: 3C800131E4, 68 ms
Jan  5 04:00:29 original postfix/smtp-amavis/smtp[7165]: 202E9131D6: to=<[email protected]>, relay=127.0.0.1[127.0.0.1]:10024, delay=0.12, delays=0.05/0/0/0.07, dsn=2.0.0, status=sent (250 2.0.0 from MTA(smtp:[127.0.0.1]:10025): 250 2.0.0 Ok: queued as 3C800131E4)

Jan  5 04:00:29 original postfix/qmgr[726]: 202E9131D6: removed
Jan  5 04:00:29 original dovecot: lmtp([email protected])<7397><qKoCD91+l2XlHAAAmKHxAA>: sieve: msgid=<[email protected]>: stored mail into mailbox 'INBOX'
Jan  5 04:00:29 original postfix/lmtp[7396]: 3C800131E4: to=<[email protected]>, relay=mail.example.test[/var/run/dovecot/lmtp], delay=0.01, delays=0/0/0/0.01, dsn=2.0.0, status=sent (250 2.0.0 <[email protected]> qKoCD91+l2XlHAAAmKHxAA Saved)
Jan  5 04:00:29 original dovecot: lmtp(7397): Disconnect from local: Logged out (state=READY)
Jan  5 04:00:29 original postfix/qmgr[726]: 3C800131E4: removed

The last example is Postfix generating the header since swaks doesn't provide one, it's similar but we have a date + identifier from postfix. That same identifier is returned in the output from the swaks mail sent:

<-  250 2.0.0 Ok: queued as 202E9131D6
 -> QUIT
<-  221 2.0.0 Bye

It does not appear that we can opt-out of the default header specifically. We can either override it like shown above, or provide our own --data template (and use any of the tokens).

# `--data` template without message-id or x-mailer headers:
# Postfix will generate the message header, which includes a queue identifier returned in swaks output for tracking the mail in logs

--data 'Date: %DATE%\nTo: %TO_ADDRESS%\nFrom: %FROM_ADDRESS%\nSubject: test %DATE%\n%NEW_HEADERS%\n%BODY%\n'

@georglauterbach
Copy link
Copy Markdown
Member Author

Thanks for your help @polarathene, tests should now pass. I had to fix _send_email_unchecked for that, but it's all nicely contained in the last commut.

@polarathene
Copy link
Copy Markdown
Member

Sorry, at a limited capacity atm recovering from an injury that's cutting my time at the desk per day short.

I wasn't able to review the actual commits today, only provide the investigation for the test failure. I'll try get the review in after some rest. Should be starting that in roughly 12 hours.


Regarding the hostname.bats fix, that's not what I meant. It should probably be a generic --data fallback when the option isn't provided as that will always be the fallback used and I can't say we want a Message-Id that defaults to the containers hostname with our usage? 😅 (the postfix one is arguably more useful too)

Thanks to @polarathene for helping me here!

I also noticed the logic of `_send_email_unchecked` was not 100% sound
when supplying the `--data` argument - this has also been fixed.
@georglauterbach
Copy link
Copy Markdown
Member Author

I force-pushed only the last 3 commits (that IIRC you did not get to yet anyway) to correct a simple error of mine.

@polarathene
Copy link
Copy Markdown
Member

to correct a simple error of mine.

You've included rspamd PR into this by accident.

ref: #3747 (comment)

I also added a bit more context to the function description and I added
comments inside the function to help better understand what's going on.
@georglauterbach

This comment was marked as outdated.

@georglauterbach

This comment was marked as outdated.

@polarathene

This comment was marked as outdated.

@georglauterbach
Copy link
Copy Markdown
Member Author

georglauterbach commented Jan 9, 2024

I'm mostly interested in preventing sending.bash helpers from growing too complex, some have large doc comments now, or a fair amount of repetition due to wrapper methods (where you may be the only one really benefiting from that duplication for that IDE integration docs tooltip feature 😝).

👍🏼 Let's do this with another PR :) Let's actually not postpone it. I have a simple, nice, and easy to review commit currently cooking for you, just waiting to be reviewed 🥧 UPDATE: There it is c78b6d1 :D

Hence, the queue ID of the first mail has to be retrieved differently. I renamed the --unchecked flag to --expect-rejection, which better conveys its intention.

Oh, is this only applicable to these rspamd tests needs again? 🤔 I see every other usage is calling the _unchecked() variant directly 👍 (with all likewise expecting failure with most using an assert_failure, except postscreen which lacks any assert on swaks output/status)

Not quite, but since these some one of the newest tests, they use the helpers. I think all the other tests can use the new _send_email_and_get_id helpers too, but of course, the tests need to be adjusted to them. And these helpers are really useful only if multiple distinct e-mails have to be sent.

From earlier feedback: #3747 (comment)

Looks like a typo? Should be the same GTUBE test string?

Refering to the Rspamd documentation on GTUBE here, I think the string is actually correct.

Thanks, from those docs they look like they're rspamd specific:

non-reject GTUBE-like patterns

Since you're the only maintainer who's familiar with rspamd specific stuff like that and it's easy enough to think GTUBE typo?! Additional context beyond "GTUBE pattern" would be helpful.

Likewise, the mapping to different rspamd actions is non-obvious. You knew it'd add a header, but all I could see was the same mail being sent with --body and what looked like a typo 😅 Looking at the full test file there wasn't much context explaining this special processing.

I understand; I will adjust this test specifically in the future by adding more comments to explain what's going on in more detail.

From earlier feedback: #3747 (comment)

The spam mail is rejected AFAIK; naturally, you would not observe Dovecot storing anything at all, right?

I actually thought the mail was meant to be looked up for checking headers received, not just inspecting logs related to the mail. I hadn't read over the full test file.

With SpamAssassin the behaviour differs as GTUBE doesn't trigger reject (D_REJECT). We have D_PASS (deliver spam to junk + quarantine) or D_BOUNCE (explicit opt-in). Both would respond to swaks with success as queuing the mail, which is still the case for a bounce as the mail was accepted and a bounce response was sent back to the sender 🤷‍♂️

Just an observation of different behaviour users would experience, I'm not too familiar with rspamd beyond that. We used to default to D_BOUNCE for any mail marked as spam (SA_TAG2 == SA_KILL, + D_BOUNCE), but decided it better to default to all spam to junk since users were getting legitimate mail for activities like registration/verification bounced. Later SA_KILL was raised to almost double of SA_TAG2, although with D_PASS default this just avoids quarantine, for D_BOUNCE some spam will go to junk, unless the spam score is notably higher.

Now I understand that I never had a lot of understanding for SA/Amavis myself 😆 Seems Rspamd and SA/Amavis behave quite differently here.

you can see that the GTUBE pattern for MAIL_ID_SPAM is a pattern that results in a reject, while MAIL_ID_HEADER just adds a header.

Ok, so rspamd will have the equivalent of D_REJECT (no bounce), instead of letting spam through into junk?

For non-GTUBE, if the score is high enough (for DMS at the moment a score of 11 IIRC) the mail is outright rejected. GTUBE can trigger such a reject instantly.

Or this is only related to the GTUBE pattern and other high scoring spam mail would still get delivered to junk folder like the add header GTUBE-like pattern tests?

High-scoring spam mail will

  1. be greylisted at a score of 4;
  2. marked with a header at a score of 6;
  3. have their subject re-written at a score of 7;
  4. rejected at a score of 11.

See

greylist = 4;
add_header = 6;
rewrite_subject = 7;
reject = 11;

GTUbe is just a short path of achieving that, or really, a way of testing the proper behavior.

From the linked rspamd docs:

Unlike SpamAssassin where GTUBE carries a spam score, in Rspamd it triggers an action and inserts the GTUBE symbol.

Is that just GTUBE specific? Or does Rspamd not have a spam score in general and only flags mail as either spam/ham?

Rspamd adds a few headers. Most important are X-Spamd-Result (no, not X-Rspamd-Result - I know, weird), which could look like this:

default: False [-4.10 / 11.00]; SIGNED_SMIME(-2.00)[]; R_SPF_ALLOW(-1.00)[+ip4:212.227.17.0/27]; RWL_AMI_LASTHOP(-1.00)[212.227.17.13:from]; MIME_GOOD(-0.20)[multipart/signed,multipart/alternative,text/plain]; ONCE_RECEIVED(0.10)[]; RCVD_VIA_SMTP_AUTH(0.00)[]; RCVD_TLS_ALL(0.00)[]; RCPT_COUNT_ONE(0.00)[1]; ASN(0.00)[asn:8560, ipnet:212.227.0.0/16, country:DE]; RECEIVED_SPAMHAUS_PBL(0.00)[79.254.17.189:received]; MIME_TRACE(0.00)[0:+,1:+,2:+,3:~,4:~]; RCVD_COUNT_ONE(0.00)[1]; RWL_MAILSPIKE_POSSIBLE(0.00)[212.227.17.13:from]; MID_RHS_MATCH_FROM(0.00)[]; R_DKIM_NA(0.00)[]; ARC_NA(0.00)[]; FROM_EQ_ENVFROM(0.00)[]; FROM_HAS_DN(0.00)[]; RCVD_IN_DNSWL_NONE(0.00)[212.227.17.13:from]; TO_MATCH_ENVRCPT_ALL(0.00)[]; TO_DN_ALL(0.00)[]; PREVIOUSLY_DELIVERED(0.00)[[email protected]]; DMARC_DNSFAIL(0.00)[online.de : query refused]; HAS_ATTACHMENT(0.00)[]

and then there is a corresponding X-Rspamd-Result, for our example it'd be no action (since the score is -4.10). If the score got beyond 6, Rspamd may add X-Spam: Yes. This is what Dovecot Sieve then matches on to put the message into Junk. Rspamd does not know of the Junk folder at all (contrary to Amavis, I guess, which somehow ties into Dovecot it seems???). Rspamd and Dovecot do not talk to each like at all.

@georglauterbach
Copy link
Copy Markdown
Member Author

Oh no, my last commit is broken. Please review everything except the last commit. I'am unable to continue today, will fix tomorrow.

Copy link
Copy Markdown
Member

@polarathene polarathene left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • Optional revisions / suggestions.
  • Add context comment on dead-code (MESSAGE_ID) if keeping it.
  • Consider setting an explicit message ID header and using common Queue ID extraction if maintaining the branched handling provides no value.

I've also provided a context comment for how Postfix Queue ID is generated, and that your ARM64 findings might be filesystem specific rather than platform? Doesn't seem we can do much about the variable length though (which is fine).

Comment thread test/helper/sending.bash Outdated
Comment thread test/helper/sending.bash Outdated
Comment thread test/helper/sending.bash Outdated
Comment thread test/helper/sending.bash Outdated
Comment on lines +171 to +172
local QUEUE_ID_REGEX='[A-Z0-9]{9,12}'
local MESSAGE_ID_REGEX="[0-9]{14}\\.${QUEUE_ID_REGEX}"
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Context comment, no action required.


Curious, I hit up the postfix docs and voila enable_long_queue_ids. That should explain the difference (seems related to the inode number?), it also notes how it defines both the Queue ID and Message ID generation for both yes / no (default) values.

So it may not have been AMD64 vs ARM64, but rather filesystems and the inode number being encoded into base 52 (yes) or base 16 (no)? Or just the inode number itself, since leading 0 is possibly dropped (so short IDs are 5 chars for time + 8 chars for 32-bit inode number)

With enable_long_queue_ids=yes you always get a z to delimit the time and inode encoded values. For the inode number encoded, base 16 vs base 51 (inode is enocded in base 52 without the z) on my system is producing 6 chars vs 3 chars. On your ARM64 system that is presumably a bit longer, but I don't know about it being ARM64 specific.


Other references:

Comment thread test/tests/parallel/set1/spam_virus/rspamd_full.bats
_wait_for_smtp_port_in_container
_wait_for_tcp_port_in_container 10024 # port 10024 is for Amavis
_send_email --from '[email protected]' --data 'amavis/spam'
_send_email --from '[email protected]' --data 'amavis/spam.txt'
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Like here:

function send_spam() {
  _send_email --from '[email protected]' --data "${GTUBE}"
}
Suggested change
_send_email --from '[email protected]' --data 'amavis/spam.txt'
_send_spam

Maybe for a future follow-up PR, since I had an approach in mind before the revised sending.bash PRs appeared 😝

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍🏼 Future PR please :D

@polarathene
Copy link
Copy Markdown
Member

polarathene commented Jan 9, 2024

I think all the other tests can use the new _send_email_and_get_id helpers too, but of course, the tests need to be adjusted to them. And these helpers are really useful only if multiple distinct e-mails have to be sent.

I'd only want to see that if it's providing value to the tests without introducing too much additional complexity / abstraction 😅

The previous approach with files was a bit straight forward to grok over without digging through actual tests using them to know what we're testing coverage wise.

We lose that a bit with migrating from external files to equivalent in args at the specific test-cases using them and implicit logic / handling. In some cases that's a non-issue and swaks is definitely better than nc.

We have a lot going on here in this PR already, so I've been avoiding discussing swaks support for --config to create presets for common sets of options/args we provide. Leveraging that will DRY up some other usage of swaks and allow for providing explicit options to override when necessary.

It'll be better for the default fallback values, since the current approach you chose to implement makes the args mandatory (or has required special handling during iterations). Precedence for swaks is config < ENV < CLI options.

Using --config will address my concern with what we'd be losing from the removal of files to --data 👍 (I want to stress that I don't want this PR dragging out further with more drastic iterations to adapt to such a change, it should be deferred)

RELATED: Alternative way of retrieving the Queue ID (only works when mail would be delivered? limiting it's usefulness)


I understand; I will adjust this test specifically in the future by adding more comments to explain what's going on in more detail.

Just pointing out the relevant rspamd docs / behaviour is sufficient.

Main issue was I had familiarity with GTUBE value, swaks and our test-suite but not rspamd implicitly changing it's behaviour for a GTUBE value variant that'd otherwise look like a typo and repeated test 😓

Similar to how LDAP support became a mystery no current maintainer at the time could grok from lack of context/familiarity.


Now I understand that I never had a lot of understanding for SA/Amavis myself 😆 Seems Rspamd and SA/Amavis behave quite differently here.

That's understandable. I think we may have other bugs related to SA/Amavis too, but it's not really something any maintainer is deeply invested in or familiar with AFAIK 🤷‍♂️

When rspamd takes the spotlight as the new default, just something to be aware of. Potentially the test-suite will improve before then and I can unify common test cases for spam/dkim support which may help with catching those differences early.

I am focused on v14 PRs, but running low on time to spare. I am not sure of my availability late Jan onwards.


For non-GTUBE, if the score is high enough (for DMS at the moment a score of 11 IIRC) the mail is outright rejected. GTUBE can trigger such a reject instantly.

That may be something to consider documenting awareness on when you introduce rspamd as default.

When handling #3756 I added a reference to the SA_KILL ENV docs section to a prior comment of mine #3058 (comment) that includes a report of a SA spam score of 8 for an important / legitimate mail.

The expectation was for mail to be delivered to spam by default regardless, not rejected / bounced. Perhaps 11 in rspamd is sufficient but the changelog should definitely communicate this difference in expectation.

High-scoring spam mail will

  1. be greylisted at a score of 4;
  2. marked with a header at a score of 6;
  3. have their subject re-written at a score of 7;
  4. rejected at a score of 11.

That doesn't appear to be communicated at our rspamd docs page?

If rspamd is to be the default in future, it'd be nice for users to more easily come across that information without having to sift through upstream docs or our related source files.

Rspamd adds a few headers. Most important are X-Spamd-Result (no, not X-Rspamd-Result - I know, weird)

Spamd may be related to SpamAssassin spamd? IIRC Rspamd does support integration with SpamAssassin. I'm not sure if that's worthwhile to DMS though, assuming it's an alternative replacement to whatever you have rspamd setup doing atm 🤔

If so that may explain the header name, might be compatibility / convention for downstream services.

Rspamd does not know of the Junk folder at all (contrary to Amavis, I guess, which somehow ties into Dovecot it seems???). Rspamd and Dovecot do not talk to each like at all.

AFAIK that is the same with Amavis:

  • SpamAssassin (if enabled) analyzes the incoming mail, assigning a spam score (and perhaps other information) to Amavis.
  • Amavis uses SA (similar to how rspamd can I suppose) for functionality it doesn't handle itself, but SA could be replaced with some other compatible service. With our v14 release, I recall there was a distinction made here with SA v4 (related to spamd?) and Amavis also had some changes.
  • Anyway... same process, some rules/logic decides on what headers to append. Amavis will perform actions like you described with rspamd, with a final decision of D_BOUNCE / D_PASS (there's also D_DISCARD / D_REJECT, but DMS doesn't help configure that, other than config override file support).
  • Unlike rspamd, we still use Amavis as a service instead of as a milter IIRC? (could be mistaken, that may be other services in DMS).
  • Mail can be handed off to Dovecot, and like rspamd Dovecot Sieve kicks in and checks for the spam headers (either from Amavis or Rspamd) and if they indicate spam mail is sent to junk (unless the shared ENV prevents this sieve script running in the first place).

That's as much as I know about it 😅

@georglauterbach
Copy link
Copy Markdown
Member Author

From what I read so far: I will provide another PR just for Rspamd docs + tests (more information is required on these fronts). I will also push another commit which applies your proposal @polarathene for simplifying queue ID management in _send_email_and_get_id. This should wrap up this PR I hope.

Copy link
Copy Markdown
Member

@polarathene polarathene left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor feedback. Otherwise LGTM 👍

Comment thread test/helper/sending.bash Outdated
Comment thread test/helper/sending.bash Outdated
Comment thread test/helper/sending.bash
Comment thread test/helper/sending.bash
Comment on lines +158 to +160
# But we also requre potential Dovecot sieve output, which requires the mesage ID,
# so we need to provide the message ID too.
ID_ENV_VAR_REF+="|${MID}"
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is $MID not obvious to the caller of _send_email_and_get_id()? ("<${1}@dms-tests>")

They could match against the Dovecot msgid with that same input then. Technically the whole need for the reference var vs just using a regular arg seems no longer required? 🤔

I'm fine with the PR merging with whichever approach you want to use.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is $MID not obvious to the caller of _send_email_and_get_id()? ("<${1}@dms-tests>")

No, I actually think it is only obvious to me and you, but not to others.

They could match against the Dovecot msgid with that same input then.

Still easier to just use _print_mail_log_for_id IMO.

Technically the whole need for the reference var vs just using a regular arg seems no longer required? 🤔

I don't get why this is the case TBH. The reference is important to not rely on subshells, which hide exit codes and asserts.

I'm fine with the PR merging with whichever approach you want to use.

I'd definitely go with refs, since there is very good reason for choosing them.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is $MID not obvious to the caller of _send_email_and_get_id()? ("<${1}@dms-tests>")

No, I actually think it is only obvious to me and you, but not to others.

I meant, tests would just call something like:

# MID is set here, it's pretty obvious to the caller
# (who'd otherwise be able to pass this to current `_send_email_and_get_id()` anyway..)
local MID="<rspamd-gtube-reject@dms-tests>"

# Send an email as you normally would, but include the MID to match:
_send_email --expect-rejection \
  --header "Message-Id: ${MID}" \
  --body "XJS${GTUBE_SUFFIX}"

# Dedicated function that takes the MID, waits for empty queue and returns Queue ID
_print_mail_log_for_queue_id "$(_get_queue_id ${MID})"
# Actual asserts that matter:
assert_output --partial 'milter-reject'
assert_output --partial '5.7.1 Gtube pattern'

# Dovecot Sieve (spam to junk) shouldn't exist for this mail (QID doesn't exist on this log line)
_filter_service_log 'mail' "msgid=${MID}"
assert_failure

They could match against the Dovecot msgid with that same input then.

Still easier to just use _print_mail_log_for_id IMO.

As shown above with the Dovecot example, your method won't help as the queue ID is for Postfix, it's not part of the Dovecot log, unless we avoid providing our own message ID header and let Postfix generate one that includes the original queue ID (this is not the queue ID that you necessarily use to match logs, as I have shown it can change while message ID persists).

Thus Queue ID and Message ID values won't necessarily cover all log lines of interest alone. Hence the logic to match by message ID, and get the queue ID of a specific log entry that it is known the queue ID can differ.


Technically the whole need for the reference var vs just using a regular arg seems no longer required? 🤔

I don't get why this is the case TBH. The reference is important to not rely on subshells, which hide exit codes and asserts.

I'd definitely go with refs, since there is very good reason for choosing them.

I just prefer keeping the helpers simple. If the main purpose is just to get the Queue ID from a Message ID, I'm not sure how this differs from convention of other helpers regarding exit codes and asserts where the use of a reference is necessary.

If this was a nicer language like Rust I'd still probably opt for a function that takes an input parameter (as reference) and returns a value rather than a reference (or mutating a value from a reference).

I'm not great with bash, so if you feel it's really worthwhile/important, I'm not going to challenge that and trust your call, I just personally don't see the pragmatic benefit provided vs something simpler.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is $MID not obvious to the caller of _send_email_and_get_id()? ("<${1}@dms-tests>")

No, I actually think it is only obvious to me and you, but not to others.

I meant, tests would just call something like:

# MID is set here, it's pretty obvious to the caller
# (who'd otherwise be able to pass this to current `_send_email_and_get_id()` anyway..)
local MID="<rspamd-gtube-reject@dms-tests>"

# Send an email as you normally would, but include the MID to match:
_send_email --expect-rejection \
  --header "Message-Id: ${MID}" \
  --body "XJS${GTUBE_SUFFIX}"

# Dedicated function that takes the MID, waits for empty queue and returns Queue ID
_print_mail_log_for_queue_id "$(_get_queue_id ${MID})"
# Actual asserts that matter:
assert_output --partial 'milter-reject'
assert_output --partial '5.7.1 Gtube pattern'

# Dovecot Sieve (spam to junk) shouldn't exist for this mail (QID doesn't exist on this log line)
_filter_service_log 'mail' "msgid=${MID}"
assert_failure

That seems more verbose than it needs to be, IMO. Moreover, with the current approach, building MID is easy if it is ever required. The line _filter_service_log 'mail' "msgid=${MID}" fails, but filtering for ${MID} directly (as _send_email_and_get_id currently does) yields proper output.

They could match against the Dovecot msgid with that same input then.

Still easier to just use _print_mail_log_for_id IMO.

As shown above with the Dovecot example, your method won't help as the queue ID is for Postfix, it's not part of the Dovecot log, unless we avoid providing our own message ID header and let Postfix generate one that includes the original queue ID (this is not the queue ID that you necessarily use to match logs, as I have shown it can change while message ID persists).

Thus Queue ID and Message ID values won't necessarily cover all log lines of interest alone. Hence the logic to match by message ID, and get the queue ID of a specific log entry that it is known the queue ID can differ.

Technically the whole need for the reference var vs just using a regular arg seems no longer required? 🤔

I don't get why this is the case TBH. The reference is important to not rely on subshells, which hide exit codes and asserts.

I'd definitely go with refs, since there is very good reason for choosing them.

I just prefer keeping the helpers simple. If the main purpose is just to get the Queue ID from a Message ID, I'm not sure how this differs from convention of other helpers regarding exit codes and asserts where the use of a reference is necessary.

If this was a nicer language like Rust I'd still probably opt for a function that takes an input parameter (as reference) and returns a value rather than a reference (or mutating a value from a reference).

I'm not great with bash, so if you feel it's really worthwhile/important, I'm not going to challenge that and trust your call, I just personally don't see the pragmatic benefit provided vs something simpler.

I think complexity is not too high really for _send_email_and_get_id. I think the local -n reference is required and I'd definitely go for it :)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That seems more verbose than it needs to be, IMO. Moreover, with the current approach, building MID is easy if it is ever required.

The line _filter_service_log 'mail' "msgid=${MID}" fails, but filtering for ${MID} directly (as _send_email_and_get_id currently does) yields proper output.

???

What is more verbose?

  • The _send_email is the equivalent of your _send_email_and_get_id --expect-rejection --body ... for one of the rspamd calls. You pass a string to be part of the MID value instead of declaring the full header.
  • Middle portion is your own test case
  • Final part shows Dovecot msgid matching. In this test case the log matching is expected to fail as the assert afterwards implies. It doesn't exist with the rspamd body triggering reject.
  • You would not get a match on this log line with your methods due to the MID being the search keyword needed, the queue ID isn't part of the log line 🤨 (my comment mentioned all that)

I think complexity is not too high really for _send_email_and_get_id. I think the local -n reference is required and I'd definitely go for it :)

My example was to draw attention that it's doing extra work than appears necessary 🤷‍♂️ as opposed to something clearer which can also match a dovecot sieve msgid log line (when one is present).

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That seems more verbose than it needs to be, IMO. Moreover, with the current approach, building MID is easy if it is ever required.
The line _filter_service_log 'mail' "msgid=${MID}" fails, but filtering for ${MID} directly (as _send_email_and_get_id currently does) yields proper output.

???

Forget what I said, I was not reading correctly to begin with - sorry.

What is more verbose?

  • The _send_email is the equivalent of your _send_email_and_get_id --expect-rejection --body ... for one of the rspamd calls. You pass a string to be part of the MID value instead of declaring the full header.

  • Middle portion is your own test case

  • Final part shows Dovecot msgid matching. In this test case the log matching is expected to fail as the assert afterwards implies. It doesn't exist with the rspamd body triggering reject.

Now I understand what you mean! We could implement this as well, I don't mind :)

  • You would not get a match on this log line with your methods due to the MID being the search keyword needed, the queue ID isn't part of the log line 🤨 (my comment mentioned all that)

I don't quite get that though; ${MID} is part of the ID we're currently using (second part of the regex (|${MID})). I think we're talking about different things here; which log line are you referring to with "this"?

I think complexity is not too high really for _send_email_and_get_id. I think the local -n reference is required and I'd definitely go for it :)

My example was to draw attention that it's doing extra work than appears necessary 🤷‍♂️ as opposed to something clearer which can also match a dovecot sieve msgid log line (when one is present).

The rspamd_full test shows that _send_email_and_get_id is already capable of providing Dovecot Sieve log lines, or am I missing something?

_print_mail_log_for_id "${MAIL_ID_HEADER}"
assert_output --partial "fileinto action: stored mail into mailbox 'Junk'"

Copy link
Copy Markdown
Member

@polarathene polarathene left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM 👍

@casperklein may want to glance over sending.bash, but I think overall this is fine to merge :)

Comment thread test/helper/sending.bash
Copy link
Copy Markdown
Member

@casperklein casperklein left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM 👍

@casperklein may want to glance over sending.bash, but I think overall this is fine to merge :)

I had a quick look, but honestly, I can't take the time to really dig into it right now. If our tests run successful with that changes, that's the best approval 👍

Go ahead and merge 🚀

@georglauterbach georglauterbach merged commit 06fab3f into master Jan 11, 2024
@georglauterbach georglauterbach deleted the tests/normalizations branch January 11, 2024 09:34
This was referenced Jan 11, 2024
@georglauterbach georglauterbach modified the milestones: v14.0.0, v13.3.0 Jan 15, 2024
@polarathene polarathene mentioned this pull request Jan 21, 2024
4 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/tests kind/improvement Improve an existing feature, configuration file or the documentation kind/update Update an existing feature, configuration file or the documentation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

other: test normalizations [TODO]: Tests - Use swaks to add header for ID to trace e-mails more efficiently

3 participants