Compare commits

..

6 Commits

Author SHA1 Message Date
Brennan Kinney 202c0102fd
ci: `main => master` branch refs correction for `uses` 2024-11-18 09:08:43 +13:00
Brennan Kinney a0445bbe7c
chore: Add entry to `CHANGELOG.md` 2024-11-15 14:17:57 +13:00
Brennan Kinney d6540de00d
lint: CRLF => LF (`github.dev` new file mistake) 2024-11-15 14:11:23 +13:00
Brennan Kinney c63e31c92f
lint: Add final newline 2024-11-15 13:58:00 +13:00
Brennan Kinney 824fac9a4f
ci(docs-preview): Checkout PR branch 2024-11-15 13:38:47 +13:00
Brennan Kinney 1d62265f53
ci(docs-preview): Refactor to `pull_request_target` 2024-11-15 13:36:47 +13:00
57 changed files with 772 additions and 1152 deletions

View File

@ -1,2 +1,3 @@
* *
!target !target
!VERSION

View File

@ -67,3 +67,10 @@ body:
- This field expects only plain text (_rendered as a fenced code block_). - This field expects only plain text (_rendered as a fenced code block_).
- You can enable debug output by setting the environment variable `LOG_LEVEL` to `debug` or `trace`. - You can enable debug output by setting the environment variable `LOG_LEVEL` to `debug` or `trace`.
render: Text render: Text
- type: input
id: form-improvements
attributes:
label: Improvements to this form?
description: If you have criticism or general feedback about this issue form, feel free to tell us so we can enhance the experience for everyone.
validations:
required: false

View File

@ -1,96 +1,45 @@
name: 'Documentation (Deploy)' name: 'Docs Preview (Deploy)'
on: on:
# This workflow runs off the primary branch which provides access to the `secrets` context: workflow_call:
workflow_run: inputs:
workflows: ['Documentation (PR)'] preview-context:
types: description: 'Preview Metadata (JSON)'
- completed required: true
type: string
secrets:
NETLIFY_AUTH_TOKEN:
required: true
NETLIFY_SITE_ID:
required: true
env:
BUILD_DIR: ${{ fromJSON( inputs.preview-context ).build_dir }}
# PR head SHA (latest commit):
PR_HEADSHA: ${{ fromJSON( inputs.preview-context ).pull_request.head_sha }}
PR_NUMBER: ${{ fromJSON( inputs.preview-context ).pull_request.number }}
# Deploy URL preview prefix (the site name for the prefix is managed at Netlify):
PREVIEW_SITE_PREFIX: ${{ fromJSON( inputs.preview-context ).netlify.deploy_prefix }}
permissions: permissions:
# Required by `actions/download-artifact`: # Required by `marocchino/sticky-pull-request-comment`:
actions: read
# Required by `set-pr-context`:
contents: read
# Required by `marocchino/sticky-pull-request-comment` (write) + `set-pr-context` (read):
pull-requests: write pull-requests: write
# Required by `myrotvorets/set-commit-status-action`:
statuses: write
jobs: jobs:
# NOTE: This is handled as pre-requisite job to minimize the noise from acquiring these two outputs needed for `deploy-preview` ENV:
pr-context:
name: 'Acquire PR Context'
runs-on: ubuntu-24.04
outputs:
PR_HEADSHA: ${{ steps.set-pr-context.outputs.head-sha }}
PR_NUMBER: ${{ steps.set-pr-context.outputs.number }}
if: ${{ github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.event == 'pull_request' }}
steps:
- name: 'Get PR context'
id: set-pr-context
env:
# Token is required for the GH CLI:
GH_TOKEN: ${{ github.token }}
# Best practice for scripts is to reference via ENV at runtime. Avoid using GHA context expressions in the script content directly:
# https://github.com/docker-mailserver/docker-mailserver/pull/4247#discussion_r1827067475
PR_TARGET_REPO: ${{ github.repository }}
# If the PR is from a fork, prefix it with `<owner-login>:`, otherwise only the PR branch name is relevant:
PR_BRANCH: |-
${{
(github.event.workflow_run.head_repository.owner.login != github.event.workflow_run.repository.owner.login)
&& format('{0}:{1}', github.event.workflow_run.head_repository.owner.login, github.event.workflow_run.head_branch)
|| github.event.workflow_run.head_branch
}}
# Use the GH CLI to query the PR branch, which provides the PR number and head SHA to assign as outputs:
# (`--jq` formats JSON to `key=value` pairs and renames `headRefOid` to `head-sha`)
run: |
gh pr view --repo "${PR_TARGET_REPO}" "${PR_BRANCH}" \
--json 'number,headRefOid' \
--jq '"number=\(.number)\nhead-sha=\(.headRefOid)"' \
>> "${GITHUB_OUTPUT}"
deploy-preview: deploy-preview:
name: 'Deploy Preview' name: 'Deploy Preview'
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
needs: [pr-context]
env:
# NOTE: Keep this in sync with the equivalent ENV in `docs-preview-prepare.yml`:
BUILD_DIR: docs/site/
# PR head SHA (latest commit):
PR_HEADSHA: ${{ needs.pr-context.outputs.PR_HEADSHA }}
PR_NUMBER: ${{ needs.pr-context.outputs.PR_NUMBER }}
# Deploy URL preview prefix (the site name for this prefix is managed at Netlify):
PREVIEW_SITE_PREFIX: pullrequest-${{ needs.pr-context.outputs.PR_NUMBER }}
steps: steps:
- name: 'Retrieve and extract the built docs preview' - name: 'Retrieve and extract the built docs preview'
uses: actions/download-artifact@v4 uses: actions/download-artifact@v4
with: with:
name: preview-build name: preview-build
path: ${{ env.BUILD_DIR }} path: ${{ env.BUILD_DIR }}
# These are needed due this approach relying on `workflow_run`, so that it can access the build artifact:
# (uploaded from the associated `docs-preview-prepare.yml` workflow run)
github-token: ${{ secrets.GITHUB_TOKEN }}
run-id: ${{ github.event.workflow_run.id }}
# ==================== # # ==================== #
# Deploy preview build # # Deploy preview build #
# ==================== # # ==================== #
# Manage workflow deployment status (Part 1/2):
# NOTE:
# - `workflow_run` trigger does not appear on the PR/commit checks status, only the initial prepare workflow triggered.
# This adds our own status check for this 2nd half of the workflow starting as `pending`, followed by `success` / `failure` at the end.
# - `enable-commit-status` from `nwtgck/actions-netlify` would have handled this,
# but the context `github.sha` that action tries to use references the primary branch commit that this workflow runs from, not the relevant PR commit.
- name: 'Commit Status (1/2) - Set Workflow Status as Pending'
uses: myrotvorets/set-commit-status-action@v2.0.1
with:
token: ${{ secrets.GITHUB_TOKEN }}
status: pending
sha: ${{ env.PR_HEADSHA }}
context: 'Deploy Preview (pull_request => workflow_run)'
- name: 'Send preview build to Netlify' - name: 'Send preview build to Netlify'
uses: nwtgck/actions-netlify@v3.0 uses: nwtgck/actions-netlify@v3.0
id: preview-netlify id: preview-netlify
@ -148,19 +97,3 @@ jobs:
[Documentation preview for this PR](${{ steps.preview-netlify.outputs.deploy-url }}) is ready! :tada: [Documentation preview for this PR](${{ steps.preview-netlify.outputs.deploy-url }}) is ready! :tada:
Built with commit: ${{ env.PR_HEADSHA }} Built with commit: ${{ env.PR_HEADSHA }}
# Manage workflow deployment status (Part 2/2):
- name: 'Commit Status (2/2) - Update deployment status'
uses: myrotvorets/set-commit-status-action@v2.0.1
# Always run this step regardless of the job failing early:
if: ${{ always() }}
# Custom status descriptions:
env:
DEPLOY_SUCCESS: Successfully deployed preview.
DEPLOY_FAILURE: Failed to deploy preview.
with:
token: ${{ secrets.GITHUB_TOKEN }}
status: ${{ job.status == 'success' && 'success' || 'failure' }}
sha: ${{ env.PR_HEADSHA }}
context: 'Deploy Preview (pull_request => workflow_run)'
description: ${{ job.status == 'success' && env.DEPLOY_SUCCESS || env.DEPLOY_FAILURE }}

View File

@ -1,28 +1,23 @@
name: 'Documentation (PR)' name: 'Docs Preview (Build)'
on: on:
pull_request: workflow_call:
paths: inputs:
- 'docs/**' preview-context:
- '.github/workflows/scripts/docs/build-docs.sh' description: 'Preview Metadata (JSON)'
- '.github/workflows/docs-preview-prepare.yml' required: true
type: string
# If this workflow is triggered while already running for the PR, cancel any earlier running instances:
# Instances of the 2nd phase of this workflow (via `workflow_run`) lack any concurrency limits due to added complexity.
concurrency:
group: deploypreview-pullrequest-${{ github.event.pull_request.number }}
cancel-in-progress: true
env: env:
# Build output directory (created by the mkdocs-material container, keep this in sync with `build-docs.sh`): # Build output directory (created by the mkdocs-material container, keep this in sync with `build-docs.sh`):
BUILD_DIR: docs/site/ BUILD_DIR: ${{ fromJSON( inputs.preview-context ).build_dir }}
# These two ensure git checkout of PR branch:
PR_REF: ${{ fromJSON( inputs.preview-context ).pull_request.head_sha }}
PR_REPO: ${{ fromJSON( inputs.preview-context ).pull_request.head_repo }}
# These two are only needed to construct `PREVIEW_URL`: # These two are only needed to construct `PREVIEW_URL`:
PREVIEW_SITE_NAME: dms-doc-previews PREVIEW_SITE_NAME: ${{ fromJSON( inputs.preview-context ).netlify.site_name }}
PREVIEW_SITE_PREFIX: pullrequest-${{ github.event.pull_request.number }} PREVIEW_SITE_PREFIX: ${{ fromJSON( inputs.preview-context ).netlify.deploy_prefix }}
# `pull_request` workflow is unreliable alone: Non-collaborator contributions lack access to secrets for security reasons.
# A separate workflow (docs-preview-deploy.yml) handles the deploy after the potentially untrusted code is first run in this workflow.
# See: https://securitylab.github.com/research/github-actions-preventing-pwn-requests/
permissions: permissions:
# Required by `actions/checkout` for git checkout: # Required by `actions/checkout` for git checkout:
contents: read contents: read
@ -33,11 +28,21 @@ jobs:
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
with:
# As `on: pull_request_target` runs this workflow from the default branch of our repo,
# Adjust to checkout the correct PR branch (including repo to support PRs from forks):
ref: ${{ env.PR_REF }}
repository: ${{ env.PR_REPO }}
# Prevent `secrets.GITHUB_TOKEN` from being stored in a `.git/config` file, which could otherwise
# be compromised when executing untrusted code from a PR (as is done in the build step below).
persist-credentials: false
# ================== # # ================== #
# Build docs preview # # Build docs preview #
# ================== # # ================== #
# CAUTION: Security risk due to executing `build-docs.sh` (which a PR could modify to be malicious):
# Care must be taken to run this workflow as if it were in an untrusted context (like the `on: pull_request` event trigger would).
- name: 'Build with mkdocs-material via Docker' - name: 'Build with mkdocs-material via Docker'
working-directory: docs/ working-directory: docs/
env: env:

89
.github/workflows/docs-preview.yml vendored Normal file
View File

@ -0,0 +1,89 @@
name: 'Documentation (Preview)'
# INFO:
# This workflow automates generating previews of our docs for PRs.
# For security reasons, it is necessary to split the workflow into two separate jobs to manage trust safely.
# MAINTAINERS:
# - This set of `pull_request_target` + `workflow_call` workflows replaces the prior `pull_request` (untrusted) + `workflow_run` (trusted) workflows approach
# due to the need for acquiring PR metadata (Head SHA + PR number), which the `workflow_run` event context cannot provide when PRs are from forks.
# - Please be mindful of the risks when maintaining this workflow:
# https://securitylab.github.com/research/github-actions-preventing-pwn-requests/
on:
pull_request_target:
paths:
- 'docs/**'
- '.github/workflows/scripts/docs/build-docs.sh'
# If this workflow is triggered while an earlier instance is already running for the PR,
# cancel that instance in favor of this newly triggered run:
concurrency:
group: deploypreview-pullrequest-${{ github.event.pull_request.number }}
cancel-in-progress: true
# Common inputs shared between the two workflow jobs (`preview` + `deploy`).
# Composed as JSON to pass as a single input which each called job will map into separate ENV for use.
env:
PREVIEW_CONTEXT: |
{
"build_dir": "docs/site/",
"netlify": {
"site_name": "dms-doc-previews",
"deploy_prefix": "pullrequest-${{ github.event.pull_request.number }}"
},
"pull_request": {
"head_repo": "${{ github.event.pull_request.head.repo.full_name }}",
"head_sha": "${{ github.event.pull_request.head.sha }}",
"number": "${{ github.event.pull_request.number }}"
}
}
# Grant `secrets.GITHUB_TOKEN` only the minimum permissions needed by actions to function:
# https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/controlling-permissions-for-github_token
# NOTE: See the associated `preview` and `deploy` workflows called for when these permissions are needed.
permissions:
contents: read
pull-requests: write
# NOTE: Reusable workflows (`workflow_call`) default to empty secrets.
# Avoid using `secrets: inherit` in favor of passing only the required secrets to the job.
jobs:
# WORKAROUND:
# The alternative is to copy/paste the JSON value to the `preview-context` input of each job.
#
# PROBLEM:
# - `<job-id>.with` restricts available contexts to only `github` and `needs`, it's not possible to use `env` context:
# https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions#jobsjob_idwithinput_id
# - Likewise, workflows presently lack support for YAML anchors:
# https://github.com/actions/runner/issues/1182
create-context:
name: 'Create Context'
runs-on: ubuntu-24.04
outputs:
preview-context: ${{ steps.set-preview-context.outputs.preview-context }}
steps:
- id: set-preview-context
# The output value must be a single-line; use `jq` to convert the JSON to minified:
# NOTE: YAML `>-` does not help fold this multi-line content due to use of indentation:
# https://github.com/orgs/community/discussions/26105#discussioncomment-3250413
run: echo "preview-context=$(jq --compact-output <<< "${PREVIEW_CONTEXT}")" >> "${GITHUB_OUTPUT}"
# The `prepare` job is for running steps in an untrusted context (necessary to build the docs):
# CAUTION: This runs a build script which the PR could modify for malicious purposes.
prepare:
needs: [create-context]
uses: docker-mailserver/docker-mailserver/.github/workflows/docs-preview-prepare.yml@master
with:
preview-context: ${{ needs.create-context.outputs.preview-context }}
# The `deploy` job is for running the remaining steps in a trusted context after building the PR branch:
# CAUTION: Do not execute any content from untrusted sources (the PR branch or the retrieved artifact from the `prepare` job)
deploy:
needs: [create-context, prepare]
uses: docker-mailserver/docker-mailserver/.github/workflows/docs-preview-deploy.yml@master
secrets:
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }}
with:
preview-context: ${{ needs.create-context.outputs.preview-context }}

View File

@ -32,7 +32,7 @@ jobs:
with: with:
submodules: recursive submodules: recursive
# Can potentially be replaced by: `${{ hashFiles('target/**', 'Dockerfile') }}` # Can potentially be replaced by: `${{ hashFiles('target/**', 'Dockerfile', 'VERSION') }}`
# Must not be affected by file metadata changes and have a consistent sort order: # Must not be affected by file metadata changes and have a consistent sort order:
# https://docs.github.com/en/actions/learn-github-actions/expressions#hashfiles # https://docs.github.com/en/actions/learn-github-actions/expressions#hashfiles
# Keying by the relevant build context is more re-usable than a commit SHA. # Keying by the relevant build context is more re-usable than a commit SHA.
@ -40,7 +40,10 @@ jobs:
id: derive-image-cache-key id: derive-image-cache-key
shell: bash shell: bash
run: | run: |
ADDITIONAL_FILES=( 'Dockerfile' ) ADDITIONAL_FILES=(
'Dockerfile'
'VERSION'
)
# Recursively collect file paths from `target/` and pipe a list of # Recursively collect file paths from `target/` and pipe a list of
# checksums to be sorted (by hash value) and finally generate a checksum # checksums to be sorted (by hash value) and finally generate a checksum
@ -71,16 +74,16 @@ jobs:
cache-buildx- cache-buildx-
- name: 'Set up QEMU' - name: 'Set up QEMU'
uses: docker/setup-qemu-action@v3.6.0 uses: docker/setup-qemu-action@v3.2.0
with: with:
platforms: arm64 platforms: arm64
- name: 'Set up Docker Buildx' - name: 'Set up Docker Buildx'
uses: docker/setup-buildx-action@v3.10.0 uses: docker/setup-buildx-action@v3.7.1
# NOTE: AMD64 can build within 2 minutes # NOTE: AMD64 can build within 2 minutes
- name: 'Build images' - name: 'Build images'
uses: docker/build-push-action@v6.15.0 uses: docker/build-push-action@v6.9.0
with: with:
context: . context: .
# Build at least the AMD64 image (which runs against the test suite). # Build at least the AMD64 image (which runs against the test suite).

View File

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

View File

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

View File

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

View File

@ -11,7 +11,7 @@ docker run \
--user "$(id -u):$(id -g)" \ --user "$(id -u):$(id -g)" \
--volume "./:/docs" \ --volume "./:/docs" \
--name "build-docs" \ --name "build-docs" \
squidfunk/mkdocs-material:9.6 build --strict squidfunk/mkdocs-material:9.5 build --strict
# Remove unnecessary build artifacts: https://github.com/squidfunk/mkdocs-material/issues/2519 # Remove unnecessary build artifacts: https://github.com/squidfunk/mkdocs-material/issues/2519
# site/ is the build output folder. # site/ is the build output folder.

View File

