Skip to content
Merged
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,13 @@ All notable changes to this project will be documented in this file. The format

If necessary, DSNs for authenticated users can be disabled via the `postfix-master.cf` override with the following contents:

```
```cf
submission/inet/smtpd_discard_ehlo_keywords=silent-discard,dsn
submissions/inet/smtpd_discard_ehlo_keywords=silent-discard,dsn
```

- using the old path for the Rspamd custom commands file (`/tmp/docker-mailserver/rspamd-modules.conf`), which was deprecated, will now prevent startup; use `/tmp/docker-mailserver/rspamd/custom-commands.conf` instead

### Added

- New environment variable `MARK_SPAM_AS_READ`. When set to `1`, marks incoming junk as "read" to avoid unwanted notification of junk as new mail ([#3489](https://github.com/docker-mailserver/docker-mailserver/pull/3489))
Expand Down
4 changes: 2 additions & 2 deletions docs/content/config/security/rspamd.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,11 +107,11 @@ DMS brings sane default settings for Rspamd. They are located at `/etc/rspamd/lo

!!! question "What is [`docker-data/dms/config/`][docs-dms-config-volume]?"

If you want to overwrite the default settings and / or provide your own settings, you can place files at `docker-data/dms/config/rspamd/override.d/` (a directory that is linked to `/etc/rspamd/override.d/`, if it exists) to override Rspamd and DMS default settings. This directory will not do a complete file override, but a [forced override of the specific settings in that file][rspamd-docs-override-dir].
If you want to overwrite the default settings and / or provide your own settings, you can place files at `docker-data/dms/config/rspamd/override.d/`. Files from this directory are copied to `/etc/rspamd/override.d/` during startup. These files [forcibly override][rspamd-docs-override-dir] Rspamd and DMS default settings.

!!! warning "Clashing Overrides"

