Compare commits

...

21 Commits

Author SHA1 Message Date
Brennan Kinney 23bb1c8e50
refactor: setup CLI `open-dkim` (#4375)
Refactoring this `setup` CLI command as part of the effort to unify our DKIM feature support between OpenDKIM + Rspamd:
- Adds a `main()` method similar to other setup CLI commands.
- Help text more aligned with equivalent rspamd DKIM setup CLI command.
- DRY some repetition such as hard-coded paths to use variables.
- OpenDKIM config files are created / initialized early on now with `_create_opendkim_configs()`. `while` loop only needs to append entries, so is easier to grok.
- `_create_dkim_key()` to scope just the logic (_and additional notes_) to key generation via `opendkim-genkey`
- Now overall logic with the `while` loop of the script occurs in `_generate_dkim_keys()`:
  - Ownership fixes are now applied after the `while` loop as that seems more appropriate than per iteration.
  - Temporary VHOST config is now removed since it's no longer useful after running.
- Tests adjusted for one new log for adding of default trusted hosts content.

Overall this should be nicer to grok/maintain. Some of this logic will be reused for the unified DKIM generation command in future, which is more likely to shift towards all domains using the same keypair by default with rspamd/opendkim config generated at runtime rather than reliant upon DMS config volume to provide that (_still expected for private key_).

---------

Co-authored-by: Casper <casperklein@users.noreply.github.com>
Co-authored-by: Georg Lauterbach <44545919+georglauterbach@users.noreply.github.com>
2025-03-31 11:27:28 +02:00
beertje44 229ebba1b8
docs: Dovecot Solr - Add compatibility note (#4433)
Co-authored-by: Casper <casperklein@users.noreply.github.com>
Co-authored-by: Brennan Kinney <5098581+polarathene@users.noreply.github.com>
2025-03-31 11:00:57 +13:00
Christian Schmidt df7a98ec50
chore: Fix broken README link for SRS (#4434) 2025-03-29 12:11:29 +01:00
Georg Lauterbach 5027f4f5b6
release: v15.0.2 (#4432)
* chore: prepare for release of v15.0.2

Signed-off-by: Georg Lauterbach <44545919+georglauterbach@users.noreply.github.com>

* Update CHANGELOG.md

---------

Signed-off-by: Georg Lauterbach <44545919+georglauterbach@users.noreply.github.com>
Co-authored-by: Brennan Kinney <5098581+polarathene@users.noreply.github.com>
2025-03-27 00:09:31 +01:00
Moritz Poldrack c2c48b2b83
fix: ensure message content is not modified by header filter (#4429) 2025-03-26 12:24:20 +13:00
Georg Lauterbach 70d645d863
release: v15.0.1 (#4423)
Signed-off-by: Georg Lauterbach <44545919+georglauterbach@users.noreply.github.com>
2025-03-23 01:27:47 +01:00
Brennan Kinney a3571a88c1
fix: DMS state volume must ensure `o+x` permission (#4420) 2025-03-18 23:48:12 +01:00
tranquillity-codes 8ca2bd212c
chore: Gender-neutral language (#4421)
Co-authored-by: itycodes <tranquillitycodes@proton.me>
2025-03-18 19:08:56 +01:00
Georg Lauterbach 0362fa682e
fix: include all files in change detection of Rspamd (#4418) 2025-03-18 00:08:14 +01:00
Brennan Kinney 7c680a0fbc
fix: `start-mailserver.sh` requires `mail_state.sh` to be sourced on restarts (#4417) 2025-03-16 15:34:51 +01:00
Brennan Kinney a156c2c031
docs: Update Dovecot link in `mailserver.env` (#4415) 2025-03-16 21:04:32 +13:00
Lasslos 6b1a566497
docs: Fail2Ban - Add example with required ENV to enable (#4402) 2025-03-06 08:29:39 +01:00
Brennan Kinney 02f068b2b2
fix: Use correct Postfix parameter for `postfix-receive-access.cf` (#4399) 2025-03-05 11:00:06 +13:00
Brennan Kinney d0629f4cb6
chore: Revise utility install scripts + add Smallstep `step` CLI (#4376)
Changes:
- `jaq` should probably live in `/usr/local/bin` with other third-party sourced binaries.
- `swaks` install properly with just `tar`, no `mv` + `rm` needed.
- Added Smallstep `step` CLI. This serves similar purpose to `openssl` commands, but is generally nicer for usage with generation and inspection of certs/keys. I've talked up using in DMS a few times in the past for our TLS helper and unifying DKIM support (_instead of separate OpenDKIM/Rspamd generators_).
- Including `step` for both AMD64 / ARM64 archs needs the alternate naming convention that it's published to GH releases with.
- Added commentary about the `tar` usage. The ownership is a common concern with GH release sources, technically a non-issue when running as `root`
2025-03-03 22:58:42 +01:00
Dmitry R. 1756ba04fb
fix: Support `chmod` on `/var/log/mail/*` when dir is empty (#4391)
Co-authored-by: Brennan Kinney <5098581+polarathene@users.noreply.github.com>
Co-authored-by: Georg Lauterbach <44545919+georglauterbach@users.noreply.github.com>
2025-03-03 21:28:15 +00:00
dependabot[bot] 807f4f7118
chore(deps): Bump docker/setup-qemu-action from 3.4.0 to 3.6.0 (#4392) 2025-03-03 21:58:58 +01:00
dependabot[bot] 0fbbc44dd3
chore(deps): Bump docker/build-push-action from 6.14.0 to 6.15.0 (#4393) 2025-03-03 20:52:28 +00:00
dependabot[bot] 3c833d8ee8
chore(deps): Bump docker/setup-buildx-action from 3.9.0 to 3.10.0 (#4394) 2025-03-03 20:50:17 +00:00
dependabot[bot] dd595e0a05
chore(deps): Bump docker/metadata-action from 5.6.1 to 5.7.0 (#4395) 2025-03-03 21:47:59 +01:00
Brennan Kinney 5686a4097a
fix: `setup email restrict` configs should only prepend once (#4379)
* fix: `setup email restrict` configs should only prepend once

* chore: Prepend to our custom parameter variant to retain applying to all `smtpd` ports

---------

Co-authored-by: Georg Lauterbach <44545919+georglauterbach@users.noreply.github.com>
2025-03-01 13:55:13 +01:00
dependabot[bot] 309b5a9086
chore(deps): Bump docker/build-push-action from 6.13.0 to 6.14.0 (#4389)
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 6.13.0 to 6.14.0.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/v6.13.0...v6.14.0)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Georg Lauterbach <44545919+georglauterbach@users.noreply.github.com>
2025-03-01 12:24:47 +00:00
26 changed files with 330 additions and 137 deletions

View File

@ -71,16 +71,16 @@ jobs:
cache-buildx-
- name: 'Set up QEMU'
uses: docker/setup-qemu-action@v3.4.0
uses: docker/setup-qemu-action@v3.6.0
with:
platforms: arm64
- name: 'Set up Docker Buildx'
uses: docker/setup-buildx-action@v3.9.0
uses: docker/setup-buildx-action@v3.10.0
# NOTE: AMD64 can build within 2 minutes
- name: 'Build images'
uses: docker/build-push-action@v6.13.0
uses: docker/build-push-action@v6.15.0
with:
context: .
# Build at least the AMD64 image (which runs against the test suite).

View File

@ -23,7 +23,7 @@ jobs:
- name: 'Prepare tags'
id: prep
uses: docker/metadata-action@v5.6.1
uses: docker/metadata-action@v5.7.0
with:
images: |
${{ secrets.DOCKER_REPOSITORY }}
@ -35,12 +35,12 @@ jobs:
type=semver,pattern={{major}}.{{minor}}.{{patch}}
- name: 'Set up QEMU'
uses: docker/setup-qemu-action@v3.4.0
uses: docker/setup-qemu-action@v3.6.0
with:
platforms: arm64
- name: 'Set up Docker Buildx'
uses: docker/setup-buildx-action@v3.9.0
uses: docker/setup-buildx-action@v3.10.0
# Try get the cached build layers from a prior `generic_build.yml` job.
# NOTE: Until adopting `type=gha` scoped cache exporter (in `docker/build-push-action`),
@ -67,7 +67,7 @@ jobs:
password: ${{ secrets.GITHUB_TOKEN }}
- name: 'Build and publish images'
uses: docker/build-push-action@v6.13.0
uses: docker/build-push-action@v6.15.0
with:
context: .
build-args: |

View File

@ -38,12 +38,12 @@ jobs:
# Ensures consistent BuildKit version (not coupled to Docker Engine),
# and increased compatibility of the build cache vs mixing buildx drivers.
- name: 'Set up Docker Buildx'
uses: docker/setup-buildx-action@v3.9.0
uses: docker/setup-buildx-action@v3.10.0
# Importing from the cache should create the image within approx 30 seconds:
# NOTE: `qemu` step is not needed as we only test for AMD64.
- name: 'Build AMD64 image from cache'
uses: docker/build-push-action@v6.13.0
uses: docker/build-push-action@v6.15.0
with:
context: .
tags: mailserver-testing:ci

View File

@ -37,12 +37,12 @@ jobs:
# Ensures consistent BuildKit version (not coupled to Docker Engine),
# and increased compatibility of the build cache vs mixing buildx drivers.
- name: 'Set up Docker Buildx'
uses: docker/setup-buildx-action@v3.9.0
uses: docker/setup-buildx-action@v3.10.0
# Importing from the cache should create the image within approx 30 seconds:
# NOTE: `qemu` step is not needed as we only test for AMD64.
- name: 'Build AMD64 image from cache'
uses: docker/build-push-action@v6.13.0
uses: docker/build-push-action@v6.15.0
with:
context: .
tags: mailserver-testing:ci

View File

@ -2,10 +2,47 @@
All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased](https://github.com/docker-mailserver/docker-mailserver/compare/v15.0.0...HEAD)
## [Unreleased](https://github.com/docker-mailserver/docker-mailserver/compare/v15.0.2...HEAD)
> **Note**: Changes and additions listed here are contained in the `:edge` image tag. These changes may not be as stable as released changes.
### Updates
- **Documentation:**
- Added a compatibility note for a Dovecot + Solr 9.8 breaking change ([#4433](https://github.com/docker-mailserver/docker-mailserver/pull/4433))
- **Internal:**
- Refactored `setup config dkim` (`open-dkim`) ([#4375](https://github.com/docker-mailserver/docker-mailserver/pull/4375))
## [v15.0.2](https://github.com/docker-mailserver/docker-mailserver/releases/tag/v15.0.2)
### Fixes
- **Postfix**
- Avoid modifying the message body when filtering sender headers. This regression was introduced from [#4120](https://github.com/docker-mailserver/docker-mailserver/pull/4120) as part of DMS v15.0.0 ([#4429](https://github.com/docker-mailserver/docker-mailserver/pull/4429))
## [v15.0.1](https://github.com/docker-mailserver/docker-mailserver/releases/tag/v15.0.1)
### Added
- **Internal:**
- Added the Smallstep `step` CLI command for future internal usage ([#4376](https://github.com/docker-mailserver/docker-mailserver/pull/4376))
### Fixes
- **Postfix:**
- `setup email restrict` generated configs now only prepend to `dms_smtpd_sender_restrictions` ([#4379](https://github.com/docker-mailserver/docker-mailserver/pull/4379))
- **Rspamd:**
- Change detection support now monitors all files found within the DMS _Config Volume_ Rspamd directory ([#4418](https://github.com/docker-mailserver/docker-mailserver/pull/4418))
- **Internal:**
- A permissions fix for `/var/log/mail` that was [added in DMS v15]((https://github.com/docker-mailserver/docker-mailserver/pull/4374)) no longer encounters an error when no log files are present during a container restart, such as with a `tmpfs` volume mount ([#4391](https://github.com/docker-mailserver/docker-mailserver/pull/4391))
- The DMS _State Volume_ (`/var/mail-state`) will now ensure it's file tree is accessible for services when the volume was created with missing executable bit ([#4420](https://github.com/docker-mailserver/docker-mailserver/pull/4420))
- The DMS _Config Volume_ (`/tmp/docker-mailserver`) now correctly updates permissions on container restarts ([#4417](https://github.com/docker-mailserver/docker-mailserver/pull/4417))
### Updates
- **Internal:**
- Minor improvements to `_install_utils()` in `packages.sh` ([#4376](https://github.com/docker-mailserver/docker-mailserver/pull/4376))
## [v15.0.0](https://github.com/docker-mailserver/docker-mailserver/releases/tag/v15.0.0)
### Breaking

View File

@ -202,7 +202,7 @@ Please read [the SSL page in the documentation][docs-tls] for more information.
Configures the handling of creating mails with forged sender addresses.
- **0** => (not recommended) Mail address spoofing allowed. Any logged in user may create email messages with a [forged sender address](https://en.wikipedia.org/wiki/Email_spoofing).
- 1 => Mail spoofing denied. Each user may only send with his own or his alias addresses. Addresses with [extension delimiters](http://www.postfix.org/postconf.5.html#recipient_delimiter) are not able to send messages.
- 1 => Mail spoofing denied. Each user may only send with their own or their alias addresses. Addresses with [extension delimiters](http://www.postfix.org/postconf.5.html#recipient_delimiter) are not able to send messages.
##### ENABLE_SRS

View File

@ -14,18 +14,48 @@ hide:
## Configuration
!!! warning
Enabling Fail2Ban support can be done via ENV, but also requires granting at least the `NET_ADMIN` capability to interact with the kernel and ban IP addresses.
DMS must be launched with the `NET_ADMIN` capability in order to be able to install the NFTables rules that actually ban IP addresses. Thus, either include `--cap-add=NET_ADMIN` in the `docker run` command, or the equivalent in the `compose.yaml`:
!!! example
```yaml
cap_add:
- NET_ADMIN
```
=== "Docker Compose"
```yaml title="compose.yaml"
services:
mailserver:
environment:
- ENABLE_FAIL2BAN=1
cap_add:
- NET_ADMIN
```
=== "Docker CLI"
```bash
docker run --rm -it \
--cap-add=NET_ADMIN \
--env ENABLE_FAIL2BAN=1
```
!!! warning "Security risk of adding non-default capabilties"
DMS bundles F2B into the image for convenience to simplify integration and deployment.
The [`NET_ADMIN`][security::cap-net-admin] and [`NET_RAW`][security::cap-net-raw] capabilities are not granted by default to the container root user, as they can be used to compromise security.
If this risk concerns you, it may be wiser to instead prefer only granting these capabilities to a dedicated Fail2Ban container ([example][lsio:f2b-image]).
!!! bug "Running Fail2Ban on Older Kernels"
DMS configures F2B to use NFTables, not IPTables (legacy). We have observed that older systems, for example NAS systems, do not support the modern NFTables rules. You will need to configure F2B to use legacy IPTables again, for example with the [``fail2ban-jail.cf``][github-file-f2bjail], see the [section on configuration further down below](#custom-files).
DMS configures F2B to use [NFTables][network::nftables], not [IPTables (legacy)][network::iptables-legacy].
We have observed that older systems (for example NAS systems), do not support the modern NFTables rules. You will need to configure F2B to use legacy IPTables again, for example with the [`fail2ban-jail.cf`][github-file-f2bjail], see the [section on configuration further down below](#custom-files).
[security::cap-net-admin]: https://0xn3va.gitbook.io/cheat-sheets/container/escaping/excessive-capabilities#cap_net_admin
[security::cap-net-raw]: https://0xn3va.gitbook.io/cheat-sheets/container/escaping/excessive-capabilities#cap_net_raw
[lsio:f2b-image]: https://docs.linuxserver.io/images/docker-fail2ban
[network::nftables]: https://en.wikipedia.org/wiki/Nftables
[network::iptables-legacy]: https://developers.redhat.com/blog/2020/08/18/iptables-the-two-variants-and-their-relationship-with-nftables#two_variants_of_the_iptables_command
### DMS Defaults

View File

@ -146,6 +146,25 @@ docker compose exec mailserver doveadm fts rescan -A
Usually within 15 minutes or so, you should be able to search your mail using the Dovecot FTS feature! :tada:
### Compatibility
Since Solr 9.8.0 was released (Jan 2025), a breaking change [deprecates support for `<lib>` directives][solr::9.8::lib-directive] which is presently used by the Dovecot supplied Solr config (`solr-config-9.xml`) to automatically load additional jars required.
To enable support for `<lib>` directives, add the following ENV to your `solr` container:
```yaml
services:
solr:
environment:
SOLR_CONFIG_LIB_ENABLED: true
```
!!! warning "Solr 10"
From the Solr 10 release onwards, this opt-in ENV will no longer be available.
If Dovecot has not updated their example Solr config ([upstream PR][dovecot::pr::solr-config-lib]), you will need to manually modify the Solr XML config to remove the `<lib>` directives and replace the suggested ENV `SOLR_CONFIG_LIB_ENABLED=true` with `SOLR_MODULES=analysis-extras`.
[docs::user-patches]: ../../config/advanced/override-defaults/user-patches.md
[docs::dovecot::full-text-search]: ../../config/advanced/full-text-search.md
[gh-dms::feature-request::dovecot-solr-package]: https://github.com/docker-mailserver/docker-mailserver/issues/4052
@ -154,3 +173,6 @@ docker compose exec mailserver doveadm fts rescan -A
[dockerfile-solr-uidgid]: https://github.com/apache/solr-docker/blob/9cd850b72309de05169544395c83a85b329d6b86/9.6/Dockerfile#L89-L92
[github-solr]: https://github.com/apache/solr
[github-dovecot::core-docs]: https://github.com/dovecot/core/tree/main/doc
[solr::9.8::lib-directive]: https://issues.apache.org/jira/browse/SOLR-16781
[dovecot::pr::solr-config-lib]: https://github.com/dovecot/core/pull/238

View File

@ -89,10 +89,10 @@ TLS_LEVEL=
# Configures the handling of creating mails with forged sender addresses.
#
# **0** => (not recommended) Mail address spoofing allowed. Any logged in user may create email messages with a forged sender address (see also https://en.wikipedia.org/wiki/Email_spoofing).
# 1 => Mail spoofing denied. Each user may only send with his own or his alias addresses. Addresses with extension delimiters(http://www.postfix.org/postconf.5.html#recipient_delimiter) are not able to send messages.
# 1 => Mail spoofing denied. Each user may only send with their own or their alias addresses. Addresses with extension delimiters(http://www.postfix.org/postconf.5.html#recipient_delimiter) are not able to send messages.
SPOOF_PROTECTION=
# Enables the Sender Rewriting Scheme. SRS is needed if your mail server acts as forwarder. See [postsrsd](https://github.com/roehling/postsrsd/blob/master/README.md#sender-rewriting-scheme-crash-course) for further explanation.
# Enables the Sender Rewriting Scheme. SRS is needed if your mail server acts as forwarder. See [postsrsd](https://github.com/roehling/postsrsd/blob/main/README.rst) for further explanation.
# - **0** => Disabled
# - 1 => Enabled
ENABLE_SRS=0
@ -508,7 +508,7 @@ DOVECOT_MAILBOX_FORMAT=maildir
# empty => no
# yes => Allow bind authentication for LDAP
# https://wiki.dovecot.org/AuthDatabase/LDAP/AuthBinds
# https://doc.dovecot.org/2.4.0/core/config/auth/databases/ldap.html#authentication-bind
DOVECOT_AUTH_BIND=
# -----------------------------------------------

View File

@ -12,9 +12,17 @@ if [[ -f /etc/dms-settings ]] && [[ $(_get_dms_env_value 'ENABLE_RSPAMD') -eq 1
fi
fi
KEYSIZE=2048
SELECTOR=mail
DOMAINS=
function _main() {
# Default parameters (updated by `_parse_arguments()`):
local KEYSIZE=2048
local SELECTOR=mail
local DMS_DOMAINS=
_require_n_parameters_or_print_usage 0 "${@}"
_parse_arguments "${@}"
_generate_dkim_keys
}
function __usage() {
printf '%s' "${PURPLE}OPEN-DKIM${RED}(${YELLOW}8${RED})
@ -57,122 +65,171 @@ ${ORANGE}EXAMPLES${RESET}
${ORANGE}EXIT STATUS${RESET}
Exit status is 0 if command was successful. If wrong arguments are provided or arguments contain
errors, the script will exit early with exit status 2.
errors, the script will exit early with a non-zero exit status.
"
}
_require_n_parameters_or_print_usage 0 "${@}"
function _parse_arguments() {
# Parse the command args through iteration:
while [[ ${#} -gt 0 ]]; do
case "${1}" in
while [[ ${#} -gt 0 ]]; do
case "${1}" in
( 'keysize' )
if [[ -n ${2+set} ]]; then
KEYSIZE="${2}"
shift
shift
else
_exit_with_error "No keysize provided after 'keysize' argument"
fi
;;
( 'keysize' )
if [[ -n ${2:-} ]]; then
KEYSIZE="${2}"
_log 'trace' "Keysize set to '${KEYSIZE}'"
else
_exit_with_error "No keysize provided after 'keysize' argument"
fi
;;
( 'selector' )
if [[ -n ${2+set} ]]; then
# shellcheck disable=SC2034
SELECTOR="${2}"
shift
shift
else
_exit_with_error "No selector provided after 'selector' argument"
fi
;;
( 'selector' )
if [[ -n ${2:-} ]]; then
SELECTOR="${2}"
_log 'trace' "Selector set to '${SELECTOR}'"
else
_exit_with_error "No selector provided after 'selector' argument"
fi
;;
( 'domain' )
if [[ -n ${2+set} ]]; then
DOMAINS="${2}"
shift
shift
else
_exit_with_error "No domain(s) provided after 'domain' argument"
fi
;;
( 'domain' )
if [[ -n ${2:-} ]]; then
DMS_DOMAINS="${2}"
_log 'trace' "Domain(s) set to '${DMS_DOMAINS}'"
else
_exit_with_error "No domain(s) provided after 'domain' argument"
fi
;;
( * )
__usage
_exit_with_error "Unknown options '${1}' ${2:+and \'${2}\'}"
;;
( 'help' )
__usage
exit 0
;;
esac
done
( * )
__usage
_exit_with_error "Unknown option(s) ${*}"
;;
esac
# Discard these two args (option + value) now that they've been processed:
shift 2
done
}
function _generate_dkim_keys() {
_generate_domains_config
if [[ ! -s ${DATABASE_VHOST} ]]; then
_log 'warn' 'No entries found, no keys to make'
exit 0
fi
# Initialize OpenDKIM configs if necessary:
_create_opendkim_configs
# Generate a keypair per domain and add reference to OpenDKIM configs:
local ENTRY_KEY KEY_TABLE_ENTRY SIGNING_TABLE_ENTRY
while read -r DKIM_DOMAIN; do
_create_dkim_key "${DKIM_DOMAIN}"
# Create / Update OpenDKIM configs with new DKIM key:
ENTRY_KEY="${SELECTOR}._domainkey.${DKIM_DOMAIN}"
KEY_TABLE_ENTRY="${ENTRY_KEY} ${DKIM_DOMAIN}:${SELECTOR}:/etc/opendkim/keys/${DKIM_DOMAIN}/${SELECTOR}.private"
SIGNING_TABLE_ENTRY="*@${DKIM_DOMAIN} ${ENTRY_KEY}"
# If no existing entry, add one:
if ! grep -q "${KEY_TABLE_ENTRY}" "${KEY_TABLE_FILE}"; then
echo "${KEY_TABLE_ENTRY}" >> "${KEY_TABLE_FILE}"
fi
if ! grep -q "${SIGNING_TABLE_ENTRY}" "${SIGNING_TABLE_FILE}"; then
echo "${SIGNING_TABLE_ENTRY}" >> "${SIGNING_TABLE_FILE}"
fi
done < <(_get_valid_lines_from_file "${DATABASE_VHOST}")
# No longer needed, remove:
rm "${DATABASE_VHOST}"
# Ensure ownership is consistent for all content belonging to the base directory,
# During container startup, an internal copy will be made via `_setup_opendkim()`
# with ownership we expect, while this chown is for the benefit of the users ownership.
chown -R "$(stat -c '%U:%G' "${OPENDKIM_BASE_DIR}")" "${OPENDKIM_BASE_DIR}"
}
# Prepare a file with one domain per line (iterated via while loop as DKIM_DOMAIN):
# Depends on methods from `scripts/helpers/postfix.sh`:
DATABASE_VHOST='/tmp/vhost.dkim'
# Prepare a file with one domain per line:
function _generate_domains_config() {
local TMP_VHOST='/tmp/vhost.dkim.tmp'
# Generate the default vhost (equivalent to /etc/postfix/vhost),
# unless CLI arg DOMAINS provided an alternative list to use instead:
if [[ -z ${DOMAINS} ]]; then
# unless CLI arg DMS_DOMAINS provided an alternative list to use instead:
if [[ -z ${DMS_DOMAINS:-} ]]; then
_obtain_hostname_and_domainname
# uses TMP_VHOST:
_vhost_collect_postfix_domains
else
tr ',' '\n' <<< "${DOMAINS}" >"${TMP_VHOST}"
tr ',' '\n' <<< "${DMS_DOMAINS}" >"${TMP_VHOST}"
fi
# uses DATABASE_VHOST + TMP_VHOST:
# Uses DATABASE_VHOST + TMP_VHOST:
_create_vhost
}
_generate_domains_config
if [[ ! -s ${DATABASE_VHOST} ]]; then
_log 'warn' 'No entries found, no keys to make'
exit 0
fi
# `opendkim-genkey` generates two files at the configured `--directory`:
# - <selector>.private (Private key, PEM encoded)
# - <selector>.txt (Public key, formatted as a TXT record for a RFC 1035 DNS Zone file)
function _create_dkim_key() {
DKIM_DOMAIN=${1?Expected to be provided a domain}
while read -r DKIM_DOMAIN; do
mkdir -p "/tmp/docker-mailserver/opendkim/keys/${DKIM_DOMAIN}"
OPENDKIM_DOMAINKEY_DIR="${OPENDKIM_BASE_DIR}/keys/${DKIM_DOMAIN}"
mkdir -p "${OPENDKIM_DOMAINKEY_DIR}"
if [[ ! -f "/tmp/docker-mailserver/opendkim/keys/${DKIM_DOMAIN}/${SELECTOR}.private" ]]; then
_log 'info' "Creating DKIM private key '/tmp/docker-mailserver/opendkim/keys/${DKIM_DOMAIN}/${SELECTOR}.private'"
DKIM_KEY_FILE="${OPENDKIM_DOMAINKEY_DIR}/${SELECTOR}.private"
if [[ ! -f "${DKIM_KEY_FILE}" ]]; then
_log 'info' "Creating DKIM private key '${DKIM_KEY_FILE}'"
# NOTE:
# --domain only affects a comment in the generated DNS Zone file
# --subdomains is the default,
# --nosubdomains would add `t=s` to the DNS TXT record generated
# http://www.opendkim.org/opendkim-genkey.8.html
opendkim-genkey \
--bits="${KEYSIZE}" \
--subdomains \
--domain="${DKIM_DOMAIN}" \
--selector="${SELECTOR}" \
--directory="/tmp/docker-mailserver/opendkim/keys/${DKIM_DOMAIN}"
--directory="${OPENDKIM_DOMAINKEY_DIR}"
fi
}
# fix permissions to use the same user:group as /tmp/docker-mailserver/opendkim/keys
chown -R "$(stat -c '%U:%G' /tmp/docker-mailserver/opendkim/keys)" "/tmp/docker-mailserver/opendkim/keys/${DKIM_DOMAIN}"
OPENDKIM_BASE_DIR='/tmp/docker-mailserver/opendkim'
KEY_TABLE_FILE="${OPENDKIM_BASE_DIR}/KeyTable"
SIGNING_TABLE_FILE="${OPENDKIM_BASE_DIR}/SigningTable"
TRUSTED_HOSTS_FILE="${OPENDKIM_BASE_DIR}/TrustedHosts"
# Create configs if missing:
function _create_opendkim_configs() {
mkdir -p "${OPENDKIM_BASE_DIR}"
local OPENDKIM_CONFIGS=(
"${KEY_TABLE_FILE}"
"${SIGNING_TABLE_FILE}"
"${TRUSTED_HOSTS_FILE}"
)
# write to KeyTable if necessary
KEYTABLEENTRY="${SELECTOR}._domainkey.${DKIM_DOMAIN} ${DKIM_DOMAIN}:${SELECTOR}:/etc/opendkim/keys/${DKIM_DOMAIN}/${SELECTOR}.private"
if [[ ! -f "/tmp/docker-mailserver/opendkim/KeyTable" ]]; then
_log 'debug' 'Creating DKIM KeyTable'
echo "${KEYTABLEENTRY}" >/tmp/docker-mailserver/opendkim/KeyTable
else
if ! grep -q "${KEYTABLEENTRY}" "/tmp/docker-mailserver/opendkim/KeyTable"; then
echo "${KEYTABLEENTRY}" >>/tmp/docker-mailserver/opendkim/KeyTable
# Only create if the file doesn't exist (avoids modifying mtime):
for FILE in "${OPENDKIM_CONFIGS[@]}"; do
if [[ ! -f "${FILE}" ]]; then
_log 'debug' "Creating OpenDKIM config '${FILE}'"
touch "${FILE}"
fi
fi
done
# write to SigningTable if necessary
SIGNINGTABLEENTRY="*@${DKIM_DOMAIN} ${SELECTOR}._domainkey.${DKIM_DOMAIN}"
if [[ ! -f /tmp/docker-mailserver/opendkim/SigningTable ]]; then
_log 'debug' 'Creating DKIM SigningTable'
echo "*@${DKIM_DOMAIN} ${SELECTOR}._domainkey.${DKIM_DOMAIN}" >/tmp/docker-mailserver/opendkim/SigningTable
else
if ! grep -q "${SIGNINGTABLEENTRY}" /tmp/docker-mailserver/opendkim/SigningTable; then
echo "${SIGNINGTABLEENTRY}" >>/tmp/docker-mailserver/opendkim/SigningTable
fi
# If file exists but is empty, add default hosts to trust:
if [[ ! -s "${TRUSTED_HOSTS_FILE}" ]]; then
_log 'debug' 'Adding default trust to OpenDKIM TrustedHosts config'
echo "127.0.0.1" > "${TRUSTED_HOSTS_FILE}"
echo "localhost" >> "${TRUSTED_HOSTS_FILE}"
fi
done < <(_get_valid_lines_from_file "${DATABASE_VHOST}")
}
# create TrustedHosts if missing
if [[ -d /tmp/docker-mailserver/opendkim ]] && [[ ! -f /tmp/docker-mailserver/opendkim/TrustedHosts ]]; then
_log 'debug' 'Creating DKIM TrustedHosts'
echo "127.0.0.1" >/tmp/docker-mailserver/opendkim/TrustedHosts
echo "localhost" >>/tmp/docker-mailserver/opendkim/TrustedHosts
fi
_main "${@}"

View File

@ -68,9 +68,10 @@ smtpd_forbid_bare_newline = yes
# smtpd_forbid_bare_newline_exclusions = $mynetworks
# Custom defined parameters for DMS:
# reject_unknown_sender_domain: https://github.com/docker-mailserver/docker-mailserver/issues/3716#issuecomment-1868033234
# Custom sender restrictions overview: https://github.com/docker-mailserver/docker-mailserver/pull/4379#issuecomment-2670365917
# `reject_unknown_sender_domain`: https://github.com/docker-mailserver/docker-mailserver/issues/3716#issuecomment-1868033234
dms_smtpd_sender_restrictions = permit_sasl_authenticated, permit_mynetworks, reject_unknown_sender_domain
# Submission ports 587 and 465 support for SPOOF_PROTECTION=1
# `SPOOF_PROTECTION=1` support requires prepending `reject_authenticated_sender_login_mismatch`
mua_sender_restrictions = reject_authenticated_sender_login_mismatch, $dms_smtpd_sender_restrictions
# Postscreen settings to drop zombies/open relays/spam early

View File

@ -8,4 +8,5 @@
/^\s*X-Mailer/ IGNORE
/^\s*X-Originating-IP/ IGNORE
/^\s*Received: from.*127.0.0.1/ IGNORE
/^Content-Type:/i PREPEND X-MS-Reactions: disallow
/^\s*X-MS-Reactions:/ IGNORE
/^\s*Message-Id:/i PREPEND X-MS-Reactions: disallow

View File

@ -36,20 +36,46 @@ function _pre_installation_steps() {
apt-get "${QUIET}" install --no-install-recommends "${EARLY_PACKAGES[@]}" 2>/dev/null
}
# Install third-party commands to /usr/local/bin
function _install_utils() {
local ARCH_A
ARCH_A=$(uname --machine)
# Alternate naming convention support: x86_64 (amd64) / aarch64 (arm64)
# https://en.wikipedia.org/wiki/X86-64#Industry_naming_conventions
local ARCH_B
case "${ARCH_A}" in
( 'x86_64' ) ARCH_B='amd64' ;;
( 'aarch64' ) ARCH_B='arm64' ;;
( * )
_log 'error' "Unsupported arch: '${ARCH_A}'"
return 1
;;
esac
# TIP: `*.tar.gz` releases tend to forget to reset UID/GID ownership when archiving.
# When extracting with `tar` as `root` the archived UID/GID is kept, unless using `--no-same-owner`.
# Likewise when the binary is in a nested location the full archived path
# must be provided + `--strip-components` to extract the file to the target directory.
# Doing this avoids the need for (`mv` + `rm`) or (`--to-stdout` + `chmod +x`)
_log 'debug' 'Installing utils sourced from Github'
_log 'trace' 'Installing jaq'
local JAQ_TAG='v2.1.0'
curl -sSfL "https://github.com/01mf02/jaq/releases/download/${JAQ_TAG}/jaq-$(uname -m)-unknown-linux-gnu" -o /usr/bin/jaq
chmod +x /usr/bin/jaq
curl -sSfL "https://github.com/01mf02/jaq/releases/download/${JAQ_TAG}/jaq-$(uname -m)-unknown-linux-gnu" -o /usr/local/bin/jaq
chmod +x /usr/local/bin/jaq
_log 'trace' 'Installing step'
local STEP_RELEASE='0.28.2'
curl -sSfL "https://github.com/smallstep/cli/releases/download/v${STEP_RELEASE}/step_linux_${STEP_RELEASE}_${ARCH_B}.tar.gz" \
| tar -xz --directory /usr/local/bin --no-same-owner --strip-components=2 "step_${STEP_RELEASE}/bin/step"
_log 'trace' 'Installing swaks'
# `perl-doc` is required for `swaks --help` to work:
apt-get "${QUIET}" install --no-install-recommends perl-doc
local SWAKS_VERSION='20240103.0'
local SWAKS_RELEASE="swaks-${SWAKS_VERSION}"
curl -sSfL "https://github.com/jetmore/swaks/releases/download/v${SWAKS_VERSION}/${SWAKS_RELEASE}.tar.gz" | tar -xz
mv "${SWAKS_RELEASE}/swaks" /usr/local/bin
rm -r "${SWAKS_RELEASE}"
curl -sSfL "https://github.com/jetmore/swaks/releases/download/v${SWAKS_VERSION}/${SWAKS_RELEASE}.tar.gz" \
| tar -xz --directory /usr/local/bin --no-same-owner --strip-components=1 "${SWAKS_RELEASE}/swaks"
}
function _install_postfix() {

View File

@ -43,7 +43,7 @@ function _monitored_files_checksums() {
# 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)
readarray -d '' STAGING_FILES_RSPAMD < <(find "${RSPAMD_DMS_D}" -type f -print0)
STAGING_FILES+=("${STAGING_FILES_RSPAMD[@]}")
fi
fi

View File

@ -1,7 +1,7 @@
#!/bin/bash
function _exit_with_error() {
if [[ -n ${1+set} ]]; then
if [[ -n ${1:-} ]]; then
_log 'error' "${1}"
else
_log 'error' "Call to '_exit_with_error' is missing a message to log"

View File

@ -43,12 +43,12 @@ RESET=$(echo -ne '\e[0m')
# message is logged. Likewise when the second argument
# is missing. Both failures will return with exit code '1'.
function _log() {
if [[ -z ${1+set} ]]; then
if [[ -z ${1:-} ]]; then
_log 'error' "Call to '_log' is missing a valid log level"
return 1
fi
if [[ -z ${2+set} ]]; then
if [[ -z ${2:-} ]]; then
_log 'error' "Call to '_log' is missing a message to log"
return 1
fi
@ -116,7 +116,7 @@ function _log() {
# variables file. If this does not yield a value either,
# use the default log level.
function _get_log_level_or_default() {
if [[ -n ${LOG_LEVEL+set} ]]; then
if [[ -n ${LOG_LEVEL:-} ]]; then
echo "${LOG_LEVEL}"
elif [[ -e /etc/dms-settings ]] && grep -q -E "^LOG_LEVEL='[a-z]+'" /etc/dms-settings; then
grep '^LOG_LEVEL=' /etc/dms-settings | cut -d "'" -f 2

View File

@ -131,9 +131,9 @@ function _reload_postfix() {
# 1. No first and second argument is supplied
# 2. The second argument is a path to a file that does not exist
function _replace_by_env_in_file() {
if [[ -z ${1+set} ]]; then
if [[ -z ${1:-} ]]; then
_dms_panic__invalid_value 'first argument unset' 'utils.sh:_replace_by_env_in_file'
elif [[ -z ${2+set} ]]; then
elif [[ -z ${2:-} ]]; then
_dms_panic__invalid_value 'second argument unset' 'utils.sh:_replace_by_env_in_file'
elif [[ ! -f ${2} ]]; then
_dms_panic__invalid_value "file '${2}' does not exist" 'utils.sh:_replace_by_env_in_file'

View File

@ -181,6 +181,9 @@ if [[ -f /CONTAINER_START ]]; then
# We cannot skip all setup routines because some need to run _after_
# the initial setup (and hence, they cannot be moved to the check stack).
_setup_directory_and_file_permissions
# shellcheck source=./startup/setup.d/mail_state.sh
source /usr/local/bin/setup.d/mail_state.sh
_setup_adjust_state_permissions
else
_setup

View File

@ -144,5 +144,5 @@ function __log_fixes() {
# Volume permissions should be corrected:
# https://github.com/docker-mailserver/docker-mailserver-helm/issues/137
chmod 755 /var/log/mail/
chmod 640 /var/log/mail/*
find /var/log/mail/ -type f -exec chmod 640 {} +
}

View File

@ -23,7 +23,11 @@ function _setup_opendkim() {
# check if any keys are available
if [[ -e /tmp/docker-mailserver/opendkim/KeyTable ]]; then
cp -a /tmp/docker-mailserver/opendkim/* /etc/opendkim/
_log 'trace' "DKIM keys added for: $(find /etc/opendkim/keys/ -maxdepth 1 -type f -printf '%f ')"
local DKIM_DOMAINS
DKIM_DOMAINS=$(find /etc/opendkim/keys/ -maxdepth 1 -type f -printf '%f ')
_log 'trace' "DKIM keys added for: ${DKIM_DOMAINS}"
chown -R opendkim:opendkim /etc/opendkim/
chmod -R 0700 /etc/opendkim/keys/
else

View File

@ -95,6 +95,11 @@ function _setup_save_states() {
function _setup_adjust_state_permissions() {
[[ ! -d ${DMS_STATE_DIR} ]] && return 0
# Parent directories must have executable bit set to descend the file tree for access,
# as each service running as a non-root user requires this to access their state directory,
# `/var/mail-state` must allow all users `+x`:
chmod +x "${DMS_STATE_DIR}"
# This ensures the user and group of the files from the external mount have their
# numeric ID values in sync. New releases where the installed packages order changes
# can change the values in the Docker image, causing an ownership mismatch.

View File

@ -93,13 +93,19 @@ EOF
function _setup_postfix_late() {
_log 'debug' 'Configuring Postfix (late setup)'
# These two config files are `access` database tables managed via `setup email restrict`:
# NOTE: Prepends to existing restrictions, thus has priority over other permit/reject policies that follow.
# https://www.postfix.org/postconf.5.html#smtpd_sender_restrictions
# https://www.postfix.org/access.5.html
__postfix__log 'trace' 'Configuring user access'
if [[ -f /tmp/docker-mailserver/postfix-send-access.cf ]]; then
sed -i -E 's|(smtpd_sender_restrictions =)|\1 check_sender_access texthash:/tmp/docker-mailserver/postfix-send-access.cf,|' /etc/postfix/main.cf
# Prefer to prepend to our specialized variant instead:
# https://github.com/docker-mailserver/docker-mailserver/pull/4379
sed -i -E 's|^(dms_smtpd_sender_restrictions =)|\1 check_sender_access texthash:/tmp/docker-mailserver/postfix-send-access.cf,|' /etc/postfix/main.cf
fi
if [[ -f /tmp/docker-mailserver/postfix-receive-access.cf ]]; then
sed -i -E 's|(smtpd_recipient_restrictions =)|\1 check_recipient_access texthash:/tmp/docker-mailserver/postfix-receive-access.cf,|' /etc/postfix/main.cf
sed -i -E 's|^(smtpd_recipient_restrictions =)|\1 check_recipient_access texthash:/tmp/docker-mailserver/postfix-receive-access.cf,|' /etc/postfix/main.cf
fi
__postfix__log 'trace' 'Configuring relay host'

View File

@ -56,7 +56,7 @@ function __handle_container_name() {
if [[ -n ${1:-} ]] && [[ ${1:-} =~ ^dms-test_ ]]; then
printf '%s' "${1}"
return 0
elif [[ -n ${CONTAINER_NAME+set} ]]; then
elif [[ -n ${CONTAINER_NAME:-} ]]; then
printf '%s' "${CONTAINER_NAME}"
return 0
else

View File

@ -17,7 +17,7 @@
# This function is internal and should not be used in tests.
function __initialize_variables() {
function __check_if_set() {
if [[ ${!1+set} != 'set' ]]; then
if [[ -z ${!1:-} ]]; then
echo "ERROR: (helper/setup.sh) '${1:?No variable name given to __check_if_set}' is not set" >&2
exit 1
fi

View File

@ -234,8 +234,9 @@ function _should_have_correct_mail_headers() {
# 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}>"
assert_line --index 14 'X-MS-Reactions: disallow'
assert_line --index 15 --partial 'Message-Id:'
assert_line --index 15 --partial "@${EXPECTED_FQDN}>"
# Mail contents example:
#

View File

@ -62,7 +62,7 @@ function teardown() { _default_teardown ; }
__init_container_without_waiting
__should_generate_dkim_key 6
__should_generate_dkim_key 7
__assert_outputs_common_dkim_logs
__should_have_tables_trustedhosts_for_domain
@ -78,7 +78,7 @@ function teardown() { _default_teardown ; }
# Only mount single config file (postfix-virtual.cf):
__init_container_without_waiting "${PWD}/test/config/postfix-virtual.cf:/tmp/docker-mailserver/postfix-virtual.cf:ro"
__should_generate_dkim_key 5
__should_generate_dkim_key 6
__assert_outputs_common_dkim_logs
__should_have_tables_trustedhosts_for_domain
@ -95,7 +95,7 @@ function teardown() { _default_teardown ; }
# Only mount single config file (postfix-accounts.cf):
__init_container_without_waiting "${PWD}/test/config/postfix-accounts.cf:/tmp/docker-mailserver/postfix-accounts.cf:ro"
__should_generate_dkim_key 5
__should_generate_dkim_key 6
__assert_outputs_common_dkim_logs
__should_have_tables_trustedhosts_for_domain
@ -113,7 +113,7 @@ function teardown() { _default_teardown ; }
__init_container_without_waiting '/tmp/docker-mailserver'
# generate first key (with a custom selector)
__should_generate_dkim_key 4 '1024' 'domain1.tld' 'mailer'
__should_generate_dkim_key 5 '1024' 'domain1.tld' 'mailer'
__assert_outputs_common_dkim_logs
# generate two additional keys different to the previous one
__should_generate_dkim_key 2 '1024' 'domain2.tld,domain3.tld'
@ -183,15 +183,15 @@ function __assert_logged_dkim_creation() {
function __assert_outputs_common_dkim_logs() {
refute_output --partial 'No entries found, no keys to make'
assert_output --partial 'Creating DKIM KeyTable'
assert_output --partial 'Creating DKIM SigningTable'
assert_output --partial 'Creating DKIM TrustedHosts'
assert_output --partial "Creating OpenDKIM config '/tmp/docker-mailserver/opendkim/KeyTable'"
assert_output --partial "Creating OpenDKIM config '/tmp/docker-mailserver/opendkim/SigningTable'"
assert_output --partial "Creating OpenDKIM config '/tmp/docker-mailserver/opendkim/TrustedHosts'"
}
function __should_support_creating_key_of_size() {
local EXPECTED_KEYSIZE=${1:-}
__should_generate_dkim_key 6 "${EXPECTED_KEYSIZE}"
__should_generate_dkim_key 7 "${EXPECTED_KEYSIZE}"
__assert_outputs_common_dkim_logs
__assert_logged_dkim_creation 'localdomain2.com'
__assert_logged_dkim_creation 'localhost.localdomain'