@ -2,112 +2,64 @@
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). 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.2...HEAD) ## [Unreleased](https://github.com/docker-mailserver/docker-mailserver/compare/v14.0.0...HEAD)
> **Note**: Changes and additions listed here are contained in the `:edge` image tag. These changes may not be as stable as released changes. > **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 ### Breaking
- **saslauthd** mechanism support via ENV `SASLAUTHD_MECHANISMS` with `pam`, `shadow`, `mysql` values has been removed. Only `ldap` and `rimap` remain supported ([#4259](https://github.com/docker-mailserver/docker-mailserver/pull/4259))
- **getmail6** has been refactored: ([#4156](https://github.com/docker-mailserver/docker-mailserver/pull/4156)) - **getmail6** has been refactored: ([#4156](https://github.com/docker-mailserver/docker-mailserver/pull/4156))
- The [DMS config volume](https://docker-mailserver.github.io/docker-mailserver/v15.0/config/advanced/optional-config/#volumes) now has support for `getmailrc_general.cf` for overriding [common default settings](https://docker-mailserver.github.io/docker-mailserver/v15.0/config/advanced/mail-getmail/#common-options). If you previously mounted this config file directly to `/etc/getmailrc_general` you should switch to our config volume support. - The [DMS config volume](https://docker-mailserver.github.io/docker-mailserver/v15.0/config/advanced/optional-config/#volumes) now has support for `getmailrc_general.cf` for overriding [common default settings](https://docker-mailserver.github.io/docker-mailserver/v15.0/config/advanced/mail-getmail/#common-options). If you previously mounted this config file directly to `/etc/getmailrc_general` you should switch to our config volume support.
- IMAP/POP3 example configs added to our [`config-examples`](https://github.com/docker-mailserver/docker-mailserver/tree/v15.0.0/config-examples/getmail).
- ENV [`GETMAIL_POLL`](https://docker-mailserver.github.io/docker-mailserver/v15.0/config/environment/#getmail_poll) now supports values above 30 minutes.
- Added `getmail` as a new service for `supervisor` to manage, replacing cron for periodic polling.
- Generated getmail configuration files no longer set the `message_log` option. Instead of individual log files per config, the [default base settings DMS configures](https://github.com/docker-mailserver/docker-mailserver/tree/v15.0.0/target/getmail/getmailrc_general) now enables `message_log_syslog`. This aligns with how other services in DMS log to syslog where it is captured in `mail.log`. - Generated getmail configuration files no longer set the `message_log` option. Instead of individual log files per config, the [default base settings DMS configures](https://github.com/docker-mailserver/docker-mailserver/tree/v15.0.0/target/getmail/getmailrc_general) now enables `message_log_syslog`. This aligns with how other services in DMS log to syslog where it is captured in `mail.log`.
- Getmail configurations have changed location from the base of the DMS Config Volume, to the `getmail/` subdirectory. Any existing configurations **must be migrated manually.** - Getmail configurations have changed location from the base of the DMS Config Volume, to the `getmail/` subdirectory. Any existing configurations **must be migrated manually.**
- **DMS v14 mistakenly** relocated the _getmail state directory_ to the _DMS Config Volume_ as a `getmail/` subdirectory. - DMS v14 mistakenly relocated the getmail state directory to the DMS Config Volume as a `getmail/` subdirectory.
- This has been corrected to `/var/lib/getmail` (_if you have mounted a DMS State Volume to `/var/mail-state`, `/var/lib/getmail` will be symlinked to `/var/mail-state/lib-getmail`_). - This has been corrected to `/var/lib/getmail` (_if you have mounted a DMS State Volume to `/var/mail-state`, `/var/lib/getmail` will be symlinked to `/var/mail-state/lib-getmail`_).
- To preserve this state when upgrading to DMS v15, **you must manually migrate `getmail/` from the _DMS Config Volume_ to `lib-getmail/` in the _DMS State Volume_.** - To preserve this state when upgrading to DMS v15, **you must manually migrate `getmail/` from the _DMS Config Volume_ to `lib-getmail/` in the _DMS State Volume_.**
- `setup email delete <EMAIL ADDRESS>` now requires explicit confirmation if the mailbox data should be deleted ([#4365](https://github.com/docker-mailserver/docker-mailserver/pull/4365)).
- **Rspamd:** Removed deprecated file path check (_DMS config volume: `./rspamd-modules.conf` => `./rspamd/custom-commands.conf`_) ([#4373](https://github.com/docker-mailserver/docker-mailserver/pull/4373)) ### Security
- **Fail2ban:**
- Ensure a secure connection, when downloading the fail2ban package ([#4080](https://github.com/docker-mailserver/docker-mailserver/pull/4080))
### Added ### Added
- **Internal:** - **Internal:**
- Add password confirmation to several `setup` CLI subcommands ([#4072](https://github.com/docker-mailserver/docker-mailserver/pull/4072)) - Add password confirmation to several `setup` CLI subcommands ([#4072](https://github.com/docker-mailserver/docker-mailserver/pull/4072))
- Added a `debug getmail` subcommand to `setup` ([#4346](https://github.com/docker-mailserver/docker-mailserver/pull/4346))
### Updates ### Updates
- **Internal:** - **Fail2ban:**
- **Removed `VERSION` file** from the repo. Releases of DMS prior to v13 (Nov 2023) would check this to detect new releases ([#3677](https://github.com/docker-mailserver/docker-mailserver/issues/3677), [#4321](https://github.com/docker-mailserver/docker-mailserver/pull/4321)) - Bump version to [1.1.0](https://github.com/fail2ban/fail2ban/releases/tag/1.1.0). For more information, check the [changelog](https://github.com/fail2ban/fail2ban/blob/1.1.0/ChangeLog).
- During image build, ensure a secure connection when downloading the `fail2ban` package ([#4080](https://github.com/docker-mailserver/docker-mailserver/pull/4080))
- **Documentation:** - **Documentation:**
- Account Management and Authentication pages have been rewritten and better organized ([#4122](https://github.com/docker-mailserver/docker-mailserver/pull/4122)) - Rewritten and organized the pages for Account Management and Authentication ([#4122](https://github.com/docker-mailserver/docker-mailserver/pull/4122))
- Add a caveat for `DMS_VMAIL_UID` not being compatible with `0` / root ([#4143](https://github.com/docker-mailserver/docker-mailserver/pull/4143)) - Add caveat for `DMS_VMAIL_UID` not being compatible with `0` / root ([#4143](https://github.com/docker-mailserver/docker-mailserver/pull/4143))
- **Getmail:** ([#4156](https://github.com/docker-mailserver/docker-mailserver/pull/4156))
- Added `getmail` as a new service for `supervisor` to manage, replacing cron for periodic polling.
- IMAP/POP3 example configs added to our [`config-examples`](https://github.com/docker-mailserver/docker-mailserver/tree/v15.0.0/config-examples/getmail).
- ENV [`GETMAIL_POLL`](https://docker-mailserver.github.io/docker-mailserver/v15.0/config/environment/#getmail_poll) now supports values above 30 minutes.
- **Postfix:** - **Postfix:**
- By default opt-out from _Microsoft reactions_ for outbound mail ([#4120](https://github.com/docker-mailserver/docker-mailserver/pull/4120)) - Disable Microsoft reactions to outgoing mail ([#4120](https://github.com/docker-mailserver/docker-mailserver/pull/4120))
- **Rspamd:** - bumped `jaq` version from 1.3.0 to 1.6.0 ([#4190](https://github.com/docker-mailserver/docker-mailserver/pull/4190))
- Updated GTube settings and tests ([#4191](https://github.com/docker-mailserver/docker-mailserver/pull/4191)) - updated Rspamd GTube settings and tests ([#4191](https://github.com/docker-mailserver/docker-mailserver/pull/4191))
- Updated externally installed software ([#4357](https://github.com/docker-mailserver/docker-mailserver/pull/4357)):
- `DOVECOT_COMMUNITY_REPO=1` custom image build ARG now supports the latest Dovecot [`2.4.x`](https://github.com/dovecot/core/releases/tag/2.4.0) (_DMS provides Dovecot `2.3.19` by default_)
- Dovecot FTS Xapian module (`1.7.12` => [`1.9.0`](https://github.com/grosjo/fts-xapian/releases/tag/1.9))
- `jaq` (`1.3.0` => [`2.1.0`](https://github.com/01mf02/jaq/releases/tag/v2.1.0))
- Fail2Ban (`1.0.2-2` => [`1.1.0`](https://github.com/fail2ban/fail2ban/releases/tag/1.1.0)) ([#4045](https://github.com/docker-mailserver/docker-mailserver/pull/4045))
- Rspamd (`3.8.4` => [`3.11.0`](https://github.com/rspamd/rspamd/releases/tag/3.11.0)) - Implicitly upgraded during image build, as the third-party repo lacks version pinning support.
### Fixes ### Fixes
- **Dovecot:** - **Dovecot:**
- The logwatch `ignore.conf` now also excludes Xapian messages about pending documents ([#4060](https://github.com/docker-mailserver/docker-mailserver/pull/4060)) - Update logwatch `ignore.conf` to exclude Xapian messages about pending documents ([#4060](https://github.com/docker-mailserver/docker-mailserver/pull/4060))
- `dovecot-fts-xapian` plugin was updated, fixing a regression with indexing ([#4095](https://github.com/docker-mailserver/docker-mailserver/pull/4095)) - `dovecot-fts-xapian` plugin was updated to `1.7.13`, fixing a regression with indexing ([#4095](https://github.com/docker-mailserver/docker-mailserver/pull/4095))
- The "dummy account" workaround for _Dovecot Quota_ feature support no longer treats the alias as a regex when checking the Dovecot UserDB ([#4222](https://github.com/docker-mailserver/docker-mailserver/pull/4222)) - The Dovecot Quota support "dummy account" workaround no longer treats the alias as a regex when checking the Dovecot UserDB ([#4222](https://github.com/docker-mailserver/docker-mailserver/pull/4222))
- **LDAP:** - **LDAP:**
- Correctly apply a compatibility fix for OAuth2 introduced in DMS `v13.3.1` which had not been applied to the actual LDAP config changes ([#4175](https://github.com/docker-mailserver/docker-mailserver/pull/4175)) - A previous compatibility fix for OAuth2 in v13.3.1 had not applied the actual LDAP config changes. This has been corrected ([#4175](https://github.com/docker-mailserver/docker-mailserver/pull/4175))
- **Internal:** - **Internal:**
- The main `mail.log` (_which is piped to stdout via `tail`_) now correctly begins from the first log line of the active container run. Previously some daemon logs and potential warnings/errors were omitted ([#4146](https://github.com/docker-mailserver/docker-mailserver/pull/4146)) - The main `mail.log` which is piped to stdout via `tail` now correctly begins from the first log line of the active container run. Previously some daemon logs and potential warnings/errors were omitted. ([#4146](https://github.com/docker-mailserver/docker-mailserver/pull/4146))
- `start-mailserver.sh` removed unused `shopt -s inherit_errexit` ([#4161](https://github.com/docker-mailserver/docker-mailserver/pull/4161)) - Fixed a regression introduced in v14 where `postfix-main.cf` appended `stderr` output into `/etc/postfix/main.cf`, causing Postfix startup to fail ([#4147](https://github.com/docker-mailserver/docker-mailserver/pull/4147))
- Fixed a regression introduced in DMS v14 where `postfix-main.cf` appended `stderr` output into `/etc/postfix/main.cf`, causing Postfix startup to fail ([#4147](https://github.com/docker-mailserver/docker-mailserver/pull/4147)) - Unused `shopt -s inherit_errexit` removed from `start-mailserver.sh` ([#4161](https://github.com/docker-mailserver/docker-mailserver/pull/4161))
- Fixed a regression introduced in DMS v14 to better support running `start-mailserver.sh` with container restarts, which now only skip calling `_setup()` ([#4323](https://github.com/docker-mailserver/docker-mailserver/pull/4323#issuecomment-2629559254), [#4374](https://github.com/docker-mailserver/docker-mailserver/pull/4374))
- The command `swaks --help` is now functional ([#4282](https://github.com/docker-mailserver/docker-mailserver/pull/4282))
- **Rspamd:** - **Rspamd:**
- DKIM private key path checking is now performed only on paths that do not contain `$` ([#4201](https://github.com/docker-mailserver/docker-mailserver/pull/4201)) - DKIM private key path checking is now performed only on paths that do not contain "$" ([#4201](https://github.com/docker-mailserver/docker-mailserver/pull/4201))
### CI ### CI
- Removed `CONTRIBUTORS.md`, `.all-contributorsrc`, and workflow ([#4141](https://github.com/docker-mailserver/docker-mailserver/pull/4141)) - Workflow for `CONTRIBUTORS.md` updates removed. `CONTRIBUTORS.md` file and dependencies removed. ([#4141](https://github.com/docker-mailserver/docker-mailserver/pull/4141))
- Refactored the workflows to be more secure for generating documentation previews on PRs ([#4267](https://github.com/docker-mailserver/docker-mailserver/pull/4267), [#4264](https://github.com/docker-mailserver/docker-mailserver/pull/4264), [#4262](https://github.com/docker-mailserver/docker-mailserver/pull/4262), [#4247](https://github.com/docker-mailserver/docker-mailserver/pull/4247), [#4244](https://github.com/docker-mailserver/docker-mailserver/pull/4244)) - Refactored the workflows for generating documentation previews on PRs ([#4264](https://github.com/docker-mailserver/docker-mailserver/pull/4264), [#4262](https://github.com/docker-mailserver/docker-mailserver/pull/4262), [#4247](https://github.com/docker-mailserver/docker-mailserver/pull/4247), [#4244](https://github.com/docker-mailserver/docker-mailserver/pull/4244))
## [v14.0.0](https://github.com/docker-mailserver/docker-mailserver/releases/tag/v14.0.0) ## [v14.0.0](https://github.com/docker-mailserver/docker-mailserver/releases/tag/v14.0.0)

View File

@ -88,6 +88,16 @@ RUN dpkg -i /dovecot-fts-xapian-*.deb && rm /dovecot-fts-xapian-*.deb
COPY target/dovecot/*.inc target/dovecot/*.conf /etc/dovecot/conf.d/ COPY target/dovecot/*.inc target/dovecot/*.conf /etc/dovecot/conf.d/
COPY target/dovecot/dovecot-purge.cron /etc/cron.d/dovecot-purge.disabled COPY target/dovecot/dovecot-purge.cron /etc/cron.d/dovecot-purge.disabled
RUN chmod 0 /etc/cron.d/dovecot-purge.disabled RUN chmod 0 /etc/cron.d/dovecot-purge.disabled
WORKDIR /usr/share/dovecot
# hadolint ignore=SC2016,SC2086,SC2069
RUN <<EOF
sedfile -i -e 's/include_try \/usr\/share\/dovecot\/protocols\.d/include_try \/etc\/dovecot\/protocols\.d/g' /etc/dovecot/dovecot.conf
sedfile -i -e 's/#mail_plugins = \$mail_plugins/mail_plugins = \$mail_plugins sieve/g' /etc/dovecot/conf.d/15-lda.conf
sedfile -i -e 's/^.*lda_mailbox_autocreate.*/lda_mailbox_autocreate = yes/g' /etc/dovecot/conf.d/15-lda.conf
sedfile -i -e 's/^.*lda_mailbox_autosubscribe.*/lda_mailbox_autosubscribe = yes/g' /etc/dovecot/conf.d/15-lda.conf
sedfile -i -e 's/^.*postmaster_address.*/postmaster_address = '${POSTMASTER_ADDRESS:="postmaster@domain.com"}'/g' /etc/dovecot/conf.d/15-lda.conf
EOF
# ----------------------------------------------- # -----------------------------------------------
# --- Rspamd ------------------------------------ # --- Rspamd ------------------------------------

View File

@ -39,7 +39,7 @@ A production-ready fullstack but simple containerized mail server (SMTP, IMAP, L
## :package: Included Services ## :package: Included Services
- [Postfix](http://www.postfix.org) with SMTP or LDAP authentication and support for [extension delimiters](https://docker-mailserver.github.io/docker-mailserver/latest/config/account-management/overview/#aliases) - [Postfix](http://www.postfix.org) with SMTP or LDAP authentication and support for [extension delimiters](https://docker-mailserver.github.io/docker-mailserver/latest/config/account-management/overview/#aliases)
- [Dovecot](https://www.dovecot.org) with SASL, IMAP, POP3, LDAP, [basic Sieve support](https://docker-mailserver.github.io/docker-mailserver/latest/config/advanced/mail-sieve) and [quotas](https://docker-mailserver.github.io/docker-mailserver/latest/config/account-management/overview/#quotas) - [Dovecot](https://www.dovecot.org) with SASL, IMAP, POP3, LDAP, [basic Sieve support](https://docker-mailserver.github.io/docker-mailserver/latest/config/advanced/mail-sieve) and [quotas](https://docker-mailserver.github.io/docker-mailserver/latest/config/user-management/#quotas)
- [Rspamd](https://rspamd.com/) - [Rspamd](https://rspamd.com/)
- [Amavis](https://www.amavis.org/) - [Amavis](https://www.amavis.org/)
- [SpamAssassin](http://spamassassin.apache.org/) supporting custom rules - [SpamAssassin](http://spamassassin.apache.org/) supporting custom rules

1
VERSION Normal file
View File

@ -0,0 +1 @@
14.0.0

View File

@ -1,10 +1,10 @@
# Docs: https://docker-mailserver.github.io/docker-mailserver/v15.0/config/advanced/mail-fetchmail # Docs: https://docker-mailserver.github.io/docker-mailserver/v14.0/config/advanced/mail-fetchmail
# Additional context, with CLI commands for verification: # Additional context, with CLI commands for verification:
# https://github.com/orgs/docker-mailserver/discussions/3994#discussioncomment-9290570 # https://github.com/orgs/docker-mailserver/discussions/3994#discussioncomment-9290570
services: services:
dms-fetch: dms-fetch:
image: ghcr.io/docker-mailserver/docker-mailserver:latest # :15.0 image: ghcr.io/docker-mailserver/docker-mailserver:latest # :14.0
hostname: mail.example.test hostname: mail.example.test
environment: environment:
ENABLE_FETCHMAIL: 1 ENABLE_FETCHMAIL: 1
@ -26,7 +26,7 @@ services:
target: /tmp/docker-mailserver/fetchmail.cf target: /tmp/docker-mailserver/fetchmail.cf
dms-remote: dms-remote:
image: ghcr.io/docker-mailserver/docker-mailserver:latest # :15.0 image: ghcr.io/docker-mailserver/docker-mailserver:latest # :14.0
hostname: mail.remote.test hostname: mail.remote.test
environment: environment:
# Allows for us send a test mail easily by trusting any mail client run within this container (`swaks`): # Allows for us send a test mail easily by trusting any mail client run within this container (`swaks`):

View File

@ -1,11 +1,11 @@
# Docs: https://docker-mailserver.github.io/docker-mailserver/v15.0/config/advanced/mail-forwarding/relay-hosts/ # Docs: https://docker-mailserver.github.io/docker-mailserver/v14.0/config/advanced/mail-forwarding/relay-hosts/
# Additional context, with CLI commands for verification: # Additional context, with CLI commands for verification:
# https://github.com/docker-mailserver/docker-mailserver/issues/4136#issuecomment-2253693490 # https://github.com/docker-mailserver/docker-mailserver/issues/4136#issuecomment-2253693490
services: services:
# This would represent your actual DMS container: # This would represent your actual DMS container:
dms-sender: dms-sender:
image: mailserver/docker-mailserver:latest # :15.0 image: mailserver/docker-mailserver:latest # :14.0
hostname: mail.example.test hostname: mail.example.test
environment: environment:
# All outbound mail will be relayed through this host # All outbound mail will be relayed through this host
@ -37,17 +37,21 @@ services:
# Pretend this is your third-party relay service: # Pretend this is your third-party relay service:
dms-relay: dms-relay:
image: mailserver/docker-mailserver:latest # :15.0 image: mailserver/docker-mailserver:latest # :14.0
hostname: smtp.relay-service.test hostname: smtp.relay-service.test
environment: environment:
# WORKAROUND: Bypass security checks from the mail-client (dms-sender container) # WORKAROUND: Bypass security checks from the mail-client (dms-sender container)
# (avoids needing expected DNS records to run this example) # (avoids needing valid DNS for this example)
- PERMIT_DOCKER=connected-networks - PERMIT_DOCKER=connected-networks
# TLS is required when relaying to dms-relay via ports 587 / 465 # TLS is required when relaying to dms-relay via ports 587 / 465
# (dms-relay will then relay the mail to dms-destination over port 25) # (dms-relay will then relay the mail to dms-destination over port 25)
- SSL_TYPE=manual - SSL_TYPE=manual
- SSL_KEY_PATH=/tmp/tls/key.pem - SSL_KEY_PATH=/tmp/tls/key.pem
- SSL_CERT_PATH=/tmp/tls/cert.pem - SSL_CERT_PATH=/tmp/tls/cert.pem
# WORKAROUND: `links` is required due to lack of properly configured DNS.
# (resolves destination.test to the IP of the dms-destination container)
links:
- "dms-destination:destination.test"
configs: configs:
- source: dms-accounts-relay - source: dms-accounts-relay
target: /tmp/docker-mailserver/postfix-accounts.cf target: /tmp/docker-mailserver/postfix-accounts.cf
@ -58,16 +62,10 @@ services:
# Pretend this is another mail server that your target recipient belongs to (like Gmail): # Pretend this is another mail server that your target recipient belongs to (like Gmail):
dms-destination: dms-destination:
image: mailserver/docker-mailserver:latest # :15.0 image: mailserver/docker-mailserver:latest # :14.0
hostname: mail.destination.test hostname: mail.destination.test
# WORKAROUND: dms-relay must be able to resolve DNS for `@destination.test` to the IP of this container: # Same workaround for purposes of the example, with the target recipient provisioned to accept mail
# Normally a MX record would direct mail to the MTA (eg: `mail.destination.test`)
networks:
default:
aliases:
- destination.test
environment: environment:
# WORKAROUND: Same workaround as needed for dms-relay
- PERMIT_DOCKER=connected-networks - PERMIT_DOCKER=connected-networks
configs: configs:
- source: dms-accounts-destination - source: dms-accounts-destination
@ -77,13 +75,6 @@ services:
# NOTE: This feature requires Docker Compose v2.23.1 (Nov 2023) or newer: # NOTE: This feature requires Docker Compose v2.23.1 (Nov 2023) or newer:
# https://github.com/compose-spec/compose-spec/pull/446 # https://github.com/compose-spec/compose-spec/pull/446
configs: configs:
# `postfix-main.cf`, a single line change to make all outbound SMTP connections over implicit TLS instead of the default explicit TLS (StartTLS).
# NOTE: If you need to only selectively relay mail, you would need to instead adjust this on the relay service in `/etc/postfix/master.cf`,
# However DMS presently modifies this when using the DMS Relay Host feature support, which may override `postfix-master.cf` or `user-patches.sh` due to `check-for-changes.sh`.
dms-main:
content: |
smtp_tls_wrappermode=yes
# DMS expects an account to be configured to run, this example provides accounts already created. # DMS expects an account to be configured to run, this example provides accounts already created.
# Login credentials: # Login credentials:
# user: "john.doe@example.test" password: "secret" # user: "john.doe@example.test" password: "secret"
@ -103,6 +94,13 @@ configs:
content: | content: |
jane.doe@destination.test|{SHA512-CRYPT}$$6$$o65y1ZXC4ooOPLwZ$$7TF1nYowEtNJpH6BwJBgdj2pPAxaCvhIKQA6ww5zdHm/AA7aemY9eoHC91DOgYNaKj1HLxSeWNDdvrp6mbtUY. jane.doe@destination.test|{SHA512-CRYPT}$$6$$o65y1ZXC4ooOPLwZ$$7TF1nYowEtNJpH6BwJBgdj2pPAxaCvhIKQA6ww5zdHm/AA7aemY9eoHC91DOgYNaKj1HLxSeWNDdvrp6mbtUY.
# This is `postfix-main.cf`, single line change to make all outbound SMTP connections over port 465 instead of 25 (default)
# If you selectively relay mail, you would need to adjust this on the relay service in `/etc/postfix/master.cf`,
# However DMS presently modifies this when using the DMS Relay Host feature support, which may override `postfix-master.cf` or `user-patches.sh` due to `check-for-changes.sh`.
dms-main:
content: |
smtp_tls_wrappermode=yes
# TLS files: # TLS files:
# - Use an ECDSA cert that's been signed by a self-signed CA for TLS cert verification. # - Use an ECDSA cert that's been signed by a self-signed CA for TLS cert verification.
# - This cert is only valid for mail.example.test, mail.destination.test, smtp.relay-service.test # - This cert is only valid for mail.example.test, mail.destination.test, smtp.relay-service.test

View File

@ -151,6 +151,6 @@ We provide this support via two config files:
[wikipedia::smarthost]: https://en.wikipedia.org/wiki/Smart_host [wikipedia::smarthost]: https://en.wikipedia.org/wiki/Smart_host
[docs::env-relay]: ../../environment.md#relay-host [docs::env-relay]: ../../environment.md#relay-host
[dms-repo::helpers-relay]: https://github.com/docker-mailserver/docker-mailserver/blob/v15.0.0/target/scripts/helpers/relay.sh [dms-repo::helpers-relay]: https://github.com/docker-mailserver/docker-mailserver/blob/v14.0.0/target/scripts/helpers/relay.sh
[dms-gh::pr-3607]: https://github.com/docker-mailserver/docker-mailserver/issues/3607 [dms-gh::pr-3607]: https://github.com/docker-mailserver/docker-mailserver/issues/3607
[dms-gh::relay-example]: https://github.com/docker-mailserver/docker-mailserver/issues/3842#issuecomment-1913380639 [dms-gh::relay-example]: https://github.com/docker-mailserver/docker-mailserver/issues/3842#issuecomment-1913380639

View File

@ -108,11 +108,3 @@ It is possible to utilize the `getmail-gmail-xoauth-tokens` helper to provide au
[getmail-docs]: https://getmail6.org/configuration.html [getmail-docs]: https://getmail6.org/configuration.html
[getmail-docs-xoauth-12]: https://github.com/getmail6/getmail6/blob/1f95606156231f1e074ba62a9baa64f892a92ef8/docs/getmailrc-examples#L286 [getmail-docs-xoauth-12]: https://github.com/getmail6/getmail6/blob/1f95606156231f1e074ba62a9baa64f892a92ef8/docs/getmailrc-examples#L286
[getmail-docs-xoauth-13]: https://github.com/getmail6/getmail6/blob/1f95606156231f1e074ba62a9baa64f892a92ef8/docs/getmailrc-examples#L351 [getmail-docs-xoauth-13]: https://github.com/getmail6/getmail6/blob/1f95606156231f1e074ba62a9baa64f892a92ef8/docs/getmailrc-examples#L351
## Debugging
To debug your `getmail` configurations, run this `setup debug` command:
```sh
docker exec -it dms-container-name setup debug getmail
```

View File

@ -29,7 +29,7 @@ Cloudflare has written an [article about DKIM, DMARC and SPF][cloudflare-dkim-dm
When DKIM is enabled: When DKIM is enabled:
1. Inbound mail will verify any included DKIM signatures 1. Inbound mail will verify any included DKIM signatures
2. Outbound mail is signed (_when your sending domain has a configured DKIM key_) 2. Outbound mail is signed (_when you're sending domain has a configured DKIM key_)
DKIM requires a public/private key pair to enable **signing (_via private key_)** your outgoing mail, while the receiving end must query DNS to **verify (_via public key_)** that the signature is trustworthy. DKIM requires a public/private key pair to enable **signing (_via private key_)** your outgoing mail, while the receiving end must query DNS to **verify (_via public key_)** that the signature is trustworthy.
@ -54,7 +54,7 @@ You'll need to repeat this process if you add any new domains.
You should have: You should have:
- At least one [email account setup][docs-accounts] - At least one [email account setup][docs-accounts-add]
- Attached a [volume for config][docs-volumes-config] to persist the generated files to local storage - Attached a [volume for config][docs-volumes-config] to persist the generated files to local storage
!!! example "Creating DKIM Keys" !!! example "Creating DKIM Keys"

View File

@ -111,7 +111,7 @@ This could be from outdated software, or running a system that isn't able to pro
- **Container runtime:** Docker and Podman for example have subtle differences. DMS docs are primarily focused on Docker, but we try to document known issues where relevant. - **Container runtime:** Docker and Podman for example have subtle differences. DMS docs are primarily focused on Docker, but we try to document known issues where relevant.
- **Rootless containers:** Introduces additional differences in behavior or requirements: - **Rootless containers:** Introduces additional differences in behavior or requirements:
- cgroup v2 is required for supporting rootless containers. - cgroup v2 is required for supporting rootless containers.
- Differences such as for container networking which may further affect support for IPv6 and preserving the client IP (Remote address). Example with Docker rootless are [binding a port to a specific interface][docker-rootless-interface] and the choice of [port forwarding driver][docs::fail2ban::rootless-portdriver]. - Differences such as for container networking which may further affect support for IPv6 and preserving the client IP (Remote address). Example with Docker rootless are [binding a port to a specific interface][docker-rootless-interface] and the choice of [port forwarding driver][docs-rootless-portdriver].
[network::docker-userlandproxy]: https://github.com/moby/moby/issues/44721 [network::docker-userlandproxy]: https://github.com/moby/moby/issues/44721
[network::docker-nftables]: https://github.com/moby/moby/issues/26824 [network::docker-nftables]: https://github.com/moby/moby/issues/26824

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. 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). - **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 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. - 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.
##### ENABLE_SRS ##### ENABLE_SRS
@ -295,7 +295,7 @@ Customize the update check interval. Number + Suffix. Suffix must be 's' for sec
- sdbox => (experimental) uses Dovecot high-performance mailbox format, one file contains one message - sdbox => (experimental) uses Dovecot high-performance mailbox format, one file contains one message
- mdbox ==> (experimental) uses Dovecot high-performance mailbox format, multiple messages per file and multiple files per box - mdbox ==> (experimental) uses Dovecot high-performance mailbox format, multiple messages per file and multiple files per box
This option has been added in November 2019. Using other format than Maildir is considered as experimental in docker-mailserver and should only be used for testing purpose. For more details, please refer to [Dovecot Documentation](https://doc.dovecot.org/admin_manual/mailbox_formats/#mailbox-formats). This option has been added in November 2019. Using other format than Maildir is considered as experimental in docker-mailserver and should only be used for testing purpose. For more details, please refer to [Dovecot Documentation](https://wiki2.dovecot.org/MailboxFormat).
##### POSTFIX_REJECT_UNKNOWN_CLIENT_HOSTNAME ##### POSTFIX_REJECT_UNKNOWN_CLIENT_HOSTNAME
@ -910,26 +910,22 @@ Note: This postgrey setting needs `ENABLE_POSTGREY=1`
##### SASLAUTHD_MECHANISMS ##### SASLAUTHD_MECHANISMS
DMS only implements support for these mechanisms: - **empty** => pam
- `ldap` => authenticate against ldap server
- **`ldap`** => Authenticate against an LDAP server - `shadow` => authenticate against local user db
- `rimap` => Authenticate against an IMAP server - `mysql` => authenticate against mysql db
- `rimap` => authenticate against imap server
- NOTE: can be a list of mechanisms like pam ldap shadow
##### SASLAUTHD_MECH_OPTIONS ##### SASLAUTHD_MECH_OPTIONS
- **empty** => None - **empty** => None
- e.g. with SASLAUTHD_MECHANISMS rimap you need to specify the ip-address/servername of the imap server ==> xxx.xxx.xxx.xxx
!!! info
With `SASLAUTHD_MECHANISMS=rimap` you need to specify the ip-address / servername of the IMAP server, such as `SASLAUTHD_MECH_OPTIONS=127.0.0.1`.
##### SASLAUTHD_LDAP_SERVER ##### SASLAUTHD_LDAP_SERVER
- **empty** => Use the same value as `LDAP_SERVER_HOST` - **empty** => same as `LDAP_SERVER_HOST`
- Note: You must include the desired URI scheme (`ldap://`, `ldaps://`, `ldapi://`).
!!! note
You must include the desired URI scheme (`ldap://`, `ldaps://`, `ldapi://`).
##### SASLAUTHD_LDAP_START_TLS ##### SASLAUTHD_LDAP_START_TLS

View File

@ -14,48 +14,18 @@ hide:
## Configuration ## Configuration
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. !!! warning
!!! example 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`:
=== "Docker Compose" ```yaml
```yaml title="compose.yaml"
services:
mailserver:
environment:
- ENABLE_FAIL2BAN=1
cap_add: cap_add:
- NET_ADMIN - 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" !!! bug "Running Fail2Ban on Older Kernels"
DMS configures F2B to use [NFTables][network::nftables], not [IPTables (legacy)][network::iptables-legacy]. 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).
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 ### DMS Defaults

View File

@ -6,41 +6,9 @@ title: 'Security | Rspamd'
Rspamd is a ["fast, free and open-source spam filtering system"][rspamd-web]. DMS integrates Rspamd like any other service. We provide a basic but easy to maintain setup of Rspamd. Rspamd is a ["fast, free and open-source spam filtering system"][rspamd-web]. DMS integrates Rspamd like any other service. We provide a basic but easy to maintain setup of Rspamd.
If you want to take a look at the default configuration files for Rspamd that DMS adds, navigate to [`target/rspamd/` inside the repository][dms-repo::default-rspamd-configuration]. Please consult the [section "The Default Configuration"](#the-default-configuration) section down below for a written overview. If you want to take a look at the default configuration files for Rspamd that DMS packs, navigate to [`target/rspamd/` inside the repository][dms-repo::default-rspamd-configuration]. Please consult the [section "The Default Configuration"](#the-default-configuration) section down below for a written overview.
### Enable Rspamd ## Related Environment Variables
Rspamd is presently opt-in for DMS, but intended to become the default anti-spam service in a future release.
DMS offers two anti-spam solutions:
- Legacy (_Amavis, SpamAssassin, OpenDKIM, OpenDMARC_)
- Rspamd (_Provides equivalent features of software from the legacy solution_)
While you could configure Rspamd to only replace some of the legacy services, it is advised to only use Rspamd with the legacy services disabled.
!!! example "Switch to Rspamd"
To use Rspamd add the following ENV config changes:
```env
ENABLE_RSPAMD=1
# Rspamd replaces the functionality of all these anti-spam services, disable them:
ENABLE_OPENDKIM=0
ENABLE_OPENDMARC=0
ENABLE_POLICYD_SPF=0
ENABLE_AMAVIS=0
ENABLE_SPAMASSASSIN=0
# Greylisting is opt-in, if you had enabled Postgrey switch to the Rspamd equivalent:
ENABLE_POSTGREY=0
RSPAMD_GREYLISTING=1
# Optional: Add anti-virus support with ClamAV (compatible with Rspamd):
ENABLE_CLAMAV=1
```
!!! info "Relevant Environment Variables"
The following environment variables are related to Rspamd: The following environment variables are related to Rspamd:
@ -55,11 +23,17 @@ While you could configure Rspamd to only replace some of the legacy services, it
9. [`MOVE_SPAM_TO_JUNK`][docs::spam-to-junk] 9. [`MOVE_SPAM_TO_JUNK`][docs::spam-to-junk]
10. [`MARK_SPAM_AS_READ`](../environment.md#mark_spam_as_read) 10. [`MARK_SPAM_AS_READ`](../environment.md#mark_spam_as_read)
## Overview of Rspamd support With these variables, you can enable Rspamd itself, and you can enable / disable certain features related to Rspamd.
## The Default Configuration
### Other Anti-Spam-Services
DMS packs other anti-spam services, like SpamAssassin or Amavis, next to Rspamd. There exist services, like ClamAV (`ENABLE_CLAMAV`), that Rspamd can utilize to improve the scanning. Except for ClamAV, we recommend disabling **all other** anti-spam services when using Rspamd. The [basic configuration shown below](#a-very-basic-configuration) provides a good starting point.
### Mode of Operation ### Mode of Operation
!!! note "Attention" !!! tip "Attention"
Read this section carefully if you want to understand how Rspamd is integrated into DMS and how it works (on a surface level). Read this section carefully if you want to understand how Rspamd is integrated into DMS and how it works (on a surface level).
@ -105,16 +79,13 @@ DMS does not set a default password for the controller worker. You may want to d
When Rspamd is enabled, we implicitly also start an instance of Redis in the container: When Rspamd is enabled, we implicitly also start an instance of Redis in the container:
- Redis is configured to persist its data via RDB snapshots to disk in the directory `/var/lib/redis` (_or the [`/var/mail-state/`][docs::dms-volumes-state] volume when present_). - Redis is configured to persist its data via RDB snapshots to disk in the directory `/var/lib/redis` (_or the [`/var/mail-state/`][docs::dms-volumes-state] volume when present_).
- With the volume mount, the snapshot will restore the Redis data across container updates, and provide a way to keep a backup. - With the volume mount, the snapshot will restore the Redis data across container restarts, and provide a way to keep backup.
- Without a volume mount a containers internal state will persist across restarts until the container is recreated due to changes like ENV or upgrading the image for the container.
Redis uses `/etc/redis/redis.conf` for configuration: Redis uses `/etc/redis/redis.conf` for configuration:
- We adjust this file when enabling the internal Redis service. - We adjust this file when enabling the internal Redis service.
- If you have an external instance of Redis to use, the internal Redis service can be opt-out via setting the ENV [`ENABLE_RSPAMD_REDIS=0`][docs::env::enable-redis] (_link also details required changes to the DMS Rspamd config_). - If you have an external instance of Redis to use, the internal Redis service can be opt-out via setting the ENV [`ENABLE_RSPAMD_REDIS=0`][docs::env::enable-redis] (_link also details required changes to the DMS Rspamd config_).
If you are interested in using Valkey instead of Redis, please refer to [this guidance][gh-dms::guide::valkey].
### Web Interface ### Web Interface
Rspamd provides a [web interface][rspamd-docs::web-ui], which contains statistics and data Rspamd collects. The interface is enabled by default and reachable on port 11334. Rspamd provides a [web interface][rspamd-docs::web-ui], which contains statistics and data Rspamd collects. The interface is enabled by default and reachable on port 11334.
@ -125,7 +96,7 @@ To use the web interface you will need to configure a password, [otherwise you w
??? example "Set a custom password" ??? example "Set a custom password"
Add this line to [your Rspamd `custom-commands.conf` config](#with-the-help-of-a-custom-file) which sets the `password` option of the _controller worker_: Add this line to [your rspamd `custom-commands.conf` config](#with-the-help-of-a-custom-file) which sets the `password` option of the _controller worker_:
``` ```
set-option-for-controller password "your hashed password here" set-option-for-controller password "your hashed password here"
@ -137,13 +108,9 @@ To use the web interface you will need to configure a password, [otherwise you w
docker exec -it <CONTAINER_NAME> rspamadm pw docker exec -it <CONTAINER_NAME> rspamadm pw
``` ```
---
**Related:** A minimal Rspamd `compose.yaml` [example with a reverse-proxy for web access][gh-dms::guide::rspamd-web].
### DNS ### DNS
DMS does not supply custom values for DNS servers (to Rspamd). If you need to use custom DNS servers, which could be required when using [DNS-based deny/allowlists](#rbls-real-time-blacklists-dnsbls-dns-based-blacklists), you need to adjust [`options.inc`][rspamd-docs::config::global] yourself. Make sure to also read our [FAQ page on DNS servers][docs::faq::dns-servers]. DMS does not supply custom values for DNS servers (to Rspamd). If you need to use custom DNS servers, which could be required when using [DNS-based deny/allowlists](#rbls-real-time-blacklists-dnsbls-dns-based-blacklists), you need to adjust [`options.inc`][rspamd-docs::basic-options] yourself. Make sure to also read our [FAQ page on DNS servers][docs::faq::dns-servers].
!!! warning !!! warning
@ -175,7 +142,7 @@ You can choose to enable ClamAV, and Rspamd will then use it to check for viruse
#### RBLs (Real-time Blacklists) / DNSBLs (DNS-based Blacklists) #### RBLs (Real-time Blacklists) / DNSBLs (DNS-based Blacklists)
The [RBL module][rspamd-docs::modules::rbl] is enabled by default. As a consequence, Rspamd will perform DNS lookups to various blacklists. Whether an RBL or a DNSBL is queried depends on where the domain name was obtained: RBL servers are queried with IP addresses extracted from message headers, DNSBL server are queried with domains and IP addresses extracted from the message body ([source][www::rbl-vs-dnsbl]). The [RBL module][rspamd-docs::modules::rbl] is enabled by default. As a consequence, Rspamd will perform DNS lookups to various blacklists. Whether an RBL or a DNSBL is queried depends on where the domain name was obtained: RBL servers are queried with IP addresses extracted from message headers, DNSBL server are queried with domains and IP addresses extracted from the message body \[[source][www::rbl-vs-dnsbl]\].
!!! danger "Rspamd and DNS Block Lists" !!! danger "Rspamd and DNS Block Lists"
@ -185,141 +152,106 @@ The [RBL module][rspamd-docs::modules::rbl] is enabled by default. As a conseque
## Providing Custom Settings & Overriding Settings ## Providing Custom Settings & Overriding Settings
!!! info "Rspamd config overriding precedence" DMS brings sane default settings for Rspamd. They are located at `/etc/rspamd/local.d/` inside the container (or `target/rspamd/local.d/` in the repository).
Rspamd has a layered approach for configuration with [`local.d` and `override.d` config directories][rspamd-docs::config-directories]. ### Manually
- DMS [extends the Rspamd default configs via `/etc/rspamd/local.d/`][dms-repo::default-rspamd-configuration]. !!! question "What is [`docker-data/dms/config/`][docs::dms-volumes-config]?"
- User config changes should be handled separately as overrides via the [DMS Config Volume][docs::dms-volumes-config] (`docker-data/dms/config/`) with either:
- `./rspamd/override.d/` - Config files placed here are copied to `/etc/rspamd/override.d/` during container startup.
- [`./rspamd/custom-commands.conf`](#with-the-help-of-a-custom-file) - Applied after copying any provided configs from `rspamd/override.d/` (DMS Config volume) to `/etc/rspamd/override.d/`.
!!! abstract "Reference docs for Rspamd config" If you want to overwrite the default settings or provide your 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.
- [Config Overview][rspamd-docs::config::overview], [Quickstart guide][rspamd-docs::config::quickstart], and [Config Syntax (UCL)][rspamd-docs::config::ucl-syntax] !!! question "What is the [`local.d` directory and how does it compare to `override.d`][rspamd-docs::config-directories]?"
- Global Options ([`options.inc`][rspamd-docs::config::global])
- [Workers][rspamd-docs::config::workers] ([`worker-controller.inc`][rspamd-docs::config::worker-controller], [`worker-proxy.inc`][rspamd-docs::config::worker-proxy])
- [Modules][rspamd-docs::modules] (_view each module page for their specific config options_)
!!! tip "View rendered config" !!! warning "Clashing Overrides"
`rspamadm configdump` will output the full rspamd configuration that is used should you need it for troubleshooting / inspection. 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.
- You can also see which modules are enabled / disabled via `rspamadm configdump --modules-state` ### With the Help of a Custom File
- Specific config sections like `dkim` or `worker` can also be used to filter the output to just those sections: `rspamadm configdump dkim worker`
- Use `--show-help` to include inline documentation for many settings.
### Using `custom-commands.conf` { #with-the-help-of-a-custom-file } DMS provides the ability to do simple adjustments to Rspamd modules with the help of a single file. Just place a file called `custom-commands.conf` into `docker-data/dms/config/rspamd/`. If this file is present, DMS will evaluate it. The structure is simple, as each line in the file looks like this:
For convenience DMS provides a single config file that will directly create or modify multiple configs at `/etc/rspamd/override.d/`. This is handled as the final rspamd configuration step during container startup.
DMS will apply this config when you provide `rspamd/custom-commands.conf` in your DMS Config volume. Configure it with directive lines as documented below.
!!! note "Only use this feature for `option = value` changes"
`custom-commands.conf` is only suitable for adding or replacing simple `option = value` settings for configs at `/etc/rspamd/override.d/`.
- New settings are appended to the associated config file.
- When replacing an existing setting in an override config, that setting may be any matching line (_allowing for nested scopes, instead of only top-level keys_).
Any changes involving more advanced [UCL config syntax][rspamd-docs::config::ucl-syntax] should instead add UCL config files directly to `rspamd/override.d/` (_in the DMS Config volume_).
!!! info "`custom-commands.conf` syntax"
There are 7 directives available to manage custom Rspamd configurations. Add these directive lines into `custom-commands.conf`, they will be processed sequentially.
**Directives:**
```txt ```txt
# For /etc/rspamd/override.d/{options.inc,worker-controller.inc,worker-proxy}.inc COMMAND ARGUMENT1 ARGUMENT2 ARGUMENT3
set-common-option <OPTION NAME> <OPTION VALUE>
set-option-for-controller <OPTION NAME> <OPTION VALUE>
set-option-for-proxy <OPTION NAME> <OPTION VALUE>
# For /etc/rspamd/override.d/<MODULE NAME>.conf
enable-module <MODULE NAME>
disable-module <MODULE NAME>
set-option-for-module <MODULE NAME> <OPTION NAME> <OPTION VALUE>
# For /etc/rspamd/override.d/<FILENAME>
add-line <FILENAME> <CONTENT>
``` ```
**Syntax:** where `COMMAND` can be:
- Blank lines are ok. 1. `disable-module`: disables the module with name `ARGUMENT1`
- `#` at the start of a line represents a comment for adding notes. 2. `enable-module`: explicitly enables the module with name `ARGUMENT1`
- `<OPTION VALUE>` and `<CONTENT>` will contain the remaining content of their line, any preceding inputs are delimited by white-space. 3. `set-option-for-module`: sets the value for option `ARGUMENT2` to `ARGUMENT3` inside module `ARGUMENT1`
4. `set-option-for-controller`: set the value of option `ARGUMENT1` to `ARGUMENT2` for the controller worker
5. `set-option-for-proxy`: set the value of option `ARGUMENT1` to `ARGUMENT2` for the proxy worker
6. `set-common-option`: set the option `ARGUMENT1` that [defines basic Rspamd behavior][rspamd-docs::basic-options] to value `ARGUMENT2`
7. `add-line`: this will add the complete line after `ARGUMENT1` (with all characters) to the file `/etc/rspamd/override.d/<ARGUMENT1>`
--- !!! example "An Example Is [Shown Down Below](#adjusting-and-extending-the-very-basic-configuration)"
??? note "`<MODULE NAME>` can also target non-module configs" !!! note "File Names & Extensions"
An example is the `statistics` module, which has config to import a separate file (`classifier-bayes.conf`) for easier overrides to this section of the module config. For command 1 - 3, we append the `.conf` suffix to the module name to get the correct file name automatically. For commands 4 - 6, the file name is fixed (you don't even need to provide it). For command 7, you will need to provide the whole file name (including the suffix) yourself!
??? example You can also have comments (the line starts with `#`) and blank lines in `custom-commands.conf` - they are properly handled and not evaluated.
```conf title="rspamd/custom-commands.conf" !!! tip "Adjusting Modules This Way"
# If you're confident you've properly secured access to the rspamd web service/API (Default port: 11334)
# with your own auth layer (eg: reverse-proxy) you can bypass rspamd requiring credentials: These simple commands are meant to give users the ability to _easily_ alter modules and their options. As a consequence, they are not powerful enough to enable multi-line adjustments. If you need to do something more complex, we advise to do that [manually](#manually)!
# https://rspamd.com/doc/workers/controller.html#controller-configuration
## Examples & Advanced Configuration
### A Very Basic Configuration
Do you want to start using Rspamd? Rspamd is disabled by default, so you need to set the following environment variables:
```env
ENABLE_RSPAMD=1
# ClamAV is compatible with Rspamd. Optionally enable it for anti-virus support:
ENABLE_CLAMAV=1
# Rspamd replaces the functionality of all these anti-spam services, disable them:
ENABLE_OPENDKIM=0
ENABLE_OPENDMARC=0
ENABLE_POLICYD_SPF=0
ENABLE_AMAVIS=0
ENABLE_SPAMASSASSIN=0
# Provided you've set `RSPAMD_GREYLISTING=1`, also disable Postgrey:
ENABLE_POSTGREY=0
```
This will enable Rspamd and disable services you don't need when using Rspamd.
### Adjusting and Extending The Very Basic Configuration
Rspamd is running, but you want or need to adjust it? First, create a file named `custom-commands.conf` under `docker-data/dms/config/rspamd` (which translates to `/tmp/docker-mailserver/rspamd/` inside the container). Then add your changes:
1. Say you want to be able to easily look at the frontend Rspamd provides on port 11334 (default) without the need to enter a password (maybe because you already provide authorization and authentication). You will have to adjust the controller worker: `set-option-for-controller secure_ip "0.0.0.0/0"`.
2. Do you additionally want to enable the auto-spam-learning for the Bayes module? No problem: `set-option-for-module classifier-bayes autolearn true`.
3. But the chartable module gets on your nerves? Easy: `disable-module chartable`.
??? example "What Does the Result Look Like?"
Here is what the file looks like in the end:
```bash
# See 1.
# ATTENTION: this disables authentication on the website - make sure you know what you're doing!
set-option-for-controller secure_ip "0.0.0.0/0" set-option-for-controller secure_ip "0.0.0.0/0"
# Some settings aren't documented well, you may find them in snippets or Rspamds default config files: # See 2.
# https://rspamd.com/doc/tutorials/quickstart.html#using-of-milter-protocol-for-rspamd--16
# /etc/rspamd/worker-proxy.inc
set-option-for-proxy reject_message "Rejected - Detected as spam"
# Equivalent to the previous example, but `add-line` is more verbose:
add-line worker-proxy.inc reject_message = "Rejected - Detected as spam"
# Enable Bayes auto-learning feature to classify spam based on Rspamd action/score results:
# NOTE: The statistics module imports a separate file for classifier-bayes config
# https://rspamd.com/doc/configuration/statistic.html#autolearning
set-option-for-module classifier-bayes autolearn true set-option-for-module classifier-bayes autolearn true
# Disable the `chartable` module: # See 3.
# https://rspamd.com/doc/modules/chartable.html
disable-module chartable disable-module chartable
``` ```
## Advanced Configuration
### DKIM Signing ### DKIM Signing
There is a dedicated [section for setting up DKIM with Rspamd in our documentation][docs::dkim-with-rspamd]. There is a dedicated [section for setting up DKIM with Rspamd in our documentation][docs::dkim-with-rspamd].
### ARC (Authenticated Received Chain)
[ARC][wikipedia::arc] support in DMS is opt-in via config file. [Enable the ARC Rspamd module][rspamd-docs::arc] by creating a config file at `docker-data/dms/config/rspamd/override.d/arc.conf`.
!!! example
For each mail domain you have DMS manage, add the equivalent `example.com` sub-section to `domain` and adjust the `path` + `selector` fields as necessary.
```conf title="rspamd/override.d/arc.conf"
sign_local = true;
sign_authenticated = true;
domain {
example.com {
path = "/tmp/docker-mailserver/rspamd/dkim/rsa-2048-mail-example.private.txt";
selector = "mail";
}
}
```
!!! tip "Using a common keypair"
As with DKIM, the keypair can be shared across your configured domains.
Your ARC config can share the same DKIM private key + selector (_with associated DNS record for the public key_).
### _Abusix_ Integration ### _Abusix_ Integration
This subsection provides information about the integration of [Abusix][abusix-web], "a set of blocklists that work as an additional email security layer for your existing mail environment". The setup is straight-forward and well documented: This subsection provides information about the integration of [Abusix][abusix-web], "a set of blocklists that work as an additional email security layer for your existing mail environment". The setup is straight-forward and well documented:
1. [Create an account][abusix-web::register] 1. [Create an account](https://app.abusix.com/)
2. Retrieve your API key 2. Retrieve your API key
3. Navigate to the ["Getting Started" documentation for Rspamd][abusix-docs::rspamd-integration] and follow the steps described there 3. Navigate to the ["Getting Started" documentation for Rspamd][abusix-docs::rspamd-integration] and follow the steps described there
4. Make sure to change `<APIKEY>` to your private API key 4. Make sure to change `<APIKEY>` to your private API key
@ -335,28 +267,17 @@ While _Abusix_ can be integrated into Postfix, Postscreen and a multitude of oth
[rspamd-docs::web-ui::password]: https://www.rspamd.com/doc/tutorials/quickstart.html#setting-the-controller-password [rspamd-docs::web-ui::password]: https://www.rspamd.com/doc/tutorials/quickstart.html#setting-the-controller-password
[rspamd-docs::modules]: https://rspamd.com/doc/modules/ [rspamd-docs::modules]: https://rspamd.com/doc/modules/
[rspamd-docs::modules::rbl]: https://rspamd.com/doc/modules/rbl.html [rspamd-docs::modules::rbl]: https://rspamd.com/doc/modules/rbl.html
[rspamd-docs::override-dir]: https://www.rspamd.com/doc/faq.html#what-are-the-locald-and-overrided-directories
[rspamd-docs::config-directories]: https://rspamd.com/doc/faq.html#what-are-the-locald-and-overrided-directories [rspamd-docs::config-directories]: https://rspamd.com/doc/faq.html#what-are-the-locald-and-overrided-directories
[rspamd-docs::config::ucl-syntax]: https://rspamd.com/doc/configuration/ucl.html [rspamd-docs::basic-options]: https://rspamd.com/doc/configuration/options.html
[rspamd-docs::config::overview]: https://rspamd.com/doc/configuration/index.html
[rspamd-docs::config::quickstart]: https://rspamd.com/doc/tutorials/quickstart.html#configuring-rspamd
[rspamd-docs::config::global]: https://rspamd.com/doc/configuration/options.html
[rspamd-docs::config::workers]: https://rspamd.com/doc/workers/
[rspamd-docs::config::worker-controller]: https://rspamd.com/doc/workers/controller.html
[rspamd-docs::config::worker-proxy]: https://rspamd.com/doc/workers/rspamd_proxy.html
[wikipedia::arc]: https://en.wikipedia.org/wiki/Authenticated_Received_Chain [www::rbl-vs-dnsbl]: https://forum.eset.com/topic/25277-dnsbl-vs-rbl-mail-security/?do=findComment&comment=119818
[rspamd-docs::arc]: https://rspamd.com/doc/modules/arc.html
[www::rbl-vs-dnsbl]: https://forum.eset.com/topic/25277-dnsbl-vs-rbl-mail-security/#comment-119818
[abusix-web]: https://abusix.com/ [abusix-web]: https://abusix.com/
[abusix-web::register]: https://app.abusix.com/
[abusix-docs::rspamd-integration]: https://abusix.com/docs/rspamd/ [abusix-docs::rspamd-integration]: https://abusix.com/docs/rspamd/
[spamhaus::faq::dnsbl-usage]: https://www.spamhaus.org/faq/section/DNSBL%20Usage#365 [spamhaus::faq::dnsbl-usage]: https://www.spamhaus.org/faq/section/DNSBL%20Usage#365
[dms-repo::rspamd-actions-config]: https://github.com/docker-mailserver/docker-mailserver/tree/v15.0.0/target/rspamd/local.d/actions.conf [dms-repo::rspamd-actions-config]: https://github.com/docker-mailserver/docker-mailserver/blob/v14.0.0/target/rspamd/local.d/actions.conf
[dms-repo::default-rspamd-configuration]: https://github.com/docker-mailserver/docker-mailserver/tree/v15.0.0/target/rspamd [dms-repo::default-rspamd-configuration]: https://github.com/docker-mailserver/docker-mailserver/tree/v14.0.0/target/rspamd
[gh-dms::guide::valkey]: https://github.com/docker-mailserver/docker-mailserver/issues/4001#issuecomment-2652596692
[gh-dms::guide::rspamd-web]: https://github.com/orgs/docker-mailserver/discussions/4269#discussioncomment-11329588
[docs::env::enable-redis]: ../environment.md#enable_rspamd_redis [docs::env::enable-redis]: ../environment.md#enable_rspamd_redis
[docs::spam-to-junk]: ../environment.md#move_spam_to_junk [docs::spam-to-junk]: ../environment.md#move_spam_to_junk

View File

@ -485,8 +485,6 @@ DSM-generated letsencrypt certificates get auto-renewed every three months.
!!! example !!! example
While DMS does not need a webserver to work, this workaround will provision a TLS certificate for DMS to use by adding a dummy site block to trigger cert provisioning.
```yaml title="compose.yaml" ```yaml title="compose.yaml"
services: services:
# Basic Caddy service to provision certs: # Basic Caddy service to provision certs:
@ -512,12 +510,9 @@ DSM-generated letsencrypt certificates get auto-renewed every three months.
- ${CADDY_DATA_DIR}/certificates/acme-v02.api.letsencrypt.org-directory/mail.example.com/mail.example.com.key:/etc/letsencrypt/live/mail.example.com/privkey.pem - ${CADDY_DATA_DIR}/certificates/acme-v02.api.letsencrypt.org-directory/mail.example.com/mail.example.com.key:/etc/letsencrypt/live/mail.example.com/privkey.pem
``` ```
An explicit entry in your `Caddyfile` config will have Caddy provision and renew a certificate for your DMS FQDN:
```caddyfile title="Caddyfile" ```caddyfile title="Caddyfile"
mail.example.com { mail.example.com {
# Optionally provision RSA 2048-bit certificate instead of ECDSA P-256: tls internal {
tls {
key_type rsa2048 key_type rsa2048
} }
@ -527,12 +522,10 @@ DSM-generated letsencrypt certificates get auto-renewed every three months.
} }
``` ```
!!! info While DMS does not need a webserver to work, this workaround will provision a TLS certificate for DMS to use.
An explicit `tls` directive affects only the site-address block it's used in: - [`tls internal`][caddy-docs::tls-internal] will create a local self-signed cert for testing. This targets only the site-address, unlike the global `local_certs` option.
- [`key_type`][caddy-docs::key-type] can be used in the `tls` block if you need to enforce RSA as the key type for certificates provisioned. The default is currently ECDSA (P-256).
- Use [`tls internal { ... }`][caddy-docs::tls-internal] if wanting to create a local self-signed cert, which may be useful for testing. This allows opt-in to use self-signed certs unlike the global `local_certs` option.
- [`key_type`][caddy-docs::key-type] can be used in the `tls` block if you need to enforce RSA as the key type for certificates provisioned. The default is currently ECDSA (P-256). This may improve compatibility with legacy clients.
??? example "With `caddy-docker-proxy`" ??? example "With `caddy-docker-proxy`"
@ -565,9 +558,9 @@ DSM-generated letsencrypt certificates get auto-renewed every three months.
labels: labels:
# Set your DMS FQDN here to add the site-address into the generated Caddyfile: # Set your DMS FQDN here to add the site-address into the generated Caddyfile:
caddy_0: mail.example.com caddy_0: mail.example.com
# Adding a dummy directive is required: # Add a dummy directive is required:
caddy_0.respond: "Hello DMS" caddy_0.respond: "Hello DMS"
# Uncomment to make a proxy for Rspamd: # Uncomment to make a proxy for Rspamd
# caddy_1: rspamd.example.com # caddy_1: rspamd.example.com
# caddy_1.reverse_proxy: "{{upstreams 11334}}" # caddy_1.reverse_proxy: "{{upstreams 11334}}"
``` ```
@ -578,23 +571,6 @@ DSM-generated letsencrypt certificates get auto-renewed every three months.
This can make the volume mounting for DMS to find the certificates non-deterministic, but you can [restrict provisioning to single service via the `acme_ca` setting][caddy::restrict-acme-provisioner]. This can make the volume mounting for DMS to find the certificates non-deterministic, but you can [restrict provisioning to single service via the `acme_ca` setting][caddy::restrict-acme-provisioner].
---
**NOTE:** Bind mounting a file directly instead of a directory will mount by inode. If the file is updated at renewal and this modifies the inode on the host system, then the container will still point to the old certificate.
If this happens, consider using our manual TLS type instead:
```yaml title="compose.yaml"
services:
mailserver:
environment:
SSL_TYPE: manual
SSL_CERT_PATH: /srv/tls/mail.example.com/mail.example.com.crt
SSL_KEY_PATH: /srv/tls/mail.example.com/mail.example.com.key
volumes:
- ${CADDY_DATA_DIR}/certificates/acme-v02.api.letsencrypt.org-directory/mail.example.com/:/srv/tls/mail.example.com/:ro
```
### Traefik ### Traefik
[Traefik][traefik::github] is an open-source application proxy using the [ACME protocol][ietf::rfc::acme]. Traefik can request certificates for domains and subdomains, and it will take care of renewals, challenge negotiations, etc. [Traefik][traefik::github] is an open-source application proxy using the [ACME protocol][ietf::rfc::acme]. Traefik can request certificates for domains and subdomains, and it will take care of renewals, challenge negotiations, etc.

View File

@ -15,20 +15,13 @@ When refactoring, writing or altering scripts or other files, adhere to these ru
Make sure to select `edge` in the dropdown menu at the top. Navigate to the page you would like to edit and click the edit button in the top right. This allows you to make changes and create a pull-request. Make sure to select `edge` in the dropdown menu at the top. Navigate to the page you would like to edit and click the edit button in the top right. This allows you to make changes and create a pull-request.
Alternatively you can make the changes locally. For that you'll need to have Docker installed and run: Alternatively you can make the changes locally. For that you'll need to have Docker installed. Navigate into the `docs/` directory. Then run:
```sh ```sh
# From the root directory of the git clone: docker run --rm -it -p 8000:8000 -v "${PWD}:/docs" squidfunk/mkdocs-material
docker run --rm -it -p 8000:8000 -v "./docs:/docs" squidfunk/mkdocs-material
``` ```
This serves the documentation on your local machine on port `8000`. Each change will be hot-reloaded onto the page you view, just edit, save and look at the result. This serves the documentation on your local machine on port `8000`. Each change will be hot-reloaded onto the page you view, just edit, save and look at the result.
!!! note
The container logs will inform you of invalid links detected, but a [few are false-positives][gh-dms::mkdocs-link-error-false-positives] due to our usage of linking to specific [content tabs][mkdocs::content-tabs].
[get-docker]: https://docs.docker.com/get-docker/ [get-docker]: https://docs.docker.com/get-docker/
[docs-bats-parallel]: https://bats-core.readthedocs.io/en/v1.8.2/usage.html#parallel-execution [docs-bats-parallel]: https://bats-core.readthedocs.io/en/v1.8.2/usage.html#parallel-execution
[gh-dms::mkdocs-link-error-false-positives]: https://github.com/docker-mailserver/docker-mailserver/pull/4366
[mkdocs::content-tabs]: https://squidfunk.github.io/mkdocs-material/reference/content-tabs/#anchor-links

View File

@ -44,21 +44,19 @@ As the official DMS image does not provide `dovecot-solr`, you'll need to includ
mailserver: mailserver:
hostname: mail.example.com hostname: mail.example.com
# The `image` setting now represents the tag for the local build configured below: # The `image` setting now represents the tag for the local build configured below:
image: local/dms:${DMS_TAG?Must set DMS image tag} image: local/dms:14.0
# Local build (no need to try pull `image` remotely): # Local build (no need to try pull `image` remotely):
pull_policy: build pull_policy: build
# Add this `build` section to your real `compose.yaml` for your DMS service: # Add this `build` section to your real `compose.yaml` for your DMS service:
build: build:
dockerfile_inline: | dockerfile_inline: |
FROM docker.io/mailserver/docker-mailserver:${DMS_TAG?Must set DMS image tag} FROM docker.io/mailserver/docker-mailserver:14.0
RUN apt-get update && apt-get install dovecot-solr RUN apt-get update && apt-get install dovecot-solr
``` ```
This approach only needs to install the package once with the image build itself which minimizes the delay of container startup. - Just run `docker compose up` and it will pull DMS and build your custom image to run a container.
- Updating to a new DMS release is straight-forward, just adjust the version tag as you normally would. If you make future changes that don't apply, you may need to force a rebuild.
- Just run `DMS_TAG='14.0' docker compose up` and it will pull the DMS image, then build your custom DMS image to run a new container instance. - This approach only needs to install the package once with the image build itself. This minimizes delay of container startup.
- Updating to a new DMS release is straight-forward, just adjust the `DMS_TAG` ENV value or change the image tag directly in `compose.yaml` as you normally would to upgrade an image.
- If you make future changes to the `dockerfile_inline` that don't seem to be applied, you may need to force a rebuild with `DMS_TAG='14.0' docker compose up --build`.
!!! note "Why doesn't DMS include `dovecot-solr`?" !!! note "Why doesn't DMS include `dovecot-solr`?"
@ -146,25 +144,6 @@ 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: 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::user-patches]: ../../config/advanced/override-defaults/user-patches.md
[docs::dovecot::full-text-search]: ../../config/advanced/full-text-search.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 [gh-dms::feature-request::dovecot-solr-package]: https://github.com/docker-mailserver/docker-mailserver/issues/4052
@ -173,6 +152,3 @@ services:
[dockerfile-solr-uidgid]: https://github.com/apache/solr-docker/blob/9cd850b72309de05169544395c83a85b329d6b86/9.6/Dockerfile#L89-L92 [dockerfile-solr-uidgid]: https://github.com/apache/solr-docker/blob/9cd850b72309de05169544395c83a85b329d6b86/9.6/Dockerfile#L89-L92
[github-solr]: https://github.com/apache/solr [github-solr]: https://github.com/apache/solr
[github-dovecot::core-docs]: https://github.com/dovecot/core/tree/main/doc [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

@ -21,6 +21,10 @@ This can be configured by [overriding the default Postfix configurations][docs::
In `postfix-main.cf` you'll have to set the [`smtp_bind_address`][postfix-docs::smtp-bind-address-ipv4] and [`smtp_bind_address6`][postfix-docs::smtp-bind-address-ipv6] In `postfix-main.cf` you'll have to set the [`smtp_bind_address`][postfix-docs::smtp-bind-address-ipv4] and [`smtp_bind_address6`][postfix-docs::smtp-bind-address-ipv6]
to the respective IP-address on the server you want to use. to the respective IP-address on the server you want to use.
[docs::overrides-postfix]: ../../config/advanced/override-defaults/postfix.md
[postfix-docs::smtp-bind-address-ipv4]: https://www.postfix.org/postconf.5.html#smtp_bind_address
[postfix-docs::smtp-bind-address-ipv6]: https://www.postfix.org/postconf.5.html#smtp_bind_address6
!!! example !!! example
=== "Contributed solution" === "Contributed solution"
@ -52,44 +56,9 @@ to the respective IP-address on the server you want to use.
If that avoids the concern with `smtp-amavis`, you may still need to additionally override for the [`relay` transport][gh-src::postfix-master-cf::relay-transport] as well if you have configured DMS to relay mail. If that avoids the concern with `smtp-amavis`, you may still need to additionally override for the [`relay` transport][gh-src::postfix-master-cf::relay-transport] as well if you have configured DMS to relay mail.
=== "Bridged Networks"
When your DMS container is using a bridge network, you'll instead need to restrict which IP address inbound and outbound traffic is routed through via the bridged interface.
For **inbound** traffic, you may configure this at whatever scope is most appropriate for you:
- **Daemon:** Change the [default bind address][inbound-ip::docker-docs::daemon] configured in `/etc/docker/daemon.json` (default `0.0.0.0`)
- **Network:** Assign the [`host_binding_ipv4` bridge driver option][inbound-ip::docker-docs::network] as shown in the below `compose.yaml` snippet.
- **Container:** Provide an explicit host IP address when [publishing a port][inbound-ip::docker-docs::container].
For **outbound** traffic, the bridge network will use the default route. You can change this by either:
- [Manually routing networks][outbound-ip::route-manually] on the host.
- Use the [`host_ipv4` driver option][outbind-ip::host-ipv4] for Docker networks to force the SNAT (source IP) that the bridged network will route outbound traffic through.
- This IP address must belong to a network interface to be routed through it.
- IPv6 support via `host_ipv6` [requires at least Docker v25][outbind-ip::host-ipv6].
---
Here is a `compose.yaml` snippet that applies the inbound + outbound settings to the default bridge network Docker Compose creates (_if it already exists, you will need to ensure it's re-created to apply the updated settings_):
```yaml title="compose.yaml"
networks:
default:
driver_opts:
# Inbound IP (sets the host IP that published ports receive traffic from):
com.docker.network.bridge.host_binding_ipv4: 198.51.100.42
# Outbound IP (sets the host IP that external hosts will receive connections from):
com.docker.network.host_ipv4: 198.51.100.42
```
!!! note "IP addresses for documentation" !!! note "IP addresses for documentation"
IP addresses shown in above examples (`198.51.100.42` + `2001:DB8::42`) are placeholders, they are IP addresses reserved for documentation by IANA (_[RFC-5737 (IPv4)][rfc-5737] and [RFC-3849 (IPv6)][rfc-3849]_). Replace them with the IP addresses you want DMS to send mail through. IP addresses shown in above examples are placeholders, they are IP addresses reserved for documentation by IANA (_[RFC-5737 (IPv4)][rfc-5737] and [RFC-3849 (IPv6)][rfc-3849]_). Replace them with the IP addresses you want DMS to send mail through.
[docs::overrides-postfix]: ../../config/advanced/override-defaults/postfix.md
[postfix-docs::smtp-bind-address-ipv4]: https://www.postfix.org/postconf.5.html#smtp_bind_address
[postfix-docs::smtp-bind-address-ipv6]: https://www.postfix.org/postconf.5.html#smtp_bind_address6
[rfc-5737]: https://datatracker.ietf.org/doc/html/rfc5737 [rfc-5737]: https://datatracker.ietf.org/doc/html/rfc5737
[rfc-3849]: https://datatracker.ietf.org/doc/html/rfc3849 [rfc-3849]: https://datatracker.ietf.org/doc/html/rfc3849
@ -97,10 +66,3 @@ to the respective IP-address on the server you want to use.
[gh-pr::3465::comment-restrictions]: https://github.com/docker-mailserver/docker-mailserver/pull/3465#discussion_r1458114528 [gh-pr::3465::comment-restrictions]: https://github.com/docker-mailserver/docker-mailserver/pull/3465#discussion_r1458114528
[gh-pr::3465::alternative-solution]: https://github.com/docker-mailserver/docker-mailserver/pull/3465#issuecomment-1678107233 [gh-pr::3465::alternative-solution]: https://github.com/docker-mailserver/docker-mailserver/pull/3465#issuecomment-1678107233
[gh-src::postfix-master-cf::relay-transport]: https://github.com/docker-mailserver/docker-mailserver/blob/9cdbef2b369fb4fb0f1b4e534da8703daf92abc9/target/postfix/master.cf#L65 [gh-src::postfix-master-cf::relay-transport]: https://github.com/docker-mailserver/docker-mailserver/blob/9cdbef2b369fb4fb0f1b4e534da8703daf92abc9/target/postfix/master.cf#L65
[inbound-ip::docker-docs::daemon]: https://docs.docker.com/reference/cli/dockerd/#default-network-options
[inbound-ip::docker-docs::network]: https://docs.docker.com/engine/network/drivers/bridge/#default-host-binding-address
[inbound-ip::docker-docs::container]: https://docs.docker.com/reference/compose-file/services/#ports
[outbound-ip::route-manually]: https://github.com/moby/moby/issues/30053#issuecomment-1077041045
[outbind-ip::host-ipv4]: https://github.com/moby/libnetwork/pull/2454
[outbind-ip::host-ipv6]: https://github.com/moby/moby/issues/46469

View File

@ -48,7 +48,7 @@ We will later dig into DKIM, DMARC & SPF, but for now, these are the records tha
- The **MX record** tells everyone which (DNS) name is responsible for e-mails on your domain. - The **MX record** tells everyone which (DNS) name is responsible for e-mails on your domain.
Because you want to keep the option of running another service on the domain name itself, you run your mail server on `mail.example.com`. Because you want to keep the option of running another service on the domain name itself, you run your mail server on `mail.example.com`.
This does not imply your e-mails will look like `test@mail.example.com`, the DNS name of your mail server is decoupled of the domain it serves e-mails for. This does not imply your e-mails will look like `test@mail.example.com`, the DNS name of your mail server is decoupled of the domain it serves e-mails for.
Your mail server could also handle emails for `test@some-other-domain.com`, if the MX record for `some-other-domain.com` points to `mail.example.com`. In theory, you mail server could even serve e-mails for `test@some-other-domain.com`, if the MX record for `some-other-domain.com` points to `mail.example.com`.
- The **A record** tells everyone which IP address the DNS name `mail.example.com` resolves to. - The **A record** tells everyone which IP address the DNS name `mail.example.com` resolves to.
- The **PTR record** is the counterpart of the A record, telling everyone what name the IP address `11.22.33.44` resolves to. - The **PTR record** is the counterpart of the A record, telling everyone what name the IP address `11.22.33.44` resolves to.

View File

@ -89,10 +89,10 @@ TLS_LEVEL=
# Configures the handling of creating mails with forged sender addresses. # 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). # **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 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. # 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.
SPOOF_PROTECTION= 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/main/README.rst) 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/master/README.md#sender-rewriting-scheme-crash-course) for further explanation.
# - **0** => Disabled # - **0** => Disabled
# - 1 => Enabled # - 1 => Enabled
ENABLE_SRS=0 ENABLE_SRS=0
@ -508,7 +508,7 @@ DOVECOT_MAILBOX_FORMAT=maildir
# empty => no # empty => no
# yes => Allow bind authentication for LDAP # yes => Allow bind authentication for LDAP
# https://doc.dovecot.org/2.4.0/core/config/auth/databases/ldap.html#authentication-bind # https://wiki.dovecot.org/AuthDatabase/LDAP/AuthBinds
DOVECOT_AUTH_BIND= DOVECOT_AUTH_BIND=
# ----------------------------------------------- # -----------------------------------------------

View File

@ -19,11 +19,7 @@ function _main() {
for MAIL_ACCOUNT in "${@}"; do for MAIL_ACCOUNT in "${@}"; do
_account_should_already_exist _account_should_already_exist
if [[ ${MAILDEL} -eq 1 ]]; then [[ ${MAILDEL} -eq 1 ]] && _remove_maildir "${MAIL_ACCOUNT}"
_remove_maildir "${MAIL_ACCOUNT}"
else
_log 'info' "The mailbox data will not be deleted."
fi
_manage_virtual_aliases_delete '_' "${MAIL_ACCOUNT}" \ _manage_virtual_aliases_delete '_' "${MAIL_ACCOUNT}" \
|| _exit_with_error "Aliases for '${MAIL_ACCOUNT}' could not be deleted" || _exit_with_error "Aliases for '${MAIL_ACCOUNT}' could not be deleted"
@ -35,7 +31,7 @@ function _main() {
_manage_accounts_delete "${MAIL_ACCOUNT}" \ _manage_accounts_delete "${MAIL_ACCOUNT}" \
|| _exit_with_error "'${MAIL_ACCOUNT}' could not be deleted" || _exit_with_error "'${MAIL_ACCOUNT}' could not be deleted"
_log 'info' "'${MAIL_ACCOUNT}' and associated data (aliases, quotas) deleted" _log 'info' "'${MAIL_ACCOUNT}' and associated data deleted"
done done
} }
@ -47,14 +43,14 @@ ${ORANGE}USAGE${RESET}
${ORANGE}OPTIONS${RESET} ${ORANGE}OPTIONS${RESET}
-y -y
Skip prompt by approving to ${LWHITE}delete all mail data${RESET} for the account(s). Skip prompt by approving to ${LWHITE}delete all mail storage${RESET} for the account(s).
${BLUE}Generic Program Information${RESET} ${BLUE}Generic Program Information${RESET}
help Print the usage information. help Print the usage information.
${ORANGE}DESCRIPTION${RESET} ${ORANGE}DESCRIPTION${RESET}
Delete a mail account, including associated data (aliases, quotas) and Delete a mail account, including associated data (aliases, quotas) and
optionally the mailbox data for that account. optionally the mailbox storage for that account.
${ORANGE}EXAMPLES${RESET} ${ORANGE}EXAMPLES${RESET}
${LWHITE}./setup.sh email del user@example.com${RESET} ${LWHITE}./setup.sh email del user@example.com${RESET}
@ -91,10 +87,12 @@ function _parse_options() {
function _maildel_request_if_missing() { function _maildel_request_if_missing() {
if [[ ${MAILDEL} -eq 0 ]]; then if [[ ${MAILDEL} -eq 0 ]]; then
local MAILDEL_CHOSEN local MAILDEL_CHOSEN
read -r -p "Do you want to delete the mailbox data as well (removing all mails)? [y/N] " MAILDEL_CHOSEN read -r -p "Do you want to delete the mailbox as well (removing all mails)? [Y/n] " MAILDEL_CHOSEN
# Delete mailbox data only if the user provides explicit confirmation. # TODO: Why would MAILDEL be set to true if MAILDEL_CHOSEN is empty?
[[ ${MAILDEL_CHOSEN,,} == "y" ]] && MAILDEL=1 if [[ ${MAILDEL_CHOSEN} =~ (y|Y|yes|Yes) ]] || [[ -z ${MAILDEL_CHOSEN} ]]; then
MAILDEL=1
fi
fi fi
} }
@ -105,10 +103,10 @@ function _remove_maildir() {
local DOMAIN_PART="${MAIL_ACCOUNT#*@}" local DOMAIN_PART="${MAIL_ACCOUNT#*@}"
local MAIL_ACCOUNT_STORAGE_DIR="/var/mail/${DOMAIN_PART}/${LOCAL_PART}" local MAIL_ACCOUNT_STORAGE_DIR="/var/mail/${DOMAIN_PART}/${LOCAL_PART}"
[[ ! -d ${MAIL_ACCOUNT_STORAGE_DIR} ]] && _exit_with_error "Mailbox data directory '${MAIL_ACCOUNT_STORAGE_DIR}' does not exist" [[ ! -d ${MAIL_ACCOUNT_STORAGE_DIR} ]] && _exit_with_error "Mailbox directory '${MAIL_ACCOUNT_STORAGE_DIR}' does not exist"
_log 'info' "Deleting mailbox data: '${MAIL_ACCOUNT_STORAGE_DIR}'" _log 'info' "Deleting Mailbox: '${MAIL_ACCOUNT_STORAGE_DIR}'"
rm -R "${MAIL_ACCOUNT_STORAGE_DIR}" || _exit_with_error 'Mailbox data could not be deleted' rm -R "${MAIL_ACCOUNT_STORAGE_DIR}" || _exit_with_error 'Mailbox could not be deleted'
# Remove parent directory too if it's empty: # Remove parent directory too if it's empty:
rmdir "/var/mail/${DOMAIN_PART}" &>/dev/null rmdir "/var/mail/${DOMAIN_PART}" &>/dev/null
} }

View File

@ -12,17 +12,9 @@ if [[ -f /etc/dms-settings ]] && [[ $(_get_dms_env_value 'ENABLE_RSPAMD') -eq 1
fi fi
fi fi
function _main() { KEYSIZE=2048
# Default parameters (updated by `_parse_arguments()`): SELECTOR=mail
local KEYSIZE=2048 DOMAINS=
local SELECTOR=mail
local DMS_DOMAINS=
_require_n_parameters_or_print_usage 0 "${@}"
_parse_arguments "${@}"
_generate_dkim_keys
}
function __usage() { function __usage() {
printf '%s' "${PURPLE}OPEN-DKIM${RED}(${YELLOW}8${RED}) printf '%s' "${PURPLE}OPEN-DKIM${RED}(${YELLOW}8${RED})
@ -65,171 +57,122 @@ ${ORANGE}EXAMPLES${RESET}
${ORANGE}EXIT STATUS${RESET} ${ORANGE}EXIT STATUS${RESET}
Exit status is 0 if command was successful. If wrong arguments are provided or arguments contain Exit status is 0 if command was successful. If wrong arguments are provided or arguments contain
errors, the script will exit early with a non-zero exit status. errors, the script will exit early with exit status 2.
" "
} }
function _parse_arguments() { _require_n_parameters_or_print_usage 0 "${@}"
# Parse the command args through iteration:
while [[ ${#} -gt 0 ]]; do while [[ ${#} -gt 0 ]]; do
case "${1}" in case "${1}" in
( 'keysize' ) ( 'keysize' )
if [[ -n ${2:-} ]]; then if [[ -n ${2+set} ]]; then
KEYSIZE="${2}" KEYSIZE="${2}"
_log 'trace' "Keysize set to '${KEYSIZE}'" shift
shift
else else
_exit_with_error "No keysize provided after 'keysize' argument" _exit_with_error "No keysize provided after 'keysize' argument"
fi fi
;; ;;
( 'selector' ) ( 'selector' )
if [[ -n ${2:-} ]]; then if [[ -n ${2+set} ]]; then
# shellcheck disable=SC2034
SELECTOR="${2}" SELECTOR="${2}"
_log 'trace' "Selector set to '${SELECTOR}'" shift
shift
else else
_exit_with_error "No selector provided after 'selector' argument" _exit_with_error "No selector provided after 'selector' argument"
fi fi
;; ;;
( 'domain' ) ( 'domain' )
if [[ -n ${2:-} ]]; then if [[ -n ${2+set} ]]; then
DMS_DOMAINS="${2}" DOMAINS="${2}"
_log 'trace' "Domain(s) set to '${DMS_DOMAINS}'" shift
shift
else else
_exit_with_error "No domain(s) provided after 'domain' argument" _exit_with_error "No domain(s) provided after 'domain' argument"
fi fi
;; ;;
( 'help' )
__usage
exit 0
;;
( * ) ( * )
__usage __usage
_exit_with_error "Unknown option(s) ${*}" _exit_with_error "Unknown options '${1}' ${2:+and \'${2}\'}"
;; ;;
esac
# Discard these two args (option + value) now that they've been processed: esac
shift 2
done done
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
_obtain_hostname_and_domainname
# uses TMP_VHOST:
_vhost_collect_postfix_domains
else
tr ',' '\n' <<< "${DOMAINS}" >"${TMP_VHOST}"
fi
# uses DATABASE_VHOST + TMP_VHOST:
_create_vhost
} }
function _generate_dkim_keys() {
_generate_domains_config _generate_domains_config
if [[ ! -s ${DATABASE_VHOST} ]]; then if [[ ! -s ${DATABASE_VHOST} ]]; then
_log 'warn' 'No entries found, no keys to make' _log 'warn' 'No entries found, no keys to make'
exit 0 exit 0
fi 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 while read -r DKIM_DOMAIN; do
_create_dkim_key "${DKIM_DOMAIN}" mkdir -p "/tmp/docker-mailserver/opendkim/keys/${DKIM_DOMAIN}"
# Create / Update OpenDKIM configs with new DKIM key: if [[ ! -f "/tmp/docker-mailserver/opendkim/keys/${DKIM_DOMAIN}/${SELECTOR}.private" ]]; then
ENTRY_KEY="${SELECTOR}._domainkey.${DKIM_DOMAIN}" _log 'info' "Creating DKIM private key '/tmp/docker-mailserver/opendkim/keys/${DKIM_DOMAIN}/${SELECTOR}.private'"
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'
function _generate_domains_config() {
local TMP_VHOST='/tmp/vhost.dkim.tmp'
# Generate the default vhost (equivalent to /etc/postfix/vhost),
# 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' <<< "${DMS_DOMAINS}" >"${TMP_VHOST}"
fi
# Uses DATABASE_VHOST + TMP_VHOST:
_create_vhost
}
# `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}
OPENDKIM_DOMAINKEY_DIR="${OPENDKIM_BASE_DIR}/keys/${DKIM_DOMAIN}"
mkdir -p "${OPENDKIM_DOMAINKEY_DIR}"
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 \ opendkim-genkey \
--bits="${KEYSIZE}" \ --bits="${KEYSIZE}" \
--subdomains \ --subdomains \
--domain="${DKIM_DOMAIN}" \ --domain="${DKIM_DOMAIN}" \
--selector="${SELECTOR}" \ --selector="${SELECTOR}" \
--directory="${OPENDKIM_DOMAINKEY_DIR}" --directory="/tmp/docker-mailserver/opendkim/keys/${DKIM_DOMAIN}"
fi fi
}
OPENDKIM_BASE_DIR='/tmp/docker-mailserver/opendkim' # fix permissions to use the same user:group as /tmp/docker-mailserver/opendkim/keys
KEY_TABLE_FILE="${OPENDKIM_BASE_DIR}/KeyTable" chown -R "$(stat -c '%U:%G' /tmp/docker-mailserver/opendkim/keys)" "/tmp/docker-mailserver/opendkim/keys/${DKIM_DOMAIN}"
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}"
)
# Only create if the file doesn't exist (avoids modifying mtime): # write to KeyTable if necessary
for FILE in "${OPENDKIM_CONFIGS[@]}"; do KEYTABLEENTRY="${SELECTOR}._domainkey.${DKIM_DOMAIN} ${DKIM_DOMAIN}:${SELECTOR}:/etc/opendkim/keys/${DKIM_DOMAIN}/${SELECTOR}.private"
if [[ ! -f "${FILE}" ]]; then if [[ ! -f "/tmp/docker-mailserver/opendkim/KeyTable" ]]; then
_log 'debug' "Creating OpenDKIM config '${FILE}'" _log 'debug' 'Creating DKIM KeyTable'
touch "${FILE}" 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
fi fi
done
# 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 fi
}
_main "${@}" # 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
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

View File

@ -64,7 +64,6 @@ ${RED}[${ORANGE}SUB${RED}]${ORANGE}COMMANDS${RESET}
${LBLUE}COMMAND${RESET} debug ${RED}:=${RESET} ${LBLUE}COMMAND${RESET} debug ${RED}:=${RESET}
setup debug ${CYAN}fetchmail${RESET} setup debug ${CYAN}fetchmail${RESET}
setup debug ${CYAN}getmail${RESET}
setup debug ${CYAN}login${RESET} <COMMANDS> setup debug ${CYAN}login${RESET} <COMMANDS>
setup debug ${CYAN}show-mail-logs${RESET} setup debug ${CYAN}show-mail-logs${RESET}
@ -151,7 +150,6 @@ function _main() {
( debug ) ( debug )
case ${2:-} in case ${2:-} in
( fetchmail ) debug-fetchmail ;; ( fetchmail ) debug-fetchmail ;;
( getmail ) debug-getmail ;;
( show-mail-logs ) cat /var/log/mail/mail.log ;; ( show-mail-logs ) cat /var/log/mail/mail.log ;;
( login ) ( login )
shift 2 shift 2

View File

@ -68,10 +68,9 @@ smtpd_forbid_bare_newline = yes
# smtpd_forbid_bare_newline_exclusions = $mynetworks # smtpd_forbid_bare_newline_exclusions = $mynetworks
# Custom defined parameters for DMS: # Custom defined parameters for DMS:
# 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
# `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 dms_smtpd_sender_restrictions = permit_sasl_authenticated, permit_mynetworks, reject_unknown_sender_domain
# `SPOOF_PROTECTION=1` support requires prepending `reject_authenticated_sender_login_mismatch` # Submission ports 587 and 465 support for SPOOF_PROTECTION=1
mua_sender_restrictions = reject_authenticated_sender_login_mismatch, $dms_smtpd_sender_restrictions mua_sender_restrictions = reject_authenticated_sender_login_mismatch, $dms_smtpd_sender_restrictions
# Postscreen settings to drop zombies/open relays/spam early # Postscreen settings to drop zombies/open relays/spam early

View File

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

View File

@ -11,39 +11,28 @@ source /usr/local/bin/helpers/log.sh
# shellcheck disable=SC2310 # shellcheck disable=SC2310
_log_level_is 'trace' && QUIET='-y' || QUIET='-qq' _log_level_is 'trace' && QUIET='-y' || QUIET='-qq'
function _install_build_deps() { function _compile_dovecot_fts_xapian() {
apt-get "${QUIET}" update apt-get "${QUIET}" update
apt-get "${QUIET}" install --no-install-recommends \ apt-get "${QUIET}" --no-install-recommends install \
automake libtool pkg-config libicu-dev libsqlite3-dev libxapian-dev make build-essential dh-make devscripts dovecot-dev automake libtool pkg-config libicu-dev libsqlite3-dev libxapian-dev make build-essential dh-make devscripts dovecot-dev
}
function _build_package() { local XAPIAN_VERSION='1.7.13'
local XAPIAN_VERSION='1.9' curl -sSfL -o dovecot-fts-xapian.tar.gz \
curl -fsSL "https://github.com/grosjo/fts-xapian/releases/download/${XAPIAN_VERSION}/dovecot-fts-xapian-${XAPIAN_VERSION}.tar.gz" \ "https://github.com/grosjo/fts-xapian/releases/download/${XAPIAN_VERSION}/dovecot-fts-xapian-${XAPIAN_VERSION}.tar.gz"
| tar -xz tar xf dovecot-fts-xapian.tar.gz
cd "fts-xapian-${XAPIAN_VERSION}" cd "fts-xapian-${XAPIAN_VERSION}"
USER=root dh_make -p "dovecot-fts-xapian-${XAPIAN_VERSION}" --single --native --copyright gpl2 -y
# Prepare for building DEB source package:
# https://manpages.debian.org/bookworm/dh-make/dh_make.1.en.html
# License LGPL 2.1: https://github.com/grosjo/fts-xapian/issues/174#issuecomment-2422404568
USER=root dh_make --packagename "dovecot-fts-xapian-${XAPIAN_VERSION}" --single --native --copyright lgpl2 -y
# Remove generated example files:
rm debian/*.ex rm debian/*.ex
# Add required package metadata: cp PACKAGES/DEB/control debian/
# https://www.debian.org/doc/manuals/maint-guide/dreq.en.html#control cp PACKAGES/DEB/changelog debian/
curl -fsSL https://raw.githubusercontent.com/grosjo/fts-xapian/refs/tags/1.7.16/PACKAGES/DEB/control > debian/control cp PACKAGES/DEB/compat debian/
# Replace version number:
sed -i -E "s|(dovecot-fts-xapian)-[1-9\.-]+|\1-${XAPIAN_VERSION}|g" debian/control
# Required to proceed with debuild:
# https://www.debian.org/doc/manuals/maint-guide/dother.en.html#compat
# (13 is the default debhelper version from the original `dh_make` generated `debian/control`):
echo '13' > debian/compat
# Build arch specific binary package via debuild: sed -i -E "s|(dovecot-fts-xapian)-[1-9\.-]+|\1-${XAPIAN_VERSION}|g" debian/control
# https://manpages.debian.org/bookworm/devscripts/debuild.1.en.html sed -i -E "s|(dovecot-fts-xapian)-[1-9\.-]+ \(.*\)(.*)|\1-${XAPIAN_VERSION} (${XAPIAN_VERSION})\2|g" debian/changelog
# https://manpages.debian.org/bookworm/dpkg-dev/dpkg-buildpackage.1.en.html
debuild --no-sign --build=any | tee /tmp/debuild.log 2>&1 debuild -us -uc -B | tee /tmp/debuild.log 2>&1
} }
_install_build_deps _compile_dovecot_fts_xapian
_build_package

View File

@ -24,58 +24,30 @@ function _pre_installation_steps() {
apt-get "${QUIET}" upgrade apt-get "${QUIET}" upgrade
_log 'trace' 'Installing packages that are needed early' _log 'trace' 'Installing packages that are needed early'
# Add packages usually required by apt to: # add packages usually required by apt to
# - not log unnecessary warnings
# - be able to add PPAs early (e.g., Rspamd)
local EARLY_PACKAGES=( local EARLY_PACKAGES=(
# Avoid logging unnecessary warnings: apt-utils # avoid useless warnings
apt-utils apt-transport-https ca-certificates curl gnupg # required for adding PPAs
# Required for adding third-party repos (/etc/apt/sources.list.d) as alternative package sources (eg: Dovecot CE and Rspamd): systemd-standalone-sysusers # avoid problems with SA / Amavis (https://github.com/docker-mailserver/docker-mailserver/pull/3403#pullrequestreview-1596689953)
apt-transport-https ca-certificates curl gnupg
# Avoid problems with SA / Amavis (https://github.com/docker-mailserver/docker-mailserver/pull/3403#pullrequestreview-1596689953):
systemd-standalone-sysusers
) )
apt-get "${QUIET}" install --no-install-recommends "${EARLY_PACKAGES[@]}" 2>/dev/null apt-get "${QUIET}" install --no-install-recommends "${EARLY_PACKAGES[@]}" 2>/dev/null
} }
# Install third-party commands to /usr/local/bin
function _install_utils() { 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 'debug' 'Installing utils sourced from Github'
_log 'trace' 'Installing jaq' _log 'trace' 'Installing jaq'
local JAQ_TAG='v2.1.0' local JAQ_TAG='v1.6.0'
curl -sSfL "https://github.com/01mf02/jaq/releases/download/${JAQ_TAG}/jaq-$(uname -m)-unknown-linux-gnu" -o /usr/local/bin/jaq curl -sSfL "https://github.com/01mf02/jaq/releases/download/${JAQ_TAG}/jaq-${JAQ_TAG}-$(uname -m)-unknown-linux-gnu" -o /usr/bin/jaq
chmod +x /usr/local/bin/jaq chmod +x /usr/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' _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_VERSION='20240103.0'
local SWAKS_RELEASE="swaks-${SWAKS_VERSION}" local SWAKS_RELEASE="swaks-${SWAKS_VERSION}"
curl -sSfL "https://github.com/jetmore/swaks/releases/download/v${SWAKS_VERSION}/${SWAKS_RELEASE}.tar.gz" \ curl -sSfL "https://github.com/jetmore/swaks/releases/download/v${SWAKS_VERSION}/${SWAKS_RELEASE}.tar.gz" | tar -xz
| tar -xz --directory /usr/local/bin --no-same-owner --strip-components=1 "${SWAKS_RELEASE}/swaks" mv "${SWAKS_RELEASE}/swaks" /usr/local/bin
rm -r "${SWAKS_RELEASE}"
} }
function _install_postfix() { function _install_postfix() {
@ -146,7 +118,7 @@ function _install_packages() {
bind9-dnsutils iputils-ping less nano bind9-dnsutils iputils-ping less nano
) )
apt-get "${QUIET}" install --no-install-recommends \ apt-get "${QUIET}" --no-install-recommends install \
"${ANTI_VIRUS_SPAM_PACKAGES[@]}" \ "${ANTI_VIRUS_SPAM_PACKAGES[@]}" \
"${CODECS_PACKAGES[@]}" \ "${CODECS_PACKAGES[@]}" \
"${MISCELLANEOUS_PACKAGES[@]}" \ "${MISCELLANEOUS_PACKAGES[@]}" \
@ -163,56 +135,43 @@ function _install_dovecot() {
dovecot-pop3d dovecot-sieve dovecot-pop3d dovecot-sieve
) )
# Additional Dovecot packages for supporting the DMS community (docs-only guide contributions). # Dovecot packages for community supported features.
DOVECOT_PACKAGES+=(dovecot-auth-lua) DOVECOT_PACKAGES+=(dovecot-auth-lua)
# (Opt-in via ENV) Change repo source for dovecot packages to a third-party repo maintained by Dovecot. # Dovecot's deb community repository only provides x86_64 packages, so do not include it
# NOTE: AMD64 / x86_64 is the only supported arch from the Dovecot CE repo (thus noDMS built for ARM64 / aarch64) # when building for another architecture.
# Repo: https://repo.dovecot.org/ce-2.4-latest/debian/bookworm/dists/bookworm/main/
# Docs: https://repo.dovecot.org/#debian
if [[ ${DOVECOT_COMMUNITY_REPO} -eq 1 ]] && [[ "$(uname --machine)" == "x86_64" ]]; then if [[ ${DOVECOT_COMMUNITY_REPO} -eq 1 ]] && [[ "$(uname --machine)" == "x86_64" ]]; then
# WARNING: Repo only provides Debian Bookworm package support for Dovecot CE 2.4+. _log 'trace' 'Using Dovecot community repository'
# As Debian Bookworm only packages Dovecot 2.3.x, building DMS with this alternative package repo may not yet be compatible with DMS: curl -sSfL https://repo.dovecot.org/DOVECOT-REPO-GPG | gpg --import
# - 2.3.19: https://salsa.debian.org/debian/dovecot/-/tree/stable/bookworm gpg --export ED409DA1 > /etc/apt/trusted.gpg.d/dovecot.gpg
# - 2.3.21: https://salsa.debian.org/debian/dovecot/-/tree/stable/bookworm-backports echo "deb https://repo.dovecot.org/ce-2.3-latest/debian/${VERSION_CODENAME} ${VERSION_CODENAME} main" > /etc/apt/sources.list.d/dovecot.list
_log 'trace' 'Adding third-party package repository (Dovecot)' _log 'trace' 'Updating Dovecot package signatures'
curl -fsSL https://repo.dovecot.org/DOVECOT-REPO-GPG-2.4 | gpg --dearmor > /usr/share/keyrings/upstream-dovecot.gpg
echo \
"deb [signed-by=/usr/share/keyrings/upstream-dovecot.gpg] https://repo.dovecot.org/ce-2.4-latest/debian/${VERSION_CODENAME} ${VERSION_CODENAME} main" \
> /etc/apt/sources.list.d/upstream-dovecot.list
# Refresh package index:
apt-get "${QUIET}" update apt-get "${QUIET}" update
# This repo instead provides `dovecot-auth-lua` as a transitional package to `dovecot-lua`, # Additional community package needed for Lua support if the Dovecot community repository is used.
# thus this extra package is required to retain lua support:
DOVECOT_PACKAGES+=(dovecot-lua) DOVECOT_PACKAGES+=(dovecot-lua)
fi fi
_log 'debug' 'Installing Dovecot' _log 'debug' 'Installing Dovecot'
apt-get "${QUIET}" install --no-install-recommends "${DOVECOT_PACKAGES[@]}" apt-get "${QUIET}" --no-install-recommends install "${DOVECOT_PACKAGES[@]}"
# Runtime dependency for fts_xapian (built via `compile.sh`): # dependency for fts_xapian
apt-get "${QUIET}" install --no-install-recommends libxapian30 apt-get "${QUIET}" --no-install-recommends install libxapian30
} }
function _install_rspamd() { function _install_rspamd() {
# NOTE: DMS only supports the rspamd package via using the third-party repo maintained by Rspamd (AMD64 + ARM64): _log 'debug' 'Installing Rspamd'
# Repo: https://rspamd.com/apt-stable/dists/bookworm/main/ _log 'trace' 'Adding Rspamd PPA'
# Docs: https://rspamd.com/downloads.html#debian-and-ubuntu-linux curl -sSfL https://rspamd.com/apt-stable/gpg.key | gpg --dearmor >/etc/apt/trusted.gpg.d/rspamd.gpg
# NOTE: Debian 12 provides Rspamd 3.4 (too old) and Rspamd discourages it's use
_log 'trace' 'Adding third-party package repository (Rspamd)'
curl -fsSL https://rspamd.com/apt-stable/gpg.key | gpg --dearmor > /usr/share/keyrings/upstream-rspamd.gpg
echo \ echo \
"deb [signed-by=/usr/share/keyrings/upstream-rspamd.gpg] https://rspamd.com/apt-stable/ ${VERSION_CODENAME} main" \ "deb [signed-by=/etc/apt/trusted.gpg.d/rspamd.gpg] http://rspamd.com/apt-stable/ ${VERSION_CODENAME} main" \
> /etc/apt/sources.list.d/upstream-rspamd.list >/etc/apt/sources.list.d/rspamd.list
# Refresh package index: _log 'trace' 'Updating package index after adding PPAs'
apt-get "${QUIET}" update apt-get "${QUIET}" update
_log 'debug' 'Installing Rspamd' _log 'trace' 'Installing actual package'
apt-get "${QUIET}" install rspamd redis-server apt-get "${QUIET}" install rspamd redis-server
} }
@ -226,7 +185,7 @@ function _install_fail2ban() {
_log 'debug' 'Installing Fail2ban' _log 'debug' 'Installing Fail2ban'
# Dependencies (https://github.com/docker-mailserver/docker-mailserver/pull/3403#discussion_r1306581431) # Dependencies (https://github.com/docker-mailserver/docker-mailserver/pull/3403#discussion_r1306581431)
apt-get "${QUIET}" install --no-install-recommends python3-pyinotify python3-dnspython python3-systemd apt-get "${QUIET}" --no-install-recommends install python3-pyinotify python3-dnspython python3-systemd
gpg --keyserver "${FAIL2BAN_GPG_PUBLIC_KEY_SERVER}" --recv-keys "${FAIL2BAN_GPG_PUBLIC_KEY_ID}" 2>&1 gpg --keyserver "${FAIL2BAN_GPG_PUBLIC_KEY_SERVER}" --recv-keys "${FAIL2BAN_GPG_PUBLIC_KEY_ID}" 2>&1

View File

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

View File

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

View File

@ -111,6 +111,14 @@ function _rspamd_handle_user_modules_adjustments() {
fi 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 if [[ -f "${RSPAMD_DMS_CUSTOM_COMMANDS_F}" ]]; then
__rspamd__log 'debug' "Found file '${RSPAMD_DMS_CUSTOM_COMMANDS_F}' - parsing and applying it" __rspamd__log 'debug' "Found file '${RSPAMD_DMS_CUSTOM_COMMANDS_F}' - parsing and applying it"

View File

@ -122,7 +122,7 @@ function _reload_postfix() {
# you can set the environment variable `POSTFIX_README_DIRECTORY='/new/dir/'` # you can set the environment variable `POSTFIX_README_DIRECTORY='/new/dir/'`
# (`POSTFIX_` is an arbitrary prefix, you can choose the one you like), # (`POSTFIX_` is an arbitrary prefix, you can choose the one you like),
# and then call this function: # and then call this function:
# `_replace_by_env_in_file 'POSTFIX_' '<PATH TO POSTFIX's main.cf>` # `_replace_by_env_in_file 'POSTFIX_' 'PATH TO POSTFIX's main.cf>`
# #
# ## Panics # ## Panics
# #
@ -131,9 +131,9 @@ function _reload_postfix() {
# 1. No first and second argument is supplied # 1. No first and second argument is supplied
# 2. The second argument is a path to a file that does not exist # 2. The second argument is a path to a file that does not exist
function _replace_by_env_in_file() { function _replace_by_env_in_file() {
if [[ -z ${1:-} ]]; then if [[ -z ${1+set} ]]; then
_dms_panic__invalid_value 'first argument unset' 'utils.sh:_replace_by_env_in_file' _dms_panic__invalid_value 'first argument unset' 'utils.sh:_replace_by_env_in_file'
elif [[ -z ${2:-} ]]; then elif [[ -z ${2+set} ]]; then
_dms_panic__invalid_value 'second argument unset' 'utils.sh:_replace_by_env_in_file' _dms_panic__invalid_value 'second argument unset' 'utils.sh:_replace_by_env_in_file'
elif [[ ! -f ${2} ]]; then elif [[ ! -f ${2} ]]; then
_dms_panic__invalid_value "file '${2}' does not exist" 'utils.sh:_replace_by_env_in_file' _dms_panic__invalid_value "file '${2}' does not exist" 'utils.sh:_replace_by_env_in_file'

View File

@ -38,11 +38,13 @@ function _register_functions() {
# ? >> Checks # ? >> Checks
_register_check_function '_check_hostname' _register_check_function '_check_hostname'
_register_check_function '_check_log_level'
_register_check_function '_check_spam_prefix' _register_check_function '_check_spam_prefix'
# ? >> Setup # ? >> Setup
_register_setup_function '_setup_vmail_id' _register_setup_function '_setup_vmail_id'
_register_setup_function '_setup_logs_general'
_register_setup_function '_setup_timezone' _register_setup_function '_setup_timezone'
if [[ ${SMTP_ONLY} -ne 1 ]]; then if [[ ${SMTP_ONLY} -ne 1 ]]; then
@ -61,6 +63,7 @@ function _register_functions() {
;; ;;
( 'LDAP' ) ( 'LDAP' )
_environment_variables_ldap
_register_setup_function '_setup_ldap' _register_setup_function '_setup_ldap'
;; ;;
@ -73,8 +76,15 @@ function _register_functions() {
;; ;;
esac esac
[[ ${ENABLE_OAUTH2} -eq 1 ]] && _register_setup_function '_setup_oauth2' if [[ ${ENABLE_OAUTH2} -eq 1 ]]; then
[[ ${ENABLE_SASLAUTHD} -eq 1 ]] && _register_setup_function '_setup_saslauthd' _environment_variables_oauth2
_register_setup_function '_setup_oauth2'
fi
if [[ ${ENABLE_SASLAUTHD} -eq 1 ]]; then
_environment_variables_saslauthd
_register_setup_function '_setup_saslauthd'
fi
_register_setup_function '_setup_dovecot_inet_protocols' _register_setup_function '_setup_dovecot_inet_protocols'
@ -88,6 +98,7 @@ function _register_functions() {
_register_setup_function '_setup_ssl' _register_setup_function '_setup_ssl'
_register_setup_function '_setup_docker_permit' _register_setup_function '_setup_docker_permit'
_register_setup_function '_setup_mailname' _register_setup_function '_setup_mailname'
_register_setup_function '_setup_dovecot_hostname'
_register_setup_function '_setup_postfix_early' _register_setup_function '_setup_postfix_early'
@ -111,17 +122,14 @@ function _register_functions() {
_register_setup_function '_setup_logwatch' _register_setup_function '_setup_logwatch'
_register_setup_function '_setup_save_states' _register_setup_function '_setup_save_states'
_register_setup_function '_setup_adjust_state_permissions' _register_setup_function '_setup_apply_fixes_after_configuration'
_register_setup_function '_environment_variables_export'
if [[ ${ENABLE_MTA_STS} -eq 1 ]]; then if [[ ${ENABLE_MTA_STS} -eq 1 ]]; then
_register_setup_function '_setup_mta_sts' _register_setup_function '_setup_mta_sts'
_register_start_daemon '_start_daemon_mta_sts_daemon' _register_start_daemon '_start_daemon_mta_sts_daemon'
fi fi
# ! The following functions must be executed after all other setup functions
_register_setup_function '_setup_directory_and_file_permissions'
_register_setup_function '_setup_run_user_patches'
# ? >> Daemons # ? >> Daemons
_register_start_daemon '_start_daemon_cron' _register_start_daemon '_start_daemon_cron'
@ -166,6 +174,9 @@ function _register_functions() {
# ? >> Executing all stacks / actual start of DMS # ? >> Executing all stacks / actual start of DMS
# ------------------------------------------------------------ # ------------------------------------------------------------
# Ensure DMS only adjusts config files for a new container.
# Container restarts should skip as they retain the modified config.
if [[ ! -f /CONTAINER_START ]]; then
_early_supervisor_setup _early_supervisor_setup
_early_variables_setup _early_variables_setup
@ -173,20 +184,16 @@ _log 'info' "Welcome to docker-mailserver ${DMS_RELEASE}"
_register_functions _register_functions
_check _check
# Ensure DMS only adjusts config files for a new container.
# Container restarts should skip as they retain the modified config.
if [[ -f /CONTAINER_START ]]; then
_log 'info' 'Container was restarted. Skipping most setup routines.'
# 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 _setup
_run_user_patches
else
# container was restarted
_early_variables_setup
_log 'info' 'Container was restarted. Skipping setup routines.'
_log 'info' "Welcome to docker-mailserver ${DMS_RELEASE}"
_register_functions
fi fi
# marker to check if container was restarted # marker to check if container was restarted

View File

@ -26,6 +26,24 @@ function _check_hostname() {
fi fi
} }
function _check_log_level() {
if [[ ${LOG_LEVEL} == 'trace' ]] \
|| [[ ${LOG_LEVEL} == 'debug' ]] \
|| [[ ${LOG_LEVEL} == 'info' ]] \
|| [[ ${LOG_LEVEL} == 'warn' ]] \
|| [[ ${LOG_LEVEL} == 'error' ]]
then
return 0
else
local DEFAULT_LOG_LEVEL='info'
_log 'warn' "Log level '${LOG_LEVEL}' is invalid (falling back to default '${DEFAULT_LOG_LEVEL}')"
# shellcheck disable=SC2034
VARS[LOG_LEVEL]="${DEFAULT_LOG_LEVEL}"
LOG_LEVEL="${DEFAULT_LOG_LEVEL}"
fi
}
function _check_spam_prefix() { function _check_spam_prefix() {
# This check should be independent of ENABLE_POP3 and ENABLE_IMAP # This check should be independent of ENABLE_POP3 and ENABLE_IMAP
if [[ ${MOVE_SPAM_TO_JUNK} -eq 0 ]] \ if [[ ${MOVE_SPAM_TO_JUNK} -eq 0 ]] \

View File

@ -82,9 +82,7 @@ function _setup_timezone() {
fi fi
} }
# Misc checks and fixes migrated here until next refactor: function _setup_apply_fixes_after_configuration() {
# NOTE: `start-mailserver.sh` runs this along with `mail-state.sh` during container restarts
function _setup_directory_and_file_permissions() {
_log 'trace' 'Removing leftover PID files from a stop/start' _log 'trace' 'Removing leftover PID files from a stop/start'
find /var/run/ -not -name 'supervisord.pid' -name '*.pid' -delete find /var/run/ -not -name 'supervisord.pid' -name '*.pid' -delete
touch /dev/shm/supervisor.sock touch /dev/shm/supervisor.sock
@ -103,11 +101,9 @@ function _setup_directory_and_file_permissions() {
_log 'debug' "Ensuring '${RSPAMD_DMS_DKIM_D}' is owned by '_rspamd:_rspamd'" _log 'debug' "Ensuring '${RSPAMD_DMS_DKIM_D}' is owned by '_rspamd:_rspamd'"
chown -R _rspamd:_rspamd "${RSPAMD_DMS_DKIM_D}" chown -R _rspamd:_rspamd "${RSPAMD_DMS_DKIM_D}"
fi fi
__log_fixes
} }
function _setup_run_user_patches() { function _run_user_patches() {
local USER_PATCHES='/tmp/docker-mailserver/user-patches.sh' local USER_PATCHES='/tmp/docker-mailserver/user-patches.sh'
if [[ -f ${USER_PATCHES} ]]; then if [[ -f ${USER_PATCHES} ]]; then
@ -117,32 +113,3 @@ function _setup_run_user_patches() {
_log 'trace' "No optional '${USER_PATCHES}' provided" _log 'trace' "No optional '${USER_PATCHES}' provided"
fi fi
} }
function __log_fixes() {
_log 'debug' 'Ensuring /var/log/mail owneership + permissions are correct'
# File/folder permissions are fine when using docker volumes, but may be wrong
# when file system folders are mounted into the container.
# Set the expected values and create missing folders/files just in case.
mkdir -p /var/log/{mail,supervisor}
# TODO: Remove these lines in a future release once concerns are resolved:
# https://github.com/docker-mailserver/docker-mailserver/pull/4370#issuecomment-2661762043
chown syslog:root /var/log/mail
if [[ ${ENABLE_CLAMAV} -eq 1 ]]; then
# TODO: Consider assigning /var/log/mail a writable non-root group for other processes like ClamAV?
# - Check if ClamAV is capable of creating files itself when they're missing?
# - Alternatively a symlink to /var/log/mail from the original intended location would allow write access
# as a user to the symlink location, while keeping ownership as root at /var/log/mail
# - `LogSyslog false` for clamd.conf + freshclam.conf could possibly be enabled instead of log files?
# However without better filtering in place (once Vector is adopted), this should be avoided.
touch /var/log/mail/{clamav,freshclam}.log
chown clamav:adm /var/log/mail/{clamav,freshclam}.log
fi
# Volume permissions should be corrected:
# https://github.com/docker-mailserver/docker-mailserver-helm/issues/137
chmod 755 /var/log/mail/
find /var/log/mail/ -type f -exec chmod 640 {} +
}

View File

@ -23,11 +23,7 @@ function _setup_opendkim() {
# check if any keys are available # check if any keys are available
if [[ -e /tmp/docker-mailserver/opendkim/KeyTable ]]; then if [[ -e /tmp/docker-mailserver/opendkim/KeyTable ]]; then
cp -a /tmp/docker-mailserver/opendkim/* /etc/opendkim/ 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/ chown -R opendkim:opendkim /etc/opendkim/
chmod -R 0700 /etc/opendkim/keys/ chmod -R 0700 /etc/opendkim/keys/
else else

View File

@ -3,54 +3,13 @@
function _setup_dovecot() { function _setup_dovecot() {
_log 'debug' 'Setting up Dovecot' _log 'debug' 'Setting up Dovecot'
# Protocol support
sedfile -i -e 's|include_try /usr/share/dovecot/protocols.d|include_try /etc/dovecot/protocols.d|g' /etc/dovecot/dovecot.conf
cp -a /usr/share/dovecot/protocols.d /etc/dovecot/ cp -a /usr/share/dovecot/protocols.d /etc/dovecot/
# Disable these protocols by default, they can be enabled later via ENV (ENABLE_POP3, ENABLE_IMAP, ENABLE_MANAGESIEVE) # disable pop3 (it will be eventually enabled later in the script, if requested)
mv /etc/dovecot/protocols.d/pop3d.protocol /etc/dovecot/protocols.d/pop3d.protocol.disab mv /etc/dovecot/protocols.d/pop3d.protocol /etc/dovecot/protocols.d/pop3d.protocol.disab
# disable imap (it will be eventually enabled later in the script, if requested)
mv /etc/dovecot/protocols.d/imapd.protocol /etc/dovecot/protocols.d/imapd.protocol.disab mv /etc/dovecot/protocols.d/imapd.protocol /etc/dovecot/protocols.d/imapd.protocol.disab
mv /etc/dovecot/protocols.d/managesieved.protocol /etc/dovecot/protocols.d/managesieved.protocol.disab mv /etc/dovecot/protocols.d/managesieved.protocol /etc/dovecot/protocols.d/managesieved.protocol.disab
sedfile -i 's|^postmaster_address = .*$|postmaster_address = '"${POSTMASTER_ADDRESS}"'|g' /etc/dovecot/conf.d/15-lda.conf
# NOTE: While Postfix will deliver to Dovecot via LMTP (Previously LDA until DMS v2),
# LDA may be used via other services like Getmail being configured to use /usr/lib/dovecot/deliver
# when mail does not need to go through Postfix.
# `mail_plugins` is scoped to the `protocol lda` config block of this file.
#
# TODO: `postmaster_address` + `hostname` appear to be for the general Dovecot config rather than LDA specific?
# https://doc.dovecot.org/2.3/settings/core/#core_setting-postmaster_address
# https://doc.dovecot.org/2.3/settings/core/#core_setting-hostname
# Dovecot 3.0 docs:
# https://doc.dovecot.org/main/core/summaries/settings.html#postmaster_address
# https://doc.dovecot.org/main/core/summaries/settings.html#postmaster_address
# https://doc.dovecot.org/main/core/config/delivery/lmtp.html#common-delivery-settings
# https://doc.dovecot.org/main/core/config/delivery/lda.html#common-delivery-settings
# https://doc.dovecot.org/main/core/config/sieve/submission.html#postmaster-address
# Shows config example with postmaster_address scoped in a `protocol lda { }` block:
# https://doc.dovecot.org/main/howto/virtual/simple_install.html#delivering-mails
#
# DMS initially copied Dovecot example configs, these were removed from Dovecot 2.4 onwards:
# https://github.com/dovecot/core/commit/5941699b277d762d98c202928cf5b5c8c70bc359
# In favor of a minimal config example:
# https://github.com/dovecot/core/commit/9a6a6aef35bb403fa96f0b5efdb0faff85b1471d
# 2.3 series example config:
# https://github.com/dovecot/core/blob/2.3.21.1/doc/example-config/conf.d/15-lda.conf
# Initial config files committed to DMS in April 2016:
# TODO: Consider housekeeping on config to only represent relevant changes/support by scripts
# https://github.com/docker-mailserver/docker-mailserver/commit/ee0d0853dd672488238eecb0ec2d26719ff45d7d
#
# TODO: `mail_plugins` appending `sieve` should probably be done for both `15-lda.conf` and `20-lmtp.conf`
# Presently DMS replaces the `20-lmtp.conf` from `dovecot-lmtpd` package with our own modified copy from 2016.
# The DMS variant only makes this one change to that file, thus we could adjust it as we do below for `15-lda.conf`
# Reference: https://github.com/docker-mailserver/docker-mailserver/pull/4350#issuecomment-2646736328
# shellcheck disable=SC2016
sedfile -i -r \
-e 's|^(\s*)#?(mail_plugins =).*|\1\2 $mail_plugins sieve|' \
-e 's|^#?(lda_mailbox_autocreate =).*|\1 yes|' \
-e 's|^#?(lda_mailbox_autosubscribe =).*|\1 yes|' \
-e "s|^#?(postmaster_address =).*|\1 ${POSTMASTER_ADDRESS}|" \
-e "s|^#?(hostname =).*|\1 ${HOSTNAME}|" \
/etc/dovecot/conf.d/15-lda.conf
if ! grep -q -E '^stats_writer_socket_path=' /etc/dovecot/dovecot.conf; then if ! grep -q -E '^stats_writer_socket_path=' /etc/dovecot/dovecot.conf; then
printf '\n%s\n' 'stats_writer_socket_path=' >>/etc/dovecot/dovecot.conf printf '\n%s\n' 'stats_writer_socket_path=' >>/etc/dovecot/dovecot.conf
@ -65,7 +24,6 @@ function _setup_dovecot() {
sedfile -i -E \ sedfile -i -E \
"s|^(mail_location =).*|\1 ${DOVECOT_MAILBOX_FORMAT}:/var/mail/%d/%n|" \ "s|^(mail_location =).*|\1 ${DOVECOT_MAILBOX_FORMAT}:/var/mail/%d/%n|" \
/etc/dovecot/conf.d/10-mail.conf /etc/dovecot/conf.d/10-mail.conf
_log 'trace' 'Enabling cron job for dbox purge' _log 'trace' 'Enabling cron job for dbox purge'
mv /etc/cron.d/dovecot-purge.disabled /etc/cron.d/dovecot-purge mv /etc/cron.d/dovecot-purge.disabled /etc/cron.d/dovecot-purge
chmod 644 /etc/cron.d/dovecot-purge chmod 644 /etc/cron.d/dovecot-purge
@ -97,12 +55,6 @@ function _setup_dovecot() {
[[ -f /tmp/docker-mailserver/dovecot.cf ]] && cp /tmp/docker-mailserver/dovecot.cf /etc/dovecot/local.conf [[ -f /tmp/docker-mailserver/dovecot.cf ]] && cp /tmp/docker-mailserver/dovecot.cf /etc/dovecot/local.conf
} }
# The `sieve` plugin is always enabled in DMS, this method handles user supplied sieve scripts + ManageSieve protocol
# NOTE: There is a related post-setup step for this sieve support handled at `_setup_post()` (setup-stack.sh)
# TODO: Improved sieve support may be needed in DMS to support this use-case:
# https://github.com/docker-mailserver/docker-mailserver/issues/3904
# TODO: Change detection support + refactor/DRY this sieve logic:
# https://github.com/orgs/docker-mailserver/discussions/2633#discussioncomment-11622955
function _setup_dovecot_sieve() { function _setup_dovecot_sieve() {
mkdir -p /usr/lib/dovecot/sieve-{filter,global,pipe} mkdir -p /usr/lib/dovecot/sieve-{filter,global,pipe}
mkdir -p /usr/lib/dovecot/sieve-global/{before,after} mkdir -p /usr/lib/dovecot/sieve-global/{before,after}
@ -240,3 +192,8 @@ function _setup_dovecot_inet_protocols() {
function _setup_dovecot_dhparam() { function _setup_dovecot_dhparam() {
_setup_dhparam 'Dovecot' '/etc/dovecot/dh.pem' _setup_dhparam 'Dovecot' '/etc/dovecot/dh.pem'
} }
function _setup_dovecot_hostname() {
_log 'debug' 'Applying hostname to Dovecot'
sedfile -i "s|^#hostname =.*$|hostname = '${HOSTNAME}'|g" /etc/dovecot/conf.d/15-lda.conf
}

View File

@ -1,5 +1,15 @@
#!/bin/bash #!/bin/bash
function _setup_logs_general() {
_log 'debug' 'Setting up general log files'
# File/folder permissions are fine when using docker volumes, but may be wrong
# when file system folders are mounted into the container.
# Set the expected values and create missing folders/files just in case.
mkdir -p /var/log/{mail,supervisor}
chown syslog:root /var/log/mail
}
function _setup_logrotate() { function _setup_logrotate() {
_log 'debug' 'Setting up logrotate' _log 'debug' 'Setting up logrotate'

View File

@ -1,24 +1,20 @@
#!/bin/bash #!/bin/bash
DMS_STATE_DIR='/var/mail-state'
# Consolidate all states into a single directory # Consolidate all states into a single directory
# (/var/mail-state) to allow persistence using docker volumes # (/var/mail-state) to allow persistence using docker volumes
function _setup_save_states() { function _setup_save_states() {
if [[ ! -d ${DMS_STATE_DIR} ]]; then local DEST DESTDIR STATEDIR SERVICEDIR SERVICEDIRS SERVICEFILE SERVICEFILES
_log 'debug' "'${DMS_STATE_DIR}' is not present - not consolidating state"
return 0
fi
_log 'debug' "Consolidating all state onto ${DMS_STATE_DIR}" STATEDIR='/var/mail-state'
local DEST SERVICEDIR SERVICEDIRS SERVICEFILE SERVICEFILES if [[ -d ${STATEDIR} ]]; then
_log 'debug' "Consolidating all state onto ${STATEDIR}"
# Always enabled features: # Always enabled features:
SERVICEDIRS=( SERVICEDIRS=(
'lib/logrotate' lib/logrotate
'lib/postfix' lib/postfix
'spool/postfix' spool/postfix
) )
# Only consolidate state for services that are enabled # Only consolidate state for services that are enabled
@ -40,10 +36,10 @@ function _setup_save_states() {
[[ ${ENABLE_SRS} -eq 1 ]] && SERVICEFILES+=('/etc/postsrsd.secret') [[ ${ENABLE_SRS} -eq 1 ]] && SERVICEFILES+=('/etc/postsrsd.secret')
for SERVICEFILE in "${SERVICEFILES[@]}"; do for SERVICEFILE in "${SERVICEFILES[@]}"; do
DEST="${DMS_STATE_DIR}/${SERVICEFILE}" DEST="${STATEDIR}/${SERVICEFILE}"
DESTDIR="${DEST%/*}"
# Append service parent dir(s) path to the state dir and ensure it exists: mkdir -p "${DESTDIR}"
mkdir -p "${DEST%/*}"
if [[ -f ${DEST} ]]; then if [[ -f ${DEST} ]]; then
_log 'trace' "Destination ${DEST} exists, linking ${SERVICEFILE} to it" _log 'trace' "Destination ${DEST} exists, linking ${SERVICEFILE} to it"
# Original content from image no longer relevant, remove it: # Original content from image no longer relevant, remove it:
@ -54,7 +50,7 @@ function _setup_save_states() {
mv "${SERVICEFILE}" "${DEST}" mv "${SERVICEFILE}" "${DEST}"
# Apply SELinux security context to match the state directory, so access # Apply SELinux security context to match the state directory, so access
# is not restricted to the current running container: # is not restricted to the current running container:
chcon -R --reference="${DMS_STATE_DIR}" "${DEST}" 2>/dev/null || true chcon -R --reference="${STATEDIR}" "${DEST}" 2>/dev/null || true
fi fi
# Symlink the original file in the container ($SERVICEFILE) to be # Symlink the original file in the container ($SERVICEFILE) to be
@ -63,7 +59,7 @@ function _setup_save_states() {
done done
for SERVICEDIR in "${SERVICEDIRS[@]}"; do for SERVICEDIR in "${SERVICEDIRS[@]}"; do
DEST="${DMS_STATE_DIR}/${SERVICEDIR//\//-}" DEST="${STATEDIR}/${SERVICEDIR//\//-}"
SERVICEDIR="/var/${SERVICEDIR}" SERVICEDIR="/var/${SERVICEDIR}"
# If relevant content is found in /var/mail-state (presumably a volume mount), # If relevant content is found in /var/mail-state (presumably a volume mount),
@ -78,45 +74,33 @@ function _setup_save_states() {
mv "${SERVICEDIR}" "${DEST}" mv "${SERVICEDIR}" "${DEST}"
# Apply SELinux security context to match the state directory, so access # Apply SELinux security context to match the state directory, so access
# is not restricted to the current running container: # is not restricted to the current running container:
# https://github.com/docker-mailserver/docker-mailserver/pull/3890 chcon -R --reference="${STATEDIR}" "${DEST}" 2>/dev/null || true
chcon -R --reference="${DMS_STATE_DIR}" "${DEST}" 2>/dev/null || true
else else
_log 'error' "${SERVICEDIR} should exist but is missing" _log 'error' "${SERVICEDIR} should exist but is missing"
fi fi
# Symlink the original path in the container ($SERVICEDIR) to be # Symlink the original path in the container ($SERVICEDIR) to be
# sourced from associated path in /var/mail-state/ ($DEST): # sourced from assocaiated path in /var/mail-state/ ($DEST):
ln -s "${DEST}" "${SERVICEDIR}" ln -s "${DEST}" "${SERVICEDIR}"
done done
}
# These corrections are to fix changes to UID/GID values between upgrades,
# or when ownership/permissions were altered externally on the host (eg: migration or system scripts)
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 # 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 # 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. # can change the values in the Docker image, causing an ownership mismatch.
# NOTE: More details about users and groups added during image builds are documented here: # NOTE: More details about users and groups added during image builds are documented here:
# https://github.com/docker-mailserver/docker-mailserver/pull/3011#issuecomment-1399120252 # https://github.com/docker-mailserver/docker-mailserver/pull/3011#issuecomment-1399120252
_log 'trace' "Ensuring correct ownership + permissions for DMS state dir: '${DMS_STATE_DIR}'" _log 'trace' "Fixing ${STATEDIR}/* permissions"
[[ ${ENABLE_AMAVIS} -eq 1 ]] && chown -R amavis:amavis "${DMS_STATE_DIR}/lib-amavis" [[ ${ENABLE_AMAVIS} -eq 1 ]] && chown -R amavis:amavis "${STATEDIR}/lib-amavis"
[[ ${ENABLE_CLAMAV} -eq 1 ]] && chown -R clamav:clamav "${DMS_STATE_DIR}/lib-clamav" [[ ${ENABLE_CLAMAV} -eq 1 ]] && chown -R clamav:clamav "${STATEDIR}/lib-clamav"
[[ ${ENABLE_FETCHMAIL} -eq 1 ]] && chown -R fetchmail:nogroup "${DMS_STATE_DIR}/lib-fetchmail" [[ ${ENABLE_FETCHMAIL} -eq 1 ]] && chown -R fetchmail:nogroup "${STATEDIR}/lib-fetchmail"
[[ ${ENABLE_MTA_STS} -eq 1 ]] && chown -R _mta-sts:_mta-sts "${DMS_STATE_DIR}/lib-mta-sts" [[ ${ENABLE_MTA_STS} -eq 1 ]] && chown -R _mta-sts:_mta-sts "${STATEDIR}/lib-mta-sts"
[[ ${ENABLE_POSTGREY} -eq 1 ]] && chown -R postgrey:postgrey "${DMS_STATE_DIR}/lib-postgrey" [[ ${ENABLE_POSTGREY} -eq 1 ]] && chown -R postgrey:postgrey "${STATEDIR}/lib-postgrey"
[[ ${ENABLE_RSPAMD} -eq 1 ]] && chown -R _rspamd:_rspamd "${DMS_STATE_DIR}/lib-rspamd" [[ ${ENABLE_RSPAMD} -eq 1 ]] && chown -R _rspamd:_rspamd "${STATEDIR}/lib-rspamd"
[[ ${ENABLE_RSPAMD_REDIS} -eq 1 ]] && chown -R redis:redis "${DMS_STATE_DIR}/lib-redis" [[ ${ENABLE_RSPAMD_REDIS} -eq 1 ]] && chown -R redis:redis "${STATEDIR}/lib-redis"
[[ ${ENABLE_SPAMASSASSIN} -eq 1 ]] && chown -R debian-spamd:debian-spamd "${DMS_STATE_DIR}/lib-spamassassin" [[ ${ENABLE_SPAMASSASSIN} -eq 1 ]] && chown -R debian-spamd:debian-spamd "${STATEDIR}/lib-spamassassin"
chown -R root:root "${DMS_STATE_DIR}/lib-logrotate" chown -R root:root "${STATEDIR}/lib-logrotate"
chown -R postfix:postfix "${DMS_STATE_DIR}/lib-postfix" chown -R postfix:postfix "${STATEDIR}/lib-postfix"
# NOTE: The Postfix spool location has mixed owner/groups to take into account: # NOTE: The Postfix spool location has mixed owner/groups to take into account:
# UID = postfix(101): active, bounce, corrupt, defer, deferred, flush, hold, incoming, maildrop, private, public, saved, trace # UID = postfix(101): active, bounce, corrupt, defer, deferred, flush, hold, incoming, maildrop, private, public, saved, trace
@ -125,14 +109,17 @@ function _setup_adjust_state_permissions() {
# GID for all other directories is root(0) # GID for all other directories is root(0)
# NOTE: `spool-postfix/private/` will be set to `postfix:postfix` when Postfix starts / restarts # NOTE: `spool-postfix/private/` will be set to `postfix:postfix` when Postfix starts / restarts
# Set most common ownership: # Set most common ownership:
chown -R postfix:root "${DMS_STATE_DIR}/spool-postfix" chown -R postfix:root "${STATEDIR}/spool-postfix"
chown root:root "${DMS_STATE_DIR}/spool-postfix" chown root:root "${STATEDIR}/spool-postfix"
# These two require the postdrop(103) group: # These two require the postdrop(103) group:
chgrp -R postdrop "${DMS_STATE_DIR}"/spool-postfix/{maildrop,public} chgrp -R postdrop "${STATEDIR}"/spool-postfix/{maildrop,public}
# These permissions rely on the `postdrop` binary having the SGID bit set. # These permissions rely on the `postdrop` binary having the SGID bit set.
# Ref: https://github.com/docker-mailserver/docker-mailserver/pull/3625 # Ref: https://github.com/docker-mailserver/docker-mailserver/pull/3625
chmod 730 "${DMS_STATE_DIR}/spool-postfix/maildrop" chmod 730 "${STATEDIR}/spool-postfix/maildrop"
chmod 710 "${DMS_STATE_DIR}/spool-postfix/public" chmod 710 "${STATEDIR}/spool-postfix/public"
else
_log 'debug' "'${STATEDIR}' is not present; Not consolidating state"
fi
} }

View File

@ -93,19 +93,13 @@ EOF
function _setup_postfix_late() { function _setup_postfix_late() {
_log 'debug' 'Configuring Postfix (late setup)' _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' __postfix__log 'trace' 'Configuring user access'
if [[ -f /tmp/docker-mailserver/postfix-send-access.cf ]]; then if [[ -f /tmp/docker-mailserver/postfix-send-access.cf ]]; then
# Prefer to prepend to our specialized variant instead: sed -i -E 's|(smtpd_sender_restrictions =)|\1 check_sender_access texthash:/tmp/docker-mailserver/postfix-send-access.cf,|' /etc/postfix/main.cf
# 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 fi
if [[ -f /tmp/docker-mailserver/postfix-receive-access.cf ]]; then 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 fi
__postfix__log 'trace' 'Configuring relay host' __postfix__log 'trace' 'Configuring relay host'

View File

@ -155,6 +155,13 @@ function __setup__security__clamav() {
if [[ ${ENABLE_CLAMAV} -eq 1 ]]; then if [[ ${ENABLE_CLAMAV} -eq 1 ]]; then
_log 'debug' 'Enabling and configuring ClamAV' _log 'debug' 'Enabling and configuring ClamAV'
local FILE
for FILE in /var/log/mail/{clamav,freshclam}.log; do
touch "${FILE}"
chown clamav:adm "${FILE}"
chmod 640 "${FILE}"
done
if [[ ${CLAMAV_MESSAGE_SIZE_LIMIT} != '25M' ]]; then if [[ ${CLAMAV_MESSAGE_SIZE_LIMIT} != '25M' ]]; then
_log 'trace' "Setting ClamAV message scan size limit to '${CLAMAV_MESSAGE_SIZE_LIMIT}'" _log 'trace' "Setting ClamAV message scan size limit to '${CLAMAV_MESSAGE_SIZE_LIMIT}'"

View File

@ -330,9 +330,9 @@ function __rspamd__setup_check_authenticated() {
fi fi
} }
# This function performs a simple check on the queried rspamd DKIM configuration: # This function performs a simple check: go through DKIM configuration files, acquire
# - Acquire all private key file locations and check whether they exist and can be accessed by Rspamd. # all private key file locations and check whether they exist and whether they can be
# - We are not checking paths that contain the '$' symbol. # accessed by Rspamd. We are not checking paths that conatain the '$' symbol.
function __rspamd__check_dkim_permissions() { function __rspamd__check_dkim_permissions() {
local KEY_FILE local KEY_FILE
while read -r KEY_FILE; do while read -r KEY_FILE; do

View File

@ -4,16 +4,9 @@
declare -A VARS declare -A VARS
function _early_variables_setup() { function _early_variables_setup() {
__environment_variables_log_level
_obtain_hostname_and_domainname _obtain_hostname_and_domainname
__environment_variables_backwards_compatibility __environment_variables_backwards_compatibility
__environment_variables_general_setup __environment_variables_general_setup
[[ ${ACCOUNT_PROVISIONER} == 'LDAP' ]] && __environment_variables_ldap
[[ ${ENABLE_OAUTH2} -eq 1 ]] && __environment_variables_oauth2
[[ ${ENABLE_SASLAUTHD} -eq 1 ]] && __environment_variables_saslauthd
__environment_variables_export
} }
# This function handles variables that are deprecated. This allows a # This function handles variables that are deprecated. This allows a
@ -62,8 +55,6 @@ function __environment_variables_general_setup() {
VARS[DMS_VMAIL_UID]="${DMS_VMAIL_UID:=5000}" VARS[DMS_VMAIL_UID]="${DMS_VMAIL_UID:=5000}"
VARS[DMS_VMAIL_GID]="${DMS_VMAIL_GID:=5000}" VARS[DMS_VMAIL_GID]="${DMS_VMAIL_GID:=5000}"
# user-customizable are last
_log 'trace' 'Setting anti-spam & anti-virus environment variables' _log 'trace' 'Setting anti-spam & anti-virus environment variables'
VARS[AMAVIS_LOGLEVEL]="${AMAVIS_LOGLEVEL:=0}" VARS[AMAVIS_LOGLEVEL]="${AMAVIS_LOGLEVEL:=0}"
@ -168,27 +159,15 @@ function __environment_variables_general_setup() {
VARS[UPDATE_CHECK_INTERVAL]="${UPDATE_CHECK_INTERVAL:=1d}" VARS[UPDATE_CHECK_INTERVAL]="${UPDATE_CHECK_INTERVAL:=1d}"
} }
function __environment_variables_log_level() { function _environment_variables_oauth2() {
if [[ ${LOG_LEVEL} == 'trace' ]] \ _log 'debug' 'Setting OAUTH2-related environment variables now'
|| [[ ${LOG_LEVEL} == 'debug' ]] \
|| [[ ${LOG_LEVEL} == 'info' ]] \
|| [[ ${LOG_LEVEL} == 'warn' ]] \
|| [[ ${LOG_LEVEL} == 'error' ]]
then
return 0
else
local DEFAULT_LOG_LEVEL='info'
_log 'warn' "Log level '${LOG_LEVEL}' is invalid (falling back to default '${DEFAULT_LOG_LEVEL}')"
# shellcheck disable=SC2034 VARS[OAUTH2_INTROSPECTION_URL]="${OAUTH2_INTROSPECTION_URL:=}"
VARS[LOG_LEVEL]="${DEFAULT_LOG_LEVEL}"
LOG_LEVEL="${DEFAULT_LOG_LEVEL}"
fi
} }
# This function handles environment variables related to LDAP. # This function handles environment variables related to LDAP.
# NOTE: SASLAuthd and Dovecot LDAP support inherit these common ENV. # NOTE: SASLAuthd and Dovecot LDAP support inherit these common ENV.
function __environment_variables_ldap() { function _environment_variables_ldap() {
_log 'debug' 'Setting LDAP-related environment variables now' _log 'debug' 'Setting LDAP-related environment variables now'
VARS[LDAP_BIND_DN]="${LDAP_BIND_DN:=}" VARS[LDAP_BIND_DN]="${LDAP_BIND_DN:=}"
@ -198,26 +177,19 @@ function __environment_variables_ldap() {
VARS[LDAP_START_TLS]="${LDAP_START_TLS:=no}" VARS[LDAP_START_TLS]="${LDAP_START_TLS:=no}"
} }
function __environment_variables_oauth2() {
_log 'debug' 'Setting OAUTH2-related environment variables now'
VARS[OAUTH2_INTROSPECTION_URL]="${OAUTH2_INTROSPECTION_URL:=}"
}
# This function handles environment variables related to SASLAUTHD # This function handles environment variables related to SASLAUTHD
# LDAP specific ENV handled in: `startup/setup.d/saslauthd.sh:_setup_saslauthd()` # LDAP specific ENV handled in: `startup/setup.d/saslauthd.sh:_setup_saslauthd()`
function __environment_variables_saslauthd() { function _environment_variables_saslauthd() {
_log 'debug' 'Setting SASLAUTHD-related environment variables now' _log 'debug' 'Setting SASLAUTHD-related environment variables now'
# This ENV is only used by the supervisor service config `saslauth.conf`: # Only used by the supervisor service command (upstream default: `/etc/default/saslauthd`)
# NOTE: `pam` is set as the upstream default in `/etc/default/saslauthd` VARS[SASLAUTHD_MECHANISMS]="${SASLAUTHD_MECHANISMS:=pam}"
VARS[SASLAUTHD_MECHANISMS]="${SASLAUTHD_MECHANISMS:=ldap}"
} }
# This function Writes the contents of the `VARS` map (associative array) # This function Writes the contents of the `VARS` map (associative array)
# to locations where they can be sourced from (e.g. `/etc/dms-settings`) # to locations where they can be sourced from (e.g. `/etc/dms-settings`)
# or where they can be used by Bash directly (e.g. `/root/.bashrc`). # or where they can be used by Bash directly (e.g. `/root/.bashrc`).
function __environment_variables_export() { function _environment_variables_export() {
_log 'debug' "Exporting environment variables now (creating '/etc/dms-settings')" _log 'debug' "Exporting environment variables now (creating '/etc/dms-settings')"
: >/root/.bashrc # make DMS variables available in login shells and their subprocesses : >/root/.bashrc # make DMS variables available in login shells and their subprocesses

View File

@ -7,6 +7,24 @@ stderr_logfile=/var/log/supervisor/%(program_name)s.log
command=/usr/sbin/saslauthd -d -a ldap -O /etc/saslauthd.conf command=/usr/sbin/saslauthd -d -a ldap -O /etc/saslauthd.conf
pidfile=/var/run/saslauthd/saslauthd.pid pidfile=/var/run/saslauthd/saslauthd.pid
[program:saslauthd_mysql]
startsecs=0
autostart=false
autorestart=true
stdout_logfile=/var/log/supervisor/%(program_name)s.log
stderr_logfile=/var/log/supervisor/%(program_name)s.log
command=/usr/sbin/saslauthd -d -a mysql -O "%(ENV_SASLAUTHD_MECH_OPTIONS)s"
pidfile=/var/run/saslauthd/saslauthd.pid
[program:saslauthd_pam]
startsecs=0
autostart=false
autorestart=true
stdout_logfile=/var/log/supervisor/%(program_name)s.log
stderr_logfile=/var/log/supervisor/%(program_name)s.log
command=/usr/sbin/saslauthd -d -a pam -O "%(ENV_SASLAUTHD_MECH_OPTIONS)s"
pidfile=/var/run/saslauthd/saslauthd.pid
[program:saslauthd_rimap] [program:saslauthd_rimap]
startsecs=0 startsecs=0
autostart=false autostart=false
@ -15,3 +33,13 @@ stdout_logfile=/var/log/supervisor/%(program_name)s.log
stderr_logfile=/var/log/supervisor/%(program_name)s.log stderr_logfile=/var/log/supervisor/%(program_name)s.log
command=/usr/sbin/saslauthd -d -a rimap -r -O "%(ENV_SASLAUTHD_MECH_OPTIONS)s" command=/usr/sbin/saslauthd -d -a rimap -r -O "%(ENV_SASLAUTHD_MECH_OPTIONS)s"
pidfile=/var/run/saslauthd/saslauthd.pid pidfile=/var/run/saslauthd/saslauthd.pid
[program:saslauthd_shadow]
startsecs=0
autostart=false
autorestart=true
stdout_logfile=/var/log/supervisor/%(program_name)s.log
stderr_logfile=/var/log/supervisor/%(program_name)s.log
command=/usr/sbin/saslauthd -d -a shadow -O "%(ENV_SASLAUTHD_MECH_OPTIONS)s"
pidfile=/var/run/saslauthd/saslauthd.pid

View File

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

View File

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

View File

@ -234,9 +234,8 @@ function _should_have_correct_mail_headers() {
# but Amavis is changing that. It also changes protocol from SMTP to ESMTP. # 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 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 8 --partial "by ${EXPECTED_FQDN} (Postfix) with ESMTP id"
assert_line --index 14 'X-MS-Reactions: disallow' assert_line --index 14 --partial 'Message-Id:'
assert_line --index 15 --partial 'Message-Id:' assert_line --index 14 --partial "@${EXPECTED_FQDN}>"
assert_line --index 15 --partial "@${EXPECTED_FQDN}>"
# Mail contents example: # Mail contents example:
# #

View File

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