Note that when also [using the `rspamd-commands` file](#with-the-help-of-a-custom-file), files in `override.d` may be overwritten in case you adjust them manually and with the help of the file.
Note that when also [using the `custom-commands.conf` file](#with-the-help-of-a-custom-file), files in `override.d` may be overwritten in case you adjust them manually and with the help of the file.

[rspamd-docs-override-dir]: https://www.rspamd.com/doc/faq.html#what-are-the-locald-and-overrided-directories
[docs-dms-config-volume]: ../../faq.md#what-about-the-docker-datadmsconfig-directory
Expand Down
63 changes: 32 additions & 31 deletions target/bin/rspamd-dkim
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,10 @@ ${ORANGE}DESCRIPTION${RESET}

${ORANGE}OPTIONS${RESET}
${BLUE}Generic Program Information${RESET}
-v Enable verbose logging (setting the log level to 'debug').
-vv Enable very verbose logging (setting the log level to 'trace').
help Print the usage information.
-f | --force Overwrite existing files if there are any
-v Enable verbose logging (setting the log level to 'debug').
-vv Enable very verbose logging (setting the log level to 'trace').
help Print the usage information.

${BLUE}Configuration adjustments${RESET}
keytype Set the type of key you want to use.
Expand Down Expand Up @@ -69,6 +70,7 @@ function __do_as_rspamd_user() {
}

function _parse_arguments() {
FORCE=0
KEYTYPE='rsa'
KEYSIZE='2048'
SELECTOR='mail'
Expand Down Expand Up @@ -112,6 +114,12 @@ function _parse_arguments() {
exit 0
;;

( '-f' | '--force' )
FORCE=1
shift 1
continue
;;

( '-vv' )
# shellcheck disable=SC2034
LOG_LEVEL='trace'
Expand All @@ -132,7 +140,6 @@ function _parse_arguments() {
__usage
_exit_with_error "Unknown option(s) '${1}' ${2:+"and '${2}'"}"
;;

esac

shift 2
Expand All @@ -150,34 +157,10 @@ function _preflight_checks() {
_log 'warn' "The directory '/tmp/docker-mailserver' does not seem to be mounted by a volume - the Rspamd (DKIM) configuration will not be persisted"
fi

# Note: Variables not marked with `local` are used
# in other functions (after this function was called).
# Also keep in sync with: target/scripts/startup/setup.d/security/rspamd.sh:__rspamd__run_early_setup_and_checks
local RSPAMD_DMS_D='/tmp/docker-mailserver/rspamd'
local RSPAMD_OVERRIDE_D='/etc/rspamd/override.d'
readonly RSPAMD_DMS_DKIM_D="${RSPAMD_DMS_D}/dkim"
readonly RSPAMD_DMS_OVERRIDE_D="${RSPAMD_DMS_D}/override.d"
_rspamd_get_envs

mkdir -p "${RSPAMD_DMS_DKIM_D}" "${RSPAMD_DMS_OVERRIDE_D}"
chown _rspamd:_rspamd "${RSPAMD_DMS_DKIM_D}"

# Mimmick target/scripts/startup/setup.d/security/rspamd.sh:__rspamd__run_early_setup_and_checks where
# ${RSPAMD_OVERRIDE_D} is linked to ${RSPAMD_DMS_OVERRIDE_D}, but not if
#
# 1. ${RSPAMD_OVERRIDE_D} has already been linked to ${RSPAMD_DMS_OVERRIDE_D}
# 2. ${RSPAMD_OVERRIDE_D} has contents already
#
# If 1. is true, then we're good since DMS' default setup linked the directory already and we will save
# a persisted location in every case. If 1. is false, 2. should be false as well since by default,
# ${RSPAMD_OVERRIDE_D} has no contents - we're good as well. What should logically never happen is
# that 1. is false but 2. is true; this case is caught nevertheless and a warning is emitted.
if [[ ! -h "${RSPAMD_OVERRIDE_D}" ]]; then
if rmdir "${RSPAMD_OVERRIDE_D}" &>/dev/null; then
ln -s "${RSPAMD_DMS_OVERRIDE_D}" "${RSPAMD_OVERRIDE_D}"
else
_log 'warn' "Could not link '${RSPAMD_OVERRIDE_D}' to '${RSPAMD_DMS_OVERRIDE_D}' (as '${RSPAMD_OVERRIDE_D}' does not appear to be empty, which is unexpected) - you will need to restart DMS for changes to take effect"
fi
fi
}

function _create_keys() {
Expand All @@ -195,6 +178,16 @@ function _create_keys() {
PUBLIC_KEY_DNS_FILE="${BASE_FILE_NAME}.public.dns.txt"
PRIVATE_KEY_FILE="${BASE_FILE_NAME}.private.txt"

if [[ -f ${PUBLIC_KEY_FILE} ]] || [[ -f ${PUBLIC_KEY_DNS_FILE} ]] || [[ -f ${PRIVATE_KEY_FILE} ]]; then
if [[ ${FORCE} -eq 0 ]]; then
_log 'error' "Not overwriting existing files (use '--force' to overwrite existing files)"
exit 1
else
_log 'info' "Overwriting existing files as the '--force' option was supplied"
rm "${PUBLIC_KEY_FILE}" "${PUBLIC_KEY_DNS_FILE}" "${PRIVATE_KEY_FILE}"
fi
fi

# shellcheck disable=SC2310
if __do_as_rspamd_user rspamadm \
dkim_keygen \
Expand Down Expand Up @@ -226,7 +219,7 @@ function _check_permissions() {
function _setup_default_signing_conf() {
local DEFAULT_CONFIG_FILE="${RSPAMD_DMS_OVERRIDE_D}/dkim_signing.conf"
if [[ -f ${DEFAULT_CONFIG_FILE} ]]; then
_log 'debug' "'${DEFAULT_CONFIG_FILE}' exists, not supplying a default"
_log 'info' "'${DEFAULT_CONFIG_FILE}' exists, not supplying a default ('--force' does not overwrite this file, manual adjustment required)"
else
_log 'info' "Supplying a default configuration (to '${DEFAULT_CONFIG_FILE}')"
cat >"${DEFAULT_CONFIG_FILE}" << EOF
Expand All @@ -253,7 +246,15 @@ domain {
}

EOF
chown _rspamd:_rspamd "${DEFAULT_CONFIG_FILE}"

# We copy here immediately in order to not rely on the changedetector - this way, users
# can immediately use the new keys. The file should not already exist in ${RSPAMD_OVERRIDE_D}
# since it would have been copied already.
cp "${DEFAULT_CONFIG_FILE}" "${RSPAMD_OVERRIDE_D}/dkim_signing.conf"
chown _rspamd:_rspamd "${DEFAULT_CONFIG_FILE}" "${RSPAMD_OVERRIDE_D}/dkim_signing.conf"

_log 'debug' 'Restarting Rspamd as initial DKIM configuration was suppplied'
supervisorctl restart rspamd
Comment on lines +250 to +257
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.

This will still potentially trigger a change detection right? As it compares to it's before/after checksums in the config volume?


It seems that DKIM keys aren't copied over to an internal location like the certs are? I'm not too concerned about that I guess 🤔

FWIW, the rspamd docs mention rspamadm template for generating configs from jinja templates if you wanted to take advantage of that 🤷‍♂️

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.

UCL can also be JSON (easier to manipulate)

Or.. since the UCL format also supports a JSON representation, you could also just concat your default config base as yaml:

# documentation: https://rspamd.com/doc/modules/dkim_signing.html
enabled: true
sign_authenticated: true
sign_local: false
try_fallback: false
use_domain: header
use_redis: false # don't change unless Redis also provides the DKIM keys
use_esld: true
allow_username_mismatch: true
check_pubkey: true # you want to use this in the beginning

with a loop over domain blocks (which could be generated as separate files if that's better):

domain:
  ${DOMAIN}:
    path: ${PRIVATE_KEY_FILE}
    selector: ${SELECTOR}

eg:

domain:
  # Single selector example:
  example.com:
    path: /dms/dkim/rsa-2048-mail-rsa-example.com.private.txt
    selector: mail-rsa

  # Multiple selectors example:
  example.org:
    selectors:
      - path: /dms/dkim/rsa-2048-mail-rsa-example.org.private.txt
        selector: mail-rsa
      - path: /dms/dkim/ed25519-mail-ec-example.org.private.txt
        selector: mail-ec

Then with yq we can take this YAML and generate a single JSON document:

$ yq -o json <(cat base.yaml domains.yaml)
{
  "enabled": true,
  "sign_authenticated": true,
  "sign_local": false,
  "try_fallback": false,
  "use_domain": "header",
  "use_redis": false,
  "use_esld": true,
  "allow_username_mismatch": true,
  "check_pubkey": true,
  "domain": {
    "example.com": {
      "path": "/dms/dkim/rsa-2048-mail-rsa-example.com.private.txt",
      "selector": "mail-rsa"
    },
    "example.org": {
      "selectors": [
        {
          "path": "/dms/dkim/rsa-2048-mail-rsa-example.org.private.txt",
          "selector": "mail-rsa"
        },
        {
          "path": "/dms/dkim/ed25519-mail-ec-example.org.private.txt",
          "selector": "mail-ec"
        }
      ]
    }
  }
}

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.

Consistent input format transformed to rspamd single vs multi selector

We can be more consistent with the generation too, changing our domains.yaml into:

domain:
  example.com:
    selectors:
      - path: /dms/dkim/rsa-2048-mail-rsa-example.com.private.txt
        selector: mail-rsa

  example.org:
    selectors:
      - path: /dms/dkim/rsa-2048-mail-rsa-example.org.private.txt
        selector: mail-rsa
      - path: /dms/dkim/ed25519-mail-ec-example.org.private.txt
        selector: mail-ec

Then we can produce the same output as above by filtering for selectors with only 1 element, and rewriting that domain entry into path + selector fields:

yq -o json \
  '. | with(.domain[]; . |=  select(.selectors | length == 1) |= .selectors[0])' \
  <(cat base.yaml domains.yaml)

Now you could handle multiple domains like the open-dkim generator supports.

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.

Better unified internal storage layout for dkim keys

Although due to multiple key types and selectors, a better structure might be:

/dms/dkim/keys/${DOMAIN}/${SELECTOR}/{private,public}.pem

The DNS TXT record can be generated from the public.pem, assuming public.pem is actually just the public key. The rspamd generator like opendkim outputs the zone file record, but additionally creates another file that extracts the record value into a single line (which other DNS providers are more compatible with).

With the above structure, both opendkim and rspamd should be compatible to share the source and you'd have fairly predictable/semantic paths to work with.


That is beneficial for the dkim_signing.conf dynamic generation since you can loop through it with yq to create the config:

# export keyword for first 2 examples for `yq` to access ENV:
export DOMAIN="example.com"
export SELECTOR="mail-rsa"
export PRIVATE_KEY_FILE="/dms/dkim/keys/example.com/mail-rsa/private.pem"

# No input, create a new YAML doc:
yq --null-input '{ env(DOMAIN): { "selectors": [{ "path": env(PRIVATE_KEY_FILE), "selector": env(SELECTOR) }] }}'

# Easier to read spread across multiple lines:
yq --null-input '{
  env(DOMAIN): {
    "selectors": [{
      "path": env(PRIVATE_KEY_FILE),
      "selector": env(SELECTOR) }]
  }
}'

# HereDoc a template to use vars as keys/values
yq -P << EOF
{
  "${DOMAIN}": {
    "selectors": [{
      "path": "${PRIVATE_KEY_FILE}",
      "selector": "${SELECTOR}"
    }]
  }
}
EOF

# All output the following YAML:
example.com:
  selectors:
    - path: /dms/dkim/keys/example.com/mail-rsa/private.pem
      selector: mail-rsa

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.

Walkthrough of dynamic dkim_signing.conf generation with yq

If each keypair location had an associated YAML snippet like above, you could then scan the directory tree for the yaml files and merge them all together:

Grab all the YAML snippets:

$ find /dms/dkim/keys -type f -name "*.yaml" -exec cat {} \;

example.com:
  selectors:
    - path: /dms/dkim/keys/example.com/mail-ec/private.pem
      selector: mail-ec
example.com:
  selectors:
    - path: /dms/dkim/keys/example.com/mail-rsa/private.pem
      selector: mail-rsa
example.org:
  selectors:
    - path: /dms/dkim/keys/example.org/mail-rsa/private.pem
      selector: mail-rsa

Now pass that into yq and merge under the domain field/object:

$ find /dms/dkim/keys -type f -name "*.yaml" \
  | xargs yq eval-all '. as $item ireduce ({}; . *+ $item ) | { "domain": . }'

domain:
  example.com:
    selectors:
      - path: /dms/dkim/keys/example.com/mail-ec/private.pem
        selector: mail-ec
      - path: /dms/dkim/keys/example.com/mail-rsa/private.pem
        selector: mail-rsa
  example.org:
    selectors:
      - path: /dms/dkim/keys/example.org/mail-rsa/private.pem
        selector: mail-rsa

If rspamd requires single selectors to avoid arrays, additionally handle that:

$ find /dms/dkim/keys -type f -name "*.yaml" \
  | xargs yq eval-all '. as $item ireduce ({}; . *+ $item ) | { "domain": . }
  | with(.domain[]; . |=  select(.selectors | length == 1) |= .selectors[0])'

domain:
  example.com:
    selectors:
      - path: /dms/dkim/keys/example.com/mail-ec/private.pem
        selector: mail-ec
      - path: /dms/dkim/keys/example.com/mail-rsa/private.pem
        selector: mail-rsa
  example.org:
    path: /dms/dkim/keys/example.org/mail-rsa/private.pem
    selector: mail-rsa

Finally, set JSON as output and prepend the base.yaml config:

$ find /dms/dkim/keys -type f -name "*.yaml" \
  | xargs yq -o json eval-all '. as $item ireduce ({}; . *+ $item ) | { "domain": . }
  | with(.domain[]; . |=  select(.selectors | length == 1) |= .selectors[0])
  |= load("base.yaml") + .'

{
  "enabled": true,
  "sign_authenticated": true,
  "sign_local": false,
  "try_fallback": false,
  "use_domain": "header",
  "use_redis": false,
  "use_esld": true,
  "allow_username_mismatch": true,
  "check_pubkey": true,
  "domain": {
    "example.org": {
      "path": "/dms/dkim/keys/example.org/mail-rsa/private.pem",
      "selector": "mail-rsa"
    },
    "example.test": {
      "selectors": [
        {
          "path": "/dms/dkim/keys/example.test/mail-ec/private.pem",
          "selector": "mail-ec"
        },
        {
          "path": "/dms/dkim/keys/example.test/mail-rsa/private.pem",
          "selector": "mail-rsa"
        }
      ]
    }
  }
}

No comments support/retained due to JSON, but that is not an issue if relying on this as internal config generated at container runtime.

The base.yaml with comments could still exist in the container and optionally be replaced by users config volume override 🤷‍♂️

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.

Alternatively, UCL supports an .include "path/to/file.conf" macro.

It can handle merging of duplicates too, but not logic for the domain being a map of path + selector or an array of such maps under selectors field AFAIK?

Might not be an issue if you can always use the selectors array, including for single elements (I assume you can). Then you can disregard all the yq + YAML dance I guess and just .include the domains config(s) as separate files similar to the above approach? 👍

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.

This will still potentially trigger a change detection right? As it compares to it's before/after checksums in the config volume?

STAGING_FILES are crated by scanning RSPAMD_DMS_D (i.e. /tmp/docker-mailserver/rspamd), so AFAIK it should not trigger another event (since the cp copies to a location not checked and chown does not change SHA sum IIRC.

It seems that DKIM keys aren't copied over to an internal location like the certs are? I'm not too concerned about that I guess 🤔

Yes. This could be changed in the future if we encounter problems, but it seems to be absolutely fine for now.

FWIW, the rspamd docs mention rspamadm template for generating configs from jinja templates if you wanted to take advantage of that 🤷‍♂️

Have not looked intot that yes.

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.

About the Rspamd DKIM proposals: wow, that was very interesting! Funnily enough, I think we should first try the .include ... macros before dancing around with yq (even though I found the yq stuff cool). Thank you for sharing your ideas here. I will make sure to remember this location; the reason I didn't touch DKIM for Rspamd here any more than the --force option is that the PR is already pretty big. The --force option was due to a recent issue I stumbled upon. I know you'd like to merge the OpenDKIM and Rspamd DKIM scripts, and I can tackle this in the future; for now, I'd like this PR to stay as-is to not inflate it any more. For a future PR that tackles DKIM again, we back-reference to your comments here!

Copy link
Copy Markdown
Member

@polarathene polarathene Oct 25, 2023

Choose a reason for hiding this comment

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

I know you'd like to merge the OpenDKIM and Rspamd DKIM scripts, and I can tackle this in the future; for now, I'd like this PR to stay as-is to not inflate it any more.
For a future PR that tackles DKIM again, we back-reference to your comments here!

Oh yeah I didn't mean to update this PR to apply the review comments here, definitely a separate PR 👍

I agree the include macro would be wiser. I'd have suggested that before typing up all the yq stuff if I had read the rest of the UCL readme first.... 😓


UPDATE: I got around to trying KCL out, and while it has some nice benefits this was a scenario that it didn't handle too well.

  • While they've migrated from Python to Rust, the syntax for iteration and such isn't likely to shift from its Python heritage which is a big 👎 for me.
  • It does seem to have some nice advantages for more basic feature set though.

For example, it might be nice for managing ENV / internal config with doc generation + type validation/constraints, even has linting and formatting tools 😎

KCL is primarily for defining configuration in a more featureful way, and then outputting YAML it seems. It can ingest YAML (kinda) too.

UPDATE2: KCL discussion got some feedback since and they have some fixes in a future release that improves viability of KCL for this: https://github.com/orgs/kcl-lang/discussions/804#discussioncomment-7430551

fi
}

Expand Down
32 changes: 32 additions & 0 deletions target/scripts/check-for-changes.sh
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ source /etc/dms-settings
# usage with DMS_HOSTNAME, which should remove the need to call this:
_obtain_hostname_and_domainname

# This is a helper to properly set all Rspamd-related environment variables
# correctly and in one place.
_rspamd_get_envs

# verify checksum file exists; must be prepared by start-mailserver.sh
if [[ ! -f ${CHKSUM_FILE} ]]; then
_exit_with_error "'${CHKSUM_FILE}' is missing" 0
Expand Down Expand Up @@ -49,6 +53,7 @@ function _check_for_changes() {
# Handle any changes
_ssl_changes
_postfix_dovecot_changes
_rspamd_changes

_log_with_date 'debug' 'Reloading services due to detected changes'

Expand Down Expand Up @@ -174,6 +179,33 @@ function _ssl_changes() {
# They presently have no special handling other than to trigger a change that will restart Postfix/Dovecot.
}

function _rspamd_changes() {
# RSPAMD_DMS_D='/tmp/docker-mailserver/rspamd'
if [[ ${CHANGED} =~ ${RSPAMD_DMS_D}/.* ]]; then

# "${RSPAMD_DMS_D}/override.d"
if [[ ${CHANGED} =~ ${RSPAMD_DMS_OVERRIDE_D}/.* ]]; then
Copy link
Copy Markdown
Member

@casperklein casperklein Oct 29, 2023

Choose a reason for hiding this comment

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

Regex not necessary?

Suggested change
if [[ ${CHANGED} =~ ${RSPAMD_DMS_OVERRIDE_D}/.* ]]; then
if [[ ${CHANGED} == "${RSPAMD_DMS_OVERRIDE_D}/"* ]]; then

Also think about the very likely case, when someone mounts something like /tmp/docker-mailserver/rspamd/overrideXd, which would also match 😆

There are other places where normal string comparison is done with =~, I did choose this just as an example.

Regardless of this, the rest of the PR looks good 👍

Copy link
Copy Markdown
Member Author

@georglauterbach georglauterbach Oct 29, 2023

Choose a reason for hiding this comment

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

I think your example wouldn't work. When you match via ==, you'd need an asterisk before as well, because ${CHANGED} is basically dir/file1 file2 dir2/dir3/file3 and matching file2 would require == *file2*, but with regex, you can just use =~ file2, hence the regex IIRC.

Also think about the very likely case, when someone mounts something like /tmp/docker-mailserver/rspamd/overrideXd, which would also match 😆

Can you elaborate please? I don't see why this would be very likely :)

Copy link
Copy Markdown
Member

@casperklein casperklein Oct 29, 2023

Choose a reason for hiding this comment

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

Can you elaborate please? I don't see why this would be very likely :)

It is not. That was just a joke 😆

Copy link
Copy Markdown
Member Author

@georglauterbach georglauterbach Oct 29, 2023

Choose a reason for hiding this comment

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

Haha, I see 😆 I have always had a hard time telling irony in texts :D

_log_with_date 'trace' 'Rspamd - Copying configuration overrides'
rm "${RSPAMD_OVERRIDE_D}"/*
cp "${RSPAMD_DMS_OVERRIDE_D}"/* "${RSPAMD_OVERRIDE_D}"
fi

# "${RSPAMD_DMS_D}/custom-commands.conf"
if [[ ${CHANGED} =~ ${RSPAMD_DMS_CUSTOM_COMMANDS_F} ]]; then
_log_with_date 'trace' 'Rspamd - Generating new configuration from custom commands'
_rspamd_handle_user_modules_adjustments
fi

# "${RSPAMD_DMS_D}/dkim"
if [[ ${CHANGED} =~ ${RSPAMD_DMS_DKIM_D} ]]; then
_log_with_date 'trace' 'Rspamd - DKIM files updated'
fi

_log_with_date 'debug' 'Rspamd configuration has changed - restarting service'
supervisorctl restart rspamd
fi
}

while true; do
_check_for_changes
sleep 2
Expand Down
6 changes: 6 additions & 0 deletions target/scripts/helpers/change-detection.sh
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,12 @@ function _monitored_files_checksums() {
"${DMS_DIR}/dovecot-quotas.cf"
"${DMS_DIR}/dovecot-masters.cf"
)

# Check whether Rspamd is used and if so, monitor it's changes as well
if [[ ${ENABLE_RSPAMD} -eq 1 ]] && [[ -d ${RSPAMD_DMS_D} ]]; then
readarray -d '' STAGING_FILES_RSPAMD < <(find "${RSPAMD_DMS_D}" -type f -name "*.sh" -print0)
Comment thread
polarathene marked this conversation as resolved.
STAGING_FILES+=("${STAGING_FILES_RSPAMD[@]}")
fi
fi

# SSL certs:
Expand Down
1 change: 1 addition & 0 deletions target/scripts/helpers/index.sh
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ function _import_scripts() {
source "${PATH_TO_SCRIPTS}/network.sh"
source "${PATH_TO_SCRIPTS}/postfix.sh"
source "${PATH_TO_SCRIPTS}/relay.sh"
source "${PATH_TO_SCRIPTS}/rspamd.sh"
source "${PATH_TO_SCRIPTS}/ssl.sh"
source "${PATH_TO_SCRIPTS}/utils.sh"

Expand Down
105 changes: 105 additions & 0 deletions target/scripts/helpers/rspamd.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
#! /bin/bash

# shellcheck disable=SC2034 # VAR appears unused.

function _rspamd_get_envs() {
readonly RSPAMD_LOCAL_D='/etc/rspamd/local.d'
readonly RSPAMD_OVERRIDE_D='/etc/rspamd/override.d'

readonly RSPAMD_DMS_D='/tmp/docker-mailserver/rspamd'
readonly RSPAMD_DMS_DKIM_D="${RSPAMD_DMS_D}/dkim"
readonly RSPAMD_DMS_OVERRIDE_D="${RSPAMD_DMS_D}/override.d"

readonly RSPAMD_DMS_CUSTOM_COMMANDS_F="${RSPAMD_DMS_D}/custom-commands.conf"
}

# Parses `RSPAMD_DMS_CUSTOM_COMMANDS_F` and executed the directives given by the file.
# To get a detailed explanation of the commands and how the file works, visit
# https://docker-mailserver.github.io/docker-mailserver/latest/config/security/rspamd/#with-the-help-of-a-custom-file
function _rspamd_handle_user_modules_adjustments() {
# Adds an option with a corresponding value to a module, or, in case the option
# is already present, overwrites it.
#
# @param ${1} = file name in ${RSPAMD_OVERRIDE_D}/
# @param ${2} = module name as it should appear in the log
# @param ${3} = option name in the module
# @param ${4} = value of the option
#
# ## Note
#
# While this function is currently bound to the scope of `_rspamd_handle_user_modules_adjustments`,
# it is written in a versatile way (taking 4 arguments instead of assuming `ARGUMENT2` / `ARGUMENT3`
# are set) so that it may be used elsewhere if needed.
function __add_or_replace() {
local MODULE_FILE=${1:?Module file name must be provided}
local MODULE_LOG_NAME=${2:?Module log name must be provided}
local OPTION=${3:?Option name must be provided}
local VALUE=${4:?Value belonging to an option must be provided}
# remove possible whitespace at the end (e.g., in case ${ARGUMENT3} is empty)
VALUE=${VALUE% }
local FILE="${RSPAMD_OVERRIDE_D}/${MODULE_FILE}"

readonly MODULE_FILE MODULE_LOG_NAME OPTION VALUE FILE

[[ -f ${FILE} ]] || touch "${FILE}"

if grep -q -E "${OPTION}.*=.*" "${FILE}"; then
__rspamd__log 'trace' "Overwriting option '${OPTION}' with value '${VALUE}' for ${MODULE_LOG_NAME}"
sed -i -E "s|([[:space:]]*${OPTION}).*|\1 = ${VALUE};|g" "${FILE}"
else
__rspamd__log 'trace' "Setting option '${OPTION}' for ${MODULE_LOG_NAME} to '${VALUE}'"
echo "${OPTION} = ${VALUE};" >>"${FILE}"
fi
}

# We check for usage of the previous location of the commands file.
# TODO This can be removed after the release of v14.0.0.
local RSPAMD_DMS_CUSTOM_COMMANDS_F_OLD="${RSPAMD_DMS_D}-modules.conf"
readonly RSPAMD_DMS_CUSTOM_COMMANDS_F_OLD
if [[ -f ${RSPAMD_DMS_CUSTOM_COMMANDS_F_OLD} ]]; then
_dms_panic__general "Old custom command file location '${RSPAMD_DMS_CUSTOM_COMMANDS_F_OLD}' is deprecated (use '${RSPAMD_DMS_CUSTOM_COMMANDS_F}' now)" 'Rspamd setup'
fi

if [[ -f "${RSPAMD_DMS_CUSTOM_COMMANDS_F}" ]]; then
__rspamd__log 'debug' "Found file '${RSPAMD_DMS_CUSTOM_COMMANDS_F}' - parsing and applying it"

local COMMAND ARGUMENT1 ARGUMENT2 ARGUMENT3
while read -r COMMAND ARGUMENT1 ARGUMENT2 ARGUMENT3; do
case "${COMMAND}" in
('disable-module')
__rspamd__helper__enable_disable_module "${ARGUMENT1}" 'false' 'override'
;;

('enable-module')
__rspamd__helper__enable_disable_module "${ARGUMENT1}" 'true' 'override'
;;

('set-option-for-module')
__add_or_replace "${ARGUMENT1}.conf" "module '${ARGUMENT1}'" "${ARGUMENT2}" "${ARGUMENT3}"
;;

('set-option-for-controller')
__add_or_replace 'worker-controller.inc' 'controller worker' "${ARGUMENT1}" "${ARGUMENT2} ${ARGUMENT3}"
;;

('set-option-for-proxy')
__add_or_replace 'worker-proxy.inc' 'proxy worker' "${ARGUMENT1}" "${ARGUMENT2} ${ARGUMENT3}"
;;

('set-common-option')
__add_or_replace 'options.inc' 'common options' "${ARGUMENT1}" "${ARGUMENT2} ${ARGUMENT3}"
;;

('add-line')
__rspamd__log 'trace' "Adding complete line to '${ARGUMENT1}'"
echo "${ARGUMENT2}${ARGUMENT3+ ${ARGUMENT3}}" >>"${RSPAMD_OVERRIDE_D}/${ARGUMENT1}"
;;

(*)
__rspamd__log 'warn' "Command '${COMMAND}' is invalid"
continue
;;
esac
done < <(_get_valid_lines_from_file "${RSPAMD_DMS_CUSTOM_COMMANDS_F}")
fi
}
Loading